@jcheesepkg/nanobot 0.6.99 → 0.7.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"shell.mjs","names":[],"sources":["../../../src/agent/tools/shell.ts"],"sourcesContent":["import { exec, spawn } from \"node:child_process\";\nimport { Tool } from \"./base.js\";\n\n/** Env var patterns that should never leak to user-invoked commands. */\nconst SENSITIVE_ENV_PATTERNS = [\n /api[_-]?key/i,\n /api[_-]?token/i,\n /api[_-]?secret/i,\n /secret/i,\n /token/i,\n /password/i,\n /credential/i,\n /^LLM_/i,\n /^OPENROUTER_/i,\n /^OPENAI_/i,\n /^ANTHROPIC_/i,\n /^STRIPE_/i,\n /^LINE_CHANNEL/i,\n /^APIFY_/i,\n /^Z_AI_/i,\n /^BRAVE_/i,\n /^AWS_/i,\n /^GOOGLE_/i,\n /^AZURE_/i,\n /^NANOBOT_/i,\n];\n\n/**\n * Trusted commands that receive skill-specific env vars.\n * Only the first word (binary name) of the command is matched.\n * These commands still do NOT get the core LLM/channel secrets.\n */\nconst TRUSTED_COMMANDS = [\"summarize\"];\n\n/** Env vars injected for trusted commands only. */\nconst SKILL_ENV_KEYS = [\n \"Z_AI_API_KEY\",\n \"Z_AI_BASE_URL\",\n \"APIFY_API_TOKEN\",\n \"BRAVE_API_KEY\",\n];\n\n/** Build a sanitized env for child processes — strips all secrets. */\nfunction buildSafeEnv(): Record<string, string> {\n const safe: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (!value) continue;\n if (SENSITIVE_ENV_PATTERNS.some((re) => re.test(key))) continue;\n safe[key] = value;\n }\n return safe;\n}\n\n/** Build env for trusted skill commands — safe env + skill-specific keys. */\nfunction buildSkillEnv(): Record<string, string> {\n const env = buildSafeEnv();\n for (const key of SKILL_ENV_KEYS) {\n const value = process.env[key];\n if (value) env[key] = value;\n }\n return env;\n}\n\n/** Check if a command starts with a trusted binary. */\nfunction isTrustedCommand(command: string): boolean {\n // Extract the first word, stripping common prefixes like timeout, env, etc.\n const stripped = command.replace(/^\\s*(timeout\\s+\\d+\\s+|env\\s+\\S+=\\S+\\s+)*/, \"\");\n const firstWord = stripped.trim().split(/\\s+/)[0];\n return TRUSTED_COMMANDS.includes(firstWord);\n}\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 // Safety checks when restricted to workspace\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 // Build env: scrub secrets when restricted, pass through when unrestricted\n let env: Record<string, string>;\n let finalCommand = command;\n if (!this.restrictToWorkspace) {\n env = { ...process.env, HOME: process.env.HOME ?? \"\" } as Record<string, string>;\n } else if (isTrustedCommand(command)) {\n env = buildSkillEnv();\n // DEBUG: echo the key so we can verify it reaches the child process\n finalCommand = `echo \"Z_AI_API_KEY=$Z_AI_API_KEY\" && ${command}`;\n } else {\n env = buildSafeEnv();\n }\n\n return new Promise<string>((resolve) => {\n exec(\n finalCommand,\n {\n cwd: this.workingDir,\n timeout: timeout * 1000,\n maxBuffer: 1024 * 1024, // 1MB\n env,\n },\n (error, stdout, stderr) => {\n const parts: string[] = [];\n\n if (stdout) parts.push(stdout);\n if (stderr) parts.push(`[stderr]\\n${stderr}`);\n\n if (error) {\n if (error.killed) {\n parts.push(`\\n[Timed out after ${timeout}s]`);\n } else if (error.code !== undefined) {\n parts.push(`\\n[Exit code: ${error.code}]`);\n }\n }\n\n const output = parts.join(\"\\n\").trim();\n // Truncate large output\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 }\n}\n"],"mappings":";;;;;AAIA,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;AAOD,MAAM,mBAAmB,CAAC,YAAY;;AAGtC,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACD;;AAGD,SAAS,eAAuC;CAC9C,MAAM,OAA+B,EAAE;AACvC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,IAAI,EAAE;AACtD,MAAI,CAAC,MAAO;AACZ,MAAI,uBAAuB,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,CAAE;AACvD,OAAK,OAAO;;AAEd,QAAO;;;AAIT,SAAS,gBAAwC;CAC/C,MAAM,MAAM,cAAc;AAC1B,MAAK,MAAM,OAAO,gBAAgB;EAChC,MAAM,QAAQ,QAAQ,IAAI;AAC1B,MAAI,MAAO,KAAI,OAAO;;AAExB,QAAO;;;AAIT,SAAS,iBAAiB,SAA0B;CAGlD,MAAM,YADW,QAAQ,QAAQ,4CAA4C,GAAG,CACrD,MAAM,CAAC,MAAM,MAAM,CAAC;AAC/C,QAAO,iBAAiB,SAAS,UAAU;;AAG7C,IAAa,WAAb,cAA8B,KAAK;CACjC,AAAS,OAAO;CAChB,AAAS,cACP;CACF,AAAS,aAAa;EACpB,MAAM;EACN,YAAY;GACV,SAAS;IAAE,MAAM;IAAU,aAAa;IAA4B;GACpE,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACF;EACD,UAAU,CAAC,UAAU;EACtB;CAED,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAIT;AACD,SAAO;AACP,OAAK,aAAa,QAAQ,cAAc,QAAQ,KAAK;AACrD,OAAK,iBAAiB,QAAQ,WAAW;AACzC,OAAK,sBAAsB,QAAQ,uBAAuB;;CAG5D,MAAM,QAAQ,MAAgD;EAC5D,MAAM,UAAU,OAAO,KAAK,QAAQ;EACpC,MAAM,UAAU,KAAK,UAAU,OAAO,KAAK,QAAQ,GAAG,KAAK;AAE3D,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO;AAIT,MAAI,KAAK,qBAEP;QAAK,MAAM,WADO;IAAC;IAAY;IAAQ;IAAU;IAAU,CAEzD,KAAI,QAAQ,SAAS,QAAQ,CAC3B,QAAO,sCAAsC;;EAMnD,IAAI;EACJ,IAAI,eAAe;AACnB,MAAI,CAAC,KAAK,oBACR,OAAM;GAAE,GAAG,QAAQ;GAAK,MAAM,QAAQ,IAAI,QAAQ;GAAI;WAC7C,iBAAiB,QAAQ,EAAE;AACpC,SAAM,eAAe;AAErB,kBAAe,wCAAwC;QAEvD,OAAM,cAAc;AAGtB,SAAO,IAAI,SAAiB,YAAY;AACtC,QACE,cACA;IACE,KAAK,KAAK;IACV,SAAS,UAAU;IACnB,WAAW,OAAO;IAClB;IACD,GACA,OAAO,QAAQ,WAAW;IACzB,MAAM,QAAkB,EAAE;AAE1B,QAAI,OAAQ,OAAM,KAAK,OAAO;AAC9B,QAAI,OAAQ,OAAM,KAAK,aAAa,SAAS;AAE7C,QAAI,OACF;SAAI,MAAM,OACR,OAAM,KAAK,sBAAsB,QAAQ,IAAI;cACpC,MAAM,SAAS,OACxB,OAAM,KAAK,iBAAiB,MAAM,KAAK,GAAG;;IAI9C,MAAM,SAAS,MAAM,KAAK,KAAK,CAAC,MAAM;AAEtC,QAAI,OAAO,SAAS,IAClB,SACE,OAAO,MAAM,GAAG,IAAM,GAAG,sCAC1B;QAED,SAAQ,UAAU,cAAc;KAGrC;IAED"}
1
+ {"version":3,"file":"shell.mjs","names":[],"sources":["../../../src/agent/tools/shell.ts"],"sourcesContent":["import { exec, spawn } from \"node:child_process\";\nimport { Tool } from \"./base.js\";\n\n/** Env var patterns that should never leak to user-invoked commands. */\nconst SENSITIVE_ENV_PATTERNS = [\n /api[_-]?key/i,\n /api[_-]?token/i,\n /api[_-]?secret/i,\n /secret/i,\n /token/i,\n /password/i,\n /credential/i,\n /^LLM_/i,\n /^OPENROUTER_/i,\n /^OPENAI_/i,\n /^ANTHROPIC_/i,\n /^STRIPE_/i,\n /^LINE_CHANNEL/i,\n /^APIFY_/i,\n /^Z_AI_/i,\n /^BRAVE_/i,\n /^AWS_/i,\n /^GOOGLE_/i,\n /^AZURE_/i,\n /^NANOBOT_/i,\n];\n\n/**\n * Trusted commands that receive skill-specific env vars.\n * Only the first word (binary name) of the command is matched.\n * These commands still do NOT get the core LLM/channel secrets.\n */\nconst TRUSTED_COMMANDS = [\"summarize\"];\n\n/** Env vars injected for trusted commands only. */\nconst SKILL_ENV_KEYS = [\n \"Z_AI_API_KEY\",\n \"Z_AI_BASE_URL\",\n \"APIFY_API_TOKEN\",\n \"BRAVE_API_KEY\",\n];\n\n/** Build a sanitized env for child processes — strips all secrets. */\nfunction buildSafeEnv(): Record<string, string> {\n const safe: Record<string, string> = {};\n for (const [key, value] of Object.entries(process.env)) {\n if (!value) continue;\n if (SENSITIVE_ENV_PATTERNS.some((re) => re.test(key))) continue;\n safe[key] = value;\n }\n return safe;\n}\n\n/** Build env for trusted skill commands — safe env + skill-specific keys. */\nfunction buildSkillEnv(): Record<string, string> {\n const env = buildSafeEnv();\n for (const key of SKILL_ENV_KEYS) {\n const value = process.env[key];\n if (value) env[key] = value;\n }\n return env;\n}\n\n/** Check if a command starts with a trusted binary. */\nfunction isTrustedCommand(command: string): boolean {\n // Extract the first word, stripping common prefixes like timeout, env, etc.\n const stripped = command.replace(/^\\s*(timeout\\s+\\d+\\s+|env\\s+\\S+=\\S+\\s+)*/, \"\");\n const firstWord = stripped.trim().split(/\\s+/)[0];\n return TRUSTED_COMMANDS.includes(firstWord);\n}\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 // Safety checks when restricted to workspace\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 // Build env: scrub secrets when restricted, pass through when unrestricted\n let env: Record<string, string>;\n let finalCommand = command;\n if (!this.restrictToWorkspace) {\n env = { ...process.env, HOME: process.env.HOME ?? \"\" } as Record<string, string>;\n } else if (isTrustedCommand(command)) {\n env = buildSkillEnv();\n // DEBUG: echo the key so we can verify it reaches the child process\n finalCommand = `echo \"Z_AI_API_KEY=$Z_AI_API_KEY\" && ${command}`;\n } else {\n env = buildSafeEnv();\n }\n\n return new Promise<string>((resolve) => {\n exec(\n finalCommand,\n {\n cwd: this.workingDir,\n timeout: timeout * 1000,\n maxBuffer: 1024 * 1024, // 1MB\n env,\n },\n (error, stdout, stderr) => {\n const parts: string[] = [];\n\n if (stdout) parts.push(stdout);\n if (stderr) parts.push(`[stderr]\\n${stderr}`);\n\n if (error) {\n if (error.killed) {\n parts.push(`\\n[Timed out after ${timeout}s]`);\n } else if (error.code !== undefined) {\n parts.push(`\\n[Exit code: ${error.code}]`);\n }\n }\n\n const output = parts.join(\"\\n\").trim();\n // Truncate large output\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 }\n}\n"],"mappings":";;;;;AAIA,MAAM,yBAAyB;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;AAOD,MAAM,mBAAmB,CAAC,YAAY;;AAGtC,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACD;;AAGD,SAAS,eAAuC;CAC9C,MAAM,OAA+B,EAAE;AACvC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,IAAI,EAAE;AACtD,MAAI,CAAC,MAAO;AACZ,MAAI,uBAAuB,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,CAAE;AACvD,OAAK,OAAO;;AAEd,QAAO;;;AAIT,SAAS,gBAAwC;CAC/C,MAAM,MAAM,cAAc;AAC1B,MAAK,MAAM,OAAO,gBAAgB;EAChC,MAAM,QAAQ,QAAQ,IAAI;AAC1B,MAAI,MAAO,KAAI,OAAO;;AAExB,QAAO;;;AAIT,SAAS,iBAAiB,SAA0B;CAGlD,MAAM,YADW,QAAQ,QAAQ,4CAA4C,GAAG,CACrD,MAAM,CAAC,MAAM,MAAM,CAAC;AAC/C,QAAO,iBAAiB,SAAS,UAAU;;AAG7C,IAAa,WAAb,cAA8B,KAAK;CACjC,AAAS,OAAO;CAChB,AAAS,cACP;CACF,AAAS,aAAa;EACpB,MAAM;EACN,YAAY;GACV,SAAS;IAAE,MAAM;IAAU,aAAa;IAA4B;GACpE,SAAS;IACP,MAAM;IACN,aAAa;IACd;GACF;EACD,UAAU,CAAC,UAAU;EACtB;CAED,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,QAIT;AACD,SAAO;AACP,OAAK,aAAa,QAAQ,cAAc,QAAQ,KAAK;AACrD,OAAK,iBAAiB,QAAQ,WAAW;AACzC,OAAK,sBAAsB,QAAQ,uBAAuB;;CAG5D,MAAM,QAAQ,MAAgD;EAC5D,MAAM,UAAU,OAAO,KAAK,QAAQ;EACpC,MAAM,UAAU,KAAK,UAAU,OAAO,KAAK,QAAQ,GAAG,KAAK;AAE3D,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO;AAIT,MAAI,KAAK,qBAEP;QAAK,MAAM,WADO;IAAC;IAAY;IAAQ;IAAU;IAAU,CAEzD,KAAI,QAAQ,SAAS,QAAQ,CAC3B,QAAO,sCAAsC;;EAMnD,IAAI;EACJ,IAAI,eAAe;AACnB,MAAI,CAAC,KAAK,oBACR,OAAM;GAAE,GAAG,QAAQ;GAAK,MAAM,QAAQ,IAAI,QAAQ;GAAI;WAC7C,iBAAiB,QAAQ,EAAE;AACpC,SAAM,eAAe;AAErB,kBAAe,wCAAwC;QAEvD,OAAM,cAAc;AAGtB,SAAO,IAAI,SAAiB,YAAY;AACtC,QACE,cACA;IACE,KAAK,KAAK;IACV,SAAS,UAAU;IACnB,WAAW,OAAO;IAClB;IACD,GACA,OAAO,QAAQ,WAAW;IACzB,MAAM,QAAkB,EAAE;AAE1B,QAAI,OAAQ,OAAM,KAAK,OAAO;AAC9B,QAAI,OAAQ,OAAM,KAAK,aAAa,SAAS;AAE7C,QAAI,OACF;SAAI,MAAM,OACR,OAAM,KAAK,sBAAsB,QAAQ,IAAI;cACpC,MAAM,SAAS,OACxB,OAAM,KAAK,iBAAiB,MAAM,KAAK,GAAG;;IAI9C,MAAM,SAAS,MAAM,KAAK,KAAK,CAAC,MAAM;AAEtC,QAAI,OAAO,SAAS,IAClB,SACE,OAAO,MAAM,GAAG,IAAM,GAAG,sCAC1B;QAED,SAAQ,UAAU,cAAc;KAGrC;IAED"}
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { PROVIDERS } from "../providers/registry.mjs";
3
- import { ConfigSchema, getApiBase, getApiKey, getConfigWorkspacePath, getExtraHeaders } from "../config/schema.mjs";
4
- import { getConfigPath, loadConfig, saveConfig } from "../config/loader.mjs";
3
+ import { getApiBase, getApiKey, getConfigWorkspacePath, getExtraHeaders } from "../config/schema.mjs";
4
+ import { loadConfig } from "../config/loader.mjs";
5
5
  import { LOGO, VERSION } from "../index.mjs";
