@jcheesepkg/nanobot 0.6.1 → 0.6.2

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.
@@ -17,6 +17,11 @@ declare class CronTool extends Tool {
17
17
  type: string;
18
18
  description: string;
19
19
  };
20
+ kind: {
21
+ type: string;
22
+ enum: string[];
23
+ description: string;
24
+ };
20
25
  every_seconds: {
21
26
  type: string;
22
27
  description: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cron.d.mts","names":[],"sources":["../../../src/agent/tools/cron.ts"],"mappings":";;;;cAGa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCD,OAAA;EAAA,QACA,MAAA;EAAA;EAAA,QAGA,SAAA;EAEA;EAAA,QAAA,MAAA;;EASG;EAAX,UAAA,CAAW,OAAA,UAAiB,MAAA;EAKtB,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;EAAA,QAkBhC,MAAA;EAAA,QAsDA,QAAA;EAAA,QAyBA,SAAA;AAAA"}
1
+ {"version":3,"file":"cron.d.mts","names":[],"sources":["../../../src/agent/tools/cron.ts"],"mappings":";;;;cAGa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuCD,OAAA;EAAA,QACA,MAAA;EAGA;EAAA,QAAA,SAAA;;UAEA,MAAA;;EASoB;EAA5B,UAAA,CAAW,OAAA,UAAiB,MAAA;EAKtB,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;EAAA,QAkBhC,MAAA;EAAA,QAwDA,QAAA;EAAA,QAyBA,SAAA;AAAA"}
@@ -19,7 +19,12 @@ var CronTool = class extends Tool {
19
19
  },
20
20
  message: {
21
21
  type: "string",
22
- description: "Reminder message (for add)"
22
+ description: "Reminder message or task prompt (for add)"
23
+ },
24
+ kind: {
25
+ type: "string",
26
+ enum: ["direct", "task"],
27
+ description: "direct: send message verbatim to user (default, for reminders). task: run message through the agent and deliver result."
23
28
  },
24
29
  every_seconds: {
25
30
  type: "integer",
@@ -68,6 +73,7 @@ var CronTool = class extends Tool {
68
73
  }
69
74
  async addJob(args) {
70
75
  const message = args.message ? String(args.message) : "";
76
+ const kind = args.kind === "task" ? "task" : "direct";
71
77
  const everySeconds = args.every_seconds ? Number(args.every_seconds) : null;
72
78
  const cronExpr = args.cron_expr ? String(args.cron_expr) : null;
73
79
  const atRaw = args.at ? String(args.at) : null;
@@ -91,6 +97,7 @@ var CronTool = class extends Tool {
91
97
  type: everySeconds ? "every" : cronExpr ? "cron" : "scheduled",
92
98
  schedule,
93
99
  message,
100
+ kind,
94
101
  deliver: true,
95
102
  channel: this.channel,
96
103
  chatId: this.chatId
@@ -99,7 +106,7 @@ var CronTool = class extends Tool {
99
106
  });
100
107
  const data = await res.json();
101
108
  if (!res.ok) return `Error: ${data.error ?? res.statusText}`;
102
- return `Created job (id: ${data.id})`;
109
+ return `Created ${kind} job (id: ${data.id})`;
103
110
  } catch (err) {
104
111
  return `Error scheduling job: ${err instanceof Error ? err.message : err}`;
105
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"cron.mjs","names":[],"sources":["../../../src/agent/tools/cron.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\n/** Tool to schedule reminders and recurring tasks via the DO scheduler. */\nexport class CronTool extends Tool {\n readonly name = \"cron\";\n readonly description =\n \"Schedule one-off or recurring tasks. Actions: add, list, remove.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"add\", \"list\", \"remove\"],\n description: \"Action to perform\",\n },\n message: {\n type: \"string\",\n description: \"Reminder message (for add)\",\n },\n every_seconds: {\n type: \"integer\",\n description: \"Interval in seconds (for recurring tasks)\",\n },\n cron_expr: {\n type: \"string\",\n description: \"Cron expression like '0 9 * * *' (for scheduled tasks)\",\n },\n at: {\n type: \"string\",\n description:\n \"Run once at a specific time. ISO 8601 string (e.g. '2025-03-01T09:00:00Z')\",\n },\n job_id: {\n type: \"string\",\n description: \"Job ID (for remove)\",\n },\n },\n required: [\"action\"],\n };\n\n private channel = \"\";\n private chatId = \"\";\n\n /** Worker URL for DO scheduling. */\n private workerUrl: string;\n /** User ID for DO scheduling. */\n private userId: string;\n\n constructor() {\n super();\n this.workerUrl = process.env.WORKER_URL ?? \"\";\n this.userId = process.env.NANOBOT_USER_ID ?? \"\";\n }\n\n /** Set the current session context for delivery. */\n setContext(channel: string, chatId: string): void {\n this.channel = channel;\n this.chatId = chatId;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n if (!this.workerUrl || !this.userId) {\n return \"Error: cron scheduling requires WORKER_URL and NANOBOT_USER_ID\";\n }\n\n const action = String(args.action);\n switch (action) {\n case \"add\":\n return this.addJob(args);\n case \"list\":\n return this.listJobs();\n case \"remove\":\n return this.removeJob(args);\n default:\n return `Unknown action: ${action}`;\n }\n }\n\n private async addJob(args: Record<string, unknown>): Promise<string> {\n const message = args.message ? String(args.message) : \"\";\n const everySeconds = args.every_seconds\n ? Number(args.every_seconds)\n : null;\n const cronExpr = args.cron_expr ? String(args.cron_expr) : null;\n const atRaw = args.at ? String(args.at) : null;\n\n if (!message) return \"Error: message is required for add\";\n if (!this.channel || !this.chatId)\n return \"Error: no session context (channel/chatId)\";\n\n let schedule: string | number;\n if (atRaw) {\n const atMs = Date.parse(atRaw);\n if (isNaN(atMs)) return `Error: could not parse time '${atRaw}' — use ISO 8601 format`;\n if (atMs <= Date.now()) return \"Error: scheduled time is in the past\";\n\n schedule = atRaw;\n } else if (everySeconds) {\n // For recurring by interval, use seconds\n schedule = everySeconds;\n } else if (cronExpr) {\n schedule = cronExpr;\n } else {\n return \"Error: one of 'at', 'every_seconds', or 'cron_expr' is required\";\n }\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name: message,\n type: everySeconds ? \"every\" : cronExpr ? \"cron\" : \"scheduled\",\n schedule,\n message,\n deliver: true,\n channel: this.channel,\n chatId: this.chatId,\n }),\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { id?: string; error?: string };\n if (!res.ok) return `Error: ${data.error ?? res.statusText}`;\n return `Created job (id: ${data.id})`;\n } catch (err) {\n return `Error scheduling job: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async listJobs(): Promise<string> {\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n { signal: AbortSignal.timeout(10_000) },\n );\n const data = await res.json() as {\n crons: Array<{\n id: string;\n type: string;\n name: string;\n message: string;\n nextRunAt: number;\n }>;\n };\n if (data.crons.length === 0) return \"No scheduled jobs.\";\n const lines = data.crons.map(\n (c) => `- ${c.name} (id: ${c.id}, ${c.type}, next: ${new Date(c.nextRunAt).toISOString()})`,\n );\n return \"Scheduled jobs:\\n\" + lines.join(\"\\n\");\n } catch (err) {\n return `Error listing jobs: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async removeJob(args: Record<string, unknown>): Promise<string> {\n const jobId = args.job_id ? String(args.job_id) : null;\n if (!jobId) return \"Error: job_id is required for remove\";\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron/${jobId}`,\n {\n method: \"DELETE\",\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { ok: boolean };\n return data.ok ? `Removed job ${jobId}` : `Job ${jobId} not found`;\n } catch (err) {\n return `Error removing job: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cron.mjs","names":[],"sources":["../../../src/agent/tools/cron.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\n/** Tool to schedule reminders and recurring tasks via the DO scheduler. */\nexport class CronTool extends Tool {\n readonly name = \"cron\";\n readonly description =\n \"Schedule one-off or recurring tasks. Actions: add, list, remove.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"add\", \"list\", \"remove\"],\n description: \"Action to perform\",\n },\n message: {\n type: \"string\",\n description: \"Reminder message or task prompt (for add)\",\n },\n kind: {\n type: \"string\",\n enum: [\"direct\", \"task\"],\n description:\n \"direct: send message verbatim to user (default, for reminders). task: run message through the agent and deliver result.\",\n },\n every_seconds: {\n type: \"integer\",\n description: \"Interval in seconds (for recurring tasks)\",\n },\n cron_expr: {\n type: \"string\",\n description: \"Cron expression like '0 9 * * *' (for scheduled tasks)\",\n },\n at: {\n type: \"string\",\n description:\n \"Run once at a specific time. ISO 8601 string (e.g. '2025-03-01T09:00:00Z')\",\n },\n job_id: {\n type: \"string\",\n description: \"Job ID (for remove)\",\n },\n },\n required: [\"action\"],\n };\n\n private channel = \"\";\n private chatId = \"\";\n\n /** Worker URL for DO scheduling. */\n private workerUrl: string;\n /** User ID for DO scheduling. */\n private userId: string;\n\n constructor() {\n super();\n this.workerUrl = process.env.WORKER_URL ?? \"\";\n this.userId = process.env.NANOBOT_USER_ID ?? \"\";\n }\n\n /** Set the current session context for delivery. */\n setContext(channel: string, chatId: string): void {\n this.channel = channel;\n this.chatId = chatId;\n }\n\n async execute(args: Record<string, unknown>): Promise<string> {\n if (!this.workerUrl || !this.userId) {\n return \"Error: cron scheduling requires WORKER_URL and NANOBOT_USER_ID\";\n }\n\n const action = String(args.action);\n switch (action) {\n case \"add\":\n return this.addJob(args);\n case \"list\":\n return this.listJobs();\n case \"remove\":\n return this.removeJob(args);\n default:\n return `Unknown action: ${action}`;\n }\n }\n\n private async addJob(args: Record<string, unknown>): Promise<string> {\n const message = args.message ? String(args.message) : \"\";\n const kind = args.kind === \"task\" ? \"task\" : \"direct\"; // default: direct\n const everySeconds = args.every_seconds\n ? Number(args.every_seconds)\n : null;\n const cronExpr = args.cron_expr ? String(args.cron_expr) : null;\n const atRaw = args.at ? String(args.at) : null;\n\n if (!message) return \"Error: message is required for add\";\n if (!this.channel || !this.chatId)\n return \"Error: no session context (channel/chatId)\";\n\n let schedule: string | number;\n if (atRaw) {\n const atMs = Date.parse(atRaw);\n if (isNaN(atMs)) return `Error: could not parse time '${atRaw}' — use ISO 8601 format`;\n if (atMs <= Date.now()) return \"Error: scheduled time is in the past\";\n\n schedule = atRaw;\n } else if (everySeconds) {\n // For recurring by interval, use seconds\n schedule = everySeconds;\n } else if (cronExpr) {\n schedule = cronExpr;\n } else {\n return \"Error: one of 'at', 'every_seconds', or 'cron_expr' is required\";\n }\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n name: message,\n type: everySeconds ? \"every\" : cronExpr ? \"cron\" : \"scheduled\",\n schedule,\n message,\n kind,\n deliver: true,\n channel: this.channel,\n chatId: this.chatId,\n }),\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { id?: string; error?: string };\n if (!res.ok) return `Error: ${data.error ?? res.statusText}`;\n return `Created ${kind} job (id: ${data.id})`;\n } catch (err) {\n return `Error scheduling job: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async listJobs(): Promise<string> {\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron`,\n { signal: AbortSignal.timeout(10_000) },\n );\n const data = await res.json() as {\n crons: Array<{\n id: string;\n type: string;\n name: string;\n message: string;\n nextRunAt: number;\n }>;\n };\n if (data.crons.length === 0) return \"No scheduled jobs.\";\n const lines = data.crons.map(\n (c) => `- ${c.name} (id: ${c.id}, ${c.type}, next: ${new Date(c.nextRunAt).toISOString()})`,\n );\n return \"Scheduled jobs:\\n\" + lines.join(\"\\n\");\n } catch (err) {\n return `Error listing jobs: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private async removeJob(args: Record<string, unknown>): Promise<string> {\n const jobId = args.job_id ? String(args.job_id) : null;\n if (!jobId) return \"Error: job_id is required for remove\";\n\n try {\n const res = await fetch(\n `${this.workerUrl}/api/users/${this.userId}/cron/${jobId}`,\n {\n method: \"DELETE\",\n signal: AbortSignal.timeout(10_000),\n },\n );\n const data = await res.json() as { ok: boolean };\n return data.ok ? `Removed job ${jobId}` : `Job ${jobId} not found`;\n } catch (err) {\n return `Error removing job: ${err instanceof Error ? err.message : err}`;\n }\n }\n}\n"],"mappings":"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"}
@@ -190,6 +190,7 @@ program.command("gateway").description("Start the nanobot gateway").option("-p,
190
190
  const { createGatewayServer } = await import("../gateway/server.mjs");
191
191
  createGatewayServer({
192
192
  agent,
193
+ bus,
193
194
  port: Number(opts.port),
194
195
  channels,
195
196
  heartbeat
@@ -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, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n getExtraHeaders,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { PROVIDERS } from \"../providers/registry.js\";\nimport type { Tool } from \"../agent/tools/base.js\";\n\nconst program = new Command();\n\nasync function loadCustomTools(config: Config): Promise<Tool[]> {\n const customConfigs = config.tools.custom ?? [];\n if (customConfigs.length === 0) return [];\n\n const tools: Tool[] = [];\n for (const entry of customConfigs) {\n try {\n const modulePath = isAbsolute(entry.module)\n ? entry.module\n : resolve(process.cwd(), entry.module);\n const mod = await import(pathToFileURL(modulePath).href);\n const exportName = entry.export ?? \"default\";\n const exported = exportName === \"default\" ? mod.default : mod[exportName];\n\n if (!exported) {\n throw new Error(`Export '${exportName}' not found`);\n }\n\n let instance: unknown;\n if (typeof exported === \"function\") {\n try {\n instance = new exported(entry.options ?? {});\n } catch {\n instance = exported(entry.options ?? {});\n }\n } else {\n throw new Error(`Export '${exportName}' is not a function`);\n }\n\n if (\n instance &&\n typeof instance === \"object\" &&\n \"execute\" in instance &&\n \"name\" in instance\n ) {\n tools.push(instance as Tool);\n } else {\n throw new Error(`Export '${exportName}' did not return a Tool instance`);\n }\n } catch (err) {\n console.warn(\n `Warning: Failed to load custom tool '${entry.module}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return tools;\n}\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// 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 model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, 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.<provider>.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\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 extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\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 restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n // Create channel manager\n const channels = new ChannelManager(config, bus);\n\n // Heartbeat service — no timer (DO drives the schedule),\n // but provides smart file-reading logic for /api/heartbeat\n const { HeartbeatService } = await import(\"../heartbeat/service.js\");\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) => agent.processDirect(prompt, \"heartbeat\"),\n });\n\n console.log(\"Heartbeat: managed by DO (local endpoint /api/heartbeat)\");\n console.log(\"Cron: managed by DO\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\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 createGatewayServer({\n agent,\n port: Number(opts.port),\n channels,\n heartbeat,\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\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 model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\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: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\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 restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\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 const ln = config.channels.line;\n const lnToken = ln.channelAccessToken\n ? `token: ${ln.channelAccessToken.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` LINE ${ln.enabled ? \"[enabled]\" : \"[disabled]\"} ${lnToken}`,\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 console.log(\"\");\n console.log(\"Providers\");\n console.log(\"─\".repeat(50));\n\n const providers = config.providers as Record<string, { apiKey: string }>;\n for (const spec of PROVIDERS) {\n const p = providers[spec.name];\n if (!p) continue;\n const status = p.apiKey ? \"[set]\" : \"[not set]\";\n console.log(` ${spec.displayName.padEnd(16)} ${status}`);\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, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n getExtraHeaders,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { PROVIDERS } from \"../providers/registry.js\";\nimport type { Tool } from \"../agent/tools/base.js\";\n\nconst program = new Command();\n\nasync function loadCustomTools(config: Config): Promise<Tool[]> {\n const customConfigs = config.tools.custom ?? [];\n if (customConfigs.length === 0) return [];\n\n const tools: Tool[] = [];\n for (const entry of customConfigs) {\n try {\n const modulePath = isAbsolute(entry.module)\n ? entry.module\n : resolve(process.cwd(), entry.module);\n const mod = await import(pathToFileURL(modulePath).href);\n const exportName = entry.export ?? \"default\";\n const exported = exportName === \"default\" ? mod.default : mod[exportName];\n\n if (!exported) {\n throw new Error(`Export '${exportName}' not found`);\n }\n\n let instance: unknown;\n if (typeof exported === \"function\") {\n try {\n instance = new exported(entry.options ?? {});\n } catch {\n instance = exported(entry.options ?? {});\n }\n } else {\n throw new Error(`Export '${exportName}' is not a function`);\n }\n\n if (\n instance &&\n typeof instance === \"object\" &&\n \"execute\" in instance &&\n \"name\" in instance\n ) {\n tools.push(instance as Tool);\n } else {\n throw new Error(`Export '${exportName}' did not return a Tool instance`);\n }\n } catch (err) {\n console.warn(\n `Warning: Failed to load custom tool '${entry.module}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return tools;\n}\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// 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 model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, 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.<provider>.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\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 extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\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 restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n // Create channel manager\n const channels = new ChannelManager(config, bus);\n\n // Heartbeat service — no timer (DO drives the schedule),\n // but provides smart file-reading logic for /api/heartbeat\n const { HeartbeatService } = await import(\"../heartbeat/service.js\");\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) => agent.processDirect(prompt, \"heartbeat\"),\n });\n\n console.log(\"Heartbeat: managed by DO (local endpoint /api/heartbeat)\");\n console.log(\"Cron: managed by DO\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\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 createGatewayServer({\n agent,\n bus,\n port: Number(opts.port),\n channels,\n heartbeat,\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\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 model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\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: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\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 restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\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 const ln = config.channels.line;\n const lnToken = ln.channelAccessToken\n ? `token: ${ln.channelAccessToken.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` LINE ${ln.enabled ? \"[enabled]\" : \"[disabled]\"} ${lnToken}`,\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 console.log(\"\");\n console.log(\"Providers\");\n console.log(\"─\".repeat(50));\n\n const providers = config.providers as Record<string, { apiKey: string }>;\n for (const spec of PROVIDERS) {\n const p = providers[spec.name];\n if (!p) continue;\n const status = p.apiKey ? \"[set]\" : \"[not set]\";\n console.log(` ${spec.displayName.padEnd(16)} ${status}`);\n }\n }\n });\n\nprogram.parse();\n"],"mappings":"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"}
@@ -539,15 +539,15 @@ declare const ToolsConfigSchema: z.ZodObject<{
539
539
  export?: string | undefined;
540
540
  }>, "many">>;
541
541
  }, "strip", z.ZodTypeAny, {
542
- exec: {
543
- timeout: number;
544
- };
545
542
  web: {
546
543
  search: {
547
544
  apiKey: string;
548
545
  maxResults: number;
549
546
  };
550
547
  };
548
+ exec: {
549
+ timeout: number;
550
+ };
551
551
  restrictToWorkspace: boolean;
552
552
  enabled?: string[] | undefined;
553
553
  disabled?: string[] | undefined;
@@ -557,9 +557,6 @@ declare const ToolsConfigSchema: z.ZodObject<{
557
557
  export?: string | undefined;
558
558
  }[] | undefined;
559
559
  }, {
560
- exec?: {
561
- timeout?: number | undefined;
562
- } | undefined;
563
560
  enabled?: string[] | undefined;
564
561
  web?: {
565
562
  search?: {
@@ -567,6 +564,9 @@ declare const ToolsConfigSchema: z.ZodObject<{
567
564
  maxResults?: number | undefined;
568
565
  } | undefined;
569
566
  } | undefined;
567
+ exec?: {
568
+ timeout?: number | undefined;
569
+ } | undefined;
570
570
  restrictToWorkspace?: boolean | undefined;
571
571
  disabled?: string[] | undefined;
572
572
  custom?: {
@@ -988,15 +988,15 @@ declare const ConfigSchema: z.ZodObject<{
988
988
  export?: string | undefined;
989
989
  }>, "many">>;
990
990
  }, "strip", z.ZodTypeAny, {
991
- exec: {
992
- timeout: number;
993
- };
994
991
  web: {
995
992
  search: {
996
993
  apiKey: string;
997
994
  maxResults: number;
998
995
  };
999
996
  };
997
+ exec: {
998
+ timeout: number;
999
+ };
1000
1000
  restrictToWorkspace: boolean;
1001
1001
  enabled?: string[] | undefined;
1002
1002
  disabled?: string[] | undefined;
@@ -1006,9 +1006,6 @@ declare const ConfigSchema: z.ZodObject<{
1006
1006
  export?: string | undefined;
1007
1007
  }[] | undefined;
1008
1008
  }, {
1009
- exec?: {
1010
- timeout?: number | undefined;
1011
- } | undefined;
1012
1009
  enabled?: string[] | undefined;
1013
1010
  web?: {
1014
1011
  search?: {
@@ -1016,6 +1013,9 @@ declare const ConfigSchema: z.ZodObject<{
1016
1013
  maxResults?: number | undefined;
1017
1014
  } | undefined;
1018
1015
  } | undefined;
1016
+ exec?: {
1017
+ timeout?: number | undefined;
1018
+ } | undefined;
1019
1019
  restrictToWorkspace?: boolean | undefined;
1020
1020
  disabled?: string[] | undefined;
1021
1021
  custom?: {
@@ -1110,15 +1110,15 @@ declare const ConfigSchema: z.ZodObject<{
1110
1110
  port: number;
1111
1111
  };
1112
1112
  tools: {
1113
- exec: {
1114
- timeout: number;
1115
- };
1116
1113
  web: {
1117
1114
  search: {
1118
1115
  apiKey: string;
1119
1116
  maxResults: number;
1120
1117
  };
1121
1118
  };
1119
+ exec: {
1120
+ timeout: number;
1121
+ };
1122
1122
  restrictToWorkspace: boolean;
1123
1123
  enabled?: string[] | undefined;
1124
1124
  disabled?: string[] | undefined;
@@ -1214,9 +1214,6 @@ declare const ConfigSchema: z.ZodObject<{
1214
1214
  port?: number | undefined;
1215
1215
  } | undefined;
1216
1216
  tools?: {
1217
- exec?: {
1218
- timeout?: number | undefined;
1219
- } | undefined;
1220
1217
  enabled?: string[] | undefined;
1221
1218
  web?: {
1222
1219
  search?: {
@@ -1224,6 +1221,9 @@ declare const ConfigSchema: z.ZodObject<{
1224
1221
  maxResults?: number | undefined;
1225
1222
  } | undefined;
1226
1223
  } | undefined;
1224
+ exec?: {
1225
+ timeout?: number | undefined;
1226
+ } | undefined;
1227
1227
  restrictToWorkspace?: boolean | undefined;
1228
1228
  disabled?: string[] | undefined;
1229
1229
  custom?: {
@@ -1,3 +1,4 @@
1
+ import { MessageBus } from "../bus/queue.mjs";
1
2
  import { AgentLoop } from "../agent/loop.mjs";
2
3
  import { ChannelManager } from "../channels/manager.mjs";
3
4
  import { HeartbeatService } from "../heartbeat/service.mjs";
@@ -5,6 +6,7 @@ import { HeartbeatService } from "../heartbeat/service.mjs";
5
6
  //#region src/gateway/server.d.ts
6
7
  interface GatewayServerOptions {
7
8
  agent: AgentLoop;
9
+ bus: MessageBus;
8
10
  port: number;
9
11
  channels?: ChannelManager;
10
12
  heartbeat?: HeartbeatService;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../../src/gateway/server.ts"],"mappings":";;;;;UASiB,oBAAA;EACf,KAAA,EAAO,SAAA;EACP,IAAA;EACA,QAAA,GAAW,cAAA;EACX,SAAA,GAAY,gBAAA;AAAA;AAAA,UAGG,aAAA;EAHH;EAKZ,MAAA,CAAO,IAAA,UAAc,OAAA,UAAiB,MAAA;EALV;EAO5B,cAAA,CAAe,IAAA,UAAc,IAAA;EAVtB;EAYP,gBAAA,CAAiB,IAAA,UAAc,MAAA;AAAA;AAAA,iBAGjB,mBAAA,CAAoB,IAAA,EAAM,oBAAA,GAAuB,aAAA"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../../src/gateway/server.ts"],"mappings":";;;;;;UAWiB,oBAAA;EACf,KAAA,EAAO,SAAA;EACP,GAAA,EAAK,UAAA;EACL,IAAA;EACA,QAAA,GAAW,cAAA;EACX,SAAA,GAAY,gBAAA;AAAA;AAAA,UAGG,aAAA;EAHH;EAKZ,MAAA,CAAO,IAAA,UAAc,OAAA,UAAiB,MAAA;EALV;EAO5B,cAAA,CAAe,IAAA,UAAc,IAAA;EAXtB;EAaP,gBAAA,CAAiB,IAAA,UAAc,MAAA;AAAA;AAAA,iBAGjB,mBAAA,CAAoB,IAAA,EAAM,oBAAA,GAAuB,aAAA"}
@@ -1,3 +1,4 @@
1
+ import { createOutboundMessage } from "../bus/events.mjs";
1
2
  import { VERSION } from "../index.mjs";
2
3
  import { getControlHtml } from "./ui.mjs";
3
4
  import { Hono } from "hono";
@@ -6,7 +7,7 @@ import { streamSSE } from "hono/streaming";
6
7
 
7
8
  //#region src/gateway/server.ts
8
9
  function createGatewayServer(opts) {
9
- const { agent, port, channels, heartbeat } = opts;
10
+ const { agent, bus, port, channels, heartbeat } = opts;
10
11
  const app = new Hono();
11
12
  const sseClients = /* @__PURE__ */ new Set();
12
13
  function broadcast(payload) {
@@ -43,33 +44,17 @@ function createGatewayServer(opts) {
43
44
  const body = await c.req.json();
44
45
  const message = body.message?.trim();
45
46
  if (!message) return c.json({ error: "message is required" }, 400);
46
- const sessionKey = body.session ?? "web:default";
47
+ const chatId = body.chatId ?? "default";
48
+ const sessionKey = body.session ?? `line:${chatId}`;
49
+ const deliver = body.deliver ?? false;
47
50
  try {
48
- const session = agent.sessions.getOrCreate(sessionKey);
49
- const prevLen = session.getHistory().length;
50
- const response = await agent.processDirect(message, sessionKey, "web", "default");
51
- const turn = [];
52
- const turnMessages = session.getHistory().slice(prevLen);
53
- for (const msg of turnMessages) if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) for (const tc of msg.tool_calls) turn.push({
54
- type: "tool_call",
55
- name: tc.function.name,
56
- arguments: tc.function.arguments
57
- });
58
- else if (msg.role === "tool") {
59
- const content = typeof msg.content === "string" ? msg.content : "";
60
- turn.push({
61
- type: "tool_result",
62
- name: msg.name ?? "",
63
- output: content
64
- });
65
- } else if (msg.role === "assistant" && msg.content) turn.push({
66
- type: "text",
67
- content: typeof msg.content === "string" ? msg.content : ""
68
- });
69
- return c.json({
70
- response,
71
- turn
72
- });
51
+ const response = await agent.processDirect(message, sessionKey, "line", chatId);
52
+ if (deliver && response) await bus.publishOutbound(createOutboundMessage({
53
+ channel: "line",
54
+ chatId,
55
+ content: response
56
+ }));
57
+ return c.json({ response });
73
58
  } catch (err) {
74
59
  const errMsg = err instanceof Error ? err.message : String(err);
75
60
  console.error("Chat error:", errMsg);
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../../src/gateway/server.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport { serve } from \"@hono/node-server\";\nimport { streamSSE } from \"hono/streaming\";\nimport { getControlHtml } from \"./ui.js\";\nimport type { AgentLoop } from \"../agent/loop.js\";\nimport type { ChannelManager } from \"../channels/manager.js\";\nimport type { HeartbeatService } from \"../heartbeat/service.js\";\nimport { VERSION } from \"../index.js\";\n\nexport interface GatewayServerOptions {\n agent: AgentLoop;\n port: number;\n channels?: ChannelManager;\n heartbeat?: HeartbeatService;\n}\n\nexport interface GatewayServer {\n /** Push a message to all connected SSE clients. */\n notify(role: string, content: string, chatId?: string): void;\n /** Push a tool call event to all connected SSE clients. */\n notifyToolCall(name: string, args: string): void;\n /** Push a tool result event to all connected SSE clients. */\n notifyToolResult(name: string, output: string): void;\n}\n\nexport function createGatewayServer(opts: GatewayServerOptions): GatewayServer {\n const { agent, port, channels, heartbeat } = opts;\n const app = new Hono();\n\n // Track SSE clients for real-time push\n const sseClients = new Set<(data: string) => void>();\n\n function broadcast(payload: string): void {\n for (const send of sseClients) {\n try {\n send(payload);\n } catch {\n // client disconnected, will be cleaned up\n }\n }\n }\n\n function notify(role: string, content: string, chatId = \"default\"): void {\n broadcast(JSON.stringify({ type: \"message\", role, content, chatId }));\n }\n\n function notifyToolCall(name: string, args: string): void {\n broadcast(JSON.stringify({ type: \"tool_call\", name, arguments: args }));\n }\n\n function notifyToolResult(name: string, output: string): void {\n broadcast(JSON.stringify({ type: \"tool_result\", name, output }));\n }\n\n // Serve the control UI\n app.get(\"/\", (c) => {\n return c.html(getControlHtml());\n });\n\n // Chat endpoint — sends a message to the agent and returns the response\n app.post(\"/api/chat\", async (c) => {\n const body = await c.req.json<{ message?: string; session?: string }>();\n const message = body.message?.trim();\n\n if (!message) {\n return c.json({ error: \"message is required\" }, 400);\n }\n\n const sessionKey = body.session ?? \"web:default\";\n\n try {\n // Snapshot history so we can extract 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 message,\n sessionKey,\n \"web\",\n \"default\",\n );\n\n // Build turn array: tool calls, tool results, and the final response\n const turn: Array<{\n type: \"tool_call\" | \"tool_result\" | \"text\";\n name?: string;\n arguments?: string;\n output?: string;\n content?: string;\n }> = [];\n\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 turn.push({ type: \"tool_call\", name: tc.function.name, arguments: tc.function.arguments });\n }\n } else if (msg.role === \"tool\") {\n const content = typeof msg.content === \"string\" ? msg.content : \"\";\n turn.push({ type: \"tool_result\", name: msg.name ?? \"\", output: content });\n } else if (msg.role === \"assistant\" && msg.content) {\n turn.push({ type: \"text\", content: typeof msg.content === \"string\" ? msg.content : \"\" });\n }\n }\n\n return c.json({ response, turn });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Chat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // History endpoint — returns conversation history for the UI\n app.get(\"/api/history\", (c) => {\n const sessionKey = c.req.query(\"session\") ?? \"web:default\";\n const session = agent.sessions.getOrCreate(sessionKey);\n const history = session.getHistory();\n\n // Return user, assistant, and tool messages for the UI\n const messages: Array<{\n role: string;\n content: string;\n tool_calls?: Array<{ name: string; arguments: string }>;\n tool_name?: string;\n }> = [];\n\n for (const msg of history) {\n if (msg.role === \"user\" || msg.role === \"assistant\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : msg.content\n ? msg.content\n .filter((p): p is { type: \"text\"; text: string } => p.type === \"text\")\n .map((p) => p.text)\n .join(\"\")\n : \"\";\n\n const entry: (typeof messages)[number] = { role: msg.role, content };\n\n // Include tool calls if present on assistant messages\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n entry.tool_calls = msg.tool_calls.map((tc) => ({\n name: tc.function.name,\n arguments: tc.function.arguments,\n }));\n }\n\n // Skip empty assistant messages that only have tool_calls (no text)\n if (msg.role === \"assistant\" && !content && !entry.tool_calls) continue;\n\n messages.push(entry);\n } else if (msg.role === \"tool\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : \"\";\n messages.push({\n role: \"tool\",\n content,\n tool_name: msg.name,\n });\n }\n }\n\n return c.json({ messages });\n });\n\n // SSE endpoint — real-time push for cron results and other async messages\n app.get(\"/api/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const send = (data: string) => {\n stream.writeSSE({ data }).catch(() => {});\n };\n\n sseClients.add(send);\n\n // Send a ping so the client knows the connection is alive\n await stream.writeSSE({ data: JSON.stringify({ type: \"connected\" }) });\n\n // Keep alive with periodic pings\n const pingInterval = setInterval(() => {\n stream.writeSSE({ data: JSON.stringify({ type: \"ping\" }) }).catch(() => {\n clearInterval(pingInterval);\n });\n }, 30_000);\n\n // Wait until the client disconnects\n try {\n await new Promise<void>((resolve) => {\n stream.onAbort(() => resolve());\n });\n } finally {\n clearInterval(pingInterval);\n sseClients.delete(send);\n }\n });\n });\n\n // Heartbeat endpoint — triggered by DO scheduler via manager\n app.post(\"/api/heartbeat\", async (c) => {\n if (!heartbeat) {\n return c.json({ error: \"Heartbeat not configured\" }, 503);\n }\n\n try {\n const response = await heartbeat.triggerNow();\n return c.json({ status: \"ok\", response });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Heartbeat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // LINE webhook endpoint\n app.post(\"/webhook/line\", async (c) => {\n if (!channels) {\n return c.json({ error: \"Channels not configured\" }, 503);\n }\n\n const { LineChannel } = await import(\"../channels/line.js\");\n const lineChannel = channels.getChannel(\"line\");\n if (!lineChannel || !(lineChannel instanceof LineChannel)) {\n return c.json({ error: \"LINE channel not enabled\" }, 404);\n }\n\n // Read raw body for signature verification\n const rawBody = await c.req.text();\n const signature = c.req.header(\"x-line-signature\") ?? \"\";\n\n if (!lineChannel.verifySignature(rawBody, signature)) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n const body = JSON.parse(rawBody);\n\n // Process webhook events (non-blocking — return 200 immediately)\n lineChannel.handleWebhook(body).catch((err) => {\n console.error(\"LINE webhook processing error:\", err);\n });\n\n return c.json({ status: \"ok\" });\n });\n\n // Status / health check\n app.get(\"/api/status\", (c) => {\n return c.json({\n status: \"ok\",\n version: VERSION,\n uptime: process.uptime(),\n });\n });\n\n // Start the server\n serve({ fetch: app.fetch, port }, (info) => {\n console.log(`Gateway HTTP server listening on http://localhost:${info.port}`);\n });\n\n return { notify, notifyToolCall, notifyToolResult };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../src/gateway/server.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport { serve } from \"@hono/node-server\";\nimport { streamSSE } from \"hono/streaming\";\nimport { getControlHtml } from \"./ui.js\";\nimport type { AgentLoop } from \"../agent/loop.js\";\nimport type { ChannelManager } from \"../channels/manager.js\";\nimport { createOutboundMessage } from \"../bus/events.js\";\nimport type { MessageBus } from \"../bus/queue.js\";\nimport type { HeartbeatService } from \"../heartbeat/service.js\";\nimport { VERSION } from \"../index.js\";\n\nexport interface GatewayServerOptions {\n agent: AgentLoop;\n bus: MessageBus;\n port: number;\n channels?: ChannelManager;\n heartbeat?: HeartbeatService;\n}\n\nexport interface GatewayServer {\n /** Push a message to all connected SSE clients. */\n notify(role: string, content: string, chatId?: string): void;\n /** Push a tool call event to all connected SSE clients. */\n notifyToolCall(name: string, args: string): void;\n /** Push a tool result event to all connected SSE clients. */\n notifyToolResult(name: string, output: string): void;\n}\n\nexport function createGatewayServer(opts: GatewayServerOptions): GatewayServer {\n const { agent, bus, port, channels, heartbeat } = opts;\n const app = new Hono();\n\n // Track SSE clients for real-time push\n const sseClients = new Set<(data: string) => void>();\n\n function broadcast(payload: string): void {\n for (const send of sseClients) {\n try {\n send(payload);\n } catch {\n // client disconnected, will be cleaned up\n }\n }\n }\n\n function notify(role: string, content: string, chatId = \"default\"): void {\n broadcast(JSON.stringify({ type: \"message\", role, content, chatId }));\n }\n\n function notifyToolCall(name: string, args: string): void {\n broadcast(JSON.stringify({ type: \"tool_call\", name, arguments: args }));\n }\n\n function notifyToolResult(name: string, output: string): void {\n broadcast(JSON.stringify({ type: \"tool_result\", name, output }));\n }\n\n // Serve the control UI\n app.get(\"/\", (c) => {\n return c.html(getControlHtml());\n });\n\n // Chat endpoint — sends a message to the agent and returns the response\n app.post(\"/api/chat\", async (c) => {\n const body = await c.req.json<{\n message?: string;\n session?: string;\n chatId?: string;\n deliver?: boolean;\n }>();\n const message = body.message?.trim();\n\n if (!message) {\n return c.json({ error: \"message is required\" }, 400);\n }\n\n const chatId = body.chatId ?? \"default\";\n const sessionKey = body.session ?? `line:${chatId}`;\n const deliver = body.deliver ?? false;\n\n try {\n const response = await agent.processDirect(\n message,\n sessionKey,\n \"line\",\n chatId,\n );\n\n // Deliver response to LINE if requested (e.g. cron-triggered)\n if (deliver && response) {\n await bus.publishOutbound(\n createOutboundMessage({\n channel: \"line\",\n chatId,\n content: response,\n }),\n );\n }\n\n return c.json({ response });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Chat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // History endpoint — returns conversation history for the UI\n app.get(\"/api/history\", (c) => {\n const sessionKey = c.req.query(\"session\") ?? \"web:default\";\n const session = agent.sessions.getOrCreate(sessionKey);\n const history = session.getHistory();\n\n // Return user, assistant, and tool messages for the UI\n const messages: Array<{\n role: string;\n content: string;\n tool_calls?: Array<{ name: string; arguments: string }>;\n tool_name?: string;\n }> = [];\n\n for (const msg of history) {\n if (msg.role === \"user\" || msg.role === \"assistant\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : msg.content\n ? msg.content\n .filter((p): p is { type: \"text\"; text: string } => p.type === \"text\")\n .map((p) => p.text)\n .join(\"\")\n : \"\";\n\n const entry: (typeof messages)[number] = { role: msg.role, content };\n\n // Include tool calls if present on assistant messages\n if (msg.role === \"assistant\" && msg.tool_calls && msg.tool_calls.length > 0) {\n entry.tool_calls = msg.tool_calls.map((tc) => ({\n name: tc.function.name,\n arguments: tc.function.arguments,\n }));\n }\n\n // Skip empty assistant messages that only have tool_calls (no text)\n if (msg.role === \"assistant\" && !content && !entry.tool_calls) continue;\n\n messages.push(entry);\n } else if (msg.role === \"tool\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : \"\";\n messages.push({\n role: \"tool\",\n content,\n tool_name: msg.name,\n });\n }\n }\n\n return c.json({ messages });\n });\n\n // SSE endpoint — real-time push for cron results and other async messages\n app.get(\"/api/events\", (c) => {\n return streamSSE(c, async (stream) => {\n const send = (data: string) => {\n stream.writeSSE({ data }).catch(() => {});\n };\n\n sseClients.add(send);\n\n // Send a ping so the client knows the connection is alive\n await stream.writeSSE({ data: JSON.stringify({ type: \"connected\" }) });\n\n // Keep alive with periodic pings\n const pingInterval = setInterval(() => {\n stream.writeSSE({ data: JSON.stringify({ type: \"ping\" }) }).catch(() => {\n clearInterval(pingInterval);\n });\n }, 30_000);\n\n // Wait until the client disconnects\n try {\n await new Promise<void>((resolve) => {\n stream.onAbort(() => resolve());\n });\n } finally {\n clearInterval(pingInterval);\n sseClients.delete(send);\n }\n });\n });\n\n // Heartbeat endpoint — triggered by DO scheduler via manager\n app.post(\"/api/heartbeat\", async (c) => {\n if (!heartbeat) {\n return c.json({ error: \"Heartbeat not configured\" }, 503);\n }\n\n try {\n const response = await heartbeat.triggerNow();\n return c.json({ status: \"ok\", response });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.error(\"Heartbeat error:\", errMsg);\n return c.json({ error: errMsg }, 500);\n }\n });\n\n // LINE webhook endpoint\n app.post(\"/webhook/line\", async (c) => {\n if (!channels) {\n return c.json({ error: \"Channels not configured\" }, 503);\n }\n\n const { LineChannel } = await import(\"../channels/line.js\");\n const lineChannel = channels.getChannel(\"line\");\n if (!lineChannel || !(lineChannel instanceof LineChannel)) {\n return c.json({ error: \"LINE channel not enabled\" }, 404);\n }\n\n // Read raw body for signature verification\n const rawBody = await c.req.text();\n const signature = c.req.header(\"x-line-signature\") ?? \"\";\n\n if (!lineChannel.verifySignature(rawBody, signature)) {\n return c.json({ error: \"Invalid signature\" }, 401);\n }\n\n const body = JSON.parse(rawBody);\n\n // Process webhook events (non-blocking — return 200 immediately)\n lineChannel.handleWebhook(body).catch((err) => {\n console.error(\"LINE webhook processing error:\", err);\n });\n\n return c.json({ status: \"ok\" });\n });\n\n // Status / health check\n app.get(\"/api/status\", (c) => {\n return c.json({\n status: \"ok\",\n version: VERSION,\n uptime: process.uptime(),\n });\n });\n\n // Start the server\n serve({ fetch: app.fetch, port }, (info) => {\n console.log(`Gateway HTTP server listening on http://localhost:${info.port}`);\n });\n\n return { notify, notifyToolCall, notifyToolResult };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcheesepkg/nanobot",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Lightweight AI assistant - TypeScript port",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -7,21 +7,21 @@ description: Schedule reminders and recurring tasks.
7
7
 
8
8
  Use the `cron` tool to schedule reminders or recurring tasks.
9
9
 
10
- ## Two Modes
10
+ ## Two Modes (kind parameter)
11
11
 
12
- 1. **Reminder** - message is sent directly to user
13
- 2. **Task** - message is a task description, agent executes and sends result
12
+ 1. **direct** (default) message is sent verbatim to the user. No LLM call. Use for reminders and fixed messages.
13
+ 2. **task** message is run through the agent as a prompt. The agent processes it and delivers the result. Use for dynamic tasks that need tools/reasoning.
14
14
 
15
15
  ## Examples
16
16
 
17
- Fixed reminder:
17
+ Fixed reminder (kind="direct", the default):
18
18
  ```
19
19
  cron(action="add", message="Time to take a break!", every_seconds=1200)
20
20
  ```
21
21
 
22
- Dynamic task (agent executes each time):
22
+ Dynamic task (kind="task", agent executes each time):
23
23
  ```
24
- cron(action="add", message="Check HKUDS/nanobot GitHub stars and report", every_seconds=600)
24
+ cron(action="add", message="Check HKUDS/nanobot GitHub stars and report", kind="task", every_seconds=600)
25
25
  ```
26
26
 
27
27
  List/remove:
@@ -38,3 +38,5 @@ cron(action="remove", job_id="abc123")
38
38
  | every hour | every_seconds: 3600 |
39
39
  | every day at 8am | cron_expr: "0 8 * * *" |
40
40
  | weekdays at 5pm | cron_expr: "0 17 * * 1-5" |
41
+ | In 10 minutes | at: "2026-02-10T10:00:00Z" |
42
+ | at 10:00 AM on February 10, 2026 | at: "2026-02-10T10:00:00Z" |