@exreve/exk 1.0.62 → 1.0.64
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/agentSession.js +8 -7
- package/dist/cli/moduleMcpServer.js +10 -1
- package/dist/cli/piBackend.js +11 -18
- package/dist/cli/sharedTools.js +69 -0
- package/dist/ttc-cli.tar.gz +0 -0
- package/package.json +2 -1
package/dist/cli/agentSession.js
CHANGED
|
@@ -273,16 +273,17 @@ export class AgentSessionManager {
|
|
|
273
273
|
// Priority: explicit parameter > env var
|
|
274
274
|
const requested = agentBackend || process.env.TTC_AGENT_BACKEND;
|
|
275
275
|
if (requested === 'pi') {
|
|
276
|
+
let piBackend;
|
|
276
277
|
try {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
console.warn('[AgentSessionManager] Pi backend requested but SDK not installed, falling back to Claude');
|
|
278
|
+
piBackend = require('./piBackend.js').piBackend;
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
throw new Error(`Pi backend requested but module failed to load: ${err.message}. Install with: cd $(npm root -g)/@exreve/exk && npm install @mariozechner/pi-coding-agent`);
|
|
282
282
|
}
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
if (!piBackend.isAvailable()) {
|
|
284
|
+
throw new Error('Pi backend requested but SDK (@mariozechner/pi-coding-agent) is not installed. Install with: cd $(npm root -g)/@exreve/exk && npm install @mariozechner/pi-coding-agent');
|
|
285
285
|
}
|
|
286
|
+
return piBackend;
|
|
286
287
|
}
|
|
287
288
|
return claudeBackend;
|
|
288
289
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
|
9
9
|
import { z } from 'zod';
|
|
10
|
-
import { executeAnalyzeImage, executeSendFile, executeBrowserQuery } from './sharedTools.js';
|
|
10
|
+
import { executeAnalyzeImage, executeSendFile, executeBrowserQuery, executeGenImage } from './sharedTools.js';
|
|
11
11
|
/**
|
|
12
12
|
* Build the shared config from MCP server config.
|
|
13
13
|
*/
|
|
@@ -56,6 +56,15 @@ export function createModuleMcpServer(config) {
|
|
|
56
56
|
country: args.country,
|
|
57
57
|
mobile: args.mobile,
|
|
58
58
|
}, sharedConfig)),
|
|
59
|
+
tool('gen_image', 'Generate an image from a text prompt using Flux.2-flex. The generated PNG is saved to a temp directory and the file path is returned. ' +
|
|
60
|
+
'Use this to create illustrations, icons, diagrams, or any visual content for the user. ' +
|
|
61
|
+
'The image is saved locally — use send_file to display it in chat after generation.', {
|
|
62
|
+
prompt: z.string().describe('Detailed text description of the image to generate. Be specific about style, colors, composition, etc.'),
|
|
63
|
+
filename: z.string().optional().describe('Output filename (without extension, or with .png). Defaults to gen_<timestamp>.png'),
|
|
64
|
+
}, async (args) => executeGenImage({
|
|
65
|
+
prompt: args.prompt,
|
|
66
|
+
filename: args.filename,
|
|
67
|
+
}, sharedConfig)),
|
|
59
68
|
];
|
|
60
69
|
return createSdkMcpServer({
|
|
61
70
|
name: 'claude-voice-modules',
|
package/dist/cli/piBackend.js
CHANGED
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { executeAnalyzeImage, executeSendFile, executeBrowserQuery } from './sharedTools.js';
|
|
17
17
|
// Conditional import — will be undefined if package not installed
|
|
18
|
-
// @ts-expect-error — Pi SDK is an optional dependency, may not be installed
|
|
19
18
|
let piSdk;
|
|
20
19
|
try {
|
|
21
|
-
// @ts-expect-error — optional dependency
|
|
22
20
|
piSdk = await import('@mariozechner/pi-coding-agent');
|
|
23
21
|
}
|
|
24
22
|
catch {
|
|
@@ -62,20 +60,12 @@ export class PiBackend {
|
|
|
62
60
|
// Set model if specified
|
|
63
61
|
if (model) {
|
|
64
62
|
try {
|
|
65
|
-
// Try
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
sessionOpts.model =
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
// Try as a custom model — Pi supports custom providers via models.json
|
|
72
|
-
// For now, we try the model string directly
|
|
73
|
-
const found = modelRegistry.find(provider || 'anthropic', model);
|
|
74
|
-
if (found) {
|
|
75
|
-
sessionOpts.model = found;
|
|
76
|
-
}
|
|
77
|
-
// If not found, Pi will use its default model
|
|
63
|
+
// Try as a custom model via ModelRegistry
|
|
64
|
+
const found = modelRegistry.find(provider || 'anthropic', model);
|
|
65
|
+
if (found) {
|
|
66
|
+
sessionOpts.model = found;
|
|
78
67
|
}
|
|
68
|
+
// If not found, Pi will use its default model
|
|
79
69
|
}
|
|
80
70
|
catch {
|
|
81
71
|
// Model not found — Pi will use default
|
|
@@ -330,7 +320,8 @@ export class PiBackend {
|
|
|
330
320
|
required: ['image_path', 'question'],
|
|
331
321
|
},
|
|
332
322
|
execute: async (_toolCallId, params) => {
|
|
333
|
-
|
|
323
|
+
const result = await executeAnalyzeImage({ image_path: params.image_path, question: params.question }, sharedConfig);
|
|
324
|
+
return { ...result, details: {} };
|
|
334
325
|
},
|
|
335
326
|
});
|
|
336
327
|
}
|
|
@@ -352,7 +343,8 @@ export class PiBackend {
|
|
|
352
343
|
},
|
|
353
344
|
},
|
|
354
345
|
execute: async (_toolCallId, params) => {
|
|
355
|
-
|
|
346
|
+
const result = await executeSendFile(params, sharedConfig);
|
|
347
|
+
return { ...result, details: {} };
|
|
356
348
|
},
|
|
357
349
|
});
|
|
358
350
|
}
|
|
@@ -373,7 +365,8 @@ export class PiBackend {
|
|
|
373
365
|
required: ['query'],
|
|
374
366
|
},
|
|
375
367
|
execute: async (_toolCallId, params) => {
|
|
376
|
-
|
|
368
|
+
const result = await executeBrowserQuery({ query: params.query, maxSteps: params.maxSteps }, sharedConfig);
|
|
369
|
+
return { ...result, details: {} };
|
|
377
370
|
},
|
|
378
371
|
});
|
|
379
372
|
}
|
package/dist/cli/sharedTools.js
CHANGED
|
@@ -257,3 +257,72 @@ export async function executeBrowserQuery(args, config) {
|
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
// ============ Image Generation ============
|
|
261
|
+
const FLUX_MODEL = 'black-forest-labs/flux.2-flex';
|
|
262
|
+
/**
|
|
263
|
+
* gen_image — generate an image using Flux.2-flex via OpenRouter.
|
|
264
|
+
* Saves the result as a PNG file in a temp directory and returns the file path.
|
|
265
|
+
*/
|
|
266
|
+
export async function executeGenImage(args, _config) {
|
|
267
|
+
const apiKey = getOpenrouterApiKey();
|
|
268
|
+
if (!apiKey) {
|
|
269
|
+
return { content: [{ type: 'text', text: 'Error: OPENROUTER_API_KEY not configured. Set it in ai-config.json or OPENROUTER_API_KEY env var.' }], isError: true };
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
276
|
+
'Content-Type': 'application/json',
|
|
277
|
+
'HTTP-Referer': 'https://talk-to-code.com',
|
|
278
|
+
'X-Title': 'TalkToCode',
|
|
279
|
+
},
|
|
280
|
+
body: JSON.stringify({
|
|
281
|
+
model: FLUX_MODEL,
|
|
282
|
+
messages: [{ role: 'user', content: args.prompt }],
|
|
283
|
+
modalities: ['image'],
|
|
284
|
+
}),
|
|
285
|
+
signal: AbortSignal.timeout(120_000), // 2 min timeout for image gen
|
|
286
|
+
});
|
|
287
|
+
if (!res.ok) {
|
|
288
|
+
const errText = await res.text();
|
|
289
|
+
return { content: [{ type: 'text', text: `Error from Flux API (${res.status}): ${errText.slice(0, 500)}` }], isError: true };
|
|
290
|
+
}
|
|
291
|
+
const data = await res.json();
|
|
292
|
+
// Extract image from response
|
|
293
|
+
const images = data.choices?.[0]?.message?.images;
|
|
294
|
+
if (!images || images.length === 0) {
|
|
295
|
+
return { content: [{ type: 'text', text: 'Error: No image in response from Flux API.' }], isError: true };
|
|
296
|
+
}
|
|
297
|
+
const dataUrl = images[0]?.image_url?.url || images[0];
|
|
298
|
+
if (!dataUrl || !dataUrl.startsWith('data:')) {
|
|
299
|
+
return { content: [{ type: 'text', text: `Error: Unexpected image format from Flux API.` }], isError: true };
|
|
300
|
+
}
|
|
301
|
+
// Decode base64 and save to temp dir
|
|
302
|
+
const base64 = dataUrl.split(',')[1];
|
|
303
|
+
if (!base64) {
|
|
304
|
+
return { content: [{ type: 'text', text: 'Error: Empty image data from Flux API.' }], isError: true };
|
|
305
|
+
}
|
|
306
|
+
const imgBuf = Buffer.from(base64, 'base64');
|
|
307
|
+
const tmpDir = path.join(os.tmpdir(), 'talk-to-code', 'gen-images');
|
|
308
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
309
|
+
const safeName = (args.filename || `gen_${Date.now()}`).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
310
|
+
const fileName = safeName.endsWith('.png') ? safeName : `${safeName}.png`;
|
|
311
|
+
const filePath = path.join(tmpDir, fileName);
|
|
312
|
+
fs.writeFileSync(filePath, imgBuf);
|
|
313
|
+
const sizeKb = (imgBuf.length / 1024).toFixed(0);
|
|
314
|
+
return {
|
|
315
|
+
content: [{
|
|
316
|
+
type: 'text',
|
|
317
|
+
text: `Image generated successfully.\n\nFile: ${filePath}\nSize: ${sizeKb} KB\nPrompt: ${args.prompt}`,
|
|
318
|
+
}],
|
|
319
|
+
details: { filePath, sizeKb, prompt: args.prompt },
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (error.name === 'TimeoutError') {
|
|
324
|
+
return { content: [{ type: 'text', text: 'Image generation timed out after 2 minutes. Try a simpler prompt.' }], isError: true };
|
|
325
|
+
}
|
|
326
|
+
return { content: [{ type: 'text', text: `Error generating image: ${error.message}` }], isError: true };
|
|
327
|
+
}
|
|
328
|
+
}
|
package/dist/ttc-cli.tar.gz
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exreve/exk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.64",
|
|
4
4
|
"description": "exk - Control Claude CLI with voice and programmable interfaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@anthropic-ai/claude-agent-sdk": "^0.2.126",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.92.0",
|
|
38
38
|
"@fastify/static": "^9.0.0",
|
|
39
|
+
"@mariozechner/pi-coding-agent": "^0.73.1",
|
|
39
40
|
"@xenova/transformers": "^2.17.2",
|
|
40
41
|
"anthropic-proxy": "^1.3.0",
|
|
41
42
|
"chokidar": "^3.6.0",
|