@blockrun/franklin 3.0.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.
Files changed (138) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +256 -0
  3. package/dist/agent/commands.d.ts +27 -0
  4. package/dist/agent/commands.js +659 -0
  5. package/dist/agent/compact.d.ts +31 -0
  6. package/dist/agent/compact.js +366 -0
  7. package/dist/agent/context.d.ts +11 -0
  8. package/dist/agent/context.js +184 -0
  9. package/dist/agent/error-classifier.d.ts +10 -0
  10. package/dist/agent/error-classifier.js +61 -0
  11. package/dist/agent/llm.d.ts +63 -0
  12. package/dist/agent/llm.js +448 -0
  13. package/dist/agent/loop.d.ts +12 -0
  14. package/dist/agent/loop.js +346 -0
  15. package/dist/agent/optimize.d.ts +53 -0
  16. package/dist/agent/optimize.js +262 -0
  17. package/dist/agent/permissions.d.ts +39 -0
  18. package/dist/agent/permissions.js +226 -0
  19. package/dist/agent/reduce.d.ts +49 -0
  20. package/dist/agent/reduce.js +317 -0
  21. package/dist/agent/streaming-executor.d.ts +36 -0
  22. package/dist/agent/streaming-executor.js +149 -0
  23. package/dist/agent/tokens.d.ts +53 -0
  24. package/dist/agent/tokens.js +185 -0
  25. package/dist/agent/types.d.ts +125 -0
  26. package/dist/agent/types.js +5 -0
  27. package/dist/banner.d.ts +1 -0
  28. package/dist/banner.js +27 -0
  29. package/dist/commands/balance.d.ts +1 -0
  30. package/dist/commands/balance.js +40 -0
  31. package/dist/commands/config.d.ts +14 -0
  32. package/dist/commands/config.js +107 -0
  33. package/dist/commands/daemon.d.ts +3 -0
  34. package/dist/commands/daemon.js +117 -0
  35. package/dist/commands/history.d.ts +5 -0
  36. package/dist/commands/history.js +31 -0
  37. package/dist/commands/init.d.ts +3 -0
  38. package/dist/commands/init.js +92 -0
  39. package/dist/commands/logs.d.ts +5 -0
  40. package/dist/commands/logs.js +89 -0
  41. package/dist/commands/models.d.ts +1 -0
  42. package/dist/commands/models.js +56 -0
  43. package/dist/commands/plugin.d.ts +14 -0
  44. package/dist/commands/plugin.js +176 -0
  45. package/dist/commands/proxy.d.ts +13 -0
  46. package/dist/commands/proxy.js +106 -0
  47. package/dist/commands/setup.d.ts +1 -0
  48. package/dist/commands/setup.js +49 -0
  49. package/dist/commands/start.d.ts +8 -0
  50. package/dist/commands/start.js +292 -0
  51. package/dist/commands/stats.d.ts +10 -0
  52. package/dist/commands/stats.js +94 -0
  53. package/dist/commands/uninit.d.ts +1 -0
  54. package/dist/commands/uninit.js +63 -0
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +41 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +179 -0
  59. package/dist/mcp/client.d.ts +44 -0
  60. package/dist/mcp/client.js +147 -0
  61. package/dist/mcp/config.d.ts +20 -0
  62. package/dist/mcp/config.js +138 -0
  63. package/dist/plugin-sdk/channel.d.ts +100 -0
  64. package/dist/plugin-sdk/channel.js +10 -0
  65. package/dist/plugin-sdk/index.d.ts +14 -0
  66. package/dist/plugin-sdk/index.js +9 -0
  67. package/dist/plugin-sdk/plugin.d.ts +87 -0
  68. package/dist/plugin-sdk/plugin.js +7 -0
  69. package/dist/plugin-sdk/search.d.ts +13 -0
  70. package/dist/plugin-sdk/search.js +4 -0
  71. package/dist/plugin-sdk/tracker.d.ts +27 -0
  72. package/dist/plugin-sdk/tracker.js +5 -0
  73. package/dist/plugin-sdk/workflow.d.ts +126 -0
  74. package/dist/plugin-sdk/workflow.js +11 -0
  75. package/dist/plugins/registry.d.ts +33 -0
  76. package/dist/plugins/registry.js +155 -0
  77. package/dist/plugins/runner.d.ts +21 -0
  78. package/dist/plugins/runner.js +453 -0
  79. package/dist/plugins-bundled/social/index.d.ts +10 -0
  80. package/dist/plugins-bundled/social/index.js +363 -0
  81. package/dist/plugins-bundled/social/plugin.json +14 -0
  82. package/dist/plugins-bundled/social/prompts.d.ts +19 -0
  83. package/dist/plugins-bundled/social/prompts.js +67 -0
  84. package/dist/plugins-bundled/social/types.d.ts +58 -0
  85. package/dist/plugins-bundled/social/types.js +16 -0
  86. package/dist/pricing.d.ts +21 -0
  87. package/dist/pricing.js +91 -0
  88. package/dist/proxy/fallback.d.ts +38 -0
  89. package/dist/proxy/fallback.js +144 -0
  90. package/dist/proxy/server.d.ts +18 -0
  91. package/dist/proxy/server.js +576 -0
  92. package/dist/proxy/sse-translator.d.ts +29 -0
  93. package/dist/proxy/sse-translator.js +270 -0
  94. package/dist/router/index.d.ts +22 -0
  95. package/dist/router/index.js +269 -0
  96. package/dist/session/search.d.ts +33 -0
  97. package/dist/session/search.js +229 -0
  98. package/dist/session/storage.d.ts +48 -0
  99. package/dist/session/storage.js +173 -0
  100. package/dist/stats/insights.d.ts +55 -0
  101. package/dist/stats/insights.js +195 -0
  102. package/dist/stats/tracker.d.ts +54 -0
  103. package/dist/stats/tracker.js +165 -0
  104. package/dist/tools/askuser.d.ts +6 -0
  105. package/dist/tools/askuser.js +76 -0
  106. package/dist/tools/bash.d.ts +5 -0
  107. package/dist/tools/bash.js +336 -0
  108. package/dist/tools/edit.d.ts +5 -0
  109. package/dist/tools/edit.js +148 -0
  110. package/dist/tools/glob.d.ts +5 -0
  111. package/dist/tools/glob.js +158 -0
  112. package/dist/tools/grep.d.ts +5 -0
  113. package/dist/tools/grep.js +194 -0
  114. package/dist/tools/imagegen.d.ts +6 -0
  115. package/dist/tools/imagegen.js +172 -0
  116. package/dist/tools/index.d.ts +17 -0
  117. package/dist/tools/index.js +30 -0
  118. package/dist/tools/read.d.ts +11 -0
  119. package/dist/tools/read.js +90 -0
  120. package/dist/tools/subagent.d.ts +5 -0
  121. package/dist/tools/subagent.js +116 -0
  122. package/dist/tools/task.d.ts +5 -0
  123. package/dist/tools/task.js +91 -0
  124. package/dist/tools/webfetch.d.ts +5 -0
  125. package/dist/tools/webfetch.js +166 -0
  126. package/dist/tools/websearch.d.ts +5 -0
  127. package/dist/tools/websearch.js +103 -0
  128. package/dist/tools/write.d.ts +5 -0
  129. package/dist/tools/write.js +114 -0
  130. package/dist/ui/app.d.ts +26 -0
  131. package/dist/ui/app.js +545 -0
  132. package/dist/ui/model-picker.d.ts +14 -0
  133. package/dist/ui/model-picker.js +161 -0
  134. package/dist/ui/terminal.d.ts +35 -0
  135. package/dist/ui/terminal.js +337 -0
  136. package/dist/wallet/manager.d.ts +10 -0
  137. package/dist/wallet/manager.js +23 -0
  138. package/package.json +79 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * WebFetch capability — fetch web page content.
