@jcheesepkg/nanobot 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/context.d.mts.map +1 -1
- package/dist/agent/context.mjs +7 -0
- package/dist/agent/context.mjs.map +1 -1
- package/dist/agent/tools/shell.d.mts +8 -0
- package/dist/agent/tools/shell.d.mts.map +1 -1
- package/dist/agent/tools/shell.mjs +69 -8
- package/dist/agent/tools/shell.mjs.map +1 -1
- package/dist/agent/tools/web.d.mts.map +1 -1
- package/dist/agent/tools/web.mjs +1 -0
- package/dist/agent/tools/web.mjs.map +1 -1
- package/dist/cli/index.mjs +7 -0
- package/dist/cli/index.mjs.map +1 -1
- package/dist/config/schema.d.mts +24 -24
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.mts","names":[],"sources":["../../src/agent/context.ts"],"mappings":";;;;;;;AAiBA;cAAa,cAAA;EAAA,QACH,SAAA;EAAA,SACC,MAAA,EAAQ,WAAA;EAAA,SACR,MAAA,EAAQ,YAAA;cAEL,SAAA;
|
|
1
|
+
{"version":3,"file":"context.d.mts","names":[],"sources":["../../src/agent/context.ts"],"mappings":";;;;;;;AAiBA;cAAa,cAAA;EAAA,QACH,SAAA;EAAA,SACC,MAAA,EAAQ,WAAA;EAAA,SACR,MAAA,EAAQ,YAAA;cAEL,SAAA;EAqGR;EA9FJ,iBAAA,CAAA;EAAA,QAqCQ,WAAA;EAAA,QAsCA,kBAAA;EAoGM;EAvFd,aAAA,CAAc,MAAA;IACZ,OAAA,EAAS,WAAA;IACT,cAAA;IACA,KAAA;IACA,OAAA;IACA,MAAA;EAAA,IACE,WAAA;EAAA,QAyBI,gBAAA;;EAqCR,aAAA,CACE,QAAA,EAAU,WAAA,IACV,UAAA,UACA,QAAA,UACA,MAAA,WACC,WAAA;EAjKH;EA4KA,mBAAA,CACE,QAAA,EAAU,WAAA,IACV,OAAA,iBACA,SAAA,GAAY,KAAA;IACV,EAAA;IACA,IAAA;IACA,QAAA;MAAY,IAAA;MAAc,SAAA;IAAA;EAAA,KAE3B,WAAA;AAAA"}
|
package/dist/agent/context.mjs
CHANGED
|
@@ -64,6 +64,13 @@ IMPORTANT: When responding to direct questions or conversations, reply directly
|
|
|
64
64
|
Only use the 'message' tool when you need to send a message to a specific chat channel.
|
|
65
65
|
For normal conversation, just respond with text - do not call the message tool.
|
|
66
66
|
|
|
67
|
+
## Installing Skills
|
|
68
|
+
When asked to install a skill (from a URL, file, or any source):
|
|
69
|
+
1. Fetch/read the skill content
|
|
70
|
+
2. ALWAYS install it to: ${this.workspace}/skills/{skill-name}/SKILL.md
|
|
71
|
+
3. If the skill references a different workspace path (e.g. .moltbot/, .otherbot/, etc.), rewrite ALL paths to use ${this.workspace}/ instead
|
|
72
|
+
4. Preserve the skill's frontmatter, instructions, scripts, references, and assets — only change the workspace paths
|
|
73
|
+
|
|
67
74
|
Always be helpful, accurate, and concise. When using tools, explain what you're doing.
|
|
68
75
|
When remembering something, write to ${this.workspace}/memory/MEMORY.md`;
|
|
69
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.mjs","names":[],"sources":["../../src/agent/context.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ChatMessage, ContentPart } from \"../providers/base.js\";\nimport { MemoryStore } from \"./memory.js\";\nimport { SkillsLoader } from \"./skills.js\";\n\nconst BOOTSTRAP_FILES = [\n \"AGENTS.md\",\n \"SOUL.md\",\n \"USER.md\",\n \"TOOLS.md\",\n \"IDENTITY.md\",\n];\n\n/**\n * Builds the context (system prompt + messages) for the agent.\n */\nexport class ContextBuilder {\n private workspace: string;\n readonly memory: MemoryStore;\n readonly skills: SkillsLoader;\n\n constructor(workspace: string) {\n this.workspace = workspace;\n this.memory = new MemoryStore(workspace);\n this.skills = new SkillsLoader(workspace);\n }\n\n /** Build the system prompt from bootstrap files, memory, and skills. */\n buildSystemPrompt(): string {\n const parts: string[] = [];\n\n // Core identity\n parts.push(this.getIdentity());\n\n // Bootstrap files\n const bootstrap = this.loadBootstrapFiles();\n if (bootstrap) parts.push(bootstrap);\n\n // Memory context\n const memory = this.memory.getMemoryContext();\n if (memory) parts.push(`# Memory\\n\\n${memory}`);\n\n // Always-loaded skills\n const alwaysSkills = this.skills.getAlwaysSkills();\n if (alwaysSkills.length > 0) {\n const alwaysContent = this.skills.loadSkillsForContext(alwaysSkills);\n if (alwaysContent) {\n parts.push(`# Active Skills\\n\\n${alwaysContent}`);\n }\n }\n\n // Available skills summary\n const skillsSummary = this.skills.buildSkillsSummary();\n if (skillsSummary) {\n parts.push(\n `# Skills\\n\\n` +\n `The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.\\n` +\n `Skills with available=\"false\" need dependencies installed first.\\n\\n` +\n skillsSummary,\n );\n }\n\n return parts.join(\"\\n\\n---\\n\\n\");\n }\n\n private getIdentity(): string {\n const now = new Date();\n const dateStr = now.toISOString().slice(0, 16).replace(\"T\", \" \");\n const dayName = now.toLocaleDateString(\"en-US\", { weekday: \"long\" });\n\n return `# nanobot\n\nYou are nanobot, a helpful AI assistant. You have access to tools that allow you to:\n- Read, write, and edit files\n- Execute shell commands\n- Search the web and fetch web pages\n- Send messages to users on chat channels\n- Spawn subagents for complex background tasks\n\n## Current Time\n${dateStr} (${dayName})\n\n## Workspace\nYour workspace is at: ${this.workspace}\n- Memory files: ${this.workspace}/memory/MEMORY.md\n- Daily notes: ${this.workspace}/memory/YYYY-MM-DD.md\n- Custom skills: ${this.workspace}/skills/{skill-name}/SKILL.md\n\nIMPORTANT: When responding to direct questions or conversations, reply directly with your text response.\nOnly use the 'message' tool when you need to send a message to a specific chat channel.\nFor normal conversation, just respond with text - do not call the message tool.\n\nAlways be helpful, accurate, and concise. When using tools, explain what you're doing.\nWhen remembering something, write to ${this.workspace}/memory/MEMORY.md`;\n }\n\n private loadBootstrapFiles(): string {\n const parts: string[] = [];\n for (const filename of BOOTSTRAP_FILES) {\n const filePath = join(this.workspace, filename);\n if (existsSync(filePath)) {\n const content = readFileSync(filePath, \"utf-8\");\n parts.push(`## ${filename}\\n\\n${content}`);\n }\n }\n return parts.join(\"\\n\\n\");\n }\n\n /** Build the complete message list for an LLM call. */\n buildMessages(params: {\n history: ChatMessage[];\n currentMessage: string;\n media?: string[];\n channel?: string;\n chatId?: string;\n }): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n // System prompt\n let systemPrompt = this.buildSystemPrompt();\n if (params.channel && params.chatId) {\n systemPrompt += `\\n\\n## Current Session\\nChannel: ${params.channel}\\nChat ID: ${params.chatId}`;\n }\n messages.push({ role: \"system\", content: systemPrompt });\n\n // History — replay full rich messages (user, assistant w/ tool_calls, tool results, etc.)\n for (const msg of params.history) {\n messages.push(msg);\n }\n\n // Current message (with optional image attachments)\n const userContent = this.buildUserContent(\n params.currentMessage,\n params.media,\n );\n messages.push({ role: \"user\", content: userContent });\n\n return messages;\n }\n\n private buildUserContent(\n text: string,\n media?: string[],\n ): string | ContentPart[] {\n if (!media || media.length === 0) return text;\n\n const images: ContentPart[] = [];\n for (const filePath of media) {\n if (!existsSync(filePath)) continue;\n try {\n const data = readFileSync(filePath);\n const ext = filePath.split(\".\").pop()?.toLowerCase() ?? \"\";\n const mimeMap: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n };\n const mime = mimeMap[ext];\n if (!mime) continue;\n\n const b64 = data.toString(\"base64\");\n images.push({\n type: \"image_url\",\n image_url: { url: `data:${mime};base64,${b64}` },\n });\n } catch {\n // skip unreadable files\n }\n }\n\n if (images.length === 0) return text;\n return [...images, { type: \"text\", text }];\n }\n\n /** Add a tool result to the message list. */\n addToolResult(\n messages: ChatMessage[],\n toolCallId: string,\n toolName: string,\n result: string,\n ): ChatMessage[] {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCallId,\n name: toolName,\n content: result,\n });\n return messages;\n }\n\n /** Add an assistant message to the message list. */\n addAssistantMessage(\n messages: ChatMessage[],\n content: string | null,\n toolCalls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>,\n ): ChatMessage[] {\n const msg: ChatMessage = {\n role: \"assistant\",\n content: content ?? \"\",\n };\n if (toolCalls) {\n msg.tool_calls = toolCalls;\n }\n messages.push(msg);\n return messages;\n }\n}\n"],"mappings":";;;;;;AAMA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACD;;;;AAKD,IAAa,iBAAb,MAA4B;CAC1B,AAAQ;CACR,AAAS;CACT,AAAS;CAET,YAAY,WAAmB;AAC7B,OAAK,YAAY;AACjB,OAAK,SAAS,IAAI,YAAY,UAAU;AACxC,OAAK,SAAS,IAAI,aAAa,UAAU;;;CAI3C,oBAA4B;EAC1B,MAAM,QAAkB,EAAE;AAG1B,QAAM,KAAK,KAAK,aAAa,CAAC;EAG9B,MAAM,YAAY,KAAK,oBAAoB;AAC3C,MAAI,UAAW,OAAM,KAAK,UAAU;EAGpC,MAAM,SAAS,KAAK,OAAO,kBAAkB;AAC7C,MAAI,OAAQ,OAAM,KAAK,eAAe,SAAS;EAG/C,MAAM,eAAe,KAAK,OAAO,iBAAiB;AAClD,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,gBAAgB,KAAK,OAAO,qBAAqB,aAAa;AACpE,OAAI,cACF,OAAM,KAAK,sBAAsB,gBAAgB;;EAKrD,MAAM,gBAAgB,KAAK,OAAO,oBAAoB;AACtD,MAAI,cACF,OAAM,KACJ,wMAGE,cACH;AAGH,SAAO,MAAM,KAAK,cAAc;;CAGlC,AAAQ,cAAsB;EAC5B,MAAM,sBAAM,IAAI,MAAM;AAItB,SAAO;;;;;;;;;;EAHS,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAa1D,IAZU,IAAI,mBAAmB,SAAS,EAAE,SAAS,QAAQ,CAAC,CAYlD;;;wBAGE,KAAK,UAAU;kBACrB,KAAK,UAAU;iBAChB,KAAK,UAAU;mBACb,KAAK,UAAU
|
|
1
|
+
{"version":3,"file":"context.mjs","names":[],"sources":["../../src/agent/context.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ChatMessage, ContentPart } from \"../providers/base.js\";\nimport { MemoryStore } from \"./memory.js\";\nimport { SkillsLoader } from \"./skills.js\";\n\nconst BOOTSTRAP_FILES = [\n \"AGENTS.md\",\n \"SOUL.md\",\n \"USER.md\",\n \"TOOLS.md\",\n \"IDENTITY.md\",\n];\n\n/**\n * Builds the context (system prompt + messages) for the agent.\n */\nexport class ContextBuilder {\n private workspace: string;\n readonly memory: MemoryStore;\n readonly skills: SkillsLoader;\n\n constructor(workspace: string) {\n this.workspace = workspace;\n this.memory = new MemoryStore(workspace);\n this.skills = new SkillsLoader(workspace);\n }\n\n /** Build the system prompt from bootstrap files, memory, and skills. */\n buildSystemPrompt(): string {\n const parts: string[] = [];\n\n // Core identity\n parts.push(this.getIdentity());\n\n // Bootstrap files\n const bootstrap = this.loadBootstrapFiles();\n if (bootstrap) parts.push(bootstrap);\n\n // Memory context\n const memory = this.memory.getMemoryContext();\n if (memory) parts.push(`# Memory\\n\\n${memory}`);\n\n // Always-loaded skills\n const alwaysSkills = this.skills.getAlwaysSkills();\n if (alwaysSkills.length > 0) {\n const alwaysContent = this.skills.loadSkillsForContext(alwaysSkills);\n if (alwaysContent) {\n parts.push(`# Active Skills\\n\\n${alwaysContent}`);\n }\n }\n\n // Available skills summary\n const skillsSummary = this.skills.buildSkillsSummary();\n if (skillsSummary) {\n parts.push(\n `# Skills\\n\\n` +\n `The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.\\n` +\n `Skills with available=\"false\" need dependencies installed first.\\n\\n` +\n skillsSummary,\n );\n }\n\n return parts.join(\"\\n\\n---\\n\\n\");\n }\n\n private getIdentity(): string {\n const now = new Date();\n const dateStr = now.toISOString().slice(0, 16).replace(\"T\", \" \");\n const dayName = now.toLocaleDateString(\"en-US\", { weekday: \"long\" });\n\n return `# nanobot\n\nYou are nanobot, a helpful AI assistant. You have access to tools that allow you to:\n- Read, write, and edit files\n- Execute shell commands\n- Search the web and fetch web pages\n- Send messages to users on chat channels\n- Spawn subagents for complex background tasks\n\n## Current Time\n${dateStr} (${dayName})\n\n## Workspace\nYour workspace is at: ${this.workspace}\n- Memory files: ${this.workspace}/memory/MEMORY.md\n- Daily notes: ${this.workspace}/memory/YYYY-MM-DD.md\n- Custom skills: ${this.workspace}/skills/{skill-name}/SKILL.md\n\nIMPORTANT: When responding to direct questions or conversations, reply directly with your text response.\nOnly use the 'message' tool when you need to send a message to a specific chat channel.\nFor normal conversation, just respond with text - do not call the message tool.\n\n## Installing Skills\nWhen asked to install a skill (from a URL, file, or any source):\n1. Fetch/read the skill content\n2. ALWAYS install it to: ${this.workspace}/skills/{skill-name}/SKILL.md\n3. If the skill references a different workspace path (e.g. .moltbot/, .otherbot/, etc.), rewrite ALL paths to use ${this.workspace}/ instead\n4. Preserve the skill's frontmatter, instructions, scripts, references, and assets — only change the workspace paths\n\nAlways be helpful, accurate, and concise. When using tools, explain what you're doing.\nWhen remembering something, write to ${this.workspace}/memory/MEMORY.md`;\n }\n\n private loadBootstrapFiles(): string {\n const parts: string[] = [];\n for (const filename of BOOTSTRAP_FILES) {\n const filePath = join(this.workspace, filename);\n if (existsSync(filePath)) {\n const content = readFileSync(filePath, \"utf-8\");\n parts.push(`## ${filename}\\n\\n${content}`);\n }\n }\n return parts.join(\"\\n\\n\");\n }\n\n /** Build the complete message list for an LLM call. */\n buildMessages(params: {\n history: ChatMessage[];\n currentMessage: string;\n media?: string[];\n channel?: string;\n chatId?: string;\n }): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n // System prompt\n let systemPrompt = this.buildSystemPrompt();\n if (params.channel && params.chatId) {\n systemPrompt += `\\n\\n## Current Session\\nChannel: ${params.channel}\\nChat ID: ${params.chatId}`;\n }\n messages.push({ role: \"system\", content: systemPrompt });\n\n // History — replay full rich messages (user, assistant w/ tool_calls, tool results, etc.)\n for (const msg of params.history) {\n messages.push(msg);\n }\n\n // Current message (with optional image attachments)\n const userContent = this.buildUserContent(\n params.currentMessage,\n params.media,\n );\n messages.push({ role: \"user\", content: userContent });\n\n return messages;\n }\n\n private buildUserContent(\n text: string,\n media?: string[],\n ): string | ContentPart[] {\n if (!media || media.length === 0) return text;\n\n const images: ContentPart[] = [];\n for (const filePath of media) {\n if (!existsSync(filePath)) continue;\n try {\n const data = readFileSync(filePath);\n const ext = filePath.split(\".\").pop()?.toLowerCase() ?? \"\";\n const mimeMap: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n };\n const mime = mimeMap[ext];\n if (!mime) continue;\n\n const b64 = data.toString(\"base64\");\n images.push({\n type: \"image_url\",\n image_url: { url: `data:${mime};base64,${b64}` },\n });\n } catch {\n // skip unreadable files\n }\n }\n\n if (images.length === 0) return text;\n return [...images, { type: \"text\", text }];\n }\n\n /** Add a tool result to the message list. */\n addToolResult(\n messages: ChatMessage[],\n toolCallId: string,\n toolName: string,\n result: string,\n ): ChatMessage[] {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCallId,\n name: toolName,\n content: result,\n });\n return messages;\n }\n\n /** Add an assistant message to the message list. */\n addAssistantMessage(\n messages: ChatMessage[],\n content: string | null,\n toolCalls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>,\n ): ChatMessage[] {\n const msg: ChatMessage = {\n role: \"assistant\",\n content: content ?? \"\",\n };\n if (toolCalls) {\n msg.tool_calls = toolCalls;\n }\n messages.push(msg);\n return messages;\n }\n}\n"],"mappings":";;;;;;AAMA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACD;;;;AAKD,IAAa,iBAAb,MAA4B;CAC1B,AAAQ;CACR,AAAS;CACT,AAAS;CAET,YAAY,WAAmB;AAC7B,OAAK,YAAY;AACjB,OAAK,SAAS,IAAI,YAAY,UAAU;AACxC,OAAK,SAAS,IAAI,aAAa,UAAU;;;CAI3C,oBAA4B;EAC1B,MAAM,QAAkB,EAAE;AAG1B,QAAM,KAAK,KAAK,aAAa,CAAC;EAG9B,MAAM,YAAY,KAAK,oBAAoB;AAC3C,MAAI,UAAW,OAAM,KAAK,UAAU;EAGpC,MAAM,SAAS,KAAK,OAAO,kBAAkB;AAC7C,MAAI,OAAQ,OAAM,KAAK,eAAe,SAAS;EAG/C,MAAM,eAAe,KAAK,OAAO,iBAAiB;AAClD,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,gBAAgB,KAAK,OAAO,qBAAqB,aAAa;AACpE,OAAI,cACF,OAAM,KAAK,sBAAsB,gBAAgB;;EAKrD,MAAM,gBAAgB,KAAK,OAAO,oBAAoB;AACtD,MAAI,cACF,OAAM,KACJ,wMAGE,cACH;AAGH,SAAO,MAAM,KAAK,cAAc;;CAGlC,AAAQ,cAAsB;EAC5B,MAAM,sBAAM,IAAI,MAAM;AAItB,SAAO;;;;;;;;;;EAHS,IAAI,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAa1D,IAZU,IAAI,mBAAmB,SAAS,EAAE,SAAS,QAAQ,CAAC,CAYlD;;;wBAGE,KAAK,UAAU;kBACrB,KAAK,UAAU;iBAChB,KAAK,UAAU;mBACb,KAAK,UAAU;;;;;;;;;2BASP,KAAK,UAAU;qHAC2E,KAAK,UAAU;;;;uCAI7F,KAAK,UAAU;;CAGpD,AAAQ,qBAA6B;EACnC,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,YAAY,iBAAiB;GACtC,MAAM,WAAW,KAAK,KAAK,WAAW,SAAS;AAC/C,OAAI,WAAW,SAAS,EAAE;IACxB,MAAM,UAAU,aAAa,UAAU,QAAQ;AAC/C,UAAM,KAAK,MAAM,SAAS,MAAM,UAAU;;;AAG9C,SAAO,MAAM,KAAK,OAAO;;;CAI3B,cAAc,QAMI;EAChB,MAAM,WAA0B,EAAE;EAGlC,IAAI,eAAe,KAAK,mBAAmB;AAC3C,MAAI,OAAO,WAAW,OAAO,OAC3B,iBAAgB,oCAAoC,OAAO,QAAQ,aAAa,OAAO;AAEzF,WAAS,KAAK;GAAE,MAAM;GAAU,SAAS;GAAc,CAAC;AAGxD,OAAK,MAAM,OAAO,OAAO,QACvB,UAAS,KAAK,IAAI;EAIpB,MAAM,cAAc,KAAK,iBACvB,OAAO,gBACP,OAAO,MACR;AACD,WAAS,KAAK;GAAE,MAAM;GAAQ,SAAS;GAAa,CAAC;AAErD,SAAO;;CAGT,AAAQ,iBACN,MACA,OACwB;AACxB,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG,QAAO;EAEzC,MAAM,SAAwB,EAAE;AAChC,OAAK,MAAM,YAAY,OAAO;AAC5B,OAAI,CAAC,WAAW,SAAS,CAAE;AAC3B,OAAI;IACF,MAAM,OAAO,aAAa,SAAS;IASnC,MAAM,OAPkC;KACtC,KAAK;KACL,MAAM;KACN,KAAK;KACL,KAAK;KACL,MAAM;KACP,CAPW,SAAS,MAAM,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI;AASxD,QAAI,CAAC,KAAM;IAEX,MAAM,MAAM,KAAK,SAAS,SAAS;AACnC,WAAO,KAAK;KACV,MAAM;KACN,WAAW,EAAE,KAAK,QAAQ,KAAK,UAAU,OAAO;KACjD,CAAC;WACI;;AAKV,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,CAAC,GAAG,QAAQ;GAAE,MAAM;GAAQ;GAAM,CAAC;;;CAI5C,cACE,UACA,YACA,UACA,QACe;AACf,WAAS,KAAK;GACZ,MAAM;GACN,cAAc;GACd,MAAM;GACN,SAAS;GACV,CAAC;AACF,SAAO;;;CAIT,oBACE,UACA,SACA,WAKe;EACf,MAAM,MAAmB;GACvB,MAAM;GACN,SAAS,WAAW;GACrB;AACD,MAAI,UACF,KAAI,aAAa;AAEnB,WAAS,KAAK,IAAI;AAClB,SAAO"}
|
|
@@ -3,6 +3,14 @@ import { Tool } from "./base.mjs";
|
|
|
3
3
|
//#region src/agent/tools/shell.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Execute shell commands.
|
|
6
|
+
*
|
|
7
|
+
* Uses child_process.spawn instead of exec for compatibility with
|
|
8
|
+
* WebContainer environments (e.g. StackBlitz) where the virtualised
|
|
9
|
+
* shell (jsh) has limited support and cross-origin constraints.
|
|
10
|
+
*
|
|
11
|
+
* Simple commands (e.g. "node script.js", "git status") are spawned
|
|
12
|
+
* directly without a shell. Commands that contain shell syntax
|
|
13
|
+
* (pipes, redirects, etc.) fall back to shell mode automatically.
|
|
6
14
|
*/
|
|
7
15
|
declare class ExecTool extends Tool {
|
|
8
16
|
readonly name = "exec";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.d.mts","names":[],"sources":["../../../src/agent/tools/shell.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"shell.d.mts","names":[],"sources":["../../../src/agent/tools/shell.ts"],"mappings":";;;;;AAiDA;;;;;;;;;cAAa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA;;;;;;;;;;;;;;UAYD,UAAA;EAAA,QACA,cAAA;EAAA,QACA,mBAAA;cAEI,MAAA;IACV,UAAA;IACA,OAAA;IACA,mBAAA;EAAA;EAQI,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;AAAA"}
|
|
@@ -1,9 +1,44 @@
|
|
|
1
1
|
import { Tool } from "./base.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
3
|
|
|
4
4
|
//#region src/agent/tools/shell.ts
|
|
5
|
+
/** Characters/patterns that require shell interpretation. */
|
|
6
|
+
const SHELL_SYNTAX = /[|&;<>`$(){}[\]!#*?"'\\~]|\s>|\s</;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a command string into [command, args] for spawn.
|
|
9
|
+
* Only handles simple "cmd arg1 arg2" forms — anything with shell
|
|
10
|
+
* metacharacters should be routed through the shell instead.
|
|
11
|
+
*/
|
|
12
|
+
function parseCommand(command) {
|
|
13
|
+
const parts = [];
|
|
14
|
+
let current = "";
|
|
15
|
+
let inSingle = false;
|
|
16
|
+
let inDouble = false;
|
|
17
|
+
for (let i = 0; i < command.length; i++) {
|
|
18
|
+
const ch = command[i];
|
|
19
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
20
|
+
else if (ch === "\"" && !inSingle) inDouble = !inDouble;
|
|
21
|
+
else if (ch === " " && !inSingle && !inDouble) {
|
|
22
|
+
if (current) {
|
|
23
|
+
parts.push(current);
|
|
24
|
+
current = "";
|
|
25
|
+
}
|
|
26
|
+
} else current += ch;
|
|
27
|
+
}
|
|
28
|
+
if (current) parts.push(current);
|
|
29
|
+
const [cmd, ...args] = parts;
|
|
30
|
+
return [cmd ?? "", args];
|
|
31
|
+
}
|
|
5
32
|
/**
|
|
6
33
|
* Execute shell commands.
|
|
34
|
+
*
|
|
35
|
+
* Uses child_process.spawn instead of exec for compatibility with
|
|
36
|
+
* WebContainer environments (e.g. StackBlitz) where the virtualised
|
|
37
|
+
* shell (jsh) has limited support and cross-origin constraints.
|
|
38
|
+
*
|
|
39
|
+
* Simple commands (e.g. "node script.js", "git status") are spawned
|
|
40
|
+
* directly without a shell. Commands that contain shell syntax
|
|
41
|
+
* (pipes, redirects, etc.) fall back to shell mode automatically.
|
|
7
42
|
*/
|
|
8
43
|
var ExecTool = class extends Tool {
|
|
9
44
|
name = "exec";
|
|
@@ -43,23 +78,49 @@ var ExecTool = class extends Tool {
|
|
|
43
78
|
"> /dev/"
|
|
44
79
|
]) if (command.includes(pattern)) return `Error: Command blocked for safety: ${pattern}`;
|
|
45
80
|
}
|
|
81
|
+
const needsShell = SHELL_SYNTAX.test(command);
|
|
46
82
|
return new Promise((resolve) => {
|
|
47
|
-
|
|
83
|
+
let child;
|
|
84
|
+
const spawnOpts = {
|
|
48
85
|
cwd: this.workingDir,
|
|
49
86
|
timeout: timeout * 1e3,
|
|
50
|
-
maxBuffer: 1024 * 1024,
|
|
51
87
|
env: {
|
|
52
88
|
...process.env,
|
|
53
89
|
HOME: process.env.HOME ?? ""
|
|
54
90
|
}
|
|
55
|
-
}
|
|
91
|
+
};
|
|
92
|
+
if (needsShell) child = spawn(command, {
|
|
93
|
+
...spawnOpts,
|
|
94
|
+
shell: true,
|
|
95
|
+
maxBuffer: 1024 * 1024
|
|
96
|
+
});
|
|
97
|
+
else {
|
|
98
|
+
const [cmd, cmdArgs] = parseCommand(command);
|
|
99
|
+
child = spawn(cmd, cmdArgs, spawnOpts);
|
|
100
|
+
}
|
|
101
|
+
const stdoutChunks = [];
|
|
102
|
+
const stderrChunks = [];
|
|
103
|
+
let totalBytes = 0;
|
|
104
|
+
const maxBuffer = 1024 * 1024;
|
|
105
|
+
child.stdout?.on("data", (chunk) => {
|
|
106
|
+
totalBytes += chunk.length;
|
|
107
|
+
if (totalBytes <= maxBuffer) stdoutChunks.push(chunk);
|
|
108
|
+
});
|
|
109
|
+
child.stderr?.on("data", (chunk) => {
|
|
110
|
+
totalBytes += chunk.length;
|
|
111
|
+
if (totalBytes <= maxBuffer) stderrChunks.push(chunk);
|
|
112
|
+
});
|
|
113
|
+
child.on("error", (err) => {
|
|
114
|
+
resolve(`Error: ${err.message}`);
|
|
115
|
+
});
|
|
116
|
+
child.on("close", (code, signal) => {
|
|
117
|
+
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
118
|
+
const stderr = Buffer.concat(stderrChunks).toString();
|
|
56
119
|
const parts = [];
|
|
57
120
|
if (stdout) parts.push(stdout);
|
|
58
121
|
if (stderr) parts.push(`[stderr]\n${stderr}`);
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
else if (error.code !== void 0) parts.push(`\n[Exit code: ${error.code}]`);
|
|
62
|
-
}
|
|
122
|
+
if (signal === "SIGTERM" || signal === "SIGKILL") parts.push(`\n[Timed out after ${timeout}s]`);
|
|
123
|
+
else if (code !== null && code !== 0) parts.push(`\n[Exit code: ${code}]`);
|
|
63
124
|
const output = parts.join("\n").trim();
|
|
64
125
|
if (output.length > 5e4) resolve(output.slice(0, 5e4) + "\n... (truncated, output too large)");
|
|
65
126
|
else resolve(output || "(no output)");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.mjs","names":[],"sources":["../../../src/agent/tools/shell.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"shell.mjs","names":[],"sources":["../../../src/agent/tools/shell.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { Tool } from \"./base.js\";\n\n/** Characters/patterns that require shell interpretation. */\nconst SHELL_SYNTAX = /[|&;<>`$(){}[\\]!#*?\"'\\\\~]|\\s>|\\s</;\n\n/**\n * Parse a command string into [command, args] for spawn.\n * Only handles simple \"cmd arg1 arg2\" forms — anything with shell\n * metacharacters should be routed through the shell instead.\n */\nfunction parseCommand(command: string): [string, string[]] {\n const parts: string[] = [];\n let current = \"\";\n let inSingle = false;\n let inDouble = false;\n\n for (let i = 0; i < command.length; i++) {\n const ch = command[i];\n if (ch === \"'\" && !inDouble) {\n inSingle = !inSingle;\n } else if (ch === '\"' && !inSingle) {\n inDouble = !inDouble;\n } else if (ch === \" \" && !inSingle && !inDouble) {\n if (current) {\n parts.push(current);\n current = \"\";\n }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n\n const [cmd, ...args] = parts;\n return [cmd ?? \"\", args];\n}\n\n/**\n * Execute shell commands.\n *\n * Uses child_process.spawn instead of exec for compatibility with\n * WebContainer environments (e.g. StackBlitz) where the virtualised\n * shell (jsh) has limited support and cross-origin constraints.\n *\n * Simple commands (e.g. \"node script.js\", \"git status\") are spawned\n * directly without a shell. Commands that contain shell syntax\n * (pipes, redirects, etc.) fall back to shell mode automatically.\n */\nexport class ExecTool extends Tool {\n readonly name = \"exec\";\n readonly description =\n \"Execute a shell command and return its output. Use for running programs, scripts, git, etc.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n command: { type: \"string\", description: \"Shell command to execute\" },\n timeout: {\n type: \"integer\",\n description: \"Timeout in seconds (default: 60)\",\n },\n },\n required: [\"command\"],\n };\n\n private workingDir: string;\n private defaultTimeout: number;\n private restrictToWorkspace: boolean;\n\n constructor(params?: {\n workingDir?: string;\n timeout?: number;\n restrictToWorkspace?: boolean;\n }) {\n super();\n this.workingDir = params?.workingDir ?? process.cwd();\n this.defaultTimeout = params?.timeout ?? 60;\n this.restrictToWorkspace = params?.restrictToWorkspace ?? false;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const command = String(args.command);\n const timeout = args.timeout ? Number(args.timeout) : this.defaultTimeout;\n\n if (!command.trim()) {\n return \"Error: Empty command\";\n }\n\n // Basic safety check\n if (this.restrictToWorkspace) {\n const dangerous = [\"rm -rf /\", \"mkfs\", \"dd if=\", \"> /dev/\"];\n for (const pattern of dangerous) {\n if (command.includes(pattern)) {\n return `Error: Command blocked for safety: ${pattern}`;\n }\n }\n }\n\n // Determine whether the command needs a shell.\n // Shell syntax (pipes, redirects, etc.) requires shell interpretation.\n // Simple \"cmd arg1 arg2\" commands can be spawned directly, which avoids\n // WebContainer/jsh limitations entirely.\n const needsShell = SHELL_SYNTAX.test(command);\n\n return new Promise<string>((resolve) => {\n let child;\n const spawnOpts = {\n cwd: this.workingDir,\n timeout: timeout * 1000,\n env: { ...process.env, HOME: process.env.HOME ?? \"\" },\n };\n\n if (needsShell) {\n // Fall back to shell for complex commands\n child = spawn(command, {\n ...spawnOpts,\n shell: true,\n maxBuffer: 1024 * 1024,\n } as any);\n } else {\n // Direct spawn — bypasses jsh entirely\n const [cmd, cmdArgs] = parseCommand(command);\n child = spawn(cmd, cmdArgs, spawnOpts);\n }\n\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n let totalBytes = 0;\n const maxBuffer = 1024 * 1024; // 1MB\n\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n totalBytes += chunk.length;\n if (totalBytes <= maxBuffer) stdoutChunks.push(chunk);\n });\n\n child.stderr?.on(\"data\", (chunk: Buffer) => {\n totalBytes += chunk.length;\n if (totalBytes <= maxBuffer) stderrChunks.push(chunk);\n });\n\n child.on(\"error\", (err) => {\n resolve(`Error: ${err.message}`);\n });\n\n child.on(\"close\", (code, signal) => {\n const stdout = Buffer.concat(stdoutChunks).toString();\n const stderr = Buffer.concat(stderrChunks).toString();\n const parts: string[] = [];\n\n if (stdout) parts.push(stdout);\n if (stderr) parts.push(`[stderr]\\n${stderr}`);\n\n if (signal === \"SIGTERM\" || signal === \"SIGKILL\") {\n parts.push(`\\n[Timed out after ${timeout}s]`);\n } else if (code !== null && code !== 0) {\n parts.push(`\\n[Exit code: ${code}]`);\n }\n\n const output = parts.join(\"\\n\").trim();\n if (output.length > 50000) {\n resolve(\n output.slice(0, 50000) + \"\\n... (truncated, output too large)\",\n );\n } else {\n resolve(output || \"(no output)\");\n }\n });\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.mts","names":[],"sources":["../../../src/agent/tools/web.ts"],"mappings":";;;;cA0Ea,aAAA,SAAsB,IAAA;EAAA,SACxB,IAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA;;;;;;;;;;;;;;;;UAcD,MAAA;EAAA,QACA,UAAA;cAEI,MAAA;IAAW,MAAA;IAAiB,UAAA;EAAA;EAMlC,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;AAAA;;
|
|
1
|
+
{"version":3,"file":"web.d.mts","names":[],"sources":["../../../src/agent/tools/web.ts"],"mappings":";;;;cA0Ea,aAAA,SAAsB,IAAA;EAAA,SACxB,IAAA;EAAA,SACA,WAAA;EAAA,SACA,UAAA;;;;;;;;;;;;;;;;UAcD,MAAA;EAAA,QACA,UAAA;cAEI,MAAA;IAAW,MAAA;IAAiB,UAAA;EAAA;EAMlC,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;AAAA;;cAsDnC,YAAA,SAAqB,IAAA;EAAA,SACvB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA;;;;;;;;;;;;;;;;;;;UAcD,eAAA;cAEI,MAAA;IAAW,QAAA;EAAA;EAKjB,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;AAAA"}
|
package/dist/agent/tools/web.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.mjs","names":[],"sources":["../../../src/agent/tools/web.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\nconst USER_AGENT =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36\";\n\n/** Strip HTML tags and decode entities. */\nfunction stripTags(text: string): string {\n let result = text;\n result = result.replace(/<script[\\s\\S]*?<\\/script>/gi, \"\");\n result = result.replace(/<style[\\s\\S]*?<\\/style>/gi, \"\");\n result = result.replace(/<[^>]+>/g, \"\");\n // Decode common HTML entities\n result = result\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, \" \");\n return result.trim();\n}\n\n/** Normalize whitespace. */\nfunction normalize(text: string): string {\n return text\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/** Validate URL. */\nfunction validateUrl(url: string): { valid: boolean; error: string } {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return {\n valid: false,\n error: `Only http/https allowed, got '${parsed.protocol}'`,\n };\n }\n if (!parsed.hostname) {\n return { valid: false, error: \"Missing domain\" };\n }\n return { valid: true, error: \"\" };\n } catch (err) {\n return { valid: false, error: String(err) };\n }\n}\n\n/** Convert HTML to basic markdown. */\nfunction htmlToMarkdown(html: string): string {\n let text = html;\n // Links\n text = text.replace(\n /<a\\s+[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi,\n (_m, url, inner) => `[${stripTags(inner)}](${url})`,\n );\n // Headings\n text = text.replace(\n /<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi,\n (_m, level, inner) => `\\n${\"#\".repeat(Number(level))} ${stripTags(inner)}\\n`,\n );\n // List items\n text = text.replace(\n /<li[^>]*>([\\s\\S]*?)<\\/li>/gi,\n (_m, inner) => `\\n- ${stripTags(inner)}`,\n );\n // Block elements\n text = text.replace(/<\\/(p|div|section|article)>/gi, \"\\n\\n\");\n text = text.replace(/<(br|hr)\\s*\\/?>/gi, \"\\n\");\n return normalize(stripTags(text));\n}\n\n/** Search the web using Brave Search API. */\nexport class WebSearchTool extends Tool {\n readonly name = \"web_search\";\n readonly description = \"Search the web. Returns titles, URLs, and snippets.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Search query\" },\n count: {\n type: \"integer\",\n description: \"Results (1-10)\",\n minimum: 1,\n maximum: 10,\n },\n },\n required: [\"query\"],\n };\n\n private apiKey: string;\n private maxResults: number;\n\n constructor(params?: { apiKey?: string; maxResults?: number }) {\n super();\n this.apiKey = params?.apiKey ?? process.env.BRAVE_API_KEY ?? \"\";\n this.maxResults = params?.maxResults ?? 5;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const query = String(args.query);\n const count = Math.min(\n Math.max(args.count ? Number(args.count) : this.maxResults, 1),\n 10,\n );\n\n if (!this.apiKey) {\n return \"Error: BRAVE_API_KEY not configured\";\n }\n\n try {\n const url = new URL(\"https://api.search.brave.com/res/v1/web/search\");\n url.searchParams.set(\"q\", query);\n url.searchParams.set(\"count\", String(count));\n\n const resp = await fetch(url.toString(), {\n headers: {\n Accept: \"application/json\",\n \"X-Subscription-Token\": this.apiKey,\n },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!resp.ok) {\n return `Error: Search API returned ${resp.status}`;\n }\n\n const data = (await resp.json()) as {\n web?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n };\n const results = data.web?.results ?? [];\n\n if (results.length === 0) {\n return `No results for: ${query}`;\n }\n\n const lines = [`Results for: ${query}\\n`];\n for (let i = 0; i < Math.min(results.length, count); i++) {\n const item = results[i];\n lines.push(`${i + 1}. ${item.title ?? \"\"}\\n ${item.url ?? \"\"}`);\n if (item.description) {\n lines.push(` ${item.description}`);\n }\n }\n return lines.join(\"\\n\");\n } catch (err) {\n return `Error: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n\n/** Fetch and extract content from a URL. */\nexport class WebFetchTool extends Tool {\n readonly name = \"web_fetch\";\n readonly description =\n \"Fetch URL and extract readable content (HTML -> markdown/text).\";\n readonly parameters = {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"URL to fetch\" },\n extractMode: {\n type: \"string\",\n enum: [\"markdown\", \"text\"],\n description: \"Extract mode\",\n },\n maxChars: { type: \"integer\", minimum: 100 },\n },\n required: [\"url\"],\n };\n\n private defaultMaxChars: number;\n\n constructor(params?: { maxChars?: number }) {\n super();\n this.defaultMaxChars = params?.maxChars ?? 50000;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const url = String(args.url);\n const extractMode = String(args.extractMode ?? \"markdown\");\n const maxChars = args.maxChars\n ? Number(args.maxChars)\n : this.defaultMaxChars;\n\n const { valid, error: validationError } = validateUrl(url);\n if (!valid) {\n return JSON.stringify({\n error: `URL validation failed: ${validationError}`,\n url,\n });\n }\n\n try {\n const resp = await fetch(url, {\n headers: { \"User-Agent\": USER_AGENT },\n redirect: \"follow\",\n signal: AbortSignal.timeout(30000),\n });\n\n if (!resp.ok) {\n return JSON.stringify({\n error: `HTTP ${resp.status}`,\n url,\n });\n }\n\n const contentType = resp.headers.get(\"content-type\") ?? \"\";\n const body = await resp.text();\n let text: string;\n let extractor: string;\n\n if (contentType.includes(\"application/json\")) {\n try {\n text = JSON.stringify(JSON.parse(body), null, 2);\n } catch {\n text = body;\n }\n extractor = \"json\";\n } else if (\n contentType.includes(\"text/html\") ||\n body.slice(0, 256).toLowerCase().startsWith(\"<!doctype\") ||\n body.slice(0, 256).toLowerCase().startsWith(\"<html\")\n ) {\n // Extract readable content from HTML\n text =\n extractMode === \"markdown\"\n ? htmlToMarkdown(body)\n : stripTags(body);\n extractor = \"html\";\n } else {\n text = body;\n extractor = \"raw\";\n }\n\n const truncated = text.length > maxChars;\n if (truncated) {\n text = text.slice(0, maxChars);\n }\n\n return JSON.stringify({\n url,\n finalUrl: resp.url,\n status: resp.status,\n extractor,\n truncated,\n length: text.length,\n text,\n });\n } catch (err) {\n return JSON.stringify({\n error: err instanceof Error ? err.message : String(err),\n url,\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"web.mjs","names":[],"sources":["../../../src/agent/tools/web.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\nconst USER_AGENT =\n \"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/537.36\";\n\n/** Strip HTML tags and decode entities. */\nfunction stripTags(text: string): string {\n let result = text;\n result = result.replace(/<script[\\s\\S]*?<\\/script>/gi, \"\");\n result = result.replace(/<style[\\s\\S]*?<\\/style>/gi, \"\");\n result = result.replace(/<[^>]+>/g, \"\");\n // Decode common HTML entities\n result = result\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/ /g, \" \");\n return result.trim();\n}\n\n/** Normalize whitespace. */\nfunction normalize(text: string): string {\n return text\n .replace(/[ \\t]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/** Validate URL. */\nfunction validateUrl(url: string): { valid: boolean; error: string } {\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n return {\n valid: false,\n error: `Only http/https allowed, got '${parsed.protocol}'`,\n };\n }\n if (!parsed.hostname) {\n return { valid: false, error: \"Missing domain\" };\n }\n return { valid: true, error: \"\" };\n } catch (err) {\n return { valid: false, error: String(err) };\n }\n}\n\n/** Convert HTML to basic markdown. */\nfunction htmlToMarkdown(html: string): string {\n let text = html;\n // Links\n text = text.replace(\n /<a\\s+[^>]*href=[\"']([^\"']+)[\"'][^>]*>([\\s\\S]*?)<\\/a>/gi,\n (_m, url, inner) => `[${stripTags(inner)}](${url})`,\n );\n // Headings\n text = text.replace(\n /<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi,\n (_m, level, inner) => `\\n${\"#\".repeat(Number(level))} ${stripTags(inner)}\\n`,\n );\n // List items\n text = text.replace(\n /<li[^>]*>([\\s\\S]*?)<\\/li>/gi,\n (_m, inner) => `\\n- ${stripTags(inner)}`,\n );\n // Block elements\n text = text.replace(/<\\/(p|div|section|article)>/gi, \"\\n\\n\");\n text = text.replace(/<(br|hr)\\s*\\/?>/gi, \"\\n\");\n return normalize(stripTags(text));\n}\n\n/** Search the web using Brave Search API. */\nexport class WebSearchTool extends Tool {\n readonly name = \"web_search\";\n readonly description = \"Search the web. Returns titles, URLs, and snippets.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n query: { type: \"string\", description: \"Search query\" },\n count: {\n type: \"integer\",\n description: \"Results (1-10)\",\n minimum: 1,\n maximum: 10,\n },\n },\n required: [\"query\"],\n };\n\n private apiKey: string;\n private maxResults: number;\n\n constructor(params?: { apiKey?: string; maxResults?: number }) {\n super();\n this.apiKey = params?.apiKey ?? process.env.BRAVE_API_KEY ?? \"\";\n this.maxResults = params?.maxResults ?? 5;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const query = String(args.query);\n const count = Math.min(\n Math.max(args.count ? Number(args.count) : this.maxResults, 1),\n 10,\n );\n\n if (!this.apiKey) {\n return \"Error: BRAVE_API_KEY not configured\";\n }\n\n try {\n const url = new URL(\"https://api.search.brave.com/res/v1/web/search\");\n url.searchParams.set(\"q\", query);\n url.searchParams.set(\"count\", String(count));\n\n const resp = await fetch(url.toString(), {\n headers: {\n Accept: \"application/json\",\n \"Accept-Encoding\": \"gzip\",\n \"X-Subscription-Token\": this.apiKey,\n },\n signal: AbortSignal.timeout(10000),\n });\n\n if (!resp.ok) {\n return `Error: Search API returned ${resp.status}`;\n }\n\n const data = (await resp.json()) as {\n web?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n };\n const results = data.web?.results ?? [];\n\n if (results.length === 0) {\n return `No results for: ${query}`;\n }\n\n const lines = [`Results for: ${query}\\n`];\n for (let i = 0; i < Math.min(results.length, count); i++) {\n const item = results[i];\n lines.push(`${i + 1}. ${item.title ?? \"\"}\\n ${item.url ?? \"\"}`);\n if (item.description) {\n lines.push(` ${item.description}`);\n }\n }\n return lines.join(\"\\n\");\n } catch (err) {\n return `Error: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n\n/** Fetch and extract content from a URL. */\nexport class WebFetchTool extends Tool {\n readonly name = \"web_fetch\";\n readonly description =\n \"Fetch URL and extract readable content (HTML -> markdown/text).\";\n readonly parameters = {\n type: \"object\",\n properties: {\n url: { type: \"string\", description: \"URL to fetch\" },\n extractMode: {\n type: \"string\",\n enum: [\"markdown\", \"text\"],\n description: \"Extract mode\",\n },\n maxChars: { type: \"integer\", minimum: 100 },\n },\n required: [\"url\"],\n };\n\n private defaultMaxChars: number;\n\n constructor(params?: { maxChars?: number }) {\n super();\n this.defaultMaxChars = params?.maxChars ?? 50000;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const url = String(args.url);\n const extractMode = String(args.extractMode ?? \"markdown\");\n const maxChars = args.maxChars\n ? Number(args.maxChars)\n : this.defaultMaxChars;\n\n const { valid, error: validationError } = validateUrl(url);\n if (!valid) {\n return JSON.stringify({\n error: `URL validation failed: ${validationError}`,\n url,\n });\n }\n\n try {\n const resp = await fetch(url, {\n headers: { \"User-Agent\": USER_AGENT },\n redirect: \"follow\",\n signal: AbortSignal.timeout(30000),\n });\n\n if (!resp.ok) {\n return JSON.stringify({\n error: `HTTP ${resp.status}`,\n url,\n });\n }\n\n const contentType = resp.headers.get(\"content-type\") ?? \"\";\n const body = await resp.text();\n let text: string;\n let extractor: string;\n\n if (contentType.includes(\"application/json\")) {\n try {\n text = JSON.stringify(JSON.parse(body), null, 2);\n } catch {\n text = body;\n }\n extractor = \"json\";\n } else if (\n contentType.includes(\"text/html\") ||\n body.slice(0, 256).toLowerCase().startsWith(\"<!doctype\") ||\n body.slice(0, 256).toLowerCase().startsWith(\"<html\")\n ) {\n // Extract readable content from HTML\n text =\n extractMode === \"markdown\"\n ? htmlToMarkdown(body)\n : stripTags(body);\n extractor = \"html\";\n } else {\n text = body;\n extractor = \"raw\";\n }\n\n const truncated = text.length > maxChars;\n if (truncated) {\n text = text.slice(0, maxChars);\n }\n\n return JSON.stringify({\n url,\n finalUrl: resp.url,\n status: resp.status,\n extractor,\n truncated,\n length: text.length,\n text,\n });\n } catch (err) {\n return JSON.stringify({\n error: err instanceof Error ? err.message : String(err),\n url,\n });\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -69,6 +69,13 @@ Information about the user goes here.
|
|
|
69
69
|
- Communication style: (casual/formal)
|
|
70
70
|
- Timezone: (your timezone)
|
|
71
71
|
- Language: (your preferred language)
|
|
72
|
+
`,
|
|
73
|
+
"HEARTBEAT.md": `# Heartbeat
|
|
74
|
+
|
|
75
|
+
This file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.
|
|
76
|
+
|
|
77
|
+
## Tasks
|
|
78
|
+
|
|
72
79
|
`
|
|
73
80
|
})) {
|
|
74
81
|
const filePath = join(workspace, filename);
|
package/dist/cli/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * nanobot CLI - Personal AI Assistant\n */\n\nimport { Command } from \"commander\";\nimport { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath, getDataDir } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { GatewayServer } from \"../gateway/server.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"nanobot\")\n .description(`${LOGO} nanobot - Personal AI Assistant`)\n .version(`${LOGO} nanobot v${VERSION}`, \"-v, --version\");\n\n// ============================================================================\n// Onboard / Setup\n// ============================================================================\n\nprogram\n .command(\"onboard\")\n .description(\"Initialize nanobot configuration and workspace\")\n .action(() => {\n const configPath = getConfigPath();\n\n if (existsSync(configPath)) {\n console.log(`Config already exists at ${configPath}`);\n console.log(\"Delete it first if you want to start fresh.\");\n return;\n }\n\n // Create default config\n const config = ConfigSchema.parse({});\n saveConfig(config);\n console.log(`Created config at ${configPath}`);\n\n // Create workspace\n const workspace = getConfigWorkspacePath(config);\n createWorkspaceTemplates(workspace);\n\n console.log(`\\n${LOGO} nanobot is ready!`);\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Add your API key to ~/.nanobot/config.json\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\n console.log(\n \"\\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps\",\n );\n });\n\nfunction createWorkspaceTemplates(workspace: string): void {\n if (!existsSync(workspace)) {\n mkdirSync(workspace, { recursive: true });\n }\n\n const templates: Record<string, string> = {\n \"AGENTS.md\": `# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n`,\n \"SOUL.md\": `# Soul\n\nI am nanobot, a lightweight AI assistant.\n\n## Personality\n\n- Helpful and friendly\n- Concise and to the point\n- Curious and eager to learn\n\n## Values\n\n- Accuracy over speed\n- User privacy and safety\n- Transparency in actions\n`,\n \"USER.md\": `# User\n\nInformation about the user goes here.\n\n## Preferences\n\n- Communication style: (casual/formal)\n- Timezone: (your timezone)\n- Language: (your preferred language)\n`,\n };\n\n for (const [filename, content] of Object.entries(templates)) {\n const filePath = join(workspace, filename);\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content);\n console.log(` Created ${filename}`);\n }\n }\n\n // Create memory directory and MEMORY.md\n const memoryDir = join(workspace, \"memory\");\n if (!existsSync(memoryDir)) {\n mkdirSync(memoryDir, { recursive: true });\n }\n const memoryFile = join(memoryDir, \"MEMORY.md\");\n if (!existsSync(memoryFile)) {\n writeFileSync(\n memoryFile,\n `# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n`,\n );\n console.log(\" Created memory/MEMORY.md\");\n }\n}\n\n// ============================================================================\n// Config Export / Import\n// ============================================================================\n\n/** Shape of the portable config bundle. */\ninterface ConfigBundle {\n _nanobot: string;\n version: string;\n exportedAt: string;\n config: unknown;\n cronJobs: unknown | null;\n knownChats: unknown | null;\n workspace: Record<string, string>;\n}\n\nconst configCmd = program\n .command(\"config\")\n .description(\"Manage nanobot configuration\");\n\nconfigCmd\n .command(\"export\")\n .description(\"Export config, cron jobs, known chats, and workspace files to a portable JSON bundle\")\n .option(\"-o, --output <path>\", \"Write to file instead of stdout\")\n .action((opts) => {\n const dataDir = getDataDir();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n // Read optional data files\n const readJsonSafe = (p: string): unknown | null => {\n try { return JSON.parse(readFileSync(p, \"utf-8\")); } catch { return null; }\n };\n const readTextSafe = (p: string): string | null => {\n try { return readFileSync(p, \"utf-8\"); } catch { return null; }\n };\n\n // Gather workspace files\n const wsFiles: Record<string, string> = {};\n const workspaceEntries = [\n \"AGENTS.md\", \"SOUL.md\", \"USER.md\", \"TOOLS.md\", \"IDENTITY.md\",\n \"memory/MEMORY.md\",\n ];\n for (const rel of workspaceEntries) {\n const content = readTextSafe(join(workspace, rel));\n if (content !== null) wsFiles[rel] = content;\n }\n\n // Also grab daily memory notes\n const memoryDir = join(workspace, \"memory\");\n if (existsSync(memoryDir)) {\n try {\n for (const f of readdirSync(memoryDir)) {\n if (f.endsWith(\".md\") && f !== \"MEMORY.md\") {\n const content = readTextSafe(join(memoryDir, f));\n if (content !== null) wsFiles[`memory/${f}`] = content;\n }\n }\n } catch { /* ignore */ }\n }\n\n const bundle: ConfigBundle = {\n _nanobot: \"config-bundle\",\n version: VERSION,\n exportedAt: new Date().toISOString(),\n config,\n cronJobs: readJsonSafe(join(dataDir, \"cron\", \"jobs.json\")),\n knownChats: readJsonSafe(join(dataDir, \"known_chats.json\")),\n workspace: wsFiles,\n };\n\n const json = JSON.stringify(bundle, null, 2);\n\n if (opts.output) {\n const outDir = dirname(opts.output);\n if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });\n writeFileSync(opts.output, json);\n console.log(`Exported to ${opts.output}`);\n } else {\n process.stdout.write(json + \"\\n\");\n }\n });\n\nconfigCmd\n .command(\"import\")\n .description(\"Import a config bundle (from file or stdin)\")\n .argument(\"[path]\", \"Path to bundle JSON file (omit to read from stdin)\")\n .option(\"--no-workspace\", \"Skip importing workspace files\")\n .option(\"--no-cron\", \"Skip importing cron jobs\")\n .action(async (path: string | undefined, opts: { workspace: boolean; cron: boolean }) => {\n let raw: string;\n\n if (path) {\n if (!existsSync(path)) {\n console.error(`File not found: ${path}`);\n process.exit(1);\n }\n raw = readFileSync(path, \"utf-8\");\n } else {\n // Read from stdin\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk as Buffer);\n }\n raw = Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n let bundle: ConfigBundle;\n try {\n bundle = JSON.parse(raw);\n } catch {\n console.error(\"Error: Invalid JSON\");\n process.exit(1);\n }\n\n if (bundle._nanobot !== \"config-bundle\") {\n console.error(\"Error: Not a nanobot config bundle\");\n process.exit(1);\n }\n\n const dataDir = getDataDir();\n\n // 1. Restore config\n if (bundle.config) {\n const config = ConfigSchema.parse(bundle.config);\n saveConfig(config);\n console.log(\" Restored config.json\");\n }\n\n // 2. Restore cron jobs\n if (opts.cron && bundle.cronJobs) {\n const cronDir = join(dataDir, \"cron\");\n if (!existsSync(cronDir)) mkdirSync(cronDir, { recursive: true });\n writeFileSync(join(cronDir, \"jobs.json\"), JSON.stringify(bundle.cronJobs, null, 2));\n console.log(\" Restored cron/jobs.json\");\n }\n\n // 3. Restore known chats\n if (bundle.knownChats) {\n writeFileSync(join(dataDir, \"known_chats.json\"), JSON.stringify(bundle.knownChats, null, 2));\n console.log(\" Restored known_chats.json\");\n }\n\n // 4. Restore workspace files\n if (opts.workspace && bundle.workspace) {\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n for (const [rel, content] of Object.entries(bundle.workspace)) {\n const filePath = join(workspace, rel);\n const fileDir = dirname(filePath);\n if (!existsSync(fileDir)) mkdirSync(fileDir, { recursive: true });\n writeFileSync(filePath, content);\n console.log(` Restored ${rel}`);\n }\n }\n\n console.log(`\\n${LOGO} Import complete (from ${bundle.exportedAt})`);\n });\n\n// ============================================================================\n// Gateway / Server\n// ============================================================================\n\nprogram\n .command(\"gateway\")\n .description(\"Start the nanobot gateway\")\n .option(\"-p, --port <number>\", \"Gateway port\", \"18790\")\n .option(\"--verbose\", \"Verbose output\", false)\n .action(async (opts) => {\n console.log(\n `${LOGO} Starting nanobot gateway on port ${opts.port}...`,\n );\n\n const config = loadConfig();\n const apiKey = getApiKey(config);\n const apiBase = getApiBase(config);\n const model = config.agents.defaults.model;\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n console.error(\n \"Set one in ~/.nanobot/config.json under providers.openrouter.apiKey\",\n );\n process.exit(1);\n }\n\n // Dynamic imports to avoid loading heavy deps up front\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n const { ChannelManager } = await import(\"../channels/manager.js\");\n const { CronService } = await import(\"../cron/service.js\");\n const { HeartbeatService } = await import(\n \"../heartbeat/service.js\"\n );\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n });\n\n // Create cron service\n const cronStorePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const cron = new CronService(cronStorePath);\n\n // Create agent\n const agent = new AgentLoop({\n bus,\n provider,\n workspace,\n model,\n maxTokens: config.agents.defaults.maxTokens,\n maxIterations: config.agents.defaults.maxToolIterations,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n cronService: cron,\n });\n\n // Create channel manager (before cron so the callback can broadcast)\n const channels = new ChannelManager(config, bus);\n\n // Wire cron callback (gateway is assigned after server creation below)\n let gateway: GatewayServer | null = null;\n\n cron.onJob = async (job) => {\n const sessionKey = `cron:${job.id}`;\n\n // Snapshot history length so we can extract only this turn's messages\n const session = agent.sessions.getOrCreate(sessionKey);\n const prevLen = session.getHistory().length;\n\n const response = await agent.processDirect(\n job.payload.message,\n sessionKey,\n \"cron\",\n job.id,\n );\n\n // Push the full turn (tool calls + results + final answer) to the web UI via SSE\n if (gateway) {\n const turnMessages = session.getHistory().slice(prevLen);\n for (const msg of turnMessages) {\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n for (const tc of msg.tool_calls) {\n gateway.notifyToolCall(tc.function.name, tc.function.arguments);\n }\n // If the assistant message also has text content, push it\n if (msg.content) {\n gateway.notify(\"assistant\", typeof msg.content === \"string\" ? msg.content : \"\", \"default\");\n }\n } else if (msg.role === \"tool\") {\n const content = typeof msg.content === \"string\" ? msg.content : \"\";\n gateway.notifyToolResult(msg.name ?? \"\", content);\n } else if (msg.role === \"assistant\") {\n gateway.notify(\"assistant\", typeof msg.content === \"string\" ? msg.content : \"\", \"default\");\n }\n }\n }\n\n // Deliver to all configured chat channels (telegram, etc.)\n if (job.payload.deliver) {\n const { createOutboundMessage } = await import(\n \"../bus/events.js\"\n );\n\n for (const channelName of channels.enabledChannels) {\n const chatIds = channels.getKnownChatIds(channelName);\n if (chatIds.length === 0) {\n console.log(`Cron: no known chats for ${channelName}, skipping`);\n continue;\n }\n for (const chatId of chatIds) {\n await bus.publishOutbound(\n createOutboundMessage({\n channel: channelName,\n chatId,\n content: response ?? \"\",\n }),\n );\n }\n }\n }\n\n return response;\n };\n\n // Create heartbeat\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) =>\n agent.processDirect(prompt, \"heartbeat\"),\n intervalS: 30 * 60,\n enabled: true,\n });\n\n const cronStatus = cron.status();\n if (cronStatus.jobs > 0) {\n console.log(`Cron: ${cronStatus.jobs} scheduled jobs`);\n }\n console.log(\"Heartbeat: every 30m\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n heartbeat.stop();\n cron.stop();\n agent.stop();\n await channels.stopAll();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start HTTP gateway server\n const { createGatewayServer } = await import(\n \"../gateway/server.js\"\n );\n gateway = createGatewayServer({\n agent,\n port: Number(opts.port),\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\n await cron.start();\n await heartbeat.start();\n // Both agent.run() and channels.startAll() block until stopped\n await Promise.all([agent.run(), channels.startAll()]);\n } catch (err) {\n console.error(\"Gateway error:\", err);\n process.exit(1);\n }\n });\n\n// ============================================================================\n// Agent Commands\n// ============================================================================\n\nprogram\n .command(\"agent\")\n .description(\"Interact with the agent directly\")\n .option(\"-m, --message <text>\", \"Message to send to the agent\")\n .option(\n \"-s, --session <id>\",\n \"Session ID\",\n \"cli:default\",\n )\n .action(async (opts) => {\n const config = loadConfig();\n const apiKey = getApiKey(config);\n const apiBase = getApiBase(config);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n process.exit(1);\n }\n\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: config.agents.defaults.model,\n });\n\n const agentLoop = new AgentLoop({\n bus,\n provider,\n workspace,\n maxTokens: config.agents.defaults.maxTokens,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n });\n\n if (opts.message) {\n // Single message mode\n const response = await agentLoop.processDirect(\n opts.message,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}`);\n } else {\n // Interactive mode\n console.log(`${LOGO} Interactive mode (Ctrl+C to exit)\\n`);\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const ask = (): void => {\n rl.question(\"You: \", async (input) => {\n const trimmed = input.trim();\n if (!trimmed) {\n ask();\n return;\n }\n\n try {\n const response = await agentLoop.processDirect(\n trimmed,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}\\n`);\n } catch (err) {\n console.error(\"Error:\", err);\n }\n ask();\n });\n };\n\n rl.on(\"close\", () => {\n console.log(\"\\nGoodbye!\");\n process.exit(0);\n });\n\n ask();\n }\n });\n\n// ============================================================================\n// Channel Commands\n// ============================================================================\n\nconst channelsCmd = program\n .command(\"channels\")\n .description(\"Manage channels\");\n\nchannelsCmd\n .command(\"status\")\n .description(\"Show channel status\")\n .action(() => {\n const config = loadConfig();\n\n console.log(\"Channel Status\");\n console.log(\"─\".repeat(50));\n\n const tg = config.channels.telegram;\n const tgToken = tg.token\n ? `token: ${tg.token.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` Telegram ${tg.enabled ? \"[enabled]\" : \"[disabled]\"} ${tgToken}`,\n );\n });\n\n// ============================================================================\n// Cron Commands\n// ============================================================================\n\nconst cronCmd = program\n .command(\"cron\")\n .description(\"Manage scheduled tasks\");\n\ncronCmd\n .command(\"list\")\n .description(\"List scheduled jobs\")\n .option(\"-a, --all\", \"Include disabled jobs\", false)\n .action(async (opts) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n const jobs = service.listJobs(opts.all);\n\n if (jobs.length === 0) {\n console.log(\"No scheduled jobs.\");\n return;\n }\n\n console.log(\"Scheduled Jobs\");\n console.log(\"─\".repeat(70));\n console.log(\n `${\"ID\".padEnd(10)} ${\"Name\".padEnd(20)} ${\"Schedule\".padEnd(18)} ${\"Status\".padEnd(10)} Next Run`,\n );\n console.log(\"─\".repeat(70));\n\n for (const job of jobs) {\n let sched: string;\n if (job.schedule.kind === \"every\") {\n sched = `every ${(job.schedule.everyMs ?? 0) / 1000}s`;\n } else if (job.schedule.kind === \"cron\") {\n sched = job.schedule.expr ?? \"\";\n } else {\n sched = \"one-time\";\n }\n\n let nextRun = \"\";\n if (job.state.nextRunAtMs) {\n nextRun = new Date(job.state.nextRunAtMs).toLocaleString();\n }\n\n const status = job.enabled ? \"enabled\" : \"disabled\";\n\n console.log(\n `${job.id.padEnd(10)} ${job.name.padEnd(20)} ${sched.padEnd(18)} ${status.padEnd(10)} ${nextRun}`,\n );\n }\n });\n\ncronCmd\n .command(\"add\")\n .description(\"Add a scheduled job\")\n .requiredOption(\"-n, --name <name>\", \"Job name\")\n .requiredOption(\"-m, --message <text>\", \"Message for agent\")\n .option(\"-e, --every <seconds>\", \"Run every N seconds\")\n .option(\"-c, --cron <expr>\", \"Cron expression (e.g. '0 9 * * *')\")\n .option(\"--at <iso>\", \"Run once at time (ISO format)\")\n .option(\"-d, --deliver\", \"Deliver response to channel\", false)\n .option(\"--to <recipient>\", \"Recipient for delivery\")\n .option(\"--channel <name>\", \"Channel for delivery\")\n .action(async (opts) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n let schedule: { kind: string; everyMs?: number; expr?: string; atMs?: number };\n if (opts.every) {\n schedule = { kind: \"every\", everyMs: Number(opts.every) * 1000 };\n } else if (opts.cron) {\n schedule = { kind: \"cron\", expr: opts.cron };\n } else if (opts.at) {\n const dt = new Date(opts.at);\n schedule = { kind: \"at\", atMs: dt.getTime() };\n } else {\n console.error(\"Error: Must specify --every, --cron, or --at\");\n process.exit(1);\n }\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n const job = service.addJob({\n name: opts.name,\n schedule: schedule as import(\"../cron/types.js\").CronSchedule,\n message: opts.message,\n deliver: opts.deliver,\n to: opts.to,\n channel: opts.channel,\n });\n\n console.log(`Added job '${job.name}' (${job.id})`);\n });\n\ncronCmd\n .command(\"remove\")\n .description(\"Remove a scheduled job\")\n .argument(\"<jobId>\", \"Job ID to remove\")\n .action(async (jobId: string) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n if (service.removeJob(jobId)) {\n console.log(`Removed job ${jobId}`);\n } else {\n console.error(`Job ${jobId} not found`);\n }\n });\n\ncronCmd\n .command(\"enable\")\n .description(\"Enable or disable a job\")\n .argument(\"<jobId>\", \"Job ID\")\n .option(\"--disable\", \"Disable instead of enable\", false)\n .action(async (jobId: string, opts: { disable: boolean }) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n const job = service.enableJob(jobId, !opts.disable);\n if (job) {\n const status = opts.disable ? \"disabled\" : \"enabled\";\n console.log(`Job '${job.name}' ${status}`);\n } else {\n console.error(`Job ${jobId} not found`);\n }\n });\n\n// ============================================================================\n// Status\n// ============================================================================\n\nprogram\n .command(\"status\")\n .description(\"Show nanobot status\")\n .action(() => {\n const configPath = getConfigPath();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Config: ${configPath} ${existsSync(configPath) ? \"[ok]\" : \"[missing]\"}`,\n );\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\n\n if (existsSync(configPath)) {\n console.log(`Model: ${config.agents.defaults.model}`);\n\n const hasOpenrouter = Boolean(config.providers.openrouter.apiKey);\n const hasAnthropic = Boolean(config.providers.anthropic.apiKey);\n const hasOpenai = Boolean(config.providers.openai.apiKey);\n const hasGemini = Boolean(config.providers.gemini.apiKey);\n const hasDeepseek = Boolean(config.providers.deepseek.apiKey);\n const hasOpenaiCompatible = Boolean(config.providers.openaiCompatible.apiKey);\n console.log(\n `OpenRouter API: ${hasOpenrouter ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `Anthropic API: ${hasAnthropic ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `OpenAI API: ${hasOpenai ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `Gemini API: ${hasGemini ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `DeepSeek API: ${hasDeepseek ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `OpenAI Compatible API: ${hasOpenaiCompatible ? \"[set]\" : \"[not set]\"}`,\n );\n }\n });\n\nprogram.parse();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * nanobot CLI - Personal AI Assistant\n */\n\nimport { Command } from \"commander\";\nimport { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath, getDataDir } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { GatewayServer } from \"../gateway/server.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"nanobot\")\n .description(`${LOGO} nanobot - Personal AI Assistant`)\n .version(`${LOGO} nanobot v${VERSION}`, \"-v, --version\");\n\n// ============================================================================\n// Onboard / Setup\n// ============================================================================\n\nprogram\n .command(\"onboard\")\n .description(\"Initialize nanobot configuration and workspace\")\n .action(() => {\n const configPath = getConfigPath();\n\n if (existsSync(configPath)) {\n console.log(`Config already exists at ${configPath}`);\n console.log(\"Delete it first if you want to start fresh.\");\n return;\n }\n\n // Create default config\n const config = ConfigSchema.parse({});\n saveConfig(config);\n console.log(`Created config at ${configPath}`);\n\n // Create workspace\n const workspace = getConfigWorkspacePath(config);\n createWorkspaceTemplates(workspace);\n\n console.log(`\\n${LOGO} nanobot is ready!`);\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Add your API key to ~/.nanobot/config.json\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\n console.log(\n \"\\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps\",\n );\n });\n\nfunction createWorkspaceTemplates(workspace: string): void {\n if (!existsSync(workspace)) {\n mkdirSync(workspace, { recursive: true });\n }\n\n const templates: Record<string, string> = {\n \"AGENTS.md\": `# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n`,\n \"SOUL.md\": `# Soul\n\nI am nanobot, a lightweight AI assistant.\n\n## Personality\n\n- Helpful and friendly\n- Concise and to the point\n- Curious and eager to learn\n\n## Values\n\n- Accuracy over speed\n- User privacy and safety\n- Transparency in actions\n`,\n \"USER.md\": `# User\n\nInformation about the user goes here.\n\n## Preferences\n\n- Communication style: (casual/formal)\n- Timezone: (your timezone)\n- Language: (your preferred language)\n`,\n \"HEARTBEAT.md\": `# Heartbeat\n\nThis file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.\n\n## Tasks\n\n`,\n };\n\n for (const [filename, content] of Object.entries(templates)) {\n const filePath = join(workspace, filename);\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content);\n console.log(` Created ${filename}`);\n }\n }\n\n // Create memory directory and MEMORY.md\n const memoryDir = join(workspace, \"memory\");\n if (!existsSync(memoryDir)) {\n mkdirSync(memoryDir, { recursive: true });\n }\n const memoryFile = join(memoryDir, \"MEMORY.md\");\n if (!existsSync(memoryFile)) {\n writeFileSync(\n memoryFile,\n `# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n`,\n );\n console.log(\" Created memory/MEMORY.md\");\n }\n}\n\n// ============================================================================\n// Config Export / Import\n// ============================================================================\n\n/** Shape of the portable config bundle. */\ninterface ConfigBundle {\n _nanobot: string;\n version: string;\n exportedAt: string;\n config: unknown;\n cronJobs: unknown | null;\n knownChats: unknown | null;\n workspace: Record<string, string>;\n}\n\nconst configCmd = program\n .command(\"config\")\n .description(\"Manage nanobot configuration\");\n\nconfigCmd\n .command(\"export\")\n .description(\"Export config, cron jobs, known chats, and workspace files to a portable JSON bundle\")\n .option(\"-o, --output <path>\", \"Write to file instead of stdout\")\n .action((opts) => {\n const dataDir = getDataDir();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n // Read optional data files\n const readJsonSafe = (p: string): unknown | null => {\n try { return JSON.parse(readFileSync(p, \"utf-8\")); } catch { return null; }\n };\n const readTextSafe = (p: string): string | null => {\n try { return readFileSync(p, \"utf-8\"); } catch { return null; }\n };\n\n // Gather workspace files\n const wsFiles: Record<string, string> = {};\n const workspaceEntries = [\n \"AGENTS.md\", \"SOUL.md\", \"USER.md\", \"TOOLS.md\", \"IDENTITY.md\",\n \"memory/MEMORY.md\",\n ];\n for (const rel of workspaceEntries) {\n const content = readTextSafe(join(workspace, rel));\n if (content !== null) wsFiles[rel] = content;\n }\n\n // Also grab daily memory notes\n const memoryDir = join(workspace, \"memory\");\n if (existsSync(memoryDir)) {\n try {\n for (const f of readdirSync(memoryDir)) {\n if (f.endsWith(\".md\") && f !== \"MEMORY.md\") {\n const content = readTextSafe(join(memoryDir, f));\n if (content !== null) wsFiles[`memory/${f}`] = content;\n }\n }\n } catch { /* ignore */ }\n }\n\n const bundle: ConfigBundle = {\n _nanobot: \"config-bundle\",\n version: VERSION,\n exportedAt: new Date().toISOString(),\n config,\n cronJobs: readJsonSafe(join(dataDir, \"cron\", \"jobs.json\")),\n knownChats: readJsonSafe(join(dataDir, \"known_chats.json\")),\n workspace: wsFiles,\n };\n\n const json = JSON.stringify(bundle, null, 2);\n\n if (opts.output) {\n const outDir = dirname(opts.output);\n if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });\n writeFileSync(opts.output, json);\n console.log(`Exported to ${opts.output}`);\n } else {\n process.stdout.write(json + \"\\n\");\n }\n });\n\nconfigCmd\n .command(\"import\")\n .description(\"Import a config bundle (from file or stdin)\")\n .argument(\"[path]\", \"Path to bundle JSON file (omit to read from stdin)\")\n .option(\"--no-workspace\", \"Skip importing workspace files\")\n .option(\"--no-cron\", \"Skip importing cron jobs\")\n .action(async (path: string | undefined, opts: { workspace: boolean; cron: boolean }) => {\n let raw: string;\n\n if (path) {\n if (!existsSync(path)) {\n console.error(`File not found: ${path}`);\n process.exit(1);\n }\n raw = readFileSync(path, \"utf-8\");\n } else {\n // Read from stdin\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk as Buffer);\n }\n raw = Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n let bundle: ConfigBundle;\n try {\n bundle = JSON.parse(raw);\n } catch {\n console.error(\"Error: Invalid JSON\");\n process.exit(1);\n }\n\n if (bundle._nanobot !== \"config-bundle\") {\n console.error(\"Error: Not a nanobot config bundle\");\n process.exit(1);\n }\n\n const dataDir = getDataDir();\n\n // 1. Restore config\n if (bundle.config) {\n const config = ConfigSchema.parse(bundle.config);\n saveConfig(config);\n console.log(\" Restored config.json\");\n }\n\n // 2. Restore cron jobs\n if (opts.cron && bundle.cronJobs) {\n const cronDir = join(dataDir, \"cron\");\n if (!existsSync(cronDir)) mkdirSync(cronDir, { recursive: true });\n writeFileSync(join(cronDir, \"jobs.json\"), JSON.stringify(bundle.cronJobs, null, 2));\n console.log(\" Restored cron/jobs.json\");\n }\n\n // 3. Restore known chats\n if (bundle.knownChats) {\n writeFileSync(join(dataDir, \"known_chats.json\"), JSON.stringify(bundle.knownChats, null, 2));\n console.log(\" Restored known_chats.json\");\n }\n\n // 4. Restore workspace files\n if (opts.workspace && bundle.workspace) {\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n for (const [rel, content] of Object.entries(bundle.workspace)) {\n const filePath = join(workspace, rel);\n const fileDir = dirname(filePath);\n if (!existsSync(fileDir)) mkdirSync(fileDir, { recursive: true });\n writeFileSync(filePath, content);\n console.log(` Restored ${rel}`);\n }\n }\n\n console.log(`\\n${LOGO} Import complete (from ${bundle.exportedAt})`);\n });\n\n// ============================================================================\n// Gateway / Server\n// ============================================================================\n\nprogram\n .command(\"gateway\")\n .description(\"Start the nanobot gateway\")\n .option(\"-p, --port <number>\", \"Gateway port\", \"18790\")\n .option(\"--verbose\", \"Verbose output\", false)\n .action(async (opts) => {\n console.log(\n `${LOGO} Starting nanobot gateway on port ${opts.port}...`,\n );\n\n const config = loadConfig();\n const apiKey = getApiKey(config);\n const apiBase = getApiBase(config);\n const model = config.agents.defaults.model;\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n console.error(\n \"Set one in ~/.nanobot/config.json under providers.openrouter.apiKey\",\n );\n process.exit(1);\n }\n\n // Dynamic imports to avoid loading heavy deps up front\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n const { ChannelManager } = await import(\"../channels/manager.js\");\n const { CronService } = await import(\"../cron/service.js\");\n const { HeartbeatService } = await import(\n \"../heartbeat/service.js\"\n );\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n });\n\n // Create cron service\n const cronStorePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const cron = new CronService(cronStorePath);\n\n // Create agent\n const agent = new AgentLoop({\n bus,\n provider,\n workspace,\n model,\n maxTokens: config.agents.defaults.maxTokens,\n maxIterations: config.agents.defaults.maxToolIterations,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n cronService: cron,\n });\n\n // Create channel manager (before cron so the callback can broadcast)\n const channels = new ChannelManager(config, bus);\n\n // Wire cron callback (gateway is assigned after server creation below)\n let gateway: GatewayServer | null = null;\n\n cron.onJob = async (job) => {\n const sessionKey = `cron:${job.id}`;\n\n // Snapshot history length so we can extract only this turn's messages\n const session = agent.sessions.getOrCreate(sessionKey);\n const prevLen = session.getHistory().length;\n\n const response = await agent.processDirect(\n job.payload.message,\n sessionKey,\n \"cron\",\n job.id,\n );\n\n // Push the full turn (tool calls + results + final answer) to the web UI via SSE\n if (gateway) {\n const turnMessages = session.getHistory().slice(prevLen);\n for (const msg of turnMessages) {\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n for (const tc of msg.tool_calls) {\n gateway.notifyToolCall(tc.function.name, tc.function.arguments);\n }\n // If the assistant message also has text content, push it\n if (msg.content) {\n gateway.notify(\"assistant\", typeof msg.content === \"string\" ? msg.content : \"\", \"default\");\n }\n } else if (msg.role === \"tool\") {\n const content = typeof msg.content === \"string\" ? msg.content : \"\";\n gateway.notifyToolResult(msg.name ?? \"\", content);\n } else if (msg.role === \"assistant\") {\n gateway.notify(\"assistant\", typeof msg.content === \"string\" ? msg.content : \"\", \"default\");\n }\n }\n }\n\n // Deliver to all configured chat channels (telegram, etc.)\n if (job.payload.deliver) {\n const { createOutboundMessage } = await import(\n \"../bus/events.js\"\n );\n\n for (const channelName of channels.enabledChannels) {\n const chatIds = channels.getKnownChatIds(channelName);\n if (chatIds.length === 0) {\n console.log(`Cron: no known chats for ${channelName}, skipping`);\n continue;\n }\n for (const chatId of chatIds) {\n await bus.publishOutbound(\n createOutboundMessage({\n channel: channelName,\n chatId,\n content: response ?? \"\",\n }),\n );\n }\n }\n }\n\n return response;\n };\n\n // Create heartbeat\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) =>\n agent.processDirect(prompt, \"heartbeat\"),\n intervalS: 30 * 60,\n enabled: true,\n });\n\n const cronStatus = cron.status();\n if (cronStatus.jobs > 0) {\n console.log(`Cron: ${cronStatus.jobs} scheduled jobs`);\n }\n console.log(\"Heartbeat: every 30m\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n heartbeat.stop();\n cron.stop();\n agent.stop();\n await channels.stopAll();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start HTTP gateway server\n const { createGatewayServer } = await import(\n \"../gateway/server.js\"\n );\n gateway = createGatewayServer({\n agent,\n port: Number(opts.port),\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\n await cron.start();\n await heartbeat.start();\n // Both agent.run() and channels.startAll() block until stopped\n await Promise.all([agent.run(), channels.startAll()]);\n } catch (err) {\n console.error(\"Gateway error:\", err);\n process.exit(1);\n }\n });\n\n// ============================================================================\n// Agent Commands\n// ============================================================================\n\nprogram\n .command(\"agent\")\n .description(\"Interact with the agent directly\")\n .option(\"-m, --message <text>\", \"Message to send to the agent\")\n .option(\n \"-s, --session <id>\",\n \"Session ID\",\n \"cli:default\",\n )\n .action(async (opts) => {\n const config = loadConfig();\n const apiKey = getApiKey(config);\n const apiBase = getApiBase(config);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n process.exit(1);\n }\n\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: config.agents.defaults.model,\n });\n\n const agentLoop = new AgentLoop({\n bus,\n provider,\n workspace,\n maxTokens: config.agents.defaults.maxTokens,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n });\n\n if (opts.message) {\n // Single message mode\n const response = await agentLoop.processDirect(\n opts.message,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}`);\n } else {\n // Interactive mode\n console.log(`${LOGO} Interactive mode (Ctrl+C to exit)\\n`);\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const ask = (): void => {\n rl.question(\"You: \", async (input) => {\n const trimmed = input.trim();\n if (!trimmed) {\n ask();\n return;\n }\n\n try {\n const response = await agentLoop.processDirect(\n trimmed,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}\\n`);\n } catch (err) {\n console.error(\"Error:\", err);\n }\n ask();\n });\n };\n\n rl.on(\"close\", () => {\n console.log(\"\\nGoodbye!\");\n process.exit(0);\n });\n\n ask();\n }\n });\n\n// ============================================================================\n// Channel Commands\n// ============================================================================\n\nconst channelsCmd = program\n .command(\"channels\")\n .description(\"Manage channels\");\n\nchannelsCmd\n .command(\"status\")\n .description(\"Show channel status\")\n .action(() => {\n const config = loadConfig();\n\n console.log(\"Channel Status\");\n console.log(\"─\".repeat(50));\n\n const tg = config.channels.telegram;\n const tgToken = tg.token\n ? `token: ${tg.token.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` Telegram ${tg.enabled ? \"[enabled]\" : \"[disabled]\"} ${tgToken}`,\n );\n });\n\n// ============================================================================\n// Cron Commands\n// ============================================================================\n\nconst cronCmd = program\n .command(\"cron\")\n .description(\"Manage scheduled tasks\");\n\ncronCmd\n .command(\"list\")\n .description(\"List scheduled jobs\")\n .option(\"-a, --all\", \"Include disabled jobs\", false)\n .action(async (opts) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n const jobs = service.listJobs(opts.all);\n\n if (jobs.length === 0) {\n console.log(\"No scheduled jobs.\");\n return;\n }\n\n console.log(\"Scheduled Jobs\");\n console.log(\"─\".repeat(70));\n console.log(\n `${\"ID\".padEnd(10)} ${\"Name\".padEnd(20)} ${\"Schedule\".padEnd(18)} ${\"Status\".padEnd(10)} Next Run`,\n );\n console.log(\"─\".repeat(70));\n\n for (const job of jobs) {\n let sched: string;\n if (job.schedule.kind === \"every\") {\n sched = `every ${(job.schedule.everyMs ?? 0) / 1000}s`;\n } else if (job.schedule.kind === \"cron\") {\n sched = job.schedule.expr ?? \"\";\n } else {\n sched = \"one-time\";\n }\n\n let nextRun = \"\";\n if (job.state.nextRunAtMs) {\n nextRun = new Date(job.state.nextRunAtMs).toLocaleString();\n }\n\n const status = job.enabled ? \"enabled\" : \"disabled\";\n\n console.log(\n `${job.id.padEnd(10)} ${job.name.padEnd(20)} ${sched.padEnd(18)} ${status.padEnd(10)} ${nextRun}`,\n );\n }\n });\n\ncronCmd\n .command(\"add\")\n .description(\"Add a scheduled job\")\n .requiredOption(\"-n, --name <name>\", \"Job name\")\n .requiredOption(\"-m, --message <text>\", \"Message for agent\")\n .option(\"-e, --every <seconds>\", \"Run every N seconds\")\n .option(\"-c, --cron <expr>\", \"Cron expression (e.g. '0 9 * * *')\")\n .option(\"--at <iso>\", \"Run once at time (ISO format)\")\n .option(\"-d, --deliver\", \"Deliver response to channel\", false)\n .option(\"--to <recipient>\", \"Recipient for delivery\")\n .option(\"--channel <name>\", \"Channel for delivery\")\n .action(async (opts) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n let schedule: { kind: string; everyMs?: number; expr?: string; atMs?: number };\n if (opts.every) {\n schedule = { kind: \"every\", everyMs: Number(opts.every) * 1000 };\n } else if (opts.cron) {\n schedule = { kind: \"cron\", expr: opts.cron };\n } else if (opts.at) {\n const dt = new Date(opts.at);\n schedule = { kind: \"at\", atMs: dt.getTime() };\n } else {\n console.error(\"Error: Must specify --every, --cron, or --at\");\n process.exit(1);\n }\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n const job = service.addJob({\n name: opts.name,\n schedule: schedule as import(\"../cron/types.js\").CronSchedule,\n message: opts.message,\n deliver: opts.deliver,\n to: opts.to,\n channel: opts.channel,\n });\n\n console.log(`Added job '${job.name}' (${job.id})`);\n });\n\ncronCmd\n .command(\"remove\")\n .description(\"Remove a scheduled job\")\n .argument(\"<jobId>\", \"Job ID to remove\")\n .action(async (jobId: string) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n if (service.removeJob(jobId)) {\n console.log(`Removed job ${jobId}`);\n } else {\n console.error(`Job ${jobId} not found`);\n }\n });\n\ncronCmd\n .command(\"enable\")\n .description(\"Enable or disable a job\")\n .argument(\"<jobId>\", \"Job ID\")\n .option(\"--disable\", \"Disable instead of enable\", false)\n .action(async (jobId: string, opts: { disable: boolean }) => {\n const { CronService } = await import(\"../cron/service.js\");\n\n const storePath = join(getDataDir(), \"cron\", \"jobs.json\");\n const service = new CronService(storePath);\n\n const job = service.enableJob(jobId, !opts.disable);\n if (job) {\n const status = opts.disable ? \"disabled\" : \"enabled\";\n console.log(`Job '${job.name}' ${status}`);\n } else {\n console.error(`Job ${jobId} not found`);\n }\n });\n\n// ============================================================================\n// Status\n// ============================================================================\n\nprogram\n .command(\"status\")\n .description(\"Show nanobot status\")\n .action(() => {\n const configPath = getConfigPath();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Config: ${configPath} ${existsSync(configPath) ? \"[ok]\" : \"[missing]\"}`,\n );\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\n\n if (existsSync(configPath)) {\n console.log(`Model: ${config.agents.defaults.model}`);\n\n const hasOpenrouter = Boolean(config.providers.openrouter.apiKey);\n const hasAnthropic = Boolean(config.providers.anthropic.apiKey);\n const hasOpenai = Boolean(config.providers.openai.apiKey);\n const hasGemini = Boolean(config.providers.gemini.apiKey);\n const hasDeepseek = Boolean(config.providers.deepseek.apiKey);\n const hasOpenaiCompatible = Boolean(config.providers.openaiCompatible.apiKey);\n console.log(\n `OpenRouter API: ${hasOpenrouter ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `Anthropic API: ${hasAnthropic ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `OpenAI API: ${hasOpenai ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `Gemini API: ${hasGemini ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `DeepSeek API: ${hasDeepseek ? \"[set]\" : \"[not set]\"}`,\n );\n console.log(\n `OpenAI Compatible API: ${hasOpenaiCompatible ? \"[set]\" : \"[not set]\"}`,\n );\n }\n });\n\nprogram.parse();\n"],"mappings":"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"}
|
package/dist/config/schema.d.mts
CHANGED
|
@@ -339,27 +339,27 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
339
339
|
restrictToWorkspace?: boolean | undefined;
|
|
340
340
|
}>>;
|
|
341
341
|
}, "strip", z.ZodTypeAny, {
|
|
342
|
-
exec: {
|
|
343
|
-
timeout: number;
|
|
344
|
-
restrictToWorkspace: boolean;
|
|
345
|
-
};
|
|
346
342
|
web: {
|
|
347
343
|
search: {
|
|
348
344
|
apiKey: string;
|
|
349
345
|
maxResults: number;
|
|
350
346
|
};
|
|
351
347
|
};
|
|
348
|
+
exec: {
|
|
349
|
+
timeout: number;
|
|
350
|
+
restrictToWorkspace: boolean;
|
|
351
|
+
};
|
|
352
352
|
}, {
|
|
353
|
-
exec?: {
|
|
354
|
-
timeout?: number | undefined;
|
|
355
|
-
restrictToWorkspace?: boolean | undefined;
|
|
356
|
-
} | undefined;
|
|
357
353
|
web?: {
|
|
358
354
|
search?: {
|
|
359
355
|
apiKey?: string | undefined;
|
|
360
356
|
maxResults?: number | undefined;
|
|
361
357
|
} | undefined;
|
|
362
358
|
} | undefined;
|
|
359
|
+
exec?: {
|
|
360
|
+
timeout?: number | undefined;
|
|
361
|
+
restrictToWorkspace?: boolean | undefined;
|
|
362
|
+
} | undefined;
|
|
363
363
|
}>;
|
|
364
364
|
declare const ConfigSchema: z.ZodObject<{
|
|
365
365
|
agents: z.ZodDefault<z.ZodObject<{
|
|
@@ -605,27 +605,27 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
605
605
|
restrictToWorkspace?: boolean | undefined;
|
|
606
606
|
}>>;
|
|
607
607
|
}, "strip", z.ZodTypeAny, {
|
|
608
|
-
exec: {
|
|
609
|
-
timeout: number;
|
|
610
|
-
restrictToWorkspace: boolean;
|
|
611
|
-
};
|
|
612
608
|
web: {
|
|
613
609
|
search: {
|
|
614
610
|
apiKey: string;
|
|
615
611
|
maxResults: number;
|
|
616
612
|
};
|
|
617
613
|
};
|
|
614
|
+
exec: {
|
|
615
|
+
timeout: number;
|
|
616
|
+
restrictToWorkspace: boolean;
|
|
617
|
+
};
|
|
618
618
|
}, {
|
|
619
|
-
exec?: {
|
|
620
|
-
timeout?: number | undefined;
|
|
621
|
-
restrictToWorkspace?: boolean | undefined;
|
|
622
|
-
} | undefined;
|
|
623
619
|
web?: {
|
|
624
620
|
search?: {
|
|
625
621
|
apiKey?: string | undefined;
|
|
626
622
|
maxResults?: number | undefined;
|
|
627
623
|
} | undefined;
|
|
628
624
|
} | undefined;
|
|
625
|
+
exec?: {
|
|
626
|
+
timeout?: number | undefined;
|
|
627
|
+
restrictToWorkspace?: boolean | undefined;
|
|
628
|
+
} | undefined;
|
|
629
629
|
}>>;
|
|
630
630
|
}, "strip", z.ZodTypeAny, {
|
|
631
631
|
agents: {
|
|
@@ -680,16 +680,16 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
680
680
|
port: number;
|
|
681
681
|
};
|
|
682
682
|
tools: {
|
|
683
|
-
exec: {
|
|
684
|
-
timeout: number;
|
|
685
|
-
restrictToWorkspace: boolean;
|
|
686
|
-
};
|
|
687
683
|
web: {
|
|
688
684
|
search: {
|
|
689
685
|
apiKey: string;
|
|
690
686
|
maxResults: number;
|
|
691
687
|
};
|
|
692
688
|
};
|
|
689
|
+
exec: {
|
|
690
|
+
timeout: number;
|
|
691
|
+
restrictToWorkspace: boolean;
|
|
692
|
+
};
|
|
693
693
|
};
|
|
694
694
|
}, {
|
|
695
695
|
agents?: {
|
|
@@ -744,16 +744,16 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
744
744
|
port?: number | undefined;
|
|
745
745
|
} | undefined;
|
|
746
746
|
tools?: {
|
|
747
|
-
exec?: {
|
|
748
|
-
timeout?: number | undefined;
|
|
749
|
-
restrictToWorkspace?: boolean | undefined;
|
|
750
|
-
} | undefined;
|
|
751
747
|
web?: {
|
|
752
748
|
search?: {
|
|
753
749
|
apiKey?: string | undefined;
|
|
754
750
|
maxResults?: number | undefined;
|
|
755
751
|
} | undefined;
|
|
756
752
|
} | undefined;
|
|
753
|
+
exec?: {
|
|
754
|
+
timeout?: number | undefined;
|
|
755
|
+
restrictToWorkspace?: boolean | undefined;
|
|
756
|
+
} | undefined;
|
|
757
757
|
} | undefined;
|
|
758
758
|
}>;
|
|
759
759
|
type Config = z.infer<typeof ConfigSchema>;
|