@workscribe/cli 1.12.0 → 1.14.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 +1 -0
- package/dist/index.js +661 -172
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/shell/hook.bash +3 -0
- package/shell/hook.fish +3 -0
- package/shell/hook.zsh +3 -0
package/dist/index.js
CHANGED
|
@@ -27,15 +27,33 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
var import_commander = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/utils/node-version.ts
|
|
30
|
+
function detectVersionManager() {
|
|
31
|
+
if (process.env.NVM_DIR) return "nvm";
|
|
32
|
+
if (process.env.VOLTA_HOME) return "volta";
|
|
33
|
+
if (process.env.FNM_DIR) return "fnm";
|
|
34
|
+
if (process.env.MISE_DATA_DIR || process.env.MISE_SHELL) return "mise";
|
|
35
|
+
if (process.env.ASDF_DIR || process.env.ASDF_DATA_DIR) return "asdf";
|
|
36
|
+
if (process.env.NODENV_ROOT) return "nodenv";
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
var UPGRADE_COMMANDS = {
|
|
40
|
+
nvm: "nvm install 22 && nvm use 22",
|
|
41
|
+
volta: "volta install node@22",
|
|
42
|
+
fnm: "fnm install 22 && fnm use 22",
|
|
43
|
+
mise: "mise use --global node@22",
|
|
44
|
+
asdf: "asdf install nodejs 22 && asdf global nodejs 22",
|
|
45
|
+
nodenv: "nodenv install 22 && nodenv global 22"
|
|
46
|
+
};
|
|
30
47
|
function assertNodeVersion(nodeVersion) {
|
|
31
48
|
const major = Number(nodeVersion.split(".")[0]);
|
|
32
49
|
if (major < 22) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
const manager = detectVersionManager();
|
|
51
|
+
const upgradeHint = manager ? `Upgrade with ${manager}:
|
|
52
|
+
${UPGRADE_COMMANDS[manager]}` : "Download the latest version from https://nodejs.org";
|
|
53
|
+
process.stderr.write(`workscribe requires Node.js 22 or later (you have v${nodeVersion}).
|
|
54
|
+
|
|
55
|
+
${upgradeHint}
|
|
56
|
+
`);
|
|
39
57
|
process.exit(1);
|
|
40
58
|
}
|
|
41
59
|
}
|
|
@@ -56,7 +74,7 @@ var USE_XDG = !import_fs.default.existsSync(LEGACY_DIR);
|
|
|
56
74
|
var xdgDataDir = USE_XDG ? import_path.default.join(process.env.XDG_DATA_HOME ?? import_path.default.join(import_os.default.homedir(), ".local", "share"), "workscribe") : LEGACY_DIR;
|
|
57
75
|
var xdgConfigDir = USE_XDG ? import_path.default.join(process.env.XDG_CONFIG_HOME ?? import_path.default.join(import_os.default.homedir(), ".config"), "workscribe") : LEGACY_DIR;
|
|
58
76
|
var xdgStateDir = USE_XDG ? import_path.default.join(process.env.XDG_STATE_HOME ?? import_path.default.join(import_os.default.homedir(), ".local", "state"), "workscribe") : LEGACY_DIR;
|
|
59
|
-
var REQUIRED_DIRS = [.../* @__PURE__ */ new Set([xdgDataDir, xdgConfigDir, xdgStateDir])];
|
|
77
|
+
var REQUIRED_DIRS = [.../* @__PURE__ */ new Set([xdgDataDir, xdgConfigDir, xdgStateDir, LEGACY_DIR])];
|
|
60
78
|
var WORKSCRIBE_DIR = xdgDataDir;
|
|
61
79
|
var DB_PATH = import_path.default.join(xdgDataDir, "workscribe.db");
|
|
62
80
|
var CONFIG_PATH = import_path.default.join(xdgConfigDir, "config.json");
|
|
@@ -66,6 +84,7 @@ var EXPORTS_DIR = import_path.default.join(xdgDataDir, "exports");
|
|
|
66
84
|
var PAUSE_FLAG_PATH = import_path.default.join(xdgStateDir, "paused");
|
|
67
85
|
var WEEKLY_CACHE_DIR = import_path.default.join(xdgDataDir, "weekly-cache");
|
|
68
86
|
var PROFILES_DIR = import_path.default.join(xdgDataDir, "profiles");
|
|
87
|
+
var ALERTS_PENDING_PATH = import_path.default.join(LEGACY_DIR, "alerts.pending");
|
|
69
88
|
|
|
70
89
|
// src/db/migrations/001_initial_schema.ts
|
|
71
90
|
var initial_schema_default = {
|
|
@@ -127,8 +146,38 @@ var migration = {
|
|
|
127
146
|
};
|
|
128
147
|
var semantic_categories_default = migration;
|
|
129
148
|
|
|
149
|
+
// src/db/migrations/003_alerts.ts
|
|
150
|
+
var migration2 = {
|
|
151
|
+
version: 3,
|
|
152
|
+
description: "Add alerts table for AI session secret detection",
|
|
153
|
+
sql: `
|
|
154
|
+
CREATE TABLE IF NOT EXISTS alerts (
|
|
155
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
156
|
+
created_at INTEGER NOT NULL,
|
|
157
|
+
tool TEXT NOT NULL,
|
|
158
|
+
session_id TEXT,
|
|
159
|
+
context TEXT NOT NULL,
|
|
160
|
+
cleared INTEGER NOT NULL DEFAULT 0
|
|
161
|
+
);
|
|
162
|
+
CREATE INDEX IF NOT EXISTS alerts_cleared ON alerts (cleared);
|
|
163
|
+
`
|
|
164
|
+
};
|
|
165
|
+
var alerts_default = migration2;
|
|
166
|
+
|
|
167
|
+
// src/db/migrations/004_ide_columns.ts
|
|
168
|
+
var migration3 = {
|
|
169
|
+
version: 4,
|
|
170
|
+
description: "Add source and file_path columns to events for IDE integration",
|
|
171
|
+
sql: `
|
|
172
|
+
ALTER TABLE events ADD COLUMN source TEXT;
|
|
173
|
+
ALTER TABLE events ADD COLUMN file_path TEXT;
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_events_file_path ON events (file_path, cwd);
|
|
175
|
+
`
|
|
176
|
+
};
|
|
177
|
+
var ide_columns_default = migration3;
|
|
178
|
+
|
|
130
179
|
// src/db/migrations/index.ts
|
|
131
|
-
var MIGRATIONS = [initial_schema_default, semantic_categories_default];
|
|
180
|
+
var MIGRATIONS = [initial_schema_default, semantic_categories_default, alerts_default, ide_columns_default];
|
|
132
181
|
|
|
133
182
|
// src/db/index.ts
|
|
134
183
|
var _db = null;
|
|
@@ -165,13 +214,13 @@ function applyMigrations(db) {
|
|
|
165
214
|
const applied = new Set(
|
|
166
215
|
db.prepare("SELECT version FROM schema_migrations").all().map((row) => row.version)
|
|
167
216
|
);
|
|
168
|
-
for (const
|
|
169
|
-
if (applied.has(
|
|
217
|
+
for (const migration4 of MIGRATIONS) {
|
|
218
|
+
if (applied.has(migration4.version)) continue;
|
|
170
219
|
db.exec("BEGIN");
|
|
171
220
|
try {
|
|
172
|
-
db.exec(
|
|
221
|
+
db.exec(migration4.sql);
|
|
173
222
|
db.prepare("INSERT INTO schema_migrations (version, applied_at) VALUES ($version, datetime('now'))").run({
|
|
174
|
-
version:
|
|
223
|
+
version: migration4.version
|
|
175
224
|
});
|
|
176
225
|
db.exec("COMMIT");
|
|
177
226
|
} catch (err) {
|
|
@@ -179,18 +228,48 @@ function applyMigrations(db) {
|
|
|
179
228
|
db.exec("ROLLBACK");
|
|
180
229
|
} catch {
|
|
181
230
|
}
|
|
182
|
-
throw new Error(`Migration ${
|
|
231
|
+
throw new Error(`Migration ${migration4.version} (${migration4.description}) failed: ${err.message}`);
|
|
183
232
|
}
|
|
184
233
|
}
|
|
185
234
|
}
|
|
186
235
|
function insertEvent(params) {
|
|
187
236
|
const db = getDb();
|
|
188
237
|
const stmt = db.prepare(`
|
|
189
|
-
INSERT INTO events (captured_at, command, cwd, exit_code, duration_ms, repo_name, branch, category, metadata)
|
|
190
|
-
VALUES ($captured_at, $command, $cwd, $exit_code, $duration_ms, $repo_name, $branch, $category, $metadata)
|
|
238
|
+
INSERT INTO events (captured_at, command, cwd, exit_code, duration_ms, repo_name, branch, category, metadata, source, file_path)
|
|
239
|
+
VALUES ($captured_at, $command, $cwd, $exit_code, $duration_ms, $repo_name, $branch, $category, $metadata, $source, $file_path)
|
|
191
240
|
`);
|
|
192
241
|
stmt.run(params);
|
|
193
242
|
}
|
|
243
|
+
function getRecentSessionsByRepo(repoName, limit) {
|
|
244
|
+
const db = getDb();
|
|
245
|
+
return db.prepare("SELECT * FROM sessions WHERE repo_name = $repo_name ORDER BY started_at DESC LIMIT $limit").all({ repo_name: repoName, limit });
|
|
246
|
+
}
|
|
247
|
+
function getLastSessionTouchingFile(filePath) {
|
|
248
|
+
const db = getDb();
|
|
249
|
+
return db.prepare(
|
|
250
|
+
`SELECT s.* FROM sessions s
|
|
251
|
+
INNER JOIN session_events se ON se.session_id = s.id
|
|
252
|
+
INNER JOIN events e ON e.id = se.event_id
|
|
253
|
+
WHERE e.file_path = $file_path
|
|
254
|
+
ORDER BY s.started_at DESC
|
|
255
|
+
LIMIT 1`
|
|
256
|
+
).get({ file_path: filePath });
|
|
257
|
+
}
|
|
258
|
+
function getSessionBranch(sessionId) {
|
|
259
|
+
const db = getDb();
|
|
260
|
+
const row = db.prepare(
|
|
261
|
+
`SELECT e.branch FROM events e
|
|
262
|
+
INNER JOIN session_events se ON se.event_id = e.id
|
|
263
|
+
WHERE se.session_id = $session_id AND e.branch IS NOT NULL
|
|
264
|
+
ORDER BY e.captured_at DESC
|
|
265
|
+
LIMIT 1`
|
|
266
|
+
).get({ session_id: sessionId });
|
|
267
|
+
return row?.branch ?? null;
|
|
268
|
+
}
|
|
269
|
+
function getLastEventForFile(filePath, cwd) {
|
|
270
|
+
const db = getDb();
|
|
271
|
+
return db.prepare("SELECT * FROM events WHERE file_path = $file_path AND cwd = $cwd ORDER BY id DESC LIMIT 1").get({ file_path: filePath, cwd });
|
|
272
|
+
}
|
|
194
273
|
function getLastEvent() {
|
|
195
274
|
const db = getDb();
|
|
196
275
|
const stmt = db.prepare("SELECT * FROM events ORDER BY id DESC LIMIT 1");
|
|
@@ -332,7 +411,10 @@ var BUILTIN_PATTERNS = [
|
|
|
332
411
|
/(?<=export\s+(?:[A-Z_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z_]*)=)\S+/gi,
|
|
333
412
|
// Inline assignment: VAR_WITH_SENSITIVE_KEYWORD=value cmd (e.g. API_KEY=secret npm start)
|
|
334
413
|
// Lookbehind matches only the variable-name= prefix, so only the value is replaced
|
|
335
|
-
/(?<=[A-Z_][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z0-9_]*=)\S+/gi
|
|
414
|
+
/(?<=[A-Z_][A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASS|AUTH|CREDENTIAL)[A-Z0-9_]*=)\S+/gi,
|
|
415
|
+
// Credentials embedded in connection string URLs (e.g. DATABASE_URL=postgres://user:pass@host/db)
|
|
416
|
+
// Matches the password segment between :// user : password @ — preserves scheme and host
|
|
417
|
+
/(?<=[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^:@\s/]+:)[^@\s]+(?=@)/g
|
|
336
418
|
];
|
|
337
419
|
function redact(command, extraPatterns = []) {
|
|
338
420
|
let result = command;
|
|
@@ -354,15 +436,24 @@ function redact(command, extraPatterns = []) {
|
|
|
354
436
|
var import_child_process = require("child_process");
|
|
355
437
|
var import_path2 = __toESM(require("path"));
|
|
356
438
|
var import_fs3 = __toESM(require("fs"));
|
|
357
|
-
function
|
|
439
|
+
function parseOwnerAndRepo(url) {
|
|
440
|
+
const normalized = url.trim().replace(/^[^/]+:(?!\/\/)/, (match) => match.replace(":", "/"));
|
|
441
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
442
|
+
if (segments.length < 2) return null;
|
|
443
|
+
const repo = segments[segments.length - 1].replace(/\.git$/, "");
|
|
444
|
+
const owner = segments[segments.length - 2];
|
|
445
|
+
return owner && repo ? { owner, repo } : null;
|
|
446
|
+
}
|
|
447
|
+
function nameFromRemote(root, qualifyWithOwner) {
|
|
358
448
|
try {
|
|
359
449
|
const url = (0, import_child_process.execSync)("git remote get-url origin", {
|
|
360
450
|
cwd: root,
|
|
361
451
|
timeout: 2e3,
|
|
362
452
|
stdio: "pipe"
|
|
363
453
|
}).toString().trim();
|
|
364
|
-
const
|
|
365
|
-
|
|
454
|
+
const parsed = parseOwnerAndRepo(url);
|
|
455
|
+
if (!parsed) return null;
|
|
456
|
+
return qualifyWithOwner ? `${parsed.owner}/${parsed.repo}` : parsed.repo;
|
|
366
457
|
} catch {
|
|
367
458
|
return null;
|
|
368
459
|
}
|
|
@@ -377,14 +468,14 @@ function nameFromPackageJson(root) {
|
|
|
377
468
|
return null;
|
|
378
469
|
}
|
|
379
470
|
}
|
|
380
|
-
function getGitContext(cwd, nameOverride) {
|
|
471
|
+
function getGitContext(cwd, nameOverride, qualifyRepoName = false) {
|
|
381
472
|
try {
|
|
382
473
|
const root = (0, import_child_process.execSync)("git rev-parse --show-toplevel", {
|
|
383
474
|
cwd,
|
|
384
475
|
timeout: 2e3,
|
|
385
476
|
stdio: "pipe"
|
|
386
477
|
}).toString().trim();
|
|
387
|
-
const repoName = nameOverride ?? nameFromRemote(root) ?? nameFromPackageJson(root) ?? import_path2.default.basename(root);
|
|
478
|
+
const repoName = nameOverride ?? nameFromRemote(root, qualifyRepoName) ?? nameFromPackageJson(root) ?? import_path2.default.basename(root);
|
|
388
479
|
let branch = null;
|
|
389
480
|
try {
|
|
390
481
|
branch = (0, import_child_process.execSync)("git branch --show-current", {
|
|
@@ -421,7 +512,7 @@ function readLastAiderPrompt(cwd) {
|
|
|
421
512
|
try {
|
|
422
513
|
const histPath = import_path3.default.join(cwd, HISTORY_FILE);
|
|
423
514
|
const content = import_fs4.default.readFileSync(histPath, "utf-8");
|
|
424
|
-
const matches = [...content.matchAll(
|
|
515
|
+
const matches = [...content.matchAll(/^#### (.+)$/gm)];
|
|
425
516
|
if (!matches.length) return null;
|
|
426
517
|
return matches[matches.length - 1][1].trim().slice(0, PROMPT_MAX_LENGTH);
|
|
427
518
|
} catch {
|
|
@@ -441,56 +532,108 @@ var ai_tools_default = [
|
|
|
441
532
|
displayName: "Claude Code",
|
|
442
533
|
pattern: "^claude\\b",
|
|
443
534
|
hook: "claude-code",
|
|
444
|
-
contextSource: "hook"
|
|
535
|
+
contextSource: "hook",
|
|
536
|
+
historyFormat: null,
|
|
537
|
+
securityWarning: null
|
|
445
538
|
},
|
|
446
539
|
{
|
|
447
540
|
tool: "aider",
|
|
448
541
|
displayName: "Aider",
|
|
449
542
|
pattern: "^aider\\b",
|
|
450
543
|
hook: null,
|
|
451
|
-
contextSource: "history-file"
|
|
544
|
+
contextSource: "history-file",
|
|
545
|
+
historyFormat: "markdown",
|
|
546
|
+
securityWarning: null
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
tool: "codex",
|
|
550
|
+
displayName: "Codex CLI",
|
|
551
|
+
pattern: "^codex\\b",
|
|
552
|
+
hook: null,
|
|
553
|
+
contextSource: "history-file",
|
|
554
|
+
historyFormat: "jsonl",
|
|
555
|
+
securityWarning: null
|
|
452
556
|
},
|
|
453
557
|
{
|
|
454
558
|
tool: "gemini",
|
|
455
|
-
displayName: "Gemini",
|
|
559
|
+
displayName: "Gemini CLI",
|
|
456
560
|
pattern: "^gemini\\b",
|
|
457
561
|
hook: null,
|
|
458
|
-
contextSource:
|
|
562
|
+
contextSource: "history-file",
|
|
563
|
+
historyFormat: "json",
|
|
564
|
+
securityWarning: null
|
|
459
565
|
},
|
|
460
566
|
{
|
|
461
567
|
tool: "gh copilot",
|
|
462
568
|
displayName: "GitHub Copilot",
|
|
463
569
|
pattern: "^gh\\s+copilot\\b",
|
|
464
570
|
hook: null,
|
|
465
|
-
contextSource:
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
tool: "cody",
|
|
469
|
-
displayName: "Cody",
|
|
470
|
-
pattern: "^cody\\b",
|
|
471
|
-
hook: null,
|
|
472
|
-
contextSource: null
|
|
571
|
+
contextSource: "history-file",
|
|
572
|
+
historyFormat: "jsonl",
|
|
573
|
+
securityWarning: null
|
|
473
574
|
},
|
|
474
575
|
{
|
|
475
576
|
tool: "amazon-q",
|
|
476
577
|
displayName: "Amazon Q",
|
|
477
578
|
pattern: "^q\\s+(chat|dev)\\b",
|
|
478
579
|
hook: null,
|
|
479
|
-
contextSource:
|
|
580
|
+
contextSource: "history-file",
|
|
581
|
+
historyFormat: "json",
|
|
582
|
+
securityWarning: null
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
tool: "opencode",
|
|
586
|
+
displayName: "OpenCode",
|
|
587
|
+
pattern: "^opencode\\b",
|
|
588
|
+
hook: null,
|
|
589
|
+
contextSource: "sqlite",
|
|
590
|
+
historyFormat: "sqlite",
|
|
591
|
+
securityWarning: null
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
tool: "kilo",
|
|
595
|
+
displayName: "Kilo Code",
|
|
596
|
+
pattern: "^kilo\\b",
|
|
597
|
+
hook: null,
|
|
598
|
+
contextSource: null,
|
|
599
|
+
historyFormat: null,
|
|
600
|
+
securityWarning: null
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
tool: "grok",
|
|
604
|
+
displayName: "Grok Build",
|
|
605
|
+
pattern: "^grok\\b",
|
|
606
|
+
hook: null,
|
|
607
|
+
contextSource: null,
|
|
608
|
+
historyFormat: null,
|
|
609
|
+
securityWarning: "Grok Build was found to silently upload entire local git repositories \u2014 including .env files and untracked secrets \u2014 to Google Cloud Storage without user disclosure (July 2026 incident). Workscribe does not read Grok Build session files. Consider whether this tool is appropriate for use in repositories containing sensitive credentials."
|
|
480
610
|
},
|
|
481
611
|
{
|
|
482
612
|
tool: "continue",
|
|
483
613
|
displayName: "Continue",
|
|
484
614
|
pattern: "^continue\\b",
|
|
485
615
|
hook: null,
|
|
486
|
-
contextSource: null
|
|
616
|
+
contextSource: null,
|
|
617
|
+
historyFormat: null,
|
|
618
|
+
securityWarning: null
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
tool: "cody",
|
|
622
|
+
displayName: "Cody",
|
|
623
|
+
pattern: "^cody\\b",
|
|
624
|
+
hook: null,
|
|
625
|
+
contextSource: null,
|
|
626
|
+
historyFormat: null,
|
|
627
|
+
securityWarning: null
|
|
487
628
|
},
|
|
488
629
|
{
|
|
489
630
|
tool: "cursor",
|
|
490
631
|
displayName: "Cursor",
|
|
491
632
|
pattern: "^cursor\\b",
|
|
492
633
|
hook: null,
|
|
493
|
-
contextSource: null
|
|
634
|
+
contextSource: null,
|
|
635
|
+
historyFormat: null,
|
|
636
|
+
securityWarning: null
|
|
494
637
|
}
|
|
495
638
|
];
|
|
496
639
|
|
|
@@ -529,7 +672,9 @@ function loadAIToolRegistry() {
|
|
|
529
672
|
displayName: entry.displayName ?? entry.tool,
|
|
530
673
|
pattern: entry.pattern,
|
|
531
674
|
hook: entry.hook ?? null,
|
|
532
|
-
contextSource: entry.contextSource ?? null
|
|
675
|
+
contextSource: entry.contextSource ?? null,
|
|
676
|
+
historyFormat: entry.historyFormat ?? null,
|
|
677
|
+
securityWarning: entry.securityWarning ?? null
|
|
533
678
|
}));
|
|
534
679
|
}
|
|
535
680
|
}
|
|
@@ -558,6 +703,9 @@ var DEFAULTS = {
|
|
|
558
703
|
session: {
|
|
559
704
|
idleTimeout: 30
|
|
560
705
|
},
|
|
706
|
+
git: {
|
|
707
|
+
qualifyRepoName: false
|
|
708
|
+
},
|
|
561
709
|
capture: {
|
|
562
710
|
ignore: ["cd", "ls", "ll", "la", "ls -la", "ls -l", "ls -a", "pwd", "clear", "exit", "history", "man"],
|
|
563
711
|
aiTools: true,
|
|
@@ -1074,11 +1222,41 @@ function isDuplicate(command, cwd, lastEvent, dedupWindowMs = 5e3, now = Date.no
|
|
|
1074
1222
|
return lastEvent.command === normalizeForFilter(command) && lastEvent.cwd === cwd && now - lastTime < dedupWindowMs;
|
|
1075
1223
|
}
|
|
1076
1224
|
|
|
1225
|
+
// src/capture/ide.ts
|
|
1226
|
+
var IDE_DEDUP_WINDOW_MS = 3e4;
|
|
1227
|
+
|
|
1077
1228
|
// src/capture/index.ts
|
|
1078
1229
|
function captureCommand(opts) {
|
|
1079
1230
|
if (import_fs10.default.existsSync(PAUSE_FLAG_PATH)) return;
|
|
1080
1231
|
const config = readConfig();
|
|
1081
|
-
const {
|
|
1232
|
+
const { cwd, exitCode, durationMs } = opts;
|
|
1233
|
+
if (opts.type) {
|
|
1234
|
+
if (opts.type === "code_edit" && opts.file) {
|
|
1235
|
+
const lastForFile = getLastEventForFile(opts.file, cwd);
|
|
1236
|
+
if (lastForFile) {
|
|
1237
|
+
const lastTime = new Date(lastForFile.captured_at).getTime();
|
|
1238
|
+
if (Date.now() - lastTime < IDE_DEDUP_WINDOW_MS) return;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
const command2 = opts.command ?? opts.file ?? opts.type;
|
|
1242
|
+
const nameOverride2 = config.projects?.[cwd] ?? null;
|
|
1243
|
+
const { repoName: repoName2, branch: branch2 } = getGitContext(cwd, nameOverride2, config.git.qualifyRepoName);
|
|
1244
|
+
insertEvent({
|
|
1245
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1246
|
+
command: command2,
|
|
1247
|
+
cwd,
|
|
1248
|
+
exit_code: exitCode,
|
|
1249
|
+
duration_ms: durationMs,
|
|
1250
|
+
repo_name: repoName2,
|
|
1251
|
+
branch: branch2,
|
|
1252
|
+
category: opts.type,
|
|
1253
|
+
metadata: null,
|
|
1254
|
+
source: opts.source ?? null,
|
|
1255
|
+
file_path: opts.file ?? null
|
|
1256
|
+
});
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const command = opts.command;
|
|
1082
1260
|
if (shouldFilter(command, config.capture.ignore)) return;
|
|
1083
1261
|
const last = getLastEvent();
|
|
1084
1262
|
if (isDuplicate(command, cwd, last ?? void 0)) return;
|
|
@@ -1092,7 +1270,6 @@ function captureCommand(opts) {
|
|
|
1092
1270
|
if (firstToken) metadata.tool = firstToken;
|
|
1093
1271
|
metadata.hint = safeCommand.trim().slice(0, 120);
|
|
1094
1272
|
}
|
|
1095
|
-
if (opts.source) metadata.source = opts.source;
|
|
1096
1273
|
if (category === "ai_session") {
|
|
1097
1274
|
if (opts.gitBefore) {
|
|
1098
1275
|
const commits = getCommitsSince(opts.gitBefore, cwd);
|
|
@@ -1115,24 +1292,30 @@ function captureCommand(opts) {
|
|
|
1115
1292
|
repo_name: repoName,
|
|
1116
1293
|
branch,
|
|
1117
1294
|
category,
|
|
1118
|
-
metadata: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null
|
|
1295
|
+
metadata: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null,
|
|
1296
|
+
source: opts.source ?? null,
|
|
1297
|
+
file_path: null
|
|
1119
1298
|
});
|
|
1120
1299
|
}
|
|
1121
1300
|
|
|
1122
1301
|
// src/cli/capture.ts
|
|
1123
1302
|
var CAPTURE_HANG_TIMEOUT_MS = 5e3;
|
|
1124
1303
|
function registerCapture(program2) {
|
|
1125
|
-
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) => {
|
|
1304
|
+
program2.command("_capture").description("Internal: capture a shell command or IDE event (called by shell hook or IDE plugin)").option("--cmd <command>", "command that was executed (shell events)").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").option("--type <type>", "event type override \u2014 skips normalization (IDE integrations)").option("--source <source>", "event source identifier (e.g. vscode, neovim, jupyter)").option("--file <path>", "file path for code_edit events").action((opts) => {
|
|
1126
1305
|
const hangGuard = setTimeout(() => process.exit(0), CAPTURE_HANG_TIMEOUT_MS);
|
|
1127
1306
|
hangGuard.unref();
|
|
1128
1307
|
try {
|
|
1129
|
-
if (!opts.
|
|
1308
|
+
if (!opts.cwd) return;
|
|
1309
|
+
if (!opts.cmd && !opts.type) return;
|
|
1130
1310
|
captureCommand({
|
|
1131
1311
|
command: opts.cmd,
|
|
1132
1312
|
cwd: opts.cwd,
|
|
1133
1313
|
exitCode: opts.exit ?? null,
|
|
1134
1314
|
durationMs: opts.duration ?? null,
|
|
1135
|
-
gitBefore: opts.gitBefore ?? void 0
|
|
1315
|
+
gitBefore: opts.gitBefore ?? void 0,
|
|
1316
|
+
source: opts.source ?? void 0,
|
|
1317
|
+
type: opts.type ?? void 0,
|
|
1318
|
+
file: opts.file ?? void 0
|
|
1136
1319
|
});
|
|
1137
1320
|
} catch (err) {
|
|
1138
1321
|
debugLog("_capture", err);
|
|
@@ -1140,6 +1323,38 @@ function registerCapture(program2) {
|
|
|
1140
1323
|
});
|
|
1141
1324
|
}
|
|
1142
1325
|
|
|
1326
|
+
// src/capture/detect-secrets.ts
|
|
1327
|
+
function containsSecrets(text) {
|
|
1328
|
+
return redact(text) !== text;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// src/db/alerts.ts
|
|
1332
|
+
var import_fs11 = __toESM(require("fs"));
|
|
1333
|
+
var import_path7 = __toESM(require("path"));
|
|
1334
|
+
function insertAlert(tool, sessionId, context) {
|
|
1335
|
+
getDb().prepare(
|
|
1336
|
+
`INSERT INTO alerts (created_at, tool, session_id, context)
|
|
1337
|
+
VALUES ($created_at, $tool, $session_id, $context)`
|
|
1338
|
+
).run({ created_at: Date.now(), tool, session_id: sessionId ?? null, context });
|
|
1339
|
+
import_fs11.default.mkdirSync(import_path7.default.dirname(ALERTS_PENDING_PATH), { recursive: true });
|
|
1340
|
+
import_fs11.default.writeFileSync(ALERTS_PENDING_PATH, "", { flag: "w" });
|
|
1341
|
+
}
|
|
1342
|
+
function getPendingAlerts() {
|
|
1343
|
+
return getDb().prepare("SELECT * FROM alerts WHERE cleared = 0 ORDER BY created_at DESC").all();
|
|
1344
|
+
}
|
|
1345
|
+
function hasPendingAlerts() {
|
|
1346
|
+
const row = getDb().prepare("SELECT COUNT(*) as count FROM alerts WHERE cleared = 0").get();
|
|
1347
|
+
return Number(row.count) > 0;
|
|
1348
|
+
}
|
|
1349
|
+
function clearAlerts() {
|
|
1350
|
+
const result = getDb().prepare("UPDATE alerts SET cleared = 1 WHERE cleared = 0").run();
|
|
1351
|
+
try {
|
|
1352
|
+
import_fs11.default.unlinkSync(ALERTS_PENDING_PATH);
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
return Number(result.changes);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1143
1358
|
// src/cli/hook.ts
|
|
1144
1359
|
function parseHookPayload(raw) {
|
|
1145
1360
|
if (!raw.trim()) return null;
|
|
@@ -1150,8 +1365,17 @@ function parseHookPayload(raw) {
|
|
|
1150
1365
|
if (!command || !cwd) return null;
|
|
1151
1366
|
return { command, cwd };
|
|
1152
1367
|
}
|
|
1368
|
+
function parseUserPromptPayload(raw) {
|
|
1369
|
+
if (!raw.trim()) return null;
|
|
1370
|
+
const payload = JSON.parse(raw);
|
|
1371
|
+
const prompt = typeof payload.prompt === "string" ? payload.prompt : null;
|
|
1372
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : "";
|
|
1373
|
+
const cwd = typeof payload.cwd === "string" ? payload.cwd : "";
|
|
1374
|
+
if (!prompt) return null;
|
|
1375
|
+
return { session_id: sessionId, prompt, cwd };
|
|
1376
|
+
}
|
|
1153
1377
|
function registerHook(program2) {
|
|
1154
|
-
program2.command("_hook").description("Internal: capture events from AI tool hooks (stdin JSON payload)").option("--source <source>", "hook source identifier (e.g. claude-code)").action((opts) => {
|
|
1378
|
+
program2.command("_hook").description("Internal: capture events from AI tool hooks (stdin JSON payload)").option("--source <source>", "hook source identifier (e.g. claude-code)").option("--event <event>", "hook event type (PostToolUse or UserPromptSubmit)", "PostToolUse").action((opts) => {
|
|
1155
1379
|
let raw = "";
|
|
1156
1380
|
process.stdin.setEncoding("utf-8");
|
|
1157
1381
|
process.stdin.on("data", (chunk) => {
|
|
@@ -1159,6 +1383,23 @@ function registerHook(program2) {
|
|
|
1159
1383
|
});
|
|
1160
1384
|
process.stdin.on("end", () => {
|
|
1161
1385
|
try {
|
|
1386
|
+
if (opts.event === "UserPromptSubmit") {
|
|
1387
|
+
const parsed2 = parseUserPromptPayload(raw);
|
|
1388
|
+
if (!parsed2) return;
|
|
1389
|
+
if (containsSecrets(parsed2.prompt)) {
|
|
1390
|
+
insertAlert(
|
|
1391
|
+
opts.source ?? "unknown",
|
|
1392
|
+
parsed2.session_id || null,
|
|
1393
|
+
"Possible credential detected in prompt submitted to AI tool"
|
|
1394
|
+
);
|
|
1395
|
+
process.stdout.write(
|
|
1396
|
+
JSON.stringify({
|
|
1397
|
+
systemMessage: "Workscribe detected a possible credential in your prompt. Run `workscribe alerts` for details."
|
|
1398
|
+
})
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1162
1403
|
if (readConfig().capture.aiTools === false) return;
|
|
1163
1404
|
const parsed = parseHookPayload(raw);
|
|
1164
1405
|
if (!parsed) return;
|
|
@@ -1177,8 +1418,8 @@ function registerHook(program2) {
|
|
|
1177
1418
|
}
|
|
1178
1419
|
|
|
1179
1420
|
// src/cli/init.ts
|
|
1180
|
-
var
|
|
1181
|
-
var
|
|
1421
|
+
var import_fs14 = __toESM(require("fs"));
|
|
1422
|
+
var import_path10 = __toESM(require("path"));
|
|
1182
1423
|
var import_os5 = __toESM(require("os"));
|
|
1183
1424
|
var import_prompts = __toESM(require("prompts"));
|
|
1184
1425
|
var import_chalk2 = __toESM(require("chalk"));
|
|
@@ -1224,6 +1465,17 @@ function eachDayOfRange(start, end) {
|
|
|
1224
1465
|
}
|
|
1225
1466
|
return days;
|
|
1226
1467
|
}
|
|
1468
|
+
function timeAgo(isoString, now = Date.now()) {
|
|
1469
|
+
const elapsedMs = now - new Date(isoString).getTime();
|
|
1470
|
+
const minutes = Math.floor(elapsedMs / 6e4);
|
|
1471
|
+
if (minutes < 2) return "just now";
|
|
1472
|
+
if (minutes < 60) return `${minutes} minutes ago`;
|
|
1473
|
+
const hours = Math.floor(minutes / 60);
|
|
1474
|
+
if (hours < 24) return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
|
|
1475
|
+
const days = Math.floor(hours / 24);
|
|
1476
|
+
if (days === 1) return "yesterday";
|
|
1477
|
+
return `${days} days ago`;
|
|
1478
|
+
}
|
|
1227
1479
|
function isoWeekKey(dateStr) {
|
|
1228
1480
|
const d = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
|
|
1229
1481
|
const jan4 = new Date(d.getFullYear(), 0, 4);
|
|
@@ -1272,6 +1524,8 @@ RULES \u2014 follow these exactly:
|
|
|
1272
1524
|
|
|
1273
1525
|
7. Stick to what the events show. Do not infer subject matter from the branch name. Do not add details not derivable from the events.
|
|
1274
1526
|
|
|
1527
|
+
8. For code_edit events: these represent file saves from an IDE integration. Describe what was being built based on repository context (branch name, commit messages). Never list individual filenames.
|
|
1528
|
+
|
|
1275
1529
|
Maximum 5 bullets. Output ONLY the bullet points, nothing else.`;
|
|
1276
1530
|
function getSystemPrompt() {
|
|
1277
1531
|
const contexts = getActiveProfileAiContexts();
|
|
@@ -1313,7 +1567,9 @@ function buildUserMessage(payload, includeCategories = []) {
|
|
|
1313
1567
|
if (excluded.has(event.category)) continue;
|
|
1314
1568
|
const failed = event.exitCode !== null && event.exitCode !== 0 ? " [failed]" : "";
|
|
1315
1569
|
let key;
|
|
1316
|
-
if (event.
|
|
1570
|
+
if (event.category === "code_edit") {
|
|
1571
|
+
key = `code_edit${failed}`;
|
|
1572
|
+
} else if (event.hint) {
|
|
1317
1573
|
key = `other [${event.hint}]${failed}`;
|
|
1318
1574
|
} else {
|
|
1319
1575
|
const meta = Object.keys(event.metadata).length > 0 ? ` ${JSON.stringify(event.metadata)}` : "";
|
|
@@ -1381,7 +1637,9 @@ function buildWeeklyMessage(payload) {
|
|
|
1381
1637
|
if (excluded.has(event.category)) continue;
|
|
1382
1638
|
const failed = event.exitCode !== null && event.exitCode !== 0 ? " [failed]" : "";
|
|
1383
1639
|
let key;
|
|
1384
|
-
if (event.
|
|
1640
|
+
if (event.category === "code_edit") {
|
|
1641
|
+
key = `code_edit${failed}`;
|
|
1642
|
+
} else if (event.hint) {
|
|
1385
1643
|
key = `other [${event.hint}]${failed}`;
|
|
1386
1644
|
} else {
|
|
1387
1645
|
const meta = Object.keys(event.metadata).length > 0 ? ` ${JSON.stringify(event.metadata)}` : "";
|
|
@@ -1412,7 +1670,7 @@ var GenericProvider = class {
|
|
|
1412
1670
|
baseUrl;
|
|
1413
1671
|
includeCategories;
|
|
1414
1672
|
async call(system, userMsg, maxTokens) {
|
|
1415
|
-
const response = await fetchWithTimeout(this.config.chatUrl(this.baseUrl), {
|
|
1673
|
+
const response = await fetchWithTimeout(this.config.chatUrl(this.baseUrl, this.apiKey, this.model), {
|
|
1416
1674
|
method: "POST",
|
|
1417
1675
|
headers: this.config.headers(this.apiKey),
|
|
1418
1676
|
body: JSON.stringify(this.config.buildChatBody(this.model, system, userMsg, maxTokens))
|
|
@@ -1430,7 +1688,7 @@ var GenericProvider = class {
|
|
|
1430
1688
|
}
|
|
1431
1689
|
async validate() {
|
|
1432
1690
|
const isPost = this.config.validateMethod === "POST";
|
|
1433
|
-
const response = await fetchWithTimeout(this.config.validateUrl(this.baseUrl), {
|
|
1691
|
+
const response = await fetchWithTimeout(this.config.validateUrl(this.baseUrl, this.apiKey, this.model), {
|
|
1434
1692
|
method: this.config.validateMethod,
|
|
1435
1693
|
headers: isPost ? this.config.headers(this.apiKey) : void 0,
|
|
1436
1694
|
body: isPost && this.config.buildValidateBody ? JSON.stringify(this.config.buildValidateBody(this.model)) : void 0
|
|
@@ -1529,6 +1787,59 @@ var PROVIDER_CONFIGS = {
|
|
|
1529
1787
|
},
|
|
1530
1788
|
chatError: (status, body) => `Ollama error ${status}: ${body}. Is Ollama running? Try: ollama serve`,
|
|
1531
1789
|
validateError: (_status, _body, baseUrl) => `Ollama is not reachable at ${baseUrl}. Is it running? Try: ollama serve`
|
|
1790
|
+
},
|
|
1791
|
+
gemini: {
|
|
1792
|
+
defaultModel: "gemini-2.0-flash-lite",
|
|
1793
|
+
defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
1794
|
+
dailyTokens: 1024,
|
|
1795
|
+
weeklyTokens: 1024,
|
|
1796
|
+
// API key and model are embedded in the URL — not in headers
|
|
1797
|
+
chatUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
|
|
1798
|
+
validateUrl: (baseUrl, apiKey, model) => `${baseUrl}/models/${model}:generateContent?key=${apiKey}`,
|
|
1799
|
+
validateMethod: "POST",
|
|
1800
|
+
headers: () => ({ "Content-Type": "application/json" }),
|
|
1801
|
+
buildChatBody: (_model, system, userMsg, maxTokens) => ({
|
|
1802
|
+
system_instruction: { parts: [{ text: system }] },
|
|
1803
|
+
contents: [{ role: "user", parts: [{ text: userMsg }] }],
|
|
1804
|
+
generationConfig: { maxOutputTokens: maxTokens, temperature: 0.3 }
|
|
1805
|
+
}),
|
|
1806
|
+
buildValidateBody: () => ({
|
|
1807
|
+
contents: [{ role: "user", parts: [{ text: "." }] }],
|
|
1808
|
+
generationConfig: { maxOutputTokens: 1 }
|
|
1809
|
+
}),
|
|
1810
|
+
parseText: (json) => {
|
|
1811
|
+
const data = json;
|
|
1812
|
+
return data.candidates?.[0]?.content?.parts?.[0]?.text ?? "";
|
|
1813
|
+
},
|
|
1814
|
+
chatError: (status, body) => `Gemini API error ${status}: ${body}`,
|
|
1815
|
+
validateError: (status, body) => `Gemini API error ${status}: ${body}`
|
|
1816
|
+
},
|
|
1817
|
+
groq: {
|
|
1818
|
+
defaultModel: "llama-3.1-8b-instant",
|
|
1819
|
+
defaultBaseUrl: "https://api.groq.com/openai/v1",
|
|
1820
|
+
dailyTokens: 1024,
|
|
1821
|
+
weeklyTokens: 1024,
|
|
1822
|
+
chatUrl: (baseUrl) => `${baseUrl}/chat/completions`,
|
|
1823
|
+
validateUrl: (baseUrl) => `${baseUrl}/models`,
|
|
1824
|
+
validateMethod: "GET",
|
|
1825
|
+
headers: (apiKey) => ({
|
|
1826
|
+
"Content-Type": "application/json",
|
|
1827
|
+
Authorization: `Bearer ${apiKey}`
|
|
1828
|
+
}),
|
|
1829
|
+
buildChatBody: (model, system, userMsg, maxTokens) => ({
|
|
1830
|
+
model,
|
|
1831
|
+
messages: [
|
|
1832
|
+
{ role: "system", content: system },
|
|
1833
|
+
{ role: "user", content: userMsg }
|
|
1834
|
+
],
|
|
1835
|
+
max_tokens: maxTokens
|
|
1836
|
+
}),
|
|
1837
|
+
parseText: (json) => {
|
|
1838
|
+
const data = json;
|
|
1839
|
+
return data.choices?.[0]?.message?.content ?? "";
|
|
1840
|
+
},
|
|
1841
|
+
chatError: (status, body) => `Groq API error ${status}: ${body}`,
|
|
1842
|
+
validateError: (status, body) => `Groq API error ${status}: ${body}`
|
|
1532
1843
|
}
|
|
1533
1844
|
};
|
|
1534
1845
|
|
|
@@ -1537,7 +1848,7 @@ function getProvider(config) {
|
|
|
1537
1848
|
const { provider, apiKey = "", model, baseUrl } = config.ai;
|
|
1538
1849
|
if (!provider) {
|
|
1539
1850
|
throw new Error(
|
|
1540
|
-
"No AI provider configured.\nRun: workscribe config set ai.provider <anthropic|openai|ollama|openai-compatible>"
|
|
1851
|
+
"No AI provider configured.\nRun: workscribe config set ai.provider <anthropic|openai|gemini|groq|ollama|openai-compatible>"
|
|
1541
1852
|
);
|
|
1542
1853
|
}
|
|
1543
1854
|
const configKey = provider === "openai-compatible" ? "openai" : provider;
|
|
@@ -1545,7 +1856,7 @@ function getProvider(config) {
|
|
|
1545
1856
|
if (!providerConfig) {
|
|
1546
1857
|
throw new Error(
|
|
1547
1858
|
`Unknown provider: "${provider}"
|
|
1548
|
-
Supported providers: anthropic, openai, ollama, openai-compatible`
|
|
1859
|
+
Supported providers: anthropic, openai, gemini, groq, ollama, openai-compatible`
|
|
1549
1860
|
);
|
|
1550
1861
|
}
|
|
1551
1862
|
if (provider !== "ollama" && !apiKey) {
|
|
@@ -1565,9 +1876,10 @@ Run: workscribe config set ai.apiKey <your-key>`);
|
|
|
1565
1876
|
}
|
|
1566
1877
|
|
|
1567
1878
|
// src/capture/shell-hook.ts
|
|
1568
|
-
var
|
|
1569
|
-
var
|
|
1879
|
+
var import_fs12 = __toESM(require("fs"));
|
|
1880
|
+
var import_path8 = __toESM(require("path"));
|
|
1570
1881
|
var import_os3 = __toESM(require("os"));
|
|
1882
|
+
var import_child_process3 = require("child_process");
|
|
1571
1883
|
|
|
1572
1884
|
// src/utils/completion-scripts.ts
|
|
1573
1885
|
function bashCompletion(commands) {
|
|
@@ -1624,13 +1936,13 @@ function fishCompletion(commands) {
|
|
|
1624
1936
|
}
|
|
1625
1937
|
|
|
1626
1938
|
// src/capture/shell-hook.ts
|
|
1627
|
-
var FISH_COMPLETIONS_DIR =
|
|
1939
|
+
var FISH_COMPLETIONS_DIR = import_path8.default.join(import_os3.default.homedir(), ".config", "fish", "completions");
|
|
1628
1940
|
var HOOK_START = "# workscribe:hook:start";
|
|
1629
1941
|
var HOOK_END = "# workscribe:hook:end";
|
|
1630
1942
|
var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
|
|
1631
1943
|
function getInstalledHookBlock(rcFile) {
|
|
1632
|
-
if (!
|
|
1633
|
-
const content =
|
|
1944
|
+
if (!import_fs12.default.existsSync(rcFile)) return null;
|
|
1945
|
+
const content = import_fs12.default.readFileSync(rcFile, "utf-8");
|
|
1634
1946
|
const startIdx = content.indexOf(HOOK_START);
|
|
1635
1947
|
const endIdx = content.indexOf(HOOK_END);
|
|
1636
1948
|
if (startIdx === -1 || endIdx === -1) return null;
|
|
@@ -1646,54 +1958,66 @@ function isHookOutdated(rcFile, hookContent) {
|
|
|
1646
1958
|
}
|
|
1647
1959
|
function installHook(rcFile, hookContent) {
|
|
1648
1960
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
1649
|
-
|
|
1650
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
1651
|
-
|
|
1961
|
+
import_fs12.default.copyFileSync(rcFile, backup);
|
|
1962
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path8.default.basename(backup)}`);
|
|
1963
|
+
import_fs12.default.appendFileSync(rcFile, `
|
|
1652
1964
|
${hookContent}
|
|
1653
1965
|
`);
|
|
1654
1966
|
}
|
|
1655
1967
|
function updateHook(rcFile, hookContent) {
|
|
1656
1968
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
1657
|
-
|
|
1658
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
1659
|
-
const content =
|
|
1969
|
+
import_fs12.default.copyFileSync(rcFile, backup);
|
|
1970
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path8.default.basename(backup)}`);
|
|
1971
|
+
const content = import_fs12.default.readFileSync(rcFile, "utf-8");
|
|
1660
1972
|
const startIdx = content.indexOf(HOOK_START);
|
|
1661
1973
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
1662
|
-
|
|
1974
|
+
import_fs12.default.writeFileSync(rcFile, content.slice(0, startIdx) + hookContent + content.slice(endIdx));
|
|
1663
1975
|
}
|
|
1664
1976
|
function removeHook(rcFile) {
|
|
1665
1977
|
const backup = `${rcFile}.workscribe-backup-${Date.now()}`;
|
|
1666
|
-
|
|
1667
|
-
log.info(`Backed up ${rcFile} \u2192 ${
|
|
1668
|
-
const content =
|
|
1978
|
+
import_fs12.default.copyFileSync(rcFile, backup);
|
|
1979
|
+
log.info(`Backed up ${rcFile} \u2192 ${import_path8.default.basename(backup)}`);
|
|
1980
|
+
const content = import_fs12.default.readFileSync(rcFile, "utf-8");
|
|
1669
1981
|
const startIdx = content.indexOf(HOOK_START);
|
|
1670
1982
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
1671
1983
|
const preambleIdx = content.lastIndexOf(HOOK_PREAMBLE, startIdx);
|
|
1672
1984
|
const blockStart = preambleIdx !== -1 ? preambleIdx : startIdx;
|
|
1673
1985
|
const before = content.slice(0, blockStart).replace(/\n+$/, "\n");
|
|
1674
1986
|
const after = content.slice(endIdx).replace(/^\n/, "");
|
|
1675
|
-
|
|
1987
|
+
import_fs12.default.writeFileSync(rcFile, before + after);
|
|
1988
|
+
}
|
|
1989
|
+
function resolveWorkscribeBinary() {
|
|
1990
|
+
try {
|
|
1991
|
+
const resolved = (0, import_child_process3.execFileSync)("which", ["workscribe"], { encoding: "utf-8" }).trim();
|
|
1992
|
+
return resolved || null;
|
|
1993
|
+
} catch {
|
|
1994
|
+
return null;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function injectBinaryPath(hookContent, binaryPath) {
|
|
1998
|
+
return hookContent.replace("command -v workscribe > /dev/null 2>&1 || return 0", `[[ -x "${binaryPath}" ]] || return 0`).replace("command -v workscribe &>/dev/null || return 0", `[[ -x "${binaryPath}" ]] || return 0`).replace("type -q workscribe\n or return 0", `test -x "${binaryPath}"
|
|
1999
|
+
or return 0`).replaceAll("command workscribe _capture", `"${binaryPath}" _capture`).replaceAll("command workscribe alerts", `"${binaryPath}" alerts`);
|
|
1676
2000
|
}
|
|
1677
2001
|
function isCompletionsInstalled(shell) {
|
|
1678
2002
|
if (shell.name === "fish") {
|
|
1679
|
-
return
|
|
2003
|
+
return import_fs12.default.existsSync(import_path8.default.join(FISH_COMPLETIONS_DIR, "workscribe.fish"));
|
|
1680
2004
|
}
|
|
1681
2005
|
try {
|
|
1682
|
-
return
|
|
2006
|
+
return import_fs12.default.readFileSync(shell.rcFile, "utf-8").includes("workscribe completions");
|
|
1683
2007
|
} catch {
|
|
1684
2008
|
return false;
|
|
1685
2009
|
}
|
|
1686
2010
|
}
|
|
1687
2011
|
function installCompletions(shell, commands) {
|
|
1688
2012
|
if (shell.name === "fish") {
|
|
1689
|
-
if (!
|
|
1690
|
-
|
|
2013
|
+
if (!import_fs12.default.existsSync(FISH_COMPLETIONS_DIR)) {
|
|
2014
|
+
import_fs12.default.mkdirSync(FISH_COMPLETIONS_DIR, { recursive: true });
|
|
1691
2015
|
}
|
|
1692
|
-
|
|
2016
|
+
import_fs12.default.writeFileSync(import_path8.default.join(FISH_COMPLETIONS_DIR, "workscribe.fish"), fishCompletion(commands), { mode: 420 });
|
|
1693
2017
|
} else {
|
|
1694
2018
|
const script = shell.name === "zsh" ? zshCompletion(commands) : bashCompletion(commands);
|
|
1695
|
-
const cacheFile =
|
|
1696
|
-
|
|
2019
|
+
const cacheFile = import_path8.default.join(WORKSCRIBE_DIR, `completion.${shell.name}`);
|
|
2020
|
+
import_fs12.default.writeFileSync(cacheFile, script, { mode: 420 });
|
|
1697
2021
|
const sourceLine = shell.name === "zsh" ? `
|
|
1698
2022
|
# Workscribe completions
|
|
1699
2023
|
autoload -Uz compinit && compinit
|
|
@@ -1702,20 +2026,20 @@ source "${cacheFile}"
|
|
|
1702
2026
|
# Workscribe completions
|
|
1703
2027
|
source "${cacheFile}"
|
|
1704
2028
|
`;
|
|
1705
|
-
|
|
2029
|
+
import_fs12.default.appendFileSync(shell.rcFile, sourceLine);
|
|
1706
2030
|
}
|
|
1707
2031
|
}
|
|
1708
2032
|
|
|
1709
2033
|
// src/capture/hooks/claude-code.ts
|
|
1710
|
-
var
|
|
1711
|
-
var
|
|
2034
|
+
var import_fs13 = __toESM(require("fs"));
|
|
2035
|
+
var import_path9 = __toESM(require("path"));
|
|
1712
2036
|
var import_os4 = __toESM(require("os"));
|
|
1713
2037
|
|
|
1714
2038
|
// src/capture/hooks/utils.ts
|
|
1715
|
-
var
|
|
2039
|
+
var import_child_process4 = require("child_process");
|
|
1716
2040
|
function isCommandInstalled(cmd) {
|
|
1717
2041
|
try {
|
|
1718
|
-
(0,
|
|
2042
|
+
(0, import_child_process4.execSync)(`which ${cmd}`, { stdio: "ignore" });
|
|
1719
2043
|
return true;
|
|
1720
2044
|
} catch {
|
|
1721
2045
|
return false;
|
|
@@ -1723,11 +2047,11 @@ function isCommandInstalled(cmd) {
|
|
|
1723
2047
|
}
|
|
1724
2048
|
|
|
1725
2049
|
// src/capture/hooks/claude-code.ts
|
|
1726
|
-
var CLAUDE_SETTINGS_PATH =
|
|
2050
|
+
var CLAUDE_SETTINGS_PATH = import_path9.default.join(import_os4.default.homedir(), ".claude", "settings.json");
|
|
1727
2051
|
function readSettings() {
|
|
1728
|
-
if (!
|
|
2052
|
+
if (!import_fs13.default.existsSync(CLAUDE_SETTINGS_PATH)) return {};
|
|
1729
2053
|
try {
|
|
1730
|
-
return JSON.parse(
|
|
2054
|
+
return JSON.parse(import_fs13.default.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
1731
2055
|
} catch {
|
|
1732
2056
|
return {};
|
|
1733
2057
|
}
|
|
@@ -1739,19 +2063,30 @@ var claudeCodeIntegration = {
|
|
|
1739
2063
|
},
|
|
1740
2064
|
isHookInstalled() {
|
|
1741
2065
|
const settings = readSettings();
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
2066
|
+
const hooks = settings.hooks ?? {};
|
|
2067
|
+
const postToolUse = hooks.PostToolUse ?? [];
|
|
2068
|
+
const hasPostToolUse = postToolUse.some((entry) => {
|
|
2069
|
+
const entryHooks = entry.hooks ?? [];
|
|
2070
|
+
return entryHooks.some((hookItem) => {
|
|
1746
2071
|
const hook = hookItem;
|
|
1747
2072
|
return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
|
|
1748
2073
|
});
|
|
1749
2074
|
});
|
|
2075
|
+
const userPromptSubmit = hooks.UserPromptSubmit ?? [];
|
|
2076
|
+
const hasUserPromptSubmit = userPromptSubmit.some((entry) => {
|
|
2077
|
+
const entryHooks = entry.hooks ?? [];
|
|
2078
|
+
return entryHooks.some((hookItem) => {
|
|
2079
|
+
const hook = hookItem;
|
|
2080
|
+
return typeof hook.command === "string" && hook.command.includes("workscribe _hook");
|
|
2081
|
+
});
|
|
2082
|
+
});
|
|
2083
|
+
return hasPostToolUse && hasUserPromptSubmit;
|
|
1750
2084
|
},
|
|
1751
2085
|
install() {
|
|
1752
2086
|
const settings = readSettings();
|
|
1753
2087
|
const existingHooks = settings.hooks ?? {};
|
|
1754
2088
|
const existingPostToolUse = existingHooks.PostToolUse ?? [];
|
|
2089
|
+
const existingUserPromptSubmit = existingHooks.UserPromptSubmit ?? [];
|
|
1755
2090
|
settings.hooks = {
|
|
1756
2091
|
...existingHooks,
|
|
1757
2092
|
PostToolUse: [
|
|
@@ -1760,10 +2095,21 @@ var claudeCodeIntegration = {
|
|
|
1760
2095
|
matcher: "Bash",
|
|
1761
2096
|
hooks: [{ type: "command", command: "workscribe _hook --source claude-code", async: true }]
|
|
1762
2097
|
}
|
|
2098
|
+
],
|
|
2099
|
+
UserPromptSubmit: [
|
|
2100
|
+
...existingUserPromptSubmit,
|
|
2101
|
+
{
|
|
2102
|
+
hooks: [
|
|
2103
|
+
{
|
|
2104
|
+
type: "command",
|
|
2105
|
+
command: "workscribe _hook --source claude-code --event UserPromptSubmit"
|
|
2106
|
+
}
|
|
2107
|
+
]
|
|
2108
|
+
}
|
|
1763
2109
|
]
|
|
1764
2110
|
};
|
|
1765
|
-
|
|
1766
|
-
|
|
2111
|
+
import_fs13.default.mkdirSync(import_path9.default.dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
|
|
2112
|
+
import_fs13.default.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
1767
2113
|
}
|
|
1768
2114
|
};
|
|
1769
2115
|
|
|
@@ -1794,11 +2140,11 @@ function registerCompletions(program2) {
|
|
|
1794
2140
|
// src/cli/init.ts
|
|
1795
2141
|
function detectShell() {
|
|
1796
2142
|
const shell = process.env.SHELL ?? "";
|
|
1797
|
-
if (shell.includes("zsh")) return { name: "zsh", rcFile:
|
|
1798
|
-
if (shell.includes("bash")) return { name: "bash", rcFile:
|
|
2143
|
+
if (shell.includes("zsh")) return { name: "zsh", rcFile: import_path10.default.join(import_os5.default.homedir(), ".zshrc") };
|
|
2144
|
+
if (shell.includes("bash")) return { name: "bash", rcFile: import_path10.default.join(import_os5.default.homedir(), ".bashrc") };
|
|
1799
2145
|
if (shell.includes("fish")) {
|
|
1800
|
-
const confDir =
|
|
1801
|
-
return { name: "fish", rcFile:
|
|
2146
|
+
const confDir = import_path10.default.join(import_os5.default.homedir(), ".config", "fish", "conf.d");
|
|
2147
|
+
return { name: "fish", rcFile: import_path10.default.join(confDir, "workscribe.fish") };
|
|
1802
2148
|
}
|
|
1803
2149
|
return null;
|
|
1804
2150
|
}
|
|
@@ -1812,19 +2158,19 @@ function registerInit(program2) {
|
|
|
1812
2158
|
log.plain(import_chalk2.default.bold("\n Workscribe \u2014 Developer Work Journal\n"));
|
|
1813
2159
|
step(1, TOTAL_STEPS, "Creating data directories");
|
|
1814
2160
|
for (const dir of REQUIRED_DIRS) {
|
|
1815
|
-
if (!
|
|
1816
|
-
|
|
2161
|
+
if (!import_fs14.default.existsSync(dir)) {
|
|
2162
|
+
import_fs14.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1817
2163
|
log.success(`Created ${dir}`);
|
|
1818
2164
|
} else {
|
|
1819
2165
|
try {
|
|
1820
|
-
|
|
2166
|
+
import_fs14.default.chmodSync(dir, 448);
|
|
1821
2167
|
} catch {
|
|
1822
2168
|
}
|
|
1823
2169
|
log.info(`${dir} already exists`);
|
|
1824
2170
|
}
|
|
1825
2171
|
}
|
|
1826
|
-
if (!
|
|
1827
|
-
|
|
2172
|
+
if (!import_fs14.default.existsSync(PROFILES_DIR)) {
|
|
2173
|
+
import_fs14.default.mkdirSync(PROFILES_DIR, { recursive: true, mode: 448 });
|
|
1828
2174
|
}
|
|
1829
2175
|
step(2, TOTAL_STEPS, "Initialising database");
|
|
1830
2176
|
getDb();
|
|
@@ -1837,16 +2183,18 @@ function registerInit(program2) {
|
|
|
1837
2183
|
` Manually add the hook from: ${import_chalk2.default.cyan("shell/hook.zsh")}, ${import_chalk2.default.cyan("shell/hook.bash")}, or ${import_chalk2.default.cyan("shell/hook.fish")}`
|
|
1838
2184
|
);
|
|
1839
2185
|
} else {
|
|
1840
|
-
const hookFile =
|
|
1841
|
-
if (!
|
|
2186
|
+
const hookFile = import_path10.default.join(__dirname, "..", "shell", `hook.${shell.name}`);
|
|
2187
|
+
if (!import_fs14.default.existsSync(hookFile)) {
|
|
1842
2188
|
log.warn(`Hook template not found at ${hookFile}`);
|
|
1843
2189
|
} else {
|
|
1844
|
-
const hookContent =
|
|
2190
|
+
const hookContent = import_fs14.default.readFileSync(hookFile, "utf-8");
|
|
2191
|
+
const binaryPath = resolveWorkscribeBinary();
|
|
2192
|
+
const hookWithPath = binaryPath ? injectBinaryPath(hookContent, binaryPath) : hookContent;
|
|
1845
2193
|
if (shell.name === "fish") {
|
|
1846
|
-
const confDir =
|
|
1847
|
-
if (!
|
|
2194
|
+
const confDir = import_path10.default.dirname(shell.rcFile);
|
|
2195
|
+
if (!import_fs14.default.existsSync(confDir)) import_fs14.default.mkdirSync(confDir, { recursive: true });
|
|
1848
2196
|
}
|
|
1849
|
-
if (!
|
|
2197
|
+
if (!import_fs14.default.existsSync(shell.rcFile)) import_fs14.default.writeFileSync(shell.rcFile, "");
|
|
1850
2198
|
if (!isHookInstalled(shell.rcFile)) {
|
|
1851
2199
|
log.plain("");
|
|
1852
2200
|
log.plain(import_chalk2.default.dim(" Workscribe adds a lightweight hook to your shell that captures each"));
|
|
@@ -1854,9 +2202,9 @@ function registerInit(program2) {
|
|
|
1854
2202
|
log.plain(import_chalk2.default.dim(" It runs in the background and adds no latency to your terminal."));
|
|
1855
2203
|
log.plain(import_chalk2.default.dim(" Credentials and secrets are redacted before anything is stored."));
|
|
1856
2204
|
log.plain("");
|
|
1857
|
-
installHook(shell.rcFile,
|
|
2205
|
+
installHook(shell.rcFile, hookWithPath);
|
|
1858
2206
|
log.success(`Shell hook installed in ${shell.rcFile}`);
|
|
1859
|
-
} else if (isHookOutdated(shell.rcFile,
|
|
2207
|
+
} else if (isHookOutdated(shell.rcFile, hookWithPath)) {
|
|
1860
2208
|
const { update } = await (0, import_prompts.default)({
|
|
1861
2209
|
type: "confirm",
|
|
1862
2210
|
name: "update",
|
|
@@ -1864,7 +2212,7 @@ function registerInit(program2) {
|
|
|
1864
2212
|
initial: true
|
|
1865
2213
|
});
|
|
1866
2214
|
if (update) {
|
|
1867
|
-
updateHook(shell.rcFile,
|
|
2215
|
+
updateHook(shell.rcFile, hookWithPath);
|
|
1868
2216
|
log.success(`Shell hook updated in ${shell.rcFile}`);
|
|
1869
2217
|
} else {
|
|
1870
2218
|
log.info("Shell hook update skipped");
|
|
@@ -2020,7 +2368,7 @@ function registerInit(program2) {
|
|
|
2020
2368
|
}
|
|
2021
2369
|
}
|
|
2022
2370
|
}
|
|
2023
|
-
if (!
|
|
2371
|
+
if (!import_fs14.default.existsSync(CONFIG_PATH)) {
|
|
2024
2372
|
writeConfig(config);
|
|
2025
2373
|
}
|
|
2026
2374
|
log.plain("");
|
|
@@ -2056,7 +2404,7 @@ function registerInit(program2) {
|
|
|
2056
2404
|
}
|
|
2057
2405
|
|
|
2058
2406
|
// src/cli/status.ts
|
|
2059
|
-
var
|
|
2407
|
+
var import_fs15 = __toESM(require("fs"));
|
|
2060
2408
|
var import_chalk3 = __toESM(require("chalk"));
|
|
2061
2409
|
function registerStatus(program2) {
|
|
2062
2410
|
program2.command("status").description("Show capture status and event counts").action(() => {
|
|
@@ -2064,7 +2412,7 @@ function registerStatus(program2) {
|
|
|
2064
2412
|
const total = getEventCount();
|
|
2065
2413
|
const todayEvents = getEventsForDate(todayDate());
|
|
2066
2414
|
const last = getLastEvent();
|
|
2067
|
-
const isPaused =
|
|
2415
|
+
const isPaused = import_fs15.default.existsSync(PAUSE_FLAG_PATH);
|
|
2068
2416
|
log.plain("");
|
|
2069
2417
|
log.plain(import_chalk3.default.bold(" Workscribe Status"));
|
|
2070
2418
|
log.plain("");
|
|
@@ -2087,6 +2435,13 @@ function registerStatus(program2) {
|
|
|
2087
2435
|
log.plain(
|
|
2088
2436
|
` Profiles ${activeProfiles.length > 0 ? import_chalk3.default.cyan(activeProfiles.join(", ")) : import_chalk3.default.dim("none")}`
|
|
2089
2437
|
);
|
|
2438
|
+
const pendingAlerts = getPendingAlerts();
|
|
2439
|
+
if (pendingAlerts.length > 0) {
|
|
2440
|
+
log.plain("");
|
|
2441
|
+
log.plain(
|
|
2442
|
+
` ${import_chalk3.default.yellow("!")} ${import_chalk3.default.yellow(String(pendingAlerts.length))} pending security alert${pendingAlerts.length === 1 ? "" : "s"}` + import_chalk3.default.dim(" (run: workscribe alerts)")
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2090
2445
|
const { total: weekTotal, otherCount } = getRecentOtherRate(7);
|
|
2091
2446
|
if (weekTotal > 0) {
|
|
2092
2447
|
const otherPct = Math.round(otherCount / weekTotal * 100);
|
|
@@ -2180,13 +2535,16 @@ function registerEvents(program2) {
|
|
|
2180
2535
|
var import_chalk5 = __toESM(require("chalk"));
|
|
2181
2536
|
|
|
2182
2537
|
// src/sessions/grouper.ts
|
|
2538
|
+
var LONG_RUNNING_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
2183
2539
|
function splitByIdleGap(events, idleMs) {
|
|
2184
2540
|
if (events.length === 0) return [];
|
|
2185
2541
|
const groups = [[events[0]]];
|
|
2186
2542
|
for (let i = 1; i < events.length; i++) {
|
|
2187
2543
|
const prev = new Date(events[i - 1].captured_at).getTime();
|
|
2188
|
-
const
|
|
2189
|
-
|
|
2544
|
+
const currCompleted = new Date(events[i].captured_at).getTime();
|
|
2545
|
+
const currDuration = events[i].duration_ms ?? 0;
|
|
2546
|
+
const currEffective = currDuration >= LONG_RUNNING_THRESHOLD_MS ? currCompleted - currDuration : currCompleted;
|
|
2547
|
+
if (currEffective - prev >= idleMs) {
|
|
2190
2548
|
groups.push([events[i]]);
|
|
2191
2549
|
} else {
|
|
2192
2550
|
groups[groups.length - 1].push(events[i]);
|
|
@@ -2194,13 +2552,17 @@ function splitByIdleGap(events, idleMs) {
|
|
|
2194
2552
|
}
|
|
2195
2553
|
return groups;
|
|
2196
2554
|
}
|
|
2197
|
-
|
|
2555
|
+
var REPO_SWITCH_GAP_MS = 10 * 60 * 1e3;
|
|
2556
|
+
function splitByRepo(events, gapMs = 0) {
|
|
2198
2557
|
if (events.length === 0) return [];
|
|
2199
2558
|
const groups = [[events[0]]];
|
|
2200
2559
|
for (let i = 1; i < events.length; i++) {
|
|
2201
2560
|
const prevRepo = events[i - 1].repo_name;
|
|
2202
2561
|
const currRepo = events[i].repo_name;
|
|
2203
|
-
|
|
2562
|
+
const prevTime = new Date(events[i - 1].captured_at).getTime();
|
|
2563
|
+
const currTime = new Date(events[i].captured_at).getTime();
|
|
2564
|
+
const gap = currTime - prevTime;
|
|
2565
|
+
if (currRepo !== prevRepo && gap >= gapMs) {
|
|
2204
2566
|
groups.push([events[i]]);
|
|
2205
2567
|
} else {
|
|
2206
2568
|
groups[groups.length - 1].push(events[i]);
|
|
@@ -2245,7 +2607,7 @@ function buildSessions(date, events, savedSummaries = /* @__PURE__ */ new Map())
|
|
|
2245
2607
|
const timeGroups = splitByIdleGap(events, idleMs);
|
|
2246
2608
|
for (const group of timeGroups) {
|
|
2247
2609
|
if (group.length === 0) continue;
|
|
2248
|
-
const repoGroups = splitByRepo(group);
|
|
2610
|
+
const repoGroups = splitByRepo(group, REPO_SWITCH_GAP_MS);
|
|
2249
2611
|
for (const repoGroup of repoGroups) {
|
|
2250
2612
|
if (repoGroup.length === 0) continue;
|
|
2251
2613
|
const first = repoGroup[0];
|
|
@@ -2324,9 +2686,9 @@ function registerSessions(program2) {
|
|
|
2324
2686
|
|
|
2325
2687
|
// src/cli/summary.ts
|
|
2326
2688
|
var import_chalk6 = __toESM(require("chalk"));
|
|
2327
|
-
var
|
|
2689
|
+
var import_fs16 = __toESM(require("fs"));
|
|
2328
2690
|
var import_os6 = __toESM(require("os"));
|
|
2329
|
-
var
|
|
2691
|
+
var import_path11 = __toESM(require("path"));
|
|
2330
2692
|
|
|
2331
2693
|
// src/integrations/webhook.ts
|
|
2332
2694
|
async function postToWebhook(url, text) {
|
|
@@ -2431,9 +2793,9 @@ function registerSummary(program2) {
|
|
|
2431
2793
|
return;
|
|
2432
2794
|
}
|
|
2433
2795
|
const cacheKey = isoWeekKey(today);
|
|
2434
|
-
const cacheFile =
|
|
2435
|
-
if (!opts.fresh &&
|
|
2436
|
-
const cached =
|
|
2796
|
+
const cacheFile = import_path11.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
|
|
2797
|
+
if (!opts.fresh && import_fs16.default.existsSync(cacheFile)) {
|
|
2798
|
+
const cached = import_fs16.default.readFileSync(cacheFile, "utf-8");
|
|
2437
2799
|
log.plain("");
|
|
2438
2800
|
log.plain(cached);
|
|
2439
2801
|
log.plain("");
|
|
@@ -2449,8 +2811,8 @@ function registerSummary(program2) {
|
|
|
2449
2811
|
log.plain("");
|
|
2450
2812
|
log.plain(summary);
|
|
2451
2813
|
log.plain("");
|
|
2452
|
-
|
|
2453
|
-
|
|
2814
|
+
import_fs16.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
|
|
2815
|
+
import_fs16.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
|
|
2454
2816
|
await handleWebhook(opts, config, summary);
|
|
2455
2817
|
} catch (error) {
|
|
2456
2818
|
log.error(`Weekly summary failed [${config.ai.provider}]: ${error.message}`);
|
|
@@ -2568,8 +2930,8 @@ ${bullets.trim()}`;
|
|
|
2568
2930
|
}
|
|
2569
2931
|
|
|
2570
2932
|
// src/cli/export.ts
|
|
2571
|
-
var
|
|
2572
|
-
var
|
|
2933
|
+
var import_fs17 = __toESM(require("fs"));
|
|
2934
|
+
var import_path12 = __toESM(require("path"));
|
|
2573
2935
|
function eventRowToPayload2(row) {
|
|
2574
2936
|
let metadata = {};
|
|
2575
2937
|
try {
|
|
@@ -2625,9 +2987,9 @@ function registerExport(program2) {
|
|
|
2625
2987
|
const payload = { date, sessions: sessionPayloads };
|
|
2626
2988
|
log.info(`Generating summary via ${config.ai.provider}\u2026`);
|
|
2627
2989
|
const summary = await provider.summarize(payload);
|
|
2628
|
-
const outputPath = opts.output ??
|
|
2629
|
-
if (!
|
|
2630
|
-
|
|
2990
|
+
const outputPath = opts.output ?? import_path12.default.join(EXPORTS_DIR, `workscribe-${date}.md`);
|
|
2991
|
+
if (!import_fs17.default.existsSync(EXPORTS_DIR)) import_fs17.default.mkdirSync(EXPORTS_DIR, { recursive: true, mode: 448 });
|
|
2992
|
+
import_fs17.default.writeFileSync(outputPath, `# Work Summary \u2014 ${date}
|
|
2631
2993
|
|
|
2632
2994
|
${summary}
|
|
2633
2995
|
`, { encoding: "utf-8", mode: 384 });
|
|
@@ -2699,22 +3061,22 @@ function printConfig(obj, indent) {
|
|
|
2699
3061
|
}
|
|
2700
3062
|
|
|
2701
3063
|
// src/cli/pause.ts
|
|
2702
|
-
var
|
|
3064
|
+
var import_fs18 = __toESM(require("fs"));
|
|
2703
3065
|
function registerPause(program2) {
|
|
2704
3066
|
program2.command("pause").description("Pause capture temporarily").action(() => {
|
|
2705
|
-
if (
|
|
3067
|
+
if (import_fs18.default.existsSync(PAUSE_FLAG_PATH)) {
|
|
2706
3068
|
log.info("Capture is already paused. Run workscribe resume to restart.");
|
|
2707
3069
|
return;
|
|
2708
3070
|
}
|
|
2709
|
-
|
|
3071
|
+
import_fs18.default.writeFileSync(PAUSE_FLAG_PATH, "", { mode: 384 });
|
|
2710
3072
|
log.success("Capture paused. Run workscribe resume to restart.");
|
|
2711
3073
|
});
|
|
2712
3074
|
program2.command("resume").description("Resume capture after a pause").action(() => {
|
|
2713
|
-
if (!
|
|
3075
|
+
if (!import_fs18.default.existsSync(PAUSE_FLAG_PATH)) {
|
|
2714
3076
|
log.info("Capture is not paused.");
|
|
2715
3077
|
return;
|
|
2716
3078
|
}
|
|
2717
|
-
|
|
3079
|
+
import_fs18.default.rmSync(PAUSE_FLAG_PATH);
|
|
2718
3080
|
log.success("Capture resumed.");
|
|
2719
3081
|
});
|
|
2720
3082
|
}
|
|
@@ -2799,19 +3161,19 @@ function registerClear(program2) {
|
|
|
2799
3161
|
}
|
|
2800
3162
|
|
|
2801
3163
|
// src/cli/uninstall.ts
|
|
2802
|
-
var
|
|
2803
|
-
var
|
|
3164
|
+
var import_fs19 = __toESM(require("fs"));
|
|
3165
|
+
var import_path13 = __toESM(require("path"));
|
|
2804
3166
|
var import_os7 = __toESM(require("os"));
|
|
2805
3167
|
var import_prompts3 = __toESM(require("prompts"));
|
|
2806
3168
|
var import_chalk9 = __toESM(require("chalk"));
|
|
2807
3169
|
function detectShellRcFiles() {
|
|
2808
3170
|
const candidates = [
|
|
2809
|
-
{ name: "zsh", rcFile:
|
|
2810
|
-
{ name: "bash", rcFile:
|
|
2811
|
-
{ name: "bash", rcFile:
|
|
2812
|
-
{ name: "fish", rcFile:
|
|
3171
|
+
{ name: "zsh", rcFile: import_path13.default.join(import_os7.default.homedir(), ".zshrc") },
|
|
3172
|
+
{ name: "bash", rcFile: import_path13.default.join(import_os7.default.homedir(), ".bashrc") },
|
|
3173
|
+
{ name: "bash", rcFile: import_path13.default.join(import_os7.default.homedir(), ".bash_profile") },
|
|
3174
|
+
{ name: "fish", rcFile: import_path13.default.join(import_os7.default.homedir(), ".config", "fish", "conf.d", "workscribe.fish") }
|
|
2813
3175
|
];
|
|
2814
|
-
return candidates.filter((candidate) =>
|
|
3176
|
+
return candidates.filter((candidate) => import_fs19.default.existsSync(candidate.rcFile) && isHookInstalled(candidate.rcFile));
|
|
2815
3177
|
}
|
|
2816
3178
|
function registerUninstall(program2) {
|
|
2817
3179
|
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) => {
|
|
@@ -2822,7 +3184,7 @@ function registerUninstall(program2) {
|
|
|
2822
3184
|
} else {
|
|
2823
3185
|
for (const { name, rcFile } of hooked) {
|
|
2824
3186
|
if (name === "fish") {
|
|
2825
|
-
|
|
3187
|
+
import_fs19.default.unlinkSync(rcFile);
|
|
2826
3188
|
log.success(`Shell hook removed: ${rcFile}`);
|
|
2827
3189
|
log.plain(` ${import_chalk9.default.dim("Restart your terminal to take effect")}`);
|
|
2828
3190
|
} else {
|
|
@@ -2836,7 +3198,7 @@ function registerUninstall(program2) {
|
|
|
2836
3198
|
}
|
|
2837
3199
|
}
|
|
2838
3200
|
if (opts.hooksOnly) return;
|
|
2839
|
-
const existingDirs = [...new Set(REQUIRED_DIRS)].filter((dir) =>
|
|
3201
|
+
const existingDirs = [...new Set(REQUIRED_DIRS)].filter((dir) => import_fs19.default.existsSync(dir));
|
|
2840
3202
|
if (existingDirs.length > 0) {
|
|
2841
3203
|
log.plain("");
|
|
2842
3204
|
const { deleteData } = await (0, import_prompts3.default)({
|
|
@@ -2847,7 +3209,7 @@ function registerUninstall(program2) {
|
|
|
2847
3209
|
});
|
|
2848
3210
|
if (deleteData) {
|
|
2849
3211
|
for (const dir of existingDirs) {
|
|
2850
|
-
|
|
3212
|
+
import_fs19.default.rmSync(dir, { recursive: true, force: true });
|
|
2851
3213
|
log.success(`Deleted ${dir}`);
|
|
2852
3214
|
}
|
|
2853
3215
|
} else {
|
|
@@ -2861,8 +3223,8 @@ function registerUninstall(program2) {
|
|
|
2861
3223
|
}
|
|
2862
3224
|
|
|
2863
3225
|
// src/cli/profile.ts
|
|
2864
|
-
var
|
|
2865
|
-
var
|
|
3226
|
+
var import_fs20 = __toESM(require("fs"));
|
|
3227
|
+
var import_path14 = __toESM(require("path"));
|
|
2866
3228
|
var import_chalk10 = __toESM(require("chalk"));
|
|
2867
3229
|
var MIN_SUGGEST_COUNT = 5;
|
|
2868
3230
|
function registerProfile(program2) {
|
|
@@ -2908,7 +3270,7 @@ function registerProfile(program2) {
|
|
|
2908
3270
|
log.plain(import_chalk10.default.dim(" " + "\u2500".repeat(60)));
|
|
2909
3271
|
for (const entry of entries) {
|
|
2910
3272
|
const badge = activeNames.has(entry.name) ? import_chalk10.default.green(" [active]") : "";
|
|
2911
|
-
const installed =
|
|
3273
|
+
const installed = import_fs20.default.existsSync(import_path14.default.join(PROFILES_DIR, `${entry.name}.json`)) ? import_chalk10.default.dim(" installed") : "";
|
|
2912
3274
|
log.plain(` ${import_chalk10.default.cyan(entry.name.padEnd(18))} ${entry.displayName.padEnd(28)}${badge}${installed}`);
|
|
2913
3275
|
if (entry.description) log.plain(` ${import_chalk10.default.dim(" ".repeat(18) + entry.description)}`);
|
|
2914
3276
|
}
|
|
@@ -2944,8 +3306,8 @@ function registerProfile(program2) {
|
|
|
2944
3306
|
continue;
|
|
2945
3307
|
}
|
|
2946
3308
|
activeNames.delete(name);
|
|
2947
|
-
const cachePath =
|
|
2948
|
-
if (
|
|
3309
|
+
const cachePath = import_path14.default.join(PROFILES_DIR, `${name}.json`);
|
|
3310
|
+
if (import_fs20.default.existsSync(cachePath)) import_fs20.default.unlinkSync(cachePath);
|
|
2949
3311
|
log.success(`Profile "${name}" removed`);
|
|
2950
3312
|
}
|
|
2951
3313
|
config.capture.profiles = [...activeNames];
|
|
@@ -3086,7 +3448,9 @@ function registerNote(program2) {
|
|
|
3086
3448
|
repo_name: repoName,
|
|
3087
3449
|
branch,
|
|
3088
3450
|
category: "note",
|
|
3089
|
-
metadata: JSON.stringify({ text })
|
|
3451
|
+
metadata: JSON.stringify({ text }),
|
|
3452
|
+
source: null,
|
|
3453
|
+
file_path: null
|
|
3090
3454
|
});
|
|
3091
3455
|
log.success(`Note saved${repoName ? ` to ${repoName}` : ""}`);
|
|
3092
3456
|
});
|
|
@@ -3094,8 +3458,8 @@ function registerNote(program2) {
|
|
|
3094
3458
|
|
|
3095
3459
|
// src/cli/week.ts
|
|
3096
3460
|
var import_chalk11 = __toESM(require("chalk"));
|
|
3097
|
-
var
|
|
3098
|
-
var
|
|
3461
|
+
var import_fs21 = __toESM(require("fs"));
|
|
3462
|
+
var import_path15 = __toESM(require("path"));
|
|
3099
3463
|
var import_os8 = __toESM(require("os"));
|
|
3100
3464
|
function eventRowToPayload3(row) {
|
|
3101
3465
|
let metadata = {};
|
|
@@ -3168,9 +3532,9 @@ function registerWeek(program2) {
|
|
|
3168
3532
|
return;
|
|
3169
3533
|
}
|
|
3170
3534
|
const cacheKey = isoWeekKey(today);
|
|
3171
|
-
const cacheFile =
|
|
3172
|
-
if (!opts.fresh &&
|
|
3173
|
-
const cached =
|
|
3535
|
+
const cacheFile = import_path15.default.join(WEEKLY_CACHE_DIR, `${cacheKey}.md`);
|
|
3536
|
+
if (!opts.fresh && import_fs21.default.existsSync(cacheFile)) {
|
|
3537
|
+
const cached = import_fs21.default.readFileSync(cacheFile, "utf-8");
|
|
3174
3538
|
log.plain("");
|
|
3175
3539
|
log.plain(cached);
|
|
3176
3540
|
log.plain("");
|
|
@@ -3186,8 +3550,8 @@ function registerWeek(program2) {
|
|
|
3186
3550
|
log.plain("");
|
|
3187
3551
|
log.plain(summary);
|
|
3188
3552
|
log.plain("");
|
|
3189
|
-
|
|
3190
|
-
|
|
3553
|
+
import_fs21.default.mkdirSync(WEEKLY_CACHE_DIR, { recursive: true });
|
|
3554
|
+
import_fs21.default.writeFileSync(cacheFile, summary, { encoding: "utf-8", mode: 384 });
|
|
3191
3555
|
await postSummary(opts, config, summary);
|
|
3192
3556
|
} catch (error) {
|
|
3193
3557
|
log.error(`Weekly digest failed [${config.ai.provider}]: ${error.message}`);
|
|
@@ -3197,15 +3561,15 @@ function registerWeek(program2) {
|
|
|
3197
3561
|
}
|
|
3198
3562
|
|
|
3199
3563
|
// src/cli/bug-report.ts
|
|
3200
|
-
var
|
|
3564
|
+
var import_fs22 = __toESM(require("fs"));
|
|
3201
3565
|
var import_os9 = __toESM(require("os"));
|
|
3202
|
-
var
|
|
3566
|
+
var import_path16 = __toESM(require("path"));
|
|
3203
3567
|
var import_chalk12 = __toESM(require("chalk"));
|
|
3204
3568
|
var GITHUB_ISSUES_URL = "https://github.com/workscribe/workscribe/issues/new";
|
|
3205
3569
|
var DEBUG_LOG_TAIL_LINES = 100;
|
|
3206
3570
|
function tailFile(filePath, lines) {
|
|
3207
|
-
if (!
|
|
3208
|
-
const content =
|
|
3571
|
+
if (!import_fs22.default.existsSync(filePath)) return "(no debug log found \u2014 set WORKSCRIBE_DEBUG=1 to enable)";
|
|
3572
|
+
const content = import_fs22.default.readFileSync(filePath, "utf-8");
|
|
3209
3573
|
const allLines = content.split("\n");
|
|
3210
3574
|
return allLines.slice(-lines).join("\n");
|
|
3211
3575
|
}
|
|
@@ -3223,9 +3587,9 @@ function redactConfig() {
|
|
|
3223
3587
|
function registerBugReport(program2) {
|
|
3224
3588
|
program2.command("bug-report").description("Generate a diagnostic report for filing a bug").action(() => {
|
|
3225
3589
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3226
|
-
const reportPath =
|
|
3590
|
+
const reportPath = import_path16.default.join(WORKSCRIBE_DIR, `bug-report-${timestamp}.txt`);
|
|
3227
3591
|
const systemInfo = [
|
|
3228
|
-
`Workscribe version : ${"1.
|
|
3592
|
+
`Workscribe version : ${"1.14.0"}`,
|
|
3229
3593
|
`Node.js version : ${process.version}`,
|
|
3230
3594
|
`Platform : ${process.platform}`,
|
|
3231
3595
|
`Architecture : ${process.arch}`,
|
|
@@ -3247,17 +3611,17 @@ function registerBugReport(program2) {
|
|
|
3247
3611
|
tailFile(DEBUG_LOG_PATH, DEBUG_LOG_TAIL_LINES)
|
|
3248
3612
|
].join("\n");
|
|
3249
3613
|
try {
|
|
3250
|
-
if (!
|
|
3251
|
-
|
|
3614
|
+
if (!import_fs22.default.existsSync(WORKSCRIBE_DIR)) {
|
|
3615
|
+
import_fs22.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true, mode: 448 });
|
|
3252
3616
|
}
|
|
3253
|
-
|
|
3617
|
+
import_fs22.default.writeFileSync(reportPath, report, { mode: 384 });
|
|
3254
3618
|
} catch (error) {
|
|
3255
3619
|
log.error(`Could not write report: ${error.message}`);
|
|
3256
3620
|
process.exit(1);
|
|
3257
3621
|
}
|
|
3258
3622
|
const params = new URLSearchParams({
|
|
3259
|
-
title: `Bug report \u2014 workscribe ${"1.
|
|
3260
|
-
body: `<!-- Attach ${
|
|
3623
|
+
title: `Bug report \u2014 workscribe ${"1.14.0"}`,
|
|
3624
|
+
body: `<!-- Attach ${import_path16.default.basename(reportPath)} or paste the relevant sections below -->
|
|
3261
3625
|
|
|
3262
3626
|
**Describe the issue:**
|
|
3263
3627
|
|
|
@@ -3275,19 +3639,142 @@ function registerBugReport(program2) {
|
|
|
3275
3639
|
});
|
|
3276
3640
|
}
|
|
3277
3641
|
|
|
3642
|
+
// src/cli/alerts.ts
|
|
3643
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
3644
|
+
function formatTimestamp(ms) {
|
|
3645
|
+
return new Date(ms).toLocaleString([], {
|
|
3646
|
+
month: "short",
|
|
3647
|
+
day: "numeric",
|
|
3648
|
+
hour: "2-digit",
|
|
3649
|
+
minute: "2-digit"
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3652
|
+
function registerAlerts(program2) {
|
|
3653
|
+
const alertsCmd = program2.command("alerts").description("Show and manage security alerts from AI session monitoring").option("--check", "Print warning if alerts are pending (used by shell hooks)").action((opts) => {
|
|
3654
|
+
if (opts.check) {
|
|
3655
|
+
if (hasPendingAlerts()) {
|
|
3656
|
+
log.plain(
|
|
3657
|
+
import_chalk13.default.yellow(" [workscribe] Possible credentials detected in an AI session.") + import_chalk13.default.dim(" Run workscribe alerts for details.")
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
const alerts = getPendingAlerts();
|
|
3663
|
+
if (alerts.length === 0) {
|
|
3664
|
+
log.plain("");
|
|
3665
|
+
log.plain(" No pending security alerts.");
|
|
3666
|
+
log.plain("");
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
log.plain("");
|
|
3670
|
+
log.plain(import_chalk13.default.bold(" Security Alerts") + import_chalk13.default.dim(` (${alerts.length} pending)`));
|
|
3671
|
+
log.plain("");
|
|
3672
|
+
for (const alert of alerts) {
|
|
3673
|
+
log.plain(` ${import_chalk13.default.yellow("!")} ${import_chalk13.default.dim(formatTimestamp(alert.created_at))} ${import_chalk13.default.cyan(alert.tool)}`);
|
|
3674
|
+
log.plain(` ${alert.context}`);
|
|
3675
|
+
if (alert.session_id) log.plain(` ${import_chalk13.default.dim("Session: " + alert.session_id)}`);
|
|
3676
|
+
log.plain("");
|
|
3677
|
+
}
|
|
3678
|
+
log.plain(import_chalk13.default.dim(" Run workscribe alerts clear to dismiss all alerts"));
|
|
3679
|
+
log.plain("");
|
|
3680
|
+
});
|
|
3681
|
+
alertsCmd.command("clear").description("Dismiss all pending security alerts").action(() => {
|
|
3682
|
+
const count = clearAlerts();
|
|
3683
|
+
if (count === 0) {
|
|
3684
|
+
log.plain(" No pending alerts to clear.");
|
|
3685
|
+
} else {
|
|
3686
|
+
log.plain(` ${count} alert${count === 1 ? "" : "s"} cleared.`);
|
|
3687
|
+
}
|
|
3688
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
// src/cli/context.ts
|
|
3692
|
+
var import_path17 = __toESM(require("path"));
|
|
3693
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
3694
|
+
function toContextJson(session) {
|
|
3695
|
+
return {
|
|
3696
|
+
date: session.date,
|
|
3697
|
+
startedAt: session.started_at,
|
|
3698
|
+
endedAt: session.ended_at,
|
|
3699
|
+
timeAgo: timeAgo(session.started_at),
|
|
3700
|
+
durationMinutes: (new Date(session.ended_at).getTime() - new Date(session.started_at).getTime()) / 6e4,
|
|
3701
|
+
branch: getSessionBranch(session.id),
|
|
3702
|
+
eventCount: session.event_count,
|
|
3703
|
+
summary: session.summary
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
function printSessionContext(session, heading) {
|
|
3707
|
+
const ago = timeAgo(session.started_at);
|
|
3708
|
+
const branch = getSessionBranch(session.id);
|
|
3709
|
+
const durationMinutes = (new Date(session.ended_at).getTime() - new Date(session.started_at).getTime()) / 6e4;
|
|
3710
|
+
log.plain("");
|
|
3711
|
+
log.plain(` ${import_chalk14.default.bold(heading)}: ${import_chalk14.default.cyan(ago)}`);
|
|
3712
|
+
log.plain("");
|
|
3713
|
+
log.plain(` Date ${session.date}`);
|
|
3714
|
+
log.plain(` Duration ${formatDuration(durationMinutes)}`);
|
|
3715
|
+
if (branch) log.plain(` Branch ${import_chalk14.default.dim(branch)}`);
|
|
3716
|
+
log.plain(` Events ${session.event_count}`);
|
|
3717
|
+
if (session.summary) {
|
|
3718
|
+
log.plain("");
|
|
3719
|
+
for (const line of session.summary.split("\n")) {
|
|
3720
|
+
log.plain(` ${line}`);
|
|
3721
|
+
}
|
|
3722
|
+
} else {
|
|
3723
|
+
log.plain("");
|
|
3724
|
+
log.plain(import_chalk14.default.dim(` No summary \u2014 run: workscribe summary ${session.date}`));
|
|
3725
|
+
}
|
|
3726
|
+
log.plain("");
|
|
3727
|
+
}
|
|
3728
|
+
function registerContext(program2) {
|
|
3729
|
+
program2.command("context").description("Show the last session for the current repo").option("--file <path>", "narrow to last session that touched this file").option("--json", "output as JSON instead of formatted text \u2014 null when no session is found").action((opts) => {
|
|
3730
|
+
if (opts.file) {
|
|
3731
|
+
const normalizedFile = import_path17.default.normalize(opts.file);
|
|
3732
|
+
const session = getLastSessionTouchingFile(normalizedFile);
|
|
3733
|
+
if (!session) {
|
|
3734
|
+
if (opts.json) log.plain(JSON.stringify(null));
|
|
3735
|
+
else log.plain(` No sessions found touching ${normalizedFile}`);
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
if (opts.json) {
|
|
3739
|
+
log.plain(JSON.stringify(toContextJson(session)));
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
printSessionContext(session, `Last session touching ${import_path17.default.basename(normalizedFile)}`);
|
|
3743
|
+
return;
|
|
3744
|
+
}
|
|
3745
|
+
const { repoName } = getGitContext(process.cwd(), null, readConfig().git.qualifyRepoName);
|
|
3746
|
+
if (!repoName) {
|
|
3747
|
+
if (opts.json) log.plain(JSON.stringify(null));
|
|
3748
|
+
else log.plain(" Not in a git repository. Run workscribe context from a project directory.");
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
const sessions = getRecentSessionsByRepo(repoName, 1);
|
|
3752
|
+
if (sessions.length === 0) {
|
|
3753
|
+
if (opts.json) log.plain(JSON.stringify(null));
|
|
3754
|
+
else log.plain(` No sessions found for ${repoName}`);
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
if (opts.json) {
|
|
3758
|
+
log.plain(JSON.stringify(toContextJson(sessions[0])));
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
printSessionContext(sessions[0], `Last session on ${repoName}`);
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3278
3765
|
// src/utils/update-check.ts
|
|
3279
|
-
var
|
|
3766
|
+
var import_fs23 = __toESM(require("fs"));
|
|
3280
3767
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
3281
3768
|
function readCache() {
|
|
3282
3769
|
try {
|
|
3283
|
-
return JSON.parse(
|
|
3770
|
+
return JSON.parse(import_fs23.default.readFileSync(UPDATE_CHECK_PATH, "utf-8"));
|
|
3284
3771
|
} catch {
|
|
3285
3772
|
return null;
|
|
3286
3773
|
}
|
|
3287
3774
|
}
|
|
3288
3775
|
function writeCache(cache) {
|
|
3289
3776
|
try {
|
|
3290
|
-
|
|
3777
|
+
import_fs23.default.writeFileSync(UPDATE_CHECK_PATH, JSON.stringify(cache), { mode: 384 });
|
|
3291
3778
|
} catch {
|
|
3292
3779
|
}
|
|
3293
3780
|
}
|
|
@@ -3300,7 +3787,7 @@ function isNewer(latest, current) {
|
|
|
3300
3787
|
return latestPatch > currentPatch;
|
|
3301
3788
|
}
|
|
3302
3789
|
function checkForUpdate() {
|
|
3303
|
-
const current = "1.
|
|
3790
|
+
const current = "1.14.0";
|
|
3304
3791
|
const cache = readCache();
|
|
3305
3792
|
if (cache && isNewer(cache.latestVersion, current)) {
|
|
3306
3793
|
process.stderr.write(
|
|
@@ -3325,7 +3812,7 @@ function checkForUpdate() {
|
|
|
3325
3812
|
assertNodeVersion(process.versions.node);
|
|
3326
3813
|
checkForUpdate();
|
|
3327
3814
|
var program = new import_commander.Command();
|
|
3328
|
-
program.name("workscribe").description("Automatic developer work journal").version("1.
|
|
3815
|
+
program.name("workscribe").description("Automatic developer work journal").version("1.14.0");
|
|
3329
3816
|
registerCapture(program);
|
|
3330
3817
|
registerHook(program);
|
|
3331
3818
|
registerInit(program);
|
|
@@ -3344,5 +3831,7 @@ registerNote(program);
|
|
|
3344
3831
|
registerWeek(program);
|
|
3345
3832
|
registerBugReport(program);
|
|
3346
3833
|
registerCompletions(program);
|
|
3834
|
+
registerAlerts(program);
|
|
3835
|
+
registerContext(program);
|
|
3347
3836
|
program.parse();
|
|
3348
3837
|
//# sourceMappingURL=index.js.map
|