freddie 0.0.41 → 0.0.42

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 (152) hide show
  1. package/AGENTS.md +85 -11
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +2 -2
  4. package/bin/freddie.js +12 -109
  5. package/package.json +11 -2
  6. package/src/acp/server.js +3 -3
  7. package/src/acp/session.js +8 -8
  8. package/src/acp/tools.js +5 -4
  9. package/src/agent/account_usage.js +5 -5
  10. package/src/agent/credential_sources.js +2 -2
  11. package/src/agent/curator.js +5 -5
  12. package/src/agent/machine.js +3 -2
  13. package/src/agent/manual_compression_feedback.js +5 -5
  14. package/src/agent/shell_hooks.js +2 -2
  15. package/src/auth.js +2 -2
  16. package/src/batch.js +2 -2
  17. package/src/cli/backup.js +3 -3
  18. package/src/cli/doctor.js +3 -3
  19. package/src/cli/dump.js +4 -2
  20. package/src/cli/env_loader.js +2 -2
  21. package/src/cli/gateway_cli.js +3 -4
  22. package/src/cli/hooks.js +2 -2
  23. package/src/cli/logs.js +4 -4
  24. package/src/cli/mcp_config.js +2 -2
  25. package/src/cli/plugins_cmd.js +3 -3
  26. package/src/cli/status.js +1 -1
  27. package/src/cli/tools_config.js +2 -2
  28. package/src/cli/uninstall.js +2 -2
  29. package/src/config.js +2 -2
  30. package/src/db.js +3 -3
  31. package/src/gateway/platforms.js +21 -0
  32. package/src/home.js +2 -2
  33. package/src/host/contract.js +39 -0
  34. package/src/host/host.js +159 -0
  35. package/src/host/index.js +27 -0
  36. package/src/index.js +2 -1
  37. package/src/mcp/server.js +5 -4
  38. package/src/observability/log.js +2 -2
  39. package/src/plugins/disk_cleanup/index.js +2 -2
  40. package/src/plugins/manager.js +13 -63
  41. package/src/plugins/memory/provider.js +26 -26
  42. package/src/plugins/observability/index.js +3 -3
  43. package/src/skills/index.js +2 -2
  44. package/src/skin/engine.js +2 -2
  45. package/src/toolsets.js +13 -15
  46. package/src/web/index.html +1 -1
  47. package/src/web/server.js +8 -94
  48. package/src/gateway/platforms/api_server.js +0 -21
  49. package/src/gateway/platforms/bluebubbles.js +0 -32
  50. package/src/gateway/platforms/dingtalk.js +0 -32
  51. package/src/gateway/platforms/discord.js +0 -24
  52. package/src/gateway/platforms/email.js +0 -51
  53. package/src/gateway/platforms/feishu.js +0 -32
  54. package/src/gateway/platforms/feishu_comment.js +0 -12
  55. package/src/gateway/platforms/feishu_comment_rules.js +0 -11
  56. package/src/gateway/platforms/homeassistant.js +0 -32
  57. package/src/gateway/platforms/matrix.js +0 -40
  58. package/src/gateway/platforms/mattermost.js +0 -29
  59. package/src/gateway/platforms/qqbot.js +0 -32
  60. package/src/gateway/platforms/signal.js +0 -33
  61. package/src/gateway/platforms/slack.js +0 -34
  62. package/src/gateway/platforms/sms.js +0 -34
  63. package/src/gateway/platforms/telegram.js +0 -38
  64. package/src/gateway/platforms/telegram_network.js +0 -17
  65. package/src/gateway/platforms/webhook.js +0 -19
  66. package/src/gateway/platforms/wecom.js +0 -32
  67. package/src/gateway/platforms/wecom_callback.js +0 -15
  68. package/src/gateway/platforms/wecom_crypto.js +0 -16
  69. package/src/gateway/platforms/weixin.js +0 -32
  70. package/src/gateway/platforms/whatsapp.js +0 -40
  71. package/src/gateway/platforms/yuanbao.js +0 -9
  72. package/src/gateway/platforms/yuanbao_media.js +0 -5
  73. package/src/gateway/platforms/yuanbao_proto.js +0 -9
  74. package/src/gateway/platforms/yuanbao_sticker.js +0 -6
  75. package/src/plugins/memory/_index.js +0 -8
  76. package/src/plugins/memory/byterover.js +0 -25
  77. package/src/plugins/memory/hindsight.js +0 -25
  78. package/src/plugins/memory/holographic.js +0 -31
  79. package/src/plugins/memory/honcho.js +0 -25
  80. package/src/plugins/memory/mem0.js +0 -25
  81. package/src/plugins/memory/openviking.js +0 -25
  82. package/src/plugins/memory/retaindb.js +0 -25
  83. package/src/plugins/memory/supermemory.js +0 -25
  84. package/src/tools/ansi_strip.js +0 -8
  85. package/src/tools/approval.js +0 -15
  86. package/src/tools/bash.js +0 -35
  87. package/src/tools/binary_extensions.js +0 -22
  88. package/src/tools/browser.js +0 -48
  89. package/src/tools/budget_config.js +0 -13
  90. package/src/tools/checkpoint.js +0 -29
  91. package/src/tools/clarify.js +0 -15
  92. package/src/tools/code_execution.js +0 -27
  93. package/src/tools/credential_files.js +0 -16
  94. package/src/tools/cronjob.js +0 -16
  95. package/src/tools/debug_helpers.js +0 -9
  96. package/src/tools/delegate.js +0 -28
  97. package/src/tools/discord_tool.js +0 -13
  98. package/src/tools/edit.js +0 -31
  99. package/src/tools/env_passthrough.js +0 -15
  100. package/src/tools/feishu_doc.js +0 -15
  101. package/src/tools/feishu_drive.js +0 -14
  102. package/src/tools/file_operations.js +0 -17
  103. package/src/tools/file_state.js +0 -16
  104. package/src/tools/file_tools.js +0 -23
  105. package/src/tools/fuzzy_match.js +0 -8
  106. package/src/tools/grep.js +0 -51
  107. package/src/tools/homeassistant_tool.js +0 -15
  108. package/src/tools/image_gen.js +0 -33
  109. package/src/tools/interrupt.js +0 -18
  110. package/src/tools/managed_tool_gateway.js +0 -11
  111. package/src/tools/mcp_oauth.js +0 -21
  112. package/src/tools/mcp_oauth_manager.js +0 -20
  113. package/src/tools/mcp_tool.js +0 -36
  114. package/src/tools/memory.js +0 -66
  115. package/src/tools/mixture_of_agents.js +0 -14
  116. package/src/tools/neutts_synth.js +0 -13
  117. package/src/tools/openrouter_client.js +0 -13
  118. package/src/tools/osv_check.js +0 -11
  119. package/src/tools/patch_parser.js +0 -42
  120. package/src/tools/path_security.js +0 -16
  121. package/src/tools/process_registry.js +0 -17
  122. package/src/tools/read.js +0 -26
  123. package/src/tools/registry.js +0 -54
  124. package/src/tools/rl_training.js +0 -13
  125. package/src/tools/schema_sanitizer.js +0 -18
  126. package/src/tools/send_message.js +0 -32
  127. package/src/tools/session_search.js +0 -23
  128. package/src/tools/skill_manager.js +0 -17
  129. package/src/tools/skill_usage.js +0 -20
  130. package/src/tools/skills_guard.js +0 -17
  131. package/src/tools/skills_hub.js +0 -31
  132. package/src/tools/skills_index.js +0 -14
  133. package/src/tools/skills_sync.js +0 -19
  134. package/src/tools/skills_tool.js +0 -11
  135. package/src/tools/slash_confirm.js +0 -16
  136. package/src/tools/terminal.js +0 -29
  137. package/src/tools/tirith_security.js +0 -25
  138. package/src/tools/todo.js +0 -54
  139. package/src/tools/tool_backend_helpers.js +0 -26
  140. package/src/tools/tool_output_limits.js +0 -15
  141. package/src/tools/tool_result_storage.js +0 -20
  142. package/src/tools/transcription.js +0 -19
  143. package/src/tools/tts.js +0 -19
  144. package/src/tools/url_safety.js +0 -15
  145. package/src/tools/vision.js +0 -18
  146. package/src/tools/voice_mode.js +0 -10
  147. package/src/tools/web_search.js +0 -37
  148. package/src/tools/web_tools.js +0 -18
  149. package/src/tools/website_policy.js +0 -14
  150. package/src/tools/write.js +0 -25
  151. package/src/tools/xai_http.js +0 -13
  152. package/src/tools/yuanbao_tools.js +0 -13
