@workscribe/cli 1.6.2 → 1.7.1
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 +46 -4
- package/dist/index.js +108 -51
- package/dist/index.js.map +1 -1
- package/package.json +7 -13
- package/shell/hook.bash +1 -1
- package/shell/hook.zsh +2 -2
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ shell hook → capture → SQLite → session grouper → AI provider → markdo
|
|
|
29
29
|
|
|
30
30
|
| Dependency | Version | Notes |
|
|
31
31
|
|---|---|---|
|
|
32
|
-
| Node.js | **22 or later** | Uses the built-in `node:sqlite` module
|
|
32
|
+
| Node.js | **22 or later** | Uses the built-in `node:sqlite` module introduced in Node 22.5 |
|
|
33
33
|
| Shell | zsh or bash | Hook is appended to `~/.zshrc` or `~/.bashrc` by `workscribe init` |
|
|
34
34
|
| OS | macOS or Linux | Windows is not supported — the shell hook requires zsh or bash. WSL2 works. |
|
|
35
35
|
|
|
@@ -47,6 +47,21 @@ source ~/.zshrc # or restart your terminal
|
|
|
47
47
|
|
|
48
48
|
`workscribe init` walks you through choosing an AI provider, installs the shell hook, and offers to install a Claude Code session capture hook if Claude Code is detected. Safe to re-run — updates outdated hooks and skips steps already complete.
|
|
49
49
|
|
|
50
|
+
## Uninstalling
|
|
51
|
+
|
|
52
|
+
If the CLI is still installed:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
workscribe uninstall # removes shell hooks, optionally deletes ~/.workscribe/
|
|
56
|
+
npm uninstall -g @workscribe/cli
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If you've already removed the binary and still see `_workscribe_precmd` errors in your terminal, remove the hook block manually:
|
|
60
|
+
|
|
61
|
+
1. Open `~/.zshrc` (or `~/.bashrc`)
|
|
62
|
+
2. Delete everything between `# workscribe:hook:start` and `# workscribe:hook:end` (inclusive)
|
|
63
|
+
3. Run `source ~/.zshrc` or restart your terminal
|
|
64
|
+
|
|
50
65
|
## AI providers
|
|
51
66
|
|
|
52
67
|
Workscribe works with any of these — switch at any time via config:
|
|
@@ -68,14 +83,22 @@ workscribe config set ai.apiKey <your-key>
|
|
|
68
83
|
|
|
69
84
|
| Command | Description |
|
|
70
85
|
|---|---|
|
|
86
|
+
| `workscribe init` | Set up Workscribe, install shell hooks, configure AI provider |
|
|
87
|
+
| `workscribe uninstall` | Remove shell hooks and optionally delete all local data |
|
|
71
88
|
| `workscribe summary` | Generate today's work summary |
|
|
72
89
|
| `workscribe summary --date 2026-05-20` | Summary for a specific date |
|
|
73
90
|
| `workscribe summary --week` | Summary for the current week |
|
|
74
91
|
| `workscribe summary --fresh` | Bypass cache and regenerate |
|
|
75
92
|
| `workscribe summary --slack` | Post summary to configured Slack channel |
|
|
76
|
-
| `workscribe
|
|
77
|
-
| `workscribe
|
|
93
|
+
| `workscribe summary --webhook <url>` | Post summary to a custom webhook URL |
|
|
94
|
+
| `workscribe sessions` | List detected work sessions for today |
|
|
95
|
+
| `workscribe sessions --date 2026-05-20` | Sessions for a specific date |
|
|
96
|
+
| `workscribe events` | List captured events for today |
|
|
97
|
+
| `workscribe events --date 2026-05-20` | Events for a specific date |
|
|
98
|
+
| `workscribe events --limit 20` | Show only the most recent N events |
|
|
78
99
|
| `workscribe export` | Save today's summary as a markdown file |
|
|
100
|
+
| `workscribe export --date 2026-05-20` | Export summary for a specific date |
|
|
101
|
+
| `workscribe export --output <path>` | Write to a custom file path |
|
|
79
102
|
| `workscribe status` | Check capture is active and show event counts |
|
|
80
103
|
| `workscribe undo` | Remove the last captured event |
|
|
81
104
|
| `workscribe undo -n 5` | Remove the last 5 captured events |
|
|
@@ -83,9 +106,11 @@ workscribe config set ai.apiKey <your-key>
|
|
|
83
106
|
| `workscribe clear` | Delete all captured events for today |
|
|
84
107
|
| `workscribe clear --date 2026-05-20` | Delete all events for a specific date |
|
|
85
108
|
| `workscribe clear --dry-run` | Preview what would be cleared without deleting |
|
|
109
|
+
| `workscribe clear --yes` | Skip the confirmation prompt |
|
|
86
110
|
| `workscribe pause` | Pause capture temporarily |
|
|
87
111
|
| `workscribe resume` | Resume capture after a pause |
|
|
88
112
|
| `workscribe config` | View current configuration |
|
|
113
|
+
| `workscribe config get KEY` | Print a single config value |
|
|
89
114
|
| `workscribe config set KEY VALUE` | Update a config value |
|
|
90
115
|
|
|
91
116
|
## Privacy
|
|
@@ -102,16 +127,33 @@ workscribe config set ai.apiKey <your-key>
|
|
|
102
127
|
workscribe config set session.idleTimeout 20 # minutes before a new session starts (default: 30)
|
|
103
128
|
workscribe config set ai.model gpt-4o # override the default model for your provider
|
|
104
129
|
workscribe config set redact.extra "MY_PATTERN" # add a custom redaction regex
|
|
130
|
+
workscribe config set summary.format json # output summary as JSON instead of markdown
|
|
131
|
+
workscribe config set capture.ignore "mycommand" # suppress a command from being captured
|
|
105
132
|
|
|
106
133
|
# Override the display name for a local clone whose folder name differs from the repo
|
|
107
134
|
workscribe config set projects./Users/me/sandbox/myrepo actual-repo-name
|
|
108
135
|
|
|
109
136
|
# Post summaries to Slack or a custom webhook
|
|
110
137
|
workscribe config set integrations.slackWebhook https://hooks.slack.com/services/...
|
|
138
|
+
workscribe config set integrations.webhook https://custom.endpoint/hook # persistent default webhook
|
|
111
139
|
workscribe summary --slack
|
|
112
|
-
workscribe summary --webhook https://custom.endpoint/hook
|
|
140
|
+
workscribe summary --webhook https://custom.endpoint/hook # one-off override
|
|
113
141
|
```
|
|
114
142
|
|
|
143
|
+
| Config key | Default | Description |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `ai.provider` | — | AI provider: `anthropic`, `openai`, `ollama`, `openai-compatible` |
|
|
146
|
+
| `ai.apiKey` | — | API key for the configured provider |
|
|
147
|
+
| `ai.model` | provider default | Model name override |
|
|
148
|
+
| `ai.baseUrl` | — | Base URL for `openai-compatible` providers |
|
|
149
|
+
| `session.idleTimeout` | `30` | Minutes of inactivity before a new session starts |
|
|
150
|
+
| `capture.ignore` | common shell builtins | Commands to exclude from capture |
|
|
151
|
+
| `redact.extra` | `[]` | Additional regex patterns to redact from captured commands |
|
|
152
|
+
| `summary.format` | `markdown` | Output format: `markdown` or `json` |
|
|
153
|
+
| `integrations.slackWebhook` | — | Slack incoming webhook URL |
|
|
154
|
+
| `integrations.webhook` | — | Default webhook URL for `--webhook` |
|
|
155
|
+
| `projects.<path>` | — | Display name override for a repo by absolute path |
|
|
156
|
+
|
|
115
157
|
## Slack integration
|
|
116
158
|
|
|
117
159
|
Post your daily or weekly summary directly to a Slack channel:
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ var import_commander = require("commander");
|
|
|
30
30
|
var import_fs4 = __toESM(require("fs"));
|
|
31
31
|
|
|
32
32
|
// src/db/index.ts
|
|
33
|
+
var import_node_sqlite = require("node:sqlite");
|
|
33
34
|
var import_fs = __toESM(require("fs"));
|
|
34
35
|
|
|
35
36
|
// src/utils/paths.ts
|
|
@@ -43,42 +44,11 @@ var EXPORTS_DIR = import_path.default.join(WORKSCRIBE_DIR, "exports");
|
|
|
43
44
|
var PAUSE_FLAG_PATH = import_path.default.join(WORKSCRIBE_DIR, "paused");
|
|
44
45
|
var WEEKLY_CACHE_DIR = import_path.default.join(WORKSCRIBE_DIR, "weekly-cache");
|
|
45
46
|
|
|
46
|
-
// src/db/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
} catch {
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
const Database = require("better-sqlite3");
|
|
55
|
-
return new Database(path9);
|
|
56
|
-
} catch {
|
|
57
|
-
}
|
|
58
|
-
throw new Error(
|
|
59
|
-
"workscribe requires a SQLite driver.\n Node 22.5+: node:sqlite is used automatically (no action needed).\n Node 20\u201321: install the optional peer dependency:\n npm install better-sqlite3"
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/db/index.ts
|
|
64
|
-
var _db = null;
|
|
65
|
-
function getDb() {
|
|
66
|
-
if (_db) return _db;
|
|
67
|
-
if (!import_fs.default.existsSync(WORKSCRIBE_DIR)) {
|
|
68
|
-
import_fs.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true });
|
|
69
|
-
}
|
|
70
|
-
_db = openDatabase(DB_PATH);
|
|
71
|
-
try {
|
|
72
|
-
import_fs.default.chmodSync(DB_PATH, 384);
|
|
73
|
-
} catch {
|
|
74
|
-
}
|
|
75
|
-
_db.exec("PRAGMA journal_mode = WAL");
|
|
76
|
-
_db.exec("PRAGMA foreign_keys = ON");
|
|
77
|
-
runMigrations(_db);
|
|
78
|
-
return _db;
|
|
79
|
-
}
|
|
80
|
-
function runMigrations(db) {
|
|
81
|
-
db.exec(`
|
|
47
|
+
// src/db/migrations/001_initial_schema.ts
|
|
48
|
+
var initial_schema_default = {
|
|
49
|
+
version: 1,
|
|
50
|
+
description: "initial schema",
|
|
51
|
+
sql: `
|
|
82
52
|
CREATE TABLE IF NOT EXISTS events (
|
|
83
53
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
54
|
captured_at TEXT NOT NULL,
|
|
@@ -112,7 +82,64 @@ function runMigrations(db) {
|
|
|
112
82
|
event_id INTEGER NOT NULL REFERENCES events(id),
|
|
113
83
|
PRIMARY KEY (session_id, event_id)
|
|
114
84
|
);
|
|
85
|
+
`
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// src/db/migrations/index.ts
|
|
89
|
+
var MIGRATIONS = [initial_schema_default];
|
|
90
|
+
|
|
91
|
+
// src/db/index.ts
|
|
92
|
+
var _db = null;
|
|
93
|
+
function getDb() {
|
|
94
|
+
if (_db) return _db;
|
|
95
|
+
if (!import_fs.default.existsSync(WORKSCRIBE_DIR)) {
|
|
96
|
+
import_fs.default.mkdirSync(WORKSCRIBE_DIR, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
const db = new import_node_sqlite.DatabaseSync(DB_PATH);
|
|
99
|
+
try {
|
|
100
|
+
import_fs.default.chmodSync(DB_PATH, 384);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
104
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
105
|
+
applyMigrations(db);
|
|
106
|
+
_db = db;
|
|
107
|
+
return _db;
|
|
108
|
+
}
|
|
109
|
+
function applyMigrations(db) {
|
|
110
|
+
db.exec(`
|
|
111
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
112
|
+
version INTEGER PRIMARY KEY,
|
|
113
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
114
|
+
)
|
|
115
115
|
`);
|
|
116
|
+
const hasEvents = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='events'").get();
|
|
117
|
+
const hasVersion1 = db.prepare("SELECT version FROM schema_migrations WHERE version = 1").get();
|
|
118
|
+
if (hasEvents && !hasVersion1) {
|
|
119
|
+
db.prepare("INSERT INTO schema_migrations (version, applied_at) VALUES ($version, datetime('now'))").run({
|
|
120
|
+
version: 1
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const applied = new Set(
|
|
124
|
+
db.prepare("SELECT version FROM schema_migrations").all().map((r) => r.version)
|
|
125
|
+
);
|
|
126
|
+
for (const migration of MIGRATIONS) {
|
|
127
|
+
if (applied.has(migration.version)) continue;
|
|
128
|
+
db.exec("BEGIN");
|
|
129
|
+
try {
|
|
130
|
+
db.exec(migration.sql);
|
|
131
|
+
db.prepare("INSERT INTO schema_migrations (version, applied_at) VALUES ($version, datetime('now'))").run({
|
|
132
|
+
version: migration.version
|
|
133
|
+
});
|
|
134
|
+
db.exec("COMMIT");
|
|
135
|
+
} catch (err) {
|
|
136
|
+
try {
|
|
137
|
+
db.exec("ROLLBACK");
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Migration ${migration.version} (${migration.description}) failed: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
116
143
|
}
|
|
117
144
|
function insertEvent(params) {
|
|
118
145
|
const db = getDb();
|
|
@@ -131,7 +158,7 @@ function getEventsForDate(date) {
|
|
|
131
158
|
const db = getDb();
|
|
132
159
|
const stmt = db.prepare(`
|
|
133
160
|
SELECT * FROM events
|
|
134
|
-
WHERE date(captured_at) = $date
|
|
161
|
+
WHERE date(captured_at, 'localtime') = $date
|
|
135
162
|
ORDER BY captured_at ASC
|
|
136
163
|
`);
|
|
137
164
|
return stmt.all({ date });
|
|
@@ -158,6 +185,16 @@ function insertSessionEvent(sessionId, eventId) {
|
|
|
158
185
|
`);
|
|
159
186
|
stmt.run({ session_id: sessionId, event_id: eventId });
|
|
160
187
|
}
|
|
188
|
+
function getEventsBySessionId(sessionId) {
|
|
189
|
+
const db = getDb();
|
|
190
|
+
const stmt = db.prepare(`
|
|
191
|
+
SELECT e.* FROM events e
|
|
192
|
+
INNER JOIN session_events se ON se.event_id = e.id
|
|
193
|
+
WHERE se.session_id = $session_id
|
|
194
|
+
ORDER BY e.captured_at ASC
|
|
195
|
+
`);
|
|
196
|
+
return stmt.all({ session_id: sessionId });
|
|
197
|
+
}
|
|
161
198
|
function getSessionsForDate(date) {
|
|
162
199
|
const db = getDb();
|
|
163
200
|
const stmt = db.prepare("SELECT * FROM sessions WHERE date = $date ORDER BY started_at ASC");
|
|
@@ -183,7 +220,7 @@ function deleteEventsByIds(ids) {
|
|
|
183
220
|
if (ids.length === 0) return;
|
|
184
221
|
const db = getDb();
|
|
185
222
|
const placeholders = ids.map(() => "?").join(", ");
|
|
186
|
-
const affectedDates = db.prepare(`SELECT DISTINCT date(captured_at) as date FROM events WHERE id IN (${placeholders})`).all(...ids);
|
|
223
|
+
const affectedDates = db.prepare(`SELECT DISTINCT date(captured_at, 'localtime') as date FROM events WHERE id IN (${placeholders})`).all(...ids);
|
|
187
224
|
db.prepare(`DELETE FROM events WHERE id IN (${placeholders})`).run(...ids);
|
|
188
225
|
for (const { date } of affectedDates) {
|
|
189
226
|
clearSessionsForDate(date);
|
|
@@ -683,6 +720,7 @@ function captureCommand(opts) {
|
|
|
683
720
|
const nameOverride = config.projects?.[cwd] ?? null;
|
|
684
721
|
const { repoName, branch } = getGitContext(cwd, nameOverride);
|
|
685
722
|
const { category, metadata } = normalizeCommand(safeCommand);
|
|
723
|
+
if (opts.source) metadata.source = opts.source;
|
|
686
724
|
if (category === "ai_session" && opts.gitBefore) {
|
|
687
725
|
const commits = getCommitsSince(opts.gitBefore, cwd);
|
|
688
726
|
if (commits.length > 0) metadata.commits = commits;
|
|
@@ -750,7 +788,7 @@ function parseHookPayload(raw) {
|
|
|
750
788
|
return { command, cwd };
|
|
751
789
|
}
|
|
752
790
|
function registerHook(program2) {
|
|
753
|
-
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(() => {
|
|
791
|
+
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) => {
|
|
754
792
|
let raw = "";
|
|
755
793
|
process.stdin.setEncoding("utf-8");
|
|
756
794
|
process.stdin.on("data", (chunk) => {
|
|
@@ -760,7 +798,13 @@ function registerHook(program2) {
|
|
|
760
798
|
try {
|
|
761
799
|
const parsed = parseHookPayload(raw);
|
|
762
800
|
if (!parsed) return;
|
|
763
|
-
captureCommand({
|
|
801
|
+
captureCommand({
|
|
802
|
+
command: parsed.command,
|
|
803
|
+
cwd: parsed.cwd,
|
|
804
|
+
exitCode: null,
|
|
805
|
+
durationMs: null,
|
|
806
|
+
source: opts.source
|
|
807
|
+
});
|
|
764
808
|
} catch (err) {
|
|
765
809
|
debugLog("_hook", err);
|
|
766
810
|
}
|
|
@@ -780,6 +824,7 @@ var import_fs6 = __toESM(require("fs"));
|
|
|
780
824
|
var import_path3 = __toESM(require("path"));
|
|
781
825
|
var HOOK_START = "# workscribe:hook:start";
|
|
782
826
|
var HOOK_END = "# workscribe:hook:end";
|
|
827
|
+
var HOOK_PREAMBLE = "# Workscribe \u2014 developer work journal";
|
|
783
828
|
function getInstalledHookBlock(rcFile) {
|
|
784
829
|
if (!import_fs6.default.existsSync(rcFile)) return null;
|
|
785
830
|
const content = import_fs6.default.readFileSync(rcFile, "utf-8");
|
|
@@ -820,7 +865,9 @@ function removeHook(rcFile) {
|
|
|
820
865
|
const content = import_fs6.default.readFileSync(rcFile, "utf-8");
|
|
821
866
|
const startIdx = content.indexOf(HOOK_START);
|
|
822
867
|
const endIdx = content.indexOf(HOOK_END) + HOOK_END.length;
|
|
823
|
-
const
|
|
868
|
+
const preambleIdx = content.lastIndexOf(HOOK_PREAMBLE, startIdx);
|
|
869
|
+
const blockStart = preambleIdx !== -1 ? preambleIdx : startIdx;
|
|
870
|
+
const before = content.slice(0, blockStart).replace(/\n+$/, "\n");
|
|
824
871
|
const after = content.slice(endIdx).replace(/^\n/, "");
|
|
825
872
|
import_fs6.default.writeFileSync(rcFile, before + after);
|
|
826
873
|
}
|
|
@@ -1043,7 +1090,7 @@ function formatDate(isoString) {
|
|
|
1043
1090
|
return new Date(isoString).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
1044
1091
|
}
|
|
1045
1092
|
function todayDate() {
|
|
1046
|
-
return (/* @__PURE__ */ new Date())
|
|
1093
|
+
return localDateStr(/* @__PURE__ */ new Date());
|
|
1047
1094
|
}
|
|
1048
1095
|
function parseDate(input) {
|
|
1049
1096
|
const d = /* @__PURE__ */ new Date(input + "T00:00:00");
|
|
@@ -1237,14 +1284,17 @@ function getOrBuildSessions(date) {
|
|
|
1237
1284
|
durationMinutes: minutesBetween(s.started_at, s.ended_at),
|
|
1238
1285
|
eventCount: s.event_count,
|
|
1239
1286
|
summary: s.summary,
|
|
1240
|
-
events:
|
|
1241
|
-
// not needed when using cached
|
|
1287
|
+
events: getEventsBySessionId(s.id)
|
|
1242
1288
|
}));
|
|
1243
1289
|
}
|
|
1290
|
+
const savedSummaries = /* @__PURE__ */ new Map();
|
|
1291
|
+
for (const s of existing) {
|
|
1292
|
+
if (s.summary) savedSummaries.set(`${s.repo_name}|${s.started_at}`, s.summary);
|
|
1293
|
+
}
|
|
1244
1294
|
clearSessionsForDate(date);
|
|
1245
|
-
return buildSessions(date, events);
|
|
1295
|
+
return buildSessions(date, events, savedSummaries);
|
|
1246
1296
|
}
|
|
1247
|
-
function buildSessions(date, events) {
|
|
1297
|
+
function buildSessions(date, events, savedSummaries = /* @__PURE__ */ new Map()) {
|
|
1248
1298
|
const config = readConfig();
|
|
1249
1299
|
const idleMs = config.session.idleTimeout * 60 * 1e3;
|
|
1250
1300
|
const sessions = [];
|
|
@@ -1257,13 +1307,14 @@ function buildSessions(date, events) {
|
|
|
1257
1307
|
const first = repoGroup[0];
|
|
1258
1308
|
const last = repoGroup[repoGroup.length - 1];
|
|
1259
1309
|
const repoName = last.repo_name ?? first.repo_name ?? null;
|
|
1310
|
+
const summary = savedSummaries.get(`${repoName}|${first.captured_at}`) ?? null;
|
|
1260
1311
|
const sessionId = insertSession({
|
|
1261
1312
|
date,
|
|
1262
1313
|
started_at: first.captured_at,
|
|
1263
1314
|
ended_at: last.captured_at,
|
|
1264
1315
|
repo_name: repoName,
|
|
1265
1316
|
event_count: repoGroup.length,
|
|
1266
|
-
summary
|
|
1317
|
+
summary
|
|
1267
1318
|
});
|
|
1268
1319
|
for (const event of repoGroup) {
|
|
1269
1320
|
insertSessionEvent(sessionId, event.id);
|
|
@@ -1276,7 +1327,7 @@ function buildSessions(date, events) {
|
|
|
1276
1327
|
repoName,
|
|
1277
1328
|
durationMinutes: minutesBetween(first.captured_at, last.captured_at),
|
|
1278
1329
|
eventCount: repoGroup.length,
|
|
1279
|
-
summary
|
|
1330
|
+
summary,
|
|
1280
1331
|
events: repoGroup
|
|
1281
1332
|
});
|
|
1282
1333
|
}
|
|
@@ -1940,7 +1991,12 @@ function registerConfig(program2) {
|
|
|
1940
1991
|
if (value === void 0) {
|
|
1941
1992
|
log.warn(`Key not found: ${key}`);
|
|
1942
1993
|
} else {
|
|
1943
|
-
|
|
1994
|
+
const lastSegment = key.split(".").pop() ?? "";
|
|
1995
|
+
if (SENSITIVE_KEYS.has(lastSegment) && typeof value === "string" && value.length > 0) {
|
|
1996
|
+
log.plain(`${value.slice(0, 4)}${"*".repeat(8)} (${value.length} chars)`);
|
|
1997
|
+
} else {
|
|
1998
|
+
log.plain(String(value));
|
|
1999
|
+
}
|
|
1944
2000
|
}
|
|
1945
2001
|
});
|
|
1946
2002
|
}
|
|
@@ -2088,7 +2144,7 @@ function detectShellRcFiles() {
|
|
|
2088
2144
|
return candidates.filter((c) => import_fs13.default.existsSync(c.rcFile) && isHookInstalled(c.rcFile));
|
|
2089
2145
|
}
|
|
2090
2146
|
function registerUninstall(program2) {
|
|
2091
|
-
program2.command("uninstall").description("Remove shell hooks and optionally delete all Workscribe data").action(async () => {
|
|
2147
|
+
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) => {
|
|
2092
2148
|
log.plain(import_chalk9.default.bold("\n Workscribe \u2014 Uninstall\n"));
|
|
2093
2149
|
const hooked = detectShellRcFiles();
|
|
2094
2150
|
if (hooked.length === 0) {
|
|
@@ -2103,6 +2159,7 @@ function registerUninstall(program2) {
|
|
|
2103
2159
|
);
|
|
2104
2160
|
}
|
|
2105
2161
|
}
|
|
2162
|
+
if (opts.hooksOnly) return;
|
|
2106
2163
|
if (import_fs13.default.existsSync(WORKSCRIBE_DIR)) {
|
|
2107
2164
|
log.plain("");
|
|
2108
2165
|
const { deleteData } = await (0, import_prompts3.default)({
|
|
@@ -2126,7 +2183,7 @@ function registerUninstall(program2) {
|
|
|
2126
2183
|
|
|
2127
2184
|
// src/index.ts
|
|
2128
2185
|
var program = new import_commander.Command();
|
|
2129
|
-
program.name("workscribe").description("Automatic developer work journal").version("1.
|
|
2186
|
+
program.name("workscribe").description("Automatic developer work journal").version("1.7.1");
|
|
2130
2187
|
registerCapture(program);
|
|
2131
2188
|
registerHook(program);
|
|
2132
2189
|
registerInit(program);
|