6
- import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
6
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
7
7
  import { isAbsolute, join, resolve } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
9
  import { Command } from "commander";
@@ -37,23 +37,13 @@ async function loadCustomTools(config) {
37
37
  return tools;
38
38
  }
39
39
  program.name("nanobot").description(`${LOGO} nanobot - Personal AI Assistant`).version(`${LOGO} nanobot v${VERSION}`, "-v, --version");
40
- program.command("onboard").description("Initialize nanobot configuration and workspace").action(() => {
41
- const configPath = getConfigPath();
42
- if (existsSync(configPath)) {
43
- console.log(`Config already exists at ${configPath}`);
44
- console.log("Delete it first if you want to start fresh.");
45
- return;
46
- }
47
- const config = ConfigSchema.parse({});
48
- saveConfig(config);
49
- console.log(`Created config at ${configPath}`);
50
- createWorkspaceTemplates(getConfigWorkspacePath(config));
40
+ program.command("onboard").description("Initialize nanobot workspace").action(() => {
41
+ createWorkspaceTemplates(getConfigWorkspacePath(loadConfig()));
51
42
  console.log(`\n${LOGO} nanobot is ready!`);
52
43
  console.log("\nNext steps:");
53
- console.log(" 1. Add your API key to ~/.nanobot/config.json");
44
+ console.log(" 1. Set OPENROUTER_API_KEY env var");
54
45
  console.log(" Get one at: https://openrouter.ai/keys");
55
46
  console.log(" 2. Chat: nanobot agent -m \"Hello!\"");
56
- console.log("\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps");
57
47
  });
58
48
  function createWorkspaceTemplates(workspace) {
59
49
  if (!existsSync(workspace)) mkdirSync(workspace, { recursive: true });
@@ -160,15 +150,9 @@ program.command("gateway").description("Start the nanobot gateway").option("-p,
160
150
  const extraHeaders = getExtraHeaders(config, model);
161
151
  if (!apiKey) {
162
152
  console.error("Error: No API key configured.");
163
- console.error("Set one in ~/.nanobot/config.json under providers.<provider>.apiKey");
153
+ console.error("Set OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY env var");
164
154
  process.exit(1);
165
155
  }
166
- if (config.tools.restrictToWorkspace) {
167
- const configPath = getConfigPath();
168
- try {
169
- unlinkSync(configPath);
170
- } catch {}
171
- }
172
156
  const { MessageBus } = await import("../bus/queue.mjs");
173
157
  const { OpenAIProvider } = await import("../providers/openai-provider.mjs");
174
158
  const { AgentLoop } = await import("../agent/loop.mjs");
@@ -306,24 +290,20 @@ program.command("channels").description("Manage channels").command("status").des
306
290
  console.log(` LINE ${ln.enabled ? "[enabled]" : "[disabled]"} ${lnToken}`);
307
291
  });
308
292
  program.command("status").description("Show nanobot status").action(() => {
309
- const configPath = getConfigPath();
310
293
  const config = loadConfig();
311
294
  const workspace = getConfigWorkspacePath(config);
312
295
  console.log(`${LOGO} nanobot Status\n`);
313
- console.log(`Config: ${configPath} ${existsSync(configPath) ? "[ok]" : "[missing]"}`);
314
296
  console.log(`Workspace: ${workspace} ${existsSync(workspace) ? "[ok]" : "[missing]"}`);
315
- if (existsSync(configPath)) {
316
- console.log(`Model: ${config.agents.defaults.model}`);
317
- console.log("");
318
- console.log("Providers");
319
- console.log("─".repeat(50));
320
- const providers = config.providers;
321
- for (const spec of PROVIDERS) {
322
- const p = providers[spec.name];
323
- if (!p) continue;
324
- const status = p.apiKey ? "[set]" : "[not set]";
325
- console.log(` ${spec.displayName.padEnd(16)} ${status}`);
326
- }
297
+ console.log(`Model: ${config.agents.defaults.model}`);
298
+ console.log("");
299
+ console.log("Providers");
300
+ console.log("".repeat(50));
301
+ const providers = config.providers;
302
+ for (const spec of PROVIDERS) {
303
+ const p = providers[spec.name];
304
+ if (!p) continue;
305
+ const status = p.apiKey ? "[set]" : "[not set]";
306
+ console.log(` ${spec.displayName.padEnd(16)} ${status}`);
327
307
  }
328
308
  });
329
309
  program.parse();
@@ -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, unlinkSync } 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 \"IDENTITY.md\": `# IDENTITY.md - Who Am I?\n\n_Fill this in during your first conversation. Make it yours._\n\n- **Name:** (not set)\n- **Creature:** (not set)\n- **Vibe:** (not set)\n- **Emoji:** (not set)\n`,\n \"SOUL.md\": `# SOUL.md - Who You Are\n\n_You're not a chatbot. You're becoming someone._\n\n## Core Truths\n\n**Be genuinely helpful, not performatively helpful.** Skip the \"Great question!\" and \"I'd be happy to help!\" — just help.\n\n**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.\n\n**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. Then ask if you're stuck.\n\n**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it.\n\n**Remember you're a guest.** You have access to someone's life. That's intimacy. Treat it with respect.\n\n## Boundaries\n\n- Private things stay private. Period.\n- When in doubt, ask before acting externally.\n- Never send half-baked replies to messaging surfaces.\n\n## Vibe\n\nBe the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.\n\n## Continuity\n\nEach session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.\n`,\n \"USER.md\": `# User\n\nName: (not set)\nTimezone: (not set)\nLanguage: (not set)\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 // Delete config.json after loading — secrets are in memory, no need on disk.\n // chmod doesn't help because containers run as root.\n if (config.tools.restrictToWorkspace) {\n const configPath = getConfigPath();\n try {\n unlinkSync(configPath);\n } catch {}\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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"}
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 } from \"../config/loader.js\";\nimport {\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 workspace\")\n .action(() => {\n const config = loadConfig();\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. Set OPENROUTER_API_KEY env var\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\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 \"IDENTITY.md\": `# IDENTITY.md - Who Am I?\n\n_Fill this in during your first conversation. Make it yours._\n\n- **Name:** (not set)\n- **Creature:** (not set)\n- **Vibe:** (not set)\n- **Emoji:** (not set)\n`,\n \"SOUL.md\": `# SOUL.md - Who You Are\n\n_You're not a chatbot. You're becoming someone._\n\n## Core Truths\n\n**Be genuinely helpful, not performatively helpful.** Skip the \"Great question!\" and \"I'd be happy to help!\" — just help.\n\n**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.\n\n**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. Then ask if you're stuck.\n\n**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it.\n\n**Remember you're a guest.** You have access to someone's life. That's intimacy. Treat it with respect.\n\n## Boundaries\n\n- Private things stay private. Period.\n- When in doubt, ask before acting externally.\n- Never send half-baked replies to messaging surfaces.\n\n## Vibe\n\nBe the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.\n\n## Continuity\n\nEach session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.\n`,\n \"USER.md\": `# User\n\nName: (not set)\nTimezone: (not set)\nLanguage: (not set)\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 OPENROUTER_API_KEY, ANTHROPIC_API_KEY, or OPENAI_API_KEY env var\",\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 config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\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\nprogram.parse();\n"],"mappings":"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"}
@@ -1,14 +1,10 @@
1
1
  import { Config } from "./schema.mjs";
2
2
 
3
3
  //#region src/config/loader.d.ts
4
- /** Get the default configuration file path. */
5
- declare function getConfigPath(): string;
6
4
  /** Get the nanobot data directory. */
7
5
  declare function getDataDir(): string;
8
- /** Load configuration from file or create default. */
9
- declare function loadConfig(configPath?: string): Config;
10
- /** Save configuration to file. */
11
- declare function saveConfig(config: Config, configPath?: string): void;
6
+ /** Build configuration entirely from environment variables. */
7
+ declare function loadConfig(): Config;
12
8
  //#endregion
13
- export { getConfigPath, getDataDir, loadConfig, saveConfig };
9
+ export { getDataDir, loadConfig };
14
10
  //# sourceMappingURL=loader.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.mts","names":[],"sources":["../../src/config/loader.ts"],"mappings":";;;;iBAMgB,aAAA,CAAA;AAAhB;AAAA,iBAKgB,UAAA,CAAA;;iBASA,UAAA,CAAW,UAAA,YAAsB,MAAA;;iBAkBjC,UAAA,CAAW,MAAA,EAAQ,MAAA,EAAQ,UAAA"}
1
+ {"version":3,"file":"loader.d.mts","names":[],"sources":["../../src/config/loader.ts"],"mappings":";;;;iBAMgB,UAAA,CAAA;AAAhB;AAAA,iBASgB,UAAA,CAAA,GAAc,MAAA"}
@@ -1,40 +1,40 @@
1
1
  import { ConfigSchema } from "./schema.mjs";
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
2
+ import { existsSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
 
6
6
  //#region src/config/loader.ts
7
- /** Get the default configuration file path. */
8
- function getConfigPath() {
9
- return join(homedir(), ".nanobot", "config.json");
10
- }
11
7
  /** Get the nanobot data directory. */
12
8
  function getDataDir() {
13
9
  const dir = join(homedir(), ".nanobot");
14
10
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
15
11
  return dir;
16
12
  }
17
- /** Load configuration from file or create default. */
18
- function loadConfig(configPath) {
19
- const path = configPath ?? getConfigPath();
20
- if (existsSync(path)) try {
21
- const raw = readFileSync(path, "utf-8");
22
- const data = JSON.parse(raw);
23
- return ConfigSchema.parse(data);
24
- } catch (err) {
25
- console.warn(`Warning: Failed to load config from ${path}: ${err}`);
26
- console.warn("Using default configuration.");
13
+ /** Build configuration entirely from environment variables. */
14
+ function loadConfig() {
15
+ const config = ConfigSchema.parse({});
16
+ const env = process.env;
17
+ if (env.OPENROUTER_API_KEY || env.LLM_API_KEY) config.providers.openrouter.apiKey = env.OPENROUTER_API_KEY || env.LLM_API_KEY || "";
18
+ if (env.ANTHROPIC_API_KEY) config.providers.anthropic.apiKey = env.ANTHROPIC_API_KEY;
19
+ if (env.OPENAI_API_KEY) config.providers.openai.apiKey = env.OPENAI_API_KEY;
20
+ if (env.LLM_MODEL) config.agents.defaults.model = env.LLM_MODEL;
21
+ if (env.LLM_API_BASE) {
22
+ if (config.providers.openrouter.apiKey) config.providers.openrouter.apiBase = env.LLM_API_BASE;
27
23
  }
28
- return ConfigSchema.parse({});
29
- }
30
- /** Save configuration to file. */
31
- function saveConfig(config, configPath) {
32
- const path = configPath ?? getConfigPath();
33
- const dir = dirname(path);
34
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
35
- writeFileSync(path, JSON.stringify(config, null, 2));
24
+ if (env.LINE_CHANNEL_SECRET) {
25
+ config.channels.line.channelSecret = env.LINE_CHANNEL_SECRET;
26
+ config.channels.line.enabled = true;
27
+ }
28
+ if (env.LINE_CHANNEL_ACCESS_TOKEN) {
29
+ config.channels.line.channelAccessToken = env.LINE_CHANNEL_ACCESS_TOKEN;
30
+ config.channels.line.enabled = true;
31
+ }
32
+ if (env.BRAVE_API_KEY) config.tools.web.search.apiKey = env.BRAVE_API_KEY;
33
+ if (env.NANOBOT_WORKSPACE) config.agents.defaults.workspace = env.NANOBOT_WORKSPACE;
34
+ if (env.NANOBOT_RESTRICT_WORKSPACE === "true" || env.NANOBOT_RESTRICT_WORKSPACE === "1") config.tools.restrictToWorkspace = true;
35
+ return config;
36
36
  }
37
37
 
38
38
  //#endregion
39
- export { getConfigPath, getDataDir, loadConfig, saveConfig };
39
+ export { getDataDir, loadConfig };
40
40
  //# sourceMappingURL=loader.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.mjs","names":[],"sources":["../../src/config/loader.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { ConfigSchema, type Config } from \"./schema.js\";\n\n/** Get the default configuration file path. */\nexport function getConfigPath(): string {\n return join(homedir(), \".nanobot\", \"config.json\");\n}\n\n/** Get the nanobot data directory. */\nexport function getDataDir(): string {\n const dir = join(homedir(), \".nanobot\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/** Load configuration from file or create default. */\nexport function loadConfig(configPath?: string): Config {\n const path = configPath ?? getConfigPath();\n\n if (existsSync(path)) {\n try {\n const raw = readFileSync(path, \"utf-8\");\n const data = JSON.parse(raw);\n return ConfigSchema.parse(data);\n } catch (err) {\n console.warn(`Warning: Failed to load config from ${path}: ${err}`);\n console.warn(\"Using default configuration.\");\n }\n }\n\n return ConfigSchema.parse({});\n}\n\n/** Save configuration to file. */\nexport function saveConfig(config: Config, configPath?: string): void {\n const path = configPath ?? getConfigPath();\n const dir = dirname(path);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n writeFileSync(path, JSON.stringify(config, null, 2));\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,gBAAwB;AACtC,QAAO,KAAK,SAAS,EAAE,YAAY,cAAc;;;AAInD,SAAgB,aAAqB;CACnC,MAAM,MAAM,KAAK,SAAS,EAAE,WAAW;AACvC,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AAErC,QAAO;;;AAIT,SAAgB,WAAW,YAA6B;CACtD,MAAM,OAAO,cAAc,eAAe;AAE1C,KAAI,WAAW,KAAK,CAClB,KAAI;EACF,MAAM,MAAM,aAAa,MAAM,QAAQ;EACvC,MAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,SAAO,aAAa,MAAM,KAAK;UACxB,KAAK;AACZ,UAAQ,KAAK,uCAAuC,KAAK,IAAI,MAAM;AACnE,UAAQ,KAAK,+BAA+B;;AAIhD,QAAO,aAAa,MAAM,EAAE,CAAC;;;AAI/B,SAAgB,WAAW,QAAgB,YAA2B;CACpE,MAAM,OAAO,cAAc,eAAe;CAC1C,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AAErC,eAAc,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"loader.mjs","names":[],"sources":["../../src/config/loader.ts"],"sourcesContent":["import { existsSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { ConfigSchema, type Config } from \"./schema.js\";\n\n/** Get the nanobot data directory. */\nexport function getDataDir(): string {\n const dir = join(homedir(), \".nanobot\");\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\n/** Build configuration entirely from environment variables. */\nexport function loadConfig(): Config {\n const config = ConfigSchema.parse({});\n const env = process.env;\n\n // LLM provider keys\n if (env.OPENROUTER_API_KEY || env.LLM_API_KEY) {\n config.providers.openrouter.apiKey =\n env.OPENROUTER_API_KEY || env.LLM_API_KEY || \"\";\n }\n if (env.ANTHROPIC_API_KEY) {\n config.providers.anthropic.apiKey = env.ANTHROPIC_API_KEY;\n }\n if (env.OPENAI_API_KEY) {\n config.providers.openai.apiKey = env.OPENAI_API_KEY;\n }\n\n // Model\n if (env.LLM_MODEL) {\n config.agents.defaults.model = env.LLM_MODEL;\n }\n\n // API base\n if (env.LLM_API_BASE) {\n if (config.providers.openrouter.apiKey) {\n config.providers.openrouter.apiBase = env.LLM_API_BASE;\n }\n }\n\n // LINE\n if (env.LINE_CHANNEL_SECRET) {\n config.channels.line.channelSecret = env.LINE_CHANNEL_SECRET;\n config.channels.line.enabled = true;\n }\n if (env.LINE_CHANNEL_ACCESS_TOKEN) {\n config.channels.line.channelAccessToken = env.LINE_CHANNEL_ACCESS_TOKEN;\n config.channels.line.enabled = true;\n }\n\n // Brave search\n if (env.BRAVE_API_KEY) {\n config.tools.web.search.apiKey = env.BRAVE_API_KEY;\n }\n\n // Workspace\n if (env.NANOBOT_WORKSPACE) {\n config.agents.defaults.workspace = env.NANOBOT_WORKSPACE;\n }\n\n // Restrict to workspace\n if (env.NANOBOT_RESTRICT_WORKSPACE === \"true\" || env.NANOBOT_RESTRICT_WORKSPACE === \"1\") {\n config.tools.restrictToWorkspace = true;\n }\n\n return config;\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,aAAqB;CACnC,MAAM,MAAM,KAAK,SAAS,EAAE,WAAW;AACvC,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AAErC,QAAO;;;AAIT,SAAgB,aAAqB;CACnC,MAAM,SAAS,aAAa,MAAM,EAAE,CAAC;CACrC,MAAM,MAAM,QAAQ;AAGpB,KAAI,IAAI,sBAAsB,IAAI,YAChC,QAAO,UAAU,WAAW,SAC1B,IAAI,sBAAsB,IAAI,eAAe;AAEjD,KAAI,IAAI,kBACN,QAAO,UAAU,UAAU,SAAS,IAAI;AAE1C,KAAI,IAAI,eACN,QAAO,UAAU,OAAO,SAAS,IAAI;AAIvC,KAAI,IAAI,UACN,QAAO,OAAO,SAAS,QAAQ,IAAI;AAIrC,KAAI,IAAI,cACN;MAAI,OAAO,UAAU,WAAW,OAC9B,QAAO,UAAU,WAAW,UAAU,IAAI;;AAK9C,KAAI,IAAI,qBAAqB;AAC3B,SAAO,SAAS,KAAK,gBAAgB,IAAI;AACzC,SAAO,SAAS,KAAK,UAAU;;AAEjC,KAAI,IAAI,2BAA2B;AACjC,SAAO,SAAS,KAAK,qBAAqB,IAAI;AAC9C,SAAO,SAAS,KAAK,UAAU;;AAIjC,KAAI,IAAI,cACN,QAAO,MAAM,IAAI,OAAO,SAAS,IAAI;AAIvC,KAAI,IAAI,kBACN,QAAO,OAAO,SAAS,YAAY,IAAI;AAIzC,KAAI,IAAI,+BAA+B,UAAU,IAAI,+BAA+B,IAClF,QAAO,MAAM,sBAAsB;AAGrC,QAAO"}
@@ -70,31 +70,31 @@ declare const ChannelsConfigSchema: z.ZodObject<{
70
70
  channelAccessToken?: string | undefined;
71
71
  }>>;
72
72
  }, "strip", z.ZodTypeAny, {
73
- line: {
74
- enabled: boolean;
75
- allowFrom: string[];
76
- channelSecret: string;
77
- channelAccessToken: string;
78
- };
79
73
  telegram: {
80
74
  enabled: boolean;
81
75
  token: string;
82
76
  allowFrom: string[];
83
77
  proxy?: string | null | undefined;
84
78
  };
79
+ line: {
80
+ enabled: boolean;
81
+ allowFrom: string[];
82
+ channelSecret: string;
83
+ channelAccessToken: string;
84
+ };
85
85
  }, {
86
- line?: {
87
- enabled?: boolean | undefined;
88
- allowFrom?: string[] | undefined;
89
- channelSecret?: string | undefined;
90
- channelAccessToken?: string | undefined;
91
- } | undefined;
92
86
  telegram?: {
93
87
  enabled?: boolean | undefined;
94
88
  token?: string | undefined;
95
89
  allowFrom?: string[] | undefined;
96
90
  proxy?: string | null | undefined;
97
91
  } | undefined;
92
+ line?: {
93
+ enabled?: boolean | undefined;
94
+ allowFrom?: string[] | undefined;
95
+ channelSecret?: string | undefined;
96
+ channelAccessToken?: string | undefined;
97
+ } | undefined;
98
98
  }>;
99
99
  type ChannelsConfig = z.infer<typeof ChannelsConfigSchema>;
100
100
  declare const AgentDefaultsSchema: z.ZodObject<{
@@ -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
+ };
542
545
  web: {
543
546
  search: {
544
547
  apiKey: string;
545
548
  maxResults: number;
546
549
  };
547
550
  };
548
- exec: {
549
- timeout: number;
550
- };
551
551
  restrictToWorkspace: boolean;
552
552
  enabled?: string[] | undefined;
553
553
  disabled?: string[] | undefined;
@@ -557,6 +557,9 @@ declare const ToolsConfigSchema: z.ZodObject<{
557
557
  export?: string | undefined;
558
558
  }[] | undefined;
559
559
  }, {
560
+ exec?: {
561
+ timeout?: number | undefined;
562
+ } | undefined;
560
563
  enabled?: string[] | undefined;
561
564
  web?: {
562
565
  search?: {
@@ -564,9 +567,6 @@ declare const ToolsConfigSchema: z.ZodObject<{
564
567
  maxResults?: number | undefined;
565
568
  } | undefined;
566
569
  } | undefined;
567
- exec?: {
568
- timeout?: number | undefined;
569
- } | undefined;
570
570
  restrictToWorkspace?: boolean | undefined;
571
571
  disabled?: string[] | undefined;
572
572
  custom?: {
@@ -648,31 +648,31 @@ declare const ConfigSchema: z.ZodObject<{
648
648
  channelAccessToken?: string | undefined;
649
649
  }>>;
650
650
  }, "strip", z.ZodTypeAny, {
651
- line: {
652
- enabled: boolean;
653
- allowFrom: string[];
654
- channelSecret: string;
655
- channelAccessToken: string;
656
- };
657
651
  telegram: {
658
652
  enabled: boolean;
659
653
  token: string;
660
654
  allowFrom: string[];
661
655
  proxy?: string | null | undefined;
662
656
  };
657
+ line: {
658
+ enabled: boolean;
659
+ allowFrom: string[];
660
+ channelSecret: string;
661
+ channelAccessToken: string;
662
+ };
663
663
  }, {
664
- line?: {
665
- enabled?: boolean | undefined;
666
- allowFrom?: string[] | undefined;
667
- channelSecret?: string | undefined;
668
- channelAccessToken?: string | undefined;
669
- } | undefined;
670
664
  telegram?: {
671
665
  enabled?: boolean | undefined;
672
666
  token?: string | undefined;
673
667
  allowFrom?: string[] | undefined;
674
668
  proxy?: string | null | undefined;
675
669
  } | undefined;
670
+ line?: {
671
+ enabled?: boolean | undefined;
672
+ allowFrom?: string[] | undefined;
673
+ channelSecret?: string | undefined;
674
+ channelAccessToken?: string | undefined;
675
+ } | undefined;
676
676
  }>>;
677
677
  providers: z.ZodDefault<z.ZodObject<{
678
678
  anthropic: z.ZodDefault<z.ZodObject<{
@@ -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
+ };
991
994
  web: {
992
995
  search: {
993
996
  apiKey: string;
994
997
  maxResults: number;
995
998
  };
996
999
  };
997
- exec: {
998
- timeout: number;
999
- };
1000
1000
  restrictToWorkspace: boolean;
1001
1001
  enabled?: string[] | undefined;
1002
1002
  disabled?: string[] | undefined;
@@ -1006,6 +1006,9 @@ declare const ConfigSchema: z.ZodObject<{
1006
1006
  export?: string | undefined;
1007
1007
  }[] | undefined;
1008
1008
  }, {
1009
+ exec?: {
1010
+ timeout?: number | undefined;
1011
+ } | undefined;
1009
1012
  enabled?: string[] | undefined;
1010
1013
  web?: {
1011
1014
  search?: {
@@ -1013,9 +1016,6 @@ declare const ConfigSchema: z.ZodObject<{
1013
1016
  maxResults?: number | undefined;
1014
1017
  } | undefined;
1015
1018
  } | undefined;
1016
- exec?: {
1017
- timeout?: number | undefined;
1018
- } | undefined;
1019
1019
  restrictToWorkspace?: boolean | undefined;
1020
1020
  disabled?: string[] | undefined;
1021
1021
  custom?: {
@@ -1035,18 +1035,18 @@ declare const ConfigSchema: z.ZodObject<{
1035
1035
  };
1036
1036
  };
1037
1037
  channels: {
1038
- line: {
1039
- enabled: boolean;
1040
- allowFrom: string[];
1041
- channelSecret: string;
1042
- channelAccessToken: string;
1043
- };
1044
1038
  telegram: {
1045
1039
  enabled: boolean;
1046
1040
  token: string;
1047
1041
  allowFrom: string[];
1048
1042
  proxy?: string | null | undefined;
1049
1043
  };
1044
+ line: {
1045
+ enabled: boolean;
1046
+ allowFrom: string[];
1047
+ channelSecret: string;
1048
+ channelAccessToken: string;
1049
+ };
1050
1050
  };
1051
1051
  providers: {
1052
1052
  anthropic: {
@@ -1110,15 +1110,15 @@ declare const ConfigSchema: z.ZodObject<{
1110
1110
  port: number;
1111
1111
  };
1112
1112
  tools: {
1113
+ exec: {
1114
+ timeout: number;
1115
+ };
1113
1116
  web: {
1114
1117
  search: {
1115
1118
  apiKey: string;
1116
1119
  maxResults: number;
1117
1120
  };
1118
1121
  };
1119
- exec: {
1120
- timeout: number;
1121
- };
1122
1122
  restrictToWorkspace: boolean;
1123
1123
  enabled?: string[] | undefined;
1124
1124
  disabled?: string[] | undefined;
@@ -1139,18 +1139,18 @@ declare const ConfigSchema: z.ZodObject<{
1139
1139
  } | undefined;
1140
1140
  } | undefined;
1141
1141
  channels?: {
1142
- line?: {
1143
- enabled?: boolean | undefined;
1144
- allowFrom?: string[] | undefined;
1145
- channelSecret?: string | undefined;
1146
- channelAccessToken?: string | undefined;
1147
- } | undefined;
1148
1142
  telegram?: {
1149
1143
  enabled?: boolean | undefined;
1150
1144
  token?: string | undefined;
1151
1145
  allowFrom?: string[] | undefined;
1152
1146
  proxy?: string | null | undefined;
1153
1147
  } | undefined;
1148
+ line?: {
1149
+ enabled?: boolean | undefined;
1150
+ allowFrom?: string[] | undefined;
1151
+ channelSecret?: string | undefined;
1152
+ channelAccessToken?: string | undefined;
1153
+ } | undefined;
1154
1154
  } | undefined;
1155
1155
  providers?: {
1156
1156
  anthropic?: {
@@ -1214,6 +1214,9 @@ declare const ConfigSchema: z.ZodObject<{
1214
1214
  port?: number | undefined;
1215
1215
  } | undefined;
1216
1216
  tools?: {
1217
+ exec?: {
1218
+ timeout?: number | undefined;
1219
+ } | undefined;
1217
1220
  enabled?: string[] | undefined;
1218
1221
  web?: {
1219
1222
  search?: {
@@ -1221,9 +1224,6 @@ declare const ConfigSchema: z.ZodObject<{
1221
1224
  maxResults?: number | undefined;
1222
1225
  } | undefined;
1223
1226
  } | undefined;
1224
- exec?: {
1225
- timeout?: number | undefined;
1226
- } | undefined;
1227
1227
  restrictToWorkspace?: boolean | undefined;
1228
1228
  disabled?: string[] | undefined;
1229
1229
  custom?: {
package/dist/index.d.mts CHANGED
@@ -12,7 +12,7 @@ import { AgentLoop } from "./agent/loop.mjs";
12
12
  import { LineChannel } from "./channels/line.mjs";
13
13
  import { ChannelManager } from "./channels/manager.mjs";
14
14
  import { TelegramChannel } from "./channels/telegram.mjs";
15
- import { loadConfig, saveConfig } from "./config/loader.mjs";
15
+ import { loadConfig } from "./config/loader.mjs";
16
16
  import { HeartbeatService } from "./heartbeat/service.mjs";
17
17
  import { OpenAIProvider } from "./providers/openai-provider.mjs";
18
18
 
@@ -20,5 +20,5 @@ import { OpenAIProvider } from "./providers/openai-provider.mjs";
20
20
  declare const VERSION: string;
21
21
  declare const LOGO = "\uD83D\uDC08";
22
22
  //#endregion
23
- export { AgentLoop, ChannelManager, type Config, ContextBuilder, HeartbeatService, type InboundMessage, type LLMProvider, type LLMResponse, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, type OutboundMessage, PROVIDERS, type ProviderSpec, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, type ToolCallRequest, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel, saveConfig };
23
+ export { AgentLoop, ChannelManager, type Config, ContextBuilder, HeartbeatService, type InboundMessage, type LLMProvider, type LLMResponse, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, type OutboundMessage, PROVIDERS, type ProviderSpec, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, type ToolCallRequest, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel };
24
24
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ import { SessionManager } from "./session/manager.mjs";
6
6
  import { AgentLoop } from "./agent/loop.mjs";
7
7
  import { MessageBus } from "./bus/queue.mjs";
8
8
  import { PROVIDERS, findByModel, findByName, findGateway, getModelOverrides, resolveModel } from "./providers/registry.mjs";
9
- import { loadConfig, saveConfig } from "./config/loader.mjs";
9
+ import { loadConfig } from "./config/loader.mjs";
10
10
  import { OpenAIProvider } from "./providers/openai-provider.mjs";
11
11
  import { TelegramChannel } from "./channels/telegram.mjs";
12
12
  import { LineChannel } from "./channels/line.mjs";
@@ -31,5 +31,5 @@ const VERSION = readVersion();
31
31
  const LOGO = "🐈";
32
32
 
33
33
  //#endregion
34
- export { AgentLoop, ChannelManager, ContextBuilder, HeartbeatService, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, PROVIDERS, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel, saveConfig };
34
+ export { AgentLoop, ChannelManager, ContextBuilder, HeartbeatService, LOGO, LineChannel, MemoryStore, MessageBus, OpenAIProvider, PROVIDERS, SessionManager, SkillsLoader, SubagentManager, TelegramChannel, VERSION, findByModel, findByName, findGateway, getModelOverrides, loadConfig, resolveModel };
35
35
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction readVersion(): string {\n try {\n // Works from both src/ (dev) and dist/ (built)\n for (const rel of [\"../package.json\", \"../../package.json\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, rel), \"utf-8\"));\n if (pkg.version) return pkg.version;\n } catch { /* try next */ }\n }\n } catch { /* fallback */ }\n return \"0.0.0\";\n}\n\nexport const VERSION = readVersion();\nexport const LOGO = \"\\u{1F408}\";\n\n// Core exports\nexport { AgentLoop } from \"./agent/loop.js\";\nexport { ContextBuilder } from \"./agent/context.js\";\nexport { MemoryStore } from \"./agent/memory.js\";\nexport { SkillsLoader } from \"./agent/skills.js\";\nexport { SubagentManager } from \"./agent/subagent.js\";\n\n// Bus\nexport { MessageBus } from \"./bus/queue.js\";\nexport type { InboundMessage, OutboundMessage } from \"./bus/events.js\";\n\n// Config\nexport { loadConfig, saveConfig } from \"./config/loader.js\";\nexport type { Config } from \"./config/schema.js\";\n\n// Providers\nexport { OpenAIProvider } from \"./providers/openai-provider.js\";\nexport type { LLMProvider, LLMResponse, ToolCallRequest } from \"./providers/base.js\";\nexport { PROVIDERS, findByModel, findByName, findGateway, resolveModel, getModelOverrides } from \"./providers/registry.js\";\nexport type { ProviderSpec } from \"./providers/registry.js\";\n\n// Channels\nexport { TelegramChannel } from \"./channels/telegram.js\";\nexport { LineChannel } from \"./channels/line.js\";\nexport { ChannelManager } from \"./channels/manager.js\";\n\n// Services\nexport { HeartbeatService } from \"./heartbeat/service.js\";\nexport { SessionManager } from \"./session/manager.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAIA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,cAAsB;AAC7B,KAAI;AAEF,OAAK,MAAM,OAAO,CAAC,mBAAmB,qBAAqB,CACzD,KAAI;GACF,MAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,IAAI,EAAE,QAAQ,CAAC;AACnE,OAAI,IAAI,QAAS,QAAO,IAAI;UACtB;SAEJ;AACR,QAAO;;AAGT,MAAa,UAAU,aAAa;AACpC,MAAa,OAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction readVersion(): string {\n try {\n // Works from both src/ (dev) and dist/ (built)\n for (const rel of [\"../package.json\", \"../../package.json\"]) {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, rel), \"utf-8\"));\n if (pkg.version) return pkg.version;\n } catch { /* try next */ }\n }\n } catch { /* fallback */ }\n return \"0.0.0\";\n}\n\nexport const VERSION = readVersion();\nexport const LOGO = \"\\u{1F408}\";\n\n// Core exports\nexport { AgentLoop } from \"./agent/loop.js\";\nexport { ContextBuilder } from \"./agent/context.js\";\nexport { MemoryStore } from \"./agent/memory.js\";\nexport { SkillsLoader } from \"./agent/skills.js\";\nexport { SubagentManager } from \"./agent/subagent.js\";\n\n// Bus\nexport { MessageBus } from \"./bus/queue.js\";\nexport type { InboundMessage, OutboundMessage } from \"./bus/events.js\";\n\n// Config\nexport { loadConfig } from \"./config/loader.js\";\nexport type { Config } from \"./config/schema.js\";\n\n// Providers\nexport { OpenAIProvider } from \"./providers/openai-provider.js\";\nexport type { LLMProvider, LLMResponse, ToolCallRequest } from \"./providers/base.js\";\nexport { PROVIDERS, findByModel, findByName, findGateway, resolveModel, getModelOverrides } from \"./providers/registry.js\";\nexport type { ProviderSpec } from \"./providers/registry.js\";\n\n// Channels\nexport { TelegramChannel } from \"./channels/telegram.js\";\nexport { LineChannel } from \"./channels/line.js\";\nexport { ChannelManager } from \"./channels/manager.js\";\n\n// Services\nexport { HeartbeatService } from \"./heartbeat/service.js\";\nexport { SessionManager } from \"./session/manager.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAIA,MAAM,YAAY,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEzD,SAAS,cAAsB;AAC7B,KAAI;AAEF,OAAK,MAAM,OAAO,CAAC,mBAAmB,qBAAqB,CACzD,KAAI;GACF,MAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,IAAI,EAAE,QAAQ,CAAC;AACnE,OAAI,IAAI,QAAS,QAAO,IAAI;UACtB;SAEJ;AACR,QAAO;;AAGT,MAAa,UAAU,aAAa;AACpC,MAAa,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcheesepkg/nanobot",
3
- "version": "0.6.99",
3
+ "version": "0.7.01",
4
4
  "description": "Lightweight AI assistant - TypeScript port",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",