freddie 0.0.48 → 0.0.50

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 (230) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +2 -1
  3. package/plugins/ansi_strip/handler.js +7 -0
  4. package/plugins/ansi_strip/plugin.js +2 -0
  5. package/plugins/approval/handler.js +13 -0
  6. package/plugins/approval/plugin.js +2 -0
  7. package/plugins/bash/handler.js +33 -0
  8. package/plugins/bash/plugin.js +2 -0
  9. package/plugins/binary_extensions/handler.js +20 -0
  10. package/plugins/binary_extensions/plugin.js +2 -0
  11. package/plugins/browser/handler.js +46 -0
  12. package/plugins/browser/plugin.js +2 -0
  13. package/plugins/budget_config/handler.js +12 -0
  14. package/plugins/budget_config/plugin.js +2 -0
  15. package/plugins/checkpoint/handler.js +27 -0
  16. package/plugins/checkpoint/plugin.js +2 -0
  17. package/plugins/clarify/handler.js +13 -0
  18. package/plugins/clarify/plugin.js +2 -0
  19. package/plugins/code_execution/handler.js +25 -0
  20. package/plugins/code_execution/plugin.js +2 -0
  21. package/plugins/core-agent-machine/plugin.js +8 -0
  22. package/plugins/core-cli/plugin.js +83 -0
  23. package/plugins/core-commands/plugin.js +7 -0
  24. package/plugins/core-compressor/plugin.js +15 -0
  25. package/plugins/core-context-engine/plugin.js +7 -0
  26. package/plugins/core-cron/plugin.js +7 -0
  27. package/plugins/core-skills/plugin.js +7 -0
  28. package/plugins/credential_files/handler.js +14 -0
  29. package/plugins/credential_files/plugin.js +2 -0
  30. package/plugins/cronjob/handler.js +14 -0
  31. package/plugins/cronjob/plugin.js +2 -0
  32. package/plugins/debug_helpers/handler.js +8 -0
  33. package/plugins/debug_helpers/plugin.js +2 -0
  34. package/plugins/delegate/handler.js +27 -0
  35. package/plugins/delegate/plugin.js +2 -0
  36. package/plugins/discord_tool/handler.js +12 -0
  37. package/plugins/discord_tool/plugin.js +2 -0
  38. package/plugins/edit/handler.js +29 -0
  39. package/plugins/edit/plugin.js +2 -0
  40. package/plugins/env_passthrough/handler.js +14 -0
  41. package/plugins/env_passthrough/plugin.js +2 -0
  42. package/plugins/feishu_doc/handler.js +14 -0
  43. package/plugins/feishu_doc/plugin.js +2 -0
  44. package/plugins/feishu_drive/handler.js +13 -0
  45. package/plugins/feishu_drive/plugin.js +2 -0
  46. package/plugins/file_operations/handler.js +15 -0
  47. package/plugins/file_operations/plugin.js +2 -0
  48. package/plugins/file_state/handler.js +14 -0
  49. package/plugins/file_state/plugin.js +2 -0
  50. package/plugins/file_tools/handler.js +21 -0
  51. package/plugins/file_tools/plugin.js +2 -0
  52. package/plugins/fuzzy_match/handler.js +7 -0
  53. package/plugins/fuzzy_match/plugin.js +2 -0
  54. package/plugins/gm-cc/plugin.js +28 -0
  55. package/plugins/grep/handler.js +49 -0
  56. package/plugins/grep/plugin.js +2 -0
  57. package/plugins/gui-agents/plugin.js +26 -0
  58. package/plugins/gui-batch/plugin.js +11 -0
  59. package/plugins/gui-chat/plugin.js +22 -0
  60. package/plugins/gui-config/plugin.js +12 -0
  61. package/plugins/gui-cron/plugin.js +13 -0
  62. package/plugins/gui-debug/plugin.js +24 -0
  63. package/plugins/gui-env/plugin.js +7 -0
  64. package/plugins/gui-gateway/plugin.js +9 -0
  65. package/plugins/gui-profiles-commands-health/plugin.js +11 -0
  66. package/plugins/gui-sessions/plugin.js +9 -0
  67. package/plugins/gui-skills/plugin.js +8 -0
  68. package/plugins/gui-tools/plugin.js +7 -0
  69. package/plugins/homeassistant_tool/handler.js +14 -0
  70. package/plugins/homeassistant_tool/plugin.js +2 -0
  71. package/plugins/image_gen/handler.js +31 -0
  72. package/plugins/image_gen/plugin.js +2 -0
  73. package/plugins/interrupt/handler.js +16 -0
  74. package/plugins/interrupt/plugin.js +2 -0
  75. package/plugins/managed_tool_gateway/handler.js +9 -0
  76. package/plugins/managed_tool_gateway/plugin.js +2 -0
  77. package/plugins/mcp_oauth/handler.js +20 -0
  78. package/plugins/mcp_oauth/plugin.js +2 -0
  79. package/plugins/mcp_oauth_manager/handler.js +18 -0
  80. package/plugins/mcp_oauth_manager/plugin.js +2 -0
  81. package/plugins/mcp_tool/handler.js +34 -0
  82. package/plugins/mcp_tool/plugin.js +2 -0
  83. package/plugins/memory/handler.js +66 -0
  84. package/plugins/memory/plugin.js +2 -0
  85. package/plugins/memory-byterover/handler.js +25 -0
  86. package/plugins/memory-byterover/plugin.js +2 -0
  87. package/plugins/memory-hindsight/handler.js +25 -0
  88. package/plugins/memory-hindsight/plugin.js +2 -0
  89. package/plugins/memory-holographic/handler.js +31 -0
  90. package/plugins/memory-holographic/plugin.js +2 -0
  91. package/plugins/memory-honcho/handler.js +25 -0
  92. package/plugins/memory-honcho/plugin.js +2 -0
  93. package/plugins/memory-mem0/handler.js +25 -0
  94. package/plugins/memory-mem0/plugin.js +2 -0
  95. package/plugins/memory-openviking/handler.js +25 -0
  96. package/plugins/memory-openviking/plugin.js +2 -0
  97. package/plugins/memory-retaindb/handler.js +25 -0
  98. package/plugins/memory-retaindb/plugin.js +2 -0
  99. package/plugins/memory-supermemory/handler.js +25 -0
  100. package/plugins/memory-supermemory/plugin.js +2 -0
  101. package/plugins/mixture_of_agents/handler.js +13 -0
  102. package/plugins/mixture_of_agents/plugin.js +2 -0
  103. package/plugins/neutts_synth/handler.js +12 -0
  104. package/plugins/neutts_synth/plugin.js +2 -0
  105. package/plugins/openrouter_client/handler.js +12 -0
  106. package/plugins/openrouter_client/plugin.js +2 -0
  107. package/plugins/osv_check/handler.js +10 -0
  108. package/plugins/osv_check/plugin.js +2 -0
  109. package/plugins/patch_parser/handler.js +40 -0
  110. package/plugins/patch_parser/plugin.js +2 -0
  111. package/plugins/path_security/handler.js +14 -0
  112. package/plugins/path_security/plugin.js +2 -0
  113. package/plugins/platform-api_server/handler.js +21 -0
  114. package/plugins/platform-api_server/plugin.js +2 -0
  115. package/plugins/platform-bluebubbles/handler.js +32 -0
  116. package/plugins/platform-bluebubbles/plugin.js +2 -0
  117. package/plugins/platform-dingtalk/handler.js +32 -0
  118. package/plugins/platform-dingtalk/plugin.js +2 -0
  119. package/plugins/platform-discord/handler.js +24 -0
  120. package/plugins/platform-discord/plugin.js +2 -0
  121. package/plugins/platform-email/handler.js +51 -0
  122. package/plugins/platform-email/plugin.js +2 -0
  123. package/plugins/platform-feishu/handler.js +32 -0
  124. package/plugins/platform-feishu/plugin.js +2 -0
  125. package/plugins/platform-feishu_comment/handler.js +12 -0
  126. package/plugins/platform-feishu_comment/plugin.js +2 -0
  127. package/plugins/platform-feishu_comment_rules/handler.js +11 -0
  128. package/plugins/platform-feishu_comment_rules/plugin.js +2 -0
  129. package/plugins/platform-homeassistant/handler.js +32 -0
  130. package/plugins/platform-homeassistant/plugin.js +2 -0
  131. package/plugins/platform-matrix/handler.js +40 -0
  132. package/plugins/platform-matrix/plugin.js +2 -0
  133. package/plugins/platform-mattermost/handler.js +29 -0
  134. package/plugins/platform-mattermost/plugin.js +2 -0
  135. package/plugins/platform-qqbot/handler.js +32 -0
  136. package/plugins/platform-qqbot/plugin.js +2 -0
  137. package/plugins/platform-signal/handler.js +33 -0
  138. package/plugins/platform-signal/plugin.js +2 -0
  139. package/plugins/platform-slack/handler.js +34 -0
  140. package/plugins/platform-slack/plugin.js +2 -0
  141. package/plugins/platform-sms/handler.js +34 -0
  142. package/plugins/platform-sms/plugin.js +2 -0
  143. package/plugins/platform-telegram/handler.js +38 -0
  144. package/plugins/platform-telegram/plugin.js +2 -0
  145. package/plugins/platform-telegram_network/handler.js +17 -0
  146. package/plugins/platform-telegram_network/plugin.js +2 -0
  147. package/plugins/platform-webhook/handler.js +19 -0
  148. package/plugins/platform-webhook/plugin.js +2 -0
  149. package/plugins/platform-wecom/handler.js +32 -0
  150. package/plugins/platform-wecom/plugin.js +2 -0
  151. package/plugins/platform-wecom_callback/handler.js +15 -0
  152. package/plugins/platform-wecom_callback/plugin.js +2 -0
  153. package/plugins/platform-wecom_crypto/handler.js +16 -0
  154. package/plugins/platform-wecom_crypto/plugin.js +2 -0
  155. package/plugins/platform-weixin/handler.js +32 -0
  156. package/plugins/platform-weixin/plugin.js +2 -0
  157. package/plugins/platform-whatsapp/handler.js +40 -0
  158. package/plugins/platform-whatsapp/plugin.js +2 -0
  159. package/plugins/platform-yuanbao/handler.js +9 -0
  160. package/plugins/platform-yuanbao/plugin.js +2 -0
  161. package/plugins/platform-yuanbao_media/handler.js +5 -0
  162. package/plugins/platform-yuanbao_media/plugin.js +2 -0
  163. package/plugins/platform-yuanbao_proto/handler.js +9 -0
  164. package/plugins/platform-yuanbao_proto/plugin.js +2 -0
  165. package/plugins/platform-yuanbao_sticker/handler.js +6 -0
  166. package/plugins/platform-yuanbao_sticker/plugin.js +2 -0
  167. package/plugins/process_registry/handler.js +15 -0
  168. package/plugins/process_registry/plugin.js +2 -0
  169. package/plugins/read/handler.js +24 -0
  170. package/plugins/read/plugin.js +2 -0
  171. package/plugins/rl_training/handler.js +12 -0
  172. package/plugins/rl_training/plugin.js +2 -0
  173. package/plugins/schema_sanitizer/handler.js +17 -0
  174. package/plugins/schema_sanitizer/plugin.js +2 -0
  175. package/plugins/send_message/handler.js +30 -0
  176. package/plugins/send_message/plugin.js +2 -0
  177. package/plugins/session_search/handler.js +21 -0
  178. package/plugins/session_search/plugin.js +2 -0
  179. package/plugins/skill_manager/handler.js +16 -0
  180. package/plugins/skill_manager/plugin.js +2 -0
  181. package/plugins/skill_usage/handler.js +18 -0
  182. package/plugins/skill_usage/plugin.js +2 -0
  183. package/plugins/skills_guard/handler.js +16 -0
  184. package/plugins/skills_guard/plugin.js +2 -0
  185. package/plugins/skills_hub/handler.js +29 -0
  186. package/plugins/skills_hub/plugin.js +2 -0
  187. package/plugins/skills_index/handler.js +12 -0
  188. package/plugins/skills_index/plugin.js +2 -0
  189. package/plugins/skills_sync/handler.js +17 -0
  190. package/plugins/skills_sync/plugin.js +2 -0
  191. package/plugins/skills_tool/handler.js +9 -0
  192. package/plugins/skills_tool/plugin.js +2 -0
  193. package/plugins/slash_confirm/handler.js +14 -0
  194. package/plugins/slash_confirm/plugin.js +2 -0
  195. package/plugins/terminal/handler.js +27 -0
  196. package/plugins/terminal/plugin.js +2 -0
  197. package/plugins/tirith_security/handler.js +23 -0
  198. package/plugins/tirith_security/plugin.js +2 -0
  199. package/plugins/todo/handler.js +52 -0
  200. package/plugins/todo/plugin.js +2 -0
  201. package/plugins/tool_backend_helpers/handler.js +24 -0
  202. package/plugins/tool_backend_helpers/plugin.js +2 -0
  203. package/plugins/tool_output_limits/handler.js +14 -0
  204. package/plugins/tool_output_limits/plugin.js +2 -0
  205. package/plugins/tool_result_storage/handler.js +18 -0
  206. package/plugins/tool_result_storage/plugin.js +2 -0
  207. package/plugins/transcription/handler.js +18 -0
  208. package/plugins/transcription/plugin.js +2 -0
  209. package/plugins/tts/handler.js +18 -0
  210. package/plugins/tts/plugin.js +2 -0
  211. package/plugins/url_safety/handler.js +14 -0
  212. package/plugins/url_safety/plugin.js +2 -0
  213. package/plugins/vision/handler.js +17 -0
  214. package/plugins/vision/plugin.js +2 -0
  215. package/plugins/voice_mode/handler.js +9 -0
  216. package/plugins/voice_mode/plugin.js +2 -0
  217. package/plugins/web_search/handler.js +35 -0
  218. package/plugins/web_search/plugin.js +2 -0
  219. package/plugins/web_tools/handler.js +17 -0
  220. package/plugins/web_tools/plugin.js +2 -0
  221. package/plugins/website_policy/handler.js +13 -0
  222. package/plugins/website_policy/plugin.js +2 -0
  223. package/plugins/write/handler.js +23 -0
  224. package/plugins/write/plugin.js +2 -0
  225. package/plugins/xai_http/handler.js +12 -0
  226. package/plugins/xai_http/plugin.js +2 -0
  227. package/plugins/yuanbao_tools/handler.js +12 -0
  228. package/plugins/yuanbao_tools/plugin.js +2 -0
  229. package/src/agent/llm_resolver.js +2 -1
  230. package/src/agent/pi-bridge.js +3 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased] - 2026-05-04
