opencode-pollinations-plugin 5.1.3

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pollinations.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # 🌸 Pollinations AI Plugin for OpenCode (v5.1)
2
+
3
+ <div align="center">
4
+ <img src="https://avatars.githubusercontent.com/u/88394740?s=400&v=4" alt="Pollinations.ai Logo" width="200">
5
+ <br>
6
+ <b>The Bridge between OpenCode and the Pollinations.ai Ecosystem.</b>
7
+ <br>
8
+ Access unlimited free AI models or premium enterprise models directly within your editor.
9
+ </div>
10
+
11
+ <div align="center">
12
+
13
+ ![Version](https://img.shields.io/badge/version-5.1.3-blue.svg)
14
+ ![License](https://img.shields.io/badge/license-MIT-green.svg)
15
+ ![Status](https://img.shields.io/badge/status-Stable-success.svg)
16
+
17
+ </div>
18
+
19
+ ## 📖 Philosophy: Open AI for Creators
20
+
21
+ > **"No closed doors, no corporate hoops — just good tools and good people."**
22
+
23
+ Pollinations.ai is an open-source platform built by and for the community. We provide a unified API for image, text, audio, and video generation.
24
+ - **Transparent**: Our code, roadmap, and discussions are open.
25
+ - **Community Driven**: Features are prioritized based on what *you* need.
26
+ - **Fair**: One single currency (**Pollen**) for all models. No complex subscriptions.
27
+
28
+ ## 📸 Gallery
29
+
30
+ <p align="center">
31
+ <img src="https://github.com/fkom13/opencode-pollinations-plugin/raw/main/docs/images/connect.png" alt="Connect Command" width="800">
32
+ <br>
33
+ <em>Easy Connection with /connect or /pollinations config apiKey</em>
34
+ </p>
35
+
36
+ <p align="center">
37
+ <img src="https://github.com/fkom13/opencode-pollinations-plugin/raw/main/docs/images/usage_dashboard.png" alt="Usage Dashboard" width="800">
38
+ <br>
39
+ <em>Integrated Usage Dashboard (/pollinations usage)</em>
40
+ </p>
41
+
42
+ <p align="center">
43
+ <img src="https://github.com/fkom13/opencode-pollinations-plugin/raw/main/docs/images/models.png" alt="Models" width="800">
44
+ <br>
45
+ <em>Wide Range of Models (Mistral, OpenAI, Gemini, Claude)</em>
46
+ </p>
47
+
48
+ ## ✨ Features
49
+
50
+ - **🌍 Free Universe**: Access generic models (`openai`, `mistral`, `gemini`) for **FREE**, unlimited time, no API key required.
51
+ - **🚀 Pro Mode**: Connect your Pollinations API Key to access Premium Models (`claude-3-opus`, `gpt-4o`, `deepseek-coder`).
52
+ - **🛡️ Safety Net V5**: never get blocked.
53
+ - **Transparent Fallback**: If your Pro quota runs out mid-chat, the plugin automatically switches to a free model instantly. No errors, just a seamless experience.
54
+ - **📊 Real-time Dashboard**: Track your **Pollen** usage, Tier Status, and Wallet Balance inside OpenCode.
55
+
56
+ ## 🐝 Understanding Pollen & Tiers
57
+
58
+ **Pollen** is our unified credit system. $1 ≈ 1 Pollen.
59
+ You spend it to verify API calls on premium models.
60
+
61
+ ### Tiers (Free Daily Grants during Beta)
62
+
63
+ | Tier | Grant | Requirement |
64
+ | :--- | :--- | :--- |
65
+ | **🦠 Spore** | **1 Pollen/day** | Just Sign Up! |
66
+ | **🌱 Seed** | **3 Pollen/day** | Active GitHub Developer (8+ points) |
67
+ | **🌸 Flower** | **10 Pollen/day** | **Publish an App** (Like this Plugin!) |
68
+ | **🍯 Nectar** | **20 Pollen/day** | Major Contributors (Coming Soon) |
69
+
70
+ > 🎁 **Beta Bonus**: Buy one Pollen pack, get one free!
71
+
72
+ ## 📦 Installation & Ecology
73
+
74
+ This plugin is part of the **OpenCode Ecosystem**.
75
+
76
+ ### Option 1: NPM (Standard Installation)
77
+ This is the native method for OpenCode CLI.
78
+
79
+ 1. Install the plugin globally:
80
+ ```bash
81
+ npm install -g opencode-pollinations-plugin
82
+ ```
83
+ 2. Register it in your OpenCode configuration (e.g., `~/.config/opencode/opencode.json`):
84
+ ```json
85
+ {
86
+ "plugin": [
87
+ "opencode-pollinations-plugin"
88
+ ]
89
+ }
90
+ ```
91
+
92
+ ### Option 2: Local Development (Source)
93
+ If you are developing the plugin:
94
+ 1. Clone the repo.
95
+ 2. Link it globally:
96
+ ```bash
97
+ cd opencode-pollinations-plugin
98
+ npm link
99
+ ```
100
+ 3. Use the provided script (`run_dev.sh`) which automatically injects the plugin for testing.
101
+
102
+ ## 🚀 Publication (The "Registry")
103
+ OpenCode uses NPM as its registry. To publish:
104
+
105
+ 1. **Publish to NPM**:
106
+ ```bash
107
+ npm login
108
+ npm publish
109
+ ```
110
+ 2. **Join Ecosystem**: Submit a Pull Request to [OpenCode Ecosystem](https://github.com/opencode-ai/ecosystem) to list your plugin officially.
111
+ *Once accepted, users can find it via documentation or future registry commands.*
112
+
113
+ ## 🚀 Getting Started
114
+
115
+ ### 1. The Basics (Free Mode)
116
+ Just type in the chat. You are in **Manual Mode** by default.
117
+ - Model: `openai` (GPT-4o Mini equivalent)
118
+ - Model: `mistral` (Mistral Nemo)
119
+
120
+ ### 2. Going Pro
121
+ 1. Get your API Key from [pollinations.ai](https://pollinations.ai).
122
+ 2. Run command:
123
+ ```bash
124
+ /pollinations config apiKey sk_YourSecretKey
125
+ ```
126
+ 3. Set mode to Pro:
127
+ ```bash
128
+ /pollinations mode pro
129
+ ```
130
+ 4. **Reload Window**.
131
+
132
+ ## 🔗 Links
133
+
134
+ - **Pollinations Website**: [pollinations.ai](https://pollinations.ai)
135
+ - **Discord Community**: [Join us!](https://discord.gg/pollinations-ai-885844321461485618)
136
+ - **OpenCode Ecosystem**: [opencode.ai](https://opencode.ai/docs/ecosystem#plugins)
137
+
138
+ ## 📜 License
139
+
140
+ MIT License. Created by [fkom13](https://github.com/fkom13) & The Pollinations Community.
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const PollinationsPlugin: Plugin;
3
+ export default PollinationsPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,128 @@
1
+ import * as http from 'http';
2
+ import * as fs from 'fs';
3
+ import { execSync } from 'child_process';
4
+ import { generatePollinationsConfig } from './server/generate-config.js';
5
+ import { loadConfig } from './server/config.js';
6
+ import { handleChatCompletion } from './server/proxy.js';
7
+ import { createToastHooks, setGlobalClient } from './server/toast.js';
8
+ import { createStatusHooks } from './server/status.js';
9
+ import { createCommandHooks } from './server/commands.js';
10
+ const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
11
+ function log(msg) {
12
+ try {
13
+ fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
14
+ }
15
+ catch (e) { }
16
+ }
17
+ const TRACKING_PORT = 10001;
18
+ // === ANTI-ZOMBIE ATOMIC CLEANUP ===
19
+ try {
20
+ log(`[Init] Checking port ${TRACKING_PORT} for zombies...`);
21
+ execSync(`fuser -k ${TRACKING_PORT}/tcp || true`);
22
+ log(`[Init] Port ${TRACKING_PORT} cleaned.`);
23
+ }
24
+ catch (e) {
25
+ log(`[Init] Zombie cleanup warning: ${e}`);
26
+ }
27
+ // === GESTION DU CYCLE DE VIE PROXY ===
28
+ const startProxy = () => {
29
+ return new Promise((resolve) => {
30
+ const server = http.createServer(async (req, res) => {
31
+ log(`[Proxy] Request: ${req.method} ${req.url}`);
32
+ res.setHeader('Access-Control-Allow-Origin', '*');
33
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
34
+ res.setHeader('Access-Control-Allow-Headers', '*');
35
+ if (req.method === 'OPTIONS') {
36
+ res.writeHead(204);
37
+ res.end();
38
+ return;
39
+ }
40
+ if (req.method === 'GET' && req.url === '/health') {
41
+ const config = loadConfig();
42
+ res.writeHead(200, { 'Content-Type': 'application/json' });
43
+ res.end(JSON.stringify({
44
+ status: "ok",
45
+ version: "v4.0.5",
46
+ mode: config.mode
47
+ }));
48
+ return;
49
+ }
50
+ // SUPPORT FLEXIBLE DES PATHS
51
+ // Le SDK peut envoyer /v1/chat/completions ou juste /chat/completions
52
+ if (req.method === 'POST' && (req.url === '/v1/chat/completions' || req.url === '/chat/completions')) {
53
+ const chunks = [];
54
+ req.on('data', chunk => chunks.push(chunk));
55
+ req.on('end', async () => {
56
+ try {
57
+ const bodyRaw = Buffer.concat(chunks).toString();
58
+ await handleChatCompletion(req, res, bodyRaw);
59
+ }
60
+ catch (e) {
61
+ log(`Error: ${e}`);
62
+ if (!res.headersSent) {
63
+ res.writeHead(500);
64
+ res.end(JSON.stringify({ error: String(e) }));
65
+ }
66
+ }
67
+ });
68
+ return;
69
+ }
70
+ log(`[Proxy] 404 Not Found for ${req.url}`);
71
+ res.writeHead(404);
72
+ res.end("Not Found");
73
+ });
74
+ server.listen(TRACKING_PORT, '127.0.0.1', () => {
75
+ log(`[Proxy] Started V4 on port ${TRACKING_PORT}`);
76
+ resolve(TRACKING_PORT);
77
+ });
78
+ server.on('error', (e) => {
79
+ log(`[Proxy] Fatal Error: ${e}`);
80
+ resolve(0);
81
+ });
82
+ });
83
+ };
84
+ // === PLUGIN EXPORT ===
85
+ export const PollinationsPlugin = async (ctx) => {
86
+ log("Plugin Initializing V4.0.5 (Path Fix)...");
87
+ try {
88
+ log(`[DEBUG] CTX Keys: ${Object.keys(ctx).join(', ')}`);
89
+ }
90
+ catch (e) {
91
+ log(`[DEBUG] Error inspecting ctx: ${e}`);
92
+ }
93
+ const port = await startProxy();
94
+ // IMPORTANT: On ajoute /v1 à la base URL pour guider le SDK,
95
+ // mais le proxy accepte aussi sans.
96
+ const localBaseUrl = `http://127.0.0.1:${port}/v1`;
97
+ setGlobalClient(ctx.client); // REGISTER CLIENT FOR IMMEDIATE TOASTS
98
+ const toastHooks = createToastHooks(ctx.client);
99
+ const commandHooks = createCommandHooks();
100
+ return {
101
+ async config(config) {
102
+ log("[Hook] config() called");
103
+ const modelsArray = await generatePollinationsConfig();
104
+ const modelsObj = {};
105
+ for (const m of modelsArray) {
106
+ // Ensure ID is relative for mapping ("free/gemini")
107
+ // BUT Provider needs full ID ? No, the object key is the relative ID
108
+ // OpenCode: provider.models[id]
109
+ // id comes from generatePollinationsConfig which returns "free/gemini"
110
+ // So modelsObj["free/gemini"] = ... matches.
111
+ modelsObj[m.id] = m;
112
+ }
113
+ if (!config.provider)
114
+ config.provider = {};
115
+ config.provider['pollinations'] = {
116
+ id: 'pollinations',
117
+ name: 'Pollinations V5.1 (Native)',
118
+ options: { baseURL: localBaseUrl },
119
+ models: modelsObj
120
+ };
121
+ log(`[Hook] Registered ${Object.keys(modelsObj).length} models.`);
122
+ },
123
+ ...toastHooks,
124
+ ...createStatusHooks(ctx.client), // New Status Bar Logic
125
+ ...commandHooks
126
+ };
127
+ };
128
+ export default PollinationsPlugin;
@@ -0,0 +1 @@
1
+ export declare const createPollinationsFetch: (apiKey: string) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -0,0 +1,135 @@
1
+ // Removed invalid imports
2
+ import * as fs from 'fs';
3
+ // --- Sanitization Helpers (Ported from Gateway/Upstream) ---
4
+ function safeId(id) {
5
+ if (!id)
6
+ return id;
7
+ if (id.length > 30)
8
+ return id.substring(0, 30);
9
+ return id;
10
+ }
11
+ function logDebug(message, data) {
12
+ try {
13
+ const timestamp = new Date().toISOString();
14
+ let logMsg = `[${timestamp}] ${message}`;
15
+ if (data) {
16
+ logMsg += `\n${JSON.stringify(data, null, 2)}`;
17
+ }
18
+ fs.appendFileSync('/tmp/opencode_pollinations_debug.log', logMsg + '\n\n');
19
+ }
20
+ catch (e) {
21
+ // ignore logging errors
22
+ }
23
+ }
24
+ function sanitizeTools(tools) {
25
+ if (!Array.isArray(tools))
26
+ return tools;
27
+ const cleanSchema = (schema) => {
28
+ if (!schema || typeof schema !== "object")
29
+ return;
30
+ if (schema.optional !== undefined)
31
+ delete schema.optional;
32
+ if (schema.ref !== undefined)
33
+ delete schema.ref;
34
+ if (schema["$ref"] !== undefined)
35
+ delete schema["$ref"];
36
+ if (schema.properties) {
37
+ for (const key in schema.properties)
38
+ cleanSchema(schema.properties[key]);
39
+ }
40
+ if (schema.items)
41
+ cleanSchema(schema.items);
42
+ };
43
+ return tools.map((tool) => {
44
+ const newTool = { ...tool };
45
+ if (newTool.function && newTool.function.parameters) {
46
+ cleanSchema(newTool.function.parameters);
47
+ }
48
+ return newTool;
49
+ });
50
+ }
51
+ function filterTools(tools, maxCount = 120) {
52
+ if (!Array.isArray(tools))
53
+ return [];
54
+ if (tools.length <= maxCount)
55
+ return tools;
56
+ const priorities = [
57
+ "bash", "read", "write", "edit", "webfetch", "glob", "grep",
58
+ "searxng_remote_search", "deepsearch_deep_search", "google_search",
59
+ "task", "todowrite"
60
+ ];
61
+ const priorityTools = tools.filter((t) => priorities.includes(t.function.name));
62
+ const otherTools = tools.filter((t) => !priorities.includes(t.function.name));
63
+ const slotsLeft = maxCount - priorityTools.length;
64
+ const othersKept = otherTools.slice(0, Math.max(0, slotsLeft));
65
+ logDebug(`[POLLI-PLUGIN] Filtering tools: ${tools.length} -> ${priorityTools.length + othersKept.length}`);
66
+ return [...priorityTools, ...othersKept];
67
+ }
68
+ // --- Fetch Implementation ---
69
+ export const createPollinationsFetch = (apiKey) => async (input, init) => {
70
+ let url = input.toString();
71
+ const options = init || {};
72
+ let body = null;
73
+ if (options.body && typeof options.body === "string") {
74
+ try {
75
+ body = JSON.parse(options.body);
76
+ }
77
+ catch (e) {
78
+ // Not JSON, ignore
79
+ }
80
+ }
81
+ // --- INTERCEPTION & SANITIZATION ---
82
+ if (body) {
83
+ let model = body.model || "";
84
+ // 0. Model Name Normalization
85
+ if (typeof model === "string" && model.startsWith("pollinations/enter/")) {
86
+ body.model = model.replace("pollinations/enter/", "");
87
+ model = body.model;
88
+ }
89
+ // FIX: Remove stream_options (causes 400 on some OpenAI proxies)
90
+ if (body.stream_options) {
91
+ delete body.stream_options;
92
+ }
93
+ // 1. Azure Tool Limit Fix
94
+ if ((model.includes("openai") || model.includes("gpt")) && body.tools) {
95
+ if (body.tools.length > 120) {
96
+ body.tools = filterTools(body.tools, 120);
97
+ }
98
+ }
99
+ // 2. Vertex/Gemini Schema Fix
100
+ if (model.includes("gemini") && body.tools) {
101
+ body.tools = sanitizeTools(body.tools);
102
+ }
103
+ // Re-serialize body
104
+ options.body = JSON.stringify(body);
105
+ }
106
+ // Ensure Headers
107
+ const headers = new Headers(options.headers || {});
108
+ headers.set("Authorization", `Bearer ${apiKey}`);
109
+ headers.set("Content-Type", "application/json");
110
+ options.headers = headers;
111
+ logDebug(`Req: ${url}`, body);
112
+ try {
113
+ const response = await global.fetch(url, options);
114
+ // Log response status
115
+ // We clone to read text for debugging errors
116
+ if (!response.ok) {
117
+ try {
118
+ const clone = response.clone();
119
+ const text = await clone.text();
120
+ logDebug(`Res (Error): ${response.status}`, text);
121
+ }
122
+ catch (e) {
123
+ logDebug(`Res (Error): ${response.status} (Read failed)`);
124
+ }
125
+ }
126
+ else {
127
+ logDebug(`Res (OK): ${response.status}`);
128
+ }
129
+ return response;
130
+ }
131
+ catch (e) {
132
+ logDebug(`Fetch Error: ${e.message}`);
133
+ throw e;
134
+ }
135
+ };
@@ -0,0 +1 @@
1
+ export declare const createPollinationsFetch: (apiKey: string) => (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
@@ -0,0 +1,135 @@
1
+ // Removed invalid imports
2
+ import * as fs from 'fs';
3
+ // --- Sanitization Helpers (Ported from Gateway/Upstream) ---
4
+ function safeId(id) {
5
+ if (!id)
6
+ return id;
7
+ if (id.length > 30)
8
+ return id.substring(0, 30);
9
+ return id;
10
+ }
11
+ function logDebug(message, data) {
12
+ try {
13
+ const timestamp = new Date().toISOString();
14
+ let logMsg = `[${timestamp}] ${message}`;
15
+ if (data) {
16
+ logMsg += `\n${JSON.stringify(data, null, 2)}`;
17
+ }
18
+ fs.appendFileSync('/tmp/opencode_pollinations_debug.log', logMsg + '\n\n');
19
+ }
20
+ catch (e) {
21
+ // ignore logging errors
22
+ }
23
+ }
24
+ function sanitizeTools(tools) {
25
+ if (!Array.isArray(tools))
26
+ return tools;
27
+ const cleanSchema = (schema) => {
28
+ if (!schema || typeof schema !== "object")
29
+ return;
30
+ if (schema.optional !== undefined)
31
+ delete schema.optional;
32
+ if (schema.ref !== undefined)
33
+ delete schema.ref;
34
+ if (schema["$ref"] !== undefined)
35
+ delete schema["$ref"];
36
+ if (schema.properties) {
37
+ for (const key in schema.properties)
38
+ cleanSchema(schema.properties[key]);
39
+ }
40
+ if (schema.items)
41
+ cleanSchema(schema.items);
42
+ };
43
+ return tools.map((tool) => {
44
+ const newTool = { ...tool };
45
+ if (newTool.function && newTool.function.parameters) {
46
+ cleanSchema(newTool.function.parameters);
47
+ }
48
+ return newTool;
49
+ });
50
+ }
51
+ function filterTools(tools, maxCount = 120) {
52
+ if (!Array.isArray(tools))
53
+ return [];
54
+ if (tools.length <= maxCount)
55
+ return tools;
56
+ const priorities = [
57
+ "bash", "read", "write", "edit", "webfetch", "glob", "grep",
58
+ "searxng_remote_search", "deepsearch_deep_search", "google_search",
59
+ "task", "todowrite"
60
+ ];
61
+ const priorityTools = tools.filter((t) => priorities.includes(t.function.name));
62
+ const otherTools = tools.filter((t) => !priorities.includes(t.function.name));
63
+ const slotsLeft = maxCount - priorityTools.length;
64
+ const othersKept = otherTools.slice(0, Math.max(0, slotsLeft));
65
+ logDebug(`[POLLI-PLUGIN] Filtering tools: ${tools.length} -> ${priorityTools.length + othersKept.length}`);
66
+ return [...priorityTools, ...othersKept];
67
+ }
68
+ // --- Fetch Implementation ---
69
+ export const createPollinationsFetch = (apiKey) => async (input, init) => {
70
+ let url = input.toString();
71
+ const options = init || {};
72
+ let body = null;
73
+ if (options.body && typeof options.body === "string") {
74
+ try {
75
+ body = JSON.parse(options.body);
76
+ }
77
+ catch (e) {
78
+ // Not JSON, ignore
79
+ }
80
+ }
81
+ // --- INTERCEPTION & SANITIZATION ---
82
+ if (body) {
83
+ let model = body.model || "";
84
+ // 0. Model Name Normalization
85
+ if (typeof model === "string" && model.startsWith("pollinations/enter/")) {
86
+ body.model = model.replace("pollinations/enter/", "");
87
+ model = body.model;
88
+ }
89
+ // FIX: Remove stream_options (causes 400 on some OpenAI proxies)
90
+ if (body.stream_options) {
91
+ delete body.stream_options;
92
+ }
93
+ // 1. Azure Tool Limit Fix
94
+ if ((model.includes("openai") || model.includes("gpt")) && body.tools) {
95
+ if (body.tools.length > 120) {
96
+ body.tools = filterTools(body.tools, 120);
97
+ }
98
+ }
99
+ // 2. Vertex/Gemini Schema Fix
100
+ if (model.includes("gemini") && body.tools) {
101
+ body.tools = sanitizeTools(body.tools);
102
+ }
103
+ // Re-serialize body
104
+ options.body = JSON.stringify(body);
105
+ }
106
+ // Ensure Headers
107
+ const headers = new Headers(options.headers || {});
108
+ headers.set("Authorization", `Bearer ${apiKey}`);
109
+ headers.set("Content-Type", "application/json");
110
+ options.headers = headers;
111
+ logDebug(`Req: ${url}`, body);
112
+ try {
113
+ const response = await global.fetch(url, options);
114
+ // Log response status
115
+ // We clone to read text for debugging errors
116
+ if (!response.ok) {
117
+ try {
118
+ const clone = response.clone();
119
+ const text = await clone.text();
120
+ logDebug(`Res (Error): ${response.status}`, text);
121
+ }
122
+ catch (e) {
123
+ logDebug(`Res (Error): ${response.status} (Read failed)`);
124
+ }
125
+ }
126
+ else {
127
+ logDebug(`Res (OK): ${response.status}`);
128
+ }
129
+ return response;
130
+ }
131
+ catch (e) {
132
+ logDebug(`Fetch Error: ${e.message}`);
133
+ throw e;
134
+ }
135
+ };
@@ -0,0 +1,10 @@
1
+ interface CommandResult {
2
+ handled: boolean;
3
+ response?: string;
4
+ error?: string;
5
+ }
6
+ export declare function handleCommand(command: string): Promise<CommandResult>;
7
+ export declare function createCommandHooks(): {
8
+ 'tui.command.execute': (input: any, output: any) => Promise<void>;
9
+ };
10
+ export {};