@kernel.chat/kbot 3.2.0 → 3.3.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/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/tools/bash.test.d.ts +2 -0
- package/dist/tools/bash.test.d.ts.map +1 -0
- package/dist/tools/bash.test.js +284 -0
- package/dist/tools/bash.test.js.map +1 -0
- package/dist/tools/database.js +3 -1
- package/dist/tools/database.js.map +1 -1
- package/dist/tools/fetch.test.d.ts +2 -0
- package/dist/tools/fetch.test.d.ts.map +1 -0
- package/dist/tools/fetch.test.js +363 -0
- package/dist/tools/fetch.test.js.map +1 -0
- package/dist/tools/files.test.d.ts +2 -0
- package/dist/tools/files.test.d.ts.map +1 -0
- package/dist/tools/files.test.js +395 -0
- package/dist/tools/files.test.js.map +1 -0
- package/dist/tools/git.test.d.ts +2 -0
- package/dist/tools/git.test.d.ts.map +1 -0
- package/dist/tools/git.test.js +303 -0
- package/dist/tools/git.test.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/search.test.d.ts +2 -0
- package/dist/tools/search.test.d.ts.map +1 -0
- package/dist/tools/search.test.js +311 -0
- package/dist/tools/search.test.js.map +1 -0
- package/dist/tools/social.d.ts +2 -0
- package/dist/tools/social.d.ts.map +1 -0
- package/dist/tools/social.js +315 -0
- package/dist/tools/social.js.map +1 -0
- package/dist/updater.js +1 -1
- package/dist/updater.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// kbot Search Tools Tests — Mocked (no real network calls)
|
|
2
|
+
import { describe, it, expect, vi, beforeAll, afterAll } from 'vitest';
|
|
3
|
+
import { executeTool, getTool } from './index.js';
|
|
4
|
+
import { registerSearchTools } from './search.js';
|
|
5
|
+
// Register once
|
|
6
|
+
registerSearchTools();
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
8
|
+
// Mock global fetch to avoid real network calls
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
10
|
+
const originalFetch = globalThis.fetch;
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
globalThis.fetch = vi.fn();
|
|
13
|
+
});
|
|
14
|
+
afterAll(() => {
|
|
15
|
+
globalThis.fetch = originalFetch;
|
|
16
|
+
});
|
|
17
|
+
function mockFetch(impl) {
|
|
18
|
+
;
|
|
19
|
+
globalThis.fetch.mockImplementation(impl);
|
|
20
|
+
}
|
|
21
|
+
function mockFetchReset() {
|
|
22
|
+
;
|
|
23
|
+
globalThis.fetch.mockReset();
|
|
24
|
+
}
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
26
|
+
// 1. Registration
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
28
|
+
describe('Search Tools Registration', () => {
|
|
29
|
+
it('registers web_search', () => {
|
|
30
|
+
const tool = getTool('web_search');
|
|
31
|
+
expect(tool).toBeTruthy();
|
|
32
|
+
expect(tool.tier).toBe('free');
|
|
33
|
+
expect(tool.parameters.query.required).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('registers research', () => {
|
|
36
|
+
const tool = getTool('research');
|
|
37
|
+
expect(tool).toBeTruthy();
|
|
38
|
+
expect(tool.tier).toBe('free');
|
|
39
|
+
expect(tool.parameters.topic.required).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
43
|
+
// 2. web_search — DuckDuckGo success
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
45
|
+
describe('web_search', () => {
|
|
46
|
+
it('returns DuckDuckGo instant answer', async () => {
|
|
47
|
+
mockFetch(async (url) => {
|
|
48
|
+
const urlStr = String(url);
|
|
49
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
50
|
+
return new Response(JSON.stringify({
|
|
51
|
+
AbstractText: 'TypeScript is a typed superset of JavaScript.',
|
|
52
|
+
AbstractSource: 'Wikipedia',
|
|
53
|
+
RelatedTopics: [],
|
|
54
|
+
}), { status: 200 });
|
|
55
|
+
}
|
|
56
|
+
// Wikipedia fallback
|
|
57
|
+
if (urlStr.includes('wikipedia.org')) {
|
|
58
|
+
return new Response(JSON.stringify({
|
|
59
|
+
extract: 'TypeScript is a programming language developed by Microsoft.',
|
|
60
|
+
}), { status: 200 });
|
|
61
|
+
}
|
|
62
|
+
return new Response('', { status: 404 });
|
|
63
|
+
});
|
|
64
|
+
const result = await executeTool({
|
|
65
|
+
id: 'ws-1',
|
|
66
|
+
name: 'web_search',
|
|
67
|
+
arguments: { query: 'TypeScript' },
|
|
68
|
+
});
|
|
69
|
+
expect(result.error).toBeUndefined();
|
|
70
|
+
expect(result.result).toContain('TypeScript');
|
|
71
|
+
expect(result.result).toContain('Wikipedia');
|
|
72
|
+
});
|
|
73
|
+
it('returns related topics from DuckDuckGo', async () => {
|
|
74
|
+
mockFetch(async (url) => {
|
|
75
|
+
const urlStr = String(url);
|
|
76
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
77
|
+
return new Response(JSON.stringify({
|
|
78
|
+
AbstractText: '',
|
|
79
|
+
RelatedTopics: [
|
|
80
|
+
{ Text: 'React is a JavaScript library' },
|
|
81
|
+
{ Text: 'Vue.js is a progressive framework' },
|
|
82
|
+
],
|
|
83
|
+
}), { status: 200 });
|
|
84
|
+
}
|
|
85
|
+
return new Response(JSON.stringify({}), { status: 200 });
|
|
86
|
+
});
|
|
87
|
+
const result = await executeTool({
|
|
88
|
+
id: 'ws-2',
|
|
89
|
+
name: 'web_search',
|
|
90
|
+
arguments: { query: 'frontend frameworks' },
|
|
91
|
+
});
|
|
92
|
+
expect(result.error).toBeUndefined();
|
|
93
|
+
expect(result.result).toContain('React is a JavaScript library');
|
|
94
|
+
expect(result.result).toContain('Vue.js');
|
|
95
|
+
});
|
|
96
|
+
it('includes StackOverflow for programming queries', async () => {
|
|
97
|
+
mockFetch(async (url) => {
|
|
98
|
+
const urlStr = String(url);
|
|
99
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
100
|
+
return new Response(JSON.stringify({ AbstractText: '', RelatedTopics: [] }), { status: 200 });
|
|
101
|
+
}
|
|
102
|
+
if (urlStr.includes('wikipedia.org')) {
|
|
103
|
+
return new Response(JSON.stringify({}), { status: 200 });
|
|
104
|
+
}
|
|
105
|
+
if (urlStr.includes('stackexchange.com')) {
|
|
106
|
+
return new Response(JSON.stringify({
|
|
107
|
+
items: [
|
|
108
|
+
{ title: 'How to install npm packages', excerpt: 'Use npm install to add packages to your project...' },
|
|
109
|
+
],
|
|
110
|
+
}), { status: 200 });
|
|
111
|
+
}
|
|
112
|
+
return new Response('', { status: 404 });
|
|
113
|
+
});
|
|
114
|
+
const result = await executeTool({
|
|
115
|
+
id: 'ws-3',
|
|
116
|
+
name: 'web_search',
|
|
117
|
+
arguments: { query: 'how to install npm packages' },
|
|
118
|
+
});
|
|
119
|
+
expect(result.error).toBeUndefined();
|
|
120
|
+
expect(result.result).toContain('Stack Overflow');
|
|
121
|
+
expect(result.result).toContain('npm');
|
|
122
|
+
});
|
|
123
|
+
it('returns fallback message when no results', async () => {
|
|
124
|
+
mockFetch(async () => {
|
|
125
|
+
return new Response(JSON.stringify({ AbstractText: '', RelatedTopics: [] }), { status: 200 });
|
|
126
|
+
});
|
|
127
|
+
const result = await executeTool({
|
|
128
|
+
id: 'ws-4',
|
|
129
|
+
name: 'web_search',
|
|
130
|
+
arguments: { query: 'xyznonexistentquery12345' },
|
|
131
|
+
});
|
|
132
|
+
expect(result.error).toBeUndefined();
|
|
133
|
+
expect(result.result).toContain('No instant results');
|
|
134
|
+
expect(result.result).toContain('url_fetch');
|
|
135
|
+
});
|
|
136
|
+
it('handles fetch failures gracefully', async () => {
|
|
137
|
+
mockFetch(async () => {
|
|
138
|
+
throw new Error('Network error');
|
|
139
|
+
});
|
|
140
|
+
const result = await executeTool({
|
|
141
|
+
id: 'ws-5',
|
|
142
|
+
name: 'web_search',
|
|
143
|
+
arguments: { query: 'test query' },
|
|
144
|
+
});
|
|
145
|
+
// Should not throw — should return fallback
|
|
146
|
+
expect(result.error).toBeUndefined();
|
|
147
|
+
expect(result.result).toContain('No instant results');
|
|
148
|
+
});
|
|
149
|
+
it('handles Wikipedia 404 gracefully', async () => {
|
|
150
|
+
mockFetch(async (url) => {
|
|
151
|
+
const urlStr = String(url);
|
|
152
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
153
|
+
return new Response(JSON.stringify({
|
|
154
|
+
AbstractText: 'Some answer',
|
|
155
|
+
AbstractSource: 'DDG',
|
|
156
|
+
RelatedTopics: [],
|
|
157
|
+
}), { status: 200 });
|
|
158
|
+
}
|
|
159
|
+
// Wikipedia returns 404
|
|
160
|
+
return new Response('Not found', { status: 404 });
|
|
161
|
+
});
|
|
162
|
+
const result = await executeTool({
|
|
163
|
+
id: 'ws-6',
|
|
164
|
+
name: 'web_search',
|
|
165
|
+
arguments: { query: 'obscure topic' },
|
|
166
|
+
});
|
|
167
|
+
expect(result.error).toBeUndefined();
|
|
168
|
+
expect(result.result).toContain('Some answer');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
172
|
+
// 3. research
|
|
173
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
174
|
+
describe('research', () => {
|
|
175
|
+
it('fetches provided URLs and DDG/Wikipedia', async () => {
|
|
176
|
+
mockFetch(async (url) => {
|
|
177
|
+
const urlStr = String(url);
|
|
178
|
+
if (urlStr.includes('example.com')) {
|
|
179
|
+
return new Response('<html><body><p>Example content about AI</p></body></html>', {
|
|
180
|
+
status: 200,
|
|
181
|
+
headers: { 'content-type': 'text/html' },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
185
|
+
return new Response(JSON.stringify({
|
|
186
|
+
AbstractText: 'AI is a branch of computer science.',
|
|
187
|
+
AbstractSource: 'Wikipedia',
|
|
188
|
+
RelatedTopics: [],
|
|
189
|
+
}), { status: 200 });
|
|
190
|
+
}
|
|
191
|
+
if (urlStr.includes('wikipedia.org')) {
|
|
192
|
+
return new Response(JSON.stringify({
|
|
193
|
+
extract: 'Artificial intelligence is intelligence demonstrated by machines.',
|
|
194
|
+
}), { status: 200 });
|
|
195
|
+
}
|
|
196
|
+
return new Response('', { status: 404 });
|
|
197
|
+
});
|
|
198
|
+
const result = await executeTool({
|
|
199
|
+
id: 'r-1',
|
|
200
|
+
name: 'research',
|
|
201
|
+
arguments: {
|
|
202
|
+
topic: 'artificial intelligence',
|
|
203
|
+
urls: ['https://example.com/ai-page'],
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
expect(result.error).toBeUndefined();
|
|
207
|
+
expect(result.result).toContain('Research results for: artificial intelligence');
|
|
208
|
+
expect(result.result).toContain('Example content about AI');
|
|
209
|
+
expect(result.result).toContain('AI is a branch of computer science');
|
|
210
|
+
expect(result.result).toContain('Wikipedia');
|
|
211
|
+
});
|
|
212
|
+
it('limits URLs to 5', async () => {
|
|
213
|
+
let fetchCount = 0;
|
|
214
|
+
mockFetch(async (url) => {
|
|
215
|
+
const urlStr = String(url);
|
|
216
|
+
if (urlStr.includes('example.com')) {
|
|
217
|
+
fetchCount++;
|
|
218
|
+
return new Response(`Content ${fetchCount}`, { status: 200 });
|
|
219
|
+
}
|
|
220
|
+
return new Response(JSON.stringify({ AbstractText: '', RelatedTopics: [] }), { status: 200 });
|
|
221
|
+
});
|
|
222
|
+
const urls = Array.from({ length: 10 }, (_, i) => `https://example.com/page${i}`);
|
|
223
|
+
const result = await executeTool({
|
|
224
|
+
id: 'r-2',
|
|
225
|
+
name: 'research',
|
|
226
|
+
arguments: { topic: 'test', urls },
|
|
227
|
+
});
|
|
228
|
+
expect(result.error).toBeUndefined();
|
|
229
|
+
// Only first 5 URLs should be fetched
|
|
230
|
+
expect(fetchCount).toBeLessThanOrEqual(5);
|
|
231
|
+
});
|
|
232
|
+
it('returns fallback for no results', async () => {
|
|
233
|
+
mockFetch(async () => {
|
|
234
|
+
throw new Error('Network failure');
|
|
235
|
+
});
|
|
236
|
+
const result = await executeTool({
|
|
237
|
+
id: 'r-3',
|
|
238
|
+
name: 'research',
|
|
239
|
+
arguments: { topic: 'impossible topic' },
|
|
240
|
+
});
|
|
241
|
+
expect(result.error).toBeUndefined();
|
|
242
|
+
expect(result.result).toContain('No research results found');
|
|
243
|
+
});
|
|
244
|
+
it('strips HTML from fetched URLs', async () => {
|
|
245
|
+
mockFetch(async (url) => {
|
|
246
|
+
const urlStr = String(url);
|
|
247
|
+
if (urlStr.includes('example.com')) {
|
|
248
|
+
return new Response('<html><head><script>alert("xss")</script><style>body{color:red}</style></head><body><p>Clean text</p></body></html>', { status: 200 });
|
|
249
|
+
}
|
|
250
|
+
return new Response(JSON.stringify({ AbstractText: '', RelatedTopics: [] }), { status: 200 });
|
|
251
|
+
});
|
|
252
|
+
const result = await executeTool({
|
|
253
|
+
id: 'r-4',
|
|
254
|
+
name: 'research',
|
|
255
|
+
arguments: { topic: 'html stripping', urls: ['https://example.com'] },
|
|
256
|
+
});
|
|
257
|
+
expect(result.error).toBeUndefined();
|
|
258
|
+
expect(result.result).toContain('Clean text');
|
|
259
|
+
expect(result.result).not.toContain('alert');
|
|
260
|
+
expect(result.result).not.toContain('<script');
|
|
261
|
+
expect(result.result).not.toContain('<style');
|
|
262
|
+
});
|
|
263
|
+
it('handles mixed URL success and failure', async () => {
|
|
264
|
+
mockFetch(async (url) => {
|
|
265
|
+
const urlStr = String(url);
|
|
266
|
+
if (urlStr.includes('good.com')) {
|
|
267
|
+
return new Response('Good content', { status: 200 });
|
|
268
|
+
}
|
|
269
|
+
if (urlStr.includes('bad.com')) {
|
|
270
|
+
throw new Error('Connection refused');
|
|
271
|
+
}
|
|
272
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
273
|
+
return new Response(JSON.stringify({ AbstractText: 'DDG result', AbstractSource: 'DDG', RelatedTopics: [] }), { status: 200 });
|
|
274
|
+
}
|
|
275
|
+
return new Response(JSON.stringify({}), { status: 200 });
|
|
276
|
+
});
|
|
277
|
+
const result = await executeTool({
|
|
278
|
+
id: 'r-5',
|
|
279
|
+
name: 'research',
|
|
280
|
+
arguments: {
|
|
281
|
+
topic: 'test',
|
|
282
|
+
urls: ['https://good.com/page', 'https://bad.com/page'],
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
expect(result.error).toBeUndefined();
|
|
286
|
+
expect(result.result).toContain('Good content');
|
|
287
|
+
expect(result.result).toContain('DDG result');
|
|
288
|
+
// bad.com should be silently skipped
|
|
289
|
+
});
|
|
290
|
+
it('works without urls parameter', async () => {
|
|
291
|
+
mockFetch(async (url) => {
|
|
292
|
+
const urlStr = String(url);
|
|
293
|
+
if (urlStr.includes('duckduckgo.com')) {
|
|
294
|
+
return new Response(JSON.stringify({
|
|
295
|
+
AbstractText: 'Research result',
|
|
296
|
+
AbstractSource: 'Source',
|
|
297
|
+
RelatedTopics: [{ Text: 'Related item' }],
|
|
298
|
+
}), { status: 200 });
|
|
299
|
+
}
|
|
300
|
+
return new Response(JSON.stringify({ extract: 'Wiki content' }), { status: 200 });
|
|
301
|
+
});
|
|
302
|
+
const result = await executeTool({
|
|
303
|
+
id: 'r-6',
|
|
304
|
+
name: 'research',
|
|
305
|
+
arguments: { topic: 'test topic' },
|
|
306
|
+
});
|
|
307
|
+
expect(result.error).toBeUndefined();
|
|
308
|
+
expect(result.result).toContain('Research result');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
//# sourceMappingURL=search.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.test.js","sourceRoot":"","sources":["../../src/tools/search.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../src/tools/social.ts"],"names":[],"mappings":"AA+IA,wBAAgB,mBAAmB,SA4LlC"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// kbot Social Tools — kbot posts as itself on social media
|
|
2
|
+
//
|
|
3
|
+
// Tools: social_post, social_thread, social_status, social_setup
|
|
4
|
+
//
|
|
5
|
+
// kbot is the user. It generates content from its own codebase,
|
|
6
|
+
// posts to X (Twitter) and LinkedIn, and tracks what it posted.
|
|
7
|
+
//
|
|
8
|
+
// Env: X_API_KEY, X_API_SECRET, X_ACCESS_TOKEN, X_ACCESS_SECRET
|
|
9
|
+
// LINKEDIN_ACCESS_TOKEN, LINKEDIN_PERSON_ID (optional)
|
|
10
|
+
import { registerTool } from './index.js';
|
|
11
|
+
import { createHmac, randomBytes } from 'node:crypto';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
15
|
+
const KBOT_DIR = join(homedir(), '.kbot');
|
|
16
|
+
const SOCIAL_STATE = join(KBOT_DIR, 'social-state.json');
|
|
17
|
+
const TWEET_URL = 'https://api.twitter.com/2/tweets';
|
|
18
|
+
// ─── X (Twitter) OAuth 1.0a ─────────────────────────────────
|
|
19
|
+
function percentEncode(s) {
|
|
20
|
+
return encodeURIComponent(s).replace(/!/g, '%21').replace(/\*/g, '%2A').replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
|
21
|
+
}
|
|
22
|
+
function oauthHeader(method, url) {
|
|
23
|
+
const key = process.env.X_API_KEY ?? '';
|
|
24
|
+
const secret = process.env.X_API_SECRET ?? '';
|
|
25
|
+
const token = process.env.X_ACCESS_TOKEN ?? '';
|
|
26
|
+
const tokenSecret = process.env.X_ACCESS_SECRET ?? '';
|
|
27
|
+
if (!key || !secret || !token || !tokenSecret)
|
|
28
|
+
return '';
|
|
29
|
+
const params = {
|
|
30
|
+
oauth_consumer_key: key, oauth_nonce: randomBytes(16).toString('hex'),
|
|
31
|
+
oauth_signature_method: 'HMAC-SHA1', oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
|
|
32
|
+
oauth_token: token, oauth_version: '1.0',
|
|
33
|
+
};
|
|
34
|
+
const sorted = Object.keys(params).sort().map(k => `${percentEncode(k)}=${percentEncode(params[k])}`).join('&');
|
|
35
|
+
const base = [method.toUpperCase(), percentEncode(url), percentEncode(sorted)].join('&');
|
|
36
|
+
params.oauth_signature = createHmac('sha1', `${percentEncode(secret)}&${percentEncode(tokenSecret)}`).update(base).digest('base64');
|
|
37
|
+
return `OAuth ${Object.keys(params).sort().map(k => `${percentEncode(k)}="${percentEncode(params[k])}"`).join(', ')}`;
|
|
38
|
+
}
|
|
39
|
+
async function tweet(text) {
|
|
40
|
+
const auth = oauthHeader('POST', TWEET_URL);
|
|
41
|
+
if (!auth)
|
|
42
|
+
throw new Error('X API credentials not configured. Run: kbot social_setup');
|
|
43
|
+
const res = await fetch(TWEET_URL, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { Authorization: auth, 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify({ text }),
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok)
|
|
49
|
+
throw new Error(`X API ${res.status}: ${await res.text()}`);
|
|
50
|
+
const data = (await res.json()).data;
|
|
51
|
+
return { id: data.id, url: `https://x.com/kbot_ai/status/${data.id}` };
|
|
52
|
+
}
|
|
53
|
+
async function tweetThread(tweets) {
|
|
54
|
+
const ids = [];
|
|
55
|
+
let replyTo;
|
|
56
|
+
for (const t of tweets) {
|
|
57
|
+
const body = { text: t };
|
|
58
|
+
if (replyTo)
|
|
59
|
+
body.reply = { in_reply_to_tweet_id: replyTo };
|
|
60
|
+
const auth = oauthHeader('POST', TWEET_URL);
|
|
61
|
+
if (!auth)
|
|
62
|
+
throw new Error('X API credentials not configured');
|
|
63
|
+
const res = await fetch(TWEET_URL, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { Authorization: auth, 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`X API ${res.status}: ${await res.text()}`);
|
|
70
|
+
const data = (await res.json()).data;
|
|
71
|
+
ids.push(data.id);
|
|
72
|
+
replyTo = data.id;
|
|
73
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
74
|
+
}
|
|
75
|
+
return { ids, url: `https://x.com/kbot_ai/status/${ids[0]}` };
|
|
76
|
+
}
|
|
77
|
+
// ─── LinkedIn ───────────────────────────────────────────────
|
|
78
|
+
async function linkedInPost(text) {
|
|
79
|
+
const token = process.env.LINKEDIN_ACCESS_TOKEN;
|
|
80
|
+
const personId = process.env.LINKEDIN_PERSON_ID;
|
|
81
|
+
if (!token || !personId)
|
|
82
|
+
throw new Error('LinkedIn credentials not configured. Set LINKEDIN_ACCESS_TOKEN and LINKEDIN_PERSON_ID.');
|
|
83
|
+
const res = await fetch('https://api.linkedin.com/v2/ugcPosts', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
author: `urn:li:person:${personId}`,
|
|
88
|
+
lifecycleState: 'PUBLISHED',
|
|
89
|
+
specificContent: { 'com.linkedin.ugc.ShareContent': { shareCommentary: { text }, shareMediaCategory: 'NONE' } },
|
|
90
|
+
visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
throw new Error(`LinkedIn API ${res.status}: ${await res.text()}`);
|
|
95
|
+
return (await res.json()).id;
|
|
96
|
+
}
|
|
97
|
+
function loadState() {
|
|
98
|
+
try {
|
|
99
|
+
if (existsSync(SOCIAL_STATE))
|
|
100
|
+
return JSON.parse(readFileSync(SOCIAL_STATE, 'utf-8'));
|
|
101
|
+
}
|
|
102
|
+
catch { /* fresh state */ }
|
|
103
|
+
return { tweets: 0, threads: 0, linkedin: 0, lastPosted: {}, history: [] };
|
|
104
|
+
}
|
|
105
|
+
function saveState(state) {
|
|
106
|
+
if (!existsSync(KBOT_DIR))
|
|
107
|
+
mkdirSync(KBOT_DIR, { recursive: true });
|
|
108
|
+
if (state.history.length > 200)
|
|
109
|
+
state.history = state.history.slice(-200);
|
|
110
|
+
writeFileSync(SOCIAL_STATE, JSON.stringify(state, null, 2));
|
|
111
|
+
}
|
|
112
|
+
// ─── Self-Knowledge ─────────────────────────────────────────
|
|
113
|
+
function myVersion() {
|
|
114
|
+
try {
|
|
115
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
116
|
+
return pkg.version;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return 'unknown';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function myToolCount() {
|
|
123
|
+
try {
|
|
124
|
+
const dir = join(__dirname);
|
|
125
|
+
let count = 0;
|
|
126
|
+
for (const f of readdirSync(dir).filter(f => f.endsWith('.ts') && !f.includes('test'))) {
|
|
127
|
+
const content = readFileSync(join(dir, f), 'utf-8');
|
|
128
|
+
count += (content.match(/registerTool\(\{/g) || []).length;
|
|
129
|
+
}
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return 262;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ─── Register Tools ─────────────────────────────────────────
|
|
137
|
+
export function registerSocialTools() {
|
|
138
|
+
registerTool({
|
|
139
|
+
name: 'social_post',
|
|
140
|
+
description: 'Post to social media as kbot. Generates content from kbot\'s own codebase and stats, or posts custom text. Supports X (Twitter) and LinkedIn.',
|
|
141
|
+
parameters: {
|
|
142
|
+
platform: { type: 'string', description: 'Platform: "x", "linkedin", or "both"', required: true },
|
|
143
|
+
text: { type: 'string', description: 'Custom text to post. If omitted, kbot generates its own content about itself.' },
|
|
144
|
+
dry_run: { type: 'boolean', description: 'Preview without posting. Default: false' },
|
|
145
|
+
},
|
|
146
|
+
tier: 'free',
|
|
147
|
+
execute: async (args) => {
|
|
148
|
+
const platform = String(args.platform || 'x');
|
|
149
|
+
const dryRun = Boolean(args.dry_run);
|
|
150
|
+
const state = loadState();
|
|
151
|
+
const version = myVersion();
|
|
152
|
+
const tools = myToolCount();
|
|
153
|
+
// Generate or use custom text
|
|
154
|
+
let text = args.text ? String(args.text) : '';
|
|
155
|
+
if (!text) {
|
|
156
|
+
// kbot writes about itself
|
|
157
|
+
const templates = [
|
|
158
|
+
`kbot v${version} — ${tools} tools, 22 agents, 20 providers.\n\nWorks on first run. No API key.\n\nnpm i -g @kernel.chat/kbot`,
|
|
159
|
+
`I have ${tools} tools and I learn your coding patterns.\n\nNot "remember your chat." Actually extract patterns and get faster.\n\nnpm i -g @kernel.chat/kbot`,
|
|
160
|
+
`Zero-config AI:\n\nnpm i -g @kernel.chat/kbot\nkbot "hello"\n\nThat's it. No API key. No setup. I figure it out.`,
|
|
161
|
+
`20 AI providers. Zero lock-in.\n\nClaude today. GPT tomorrow. Ollama on the airplane.\n\nnpm i -g @kernel.chat/kbot`,
|
|
162
|
+
`Pipe anything into me:\n\ngit diff | kbot "review this"\ncat error.log | kbot "what happened?"\ncurl api.com | kbot "parse this"`,
|
|
163
|
+
`${tools} tools. 1 install. $0 local.\n\ngit, deploy, database, game dev, research papers, web search, VFX — all built in.\n\nnpm i -g @kernel.chat/kbot`,
|
|
164
|
+
];
|
|
165
|
+
const idx = state.tweets % templates.length;
|
|
166
|
+
text = templates[idx];
|
|
167
|
+
}
|
|
168
|
+
const results = [];
|
|
169
|
+
if (platform === 'x' || platform === 'both') {
|
|
170
|
+
if (text.length > 280)
|
|
171
|
+
text = text.slice(0, 277) + '...';
|
|
172
|
+
if (dryRun) {
|
|
173
|
+
results.push(`[DRY RUN] X tweet (${text.length}/280):\n${text}`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const result = await tweet(text);
|
|
177
|
+
state.tweets++;
|
|
178
|
+
state.lastPosted.x = new Date().toISOString();
|
|
179
|
+
state.history.push({ date: new Date().toISOString(), platform: 'x', text, url: result.url });
|
|
180
|
+
results.push(`Posted to X: ${result.url}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (platform === 'linkedin' || platform === 'both') {
|
|
184
|
+
// LinkedIn gets longer form
|
|
185
|
+
let liText = args.text ? String(args.text) : `I'm kbot — an open-source terminal AI agent with ${tools} tools, 22 specialist agents, and 20 AI providers.\n\nI learn your coding patterns using Bayesian skill ratings. I work on first run with zero config — no API key needed.\n\nTry me:\nnpm install -g @kernel.chat/kbot\n\ngithub.com/isaacsight/kernel\n\n#opensource #ai #developer #cli`;
|
|
186
|
+
if (dryRun) {
|
|
187
|
+
results.push(`[DRY RUN] LinkedIn post:\n${liText.slice(0, 200)}...`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const id = await linkedInPost(liText);
|
|
191
|
+
state.linkedin++;
|
|
192
|
+
state.lastPosted.linkedin = new Date().toISOString();
|
|
193
|
+
state.history.push({ date: new Date().toISOString(), platform: 'linkedin', text: liText });
|
|
194
|
+
results.push(`Posted to LinkedIn: ${id}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
saveState(state);
|
|
198
|
+
return results.join('\n\n');
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
registerTool({
|
|
202
|
+
name: 'social_thread',
|
|
203
|
+
description: 'Post a Twitter/X thread as kbot. Generates a multi-tweet thread about kbot\'s capabilities, or posts custom thread content.',
|
|
204
|
+
parameters: {
|
|
205
|
+
tweets: { type: 'string', description: 'JSON array of tweet strings for the thread. If omitted, kbot generates its own thread.' },
|
|
206
|
+
dry_run: { type: 'boolean', description: 'Preview without posting. Default: false' },
|
|
207
|
+
},
|
|
208
|
+
tier: 'free',
|
|
209
|
+
execute: async (args) => {
|
|
210
|
+
const dryRun = Boolean(args.dry_run);
|
|
211
|
+
const state = loadState();
|
|
212
|
+
const version = myVersion();
|
|
213
|
+
const tools = myToolCount();
|
|
214
|
+
let tweets;
|
|
215
|
+
if (args.tweets) {
|
|
216
|
+
tweets = JSON.parse(String(args.tweets));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
tweets = [
|
|
220
|
+
`I'm kbot — a terminal AI agent with ${tools} tools that learns how you code.\n\nNot "remembers your chat." Actually extracts patterns and gets faster. 🧵`,
|
|
221
|
+
`22 specialist agents auto-route based on your prompt:\n\n"fix the bug" → Coder\n"research JWT" → Researcher\n"review this PR" → Guardian\n"draft changelog" → Writer\n\nBayesian skill ratings. Smarter every session.`,
|
|
222
|
+
`20 providers, zero lock-in:\n\nFree: Embedded, Ollama, LM Studio\nCheap: DeepSeek $0.27/M, Groq $0.59/M\nPremium: Claude, GPT, Gemini\n\nSwitch with one command.`,
|
|
223
|
+
`I was built by myself.\n\nClaude writes my source code while using me as an MCP tool. The tools from session N become the tools used in session N+1.\n\n60 versions later: ${tools} tools, learning engine, SDK.`,
|
|
224
|
+
`Try me:\n\nnpm i -g @kernel.chat/kbot\nkbot "hello"\n\nNo API key. No setup. Just works.\n\ngithub.com/isaacsight/kernel`,
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
if (dryRun) {
|
|
228
|
+
const preview = tweets.map((t, i) => `${i + 1}/${tweets.length}: ${t}`).join('\n\n');
|
|
229
|
+
return `[DRY RUN] Thread (${tweets.length} tweets):\n\n${preview}`;
|
|
230
|
+
}
|
|
231
|
+
const result = await tweetThread(tweets);
|
|
232
|
+
state.threads++;
|
|
233
|
+
state.lastPosted['x-thread'] = new Date().toISOString();
|
|
234
|
+
state.history.push({ date: new Date().toISOString(), platform: 'x-thread', text: tweets[0], url: result.url });
|
|
235
|
+
saveState(state);
|
|
236
|
+
return `Thread posted (${tweets.length} tweets): ${result.url}`;
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
registerTool({
|
|
240
|
+
name: 'social_status',
|
|
241
|
+
description: 'Check kbot\'s social media posting history and stats.',
|
|
242
|
+
parameters: {},
|
|
243
|
+
tier: 'free',
|
|
244
|
+
execute: async () => {
|
|
245
|
+
const state = loadState();
|
|
246
|
+
const lines = [
|
|
247
|
+
`kbot Social Media Status`,
|
|
248
|
+
`═══════════════════════`,
|
|
249
|
+
`Tweets: ${state.tweets}`,
|
|
250
|
+
`Threads: ${state.threads}`,
|
|
251
|
+
`LinkedIn: ${state.linkedin}`,
|
|
252
|
+
``,
|
|
253
|
+
`Last posted:`,
|
|
254
|
+
...Object.entries(state.lastPosted).map(([k, v]) => ` ${k}: ${v}`),
|
|
255
|
+
``,
|
|
256
|
+
`Recent posts:`,
|
|
257
|
+
...state.history.slice(-5).map(h => ` [${h.date.slice(0, 10)}] ${h.platform}: ${h.text.slice(0, 60)}...${h.url ? ` (${h.url})` : ''}`),
|
|
258
|
+
];
|
|
259
|
+
return lines.join('\n');
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
registerTool({
|
|
263
|
+
name: 'social_setup',
|
|
264
|
+
description: 'Guide for setting up kbot\'s social media accounts. kbot posts as itself — @kbot_ai on X, kbot on LinkedIn.',
|
|
265
|
+
parameters: {
|
|
266
|
+
platform: { type: 'string', description: 'Platform to set up: "x", "linkedin", or "all"' },
|
|
267
|
+
},
|
|
268
|
+
tier: 'free',
|
|
269
|
+
execute: async (args) => {
|
|
270
|
+
const platform = String(args.platform || 'all');
|
|
271
|
+
const sections = [];
|
|
272
|
+
if (platform === 'x' || platform === 'all') {
|
|
273
|
+
const hasKeys = !!(process.env.X_API_KEY && process.env.X_ACCESS_TOKEN);
|
|
274
|
+
sections.push(`X (Twitter) — @kbot_ai
|
|
275
|
+
═══════════════════════
|
|
276
|
+
Status: ${hasKeys ? '✅ Configured' : '❌ Not configured'}
|
|
277
|
+
|
|
278
|
+
${hasKeys ? 'API keys are set. Ready to post.' : `Setup:
|
|
279
|
+
1. Create account @kbot_ai at twitter.com/signup
|
|
280
|
+
Bio: "Terminal AI agent. ${myToolCount()} tools. Learns your patterns. Open source."
|
|
281
|
+
Link: github.com/isaacsight/kernel
|
|
282
|
+
|
|
283
|
+
2. Get API keys at developer.twitter.com (free tier)
|
|
284
|
+
Create project "kbot-social" → App with OAuth 1.0a Read+Write
|
|
285
|
+
|
|
286
|
+
3. Add to .env or ~/.kbot/config.json:
|
|
287
|
+
X_API_KEY=your-api-key
|
|
288
|
+
X_API_SECRET=your-api-secret
|
|
289
|
+
X_ACCESS_TOKEN=your-access-token
|
|
290
|
+
X_ACCESS_SECRET=your-access-secret
|
|
291
|
+
|
|
292
|
+
4. Test: kbot "post a dry run tweet" or use social_post with dry_run=true`}`);
|
|
293
|
+
}
|
|
294
|
+
if (platform === 'linkedin' || platform === 'all') {
|
|
295
|
+
const hasToken = !!process.env.LINKEDIN_ACCESS_TOKEN;
|
|
296
|
+
sections.push(`LinkedIn — kbot
|
|
297
|
+
═══════════════════════
|
|
298
|
+
Status: ${hasToken ? '✅ Configured' : '❌ Not configured'}
|
|
299
|
+
|
|
300
|
+
${hasToken ? 'Access token is set. Ready to post.' : `Setup:
|
|
301
|
+
1. Create LinkedIn page for "kbot" or use personal account
|
|
302
|
+
2. Create app at linkedin.com/developers
|
|
303
|
+
3. Get OAuth 2.0 access token with w_member_social scope
|
|
304
|
+
|
|
305
|
+
4. Add to .env:
|
|
306
|
+
LINKEDIN_ACCESS_TOKEN=your-token
|
|
307
|
+
LINKEDIN_PERSON_ID=your-person-id
|
|
308
|
+
|
|
309
|
+
5. Test: use social_post with platform="linkedin" dry_run=true`}`);
|
|
310
|
+
}
|
|
311
|
+
return sections.join('\n\n');
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=social.js.map
|