4
+
5
+ ### Fixed
6
+ - Add `plugins/` to npm `files` array so `bun x freddie` includes all commands (dashboard, tools, cron, etc.)
7
+ - Switch `anentrypoint-design` dep from `file:../anentrypoint-design` to `^0.0.40` registry version so published package installs cleanly without local sibling repo
8
+
3
9
  ## [0.1.2] - 2026-05-04
4
10
 
5
11
  ### Security
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freddie",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "type": "module",
5
5
  "description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
6
6
  "bin": {
@@ -43,6 +43,7 @@
43
43
  "files": [
44
44
  "bin/",
45
45
  "src/",
46
+ "plugins/",
46
47
  "skills/",
47
48
  "README.md",
48
49
  "CHANGELOG.md",
@@ -0,0 +1,7 @@
1
+ import { ansiStrip } from '../../src/utils.js'
2
+ export const _tool = ({
3
+ name: 'ansi_strip',
4
+ toolset: 'core',
5
+ schema: { name: 'ansi_strip', description: 'Strip ANSI escape sequences.', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } },
6
+ handler: async ({ text }) => ({ text: ansiStrip(text) }),
7
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-ansi_strip', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,13 @@
1
+ export const _tool = ({
2
+ name: 'approval',
3
+ toolset: 'core',
4
+ schema: {
5
+ name: 'approval',
6
+ description: 'Request approval for a destructive action. Returns the user decision (allow|deny). In non-interactive contexts, defaults to deny unless config.acp.always_allow includes this action.',
7
+ parameters: { type: 'object', properties: { action: { type: 'string' }, reason: { type: 'string' } }, required: ['action'] },
8
+ },
9
+ handler: async ({ action, reason = '' }, ctx = {}) => {
10
+ if (typeof ctx.askApproval === 'function') return await ctx.askApproval({ action, reason })
11
+ return { decision: 'deny', reason: 'no interactive approval channel; supply ctx.askApproval' }
12
+ },
13
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-approval', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,33 @@
1
+ import { spawn } from 'node:child_process'
2
+ export const _tool = ({
3
+ name: 'bash',
4
+ toolset: 'core',
5
+ schema: {
6
+ name: 'bash',
7
+ description: 'Run a shell command. Returns stdout/stderr/exitCode.',
8
+ parameters: {
9
+ type: 'object',
10
+ properties: {
11
+ command: { type: 'string', description: 'Shell command to execute' },
12
+ cwd: { type: 'string', description: 'Working directory' },
13
+ timeout_ms: { type: 'number', description: 'Hard timeout in ms', default: 60000 },
14
+ background: { type: 'boolean', default: false },
15
+ },
16
+ required: ['command'],
17
+ },
18
+ },
19
+ handler: async (args) => {
20
+ const { command, cwd = process.cwd(), timeout_ms = 60000 } = args
21
+ return await new Promise((resolve) => {
22
+ const sh = process.platform === 'win32' ? 'cmd' : 'sh'
23
+ const flag = process.platform === 'win32' ? '/c' : '-c'
24
+ const child = spawn(sh, [flag, command], { cwd, env: process.env })
25
+ let stdout = '', stderr = ''
26
+ const t = setTimeout(() => { try { child.kill('SIGKILL') } catch {} resolve({ exitCode: -1, stdout, stderr: stderr + '\n[timeout]', timedOut: true }) }, timeout_ms)
27
+ child.stdout?.on('data', d => stdout += d.toString())
28
+ child.stderr?.on('data', d => stderr += d.toString())
29
+ child.on('close', code => { clearTimeout(t); resolve({ exitCode: code, stdout, stderr }) })
30
+ child.on('error', e => { clearTimeout(t); resolve({ exitCode: -1, stdout, stderr: stderr + '\n' + e.message }) })
31
+ })
32
+ },
33
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-bash', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,20 @@
1
+ export const BINARY_EXTENSIONS = new Set([
2
+ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.ico', '.tiff',
3
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
4
+ '.zip', '.tar', '.gz', '.bz2', '.xz', '.7z', '.rar',
5
+ '.exe', '.dll', '.so', '.dylib', '.bin',
6
+ '.mp3', '.mp4', '.mov', '.avi', '.flac', '.ogg',
7
+ '.ttf', '.otf', '.woff', '.woff2',
8
+ '.wasm', '.class', '.jar',
9
+ ])
10
+ export function isBinary(filename) {
11
+ const lower = String(filename).toLowerCase()
12
+ for (const ext of BINARY_EXTENSIONS) if (lower.endsWith(ext)) return true
13
+ return false
14
+ }
15
+ export const _tool = ({
16
+ name: 'binary_extensions',
17
+ toolset: 'core',
18
+ schema: { name: 'binary_extensions', description: 'Check whether a filename has a known binary extension.', parameters: { type: 'object', properties: { filename: { type: 'string' } }, required: ['filename'] } },
19
+ handler: async ({ filename }) => ({ binary: isBinary(filename), known: BINARY_EXTENSIONS.size }),
20
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-binary_extensions', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,46 @@
1
+ let _puppeteerAvail = null
2
+ async function probe() {
3
+ if (_puppeteerAvail !== null) return _puppeteerAvail
4
+ try { await import('puppeteer-core'); _puppeteerAvail = true } catch { _puppeteerAvail = false }
5
+ return _puppeteerAvail
6
+ }
7
+
8
+ export const _tool = ({
9
+ name: 'browser',
10
+ toolset: 'browse',
11
+ schema: {
12
+ name: 'browser',
13
+ description: 'Browser automation: navigate, click, type, evaluate, screenshot. Requires puppeteer-core.',
14
+ parameters: {
15
+ type: 'object',
16
+ properties: {
17
+ action: { type: 'string', enum: ['navigate', 'click', 'type', 'evaluate', 'screenshot', 'text'] },
18
+ url: { type: 'string' },
19
+ selector: { type: 'string' },
20
+ text: { type: 'string' },
21
+ script: { type: 'string' },
22
+ path: { type: 'string' },
23
+ },
24
+ required: ['action'],
25
+ },
26
+ },
27
+ checkFn: () => true,
28
+ requiresEnv: ['puppeteer-core'],
29
+ handler: async (args) => {
30
+ const ok = await probe()
31
+ if (!ok) return { error: 'puppeteer-core not installed. Run: npm install puppeteer-core' }
32
+ const puppeteer = (await import('puppeteer-core')).default
33
+ const browser = await puppeteer.launch({ headless: 'new' })
34
+ try {
35
+ const page = await browser.newPage()
36
+ const a = args.action
37
+ if (a === 'navigate') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); return { url: page.url(), title: await page.title() } }
38
+ if (a === 'click') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); await page.click(args.selector); return { ok: true } }
39
+ if (a === 'type') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); await page.type(args.selector, args.text); return { ok: true } }
40
+ if (a === 'evaluate') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); return { result: await page.evaluate(args.script) } }
41
+ if (a === 'text') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); return { text: await page.evaluate(() => document.body.innerText) } }
42
+ if (a === 'screenshot') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); await page.screenshot({ path: args.path }); return { saved: args.path } }
43
+ return { error: 'unknown action: ' + a }
44
+ } finally { await browser.close() }
45
+ },
46
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-browser', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,12 @@
1
+ import { getConfigValue, saveConfigValue } from '../../src/config.js'
2
+
3
+ export const _tool = ({
4
+ name: 'budget_config',
5
+ toolset: 'core',
6
+ schema: { name: 'budget_config', description: 'Read/write per-session token budget limits.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['get', 'set'] }, max_tokens: { type: 'number' }, max_cost_usd: { type: 'number' } }, required: ['action'] } },
7
+ handler: async ({ action, max_tokens, max_cost_usd }) => {
8
+ if (action === 'get') return { max_tokens: getConfigValue('budget.max_tokens'), max_cost_usd: getConfigValue('budget.max_cost_usd') }
9
+ if (action === 'set') { if (typeof max_tokens === 'number') saveConfigValue('budget.max_tokens', max_tokens); if (typeof max_cost_usd === 'number') saveConfigValue('budget.max_cost_usd', max_cost_usd); return { saved: true } }
10
+ return { error: 'unknown action' }
11
+ },
12
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-budget_config', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,27 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFreddieHome } from '../../src/home.js'
4
+ function dir() { const d = path.join(getFreddieHome(), 'checkpoints'); fs.mkdirSync(d, { recursive: true }); return d }
5
+
6
+ const ACTIONS = {
7
+ save: ({ name, data }) => {
8
+ if (!name) return { error: 'name required' }
9
+ const p = path.join(dir(), name + '.json')
10
+ fs.writeFileSync(p, JSON.stringify(data || {}, null, 2), 'utf8')
11
+ return { saved: p }
12
+ },
13
+ load: ({ name }) => {
14
+ const p = path.join(dir(), name + '.json')
15
+ if (!fs.existsSync(p)) return { error: 'not found' }
16
+ return { data: JSON.parse(fs.readFileSync(p, 'utf8')) }
17
+ },
18
+ list: () => ({ items: fs.readdirSync(dir()).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, '')) }),
19
+ delete: ({ name }) => { const p = path.join(dir(), name + '.json'); if (fs.existsSync(p)) fs.unlinkSync(p); return { deleted: name } },
20
+ }
21
+
22
+ export const _tool = ({
23
+ name: 'checkpoint',
24
+ toolset: 'core',
25
+ schema: { name: 'checkpoint', description: 'Save/load/list/delete named JSON checkpoints under ~/.freddie/checkpoints.', parameters: { type: 'object', properties: { action: { type: 'string', enum: Object.keys(ACTIONS) }, name: { type: 'string' }, data: {} }, required: ['action'] } },
26
+ handler: async (args) => { const fn = ACTIONS[args.action]; return fn ? fn(args) : { error: 'unknown action' } },
27
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-checkpoint', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,13 @@
1
+ export const _tool = ({
2
+ name: 'clarify',
3
+ toolset: 'core',
4
+ schema: {
5
+ name: 'clarify',
6
+ description: 'Pose a clarifying question to the user before proceeding. Returns the user response. In non-interactive contexts, returns { error } so the agent must proceed with stated assumption.',
7
+ parameters: { type: 'object', properties: { question: { type: 'string' }, options: { type: 'array', items: { type: 'string' } } }, required: ['question'] },
8
+ },
9
+ handler: async ({ question, options = [] }, ctx = {}) => {
10
+ if (typeof ctx.askUser === 'function') return await ctx.askUser({ question, options })
11
+ return { error: 'no interactive channel; assume defaults', question, options }
12
+ },
13
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-clarify', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,25 @@
1
+ import { spawn } from 'node:child_process'
2
+ const RUNNERS = {
3
+ python: ['python', '-c'], python3: ['python3', '-c'],
4
+ node: ['node', '-e'], deno: ['deno', 'eval'],
5
+ ruby: ['ruby', '-e'], bash: ['bash', '-c'],
6
+ }
7
+
8
+ export const _tool = ({
9
+ name: 'code_execution',
10
+ toolset: 'core',
11
+ schema: { name: 'code_execution', description: 'Execute a code snippet in a chosen runner (python, node, deno, ruby, bash). Returns stdout/stderr/exitCode.', parameters: { type: 'object', properties: { code: { type: 'string' }, runner: { type: 'string', enum: Object.keys(RUNNERS), default: 'python' }, timeout_ms: { type: 'number', default: 30000 } }, required: ['code'] } },
12
+ handler: async ({ code, runner = 'python', timeout_ms = 30000 }) => {
13
+ const cmd = RUNNERS[runner]
14
+ if (!cmd) return { error: 'unknown runner: ' + runner }
15
+ return await new Promise(resolve => {
16
+ const child = spawn(cmd[0], [cmd[1], code], { env: process.env })
17
+ let stdout = '', stderr = ''
18
+ const t = setTimeout(() => { try { child.kill('SIGKILL') } catch {} resolve({ exitCode: -1, stdout, stderr: stderr + '\n[timeout]' }) }, timeout_ms)
19
+ child.stdout?.on('data', d => stdout += d.toString())
20
+ child.stderr?.on('data', d => stderr += d.toString())
21
+ child.on('close', code => { clearTimeout(t); resolve({ exitCode: code, stdout, stderr }) })
22
+ child.on('error', e => { clearTimeout(t); resolve({ exitCode: -1, stdout, stderr: stderr + '\n' + e.message }) })
23
+ })
24
+ },
25
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-code_execution', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,8 @@
1
+ import { runTurn, createAgentMachine } from '../../src/agent/machine.js'
2
+ export default {
3
+ name: 'core-agent-machine', surfaces: 'pi',
4
+ register({ pi }) {
5
+ pi.agentExts.register({ name: 'runTurn', fn: runTurn })
6
+ pi.agentExts.register({ name: 'createAgentMachine', fn: createAgentMachine })
7
+ },
8
+ }
@@ -0,0 +1,83 @@
1
+ import { listAllProfiles, createProfile, deleteProfile, switchProfile } from '../../src/commands/profile.js'
2
+ import { listSkills } from '../../src/skills/index.js'
3
+ import { Gateway } from '../../src/gateway/run.js'
4
+ import { makePlatform } from '../../src/gateway/platforms.js'
5
+ import { AcpServer } from '../../src/acp/server.js'
6
+ import { COMMANDS_BY_CATEGORY } from '../../src/commands/registry.js'
7
+ import { getActiveSkin, listBuiltinSkins, setActiveSkin } from '../../src/skin/engine.js'
8
+ import { listSessions, search } from '../../src/sessions.js'
9
+
10
+ export default {
11
+ name: 'core-cli', surfaces: 'pi',
12
+ register({ pi, host }) {
13
+ const C = pi.cli.register.bind(pi.cli)
14
+ C({ name: 'tools', description: 'List/inspect tools', args: [{ name: 'action', default: 'list' }, { name: 'name' }], action: async (action, name) => {
15
+ if (action === 'get' && name) { console.log(JSON.stringify(host.pi.tools.get(name)?.schema, null, 2)); return }
16
+ for (const t of host.pi.tools.list()) console.log(`${(t.toolset || 'core').padEnd(10)} ${t.name}\t${(t.schema?.description || '').slice(0, 60)}`)
17
+ } })
18
+ C({ name: 'skills', description: 'List skills', args: [{ name: 'action', default: 'list' }], action: () => { for (const s of listSkills()) console.log(`${s.name}\t${(s.description || '').slice(0, 80)}`) } })
19
+ C({ name: 'profile', description: 'Manage profiles', args: [{ name: 'action', default: 'list' }, { name: 'name' }], action: (action, name) => {
20
+ if (action === 'list') { for (const p of listAllProfiles()) console.log(p); return }
21
+ if (action === 'create') { createProfile(name); console.log('created:', name); return }
22
+ if (action === 'delete') { deleteProfile(name); console.log('deleted:', name); return }
23
+ if (action === 'switch') { switchProfile(name); console.log('switched:', name || 'default'); return }
24
+ } })
25
+ C({ name: 'skin', description: 'Switch UI skin', args: [{ name: 'name' }], action: (name) => {
26
+ if (!name) { console.log('active:', getActiveSkin().name); console.log('available:', listBuiltinSkins().join(', ')); return }
27
+ setActiveSkin(name); console.log('switched to:', name)
28
+ } })
29
+ C({ name: 'sessions', description: 'List recent sessions', action: async () => { for (const s of await listSessions()) console.log(`${s.id}\t${s.platform}\t${new Date(s.updated_at).toISOString()}\t${s.title || ''}`) } })
30
+ C({ name: 'search', description: 'FTS search across messages', args: [{ name: 'query', required: true }], action: async (q) => { for (const r of await search(q)) console.log(`${r.session_id}\t${(r.content || '').slice(0, 100)}`) } })
31
+ C({ name: 'gateway', description: 'Start messaging gateway', options: [{ flag: '--port <port>', default: '0' }], action: async (opts) => {
32
+ const webhook = await makePlatform('webhook', { port: Number(opts.port) })
33
+ const api = await makePlatform('api_server', { port: 0 })
34
+ const gw = new Gateway({ platforms: { webhook, api_server: api } })
35
+ await gw.start()
36
+ console.log('webhook port:', webhook.port, '\napi_server port:', api.port)
37
+ process.on('SIGINT', async () => { await gw.stop(); process.exit(0) })
38
+ } })
39
+ C({ name: 'acp', description: 'Start ACP json-rpc stdio server', action: () => { new AcpServer().start() } })
40
+ C({ name: 'help-all', description: 'Print all slash commands', action: () => {
41
+ for (const [cat, cmds] of Object.entries(COMMANDS_BY_CATEGORY)) {
42
+ console.log(`\n# ${cat}`)
43
+ for (const c of cmds) console.log(` /${c.name}${c.args_hint ? ' ' + c.args_hint : ''}\t${c.description}`)
44
+ }
45
+ } })
46
+ C({ name: 'run', description: 'Interactive REPL', action: async () => {
47
+ const { interactive } = await import('../../src/cli/interactive.js')
48
+ let callLLM = null
49
+ try { ({ callLLM } = await import('../../src/agent/pi-bridge.js')) } catch {}
50
+ await interactive({ callLLM })
51
+ } })
52
+ C({ name: 'exec', description: 'Run a single prompt through the agent and exit', options: [{ flag: '--prompt <prompt>', required: true }, { flag: '--model <model>', default: '' }, { flag: '--timeout <ms>', default: '60000' }], action: async (opts) => {
53
+ const { runTurn } = await import('../../src/agent/machine.js')
54
+ const { callLLM } = await import('../../src/agent/acptoapi-bridge.js')
55
+ const out = await runTurn({ prompt: opts.prompt, callLLM, model: opts.model || undefined, timeoutMs: Number(opts.timeout) })
56
+ if (out.error) { console.error('error:', out.error); process.exit(1) }
57
+ console.log(out.result || out.messages?.at(-1)?.content || '')
58
+ process.exit(0)
59
+ } })
60
+ C({ name: 'cron', description: 'Manage cron jobs', args: [{ name: 'action', default: 'list' }, { name: 'a1' }, { name: 'a2' }], action: async (action, a1, a2) => {
61
+ const { listJobs, createJob, cancelJob, deleteJob, tick } = await import('../../src/cron/scheduler.js')
62
+ if (action === 'list') { for (const j of await listJobs()) console.log(`${j.id}\t${j.cron}\t${j.enabled ? 'on ' : 'off'}\t${j.prompt.slice(0, 60)}`); return }
63
+ if (action === 'add') { console.log('created:', await createJob({ cron: a1, prompt: a2 })); return }
64
+ if (action === 'cancel') { await cancelJob(Number(a1)); console.log('cancelled:', a1); return }
65
+ if (action === 'delete') { await deleteJob(Number(a1)); console.log('deleted:', a1); return }
66
+ if (action === 'tick') { console.log('fired:', (await tick()).length); return }
67
+ } })
68
+ C({ name: 'batch', description: 'Run prompts in parallel from file', args: [{ name: 'file', required: true }], options: [{ flag: '--concurrency <n>', default: '4' }, { flag: '--model <model>', default: '' }], action: async (file, opts) => {
69
+ const fs = await import('node:fs')
70
+ const { runBatch } = await import('../../src/batch.js')
71
+ const raw = fs.readFileSync(file, 'utf8').trim().split('\n')
72
+ const prompts = raw.map(l => { try { return JSON.parse(l).prompt || JSON.parse(l) } catch { return l } }).filter(Boolean)
73
+ const out = await runBatch({ prompts, concurrency: Number(opts.concurrency), model: opts.model })
74
+ console.log('batch:', out.id, '\nfile:', out.file, '\nresults:', out.results.length)
75
+ } })
76
+ C({ name: 'dashboard', description: 'Boot web dashboard', options: [{ flag: '--port <port>', default: '0' }], action: async (opts) => {
77
+ const { createDashboard } = await import('../../src/web/server.js')
78
+ const d = await createDashboard({ port: Number(opts.port) })
79
+ console.log('dashboard:', d.url)
80
+ process.on('SIGINT', async () => { await d.stop(); process.exit(0) })
81
+ } })
82
+ },
83
+ }
@@ -0,0 +1,7 @@
1
+ import { COMMAND_REGISTRY, COMMANDS_BY_CATEGORY } from '../../src/commands/registry.js'
2
+ export default {
3
+ name: 'core-commands', surfaces: 'pi',
4
+ register({ pi }) {
5
+ for (const c of COMMAND_REGISTRY) pi.commands.register({ name: c.name, description: c.description, category: c.category, aliases: c.aliases, args_hint: c.args_hint })
6
+ },
7
+ }
@@ -0,0 +1,15 @@
1
+ import { compress, shouldCompress, computeCompressionPlan } from '../../src/agent/compress/index.js'
2
+ export default {
3
+ name: 'core-compressor', surfaces: 'pi', requires: ['core-agent-machine'],
4
+ register({ pi, hooks }) {
5
+ pi.agentExts.register({ name: 'compress', fn: compress })
6
+ pi.agentExts.register({ name: 'shouldCompress', fn: shouldCompress })
7
+ hooks.on('preLlmCall', async (payload) => {
8
+ if (payload && shouldCompress(payload.messages || [])) {
9
+ const plan = computeCompressionPlan(payload.messages)
10
+ if (plan?.compressed) return { ...payload, messages: plan.compressed }
11
+ }
12
+ return payload
13
+ })
14
+ },
15
+ }
@@ -0,0 +1,7 @@
1
+ import { buildContext, blocksToSystemMessage, ContextPlugins } from '../../src/context/engine.js'
2
+ export default {
3
+ name: 'core-context-engine', surfaces: 'pi',
4
+ register({ pi }) {
5
+ pi.contexts.register({ name: 'engine', build: buildContext, render: blocksToSystemMessage, plugins: ContextPlugins })
6
+ },
7
+ }
@@ -0,0 +1,7 @@
1
+ import { listJobs, createJob, cancelJob, deleteJob, tick, startScheduler, stopScheduler } from '../../src/cron/scheduler.js'
2
+ export default {
3
+ name: 'core-cron', surfaces: 'pi',
4
+ register({ pi }) {
5
+ pi.crons.register({ name: 'scheduler', list: listJobs, create: createJob, cancel: cancelJob, delete: deleteJob, tick, start: startScheduler, stop: stopScheduler })
6
+ },
7
+ }
@@ -0,0 +1,7 @@
1
+ import { listSkills } from '../../src/skills/index.js'
2
+ export default {
3
+ name: 'core-skills', surfaces: 'pi',
4
+ register({ pi }) {
5
+ for (const s of listSkills()) pi.skills.register({ name: s.name, description: s.description, path: s.path, body: s.body })
6
+ },
7
+ }
@@ -0,0 +1,14 @@
1
+ import { getAuthStore } from '../../src/auth.js'
2
+ export const _tool = ({
3
+ name: 'credential_files',
4
+ toolset: 'core',
5
+ schema: { name: 'credential_files', description: 'Get/set credentials in ~/.freddie/auth/.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['get', 'set', 'list', 'delete'] }, name: { type: 'string' }, value: {} }, required: ['action'] } },
6
+ handler: async ({ action, name, value }) => {
7
+ const s = getAuthStore()
8
+ if (action === 'get') return { credential: await s.getCredential(name) }
9
+ if (action === 'set') return await s.setCredential(name, value)
10
+ if (action === 'list') return { credentials: await s.listCredentials() }
11
+ if (action === 'delete') return await s.deleteCredential(name)
12
+ return { error: 'unknown action' }
13
+ },
14
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-credential_files', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,14 @@
1
+ import { createJob, listJobs, cancelJob, deleteJob } from '../../src/cron/scheduler.js'
2
+ const ACTIONS = {
3
+ add: async ({ cron, prompt, model = null }) => ({ id: await createJob({ cron, prompt, model }) }),
4
+ list: async () => ({ jobs: await listJobs() }),
5
+ cancel: async ({ id }) => { await cancelJob(id); return { id, cancelled: true } },
6
+ delete: async ({ id }) => { await deleteJob(id); return { id, deleted: true } },
7
+ }
8
+
9
+ export const _tool = ({
10
+ name: 'cronjob',
11
+ toolset: 'core',
12
+ schema: { name: 'cronjob', description: 'Manage agent cron jobs (add, list, cancel, delete).', parameters: { type: 'object', properties: { action: { type: 'string', enum: Object.keys(ACTIONS) }, cron: { type: 'string' }, prompt: { type: 'string' }, model: { type: 'string' }, id: { type: 'number' } }, required: ['action'] } },
13
+ handler: async (args) => { const fn = ACTIONS[args.action]; if (!fn) return { error: 'unknown action' }; try { return await fn(args) } catch (e) { return { error: String(e.message || e) } } },
14
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-cronjob', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,8 @@
1
+ import { snapshotAll, listDebug } from '../../src/observability/debug.js'
2
+
3
+ export const _tool = ({
4
+ name: 'debug_helpers',
5
+ toolset: 'core',
6
+ schema: { name: 'debug_helpers', description: 'Inspect any registered /debug subsystem.', parameters: { type: 'object', properties: { name: { type: 'string' } } } },
7
+ handler: async ({ name }) => name ? snapshotAll()[name] || { error: 'unknown subsystem: ' + name } : { subsystems: listDebug() },
8
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-debug_helpers', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,27 @@
1
+ import { runTurn } from '../../src/agent/machine.js'
2
+
3
+ const MAX_DEPTH = 3
4
+
5
+ export const _tool = ({
6
+ name: 'delegate',
7
+ toolset: 'core',
8
+ schema: {
9
+ name: 'delegate',
10
+ description: 'Spawn a sub-agent to handle a focused task. Returns the sub-agent final result.',
11
+ parameters: {
12
+ type: 'object',
13
+ properties: {
14
+ task: { type: 'string' },
15
+ model: { type: 'string' },
16
+ max_iterations: { type: 'number', default: 30 },
17
+ },
18
+ required: ['task'],
19
+ },
20
+ },
21
+ handler: async ({ task, model, max_iterations = 30 }, ctx = {}) => {
22
+ const depth = (ctx.depth || 0) + 1
23
+ if (depth > MAX_DEPTH) return { error: `delegate recursion depth exceeded (${MAX_DEPTH})` }
24
+ const out = await runTurn({ prompt: task, model, callLLM: ctx.callLLM, maxIterations: max_iterations, timeoutMs: 60000 })
25
+ return { result: out.result, error: out.error, iterations: out.iterations, depth }
26
+ },
27
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-delegate', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,12 @@
1
+ export const _tool = ({
2
+ name: 'discord_tool',
3
+ toolset: 'core',
4
+ schema: { name: 'discord_tool', description: 'Send a message to a Discord channel via REST.', parameters: { type: 'object', properties: { channel_id: { type: 'string' }, content: { type: 'string' } }, required: ['channel_id', 'content'] } },
5
+ requiresEnv: ['DISCORD_BOT_TOKEN'],
6
+ checkFn: () => Boolean(process.env.DISCORD_BOT_TOKEN),
7
+ handler: async ({ channel_id, content }) => {
8
+ if (!process.env.DISCORD_BOT_TOKEN) return { error: 'DISCORD_BOT_TOKEN required' }
9
+ const r = await fetch(`https://discord.com/api/v10/channels/${channel_id}/messages`, { method: 'POST', headers: { authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}`, 'content-type': 'application/json' }, body: JSON.stringify({ content }) })
10
+ return await r.json()
11
+ },
12
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-discord_tool', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,29 @@
1
+ import fs from 'node:fs'
2
+ export const _tool = ({
3
+ name: 'edit',
4
+ toolset: 'core',
5
+ schema: {
6
+ name: 'edit',
7
+ description: 'Replace exact string in file. Fails if old_string occurs zero or multiple times unless replace_all.',
8
+ parameters: {
9
+ type: 'object',
10
+ properties: {
11
+ path: { type: 'string' },
12
+ old_string: { type: 'string' },
13
+ new_string: { type: 'string' },
14
+ replace_all: { type: 'boolean', default: false },
15
+ },
16
+ required: ['path', 'old_string', 'new_string'],
17
+ },
18
+ },
19
+ handler: async ({ path: p, old_string, new_string, replace_all = false }) => {
20
+ if (!fs.existsSync(p)) return { error: `not found: ${p}` }
21
+ const src = fs.readFileSync(p, 'utf8')
22
+ const occurrences = src.split(old_string).length - 1
23
+ if (occurrences === 0) return { error: 'old_string not found' }
24
+ if (occurrences > 1 && !replace_all) return { error: `old_string matches ${occurrences} times; pass replace_all=true` }
25
+ const out = replace_all ? src.split(old_string).join(new_string) : src.replace(old_string, new_string)
26
+ fs.writeFileSync(p, out, 'utf8')
27
+ return { path: p, replacements: replace_all ? occurrences : 1 }
28
+ },
29
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-edit', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,14 @@
1
+ import { getConfigValue } from '../../src/config.js'
2
+
3
+ export function buildBashEnv() {
4
+ const allow = getConfigValue('terminal.env_passthrough', ['HOME', 'USER', 'LANG', 'PATH', 'SHELL']) || []
5
+ const out = {}
6
+ for (const k of allow) if (process.env[k]) out[k] = process.env[k]
7
+ return out
8
+ }
9
+ export const _tool = ({
10
+ name: 'env_passthrough',
11
+ toolset: 'core',
12
+ schema: { name: 'env_passthrough', description: 'Compute the env-var subset that should be passed through to spawned shells.', parameters: { type: 'object', properties: {} } },
13
+ handler: async () => ({ env: buildBashEnv() }),
14
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-env_passthrough', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,14 @@
1
+ export const _tool = ({
2
+ name: 'feishu_doc',
3
+ toolset: 'core',
4
+ schema: { name: 'feishu_doc', description: 'Read or update a Feishu doc by token.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['get', 'patch'] }, doc_token: { type: 'string' }, content: {} }, required: ['action', 'doc_token'] } },
5
+ requiresEnv: ['FEISHU_APP_TOKEN'],
6
+ checkFn: () => Boolean(process.env.FEISHU_APP_TOKEN),
7
+ handler: async ({ action, doc_token, content }) => {
8
+ const auth = { authorization: `Bearer ${process.env.FEISHU_APP_TOKEN}` }
9
+ const base = 'https://open.feishu.cn/open-apis/docx/v1/documents/' + doc_token
10
+ if (action === 'get') return await (await fetch(base, { headers: auth })).json()
11
+ if (action === 'patch') return await (await fetch(base + '/blocks', { method: 'PATCH', headers: { ...auth, 'content-type': 'application/json' }, body: JSON.stringify(content) })).json()
12
+ return { error: 'unknown action' }
13
+ },
14
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-feishu_doc', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }