@workscribe/cli 1.7.2 → 1.8.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 +195 -116
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,8 @@ workscribe summary --webhook https://custom.endpoint/hook # one-
|
|
|
148
148
|
| `ai.baseUrl` | — | Base URL for `openai-compatible` providers |
|
|
149
149
|
| `session.idleTimeout` | `30` | Minutes of inactivity before a new session starts |
|
|
150
150
|
| `capture.ignore` | common shell builtins | Commands to exclude from capture |
|
|
151
|
+
| `capture.aiTools` | `true` | Set to `false` to disable all AI session capture |
|
|
152
|
+
| `capture.includeAiderPrompt` | `true` | Set to `false` to skip reading Aider's history file for prompt context |
|
|
151
153
|
| `redact.extra` | `[]` | Additional regex patterns to redact from captured commands |
|
|
152
154
|
| `summary.format` | `markdown` | Output format: `markdown` or `json` |
|
|
153
155
|
| `integrations.slackWebhook` | — | Slack incoming webhook URL |
|
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");
|
|
@@ -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 {
|
|
@@ -596,7 +666,14 @@ var RULES = [
|
|
|
596
666
|
makeRule(/^(mkdir|touch|ln)\b/, "file_ops", (m) => ({ tool: m[1] })),
|
|
597
667
|
makeRule(/^(chmod|chown|chgrp)\b/, "file_ops", (m) => ({ tool: m[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,20 @@ 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;
|
|
723
803
|
if (opts.source) metadata.source = opts.source;
|
|
724
|
-
if (category === "ai_session"
|
|
725
|
-
|
|
726
|
-
|
|
804
|
+
if (category === "ai_session") {
|
|
805
|
+
if (opts.gitBefore) {
|
|
806
|
+
const commits = getCommitsSince(opts.gitBefore, cwd);
|
|
807
|
+
if (commits.length > 0) metadata.commits = commits;
|
|
808
|
+
}
|
|
809
|
+
if (config.capture.includeAiderPrompt !== false) {
|
|
810
|
+
const toolEntry = loadAIToolRegistry().find((entry) => entry.tool === metadata.tool);
|
|
811
|
+
if (toolEntry?.contextSource === "history-file") {
|
|
812
|
+
const prompt = readLastAiderPrompt(cwd);
|
|
813
|
+
if (prompt) metadata.prompt = prompt;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
727
816
|
}
|
|
728
817
|
insertEvent({
|
|
729
818
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -738,27 +827,6 @@ function captureCommand(opts) {
|
|
|
738
827
|
});
|
|
739
828
|
}
|
|
740
829
|
|
|
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
830
|
// src/cli/capture.ts
|
|
763
831
|
function registerCapture(program2) {
|
|
764
832
|
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 +864,7 @@ function registerHook(program2) {
|
|
|
796
864
|
});
|
|
797
865
|
process.stdin.on("end", () => {
|
|
798
866
|
try {
|
|
867
|
+
if (readConfig().capture.aiTools === false) return;
|
|
799
868
|
const parsed = parseHookPayload(raw);
|
|
800
869
|
if (!parsed) return;
|
|
801
870
|
captureCommand({
|
|
@@ -813,21 +882,21 @@ function registerHook(program2) {
|
|
|
813
882
|
}
|
|
814
883
|
|
|
815
884
|
// src/cli/init.ts
|
|
816
|
-
var
|
|
817
|
-
var
|
|
818
|
-
var
|
|
885
|
+
var import_fs10 = __toESM(require("fs"));
|
|
886
|
+
var import_path7 = __toESM(require("path"));
|
|
887
|
+
var import_os4 = __toESM(require("os"));
|
|
819
888
|
var import_prompts = __toESM(require("prompts"));
|
|
820
889
|
var import_chalk2 = __toESM(require("chalk"));
|
|
821
890
|
|
|
822
891
|
// src/capture/shell-hook.ts
|
|
823
|
-
var
|
|
824
|
-
var
|
|
892
|
+
var import_fs8 = __toESM(require("fs"));
|
|
893
|
+
var import_path5 = __toESM(require("path"));
|
|
825
894
|
var HOOK_START = "# workscribe:hook:start";
|
|
826
895
|
var HOOK_END = "# workscribe:hook:end";
|
|
827
896
|
var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
|
|
828
897
|
function getInstalledHookBlock(rcFile) {
|
|
829
|
-
if (!
|
|
830
|
-
const content =
|
|
898
|
+
if (!import_fs8.default.existsSync(rcFile)) return null;
|
|
899
|
+
const content = import_fs8.default.readFileSync(rcFile, "utf-8");
|
|
831
900
|
const startIdx = content.indexOf(HOOK_START);
|
|
832
901
|
const endIdx = content.indexOf(HOOK_END);
|
|
833
902
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -843,39 +912,39 @@ function isHookOutdated(rcFile, hookContent) {
|
|
|
843
912
|
}
|
|
844
913
|
function installHook(rcFile, hookContent) {
|
|
845
914
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
846
|
-
|
|
847
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
848
|
-
|
|
915
|
+
import_fs8.default.copyFileSync(rcFile, backup);
|
|
916
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
|
|
917
|
+
import_fs8.default.appendFileSync(rcFile, `
|
|
849
918
|
${hookContent}
|
|
850
919
|
`);
|
|
851
920
|
}
|
|
852
921
|
function updateHook(rcFile, hookContent) {
|
|
853
922
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
854
|
-
|
|
855
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
856
|
-
const content =
|
|
923
|
+
import_fs8.default.copyFileSync(rcFile, backup);
|
|
924
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
|
|
925
|
+
const content = import_fs8.default.readFileSync(rcFile, "utf-8");
|
|
857
926
|
const startIdx = content.indexOf(HOOK_START);
|
|
858
927
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
859
|
-
|
|
928
|
+
import_fs8.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
|
|
860
929
|
}
|
|
861
930
|
function removeHook(rcFile) {
|
|
862
931
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
863
|
-
|
|
864
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
865
|
-
const content =
|
|
932
|
+
import_fs8.default.copyFileSync(rcFile, backup);
|
|
933
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path5.default.basename(backup)}`);
|
|
934
|
+
const content = import_fs8.default.readFileSync(rcFile, "utf-8");
|
|
866
935
|
const startIdx = content.indexOf(HOOK_START);
|
|
867
936
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
868
937
|
const preambleIdx = content.lastIndexOf(HOOK_PREAMBLE, startIdx);
|
|
869
938
|
const blockStart = preambleIdx !== -1 ? preambleIdx : startIdx;
|
|
870
939
|
const before = content.slice(0, blockStart).replace(/\n+$/, "\n");
|
|
871
940
|
const after = content.slice(endIdx).replace(/^\n/, "");
|
|
872
|
-
|
|
941
|
+
import_fs8.default.writeFileSync(rcFile, before + after);
|
|
873
942
|
}
|
|
874
943
|
|
|
875
944
|
// src/capture/hooks/claude-code.ts
|
|
876
|
-
var
|
|
877
|
-
var
|
|
878
|
-
var
|
|
945
|
+
var import_fs9 = __toESM(require("fs"));
|
|
946
|
+
var import_path6 = __toESM(require("path"));
|
|
947
|
+
var import_os3 = __toESM(require("os"));
|
|
879
948
|
|
|
880
949
|
// src/capture/hooks/utils.ts
|
|
881
950
|
var import_child_process3 = require("child_process");
|
|
@@ -889,11 +958,11 @@ function isCommandInstalled(cmd) {
|
|
|
889
958
|
}
|
|
890
959
|
|
|
891
960
|
// src/capture/hooks/claude-code.ts
|
|
892
|
-
var CLAUDE_SETTINGS_PATH =
|
|
961
|
+
var CLAUDE_SETTINGS_PATH = import_path6.default.join(import_os3.default.homedir(), ".claude", "settings.json");
|
|
893
962
|
function readSettings() {
|
|
894
|
-
if (!
|
|
963
|
+
if (!import_fs9.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
|
|
895
964
|
try {
|
|
896
|
-
return JSON.parse(
|
|
965
|
+
return JSON.parse(import_fs9.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
897
966
|
} catch {
|
|
898
967
|
return {};
|
|
899
968
|
}
|
|
@@ -928,8 +997,8 @@ var claudeCodeIntegration = {
|
|
|
928
997
|
}
|
|
929
998
|
]
|
|
930
999
|
};
|
|
931
|
-
|
|
932
|
-
|
|
1000
|
+
import_fs9.default.mkdirSync(import_path6.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
|
|
1001
|
+
import_fs9.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
933
1002
|
}
|
|
934
1003
|
};
|
|
935
1004
|
|
|
@@ -941,19 +1010,19 @@ var AI_HOOK_INTEGRATIONS = {
|
|
|
941
1010
|
// src/cli/init.ts
|
|
942
1011
|
function detectShell() {
|
|
943
1012
|
const shell = process.env.SHELL ?? "";
|
|
944
|
-
if (shell.includes("zsh")) return { name: "zsh", rcFile:
|
|
945
|
-
if (shell.includes("bash")) return { name: "bash", rcFile:
|
|
1013
|
+
if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path7.default.join(import_os4.default.homedir(), ".zshrc") };
|
|
1014
|
+
if (shell.includes("bash")) return { name: "bash", rcFile: import_path7.default.join(import_os4.default.homedir(), ".bashrc") };
|
|
946
1015
|
return null;
|
|
947
1016
|
}
|
|
948
1017
|
function registerInit(program2) {
|
|
949
1018
|
program2.command("init").description("Set up Workscribe and install or update shell hooks").action(async () => {
|
|
950
1019
|
log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
|
|
951
|
-
if (!
|
|
952
|
-
|
|
1020
|
+
if (!import_fs10.default.existsSync(WORKSCRIBE_DIR)) {
|
|
1021
|
+
import_fs10.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true, mode: 448 });
|
|
953
1022
|
log.success(`Created ${WORKSCRIBE_DIR}`);
|
|
954
1023
|
} else {
|
|
955
1024
|
try {
|
|
956
|
-
|
|
1025
|
+
import_fs10.default.chmodSync(WORKSCRIBE_DIR, 448);
|
|
957
1026
|
} catch {
|
|
958
1027
|
}
|
|
959
1028
|
}
|
|
@@ -964,12 +1033,12 @@ function registerInit(program2) {
|
|
|
964
1033
|
log.warn("Could not detect shell. Supported: zsh, bash");
|
|
965
1034
|
log.plain(`Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")} or ${import_chalk2.default.cyan("shell/hook.bash")}`);
|
|
966
1035
|
} else {
|
|
967
|
-
const hookFile =
|
|
968
|
-
if (!
|
|
1036
|
+
const hookFile = import_path7.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
|
|
1037
|
+
if (!import_fs10.default.existsSync(hookFile)) {
|
|
969
1038
|
log.warn(`Hook template not found at ${hookFile}`);
|
|
970
1039
|
} else {
|
|
971
|
-
const hookContent =
|
|
972
|
-
if (!
|
|
1040
|
+
const hookContent = import_fs10.default.readFileSync(hookFile, "utf-8");
|
|
1041
|
+
if (!import_fs10.default.existsSync(shell.rcFile)) import_fs10.default.writeFileSync(shell.rcFile, "");
|
|
973
1042
|
if (!isHookInstalled(shell.rcFile)) {
|
|
974
1043
|
installHook(shell.rcFile, hookContent);
|
|
975
1044
|
log.success(`Shell hook installed in ${shell.rcFile}`);
|
|
@@ -991,21 +1060,29 @@ function registerInit(program2) {
|
|
|
991
1060
|
}
|
|
992
1061
|
}
|
|
993
1062
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1063
|
+
const installedTools = loadAIToolRegistry().filter(
|
|
1064
|
+
(t) => t.hook !== null && AI_HOOK_INTEGRATIONS[t.hook]?.isInstalled()
|
|
1065
|
+
);
|
|
1066
|
+
if (installedTools.length === 0) {
|
|
1067
|
+
log.info("No AI coding tools detected \u2014 re-run workscribe init after installing one");
|
|
1068
|
+
} else {
|
|
1069
|
+
for (const tool of installedTools) {
|
|
1070
|
+
const integration = AI_HOOK_INTEGRATIONS[tool.hook];
|
|
1071
|
+
if (integration.isHookInstalled()) {
|
|
1072
|
+
log.info(`${integration.displayName} session hook already installed`);
|
|
1073
|
+
} else {
|
|
1074
|
+
const { install } = await (0, import_prompts.default)({
|
|
1075
|
+
type: "confirm",
|
|
1076
|
+
name: "install",
|
|
1077
|
+
message: `${integration.displayName} detected. Install session capture hook?`,
|
|
1078
|
+
initial: false
|
|
1079
|
+
});
|
|
1080
|
+
if (install) {
|
|
1081
|
+
integration.install();
|
|
1082
|
+
log.success(`${integration.displayName} session hook installed`);
|
|
1083
|
+
} else {
|
|
1084
|
+
log.info(`${integration.displayName} hook skipped \u2014 re-run workscribe init to install later`);
|
|
1085
|
+
}
|
|
1009
1086
|
}
|
|
1010
1087
|
}
|
|
1011
1088
|
}
|
|
@@ -1057,7 +1134,7 @@ function registerInit(program2) {
|
|
|
1057
1134
|
log.info("Skipped \u2014 run later: workscribe config set ai.provider <provider>");
|
|
1058
1135
|
}
|
|
1059
1136
|
}
|
|
1060
|
-
if (!
|
|
1137
|
+
if (!import_fs10.default.existsSync(CONFIG_PATH)) {
|
|
1061
1138
|
writeConfig(config);
|
|
1062
1139
|
log.success("Config file created");
|
|
1063
1140
|
}
|
|
@@ -1076,7 +1153,7 @@ function registerInit(program2) {
|
|
|
1076
1153
|
}
|
|
1077
1154
|
|
|
1078
1155
|
// src/cli/status.ts
|
|
1079
|
-
var
|
|
1156
|
+
var import_fs11 = __toESM(require("fs"));
|
|
1080
1157
|
var import_chalk3 = __toESM(require("chalk"));
|
|
1081
1158
|
|
|
1082
1159
|
// src/utils/format.ts
|
|
@@ -1134,7 +1211,7 @@ function registerStatus(program2) {
|
|
|
1134
1211
|
const total = getEventCount();
|
|
1135
1212
|
const todayEvents = getEventsForDate(todayDate());
|
|
1136
1213
|
const last = getLastEvent();
|
|
1137
|
-
const isPaused =
|
|
1214
|
+
const isPaused = import_fs11.default.existsSync(PAUSE_FLAG_PATH);
|
|
1138
1215
|
log.plain("");
|
|
1139
1216
|
log.plain(import_chalk3.default.bold(" Workscribe Status"));
|
|
1140
1217
|
log.plain("");
|
|
@@ -1182,7 +1259,9 @@ var CATEGORY_COLORS = {
|
|
|
1182
1259
|
file_ops: import_chalk4.default.yellow,
|
|
1183
1260
|
other: import_chalk4.default.gray
|
|
1184
1261
|
};
|
|
1185
|
-
var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
|
|
1262
|
+
var AI_TOOL_DISPLAY_NAMES = Object.fromEntries(
|
|
1263
|
+
loadAIToolRegistry().map((t) => [t.tool, t.displayName])
|
|
1264
|
+
);
|
|
1186
1265
|
function registerEvents(program2) {
|
|
1187
1266
|
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
1267
|
let date = todayDate();
|
|
@@ -1370,9 +1449,9 @@ function registerSessions(program2) {
|
|
|
1370
1449
|
|
|
1371
1450
|
// src/cli/summary.ts
|
|
1372
1451
|
var import_chalk6 = __toESM(require("chalk"));
|
|
1373
|
-
var
|
|
1374
|
-
var
|
|
1375
|
-
var
|
|
1452
|
+
var import_fs12 = __toESM(require("fs"));
|
|
1453
|
+
var import_os5 = __toESM(require("os"));
|
|
1454
|
+
var import_path8 = __toESM(require("path"));
|
|
1376
1455
|
|
|
1377
1456
|
// src/ai/prompt.ts
|
|
1378
1457
|
var SYSTEM_PROMPT = `You write short developer work summaries. Here is an example:
|
|
@@ -1767,7 +1846,7 @@ async function handleWebhook(opts, config, summary) {
|
|
|
1767
1846
|
if (webhookUrl) {
|
|
1768
1847
|
try {
|
|
1769
1848
|
log.info("Posting to webhook\u2026");
|
|
1770
|
-
const username =
|
|
1849
|
+
const username = import_os5.default.userInfo().username;
|
|
1771
1850
|
const body = isSlackWebhookUrl(webhookUrl) ? formatForSlack(summary, username) : summary;
|
|
1772
1851
|
await postToWebhook(webhookUrl, body);
|
|
1773
1852
|
log.success("Posted.");
|
|
@@ -1804,9 +1883,9 @@ function registerSummary(program2) {
|
|
|
1804
1883
|
return;
|
|
1805
1884
|
}
|
|
1806
1885
|
const cacheKey = isoWeekKey(today);
|
|
1807
|
-
const cacheFile =
|
|
1808
|
-
if (!opts.fresh &&
|
|
1809
|
-
const cached =
|
|
1886
|
+
const cacheFile = import_path8.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
|
|
1887
|
+
if (!opts.fresh && import_fs12.default.existsSync(cacheFile)) {
|
|
1888
|
+
const cached = import_fs12.default.readFileSync(cacheFile, "utf-8");
|
|
1810
1889
|
log.plain("");
|
|
1811
1890
|
log.plain(cached);
|
|
1812
1891
|
log.plain("");
|
|
@@ -1822,8 +1901,8 @@ function registerSummary(program2) {
|
|
|
1822
1901
|
log.plain("");
|
|
1823
1902
|
log.plain(summary);
|
|
1824
1903
|
log.plain("");
|
|
1825
|
-
|
|
1826
|
-
|
|
1904
|
+
import_fs12.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
|
|
1905
|
+
import_fs12.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
|
|
1827
1906
|
await handleWebhook(opts, config, summary);
|
|
1828
1907
|
} catch (e) {
|
|
1829
1908
|
log.error(`Weekly summary failed [${config.ai.provider}]: ${e.message}`);
|
|
@@ -1910,8 +1989,8 @@ ${bullets.trim()}`;
|
|
|
1910
1989
|
}
|
|
1911
1990
|
|
|
1912
1991
|
// src/cli/export.ts
|
|
1913
|
-
var
|
|
1914
|
-
var
|
|
1992
|
+
var import_fs13 = __toESM(require("fs"));
|
|
1993
|
+
var import_path9 = __toESM(require("path"));
|
|
1915
1994
|
function eventRowToPayload2(row) {
|
|
1916
1995
|
let metadata = {};
|
|
1917
1996
|
try {
|
|
@@ -1956,9 +2035,9 @@ function registerExport(program2) {
|
|
|
1956
2035
|
const payload = { date, sessions: sessionPayloads };
|
|
1957
2036
|
log.info(`Generating summary via ${config.ai.provider}\u2026`);
|
|
1958
2037
|
const summary = await provider.summarize(payload);
|
|
1959
|
-
const outputPath = opts.output ??
|
|
1960
|
-
if (!
|
|
1961
|
-
|
|
2038
|
+
const outputPath = opts.output ?? import_path9.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
|
|
2039
|
+
if (!import_fs13.default.existsSync(EXPORTS_DIR)) import_fs13.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
|
|
2040
|
+
import_fs13.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
|
|
1962
2041
|
|
|
1963
2042
|
${summary}
|
|
1964
2043
|
`, { encoding: "utf-8", mode: 384 });
|
|
@@ -2030,22 +2109,22 @@ function printConfig(obj, indent) {
|
|
|
2030
2109
|
}
|
|
2031
2110
|
|
|
2032
2111
|
// src/cli/pause.ts
|
|
2033
|
-
var
|
|
2112
|
+
var import_fs14 = __toESM(require("fs"));
|
|
2034
2113
|
function registerPause(program2) {
|
|
2035
2114
|
program2.command("pause").description("Pause capture temporarily").action(() => {
|
|
2036
|
-
if (
|
|
2115
|
+
if (import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
|
|
2037
2116
|
log.info("Capture is already paused. Run workscribe resume to restart.");
|
|
2038
2117
|
return;
|
|
2039
2118
|
}
|
|
2040
|
-
|
|
2119
|
+
import_fs14.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
|
|
2041
2120
|
log.success("Capture paused. Run workscribe resume to restart.");
|
|
2042
2121
|
});
|
|
2043
2122
|
program2.command("resume").description("Resume capture after a pause").action(() => {
|
|
2044
|
-
if (!
|
|
2123
|
+
if (!import_fs14.default.existsSync(PAUSE_FLAG_PATH)) {
|
|
2045
2124
|
log.info("Capture is not paused.");
|
|
2046
2125
|
return;
|
|
2047
2126
|
}
|
|
2048
|
-
|
|
2127
|
+
import_fs14.default.rmSync(PAUSE_FLAG_PATH);
|
|
2049
2128
|
log.success("Capture resumed.");
|
|
2050
2129
|
});
|
|
2051
2130
|
}
|
|
@@ -2130,18 +2209,18 @@ function registerClear(program2) {
|
|
|
2130
2209
|
}
|
|
2131
2210
|
|
|
2132
2211
|
// src/cli/uninstall.ts
|
|
2133
|
-
var
|
|
2134
|
-
var
|
|
2135
|
-
var
|
|
2212
|
+
var import_fs15 = __toESM(require("fs"));
|
|
2213
|
+
var import_path10 = __toESM(require("path"));
|
|
2214
|
+
var import_os6 = __toESM(require("os"));
|
|
2136
2215
|
var import_prompts3 = __toESM(require("prompts"));
|
|
2137
2216
|
var import_chalk9 = __toESM(require("chalk"));
|
|
2138
2217
|
function detectShellRcFiles() {
|
|
2139
2218
|
const candidates = [
|
|
2140
|
-
{ name: "zsh", rcFile:
|
|
2141
|
-
{ name: "bash", rcFile:
|
|
2142
|
-
{ name: "bash", rcFile:
|
|
2219
|
+
{ name: "zsh", rcFile: import_path10.default.join(import_os6.default.homedir(), ".zshrc") },
|
|
2220
|
+
{ name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bashrc") },
|
|
2221
|
+
{ name: "bash", rcFile: import_path10.default.join(import_os6.default.homedir(), ".bash_profile") }
|
|
2143
2222
|
];
|
|
2144
|
-
return candidates.filter((c) =>
|
|
2223
|
+
return candidates.filter((c) => import_fs15.default.existsSync(c.rcFile) && isHookInstalled(c.rcFile));
|
|
2145
2224
|
}
|
|
2146
2225
|
function registerUninstall(program2) {
|
|
2147
2226
|
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 +2239,7 @@ function registerUninstall(program2) {
|
|
|
2160
2239
|
}
|
|
2161
2240
|
}
|
|
2162
2241
|
if (opts.hooksOnly) return;
|
|
2163
|
-
if (
|
|
2242
|
+
if (import_fs15.default.existsSync(WORKSCRIBE_DIR)) {
|
|
2164
2243
|
log.plain("");
|
|
2165
2244
|
const { deleteData } = await (0, import_prompts3.default)({
|
|
2166
2245
|
type: "confirm",
|
|
@@ -2169,7 +2248,7 @@ function registerUninstall(program2) {
|
|
|
2169
2248
|
initial: false
|
|
2170
2249
|
});
|
|
2171
2250
|
if (deleteData) {
|
|
2172
|
-
|
|
2251
|
+
import_fs15.default.rmSync(WORKSCRIBE_DIR, { recursive: true, force: true });
|
|
2173
2252
|
log.success(`Deleted ${WORKSCRIBE_DIR}`);
|
|
2174
2253
|
} else {
|
|
2175
2254
|
log.info(`Data kept at ${WORKSCRIBE_DIR}`);
|
|
@@ -2183,7 +2262,7 @@ function registerUninstall(program2) {
|
|
|
2183
2262
|
|
|
2184
2263
|
// src/index.ts
|
|
2185
2264
|
var program = new import_commander.Command();
|
|
2186
|
-
program.name("workscribe").description("Automatic developer work journal").version("1.
|
|
2265
|
+
program.name("workscribe").description("Automatic developer work journal").version("1.8.0");
|
|
2187
2266
|
registerCapture(program);
|
|
2188
2267
|
registerHook(program);
|
|
2189
2268
|
registerInit(program);
|