@clauderecallhq/cli 0.0.1 → 0.11.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/LICENSE +33 -0
- package/README.md +543 -3
- package/README.public.md +523 -0
- package/dist/cli.js +354 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/activate.js +69 -0
- package/dist/commands/activate.js.map +1 -0
- package/dist/commands/audit-secrets.js +103 -0
- package/dist/commands/audit-secrets.js.map +1 -0
- package/dist/commands/blame.js +35 -0
- package/dist/commands/blame.js.map +1 -0
- package/dist/commands/config-verification.js +18 -0
- package/dist/commands/config-verification.js.map +1 -0
- package/dist/commands/context.js +144 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/correlate.js +70 -0
- package/dist/commands/correlate.js.map +1 -0
- package/dist/commands/digest.js +78 -0
- package/dist/commands/digest.js.map +1 -0
- package/dist/commands/health.js +62 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/index.js +247 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/install-extension.js +138 -0
- package/dist/commands/install-extension.js.map +1 -0
- package/dist/commands/license.js +39 -0
- package/dist/commands/license.js.map +1 -0
- package/dist/commands/list.js +47 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/mcp.js +29 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/open.js +28 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/paste.js +154 -0
- package/dist/commands/paste.js.map +1 -0
- package/dist/commands/projects.js +36 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/search.js +67 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/semantic.js +173 -0
- package/dist/commands/semantic.js.map +1 -0
- package/dist/commands/show.js +121 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/start.js +47 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stats.js +133 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/status.js +45 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.js +29 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/thread.js +396 -0
- package/dist/commands/thread.js.map +1 -0
- package/dist/context/formatter.js +103 -0
- package/dist/context/formatter.js.map +1 -0
- package/dist/daemon/auto-tag-config.js +103 -0
- package/dist/daemon/auto-tag-config.js.map +1 -0
- package/dist/daemon/auto-tag-config.test.js +72 -0
- package/dist/daemon/auto-tag-config.test.js.map +1 -0
- package/dist/daemon/auto-title-config.js +70 -0
- package/dist/daemon/auto-title-config.js.map +1 -0
- package/dist/daemon/bulk-title-jobs.js +170 -0
- package/dist/daemon/bulk-title-jobs.js.map +1 -0
- package/dist/daemon/correlator.js +320 -0
- package/dist/daemon/correlator.js.map +1 -0
- package/dist/daemon/discover.js +316 -0
- package/dist/daemon/discover.js.map +1 -0
- package/dist/daemon/editor-detection.js +186 -0
- package/dist/daemon/editor-detection.js.map +1 -0
- package/dist/daemon/entrypoint.js +55 -0
- package/dist/daemon/entrypoint.js.map +1 -0
- package/dist/daemon/git-correlator.js +256 -0
- package/dist/daemon/git-correlator.js.map +1 -0
- package/dist/daemon/mcp-installer.js +108 -0
- package/dist/daemon/mcp-installer.js.map +1 -0
- package/dist/daemon/onboarding-state.js +140 -0
- package/dist/daemon/onboarding-state.js.map +1 -0
- package/dist/daemon/pidfile.js +57 -0
- package/dist/daemon/pidfile.js.map +1 -0
- package/dist/daemon/ports.js +48 -0
- package/dist/daemon/ports.js.map +1 -0
- package/dist/daemon/scanProgressRegistry.js +62 -0
- package/dist/daemon/scanProgressRegistry.js.map +1 -0
- package/dist/daemon/server.js +2010 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/tag-scanner/anthropic-client.js +40 -0
- package/dist/daemon/tag-scanner/anthropic-client.js.map +1 -0
- package/dist/daemon/tag-scanner/autopilot.js +131 -0
- package/dist/daemon/tag-scanner/autopilot.js.map +1 -0
- package/dist/daemon/tag-scanner/claude-cli-driver.js +250 -0
- package/dist/daemon/tag-scanner/claude-cli-driver.js.map +1 -0
- package/dist/daemon/tag-scanner/orchestrator.js +88 -0
- package/dist/daemon/tag-scanner/orchestrator.js.map +1 -0
- package/dist/daemon/tag-scanner/prompt.js +46 -0
- package/dist/daemon/tag-scanner/prompt.js.map +1 -0
- package/dist/daemon/tag-scanner/prompt.test.js +48 -0
- package/dist/daemon/tag-scanner/prompt.test.js.map +1 -0
- package/dist/daemon/tag-scanner/scan-state.js +49 -0
- package/dist/daemon/tag-scanner/scan-state.js.map +1 -0
- package/dist/daemon/tag-scanner/session-fetcher.js +82 -0
- package/dist/daemon/tag-scanner/session-fetcher.js.map +1 -0
- package/dist/daemon/tag-scanner/session-fetcher.test.js +34 -0
- package/dist/daemon/tag-scanner/session-fetcher.test.js.map +1 -0
- package/dist/daemon/tag-scanner/validator.js +50 -0
- package/dist/daemon/tag-scanner/validator.js.map +1 -0
- package/dist/daemon/tag-scanner/validator.test.js +41 -0
- package/dist/daemon/tag-scanner/validator.test.js.map +1 -0
- package/dist/daemon/terminal-registry.js +443 -0
- package/dist/daemon/terminal-registry.js.map +1 -0
- package/dist/daemon/ui.js +64 -0
- package/dist/daemon/ui.js.map +1 -0
- package/dist/daemon/watcher.js +256 -0
- package/dist/daemon/watcher.js.map +1 -0
- package/dist/db/client.js +22 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/schema.js +496 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/license/api-base.js +13 -0
- package/dist/license/api-base.js.map +1 -0
- package/dist/license/manager.js +43 -0
- package/dist/license/manager.js.map +1 -0
- package/dist/license/public-key.js +19 -0
- package/dist/license/public-key.js.map +1 -0
- package/dist/license/storage.js +27 -0
- package/dist/license/storage.js.map +1 -0
- package/dist/license/verify.js +23 -0
- package/dist/license/verify.js.map +1 -0
- package/dist/mcp/audit.js +126 -0
- package/dist/mcp/audit.js.map +1 -0
- package/dist/mcp/prompts.js +180 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/server.js +502 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/thread-tools.js +363 -0
- package/dist/mcp/thread-tools.js.map +1 -0
- package/dist/mcp/write-tools.js +239 -0
- package/dist/mcp/write-tools.js.map +1 -0
- package/dist/parser/jsonl.js +150 -0
- package/dist/parser/jsonl.js.map +1 -0
- package/dist/semantic/chunker.js +47 -0
- package/dist/semantic/chunker.js.map +1 -0
- package/dist/semantic/config.js +74 -0
- package/dist/semantic/config.js.map +1 -0
- package/dist/semantic/embedder.js +54 -0
- package/dist/semantic/embedder.js.map +1 -0
- package/dist/semantic/fusion.js +38 -0
- package/dist/semantic/fusion.js.map +1 -0
- package/dist/semantic/model-download.js +69 -0
- package/dist/semantic/model-download.js.map +1 -0
- package/dist/semantic/pipeline.js +375 -0
- package/dist/semantic/pipeline.js.map +1 -0
- package/dist/semantic/query.js +42 -0
- package/dist/semantic/query.js.map +1 -0
- package/dist/semantic/worker.js +78 -0
- package/dist/semantic/worker.js.map +1 -0
- package/dist/stats/backfill.js +151 -0
- package/dist/stats/backfill.js.map +1 -0
- package/dist/stats/health.js +102 -0
- package/dist/stats/health.js.map +1 -0
- package/dist/stats/query.js +385 -0
- package/dist/stats/query.js.map +1 -0
- package/dist/utils/aliases.js +107 -0
- package/dist/utils/aliases.js.map +1 -0
- package/dist/utils/autoCollections.js +635 -0
- package/dist/utils/autoCollections.js.map +1 -0
- package/dist/utils/autoTitle.js +348 -0
- package/dist/utils/autoTitle.js.map +1 -0
- package/dist/utils/collections.js +446 -0
- package/dist/utils/collections.js.map +1 -0
- package/dist/utils/format.js +46 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/notes.js +270 -0
- package/dist/utils/notes.js.map +1 -0
- package/dist/utils/paths.js +50 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/pricing.js +257 -0
- package/dist/utils/pricing.js.map +1 -0
- package/dist/utils/secret-scanner.js +166 -0
- package/dist/utils/secret-scanner.js.map +1 -0
- package/dist/utils/sessionLabel.js +64 -0
- package/dist/utils/sessionLabel.js.map +1 -0
- package/dist/utils/tags.js +97 -0
- package/dist/utils/tags.js.map +1 -0
- package/dist/utils/thread-context.js +129 -0
- package/dist/utils/thread-context.js.map +1 -0
- package/dist/utils/threadFilter.js +18 -0
- package/dist/utils/threadFilter.js.map +1 -0
- package/dist/utils/threads-titler.js +298 -0
- package/dist/utils/threads-titler.js.map +1 -0
- package/dist/utils/threads.js +383 -0
- package/dist/utils/threads.js.map +1 -0
- package/dist/utils/usage.js +76 -0
- package/dist/utils/usage.js.map +1 -0
- package/dist/verification/compute.js +88 -0
- package/dist/verification/compute.js.map +1 -0
- package/dist/verification/config.js +34 -0
- package/dist/verification/config.js.map +1 -0
- package/dist/web/assets/index-CIr6J4Fw.js +1201 -0
- package/dist/web/assets/index-Ctc8g9Jw.css +1 -0
- package/dist/web/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/dist/web/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/dist/web/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/dist/web/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-700-normal-CjBOestx.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-700-normal-DrXBdSj3.woff +0 -0
- package/dist/web/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/dist/web/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/dist/web/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/dist/web/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-ext-700-normal-BjwYoWNd.woff2 +0 -0
- package/dist/web/assets/inter-cyrillic-ext-700-normal-LO58E6JB.woff +0 -0
- package/dist/web/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/dist/web/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/dist/web/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/dist/web/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/dist/web/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/dist/web/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/dist/web/assets/inter-greek-700-normal-BUv2fZ6O.woff +0 -0
- package/dist/web/assets/inter-greek-700-normal-C3JjAnD8.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/dist/web/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/dist/web/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/dist/web/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/dist/web/assets/inter-greek-ext-700-normal-BoQ6DsYi.woff +0 -0
- package/dist/web/assets/inter-greek-ext-700-normal-qfdV9bQt.woff2 +0 -0
- package/dist/web/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/web/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/web/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/web/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/web/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/dist/web/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/dist/web/assets/inter-latin-700-normal-BLAVimhd.woff +0 -0
- package/dist/web/assets/inter-latin-700-normal-Yt3aPRUw.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/dist/web/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/dist/web/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/dist/web/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-700-normal-Ca8adRJv.woff2 +0 -0
- package/dist/web/assets/inter-latin-ext-700-normal-TidjK2hL.woff +0 -0
- package/dist/web/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/dist/web/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/dist/web/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/dist/web/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/dist/web/assets/inter-vietnamese-700-normal-BZaoP0fm.woff +0 -0
- package/dist/web/assets/inter-vietnamese-700-normal-DlLaEgI2.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/web/favicon.svg +9 -0
- package/dist/web/index.html +15 -0
- package/package.json +79 -9
- package/bin/cli.js +0 -12
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret scanner. Used in two places:
|
|
3
|
+
*
|
|
4
|
+
* 1. `recall paste` — pre-archive gate. Refuses (by default) to store content
|
|
5
|
+
* that matches a known high-severity pattern unless --force is passed.
|
|
6
|
+
* 2. UI warnings — Notes editor can call this client-side to show a
|
|
7
|
+
* "this note looks like it contains a secret" banner before save.
|
|
8
|
+
*
|
|
9
|
+
* Patterns are deliberately conservative: we'd rather let through a fake secret
|
|
10
|
+
* than mask a real one. Users can always override with --force if they really
|
|
11
|
+
* want to archive something that matches a pattern (e.g. a sample key in docs).
|
|
12
|
+
*/
|
|
13
|
+
const PATTERNS = [
|
|
14
|
+
// --- Well-known provider keys ---
|
|
15
|
+
{ name: 'Anthropic API key', regex: /sk-ant-[a-zA-Z0-9_\-]{40,}/g, severity: 'high' },
|
|
16
|
+
{ name: 'OpenAI API key', regex: /sk-(?:proj-)?[a-zA-Z0-9]{32,}/g, severity: 'high' },
|
|
17
|
+
{ name: 'AWS access key ID', regex: /AKIA[0-9A-Z]{16}/g, severity: 'high' },
|
|
18
|
+
{ name: 'GitHub PAT', regex: /gh[pousr]_[A-Za-z0-9]{20,}/g, severity: 'high' },
|
|
19
|
+
{ name: 'Stripe live/test key', regex: /(?:sk|rk|pk)_(?:live|test)_[a-zA-Z0-9]{24,}/g, severity: 'high' },
|
|
20
|
+
{ name: 'Slack token', regex: /xox[abprs]-[A-Za-z0-9\-]{10,}/g, severity: 'high' },
|
|
21
|
+
{ name: 'Google API key', regex: /AIza[0-9A-Za-z_\-]{35}/g, severity: 'high' },
|
|
22
|
+
{ name: 'Private key block', regex: /-----BEGIN (?:RSA |DSA |EC |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g, severity: 'high' },
|
|
23
|
+
// --- Other named providers ---
|
|
24
|
+
{ name: 'Apify token', regex: /apify_api_[A-Za-z0-9]{20,}/g, severity: 'high' },
|
|
25
|
+
{ name: 'Notion integration', regex: /(?:secret_|ntn_)[A-Za-z0-9]{40,}/g, severity: 'high' },
|
|
26
|
+
{ name: 'Vercel token', regex: /vercel_[A-Za-z0-9]{24,}/g, severity: 'high' },
|
|
27
|
+
{ name: 'Supabase service key', regex: /sbp_[A-Za-z0-9]{40,}/g, severity: 'high' },
|
|
28
|
+
{ name: 'SendGrid key', regex: /SG\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}/g, severity: 'high' },
|
|
29
|
+
{ name: 'Mailgun key', regex: /key-[a-f0-9]{32}/g, severity: 'high' },
|
|
30
|
+
{ name: 'Twilio SID', regex: /AC[a-f0-9]{32}/g, severity: 'high' },
|
|
31
|
+
{ name: 'Discord bot token', regex: /[MN][A-Za-z\d]{23}\.[\w-]{6}\.[\w-]{27,38}/g, severity: 'high' },
|
|
32
|
+
{ name: 'npm token', regex: /npm_[A-Za-z0-9]{36}/g, severity: 'high' },
|
|
33
|
+
{ name: 'HuggingFace token', regex: /hf_[A-Za-z0-9]{30,}/g, severity: 'high' },
|
|
34
|
+
{ name: 'Replicate token', regex: /r8_[A-Za-z0-9]{32,}/g, severity: 'high' },
|
|
35
|
+
{ name: 'Figma token', regex: /figd_[A-Za-z0-9_\-]{30,}/g, severity: 'high' },
|
|
36
|
+
{ name: 'Linear key', regex: /lin_api_[A-Za-z0-9]{30,}/g, severity: 'high' },
|
|
37
|
+
{ name: 'DigitalOcean token', regex: /dop_v1_[a-f0-9]{64}/g, severity: 'high' },
|
|
38
|
+
// --- Generic catch-alls (broad; run last so named matches win dedup) ---
|
|
39
|
+
//
|
|
40
|
+
// Provider-prefixed tokens follow an observable pattern:
|
|
41
|
+
// <lowercase-provider>_(api|pat|token|sk|pk|key|auth)_<high-entropy-blob>
|
|
42
|
+
// The 20+ char blob filters out common english words and short IDs.
|
|
43
|
+
// Requires at least one digit in the blob to avoid matching labels like
|
|
44
|
+
// `stripe_api_key_configuration`.
|
|
45
|
+
{ name: 'Generic provider token', regex: /\b[a-z][a-z0-9]{2,20}_(?:api|pat|token|sk|pk|key|auth)_(?=[A-Za-z0-9_\-]*\d)[A-Za-z0-9_\-]{20,}\b/g, severity: 'high' },
|
|
46
|
+
// Bearer tokens in Authorization headers.
|
|
47
|
+
{ name: 'Bearer token', regex: /\b[Bb]earer\s+[A-Za-z0-9_\-\.=]{24,}\b/g, severity: 'medium' },
|
|
48
|
+
// --- Webhook URLs that embed a token in the path ---
|
|
49
|
+
// These URLs are themselves credentials — anyone with the URL can post
|
|
50
|
+
// to the channel, so leaking one is identical to leaking a key.
|
|
51
|
+
{ name: 'Slack webhook URL',
|
|
52
|
+
regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]{20,}/g,
|
|
53
|
+
severity: 'high' },
|
|
54
|
+
{ name: 'Discord webhook URL',
|
|
55
|
+
regex: /https:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_\-]{40,}/g,
|
|
56
|
+
severity: 'high' },
|
|
57
|
+
{ name: 'Teams webhook URL',
|
|
58
|
+
regex: /https:\/\/[a-zA-Z0-9.\-]+\.webhook\.office\.com\/webhookb2\/[A-Za-z0-9@_\-\/]{30,}/g,
|
|
59
|
+
severity: 'high' },
|
|
60
|
+
// High-entropy blob immediately following a secret-y keyword. Catches the
|
|
61
|
+
// markdown-doc leak case where the secret is on its own line in a code
|
|
62
|
+
// fence, e.g.:
|
|
63
|
+
//
|
|
64
|
+
// **Webhook signing secret** (copy exactly):
|
|
65
|
+
// ```
|
|
66
|
+
// b02233572afa9fe5ce33083a5984f1b2c23986c337a9d3a3422daa02de61867e
|
|
67
|
+
// ```
|
|
68
|
+
//
|
|
69
|
+
// The keyword and value can be separated by up to 200 chars of glue
|
|
70
|
+
// (whitespace, fence markers, parenthetical notes). The 32-char minimum
|
|
71
|
+
// on the blob avoids false-positives on short hashes / IDs while still
|
|
72
|
+
// catching 64-char hex (32-byte) and 40-char hex (20-byte) secrets.
|
|
73
|
+
{ name: 'Secret near keyword',
|
|
74
|
+
regex: /\b(?:webhook[_\s\-]?secret|signing[_\s\-]?secret|webhook[_\s\-]?signing[_\s\-]?secret|api[_\s\-]?secret|client[_\s\-]?secret|private[_\s\-]?key|access[_\s\-]?token|auth[_\s\-]?token|api[_\s\-]?key)\b[\s\S]{0,200}?\b(?:[a-fA-F0-9]{32,}|[A-Za-z0-9+/_\-]{20,}(?:\.[A-Za-z0-9+/_\-]{10,}){1,2}|[A-Za-z0-9+/_\-]{40,}={0,2})\b/gi,
|
|
75
|
+
severity: 'high' },
|
|
76
|
+
// --- Formats, not providers ---
|
|
77
|
+
{ name: 'JWT', regex: /eyJ[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}\.[a-zA-Z0-9_\-]{10,}/g, severity: 'medium' },
|
|
78
|
+
{ name: 'URL with password', regex: /https?:\/\/[^:\s/@]+:[^@\s]{6,}@[^\s/]+/g, severity: 'high' },
|
|
79
|
+
// Password / secret / token / api-key assignments. The lookbehind blocks
|
|
80
|
+
// matches inside camelCase identifiers (e.g. `mySecretLogger`) but allows
|
|
81
|
+
// env-var style names where the keyword is preceded by `_` (e.g.
|
|
82
|
+
// `WEBHOOK_SECRET=`, `LEMONSQUEEZY_WEBHOOK_SECRET=`, `STRIPE_API_KEY=`).
|
|
83
|
+
// The 16+ char value charset deliberately excludes `.` and `:` so that
|
|
84
|
+
// `secret = process.env.X` and `apiKey: args.resendApiKey` (code, not
|
|
85
|
+
// values) do not get redacted.
|
|
86
|
+
{ name: 'Password assignment',
|
|
87
|
+
regex: /(?<![A-Za-z])(?:password|passwd|pwd|secret|token|api[_\-]?key|access[_\-]?key|auth[_\-]?token|webhook[_\-]?secret|client[_\-]?secret|private[_\-]?key)\b\s*[:=]\s*["']?[A-Za-z0-9_\-+/=]{16,}/gi,
|
|
88
|
+
severity: 'high' },
|
|
89
|
+
];
|
|
90
|
+
function mask(s) {
|
|
91
|
+
if (s.length <= 8)
|
|
92
|
+
return s.slice(0, 2) + '•'.repeat(Math.max(0, s.length - 4)) + s.slice(-2);
|
|
93
|
+
const head = s.slice(0, Math.min(6, Math.floor(s.length / 3)));
|
|
94
|
+
const tail = s.slice(-Math.min(4, Math.floor(s.length / 4)));
|
|
95
|
+
return `${head}${'•'.repeat(Math.max(3, s.length - head.length - tail.length))}${tail}`;
|
|
96
|
+
}
|
|
97
|
+
export function scanForSecrets(text) {
|
|
98
|
+
if (!text)
|
|
99
|
+
return [];
|
|
100
|
+
// The raw secret is held in this local scope only — dedup key is hashed
|
|
101
|
+
// (cheap non-cryptographic hash is fine; we just need uniqueness, not
|
|
102
|
+
// unpredictability) and the plaintext never escapes the function.
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const p of PATTERNS) {
|
|
106
|
+
p.regex.lastIndex = 0;
|
|
107
|
+
for (const m of text.matchAll(p.regex)) {
|
|
108
|
+
const raw = m[0];
|
|
109
|
+
const key = `${p.name}::${fingerprint(raw)}`;
|
|
110
|
+
if (seen.has(key))
|
|
111
|
+
continue;
|
|
112
|
+
seen.add(key);
|
|
113
|
+
out.push({
|
|
114
|
+
pattern: p.name,
|
|
115
|
+
maskedPreview: mask(raw),
|
|
116
|
+
offset: m.index ?? 0,
|
|
117
|
+
severity: p.severity,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/** Small non-cryptographic hash (djb2). Dedup only — never reversed. */
|
|
124
|
+
function fingerprint(s) {
|
|
125
|
+
let h = 5381;
|
|
126
|
+
for (let i = 0; i < s.length; i++)
|
|
127
|
+
h = ((h << 5) + h + s.charCodeAt(i)) | 0;
|
|
128
|
+
return (h >>> 0).toString(36);
|
|
129
|
+
}
|
|
130
|
+
export function hasHighSeveritySecret(text) {
|
|
131
|
+
return scanForSecrets(text).some((m) => m.severity === 'high');
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Redact every detected secret in-place with a masked preview. Used on the
|
|
135
|
+
* indexing hot path so secrets that appear in raw session JSONL never make
|
|
136
|
+
* it into ~/.recall/db.sqlite (or the FTS index, the plain-text mirrors,
|
|
137
|
+
* or the exported context markdown).
|
|
138
|
+
*
|
|
139
|
+
* The original JSONL at ~/.claude/projects/ is NEVER modified — that's the
|
|
140
|
+
* durability invariant. The redaction only affects our derived store, so a
|
|
141
|
+
* user who really needs the cleartext can still recover it from source.
|
|
142
|
+
*
|
|
143
|
+
* Returns the redacted string and the count of unique secrets redacted.
|
|
144
|
+
* `count` is useful for telemetry / UI badges ("3 secrets auto-redacted
|
|
145
|
+
* in this session").
|
|
146
|
+
*/
|
|
147
|
+
export function redactSecrets(text) {
|
|
148
|
+
if (!text)
|
|
149
|
+
return { redacted: text, count: 0 };
|
|
150
|
+
let redacted = text;
|
|
151
|
+
let count = 0;
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
for (const p of PATTERNS) {
|
|
154
|
+
p.regex.lastIndex = 0;
|
|
155
|
+
redacted = redacted.replace(p.regex, (m) => {
|
|
156
|
+
const key = `${p.name}::${fingerprint(m)}`;
|
|
157
|
+
if (!seen.has(key)) {
|
|
158
|
+
seen.add(key);
|
|
159
|
+
count += 1;
|
|
160
|
+
}
|
|
161
|
+
return `[REDACTED ${p.name}: ${mask(m)}]`;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return { redacted, count };
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=secret-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scanner.js","sourceRoot":"","sources":["../../src/utils/secret-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session label resolution. Implements the standard fallback chain used
|
|
3
|
+
* everywhere a session needs a human-readable name.
|
|
4
|
+
*
|
|
5
|
+
* Precedence (mirrors AliasEditor + SessionsPane):
|
|
6
|
+
* 1. Agent ✨ auto_title wins unconditionally — clicking ✨ is a deliberate
|
|
7
|
+
* request to display THIS title.
|
|
8
|
+
* 2. Manual alias beats heuristic-only auto_title.
|
|
9
|
+
* 3. Heuristic auto_title beats correlator-set ('auto') alias — fixes
|
|
10
|
+
* child sessions that inherited a noisy terminal-tab name like "claude"
|
|
11
|
+
* or "test test" from a wrongly-linked terminal.
|
|
12
|
+
* 4. Otherwise: alias → auto_title → first_user_message → id.slice(0,8).
|
|
13
|
+
*
|
|
14
|
+
* Callers without `aliasSource` / `autoTitleSource` collapse to the simpler
|
|
15
|
+
* legacy chain (alias → auto_title → first_user_message → id) — kept for
|
|
16
|
+
* backward compatibility with consumers that don't yet plumb source fields.
|
|
17
|
+
*
|
|
18
|
+
* Pure function with no DB access. Tests live at `test/sessionLabel.test.mjs`.
|
|
19
|
+
*/
|
|
20
|
+
const FIRST_MESSAGE_MAX_LEN = 60;
|
|
21
|
+
function nonEmpty(value) {
|
|
22
|
+
if (value == null)
|
|
23
|
+
return null;
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
26
|
+
}
|
|
27
|
+
function truncateFirstMessage(raw) {
|
|
28
|
+
const firstLine = raw.split('\n', 1)[0].trim();
|
|
29
|
+
if (!firstLine)
|
|
30
|
+
return null;
|
|
31
|
+
return firstLine.length > FIRST_MESSAGE_MAX_LEN
|
|
32
|
+
? firstLine.slice(0, FIRST_MESSAGE_MAX_LEN)
|
|
33
|
+
: firstLine;
|
|
34
|
+
}
|
|
35
|
+
export function resolveSessionLabel(input) {
|
|
36
|
+
const alias = nonEmpty(input.alias);
|
|
37
|
+
const autoTitle = nonEmpty(input.autoTitle);
|
|
38
|
+
const firstMessage = nonEmpty(input.firstUserMessage);
|
|
39
|
+
const aliasSource = input.aliasSource ?? null;
|
|
40
|
+
const autoTitleSource = input.autoTitleSource ?? null;
|
|
41
|
+
// 1. Agent title always wins.
|
|
42
|
+
if (autoTitle && autoTitleSource === 'agent')
|
|
43
|
+
return autoTitle;
|
|
44
|
+
// 2. Manual alias beats a heuristic auto_title.
|
|
45
|
+
if (alias && aliasSource === 'manual')
|
|
46
|
+
return alias;
|
|
47
|
+
// 3. Any auto_title beats an auto-set alias (terminal-tab inheritance fix).
|
|
48
|
+
// Also covers the legacy callers (no source fields): if both exist with
|
|
49
|
+
// no source info, prefer alias unless we explicitly know it's auto.
|
|
50
|
+
if (autoTitle && (aliasSource === 'auto' || alias === null))
|
|
51
|
+
return autoTitle;
|
|
52
|
+
// 4. Fall through to the legacy chain.
|
|
53
|
+
if (alias)
|
|
54
|
+
return alias;
|
|
55
|
+
if (autoTitle)
|
|
56
|
+
return autoTitle;
|
|
57
|
+
if (firstMessage) {
|
|
58
|
+
const truncated = truncateFirstMessage(firstMessage);
|
|
59
|
+
if (truncated)
|
|
60
|
+
return truncated;
|
|
61
|
+
}
|
|
62
|
+
return input.sessionId.slice(0, 8);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=sessionLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionLabel.js","sourceRoot":"","sources":["../../src/utils/sessionLabel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAajC,SAAS,QAAQ,CAAC,KAAgC;IAChD,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAW;IACvC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,OAAO,SAAS,CAAC,MAAM,GAAG,qBAAqB;QAC7C,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC;QAC3C,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAwB;IAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC;IAC9C,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe,IAAI,IAAI,CAAC;IAEtD,8BAA8B;IAC9B,IAAI,SAAS,IAAI,eAAe,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/D,gDAAgD;IAChD,IAAI,KAAK,IAAI,WAAW,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEpD,4EAA4E;IAC5E,2EAA2E;IAC3E,uEAAuE;IACvE,IAAI,SAAS,IAAI,CAAC,WAAW,KAAK,MAAM,IAAI,KAAK,KAAK,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAE9E,uCAAuC;IACvC,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,SAAS,GAAG,oBAAoB,CAAC,YAAY,CAAC,CAAC;QACrD,IAAI,SAAS;YAAE,OAAO,SAAS,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getDb } from '../db/client.js';
|
|
4
|
+
import { RECALL_HOME, ensureRecallHome } from './paths.js';
|
|
5
|
+
/**
|
|
6
|
+
* Tags for sessions. Three durability layers, same pattern as aliases + notes:
|
|
7
|
+
*
|
|
8
|
+
* 1. session_tags — the current (session_id, tag) many-to-many state.
|
|
9
|
+
* 2. tag_events — append-only audit log of every add/remove. Never trimmed.
|
|
10
|
+
* 3. ~/.recall/tags.json — plain-text mirror on disk, rewritten after every
|
|
11
|
+
* mutation. Contains both current state AND the full event log, so tags
|
|
12
|
+
* can be reconstructed end-to-end from this file alone.
|
|
13
|
+
*/
|
|
14
|
+
export const TAGS_BACKUP_FILE = join(RECALL_HOME, 'tags.json');
|
|
15
|
+
/** Normalize user input into a canonical tag token. */
|
|
16
|
+
export function normalizeTag(raw) {
|
|
17
|
+
return raw
|
|
18
|
+
.trim()
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/^#+/, '')
|
|
21
|
+
.replace(/[\s/\\]+/g, '-')
|
|
22
|
+
.replace(/[^a-z0-9\-._]/g, '')
|
|
23
|
+
.slice(0, 40);
|
|
24
|
+
}
|
|
25
|
+
export function addTag(sessionId, rawTag) {
|
|
26
|
+
const tag = normalizeTag(rawTag);
|
|
27
|
+
if (!tag)
|
|
28
|
+
throw new Error('tag must contain at least one alphanumeric character');
|
|
29
|
+
const db = getDb();
|
|
30
|
+
const now = new Date().toISOString();
|
|
31
|
+
const exists = db
|
|
32
|
+
.prepare('SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?')
|
|
33
|
+
.get(sessionId, tag);
|
|
34
|
+
if (exists) {
|
|
35
|
+
return { tag, added: false };
|
|
36
|
+
}
|
|
37
|
+
const txn = db.transaction(() => {
|
|
38
|
+
db.prepare('INSERT INTO session_tags (session_id, tag, created_at) VALUES (?, ?, ?)').run(sessionId, tag, now);
|
|
39
|
+
db.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'add', ?)").run(sessionId, tag, now);
|
|
40
|
+
});
|
|
41
|
+
txn();
|
|
42
|
+
backupTags();
|
|
43
|
+
return { tag, added: true };
|
|
44
|
+
}
|
|
45
|
+
export function removeTag(sessionId, rawTag) {
|
|
46
|
+
const tag = normalizeTag(rawTag);
|
|
47
|
+
if (!tag)
|
|
48
|
+
return { tag: '', removed: false };
|
|
49
|
+
const db = getDb();
|
|
50
|
+
const now = new Date().toISOString();
|
|
51
|
+
const existed = db
|
|
52
|
+
.prepare('SELECT 1 FROM session_tags WHERE session_id = ? AND tag = ?')
|
|
53
|
+
.get(sessionId, tag);
|
|
54
|
+
if (!existed)
|
|
55
|
+
return { tag, removed: false };
|
|
56
|
+
const txn = db.transaction(() => {
|
|
57
|
+
db.prepare('DELETE FROM session_tags WHERE session_id = ? AND tag = ?').run(sessionId, tag);
|
|
58
|
+
db.prepare("INSERT INTO tag_events (session_id, tag, action, at) VALUES (?, ?, 'remove', ?)").run(sessionId, tag, now);
|
|
59
|
+
});
|
|
60
|
+
txn();
|
|
61
|
+
backupTags();
|
|
62
|
+
return { tag, removed: true };
|
|
63
|
+
}
|
|
64
|
+
export function tagsForSession(sessionId) {
|
|
65
|
+
return getDb()
|
|
66
|
+
.prepare('SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag')
|
|
67
|
+
.all(sessionId).map((r) => r.tag);
|
|
68
|
+
}
|
|
69
|
+
export function allTagsWithCounts() {
|
|
70
|
+
return getDb()
|
|
71
|
+
.prepare(`SELECT tag, COUNT(*) AS count FROM session_tags
|
|
72
|
+
GROUP BY tag ORDER BY count DESC, tag ASC`)
|
|
73
|
+
.all();
|
|
74
|
+
}
|
|
75
|
+
export function backupTags() {
|
|
76
|
+
try {
|
|
77
|
+
ensureRecallHome();
|
|
78
|
+
const db = getDb();
|
|
79
|
+
const currentTags = db
|
|
80
|
+
.prepare('SELECT session_id, tag, created_at FROM session_tags ORDER BY session_id, tag')
|
|
81
|
+
.all();
|
|
82
|
+
const events = db
|
|
83
|
+
.prepare('SELECT id, session_id, tag, action, at FROM tag_events ORDER BY at ASC, id ASC')
|
|
84
|
+
.all();
|
|
85
|
+
const payload = {
|
|
86
|
+
schema: 'claude-recall.tags.v1',
|
|
87
|
+
backed_up_at: new Date().toISOString(),
|
|
88
|
+
current: currentTags,
|
|
89
|
+
events,
|
|
90
|
+
};
|
|
91
|
+
writeFileSync(TAGS_BACKUP_FILE, JSON.stringify(payload, null, 2));
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error('[tags] backup failed:', err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=tags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.js","sourceRoot":"","sources":["../../src/utils/tags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAgB/D,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG;SACP,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC;SACzB,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,SAAiB,EAAE,MAAc;IACtD,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAElF,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,MAAM,GAAG,EAAE;SACd,OAAO,CAAC,6DAA6D,CAAC;SACtE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAEvB,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAC9B,EAAE,CAAC,OAAO,CACR,yEAAyE,CAC1E,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3B,EAAE,CAAC,OAAO,CACR,8EAA8E,CAC/E,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IACH,GAAG,EAAE,CAAC;IAEN,UAAU,EAAE,CAAC;IACb,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAE,MAAc;IACzD,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAE7C,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,MAAM,OAAO,GAAG,EAAE;SACf,OAAO,CAAC,6DAA6D,CAAC;SACtE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAEvB,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAE7C,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAC9B,EAAE,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC,GAAG,CACzE,SAAS,EACT,GAAG,CACJ,CAAC;QACF,EAAE,CAAC,OAAO,CACR,iFAAiF,CAClF,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IACH,GAAG,EAAE,CAAC;IAEN,UAAU,EAAE,CAAC;IACb,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OACE,KAAK,EAAE;SACJ,OAAO,CAAC,gEAAgE,CAAC;SACzE,GAAG,CAAC,SAAS,CACjB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,KAAK,EAAE;SACX,OAAO,CACN;iDAC2C,CAC5C;SACA,GAAG,EAA2C,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,gBAAgB,EAAE,CAAC;QACnB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;QACnB,MAAM,WAAW,GAAG,EAAE;aACnB,OAAO,CAAC,+EAA+E,CAAC;aACxF,GAAG,EAAc,CAAC;QACrB,MAAM,MAAM,GAAG,EAAE;aACd,OAAO,CACN,gFAAgF,CACjF;aACA,GAAG,EAAmB,CAAC;QAC1B,MAAM,OAAO,GAAG;YACd,MAAM,EAAE,uBAAuB;YAC/B,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACtC,OAAO,EAAE,WAAW;YACpB,MAAM;SACP,CAAC;QACF,aAAa,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getDb } from '../db/client.js';
|
|
2
|
+
/**
|
|
3
|
+
* v0.15b prep — thread-context resolver for agent-tier auto-title generation.
|
|
4
|
+
*
|
|
5
|
+
* When the user clicks "Generate title" on a session that already lives inside
|
|
6
|
+
* a thread, the LLM should know who the parent and siblings are so it can
|
|
7
|
+
* mint a title that reflects the session's role in the hierarchy
|
|
8
|
+
* ("Phase A: API design") instead of a generic single-session summary.
|
|
9
|
+
*
|
|
10
|
+
* This module is read-only against the existing `threads` and `thread_edges`
|
|
11
|
+
* tables. It does not mutate state, does not write mirrors, and never throws
|
|
12
|
+
* on a missing edge — callers in `autoTitle.ts` fall back to the no-context
|
|
13
|
+
* prompt when this returns null.
|
|
14
|
+
*
|
|
15
|
+
* Multi-thread tie-break: if a session belongs to multiple threads, we pick
|
|
16
|
+
* the thread the session was added to most recently (by `thread_edges.added_at`
|
|
17
|
+
* for that session's edge). Rationale: the user's last action is the strongest
|
|
18
|
+
* signal of which thread they consider "current." A generated title that
|
|
19
|
+
* reflects an older, possibly archived thread would feel stale.
|
|
20
|
+
*
|
|
21
|
+
* Title fallback chain (consumers should rely on this helper rather than
|
|
22
|
+
* re-implementing it): `alias → auto_title → first_user_message → id.slice(0,8)`.
|
|
23
|
+
* Same chain used by `displayTitleFor` in autoCollections.ts and the web UI.
|
|
24
|
+
*/
|
|
25
|
+
const DISPLAY_TITLE_MAX = 80;
|
|
26
|
+
/**
|
|
27
|
+
* Display title with the canonical fallback chain. Trims, takes the first
|
|
28
|
+
* line, caps at 80 chars with an ellipsis. Returns the 8-char id slice when
|
|
29
|
+
* nothing else is available so the prompt always has *something* to refer to.
|
|
30
|
+
*/
|
|
31
|
+
function displayTitleFor(row) {
|
|
32
|
+
if (row.alias && row.alias.trim())
|
|
33
|
+
return row.alias.trim();
|
|
34
|
+
if (row.auto_title && row.auto_title.trim())
|
|
35
|
+
return row.auto_title.trim();
|
|
36
|
+
const msg = (row.first_user_message ?? '').trim();
|
|
37
|
+
if (!msg)
|
|
38
|
+
return row.id.slice(0, 8);
|
|
39
|
+
const firstLine = msg.split('\n')[0].trim();
|
|
40
|
+
return firstLine.length > DISPLAY_TITLE_MAX
|
|
41
|
+
? firstLine.slice(0, DISPLAY_TITLE_MAX) + '…'
|
|
42
|
+
: firstLine;
|
|
43
|
+
}
|
|
44
|
+
function loadDisplayRow(sessionId) {
|
|
45
|
+
const db = getDb();
|
|
46
|
+
const row = db
|
|
47
|
+
.prepare(`SELECT s.id AS id,
|
|
48
|
+
sa.alias AS alias,
|
|
49
|
+
s.auto_title AS auto_title,
|
|
50
|
+
s.first_user_message AS first_user_message
|
|
51
|
+
FROM sessions s
|
|
52
|
+
LEFT JOIN session_aliases sa ON sa.session_id = s.id
|
|
53
|
+
WHERE s.id = ?`)
|
|
54
|
+
.get(sessionId);
|
|
55
|
+
return row ?? null;
|
|
56
|
+
}
|
|
57
|
+
function loadDisplayTitle(sessionId) {
|
|
58
|
+
const row = loadDisplayRow(sessionId);
|
|
59
|
+
if (!row)
|
|
60
|
+
return sessionId.slice(0, 8);
|
|
61
|
+
return displayTitleFor(row);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the most-relevant thread context for a session.
|
|
65
|
+
*
|
|
66
|
+
* Returns null when:
|
|
67
|
+
* - the session does not exist, OR
|
|
68
|
+
* - the session has no rows in `thread_edges`, OR
|
|
69
|
+
* - the only matching threads are archived.
|
|
70
|
+
*/
|
|
71
|
+
export function getThreadContextForSession(sessionId) {
|
|
72
|
+
if (!sessionId)
|
|
73
|
+
return null;
|
|
74
|
+
const db = getDb();
|
|
75
|
+
// Pick the most-recently-added edge for this session across non-archived
|
|
76
|
+
// threads. ORDER BY added_at DESC ensures the user's last action wins;
|
|
77
|
+
// LIMIT 1 collapses ties deterministically (by primary key tie-break SQLite
|
|
78
|
+
// applies — stable enough for our purposes since we only need ONE thread).
|
|
79
|
+
const edge = db
|
|
80
|
+
.prepare(`SELECT e.thread_id AS thread_id,
|
|
81
|
+
t.name AS thread_name,
|
|
82
|
+
e.parent_session_id AS parent_session_id,
|
|
83
|
+
e.added_at AS added_at
|
|
84
|
+
FROM thread_edges e
|
|
85
|
+
JOIN threads t ON t.id = e.thread_id
|
|
86
|
+
WHERE e.session_id = ?
|
|
87
|
+
AND t.archived = 0
|
|
88
|
+
ORDER BY e.added_at DESC
|
|
89
|
+
LIMIT 1`)
|
|
90
|
+
.get(sessionId);
|
|
91
|
+
if (!edge)
|
|
92
|
+
return null;
|
|
93
|
+
const parent_session = edge.parent_session_id
|
|
94
|
+
? {
|
|
95
|
+
id: edge.parent_session_id,
|
|
96
|
+
title: loadDisplayTitle(edge.parent_session_id),
|
|
97
|
+
}
|
|
98
|
+
: null;
|
|
99
|
+
const excludeIds = edge.parent_session_id
|
|
100
|
+
? [sessionId, edge.parent_session_id]
|
|
101
|
+
: [sessionId];
|
|
102
|
+
const placeholders = excludeIds.map(() => '?').join(', ');
|
|
103
|
+
const siblingRows = db
|
|
104
|
+
.prepare(`SELECT e.session_id AS session_id,
|
|
105
|
+
s.id AS id,
|
|
106
|
+
sa.alias AS alias,
|
|
107
|
+
s.auto_title AS auto_title,
|
|
108
|
+
s.first_user_message AS first_user_message
|
|
109
|
+
FROM thread_edges e
|
|
110
|
+
LEFT JOIN sessions s ON s.id = e.session_id
|
|
111
|
+
LEFT JOIN session_aliases sa ON sa.session_id = e.session_id
|
|
112
|
+
WHERE e.thread_id = ?
|
|
113
|
+
AND e.session_id NOT IN (${placeholders})
|
|
114
|
+
ORDER BY e.added_at ASC`)
|
|
115
|
+
.all(edge.thread_id, ...excludeIds);
|
|
116
|
+
const siblings = siblingRows.map((r) => ({
|
|
117
|
+
id: r.session_id,
|
|
118
|
+
// If the sibling row's session is missing (foreign-key gap, shouldn't
|
|
119
|
+
// happen but defensive), fall back to its id slice.
|
|
120
|
+
title: r.id ? displayTitleFor(r) : r.session_id.slice(0, 8),
|
|
121
|
+
}));
|
|
122
|
+
return {
|
|
123
|
+
thread_id: edge.thread_id,
|
|
124
|
+
thread_name: edge.thread_name,
|
|
125
|
+
parent_session,
|
|
126
|
+
siblings,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=thread-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread-context.js","sourceRoot":"","sources":["../../src/utils/thread-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread name typeahead filter. Pure helper used by the SessionThreadsRow
|
|
3
|
+
* "+ Add to thread" picker (v0.15a Bucket 1 fix 5).
|
|
4
|
+
*
|
|
5
|
+
* Case-insensitive substring match against `thread.name`. Empty query returns
|
|
6
|
+
* the input unchanged so the caller can show the full list before any typing.
|
|
7
|
+
* Whitespace-only queries are treated as empty.
|
|
8
|
+
*
|
|
9
|
+
* Stays in `src/utils/` (instead of `src/web/`) so unit tests under `test/`
|
|
10
|
+
* can import it directly from the compiled `dist/`.
|
|
11
|
+
*/
|
|
12
|
+
export function filterThreadsByName(threads, query) {
|
|
13
|
+
const norm = query.trim().toLowerCase();
|
|
14
|
+
if (!norm)
|
|
15
|
+
return [...threads];
|
|
16
|
+
return threads.filter((t) => t.name.toLowerCase().includes(norm));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=threadFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threadFilter.js","sourceRoot":"","sources":["../../src/utils/threadFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,UAAU,mBAAmB,CACjC,OAAqB,EACrB,KAAa;IAEb,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;IAC/B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AACpE,CAAC"}
|