@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.
@@ -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,2 @@
1
+ export declare function registerSocialTools(): void;
2
+ //# sourceMappingURL=social.d.ts.map
@@ -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