walkietalkiebot 0.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/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +8 -0
- package/README.md +169 -0
- package/bin/wtb-server.js +336 -0
- package/bin/wtb.js +43 -0
- package/dist/Talkie_logo.png +0 -0
- package/dist/assets/index-UPnYoRh1.js +81 -0
- package/dist/assets/index-VbGv60d-.css +1 -0
- package/dist/index.html +14 -0
- package/mcp-server/dist/index.js +401 -0
- package/package.json +86 -0
- package/server/api.js +629 -0
- package/server/db/index.js +67 -0
- package/server/db/repositories/activities.js +85 -0
- package/server/db/repositories/activities.test.js +106 -0
- package/server/db/repositories/conversations.js +93 -0
- package/server/db/repositories/conversations.test.js +137 -0
- package/server/db/repositories/jobs.js +128 -0
- package/server/db/repositories/messages.js +98 -0
- package/server/db/repositories/messages.test.js +152 -0
- package/server/db/repositories/plans.js +57 -0
- package/server/db/repositories/plans.test.js +98 -0
- package/server/db/repositories/search.js +34 -0
- package/server/db/repositories/search.test.js +61 -0
- package/server/db/repositories/telegram.js +30 -0
- package/server/db/schema.js +165 -0
- package/server/index.js +137 -0
- package/server/jobs/api.js +108 -0
- package/server/jobs/manager.js +231 -0
- package/server/jobs/runner.js +246 -0
- package/server/notifications/dispatcher.js +40 -0
- package/server/notifications/macos.js +24 -0
- package/server/notifications/types.js +0 -0
- package/server/ssl.js +61 -0
- package/server/state.js +30 -0
- package/server/telegram/commands.js +160 -0
- package/server/telegram/handlers.js +299 -0
- package/server/telegram/index.js +46 -0
- package/server/test/helpers.js +14 -0
- package/skills/export-tape/SKILL.md +26 -0
- package/skills/launch-voice/SKILL.md +25 -0
- package/skills/manage-plans/SKILL.md +32 -0
- package/skills/save-conversation/SKILL.md +24 -0
- package/skills/search-tapes/SKILL.md +21 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import * as conversations from "../db/repositories/conversations.js";
|
|
3
|
+
import * as messages from "../db/repositories/messages.js";
|
|
4
|
+
import * as telegramState from "../db/repositories/telegram.js";
|
|
5
|
+
import * as activities from "../db/repositories/activities.js";
|
|
6
|
+
const WEB_UI_URL = process.env.WTB_URL || "https://localhost:5173";
|
|
7
|
+
function setupHandlers(bot) {
|
|
8
|
+
bot.on("message:text", async (ctx) => {
|
|
9
|
+
const userId = ctx.from?.id;
|
|
10
|
+
if (!userId) return;
|
|
11
|
+
const text = ctx.message.text;
|
|
12
|
+
if (text.startsWith("/")) return;
|
|
13
|
+
let state = telegramState.getTelegramState(userId);
|
|
14
|
+
if (!state?.current_conversation_id) {
|
|
15
|
+
const id = crypto.randomUUID();
|
|
16
|
+
const title = text.length > 40 ? text.slice(0, 40) + "..." : text;
|
|
17
|
+
conversations.createConversation({ id, title });
|
|
18
|
+
telegramState.setTelegramConversation(userId, id);
|
|
19
|
+
state = { user_id: userId, current_conversation_id: id, updated_at: Date.now() };
|
|
20
|
+
}
|
|
21
|
+
const conversationId = state.current_conversation_id;
|
|
22
|
+
const conv = conversations.getConversation(conversationId);
|
|
23
|
+
if (!conv) {
|
|
24
|
+
telegramState.setTelegramConversation(userId, null);
|
|
25
|
+
await ctx.reply("Your conversation was deleted. Creating a new one...");
|
|
26
|
+
const id = crypto.randomUUID();
|
|
27
|
+
const title = text.length > 40 ? text.slice(0, 40) + "..." : text;
|
|
28
|
+
conversations.createConversation({ id, title });
|
|
29
|
+
telegramState.setTelegramConversation(userId, id);
|
|
30
|
+
}
|
|
31
|
+
const messageId = crypto.randomUUID();
|
|
32
|
+
messages.createMessage({
|
|
33
|
+
id: messageId,
|
|
34
|
+
conversationId,
|
|
35
|
+
role: "user",
|
|
36
|
+
content: text,
|
|
37
|
+
source: "telegram"
|
|
38
|
+
});
|
|
39
|
+
await ctx.replyWithChatAction("typing");
|
|
40
|
+
try {
|
|
41
|
+
const { Agent: UndiciAgent, fetch: undiciFetch } = await import("undici");
|
|
42
|
+
const response = await undiciFetch(`${WEB_UI_URL}/api/claude-code`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
message: text,
|
|
47
|
+
history: messages.getMessagesForConversation(conversationId).map((m) => ({
|
|
48
|
+
role: m.role,
|
|
49
|
+
content: m.content
|
|
50
|
+
}))
|
|
51
|
+
}),
|
|
52
|
+
dispatcher: new UndiciAgent({ connect: { rejectUnauthorized: false } })
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(`API error: ${response.status}`);
|
|
56
|
+
}
|
|
57
|
+
let fullResponse = "";
|
|
58
|
+
const toolCalls = [];
|
|
59
|
+
const reader = response.body?.getReader();
|
|
60
|
+
const decoder = new TextDecoder();
|
|
61
|
+
if (reader) {
|
|
62
|
+
let buffer = "";
|
|
63
|
+
while (true) {
|
|
64
|
+
const { done, value } = await reader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
buffer += decoder.decode(value, { stream: true });
|
|
67
|
+
const lines = buffer.split("\n");
|
|
68
|
+
buffer = lines.pop() || "";
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (!line.startsWith("data: ")) continue;
|
|
71
|
+
const data = line.slice(6);
|
|
72
|
+
try {
|
|
73
|
+
const event = JSON.parse(data);
|
|
74
|
+
if (event.text) {
|
|
75
|
+
fullResponse += event.text;
|
|
76
|
+
}
|
|
77
|
+
if (event.activity) {
|
|
78
|
+
if (event.activity.type === "tool_start") {
|
|
79
|
+
toolCalls.push({
|
|
80
|
+
id: event.activity.id || crypto.randomUUID(),
|
|
81
|
+
tool: event.activity.tool,
|
|
82
|
+
input: event.activity.input,
|
|
83
|
+
status: "complete"
|
|
84
|
+
});
|
|
85
|
+
} else if (event.activity.type === "tool_end") {
|
|
86
|
+
const tc = toolCalls.find((t) => t.id === event.activity.id);
|
|
87
|
+
if (tc) {
|
|
88
|
+
tc.status = event.activity.status || "complete";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (event.error) {
|
|
93
|
+
console.error("Claude Code error:", event.error);
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
await ctx.replyWithChatAction("typing");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (fullResponse.trim()) {
|
|
102
|
+
const assistantMessageId = crypto.randomUUID();
|
|
103
|
+
messages.createMessage({
|
|
104
|
+
id: assistantMessageId,
|
|
105
|
+
conversationId,
|
|
106
|
+
role: "assistant",
|
|
107
|
+
content: fullResponse,
|
|
108
|
+
source: "telegram"
|
|
109
|
+
});
|
|
110
|
+
if (toolCalls.length > 0) {
|
|
111
|
+
activities.createActivitiesBatch(
|
|
112
|
+
toolCalls.map((tc) => ({
|
|
113
|
+
id: tc.id,
|
|
114
|
+
conversationId,
|
|
115
|
+
messageId: assistantMessageId,
|
|
116
|
+
tool: tc.tool,
|
|
117
|
+
input: tc.input,
|
|
118
|
+
status: tc.status,
|
|
119
|
+
timestamp: Date.now()
|
|
120
|
+
}))
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const maxLength = 4096;
|
|
124
|
+
if (fullResponse.length > maxLength) {
|
|
125
|
+
for (let i = 0; i < fullResponse.length; i += maxLength) {
|
|
126
|
+
await ctx.reply(fullResponse.slice(i, i + maxLength));
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
await ctx.reply(fullResponse);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
await ctx.reply("I didn't get a response. Please try again.");
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error("Error processing Telegram message:", err);
|
|
136
|
+
const keyboard = new InlineKeyboard().text("Try again", "retry_message");
|
|
137
|
+
await ctx.reply(
|
|
138
|
+
"Sorry, I encountered an error processing your message. Make sure the Walkie Talkie Bot server is running and Claude Code is available.",
|
|
139
|
+
{ reply_markup: keyboard }
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
bot.callbackQuery("retry_message", async (ctx) => {
|
|
144
|
+
await ctx.answerCallbackQuery({ text: "Please send your message again" });
|
|
145
|
+
await ctx.deleteMessage();
|
|
146
|
+
});
|
|
147
|
+
bot.on("message:voice", async (ctx) => {
|
|
148
|
+
await ctx.reply(
|
|
149
|
+
"Voice messages aren't supported yet. Please type your message instead, or use the web UI for voice input."
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
bot.on("message:video", async (ctx) => {
|
|
153
|
+
await ctx.reply(
|
|
154
|
+
"Videos aren't supported yet. Please describe what you'd like to share, or use the web UI to attach files."
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
bot.on("message:video_note", async (ctx) => {
|
|
158
|
+
await ctx.reply(
|
|
159
|
+
"Video messages aren't supported yet. Please type your message instead."
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
bot.on("message:photo", async (ctx) => {
|
|
163
|
+
const userId = ctx.from?.id;
|
|
164
|
+
if (!userId) return;
|
|
165
|
+
const caption = ctx.message.caption || "";
|
|
166
|
+
const photos = ctx.message.photo;
|
|
167
|
+
const photo = photos[photos.length - 1];
|
|
168
|
+
await ctx.replyWithChatAction("typing");
|
|
169
|
+
try {
|
|
170
|
+
const file = await ctx.api.getFile(photo.file_id);
|
|
171
|
+
const fileUrl = `https://api.telegram.org/file/bot${ctx.api.token}/${file.file_path}`;
|
|
172
|
+
const imageResponse = await fetch(fileUrl);
|
|
173
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
174
|
+
const base64 = Buffer.from(imageBuffer).toString("base64");
|
|
175
|
+
const mimeType = file.file_path?.endsWith(".png") ? "image/png" : "image/jpeg";
|
|
176
|
+
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
177
|
+
let state = telegramState.getTelegramState(userId);
|
|
178
|
+
if (!state?.current_conversation_id) {
|
|
179
|
+
const id = crypto.randomUUID();
|
|
180
|
+
const title = caption || "Image conversation";
|
|
181
|
+
conversations.createConversation({ id, title: title.slice(0, 40) });
|
|
182
|
+
telegramState.setTelegramConversation(userId, id);
|
|
183
|
+
state = { user_id: userId, current_conversation_id: id, updated_at: Date.now() };
|
|
184
|
+
}
|
|
185
|
+
const conversationId = state.current_conversation_id;
|
|
186
|
+
const { Agent, fetch: undiciFetch } = await import("undici");
|
|
187
|
+
const analyzeResponse = await undiciFetch(`${WEB_UI_URL}/api/analyze-image`, {
|
|
188
|
+
method: "POST",
|
|
189
|
+
headers: { "Content-Type": "application/json" },
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
dataUrl,
|
|
192
|
+
fileName: file.file_path || "photo.jpg",
|
|
193
|
+
type: mimeType,
|
|
194
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
195
|
+
}),
|
|
196
|
+
dispatcher: new Agent({ connect: { rejectUnauthorized: false } })
|
|
197
|
+
});
|
|
198
|
+
if (!analyzeResponse.ok) {
|
|
199
|
+
const error = await analyzeResponse.text();
|
|
200
|
+
await ctx.reply(`Couldn't analyze image: ${error}
|
|
201
|
+
|
|
202
|
+
Make sure an API key is configured in Settings.`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const { description } = await analyzeResponse.json();
|
|
206
|
+
const messageText = caption ? `[Image attached: ${description}]
|
|
207
|
+
|
|
208
|
+
User says: ${caption}` : `[Image attached: ${description}]
|
|
209
|
+
|
|
210
|
+
Describe what you see and ask if I have any questions about it.`;
|
|
211
|
+
const messageId = crypto.randomUUID();
|
|
212
|
+
messages.createMessage({
|
|
213
|
+
id: messageId,
|
|
214
|
+
conversationId,
|
|
215
|
+
role: "user",
|
|
216
|
+
content: caption || "[Sent an image]",
|
|
217
|
+
source: "telegram",
|
|
218
|
+
images: [{
|
|
219
|
+
id: crypto.randomUUID(),
|
|
220
|
+
dataUrl,
|
|
221
|
+
fileName: file.file_path || "photo.jpg",
|
|
222
|
+
description
|
|
223
|
+
}]
|
|
224
|
+
});
|
|
225
|
+
const response = await undiciFetch(`${WEB_UI_URL}/api/claude-code`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: { "Content-Type": "application/json" },
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
message: messageText,
|
|
230
|
+
history: messages.getMessagesForConversation(conversationId).slice(0, -1).map((m) => ({
|
|
231
|
+
role: m.role,
|
|
232
|
+
content: m.content
|
|
233
|
+
}))
|
|
234
|
+
}),
|
|
235
|
+
dispatcher: new Agent({ connect: { rejectUnauthorized: false } })
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
throw new Error(`API error: ${response.status}`);
|
|
239
|
+
}
|
|
240
|
+
let fullResponse = "";
|
|
241
|
+
const reader = response.body?.getReader();
|
|
242
|
+
const decoder = new TextDecoder();
|
|
243
|
+
if (reader) {
|
|
244
|
+
let buffer = "";
|
|
245
|
+
while (true) {
|
|
246
|
+
const { done, value } = await reader.read();
|
|
247
|
+
if (done) break;
|
|
248
|
+
buffer += decoder.decode(value, { stream: true });
|
|
249
|
+
const lines = buffer.split("\n");
|
|
250
|
+
buffer = lines.pop() || "";
|
|
251
|
+
for (const line of lines) {
|
|
252
|
+
if (!line.startsWith("data: ")) continue;
|
|
253
|
+
try {
|
|
254
|
+
const event = JSON.parse(line.slice(6));
|
|
255
|
+
if (event.text) fullResponse += event.text;
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
await ctx.replyWithChatAction("typing");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (fullResponse.trim()) {
|
|
263
|
+
messages.createMessage({
|
|
264
|
+
id: crypto.randomUUID(),
|
|
265
|
+
conversationId,
|
|
266
|
+
role: "assistant",
|
|
267
|
+
content: fullResponse,
|
|
268
|
+
source: "telegram"
|
|
269
|
+
});
|
|
270
|
+
const maxLength = 4096;
|
|
271
|
+
if (fullResponse.length > maxLength) {
|
|
272
|
+
for (let i = 0; i < fullResponse.length; i += maxLength) {
|
|
273
|
+
await ctx.reply(fullResponse.slice(i, i + maxLength));
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
await ctx.reply(fullResponse);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
await ctx.reply("I received the image but didn't get a response. Please try again.");
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
console.error("Error processing Telegram photo:", err);
|
|
283
|
+
await ctx.reply(
|
|
284
|
+
"Sorry, I had trouble processing that image. Make sure the Walkie Talkie Bot server is running and an API key is configured."
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
bot.on("message:document", async (ctx) => {
|
|
289
|
+
await ctx.reply(
|
|
290
|
+
"File attachments aren't supported via Telegram yet. Please use the web UI to attach files."
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
bot.on("message:sticker", async (ctx) => {
|
|
294
|
+
await ctx.reply("\u{1F60A}");
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
setupHandlers
|
|
299
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Bot } from "grammy";
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { setupCommands } from "./commands.js";
|
|
6
|
+
import { setupHandlers } from "./handlers.js";
|
|
7
|
+
let bot = null;
|
|
8
|
+
function getToken() {
|
|
9
|
+
if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
10
|
+
return process.env.TELEGRAM_BOT_TOKEN;
|
|
11
|
+
}
|
|
12
|
+
const tokenPath = join(homedir(), ".wtb", "telegram.token");
|
|
13
|
+
if (existsSync(tokenPath)) {
|
|
14
|
+
return readFileSync(tokenPath, "utf-8").trim();
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
async function startTelegramBot() {
|
|
19
|
+
const token = getToken();
|
|
20
|
+
if (!token) {
|
|
21
|
+
throw new Error("Telegram bot token not found. Set TELEGRAM_BOT_TOKEN env or create ~/.wtb/telegram.token");
|
|
22
|
+
}
|
|
23
|
+
bot = new Bot(token);
|
|
24
|
+
setupCommands(bot);
|
|
25
|
+
setupHandlers(bot);
|
|
26
|
+
await bot.start({
|
|
27
|
+
onStart: () => {
|
|
28
|
+
console.log("Telegram bot started");
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function stopTelegramBot() {
|
|
33
|
+
if (bot) {
|
|
34
|
+
bot.stop();
|
|
35
|
+
bot = null;
|
|
36
|
+
console.log("Telegram bot stopped");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function getBot() {
|
|
40
|
+
return bot;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
getBot,
|
|
44
|
+
startTelegramBot,
|
|
45
|
+
stopTelegramBot
|
|
46
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { initDbForTesting } from "../db/index.js";
|
|
3
|
+
function createTestDb() {
|
|
4
|
+
const db = new Database(":memory:");
|
|
5
|
+
initDbForTesting(db);
|
|
6
|
+
return db;
|
|
7
|
+
}
|
|
8
|
+
function resetTestDb() {
|
|
9
|
+
return createTestDb();
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
createTestDb,
|
|
13
|
+
resetTestDb
|
|
14
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: export-tape
|
|
3
|
+
description: Export a Walkie Talkie Bot conversation as markdown or JSON. Use when the user wants to export or share a conversation.
|
|
4
|
+
allowed-tools: list_conversations, export_conversation
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Export Conversation
|
|
8
|
+
|
|
9
|
+
Export a saved conversation as formatted markdown or structured JSON.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. If the user doesn't specify a conversation, use `list_conversations` to show recent tapes
|
|
14
|
+
2. Use `export_conversation` with the chosen conversation ID
|
|
15
|
+
3. Default format is "markdown". Use "json" if the user wants structured data.
|
|
16
|
+
4. Present the exported content to the user
|
|
17
|
+
|
|
18
|
+
## Formats
|
|
19
|
+
|
|
20
|
+
- **markdown**: Human-readable with headers, role labels, and tool activity summary
|
|
21
|
+
- **json**: Full structured data including messages, images, and activities
|
|
22
|
+
|
|
23
|
+
## Notes
|
|
24
|
+
|
|
25
|
+
- Works offline (no WTB server needed)
|
|
26
|
+
- Exported markdown includes tool activity history at the end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: launch-voice
|
|
3
|
+
description: Launch Walkie Talkie Bot's voice interface in the browser. Use when the user wants to switch to voice interaction.
|
|
4
|
+
allowed-tools: launch_wtb, get_wtb_status
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Launch Voice UI
|
|
8
|
+
|
|
9
|
+
Start the Walkie Talkie Bot web server and open the voice interface in the browser.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Check if WTB is already running with `get_wtb_status`
|
|
14
|
+
2. If not running, use `launch_wtb` to start the server and open the browser
|
|
15
|
+
3. Tell the user the URL (typically https://localhost:5173)
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- The `walkietalkiebot` npm package must be installed globally or available via npx
|
|
20
|
+
- Chrome or Edge is recommended (Web Speech API support)
|
|
21
|
+
- The browser will show a certificate warning on first visit (self-signed cert)
|
|
22
|
+
|
|
23
|
+
## If Not Installed
|
|
24
|
+
|
|
25
|
+
Tell the user to install with: `npm install -g walkietalkiebot`
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: manage-plans
|
|
3
|
+
description: Create, update, and track implementation plans in Walkie Talkie Bot. Use when the user wants to manage structured plans.
|
|
4
|
+
allowed-tools: list_plans, get_plan, create_plan, update_plan, delete_plan
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Manage Plans
|
|
8
|
+
|
|
9
|
+
Create and manage implementation plans in Walkie Talkie Bot's database.
|
|
10
|
+
|
|
11
|
+
## Status Workflow
|
|
12
|
+
|
|
13
|
+
Plans follow this lifecycle:
|
|
14
|
+
1. **draft** — newly created, not yet reviewed
|
|
15
|
+
2. **approved** — reviewed and accepted
|
|
16
|
+
3. **in_progress** — actively being worked on
|
|
17
|
+
4. **completed** — all steps finished
|
|
18
|
+
5. **archived** — stored for reference
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
- **Create**: Use `create_plan` with a title and markdown content. Default status is "draft".
|
|
23
|
+
- **List**: Use `list_plans` to see all plans with their current status.
|
|
24
|
+
- **View**: Use `get_plan` to read the full content of a specific plan.
|
|
25
|
+
- **Update**: Use `update_plan` to change title, content, or advance the status.
|
|
26
|
+
- **Delete**: Use `delete_plan` to permanently remove a plan.
|
|
27
|
+
|
|
28
|
+
## Notes
|
|
29
|
+
|
|
30
|
+
- Plans can be linked to conversations via `conversationId`
|
|
31
|
+
- Use markdown formatting in plan content
|
|
32
|
+
- Works offline (no WTB server needed)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: save-conversation
|
|
3
|
+
description: Save the current conversation to Walkie Talkie Bot as a cassette tape. Use when the user wants to archive or save a conversation.
|
|
4
|
+
allowed-tools: create_conversation, add_message, list_conversations
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Save Conversation
|
|
8
|
+
|
|
9
|
+
Save the current Claude Code conversation as a cassette tape in Walkie Talkie Bot's database.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Ask the user for a title, or generate one from the conversation topic
|
|
14
|
+
2. Use `create_conversation` to create a new tape with that title
|
|
15
|
+
3. For each meaningful exchange in the conversation, use `add_message` to add it:
|
|
16
|
+
- Set `role` to "user" or "assistant" as appropriate
|
|
17
|
+
- Set `source` to "claude-code"
|
|
18
|
+
4. Report the conversation ID and title back to the user
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
|
|
22
|
+
- This tool works offline (no WTB server needed)
|
|
23
|
+
- Messages are stored in `~/.wtb/wtb.db`
|
|
24
|
+
- The saved tape will appear in the tape collection when the web UI is running
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: search-tapes
|
|
3
|
+
description: Search across all Walkie Talkie Bot conversations for specific content. Use when the user wants to find something they discussed before.
|
|
4
|
+
allowed-tools: search_conversations, get_conversation
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Search Tapes
|
|
8
|
+
|
|
9
|
+
Full-text search across all saved conversations in Walkie Talkie Bot.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Use `search_conversations` with the user's query
|
|
14
|
+
2. Present results with conversation titles, matching snippets, and timestamps
|
|
15
|
+
3. If the user wants to see a full conversation, use `get_conversation` with its ID
|
|
16
|
+
|
|
17
|
+
## Notes
|
|
18
|
+
|
|
19
|
+
- Uses FTS5 for fast, ranked full-text search
|
|
20
|
+
- Works offline (no WTB server needed)
|
|
21
|
+
- Returns highlighted snippets showing where the match was found
|