get-claudia 1.32.0 → 1.34.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.
@@ -0,0 +1,396 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Bridge } from '../src/bridge.js';
4
+
5
+ // --- Mock helpers ---
6
+
7
+ /**
8
+ * Create a Bridge with mocked internals for testing tool_use flow.
9
+ * Skips start() entirely; sets up provider, mcpClient, and toolManager directly.
10
+ */
11
+ function createTestBridge(opts = {}) {
12
+ const config = {
13
+ model: 'claude-sonnet-4-20250514',
14
+ maxTokens: 2048,
15
+ toolUse: opts.toolUse ?? true,
16
+ toolUseMaxIterations: opts.maxIterations ?? 5,
17
+ preRecall: opts.preRecall ?? true,
18
+ channels: {
19
+ telegram: { model: '', toolUse: opts.telegramToolUse },
20
+ slack: { model: '', toolUse: opts.slackToolUse },
21
+ },
22
+ memoryDaemon: { pythonPath: '/nonexistent', moduleName: 'test' },
23
+ ...opts.configOverrides,
24
+ };
25
+
26
+ const bridge = new Bridge(config);
27
+ bridge.provider = opts.provider || 'anthropic';
28
+ bridge.memoryAvailable = true;
29
+ bridge._personality = 'You are Claudia.';
30
+
31
+ // Mock MCP client
32
+ bridge.mcpClient = {
33
+ callTool: opts.mcpCallTool || (async ({ name, arguments: args }) => ({
34
+ content: [{ type: 'text', text: JSON.stringify({ tool: name, args, ok: true }) }],
35
+ })),
36
+ listTools: async () => ({ tools: [] }),
37
+ };
38
+
39
+ // Mock ToolManager
40
+ bridge._toolManager = {
41
+ isExposed: (name) => name.startsWith('memory.') && !['memory.purge', 'memory.buffer_turn', 'memory.merge_entities'].includes(name),
42
+ getAnthropicTools: () => [
43
+ { name: 'memory.recall', description: 'Search memories', input_schema: { type: 'object', properties: { query: { type: 'string' } } } },
44
+ { name: 'memory.remember', description: 'Store memory', input_schema: { type: 'object', properties: { content: { type: 'string' } } } },
45
+ ],
46
+ getOllamaTools: () => [
47
+ { type: 'function', function: { name: 'memory.recall', description: 'Search memories', parameters: { type: 'object', properties: { query: { type: 'string' } } } } },
48
+ ],
49
+ isReady: () => true,
50
+ toolCount: 2,
51
+ };
52
+ bridge._toolUseEnabled = true;
53
+
54
+ // Mock Anthropic client
55
+ bridge.anthropic = {
56
+ messages: {
57
+ create: opts.anthropicCreate || (async () => ({
58
+ content: [{ type: 'text', text: 'Hello from Claudia!' }],
59
+ stop_reason: 'end_turn',
60
+ usage: { input_tokens: 100, output_tokens: 50 },
61
+ })),
62
+ },
63
+ };
64
+
65
+ return bridge;
66
+ }
67
+
68
+ describe('Bridge._isToolUseEnabled', () => {
69
+ it('returns true for Anthropic by default (auto-detect)', () => {
70
+ const bridge = new Bridge({
71
+ channels: {},
72
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
73
+ });
74
+ bridge.provider = 'anthropic';
75
+ assert.equal(bridge._isToolUseEnabled(), true);
76
+ });
77
+
78
+ it('returns false for Ollama by default (auto-detect)', () => {
79
+ const bridge = new Bridge({
80
+ channels: {},
81
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
82
+ });
83
+ bridge.provider = 'ollama';
84
+ assert.equal(bridge._isToolUseEnabled(), false);
85
+ });
86
+
87
+ it('respects global toolUse: false override', () => {
88
+ const bridge = new Bridge({
89
+ toolUse: false,
90
+ channels: {},
91
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
92
+ });
93
+ bridge.provider = 'anthropic';
94
+ assert.equal(bridge._isToolUseEnabled(), false);
95
+ });
96
+
97
+ it('respects per-channel toolUse override', () => {
98
+ const bridge = new Bridge({
99
+ toolUse: true,
100
+ channels: { telegram: { toolUse: false } },
101
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
102
+ });
103
+ bridge.provider = 'anthropic';
104
+
105
+ assert.equal(bridge._isToolUseEnabled('telegram'), false);
106
+ assert.equal(bridge._isToolUseEnabled('slack'), true);
107
+ });
108
+
109
+ it('per-channel undefined falls through to global', () => {
110
+ const bridge = new Bridge({
111
+ toolUse: true,
112
+ channels: { telegram: { toolUse: undefined } },
113
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
114
+ });
115
+ bridge.provider = 'anthropic';
116
+ assert.equal(bridge._isToolUseEnabled('telegram'), true);
117
+ });
118
+ });
119
+
120
+ describe('Bridge._callAnthropicWithTools', () => {
121
+ it('handles text-only response (no tool calls)', async () => {
122
+ const bridge = createTestBridge();
123
+
124
+ const result = await bridge._callAnthropicWithTools(
125
+ 'You are Claudia.',
126
+ [{ role: 'user', content: 'Hello' }],
127
+ 'claude-sonnet-4-20250514',
128
+ 'telegram'
129
+ );
130
+
131
+ assert.equal(result.text, 'Hello from Claudia!');
132
+ assert.ok(result.usage);
133
+ });
134
+
135
+ it('executes single tool call and returns final text', async () => {
136
+ let callCount = 0;
137
+ const bridge = createTestBridge({
138
+ anthropicCreate: async (params) => {
139
+ callCount++;
140
+ if (callCount === 1) {
141
+ // First call: model wants to use a tool
142
+ return {
143
+ content: [
144
+ { type: 'text', text: 'Let me check...' },
145
+ { type: 'tool_use', id: 'tu_1', name: 'memory.recall', input: { query: 'user preferences' } },
146
+ ],
147
+ stop_reason: 'tool_use',
148
+ usage: { input_tokens: 50, output_tokens: 30 },
149
+ };
150
+ }
151
+ // Second call: model responds with text
152
+ return {
153
+ content: [{ type: 'text', text: 'Based on your preferences, you like coffee.' }],
154
+ stop_reason: 'end_turn',
155
+ usage: { input_tokens: 80, output_tokens: 40 },
156
+ };
157
+ },
158
+ });
159
+
160
+ const messages = [{ role: 'user', content: 'What do I like?' }];
161
+ const result = await bridge._callAnthropicWithTools(
162
+ 'System prompt',
163
+ messages,
164
+ 'claude-sonnet-4-20250514',
165
+ 'telegram'
166
+ );
167
+
168
+ assert.equal(result.text, 'Based on your preferences, you like coffee.');
169
+ assert.equal(callCount, 2);
170
+ // Usage should be accumulated
171
+ assert.equal(result.usage.input_tokens, 130);
172
+ assert.equal(result.usage.output_tokens, 70);
173
+ });
174
+
175
+ it('handles multiple tool calls in one response', async () => {
176
+ let callCount = 0;
177
+ const toolCalls = [];
178
+
179
+ const bridge = createTestBridge({
180
+ anthropicCreate: async () => {
181
+ callCount++;
182
+ if (callCount === 1) {
183
+ return {
184
+ content: [
185
+ { type: 'tool_use', id: 'tu_1', name: 'memory.recall', input: { query: 'Alice' } },
186
+ { type: 'tool_use', id: 'tu_2', name: 'memory.recall', input: { query: 'Bob' } },
187
+ ],
188
+ stop_reason: 'tool_use',
189
+ usage: { input_tokens: 50, output_tokens: 30 },
190
+ };
191
+ }
192
+ return {
193
+ content: [{ type: 'text', text: 'Found info on both.' }],
194
+ stop_reason: 'end_turn',
195
+ usage: { input_tokens: 80, output_tokens: 20 },
196
+ };
197
+ },
198
+ mcpCallTool: async ({ name, arguments: args }) => {
199
+ toolCalls.push({ name, args });
200
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true }) }] };
201
+ },
202
+ });
203
+
204
+ const result = await bridge._callAnthropicWithTools(
205
+ 'System',
206
+ [{ role: 'user', content: 'Tell me about Alice and Bob' }],
207
+ 'model',
208
+ 'telegram'
209
+ );
210
+
211
+ assert.equal(result.text, 'Found info on both.');
212
+ assert.equal(toolCalls.length, 2);
213
+ assert.equal(toolCalls[0].args.query, 'Alice');
214
+ assert.equal(toolCalls[1].args.query, 'Bob');
215
+ });
216
+
217
+ it('respects max iterations guard', async () => {
218
+ let callCount = 0;
219
+
220
+ const bridge = createTestBridge({
221
+ maxIterations: 2,
222
+ anthropicCreate: async (params) => {
223
+ callCount++;
224
+ // Always request tool use (except final forced call without tools)
225
+ if (params.tools) {
226
+ return {
227
+ content: [
228
+ { type: 'tool_use', id: `tu_${callCount}`, name: 'memory.recall', input: { query: 'test' } },
229
+ ],
230
+ stop_reason: 'tool_use',
231
+ usage: { input_tokens: 10, output_tokens: 10 },
232
+ };
233
+ }
234
+ // Final call without tools
235
+ return {
236
+ content: [{ type: 'text', text: 'Final response after max iterations' }],
237
+ stop_reason: 'end_turn',
238
+ usage: { input_tokens: 10, output_tokens: 10 },
239
+ };
240
+ },
241
+ });
242
+
243
+ const result = await bridge._callAnthropicWithTools(
244
+ 'System',
245
+ [{ role: 'user', content: 'Keep calling' }],
246
+ 'model',
247
+ 'telegram'
248
+ );
249
+
250
+ assert.equal(result.text, 'Final response after max iterations');
251
+ // 2 iterations with tools + 1 final without tools = 3 total API calls
252
+ assert.equal(callCount, 3);
253
+ });
254
+ });
255
+
256
+ describe('Bridge._executeToolCall', () => {
257
+ it('rejects non-exposed tools', async () => {
258
+ const bridge = createTestBridge();
259
+ const result = await bridge._executeToolCall('memory.purge', {}, 'telegram');
260
+
261
+ const parsed = JSON.parse(result);
262
+ assert.ok(parsed.error);
263
+ assert.ok(parsed.error.includes('not available'));
264
+ });
265
+
266
+ it('auto-injects source_channel for memory.remember', async () => {
267
+ let capturedArgs;
268
+ const bridge = createTestBridge({
269
+ mcpCallTool: async ({ name, arguments: args }) => {
270
+ capturedArgs = args;
271
+ return { content: [{ type: 'text', text: '{"ok": true}' }] };
272
+ },
273
+ });
274
+
275
+ await bridge._executeToolCall('memory.remember', { content: 'User likes tea' }, 'telegram');
276
+
277
+ assert.equal(capturedArgs.source_channel, 'telegram');
278
+ assert.equal(capturedArgs.content, 'User likes tea');
279
+ });
280
+
281
+ it('auto-injects source_channel for memory.batch', async () => {
282
+ let capturedArgs;
283
+ const bridge = createTestBridge({
284
+ mcpCallTool: async ({ name, arguments: args }) => {
285
+ capturedArgs = args;
286
+ return { content: [{ type: 'text', text: '{"ok": true}' }] };
287
+ },
288
+ });
289
+
290
+ await bridge._executeToolCall('memory.batch', { operations: [] }, 'slack');
291
+
292
+ assert.equal(capturedArgs.source_channel, 'slack');
293
+ });
294
+
295
+ it('does not inject source_channel for read operations', async () => {
296
+ let capturedArgs;
297
+ const bridge = createTestBridge({
298
+ mcpCallTool: async ({ name, arguments: args }) => {
299
+ capturedArgs = args;
300
+ return { content: [{ type: 'text', text: '{"results": []}' }] };
301
+ },
302
+ });
303
+
304
+ await bridge._executeToolCall('memory.recall', { query: 'test' }, 'telegram');
305
+
306
+ assert.equal(capturedArgs.source_channel, undefined);
307
+ assert.equal(capturedArgs.query, 'test');
308
+ });
309
+
310
+ it('returns error JSON on MCP failure (never throws)', async () => {
311
+ const bridge = createTestBridge({
312
+ mcpCallTool: async () => {
313
+ throw new Error('Connection lost');
314
+ },
315
+ });
316
+
317
+ const result = await bridge._executeToolCall('memory.recall', { query: 'test' }, 'telegram');
318
+ const parsed = JSON.parse(result);
319
+
320
+ assert.ok(parsed.error);
321
+ assert.ok(parsed.error.includes('Connection lost'));
322
+ });
323
+ });
324
+
325
+ describe('Bridge.processMessage with toolUse', () => {
326
+ it('uses tool loop when toolUse is enabled', async () => {
327
+ let usedTools = false;
328
+ const bridge = createTestBridge({
329
+ anthropicCreate: async (params) => {
330
+ if (params.tools) usedTools = true;
331
+ return {
332
+ content: [{ type: 'text', text: 'Response with tools' }],
333
+ stop_reason: 'end_turn',
334
+ usage: { input_tokens: 10, output_tokens: 10 },
335
+ };
336
+ },
337
+ });
338
+
339
+ const result = await bridge.processMessage(
340
+ { text: 'Hello', userId: '1', userName: 'Test', channel: 'telegram' },
341
+ []
342
+ );
343
+
344
+ assert.equal(result.text, 'Response with tools');
345
+ assert.ok(usedTools, 'Should have passed tools to API');
346
+ });
347
+
348
+ it('skips tool loop when toolUse is disabled', async () => {
349
+ let usedTools = false;
350
+ const bridge = createTestBridge({
351
+ toolUse: false,
352
+ anthropicCreate: async (params) => {
353
+ if (params.tools) usedTools = true;
354
+ return {
355
+ content: [{ type: 'text', text: 'Response without tools' }],
356
+ stop_reason: 'end_turn',
357
+ usage: { input_tokens: 10, output_tokens: 10 },
358
+ };
359
+ },
360
+ });
361
+ bridge._toolUseEnabled = false;
362
+
363
+ const result = await bridge.processMessage(
364
+ { text: 'Hello', userId: '1', userName: 'Test', channel: 'telegram' },
365
+ []
366
+ );
367
+
368
+ assert.equal(result.text, 'Response without tools');
369
+ assert.ok(!usedTools, 'Should NOT have passed tools to API');
370
+ });
371
+ });
372
+
373
+ describe('Bridge._buildSystemPrompt with toolUse', () => {
374
+ it('appends tool instructions when toolUse is true', () => {
375
+ const bridge = new Bridge({
376
+ channels: {},
377
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
378
+ });
379
+ bridge._personality = 'You are Claudia.';
380
+
381
+ const prompt = bridge._buildSystemPrompt('', 'Test', 'telegram', true);
382
+ assert.ok(prompt.includes('# Memory Tools'));
383
+ assert.ok(prompt.includes('Search for more context'));
384
+ });
385
+
386
+ it('omits tool instructions when toolUse is false', () => {
387
+ const bridge = new Bridge({
388
+ channels: {},
389
+ memoryDaemon: { pythonPath: '/x', moduleName: 'x' },
390
+ });
391
+ bridge._personality = 'You are Claudia.';
392
+
393
+ const prompt = bridge._buildSystemPrompt('', 'Test', 'telegram', false);
394
+ assert.ok(!prompt.includes('# Memory Tools'));
395
+ });
396
+ });
@@ -0,0 +1,192 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ToolManager, EXPOSED_TOOLS } from '../src/tools.js';
4
+
5
+ // Mock MCP tool schemas matching what the daemon returns
6
+ const MOCK_MCP_TOOLS = [
7
+ {
8
+ name: 'memory.recall',
9
+ description: 'Search memories semantically',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ query: { type: 'string', description: 'Search query' },
14
+ limit: { type: 'integer', description: 'Max results' },
15
+ },
16
+ required: ['query'],
17
+ },
18
+ },
19
+ {
20
+ name: 'memory.remember',
21
+ description: 'Store a new memory',
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ content: { type: 'string', description: 'Memory content' },
26
+ about: { type: ['array', 'string'], description: 'Related entities' },
27
+ importance: { type: 'number', description: 'Importance score' },
28
+ },
29
+ required: ['content'],
30
+ },
31
+ },
32
+ {
33
+ name: 'memory.about',
34
+ description: 'Get context about an entity',
35
+ inputSchema: {
36
+ type: 'object',
37
+ properties: {
38
+ entity: { type: 'string' },
39
+ },
40
+ required: ['entity'],
41
+ },
42
+ },
43
+ // Gateway-internal tool (should be filtered out)
44
+ {
45
+ name: 'memory.buffer_turn',
46
+ description: 'Buffer a conversation turn',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ user_content: { type: 'string' },
51
+ assistant_content: { type: 'string' },
52
+ },
53
+ },
54
+ },
55
+ // Destructive/admin tool (should be filtered out)
56
+ {
57
+ name: 'memory.purge',
58
+ description: 'Purge all memories',
59
+ inputSchema: { type: 'object', properties: {} },
60
+ },
61
+ {
62
+ name: 'memory.merge_entities',
63
+ description: 'Merge duplicate entities',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: {
67
+ source: { type: 'string' },
68
+ target: { type: 'string' },
69
+ },
70
+ },
71
+ },
72
+ ];
73
+
74
+ class MockMcpClient {
75
+ constructor(tools = MOCK_MCP_TOOLS) {
76
+ this._tools = tools;
77
+ }
78
+ async listTools() {
79
+ return { tools: this._tools };
80
+ }
81
+ }
82
+
83
+ describe('ToolManager', () => {
84
+ it('filters to only exposed tools', async () => {
85
+ const tm = new ToolManager();
86
+ await tm.initialize(new MockMcpClient());
87
+
88
+ const tools = tm.getAnthropicTools();
89
+ const names = tools.map((t) => t.name);
90
+
91
+ assert.ok(names.includes('memory.recall'));
92
+ assert.ok(names.includes('memory.remember'));
93
+ assert.ok(names.includes('memory.about'));
94
+ assert.ok(!names.includes('memory.buffer_turn'), 'Should filter out gateway-internal tools');
95
+ assert.ok(!names.includes('memory.purge'), 'Should filter out destructive tools');
96
+ assert.ok(!names.includes('memory.merge_entities'), 'Should filter out admin tools');
97
+ });
98
+
99
+ it('converts MCP inputSchema to Anthropic input_schema', async () => {
100
+ const tm = new ToolManager();
101
+ await tm.initialize(new MockMcpClient());
102
+
103
+ const recallTool = tm.getAnthropicTools().find((t) => t.name === 'memory.recall');
104
+ assert.ok(recallTool);
105
+ assert.ok(recallTool.input_schema, 'Should have input_schema (snake_case)');
106
+ assert.equal(recallTool.input_schema.type, 'object');
107
+ assert.ok(recallTool.input_schema.properties.query);
108
+ assert.deepEqual(recallTool.input_schema.required, ['query']);
109
+ });
110
+
111
+ it('normalizes union types like ["array", "string"]', async () => {
112
+ const tm = new ToolManager();
113
+ await tm.initialize(new MockMcpClient());
114
+
115
+ const rememberTool = tm.getAnthropicTools().find((t) => t.name === 'memory.remember');
116
+ const aboutProp = rememberTool.input_schema.properties.about;
117
+
118
+ // Should normalize to the first non-null type
119
+ assert.equal(aboutProp.type, 'array');
120
+ // Should note the alternative in description
121
+ assert.ok(aboutProp.description.includes('string'), 'Should mention alternative type');
122
+ });
123
+
124
+ it('converts to Ollama format', async () => {
125
+ const tm = new ToolManager();
126
+ await tm.initialize(new MockMcpClient());
127
+
128
+ const tools = tm.getOllamaTools();
129
+ assert.ok(tools.length > 0);
130
+
131
+ const recallTool = tools.find((t) => t.function.name === 'memory.recall');
132
+ assert.ok(recallTool);
133
+ assert.equal(recallTool.type, 'function');
134
+ assert.ok(recallTool.function.description);
135
+ assert.ok(recallTool.function.parameters);
136
+ assert.equal(recallTool.function.parameters.type, 'object');
137
+ });
138
+
139
+ it('isExposed returns correct values', () => {
140
+ const tm = new ToolManager();
141
+
142
+ assert.equal(tm.isExposed('memory.recall'), true);
143
+ assert.equal(tm.isExposed('memory.remember'), true);
144
+ assert.equal(tm.isExposed('memory.trace'), true);
145
+ assert.equal(tm.isExposed('memory.buffer_turn'), false);
146
+ assert.equal(tm.isExposed('memory.purge'), false);
147
+ assert.equal(tm.isExposed('memory.merge_entities'), false);
148
+ assert.equal(tm.isExposed('nonexistent.tool'), false);
149
+ });
150
+
151
+ it('handles empty tool list gracefully', async () => {
152
+ const tm = new ToolManager();
153
+ await tm.initialize(new MockMcpClient([]));
154
+
155
+ assert.deepEqual(tm.getAnthropicTools(), []);
156
+ assert.deepEqual(tm.getOllamaTools(), []);
157
+ assert.equal(tm.isReady(), false);
158
+ assert.equal(tm.toolCount, 0);
159
+ });
160
+
161
+ it('handles MCP client error gracefully', async () => {
162
+ const failingClient = {
163
+ async listTools() {
164
+ throw new Error('Connection refused');
165
+ },
166
+ };
167
+
168
+ const tm = new ToolManager();
169
+ await tm.initialize(failingClient);
170
+
171
+ assert.equal(tm.isReady(), false);
172
+ assert.deepEqual(tm.getAnthropicTools(), []);
173
+ });
174
+
175
+ it('EXPOSED_TOOLS contains expected set of 14 tools', () => {
176
+ assert.equal(EXPOSED_TOOLS.size, 14);
177
+ assert.ok(EXPOSED_TOOLS.has('memory.recall'));
178
+ assert.ok(EXPOSED_TOOLS.has('memory.about'));
179
+ assert.ok(EXPOSED_TOOLS.has('memory.remember'));
180
+ assert.ok(EXPOSED_TOOLS.has('memory.relate'));
181
+ assert.ok(EXPOSED_TOOLS.has('memory.entity'));
182
+ assert.ok(EXPOSED_TOOLS.has('memory.search_entities'));
183
+ assert.ok(EXPOSED_TOOLS.has('memory.batch'));
184
+ assert.ok(EXPOSED_TOOLS.has('memory.correct'));
185
+ assert.ok(EXPOSED_TOOLS.has('memory.invalidate'));
186
+ assert.ok(EXPOSED_TOOLS.has('memory.trace'));
187
+ assert.ok(EXPOSED_TOOLS.has('memory.reflections'));
188
+ assert.ok(EXPOSED_TOOLS.has('memory.project_network'));
189
+ assert.ok(EXPOSED_TOOLS.has('memory.find_path'));
190
+ assert.ok(EXPOSED_TOOLS.has('memory.briefing'));
191
+ });
192
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-claudia",
3
- "version": "1.32.0",
3
+ "version": "1.34.0",
4
4
  "description": "An AI assistant who learns how you work.",
5
5
  "keywords": [
6
6
  "claudia",
@@ -106,7 +106,8 @@ argument-hint: [arg] # Optional: show in /skill [arg] help
106
106
  | `brain/` | 3D memory visualizer | "show your brain" |
107
107
  | `brain-monitor/` | Terminal memory dashboard | "brain monitor", "memory dashboard" |
108
108
  | `meditate/` | End-of-session reflection | "let's wrap up", "end the session" |
109
- | `setup-telegram.md` | Guided Telegram relay setup | "set up Telegram", "connect Telegram" |
109
+ | `setup-telegram.md` | Guided Telegram relay setup | "Telegram relay", "set up relay" |
110
+ | `setup-gateway.md` | Guided gateway setup (Telegram/Slack) | "set up gateway", "connect Telegram", "setup messaging" |
110
111
 
111
112
  ### Explicit Only (`/skill-name`)
112
113
 
@@ -146,7 +147,7 @@ effort-level: medium
146
147
  | Effort | Skills |
147
148
  |--------|--------|
148
149
  | **low** | morning-brief, accountability-check, client-health, financial-snapshot, growth-check, databases, diagnose, brain-monitor |
149
- | **medium** | meeting-prep, draft-reply, follow-up-draft, file-document, new-person, capture-meeting, summarize-doc, memory-audit, brain, gateway, fix-duplicates, memory-health, memory-manager, onboarding, structure-generator, agent-dispatcher, setup-telegram |
150
+ | **medium** | meeting-prep, draft-reply, follow-up-draft, file-document, new-person, capture-meeting, summarize-doc, memory-audit, brain, gateway, fix-duplicates, memory-health, memory-manager, onboarding, structure-generator, agent-dispatcher, setup-telegram, setup-gateway |
150
151
  | **high** | weekly-review, meditate, research, what-am-i-missing, map-connections, commitment-detector, capability-suggester, concierge, connector-discovery, pattern-recognizer, relationship-tracker, risk-surfacer, structure-evolution, hire-agent |
151
152
  | **max** | ingest-sources, pipeline-review, deep-context |
152
153
 
@@ -9,7 +9,7 @@ effort-level: medium
9
9
 
10
10
  Manage the Claudia Gateway service (Telegram, Slack) from within a session. Start, stop, or check status without needing a separate terminal.
11
11
 
12
- **Triggers:** `/gateway`, `/gateway start`, `/gateway stop`, `/gateway status`, or natural language like "start the gateway", "connect telegram", "stop the gateway", "is the gateway running?"
12
+ **Triggers:** `/gateway`, `/gateway start`, `/gateway stop`, `/gateway status`, or natural language like "start the gateway", "stop the gateway", "is the gateway running?"
13
13
 
14
14
  ---
15
15
 
@@ -98,7 +98,7 @@ ls ~/.claudia/gateway/src/index.js 2>/dev/null
98
98
 
99
99
  If not found:
100
100
  ```
101
- "The gateway isn't installed yet. You can set it up by running the gateway installer:
101
+ "The gateway isn't installed yet. Run `/setup-gateway` for a guided walkthrough, or install manually:
102
102
  `bash ~/.claudia/gateway/scripts/install.sh`"
103
103
  ```
104
104
 
@@ -147,9 +147,9 @@ If failed, show the error from the log:
147
147
  [relevant lines from log]
148
148
 
149
149
  Common issues:
150
- - `TELEGRAM_BOT_TOKEN` not set in your shell profile or `~/.claudia/config.json`
150
+ - `TELEGRAM_BOT_TOKEN` not set in your shell profile. Run `/setup-gateway` to configure it.
151
151
  - Ollama not running (if using local models)
152
- - Port conflict on 3848"
152
+ - Port conflict on 3849"
153
153
  ```
154
154
 
155
155
  ---