@cachly-dev/mcp-server 0.10.24 → 0.10.25

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +0 -131
  2. package/README.md +112 -136
  3. package/dist/__tests__/brain-predict-failures.test.d.ts +17 -0
  4. package/dist/__tests__/brain-predict-failures.test.js +339 -0
  5. package/dist/__tests__/brain-predict-failures.test.js.map +1 -0
  6. package/dist/handlers/brain.js +67 -7
  7. package/dist/handlers/brain.js.map +1 -1
  8. package/dist/handlers/fedbrain.js +44 -93
  9. package/dist/handlers/fedbrain.js.map +1 -1
  10. package/dist/handlers/roadmap.js +2 -8
  11. package/dist/handlers/roadmap.js.map +1 -1
  12. package/dist/handlers/syndicate.js +7 -15
  13. package/dist/handlers/syndicate.js.map +1 -1
  14. package/dist/handlers/team.js +50 -62
  15. package/dist/handlers/team.js.map +1 -1
  16. package/dist/index.js +122 -979
  17. package/dist/index.js.map +1 -1
  18. package/dist/tools.d.ts +54 -3
  19. package/dist/tools.js +62 -8
  20. package/dist/tools.js.map +1 -1
  21. package/package.json +13 -29
  22. package/scripts/postinstall.js +12 -12
  23. package/server.json +9 -3
  24. package/smithery.yaml +8 -15
  25. package/src/__tests__/brain-predict-failures.test.ts +430 -0
  26. package/src/handlers/brain.ts +64 -7
  27. package/src/handlers/fedbrain.ts +38 -88
  28. package/src/handlers/roadmap.ts +2 -7
  29. package/src/handlers/syndicate.ts +7 -13
  30. package/src/handlers/team.ts +33 -57
  31. package/src/index.ts +135 -1011
  32. package/src/tools.ts +66 -8
  33. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -50
  34. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  35. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
  36. package/.github/workflows/publish.yml +0 -72
  37. package/LAUNCH_TEXTS.md +0 -182
  38. package/LICENSE +0 -202
  39. package/dist/__tests__/auth.test.d.ts +0 -1
  40. package/dist/__tests__/auth.test.js +0 -107
  41. package/dist/__tests__/auth.test.js.map +0 -1
  42. package/glama.json +0 -36
  43. package/llms.txt +0 -155
  44. package/src/__tests__/auth.test.ts +0 -147
@@ -1,107 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
3
- import { jwtExpiryMs, checkJwt, handleApiError } from '../auth.js';
4
- function makeJwt(payload) {
5
- const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
6
- const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
7
- return `${header}.${body}.fakesig`;
8
- }
9
- const nowSec = () => Math.floor(Date.now() / 1000);
10
- describe('jwtExpiryMs', () => {
11
- it('returns null for an empty string', () => {
12
- expect(jwtExpiryMs('')).toBeNull();
13
- });
14
- it('returns null for a plain (non-JWT) string', () => {
15
- expect(jwtExpiryMs('not-a-token')).toBeNull();
16
- });
17
- it('returns null when the JWT has only two parts', () => {
18
- expect(jwtExpiryMs('header.payload')).toBeNull();
19
- });
20
- it('returns null when the JWT has four parts', () => {
21
- expect(jwtExpiryMs('a.b.c.d')).toBeNull();
22
- });
23
- it('returns null when the payload base64 is invalid', () => {
24
- expect(jwtExpiryMs('header.!!!.sig')).toBeNull();
25
- });
26
- it('returns null when the payload has no exp field', () => {
27
- expect(jwtExpiryMs(makeJwt({ sub: 'user-123', iat: nowSec() }))).toBeNull();
28
- });
29
- it('returns null when exp is 0', () => {
30
- expect(jwtExpiryMs(makeJwt({ exp: 0 }))).toBeNull();
31
- });
32
- it('converts exp (seconds) to milliseconds', () => {
33
- const expSec = nowSec() + 3600;
34
- expect(jwtExpiryMs(makeJwt({ exp: expSec }))).toBe(expSec * 1000);
35
- });
36
- it('returns a past timestamp for an expired token', () => {
37
- expect(jwtExpiryMs(makeJwt({ exp: nowSec() - 300 }))).toBeLessThan(Date.now());
38
- });
39
- it('returns a future timestamp for a valid token', () => {
40
- expect(jwtExpiryMs(makeJwt({ exp: nowSec() + 86400 }))).toBeGreaterThan(Date.now());
41
- });
42
- it('handles a token expiring in less than 60 minutes', () => {
43
- const expSec = nowSec() + 30 * 60;
44
- const result = jwtExpiryMs(makeJwt({ exp: expSec }));
45
- const minsLeft = Math.floor((result - Date.now()) / 60_000);
46
- expect(minsLeft).toBeGreaterThanOrEqual(29);
47
- expect(minsLeft).toBeLessThanOrEqual(30);
48
- });
49
- it('ignores extra claims', () => {
50
- const expSec = nowSec() + 3600;
51
- expect(jwtExpiryMs(makeJwt({ sub: 'u', iat: nowSec(), exp: expSec, plan: 'free' }))).toBe(expSec * 1000);
52
- });
53
- it('handles padding-free base64url payloads', () => {
54
- // base64url (no =) — Buffer.from handles this natively
55
- const expSec = nowSec() + 7200;
56
- expect(jwtExpiryMs(makeJwt({ exp: expSec, x: 'a' }))).toBe(expSec * 1000);
57
- });
58
- });
59
- describe('checkJwt', () => {
60
- it('throws InvalidRequest when jwt is empty', () => {
61
- expect(() => checkJwt('')).toThrowError(expect.objectContaining({ code: ErrorCode.InvalidRequest, message: expect.stringContaining('CACHLY_JWT env var not set') }));
62
- });
63
- it('throws with setup URL when jwt is missing', () => {
64
- expect(() => checkJwt('')).toThrowError(expect.objectContaining({ message: expect.stringContaining('cachly.dev/setup-ai') }));
65
- });
66
- it('throws InvalidRequest with expiry timestamp for an expired token', () => {
67
- const expSec = nowSec() - 60;
68
- const token = makeJwt({ exp: expSec });
69
- expect(() => checkJwt(token)).toThrowError(expect.objectContaining({
70
- code: ErrorCode.InvalidRequest,
71
- message: expect.stringContaining('expired at'),
72
- }));
73
- });
74
- it('does not throw for a valid non-expired token', () => {
75
- expect(() => checkJwt(makeJwt({ exp: nowSec() + 3600 }))).not.toThrow();
76
- });
77
- it('does not throw for a token with no exp claim', () => {
78
- expect(() => checkJwt(makeJwt({ sub: 'user' }))).not.toThrow();
79
- });
80
- });
81
- describe('handleApiError', () => {
82
- it('throws InvalidRequest for 401 with the detail message', () => {
83
- expect(() => handleApiError(401, 'token expired')).toThrowError(expect.objectContaining({
84
- code: ErrorCode.InvalidRequest,
85
- message: expect.stringContaining('Authentication failed (401): token expired'),
86
- }));
87
- });
88
- it('throws InvalidRequest for 403 with the detail message', () => {
89
- expect(() => handleApiError(403, 'plan limit reached')).toThrowError(expect.objectContaining({
90
- code: ErrorCode.InvalidRequest,
91
- message: expect.stringContaining('Access denied (403): plan limit reached'),
92
- }));
93
- });
94
- it('throws InternalError for other status codes', () => {
95
- expect(() => handleApiError(500, 'server crash')).toThrowError(expect.objectContaining({
96
- code: ErrorCode.InternalError,
97
- message: expect.stringContaining('cachly API error 500: server crash'),
98
- }));
99
- });
100
- it('includes the cachly.dev/setup-ai link for 401 errors', () => {
101
- expect(() => handleApiError(401, 'Unauthorized')).toThrowError(expect.objectContaining({ message: expect.stringContaining('cachly.dev/setup-ai') }));
102
- });
103
- it('includes account access hint for 403 errors', () => {
104
- expect(() => handleApiError(403, 'Forbidden')).toThrowError(expect.objectContaining({ message: expect.stringContaining('access to this resource') }));
105
- });
106
- });
107
- //# sourceMappingURL=auth.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
package/glama.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "$schema": "https://glama.ai/mcp/schemas/server.json",
3
- "maintainers": [
4
- "HeinrichNebula"
5
- ],
6
- "categories": [
7
- "memory",
8
- "developer-tools",
9
- "productivity",
10
- "knowledge-management",
11
- "team-collaboration"
12
- ],
13
- "keywords": [
14
- "persistent-memory",
15
- "ai-memory",
16
- "mcp-server",
17
- "claude-code",
18
- "cursor",
19
- "copilot",
20
- "windsurf",
21
- "cline",
22
- "zed",
23
- "causal-trace",
24
- "brain-predict",
25
- "smart-recall",
26
- "knowledge-graph",
27
- "team-brain",
28
- "brain-from-git",
29
- "failure-prediction",
30
- "memory-crystals",
31
- "gdpr",
32
- "free-tier",
33
- "readme-badge",
34
- "incremental-learning"
35
- ]
36
- }
package/llms.txt DELETED
@@ -1,155 +0,0 @@
1
- # cachly AI Brain — MCP Server
2
-
3
- > Persistent memory layer for AI coding assistants. Gives Claude Code, Cursor, GitHub Copilot, Windsurf, Cline and Zed a permanent brain that learns from every session, bug fix, git commit, and CI run — automatically.
4
-
5
- ## What cachly is
6
-
7
- cachly is an MCP (Model Context Protocol) server that adds long-term memory to AI coding assistants. Unlike conversation-window context, cachly memory persists forever across sessions, projects, and team members.
8
-
9
- It works by maintaining a Causal Knowledge Graph (CKG) — a structured store of lessons, bug fixes, architecture decisions, deployment commands, and failure patterns. On every session start, the AI receives a briefing with relevant lessons. On every fix or commit, new lessons are automatically stored.
10
-
11
- **Key differentiator**: causal_trace — given any error or problem description, traces the causal chain through the graph (root cause → intermediate causes → known fix). No other memory system does this.
12
-
13
- ## Installation (zero config)
14
-
15
- ```bash
16
- npx @cachly-dev/mcp-server@latest setup
17
- ```
18
-
19
- This command: authenticates via OAuth device flow (one browser click), auto-detects all installed editors, writes the correct MCP config for each, creates CLAUDE.md with Brain rules, installs a git post-commit hook.
20
-
21
- Free tier: 25 MB, no credit card, German servers (GDPR).
22
-
23
- ## Supported editors
24
-
25
- Claude Code, Cursor, Windsurf, VS Code, GitHub Copilot, Cline, Zed, Continue.dev
26
-
27
- ## How memory works
28
-
29
- 1. Session starts → AI receives: last session summary, open failures, relevant lessons, memory crystal (compressed history)
30
- 2. During work → AI stores: WIP state, architecture decisions, file summaries
31
- 3. Bug fixed → lesson stored: what failed, what worked, commands used, severity, file paths
32
- 4. Editor closes → session summary saved, git log analyzed, ambient lessons extracted
33
- 5. Next session → AI arrives pre-briefed
34
-
35
- ## Tool catalog (89 tools)
36
-
37
- ### Session & Memory
38
- - session_start: Single-call session briefing — last summary, open failures, recent lessons, brain health
39
- - session_end: Save session summary when finishing work
40
- - session_handoff: Detailed handoff for next chat window
41
- - session_ping: Lightweight checkpoint every ~5 tool calls
42
- - auto_learn_session: Auto-learn from session observations without explicit calls
43
- - sync_file_changes: Associate recent file changes with brain knowledge
44
-
45
- ### Lesson Storage & Recall
46
- - learn_from_attempts: Store structured lesson from any fix, deploy, or discovery
47
- - recall_best_solution: Best known solution for a topic with success/failure history
48
- - smart_recall: BM25+ semantic search across all brain data (11 languages: EN DE FR ES IT PT ZH JA KO AR HE)
49
- - brain_search: Full-text search over all brain data
50
- - recall_at: Brain archaeology — see what a lesson looked like at a point in time
51
-
52
- ### Causal Analysis & Prediction
53
- - causal_trace: Root Cause Analysis through memory — problem → causal chain → known fix
54
- - brain_predict: Predictive pre-fetch — predicts likely failures given current context, returns fixes
55
- - brain_predict_failures: Pre-deploy failure prediction with probability percentages
56
- - trace_dependency: Find all lessons that depend on a given prerequisite
57
- - knowledge_decay: Confidence scoring for every lesson
58
-
59
- ### Context Management
60
- - remember_context: Save architecture findings, decisions, file summaries
61
- - recall_context: Retrieve saved context by key (supports glob)
62
- - list_remembered: List all cached context entries
63
- - forget_context: Delete cached context entries
64
- - compact_recover: Reconstruct full context from Memory Crystal after context limit
65
-
66
- ### Memory Maintenance
67
- - memory_consolidate: Weekly garbage collector — deduplicates, detects contradictions, expires stale lessons
68
- - memory_crystalize: Compress sessions into dense Memory Crystal snapshot
69
- - crystal_view: Inspect current Memory Crystal
70
- - brain_diff: See exactly what changed in the brain since a point in time
71
- - brain_doctor: Health check — lesson count, IQ boost %, open failures, recommendations
72
- - brain_from_git: Bootstrap brain lessons from existing git history
73
-
74
- ### Team Brain
75
- - team_learn: Store lesson in shared team brain
76
- - team_recall: Recall from shared team brain (shows who learned what)
77
- - team_synthesize: Merge multiple contributors' lessons on same topic into canonical version
78
- - madc_deliberate: Multi-Agent Deliberation Chamber — 6 specialist agents vote on conflicting lessons
79
-
80
- ### Knowledge Commons (Global)
81
- - syndicate: Contribute verified lesson to global Knowledge Commons
82
- - syndicate_search: Search global Knowledge Commons
83
- - syndicate_trending: Trending lessons globally
84
- - fedbrain_contribute: Contribute with cryptographic knowledge certificate
85
- - fedbrain_search: Context-weighted search of global commons
86
- - fedbrain_confirm: Confirm a syndicated lesson worked for you
87
- - publish_lesson: Publish lesson to Cachly Public Brain
88
- - import_public_brain: Import community lessons for a framework
89
- - global_learn: Store lesson that applies across all your projects
90
- - global_recall: Retrieve cross-project lessons
91
-
92
- ### Continuous Learning Stream
93
- - cls_ingest: Ingest learning signals from git commits, CI runs, IDE diagnostics without session_end
94
- - cls_install_hooks: Generate git post-commit hook and GitHub Actions step for automatic learning
95
-
96
- ### Autopilot — self-managing AI instructions
97
- - autopilot: Generate a CLAUDE.md / copilot-instructions.md / .github/copilot-instructions.md from actual Brain content. Supports Claude, Cursor, Copilot, Windsurf, Gemini. style="minimal" for just the hooks, style="full" for complete ruleset with examples. Re-run as the Brain learns more to continuously upgrade the instructions file.
98
-
99
- ### Roadmap & Planning
100
- - roadmap_add: Add item to persistent project roadmap stored in Brain
101
- - roadmap_update: Update roadmap item status, priority, details
102
- - roadmap_list: List roadmap items filtered by status, priority, tag, milestone
103
- - roadmap_next: Get the single most important next actionable item
104
-
105
- ### Cache & Infrastructure
106
- - list_instances / create_instance / delete_instance: Manage Brain instances
107
- - cache_get / cache_set / cache_delete: Standard Redis/Valkey cache operations
108
- - cache_mget / cache_mset: Bulk pipeline (single round-trip)
109
- - cache_exists / cache_ttl / cache_keys / cache_stats: Cache introspection
110
- - semantic_search: Find cached entries by semantic similarity
111
- - cache_warmup: Pre-warm semantic cache with prompt/value pairs
112
- - cache_lock_acquire / cache_lock_release: Distributed locking
113
- - cache_stream_set / cache_stream_get: LLM token stream caching
114
- - index_project: Index source files for semantic search
115
- - detect_namespace: Classify prompts into semantic namespaces
116
- - get_connection_string: Get Redis/Valkey connection string
117
- - get_api_status: Full diagnostic — call when anything is broken
118
-
119
- ### Organization & Teams
120
- - list_orgs / create_org / invite_member / get_org_plan: Multi-tenant organization management
121
- - ckg_inspect: Inspect Causal Knowledge Graph for a concept
122
- - autopilot: Generate CLAUDE.md / copilot-instructions.md for self-managing AI
123
- - setup_ai_memory: One-shot setup of 3-layer AI Memory system
124
- - brain_federate: Private org knowledge transfer
125
-
126
- ## Comparison to alternatives
127
-
128
- | Feature | cachly | mem0 | Plain CLAUDE.md |
129
- |---------|--------|------|-----------------|
130
- | Persistent across sessions | ✅ | ✅ | Manual |
131
- | Causal root cause analysis | ✅ | ❌ | ❌ |
132
- | Automatic (no explicit calls) | ✅ | ❌ | ❌ |
133
- | Team knowledge sharing | ✅ | Paid | ❌ |
134
- | Failure prediction | ✅ | ❌ | ❌ |
135
- | Git-ambient learning | ✅ | ❌ | ❌ |
136
- | 11-language search | ✅ | ❌ | ❌ |
137
- | Free tier forever | ✅ | Limited | ✅ |
138
- | GDPR / EU servers | ✅ | ❌ | ✅ |
139
-
140
- ## Pricing
141
-
142
- - Free: 25 MB, 1 instance, €0/month forever
143
- - Dev: 200 MB, €19/month
144
- - Pro: 900 MB, team features, €49/month
145
- - Speed: 900 MB + Dragonfly engine, €79/month
146
- - Business: 7 GB, €199/month
147
-
148
- All plans: German servers, GDPR-compliant, 99.9% SLA.
149
-
150
- ## Links
151
-
152
- - npm: https://www.npmjs.com/package/@cachly-dev/mcp-server
153
- - GitHub: https://github.com/cachly-dev/cachly-mcp
154
- - Docs: https://cachly.dev/docs/ai-memory
155
- - Dashboard: https://cachly.dev
@@ -1,147 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
3
- import { jwtExpiryMs, checkJwt, handleApiError } from '../auth.js';
4
-
5
- function makeJwt(payload: Record<string, unknown>): string {
6
- const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
7
- const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
8
- return `${header}.${body}.fakesig`;
9
- }
10
-
11
- const nowSec = () => Math.floor(Date.now() / 1000);
12
-
13
- describe('jwtExpiryMs', () => {
14
- it('returns null for an empty string', () => {
15
- expect(jwtExpiryMs('')).toBeNull();
16
- });
17
-
18
- it('returns null for a plain (non-JWT) string', () => {
19
- expect(jwtExpiryMs('not-a-token')).toBeNull();
20
- });
21
-
22
- it('returns null when the JWT has only two parts', () => {
23
- expect(jwtExpiryMs('header.payload')).toBeNull();
24
- });
25
-
26
- it('returns null when the JWT has four parts', () => {
27
- expect(jwtExpiryMs('a.b.c.d')).toBeNull();
28
- });
29
-
30
- it('returns null when the payload base64 is invalid', () => {
31
- expect(jwtExpiryMs('header.!!!.sig')).toBeNull();
32
- });
33
-
34
- it('returns null when the payload has no exp field', () => {
35
- expect(jwtExpiryMs(makeJwt({ sub: 'user-123', iat: nowSec() }))).toBeNull();
36
- });
37
-
38
- it('returns null when exp is 0', () => {
39
- expect(jwtExpiryMs(makeJwt({ exp: 0 }))).toBeNull();
40
- });
41
-
42
- it('converts exp (seconds) to milliseconds', () => {
43
- const expSec = nowSec() + 3600;
44
- expect(jwtExpiryMs(makeJwt({ exp: expSec }))).toBe(expSec * 1000);
45
- });
46
-
47
- it('returns a past timestamp for an expired token', () => {
48
- expect(jwtExpiryMs(makeJwt({ exp: nowSec() - 300 }))).toBeLessThan(Date.now());
49
- });
50
-
51
- it('returns a future timestamp for a valid token', () => {
52
- expect(jwtExpiryMs(makeJwt({ exp: nowSec() + 86400 }))).toBeGreaterThan(Date.now());
53
- });
54
-
55
- it('handles a token expiring in less than 60 minutes', () => {
56
- const expSec = nowSec() + 30 * 60;
57
- const result = jwtExpiryMs(makeJwt({ exp: expSec }))!;
58
- const minsLeft = Math.floor((result - Date.now()) / 60_000);
59
- expect(minsLeft).toBeGreaterThanOrEqual(29);
60
- expect(minsLeft).toBeLessThanOrEqual(30);
61
- });
62
-
63
- it('ignores extra claims', () => {
64
- const expSec = nowSec() + 3600;
65
- expect(jwtExpiryMs(makeJwt({ sub: 'u', iat: nowSec(), exp: expSec, plan: 'free' }))).toBe(expSec * 1000);
66
- });
67
-
68
- it('handles padding-free base64url payloads', () => {
69
- // base64url (no =) — Buffer.from handles this natively
70
- const expSec = nowSec() + 7200;
71
- expect(jwtExpiryMs(makeJwt({ exp: expSec, x: 'a' }))).toBe(expSec * 1000);
72
- });
73
- });
74
-
75
- describe('checkJwt', () => {
76
- it('throws InvalidRequest when jwt is empty', () => {
77
- expect(() => checkJwt('')).toThrowError(
78
- expect.objectContaining({ code: ErrorCode.InvalidRequest, message: expect.stringContaining('CACHLY_JWT env var not set') }),
79
- );
80
- });
81
-
82
- it('throws with setup URL when jwt is missing', () => {
83
- expect(() => checkJwt('')).toThrowError(
84
- expect.objectContaining({ message: expect.stringContaining('cachly.dev/setup-ai') }),
85
- );
86
- });
87
-
88
- it('throws InvalidRequest with expiry timestamp for an expired token', () => {
89
- const expSec = nowSec() - 60;
90
- const token = makeJwt({ exp: expSec });
91
- expect(() => checkJwt(token)).toThrowError(
92
- expect.objectContaining({
93
- code: ErrorCode.InvalidRequest,
94
- message: expect.stringContaining('expired at'),
95
- }),
96
- );
97
- });
98
-
99
- it('does not throw for a valid non-expired token', () => {
100
- expect(() => checkJwt(makeJwt({ exp: nowSec() + 3600 }))).not.toThrow();
101
- });
102
-
103
- it('does not throw for a token with no exp claim', () => {
104
- expect(() => checkJwt(makeJwt({ sub: 'user' }))).not.toThrow();
105
- });
106
- });
107
-
108
- describe('handleApiError', () => {
109
- it('throws InvalidRequest for 401 with the detail message', () => {
110
- expect(() => handleApiError(401, 'token expired')).toThrowError(
111
- expect.objectContaining({
112
- code: ErrorCode.InvalidRequest,
113
- message: expect.stringContaining('Authentication failed (401): token expired'),
114
- }),
115
- );
116
- });
117
-
118
- it('throws InvalidRequest for 403 with the detail message', () => {
119
- expect(() => handleApiError(403, 'plan limit reached')).toThrowError(
120
- expect.objectContaining({
121
- code: ErrorCode.InvalidRequest,
122
- message: expect.stringContaining('Access denied (403): plan limit reached'),
123
- }),
124
- );
125
- });
126
-
127
- it('throws InternalError for other status codes', () => {
128
- expect(() => handleApiError(500, 'server crash')).toThrowError(
129
- expect.objectContaining({
130
- code: ErrorCode.InternalError,
131
- message: expect.stringContaining('cachly API error 500: server crash'),
132
- }),
133
- );
134
- });
135
-
136
- it('includes the cachly.dev/setup-ai link for 401 errors', () => {
137
- expect(() => handleApiError(401, 'Unauthorized')).toThrowError(
138
- expect.objectContaining({ message: expect.stringContaining('cachly.dev/setup-ai') }),
139
- );
140
- });
141
-
142
- it('includes account access hint for 403 errors', () => {
143
- expect(() => handleApiError(403, 'Forbidden')).toThrowError(
144
- expect.objectContaining({ message: expect.stringContaining('access to this resource') }),
145
- );
146
- });
147
- });