3
+ */
4
+ import { USER_AGENT } from '../config.js';
5
+ const MAX_BODY_BYTES = 256 * 1024; // 256KB
6
+ // ─── Session cache ──────────────────────────────────────────────────────────
7
+ // Avoids re-fetching the same URL within a session (common in research tasks).
8
+ // 15-min TTL, max 50 entries.
9
+ const CACHE_TTL_MS = 15 * 60 * 1000;
10
+ const MAX_CACHE_ENTRIES = 50;
11
+ const fetchCache = new Map();
12
+ function getCached(url) {
13
+ const entry = fetchCache.get(url);
14
+ if (!entry)
15
+ return null;
16
+ if (Date.now() > entry.expiresAt) {
17
+ fetchCache.delete(url);
18
+ return null;
19
+ }
20
+ return entry.output;
21
+ }
22
+ function setCached(url, output) {
23
+ // Evict oldest entry if at capacity
24
+ if (fetchCache.size >= MAX_CACHE_ENTRIES) {
25
+ const firstKey = fetchCache.keys().next().value;
26
+ if (firstKey)
27
+ fetchCache.delete(firstKey);
28
+ }
29
+ fetchCache.set(url, { output, expiresAt: Date.now() + CACHE_TTL_MS });
30
+ }
31
+ // ─── Execute ────────────────────────────────────────────────────────────────
32
+ async function execute(input, _ctx) {
33
+ const { url, max_length } = input;
34
+ if (!url) {
35
+ return { output: 'Error: url is required', isError: true };
36
+ }
37
+ // Basic URL validation
38
+ let parsed;
39
+ try {
40
+ parsed = new URL(url);
41
+ }
42
+ catch {
43
+ return { output: `Error: invalid URL: ${url}`, isError: true };
44
+ }
45
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
46
+ return { output: `Error: only http/https URLs are supported`, isError: true };
47
+ }
48
+ // Check cache first
49
+ const cached = getCached(url);
50
+ if (cached) {
51
+ return { output: cached + '\n\n(cached)' };
52
+ }
53
+ const controller = new AbortController();
54
+ const timeout = setTimeout(() => controller.abort(), 30_000);
55
+ try {
56
+ const response = await fetch(url, {
57
+ signal: controller.signal,
58
+ headers: {
59
+ 'User-Agent': USER_AGENT,
60
+ 'Accept': 'text/html,application/json,text/plain,*/*',
61
+ },
62
+ redirect: 'follow',
63
+ });
64
+ if (!response.ok) {
65
+ return {
66
+ output: `HTTP ${response.status} ${response.statusText} for ${url}`,
67
+ isError: true,
68
+ };
69
+ }
70
+ const contentType = response.headers.get('content-type') || '';
71
+ const maxLen = Math.min(max_length ?? MAX_BODY_BYTES, MAX_BODY_BYTES);
72
+ // Read body with size limit
73
+ const reader = response.body?.getReader();
74
+ if (!reader) {
75
+ return { output: 'Error: no response body', isError: true };
76
+ }
77
+ const chunks = [];
78
+ let totalBytes = 0;
79
+ try {
80
+ while (totalBytes < maxLen) {
81
+ const { done, value } = await reader.read();
82
+ if (done)
83
+ break;
84
+ chunks.push(value);
85
+ totalBytes += value.length;
86
+ }
87
+ }
88
+ finally {
89
+ reader.releaseLock();
90
+ }
91
+ const decoder = new TextDecoder();
92
+ let body = decoder.decode(Buffer.concat(chunks)).slice(0, maxLen);
93
+ // Format response based on content type
94
+ if (contentType.includes('json')) {
95
+ try {
96
+ const parsedJson = JSON.parse(body);
97
+ body = JSON.stringify(parsedJson, null, 2).slice(0, maxLen);
98
+ }
99
+ catch { /* leave as-is if not valid JSON */ }
100
+ }
101
+ else if (contentType.includes('html')) {
102
+ body = stripHtml(body);
103
+ }
104
+ let output = `URL: ${url}\nStatus: ${response.status}\nContent-Type: ${contentType}\n\n${body}`;
105
+ if (totalBytes >= maxLen) {
106
+ output += '\n\n... (content truncated)';
107
+ }
108
+ // Cache successful responses
109
+ setCached(url, output);
110
+ return { output };
111
+ }
112
+ catch (err) {
113
+ const msg = err instanceof Error ? err.message : String(err);
114
+ if (msg.includes('abort')) {
115
+ return { output: `Error: request timed out after 30s for ${url}`, isError: true };
116
+ }
117
+ return { output: `Error fetching ${url}: ${msg}`, isError: true };
118
+ }
119
+ finally {
120
+ clearTimeout(timeout);
121
+ }
122
+ }
123
+ function stripHtml(html) {
124
+ return html
125
+ // Remove non-content elements
126
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
127
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
128
+ .replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, '')
129
+ .replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '')
130
+ .replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '')
131
+ .replace(/<aside[^>]*>[\s\S]*?<\/aside>/gi, '')
132
+ .replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, '')
133
+ .replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '')
134
+ .replace(/<form[^>]*>[\s\S]*?<\/form>/gi, '')
135
+ // Convert block elements to newlines for readability
136
+ .replace(/<\/?(p|div|h[1-6]|li|br|tr)[^>]*>/gi, '\n')
137
+ // Strip remaining tags
138
+ .replace(/<[^>]+>/g, ' ')
139
+ // Decode entities
140
+ .replace(/&nbsp;/g, ' ')
141
+ .replace(/&amp;/g, '&')
142
+ .replace(/&lt;/g, '<')
143
+ .replace(/&gt;/g, '>')
144
+ .replace(/&quot;/g, '"')
145
+ .replace(/&#39;/g, "'")
146
+ // Clean whitespace
147
+ .replace(/[ \t]+/g, ' ')
148
+ .replace(/\n{3,}/g, '\n\n')
149
+ .trim();
150
+ }
151
+ export const webFetchCapability = {
152
+ spec: {
153
+ name: 'WebFetch',
154
+ description: 'Fetch a web page and return its content. HTML tags are stripped for readability. Results are cached for 15 minutes.',
155
+ input_schema: {
156
+ type: 'object',
157
+ properties: {
158
+ url: { type: 'string', description: 'The URL to fetch' },
159
+ max_length: { type: 'number', description: 'Max content bytes to return. Default: 256KB' },
160
+ },
161
+ required: ['url'],
162
+ },
163
+ },
164
+ execute,
165
+ concurrent: true,
166
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * WebSearch capability — search the web via BlockRun API or DuckDuckGo fallback.
3
+ */
4
+ import type { CapabilityHandler } from '../agent/types.js';
5
+ export declare const webSearchCapability: CapabilityHandler;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * WebSearch capability — search the web via BlockRun API or DuckDuckGo fallback.
3
+ */
4
+ import { VERSION } from '../config.js';
5
+ async function execute(input, _ctx) {
6
+ const { query, max_results } = input;
7
+ if (!query) {
8
+ return { output: 'Error: query is required', isError: true };
9
+ }
10
+ const maxResults = Math.min(Math.max(max_results ?? 5, 1), 20);
11
+ // Try DuckDuckGo HTML search (no API key needed)
12
+ try {
13
+ const encoded = encodeURIComponent(query);
14
+ const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
15
+ const controller = new AbortController();
16
+ const timeout = setTimeout(() => controller.abort(), 15_000);
17
+ const response = await fetch(url, {
18
+ signal: controller.signal,
19
+ headers: {
20
+ 'User-Agent': `runcode/${VERSION} (coding-agent)`,
21
+ },
22
+ });
23
+ clearTimeout(timeout);
24
+ if (!response.ok) {
25
+ return { output: `Search failed: HTTP ${response.status}`, isError: true };
26
+ }
27
+ const html = await response.text();
28
+ const results = parseDuckDuckGoResults(html, maxResults);
29
+ if (results.length === 0) {
30
+ return { output: `No results found for: ${query}` };
31
+ }
32
+ const formatted = results
33
+ .map((r, i) => `${i + 1}. ${r.title}\n ${r.url}\n ${r.snippet}`)
34
+ .join('\n\n');
35
+ return { output: `Search results for "${query}":\n\n${formatted}` };
36
+ }
37
+ catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ if (msg.includes('abort')) {
40
+ return { output: `Search timed out after 15s for: ${query}`, isError: true };
41
+ }
42
+ return { output: `Search error: ${msg}`, isError: true };
43
+ }
44
+ }
45
+ function parseDuckDuckGoResults(html, maxResults) {
46
+ const results = [];
47
+ // Primary parser: match result blocks by class names
48
+ const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
49
+ const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
50
+ let links = [...html.matchAll(linkRegex)];
51
+ let snippets = [...html.matchAll(snippetRegex)];
52
+ // Fallback parser if primary finds nothing (DDG may have updated HTML)
53
+ if (links.length === 0) {
54
+ const fallbackLink = /<a[^>]*class="[^"]*result[^"]*"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
55
+ links = [...html.matchAll(fallbackLink)];
56
+ }
57
+ for (let i = 0; i < Math.min(links.length, maxResults); i++) {
58
+ const link = links[i];
59
+ const snippet = snippets[i];
60
+ let url = link[1] || '';
61
+ // DuckDuckGo wraps URLs in redirect — extract the actual URL
62
+ const uddgMatch = url.match(/uddg=([^&]+)/);
63
+ if (uddgMatch) {
64
+ url = decodeURIComponent(uddgMatch[1]);
65
+ }
66
+ // Skip internal DDG links
67
+ if (url.startsWith('/') || url.includes('duckduckgo.com'))
68
+ continue;
69
+ results.push({
70
+ title: stripTags(link[2] || '').trim(),
71
+ url,
72
+ snippet: stripTags(snippet?.[1] || '').trim(),
73
+ });
74
+ }
75
+ return results;
76
+ }
77
+ function stripTags(html) {
78
+ return html
79
+ .replace(/<[^>]+>/g, '')
80
+ .replace(/&amp;/g, '&')
81
+ .replace(/&lt;/g, '<')
82
+ .replace(/&gt;/g, '>')
83
+ .replace(/&quot;/g, '"')
84
+ .replace(/&#39;/g, "'")
85
+ .replace(/&nbsp;/g, ' ')
86
+ .replace(/\s+/g, ' ');
87
+ }
88
+ export const webSearchCapability = {
89
+ spec: {
90
+ name: 'WebSearch',
91
+ description: 'Search the web and return results with titles, URLs, and snippets.',
92
+ input_schema: {
93
+ type: 'object',
94
+ properties: {
95
+ query: { type: 'string', description: 'The search query' },
96
+ max_results: { type: 'number', description: 'Max number of results. Default: 5' },
97
+ },
98
+ required: ['query'],
99
+ },
100
+ },
101
+ execute,
102
+ concurrent: true,
103
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Write capability — creates or overwrites files.
3
+ */
4
+ import type { CapabilityHandler } from '../agent/types.js';
5
+ export declare const writeCapability: CapabilityHandler;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Write capability — creates or overwrites files.
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ function withTrailingSep(value) {
8
+ return value.endsWith(path.sep) ? value : value + path.sep;
9
+ }
10
+ function isWithinDir(target, dir) {
11
+ const normalizedTarget = path.resolve(target);
12
+ const normalizedDir = withTrailingSep(path.resolve(dir));
13
+ return normalizedTarget === normalizedDir.slice(0, -1) || normalizedTarget.startsWith(normalizedDir);
14
+ }
15
+ function getAllowedTempDirs() {
16
+ const candidates = new Set([path.resolve(os.tmpdir())]);
17
+ for (const dir of [...candidates]) {
18
+ try {
19
+ candidates.add(path.resolve(fs.realpathSync(dir)));
20
+ }
21
+ catch {
22
+ // Best effort only.
23
+ }
24
+ if (dir.startsWith('/private/')) {
25
+ candidates.add(dir.slice('/private'.length));
26
+ }
27
+ else {
28
+ candidates.add(path.join('/private', dir));
29
+ }
30
+ }
31
+ return [...candidates];
32
+ }
33
+ async function execute(input, ctx) {
34
+ const { file_path: filePath, content } = input;
35
+ if (!filePath) {
36
+ return { output: 'Error: file_path is required', isError: true };
37
+ }
38
+ if (content === undefined || content === null) {
39
+ return { output: 'Error: content is required', isError: true };
40
+ }
41
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(ctx.workingDir, filePath);
42
+ // Safety: block system paths and sensitive home directories
43
+ // Resolve symlinks to prevent traversal attacks
44
+ const home = os.homedir();
45
+ const allowedTempDirs = getAllowedTempDirs();
46
+ const dangerousPaths = [
47
+ '/etc/', '/usr/', '/bin/', '/sbin/', '/var/', '/System/',
48
+ path.join(home, '.ssh') + '/',
49
+ path.join(home, '.aws') + '/',
50
+ path.join(home, '.kube') + '/',
51
+ path.join(home, '.gnupg') + '/',
52
+ path.join(home, '.config/gcloud') + '/',
53
+ ];
54
+ // Check both the resolved path and the real path (after symlink resolution)
55
+ const checkPath = (p) => !allowedTempDirs.some(dir => isWithinDir(p, dir)) &&
56
+ dangerousPaths.some(dp => p.startsWith(dp));
57
+ if (checkPath(resolved)) {
58
+ return { output: `Error: refusing to write to sensitive path: ${resolved}`, isError: true };
59
+ }
60
+ // Also check parent dir's real path if it already exists (symlink protection)
61
+ const parentDir = path.dirname(resolved);
62
+ try {
63
+ if (fs.existsSync(parentDir)) {
64
+ const realParent = fs.realpathSync(parentDir);
65
+ if (checkPath(realParent + '/')) {
66
+ return { output: `Error: refusing to write — path resolves to sensitive location: ${realParent}`, isError: true };
67
+ }
68
+ }
69
+ }
70
+ catch { /* parent doesn't exist yet, will be created */ }
71
+ // Also check if target file itself is a symlink to a sensitive location
72
+ try {
73
+ if (fs.existsSync(resolved) && fs.lstatSync(resolved).isSymbolicLink()) {
74
+ const realTarget = fs.realpathSync(resolved);
75
+ if (checkPath(realTarget)) {
76
+ return { output: `Error: refusing to write — symlink resolves to sensitive location: ${realTarget}`, isError: true };
77
+ }
78
+ }
79
+ }
80
+ catch { /* file doesn't exist yet, ok */ }
81
+ try {
82
+ // Ensure parent directory exists
83
+ const parentDir = path.dirname(resolved);
84
+ fs.mkdirSync(parentDir, { recursive: true });
85
+ const existed = fs.existsSync(resolved);
86
+ fs.writeFileSync(resolved, content, 'utf-8');
87
+ const lineCount = content.split('\n').length;
88
+ const byteCount = Buffer.byteLength(content, 'utf-8');
89
+ const sizeStr = byteCount >= 1024 ? `${(byteCount / 1024).toFixed(1)}KB` : `${byteCount}B`;
90
+ return {
91
+ output: `${existed ? 'Updated' : 'Created'} ${resolved} (${lineCount} lines, ${sizeStr})`,
92
+ };
93
+ }
94
+ catch (err) {
95
+ const msg = err instanceof Error ? err.message : String(err);
96
+ return { output: `Error writing file: ${msg}`, isError: true };
97
+ }
98
+ }
99
+ export const writeCapability = {
100
+ spec: {
101
+ name: 'Write',
102
+ description: 'Create or overwrite a file.',
103
+ input_schema: {
104
+ type: 'object',
105
+ properties: {
106
+ file_path: { type: 'string', description: 'Absolute path' },
107
+ content: { type: 'string', description: 'File content' },
108
+ },
109
+ required: ['file_path', 'content'],
110
+ },
111
+ },
112
+ execute,
113
+ concurrent: false,
114
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * RunCode ink-based terminal UI.
3
+ * Real-time streaming, thinking animation, tool progress, slash commands.
4
+ */
5
+ import type { StreamEvent } from '../agent/types.js';
6
+ export interface InkUIHandle {
7
+ handleEvent: (event: StreamEvent) => void;
8
+ updateModel: (model: string) => void;
9
+ updateBalance: (balance: string) => void;
10
+ onTurnDone: (cb: () => void) => void;
11
+ waitForInput: () => Promise<string | null>;
12
+ onAbort: (cb: () => void) => void;
13
+ cleanup: () => void;
14
+ requestPermission: (toolName: string, description: string) => Promise<'yes' | 'no' | 'always'>;
15
+ requestAskUser: (question: string, options?: string[]) => Promise<string>;
16
+ }
17
+ export declare function launchInkUI(opts: {
18
+ model: string;
19
+ workDir: string;
20
+ version: string;
21
+ walletAddress?: string;
22
+ walletBalance?: string;
23
+ chain?: string;
24
+ showPicker?: boolean;
25
+ onModelChange?: (model: string) => void;
26
+ }): InkUIHandle;