@@ -1,15 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- registry.register({
4
- name: 'approval',
5
- toolset: 'core',
6
- schema: {
7
- name: 'approval',
8
- 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.',
9
- parameters: { type: 'object', properties: { action: { type: 'string' }, reason: { type: 'string' } }, required: ['action'] },
10
- },
11
- handler: async ({ action, reason = '' }, ctx = {}) => {
12
- if (typeof ctx.askApproval === 'function') return await ctx.askApproval({ action, reason })
13
- return { decision: 'deny', reason: 'no interactive approval channel; supply ctx.askApproval' }
14
- },
15
- })
package/src/tools/bash.js DELETED
@@ -1,35 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import { registry } from './registry.js'
3
-
4
- registry.register({
5
- name: 'bash',
6
- toolset: 'core',
7
- schema: {
8
- name: 'bash',
9
- description: 'Run a shell command. Returns stdout/stderr/exitCode.',
10
- parameters: {
11
- type: 'object',
12
- properties: {
13
- command: { type: 'string', description: 'Shell command to execute' },
14
- cwd: { type: 'string', description: 'Working directory' },
15
- timeout_ms: { type: 'number', description: 'Hard timeout in ms', default: 60000 },
16
- background: { type: 'boolean', default: false },
17
- },
18
- required: ['command'],
19
- },
20
- },
21
- handler: async (args) => {
22
- const { command, cwd = process.cwd(), timeout_ms = 60000 } = args
23
- return await new Promise((resolve) => {
24
- const sh = process.platform === 'win32' ? 'cmd' : 'sh'
25
- const flag = process.platform === 'win32' ? '/c' : '-c'
26
- const child = spawn(sh, [flag, command], { cwd, env: process.env })
27
- let stdout = '', stderr = ''
28
- const t = setTimeout(() => { try { child.kill('SIGKILL') } catch {} resolve({ exitCode: -1, stdout, stderr: stderr + '\n[timeout]', timedOut: true }) }, timeout_ms)
29
- child.stdout?.on('data', d => stdout += d.toString())
30
- child.stderr?.on('data', d => stderr += d.toString())
31
- child.on('close', code => { clearTimeout(t); resolve({ exitCode: code, stdout, stderr }) })
32
- child.on('error', e => { clearTimeout(t); resolve({ exitCode: -1, stdout, stderr: stderr + '\n' + e.message }) })
33
- })
34
- },
35
- })
@@ -1,22 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- export const BINARY_EXTENSIONS = new Set([
4
- '.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.ico', '.tiff',
5
- '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
6
- '.zip', '.tar', '.gz', '.bz2', '.xz', '.7z', '.rar',
7
- '.exe', '.dll', '.so', '.dylib', '.bin',
8
- '.mp3', '.mp4', '.mov', '.avi', '.flac', '.ogg',
9
- '.ttf', '.otf', '.woff', '.woff2',
10
- '.wasm', '.class', '.jar',
11
- ])
12
- export function isBinary(filename) {
13
- const lower = String(filename).toLowerCase()
14
- for (const ext of BINARY_EXTENSIONS) if (lower.endsWith(ext)) return true
15
- return false
16
- }
17
- registry.register({
18
- name: 'binary_extensions',
19
- toolset: 'core',
20
- schema: { name: 'binary_extensions', description: 'Check whether a filename has a known binary extension.', parameters: { type: 'object', properties: { filename: { type: 'string' } }, required: ['filename'] } },
21
- handler: async ({ filename }) => ({ binary: isBinary(filename), known: BINARY_EXTENSIONS.size }),
22
- })
@@ -1,48 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- let _puppeteerAvail = null
4
- async function probe() {
5
- if (_puppeteerAvail !== null) return _puppeteerAvail
6
- try { await import('puppeteer-core'); _puppeteerAvail = true } catch { _puppeteerAvail = false }
7
- return _puppeteerAvail
8
- }
9
-
10
- registry.register({
11
- name: 'browser',
12
- toolset: 'browse',
13
- schema: {
14
- name: 'browser',
15
- description: 'Browser automation: navigate, click, type, evaluate, screenshot. Requires puppeteer-core.',
16
- parameters: {
17
- type: 'object',
18
- properties: {
19
- action: { type: 'string', enum: ['navigate', 'click', 'type', 'evaluate', 'screenshot', 'text'] },
20
- url: { type: 'string' },
21
- selector: { type: 'string' },
22
- text: { type: 'string' },
23
- script: { type: 'string' },
24
- path: { type: 'string' },
25
- },
26
- required: ['action'],
27
- },
28
- },
29
- checkFn: () => true,
30
- requiresEnv: ['puppeteer-core'],
31
- handler: async (args) => {
32
- const ok = await probe()
33
- if (!ok) return { error: 'puppeteer-core not installed. Run: npm install puppeteer-core' }
34
- const puppeteer = (await import('puppeteer-core')).default
35
- const browser = await puppeteer.launch({ headless: 'new' })
36
- try {
37
- const page = await browser.newPage()
38
- const a = args.action
39
- if (a === 'navigate') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); return { url: page.url(), title: await page.title() } }
40
- if (a === 'click') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); await page.click(args.selector); return { ok: true } }
41
- if (a === 'type') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); await page.type(args.selector, args.text); return { ok: true } }
42
- if (a === 'evaluate') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); return { result: await page.evaluate(args.script) } }
43
- if (a === 'text') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); return { text: await page.evaluate(() => document.body.innerText) } }
44
- if (a === 'screenshot') { await page.goto(args.url, { waitUntil: 'domcontentloaded' }); await page.screenshot({ path: args.path }); return { saved: args.path } }
45
- return { error: 'unknown action: ' + a }
46
- } finally { await browser.close() }
47
- },
48
- })
@@ -1,13 +0,0 @@
1
- import { registry } from './registry.js'
2
- import { getConfigValue, saveConfigValue } from '../config.js'
3
-
4
- registry.register({
5
- name: 'budget_config',
6
- toolset: 'core',
7
- 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'] } },
8
- handler: async ({ action, max_tokens, max_cost_usd }) => {
9
- if (action === 'get') return { max_tokens: getConfigValue('budget.max_tokens'), max_cost_usd: getConfigValue('budget.max_cost_usd') }
10
- 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 } }
11
- return { error: 'unknown action' }
12
- },
13
- })
@@ -1,29 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { getFophHome } from '../home.js'
4
- import { registry } from './registry.js'
5
-
6
- function dir() { const d = path.join(getFophHome(), 'checkpoints'); fs.mkdirSync(d, { recursive: true }); return d }
7
-
8
- const ACTIONS = {
9
- save: ({ name, data }) => {
10
- if (!name) return { error: 'name required' }
11
- const p = path.join(dir(), name + '.json')
12
- fs.writeFileSync(p, JSON.stringify(data || {}, null, 2), 'utf8')
13
- return { saved: p }
14
- },
15
- load: ({ name }) => {
16
- const p = path.join(dir(), name + '.json')
17
- if (!fs.existsSync(p)) return { error: 'not found' }
18
- return { data: JSON.parse(fs.readFileSync(p, 'utf8')) }
19
- },
20
- list: () => ({ items: fs.readdirSync(dir()).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, '')) }),
21
- delete: ({ name }) => { const p = path.join(dir(), name + '.json'); if (fs.existsSync(p)) fs.unlinkSync(p); return { deleted: name } },
22
- }
23
-
24
- registry.register({
25
- name: 'checkpoint',
26
- toolset: 'core',
27
- 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'] } },
28
- handler: async (args) => { const fn = ACTIONS[args.action]; return fn ? fn(args) : { error: 'unknown action' } },
29
- })
@@ -1,15 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- registry.register({
4
- name: 'clarify',
5
- toolset: 'core',
6
- schema: {
7
- name: 'clarify',
8
- 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.',
9
- parameters: { type: 'object', properties: { question: { type: 'string' }, options: { type: 'array', items: { type: 'string' } } }, required: ['question'] },
10
- },
11
- handler: async ({ question, options = [] }, ctx = {}) => {
12
- if (typeof ctx.askUser === 'function') return await ctx.askUser({ question, options })
13
- return { error: 'no interactive channel; assume defaults', question, options }
14
- },
15
- })
@@ -1,27 +0,0 @@
1
- import { spawn } from 'node:child_process'
2
- import { registry } from './registry.js'
3
-
4
- const RUNNERS = {
5
- python: ['python', '-c'], python3: ['python3', '-c'],
6
- node: ['node', '-e'], deno: ['deno', 'eval'],
7
- ruby: ['ruby', '-e'], bash: ['bash', '-c'],
8
- }
9
-
10
- registry.register({
11
- name: 'code_execution',
12
- toolset: 'core',
13
- 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'] } },
14
- handler: async ({ code, runner = 'python', timeout_ms = 30000 }) => {
15
- const cmd = RUNNERS[runner]
16
- if (!cmd) return { error: 'unknown runner: ' + runner }
17
- return await new Promise(resolve => {
18
- const child = spawn(cmd[0], [cmd[1], code], { env: process.env })
19
- let stdout = '', stderr = ''
20
- const t = setTimeout(() => { try { child.kill('SIGKILL') } catch {} resolve({ exitCode: -1, stdout, stderr: stderr + '\n[timeout]' }) }, timeout_ms)
21
- child.stdout?.on('data', d => stdout += d.toString())
22
- child.stderr?.on('data', d => stderr += d.toString())
23
- child.on('close', code => { clearTimeout(t); resolve({ exitCode: code, stdout, stderr }) })
24
- child.on('error', e => { clearTimeout(t); resolve({ exitCode: -1, stdout, stderr: stderr + '\n' + e.message }) })
25
- })
26
- },
27
- })
@@ -1,16 +0,0 @@
1
- import { getAuthStore } from '../auth.js'
2
- import { registry } from './registry.js'
3
-
4
- registry.register({
5
- name: 'credential_files',
6
- toolset: 'core',
7
- 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'] } },
8
- handler: async ({ action, name, value }) => {
9
- const s = getAuthStore()
10
- if (action === 'get') return { credential: await s.getCredential(name) }
11
- if (action === 'set') return await s.setCredential(name, value)
12
- if (action === 'list') return { credentials: await s.listCredentials() }
13
- if (action === 'delete') return await s.deleteCredential(name)
14
- return { error: 'unknown action' }
15
- },
16
- })
@@ -1,16 +0,0 @@
1
- import { createJob, listJobs, cancelJob, deleteJob } from '../cron/scheduler.js'
2
- import { registry } from './registry.js'
3
-
4
- const ACTIONS = {
5
- add: ({ cron, prompt, model = null }) => ({ id: createJob({ cron, prompt, model }) }),
6
- list: () => ({ jobs: listJobs() }),
7
- cancel: ({ id }) => { cancelJob(id); return { id, cancelled: true } },
8
- delete: ({ id }) => { deleteJob(id); return { id, deleted: true } },
9
- }
10
-
11
- registry.register({
12
- name: 'cronjob',
13
- toolset: 'core',
14
- 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'] } },
15
- handler: async (args) => { const fn = ACTIONS[args.action]; if (!fn) return { error: 'unknown action' }; try { return fn(args) } catch (e) { return { error: String(e.message || e) } } },
16
- })
@@ -1,9 +0,0 @@
1
- import { registry } from './registry.js'
2
- import { snapshotAll, listDebug } from '../observability/debug.js'
3
-
4
- registry.register({
5
- name: 'debug_helpers',
6
- toolset: 'core',
7
- schema: { name: 'debug_helpers', description: 'Inspect any registered /debug subsystem.', parameters: { type: 'object', properties: { name: { type: 'string' } } } },
8
- handler: async ({ name }) => name ? snapshotAll()[name] || { error: 'unknown subsystem: ' + name } : { subsystems: listDebug() },
9
- })
@@ -1,28 +0,0 @@
1
- import { registry } from './registry.js'
2
- import { runTurn } from '../agent/machine.js'
3
-
4
- const MAX_DEPTH = 3
5
-
6
- registry.register({
7
- name: 'delegate',
8
- toolset: 'core',
9
- schema: {
10
- name: 'delegate',
11
- description: 'Spawn a sub-agent to handle a focused task. Returns the sub-agent final result.',
12
- parameters: {
13
- type: 'object',
14
- properties: {
15
- task: { type: 'string' },
16
- model: { type: 'string' },
17
- max_iterations: { type: 'number', default: 30 },
18
- },
19
- required: ['task'],
20
- },
21
- },
22
- handler: async ({ task, model, max_iterations = 30 }, ctx = {}) => {
23
- const depth = (ctx.depth || 0) + 1
24
- if (depth > MAX_DEPTH) return { error: `delegate recursion depth exceeded (${MAX_DEPTH})` }
25
- const out = await runTurn({ prompt: task, model, callLLM: ctx.callLLM, maxIterations: max_iterations, timeoutMs: 60000 })
26
- return { result: out.result, error: out.error, iterations: out.iterations, depth }
27
- },
28
- })
@@ -1,13 +0,0 @@
1
- import { registry } from './registry.js'
2
- registry.register({
3
- name: 'discord_tool',
4
- toolset: 'core',
5
- 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'] } },
6
- requiresEnv: ['DISCORD_BOT_TOKEN'],
7
- checkFn: () => Boolean(process.env.DISCORD_BOT_TOKEN),
8
- handler: async ({ channel_id, content }) => {
9
- if (!process.env.DISCORD_BOT_TOKEN) return { error: 'DISCORD_BOT_TOKEN required' }
10
- 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 }) })
11
- return await r.json()
12
- },
13
- })
package/src/tools/edit.js DELETED
@@ -1,31 +0,0 @@
1
- import fs from 'node:fs'
2
- import { registry } from './registry.js'
3
-
4
- registry.register({
5
- name: 'edit',
6
- toolset: 'core',
7
- schema: {
8
- name: 'edit',
9
- description: 'Replace exact string in file. Fails if old_string occurs zero or multiple times unless replace_all.',
10
- parameters: {
11
- type: 'object',
12
- properties: {
13
- path: { type: 'string' },
14
- old_string: { type: 'string' },
15
- new_string: { type: 'string' },
16
- replace_all: { type: 'boolean', default: false },
17
- },
18
- required: ['path', 'old_string', 'new_string'],
19
- },
20
- },
21
- handler: async ({ path: p, old_string, new_string, replace_all = false }) => {
22
- if (!fs.existsSync(p)) return { error: `not found: ${p}` }
23
- const src = fs.readFileSync(p, 'utf8')
24
- const occurrences = src.split(old_string).length - 1
25
- if (occurrences === 0) return { error: 'old_string not found' }
26
- if (occurrences > 1 && !replace_all) return { error: `old_string matches ${occurrences} times; pass replace_all=true` }
27
- const out = replace_all ? src.split(old_string).join(new_string) : src.replace(old_string, new_string)
28
- fs.writeFileSync(p, out, 'utf8')
29
- return { path: p, replacements: replace_all ? occurrences : 1 }
30
- },
31
- })
@@ -1,15 +0,0 @@
1
- import { registry } from './registry.js'
2
- import { getConfigValue } from '../config.js'
3
-
4
- export function buildBashEnv() {
5
- const allow = getConfigValue('terminal.env_passthrough', ['HOME', 'USER', 'LANG', 'PATH', 'SHELL']) || []
6
- const out = {}
7
- for (const k of allow) if (process.env[k]) out[k] = process.env[k]
8
- return out
9
- }
10
- registry.register({
11
- name: 'env_passthrough',
12
- toolset: 'core',
13
- schema: { name: 'env_passthrough', description: 'Compute the env-var subset that should be passed through to spawned shells.', parameters: { type: 'object', properties: {} } },
14
- handler: async () => ({ env: buildBashEnv() }),
15
- })
@@ -1,15 +0,0 @@
1
- import { registry } from './registry.js'
2
- registry.register({
3
- name: 'feishu_doc',
4
- toolset: 'core',
5
- 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'] } },
6
- requiresEnv: ['FEISHU_APP_TOKEN'],
7
- checkFn: () => Boolean(process.env.FEISHU_APP_TOKEN),
8
- handler: async ({ action, doc_token, content }) => {
9
- const auth = { authorization: `Bearer ${process.env.FEISHU_APP_TOKEN}` }
10
- const base = 'https://open.feishu.cn/open-apis/docx/v1/documents/' + doc_token
11
- if (action === 'get') return await (await fetch(base, { headers: auth })).json()
12
- if (action === 'patch') return await (await fetch(base + '/blocks', { method: 'PATCH', headers: { ...auth, 'content-type': 'application/json' }, body: JSON.stringify(content) })).json()
13
- return { error: 'unknown action' }
14
- },
15
- })
@@ -1,14 +0,0 @@
1
- import { registry } from './registry.js'
2
- registry.register({
3
- name: 'feishu_drive',
4
- toolset: 'core',
5
- schema: { name: 'feishu_drive', description: 'List or download Feishu Drive files.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['list', 'download'] }, folder_token: { type: 'string' }, file_token: { type: 'string' } }, required: ['action'] } },
6
- requiresEnv: ['FEISHU_APP_TOKEN'],
7
- checkFn: () => Boolean(process.env.FEISHU_APP_TOKEN),
8
- handler: async ({ action, folder_token, file_token }) => {
9
- const auth = { authorization: `Bearer ${process.env.FEISHU_APP_TOKEN}` }
10
- if (action === 'list') return await (await fetch('https://open.feishu.cn/open-apis/drive/v1/files?folder_token=' + (folder_token || ''), { headers: auth })).json()
11
- if (action === 'download') return await (await fetch(`https://open.feishu.cn/open-apis/drive/v1/files/${file_token}/download`, { headers: auth })).json()
12
- return { error: 'unknown action' }
13
- },
14
- })
@@ -1,17 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { registry } from './registry.js'
4
-
5
- const ACTIONS = {
6
- move: ({ src, dest }) => { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.renameSync(src, dest); return { moved: dest } },
7
- copy: ({ src, dest }) => { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.copyFileSync(src, dest); return { copied: dest } },
8
- delete: ({ path: p }) => { if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true }); return { deleted: p } },
9
- mkdir: ({ path: p }) => { fs.mkdirSync(p, { recursive: true }); return { created: p } },
10
- stat: ({ path: p }) => { const s = fs.statSync(p); return { size: s.size, mtime: s.mtimeMs, isDir: s.isDirectory() } },
11
- }
12
- registry.register({
13
- name: 'file_operations',
14
- toolset: 'core',
15
- schema: { name: 'file_operations', description: 'move/copy/delete/mkdir/stat — atomic file ops.', parameters: { type: 'object', properties: { action: { type: 'string', enum: Object.keys(ACTIONS) }, src: { type: 'string' }, dest: { type: 'string' }, path: { type: 'string' } }, required: ['action'] } },
16
- handler: async (a) => { const fn = ACTIONS[a.action]; try { return fn ? fn(a) : { error: 'unknown action' } } catch (e) { return { error: String(e.message || e) } } },
17
- })
@@ -1,16 +0,0 @@
1
- import { db } from '../db.js'
2
- import { registry } from './registry.js'
3
-
4
- async function init() { const d = await db(); d.exec(`CREATE TABLE IF NOT EXISTS file_state (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, file_path TEXT NOT NULL, action TEXT NOT NULL, ts INTEGER NOT NULL)`); return d }
5
- registry.register({
6
- name: 'file_state',
7
- toolset: 'core',
8
- schema: { name: 'file_state', description: 'Track files modified in this session (read|write|edit|delete) for diff-summary purposes.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['record', 'list', 'changed_in_session'] }, session_id: { type: 'string' }, file_path: { type: 'string' }, op: { type: 'string' } }, required: ['action'] } },
9
- handler: async ({ action, session_id, file_path, op }) => {
10
- const d = await init()
11
- if (action === 'record') { d.prepare('INSERT INTO file_state (session_id, file_path, action, ts) VALUES (?, ?, ?, ?)').run(session_id, file_path, op, Date.now()); return { recorded: true } }
12
- if (action === 'list') return { items: d.prepare('SELECT * FROM file_state WHERE session_id = ? ORDER BY id DESC').all(session_id) }
13
- if (action === 'changed_in_session') return { files: [...new Set(d.prepare('SELECT file_path FROM file_state WHERE session_id = ?').all(session_id).map(r => r.file_path))] }
14
- return { error: 'unknown action' }
15
- },
16
- })
@@ -1,23 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { registry } from './registry.js'
4
-
5
- function walk(dir, out, skip) {
6
- let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return }
7
- for (const e of entries) {
8
- if (skip.has(e.name)) continue
9
- const full = path.join(dir, e.name)
10
- if (e.isDirectory()) walk(full, out, skip)
11
- else out.push(full)
12
- }
13
- }
14
- registry.register({
15
- name: 'file_tools',
16
- toolset: 'core',
17
- schema: { name: 'file_tools', description: 'list/glob files (recursive walk skipping node_modules, .git, dist).', parameters: { type: 'object', properties: { dir: { type: 'string', default: '.' }, ext: { type: 'string' }, limit: { type: 'number', default: 1000 } } } },
18
- handler: async ({ dir = '.', ext, limit = 1000 }) => {
19
- const out = []; walk(dir, out, new Set(['node_modules', '.git', 'dist', '.cache', 'build']))
20
- const filtered = ext ? out.filter(f => f.endsWith(ext)) : out
21
- return { files: filtered.slice(0, limit), total: filtered.length, truncated: filtered.length > limit }
22
- },
23
- })
@@ -1,8 +0,0 @@
1
- import { fuzzyMatch as helper } from '../utils.js'
2
- import { registry } from './registry.js'
3
- registry.register({
4
- name: 'fuzzy_match',
5
- toolset: 'core',
6
- schema: { name: 'fuzzy_match', description: 'Score a candidate string against a needle. Returns 0 for no match, higher for better match.', parameters: { type: 'object', properties: { needle: { type: 'string' }, haystack: { type: 'string' } }, required: ['needle', 'haystack'] } },
7
- handler: async ({ needle, haystack }) => ({ score: helper(needle, haystack) }),
8
- })
package/src/tools/grep.js DELETED
@@ -1,51 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { registry } from './registry.js'
4
-
5
- registry.register({
6
- name: 'grep',
7
- toolset: 'core',
8
- schema: {
9
- name: 'grep',
10
- description: 'Recursive regex search across files. Returns file:line:content matches.',
11
- parameters: {
12
- type: 'object',
13
- properties: {
14
- pattern: { type: 'string' },
15
- path: { type: 'string', default: '.' },
16
- glob: { type: 'string' },
17
- head_limit: { type: 'number', default: 200 },
18
- ignore_case: { type: 'boolean', default: false },
19
- },
20
- required: ['pattern'],
21
- },
22
- },
23
- handler: async ({ pattern, path: root = '.', head_limit = 200, ignore_case = false, glob }) => {
24
- const re = new RegExp(pattern, ignore_case ? 'i' : '')
25
- const out = []
26
- const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', '.cache'])
27
- const walk = (d) => {
28
- if (out.length >= head_limit) return
29
- let entries
30
- try { entries = fs.readdirSync(d, { withFileTypes: true }) } catch { return }
31
- for (const e of entries) {
32
- if (out.length >= head_limit) return
33
- const full = path.join(d, e.name)
34
- if (e.isDirectory()) { if (!skipDirs.has(e.name)) walk(full); continue }
35
- if (glob && !matchGlob(e.name, glob)) continue
36
- let content
37
- try { content = fs.readFileSync(full, 'utf8') } catch { continue }
38
- content.split('\n').forEach((line, i) => {
39
- if (out.length < head_limit && re.test(line)) out.push(`${full}:${i + 1}:${line.slice(0, 200)}`)
40
- })
41
- }
42
- }
43
- walk(root)
44
- return { matches: out, total: out.length, truncated: out.length >= head_limit }
45
- },
46
- })
47
-
48
- function matchGlob(name, glob) {
49
- const re = new RegExp('^' + glob.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i')
50
- return re.test(name)
51
- }
@@ -1,15 +0,0 @@
1
- import { registry } from './registry.js'
2
- registry.register({
3
- name: 'homeassistant_tool',
4
- toolset: 'core',
5
- schema: { name: 'homeassistant_tool', description: 'Read state or call a service on Home Assistant.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['state', 'service'] }, entity_id: { type: 'string' }, domain: { type: 'string' }, service: { type: 'string' }, data: {} }, required: ['action'] } },
6
- requiresEnv: ['HASS_TOKEN', 'HASS_URL'],
7
- checkFn: () => Boolean(process.env.HASS_TOKEN),
8
- handler: async ({ action, entity_id, domain, service, data = {} }) => {
9
- const url = process.env.HASS_URL || 'http://homeassistant.local:8123'
10
- const auth = { authorization: `Bearer ${process.env.HASS_TOKEN}` }
11
- if (action === 'state') return await (await fetch(`${url}/api/states/${entity_id}`, { headers: auth })).json()
12
- if (action === 'service') return await (await fetch(`${url}/api/services/${domain}/${service}`, { method: 'POST', headers: { ...auth, 'content-type': 'application/json' }, body: JSON.stringify({ entity_id, ...data }) })).json()
13
- return { error: 'unknown action' }
14
- },
15
- })
@@ -1,33 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- registry.register({
4
- name: 'image_gen',
5
- toolset: 'creative',
6
- schema: {
7
- name: 'image_gen',
8
- description: 'Generate an image from a prompt. Provider via config.image_gen.provider (openai|replicate).',
9
- parameters: {
10
- type: 'object',
11
- properties: {
12
- prompt: { type: 'string' },
13
- provider: { type: 'string', enum: ['openai', 'replicate'] },
14
- size: { type: 'string', default: '1024x1024' },
15
- model: { type: 'string' },
16
- },
17
- required: ['prompt'],
18
- },
19
- },
20
- checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.REPLICATE_API_TOKEN),
21
- requiresEnv: ['OPENAI_API_KEY or REPLICATE_API_TOKEN'],
22
- handler: async ({ prompt, provider, size = '1024x1024', model }) => {
23
- const which = provider || (process.env.OPENAI_API_KEY ? 'openai' : 'replicate')
24
- if (which === 'openai') {
25
- if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
26
- const res = await fetch('https://api.openai.com/v1/images/generations', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model: model || 'gpt-image-1', prompt, size }) })
27
- return await res.json()
28
- }
29
- if (!process.env.REPLICATE_API_TOKEN) return { error: 'REPLICATE_API_TOKEN required' }
30
- const res = await fetch('https://api.replicate.com/v1/predictions', { method: 'POST', headers: { authorization: `Token ${process.env.REPLICATE_API_TOKEN}`, 'content-type': 'application/json' }, body: JSON.stringify({ version: model || 'black-forest-labs/flux-schnell', input: { prompt } }) })
31
- return await res.json()
32
- },
33
- })
@@ -1,18 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- const _flags = new Map()
4
- export function setInterrupt(sessionId) { _flags.set(sessionId, true) }
5
- export function isInterrupted(sessionId) { return _flags.get(sessionId) === true }
6
- export function clearInterrupt(sessionId) { _flags.delete(sessionId) }
7
-
8
- registry.register({
9
- name: 'interrupt',
10
- toolset: 'core',
11
- schema: { name: 'interrupt', description: 'Set/clear/check interrupt flag for a session — agent loop polls and exits early.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['set', 'clear', 'check'] }, session_id: { type: 'string' } }, required: ['action', 'session_id'] } },
12
- handler: async ({ action, session_id }) => {
13
- if (action === 'set') { setInterrupt(session_id); return { interrupted: true } }
14
- if (action === 'clear') { clearInterrupt(session_id); return { cleared: true } }
15
- if (action === 'check') return { interrupted: isInterrupted(session_id) }
16
- return { error: 'unknown action' }
17
- },
18
- })
@@ -1,11 +0,0 @@
1
- import { registry } from './registry.js'
2
-
3
- registry.register({
4
- name: 'managed_tool_gateway',
5
- toolset: 'core',
6
- schema: { name: 'managed_tool_gateway', description: 'Proxy: dispatch any registered tool by name with arguments. Used for tool-level audit and policy interception.', parameters: { type: 'object', properties: { name: { type: 'string' }, arguments: {} }, required: ['name'] } },
7
- handler: async ({ name, arguments: args = {} }, ctx = {}) => {
8
- if (typeof ctx.audit === 'function') ctx.audit({ name, args })
9
- return { result: await registry.dispatch(name, args, ctx) }
10
- },
11
- })
@@ -1,21 +0,0 @@
1
- import { registry } from './registry.js'
2
- registry.register({
3
- name: 'mcp_oauth',
4
- toolset: 'core',
5
- schema: { name: 'mcp_oauth', description: 'OAuth flow for an MCP server: build authorize URL, exchange code for token.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['authorize_url', 'exchange'] }, server_url: { type: 'string' }, client_id: { type: 'string' }, redirect_uri: { type: 'string' }, code: { type: 'string' }, code_verifier: { type: 'string' } }, required: ['action', 'server_url'] } },
6
- handler: async ({ action, server_url, client_id, redirect_uri, code, code_verifier }) => {
7
- if (action === 'authorize_url') {
8
- const u = new URL(server_url + '/authorize')
9
- if (client_id) u.searchParams.set('client_id', client_id)
10
- if (redirect_uri) u.searchParams.set('redirect_uri', redirect_uri)
11
- u.searchParams.set('response_type', 'code')
12
- u.searchParams.set('code_challenge_method', 'S256')
13
- return { url: u.toString() }
14
- }
15
- if (action === 'exchange') {
16
- const r = await fetch(server_url + '/token', { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri, code_verifier, client_id }).toString() })
17
- return await r.json()
18
- }
19
- return { error: 'unknown action' }
20
- },
21
- })