diff --git a/README.md b/README.md index d0e993c..a004da7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ mkdir -p ~/.nanobot **2. Chat** ```bash -bun run start agent +bun run nanobot agent ``` That's it. @@ -54,22 +54,21 @@ That's it. Chat with the agent from your terminal. Does not require a running gateway. ``` -bun run start agent [options] +bun run nanobot agent [options] ``` | Option | Description | |--------|-------------| | `-c, --config ` | Path to `config.json` (default: `~/.nanobot/config.json`) | | `-m, --message ` | Send a single message and exit (non-interactive) | -| `-w, --workspace ` | Override the workspace directory | | `-M, --model ` | Override the model for this session | **Interactive mode** (default when no `-m` is given): ```bash -bun run start agent -bun run start agent -c ~/.nanobot-work/config.json -bun run start agent -w /tmp/scratch +bun run nanobot agent +bun run nanobot agent -c ~/.nanobot-work/config.json +bun run nanobot agent -w /tmp/scratch ``` Press `Ctrl+C` to exit. @@ -77,8 +76,8 @@ Press `Ctrl+C` to exit. **Single-shot mode:** ```bash -bun run start agent -m "What time is it in Tokyo?" -bun run start agent -m "Summarize the file ./notes.md" +bun run nanobot agent -m "What time is it in Tokyo?" +bun run nanobot agent -m "Summarize the file ./notes.md" ``` ### `gateway` — Mattermost bot @@ -86,7 +85,7 @@ bun run start agent -m "Summarize the file ./notes.md" Runs the full stack: Mattermost WebSocket channel, agent loop, cron scheduler, and heartbeat. ``` -bun run start gateway [options] +bun run nanobot gateway [options] ``` | Option | Description | @@ -94,8 +93,8 @@ bun run start gateway [options] | `-c, --config ` | Path to `config.json` (default: `~/.nanobot/config.json`) | ```bash -bun run start gateway -bun run start gateway -c ~/.nanobot-work/config.json +bun run nanobot gateway +bun run nanobot gateway -c ~/.nanobot-work/config.json ``` Handles `SIGINT` / `SIGTERM` for graceful shutdown. @@ -110,7 +109,6 @@ Environment variable overrides: |----------|-------------------| | `NANOBOT_CONFIG` | path to config file | | `NANOBOT_MODEL` | `agent.model` | -| `NANOBOT_WORKSPACE` | `agent.workspacePath` | ### Full config reference @@ -198,7 +196,7 @@ For Ollama, set `providers.ollama.apiBase` (default: `http://localhost:11434/api } ``` -4. Run `bun run start gateway` +4. Run `bun run nanobot gateway` `allowFrom` controls which users the bot responds to. Use `["*"]` to allow all users. @@ -233,10 +231,10 @@ Run separate instances with different configs — useful for isolated workspaces ```bash # Instance A -bun run start gateway -c ~/.nanobot-a/config.json +bun run nanobot gateway -c ~/.nanobot-a/config.json # Instance B -bun run start gateway -c ~/.nanobot-b/config.json +bun run nanobot gateway -c ~/.nanobot-b/config.json ``` Each instance needs its own config file. Set a different `agent.workspacePath` per instance to keep memory, sessions, and cron jobs isolated: @@ -252,10 +250,10 @@ Each instance needs its own config file. Set a different `agent.workspacePath` p To run a local CLI session against a specific instance: ```bash -bun run start agent -c ~/.nanobot-a/config.json -m "Hello" +bun run nanobot agent -c ~/.nanobot-a/config.json -m "Hello" # Temporarily override the workspace for a one-off run -bun run start agent -c ~/.nanobot-a/config.json -w /tmp/scratch +bun run nanobot agent -c ~/.nanobot-a/config.json -w /tmp/scratch ``` ## Linux service (systemd) diff --git a/package.json b/package.json index f2c8253..4a7596c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "type": "module", "module": "index.ts", "scripts": { - "start": "bun run index.ts", + "nanobot": "bun run index.ts", "dev": "bun --watch run index.ts", "typecheck": "tsc --noEmit", "fmt": "oxfmt --check", diff --git a/src/cli/agent.ts b/src/cli/agent.ts index a920d69..c90a37f 100644 --- a/src/cli/agent.ts +++ b/src/cli/agent.ts @@ -1,4 +1,3 @@ -import { mkdirSync } from 'node:fs'; import { createInterface } from 'node:readline'; import { Command } from 'commander'; import pc from 'picocolors'; @@ -8,85 +7,79 @@ import type { Config } from '../config/types.ts'; import { makeProvider } from '../provider/index.ts'; export function agentCommand(program: Command, config: Config, workspace: string): void { - mkdirSync(workspace, { recursive: true }); - program .command('agent') .description('Run the agent interactively or send a single message.') - .option('-c, --config ', 'Path to config.json') .option('-m, --message ', 'Single message to process (non-interactive)') - .option('-w, --workspace ', 'Workspace path override') .option('-M, --model ', 'Model override') - .action( - async (opts: { config?: string; message?: string; workspace?: string; model?: string }) => { - const model = opts.model ?? config.agent.model; - const provider = makeProvider( - config.providers, - model, - config.agent.maxTokens, - config.agent.temperature, - ); - const bus = new MessageBus(); + .action(async (opts: { config?: string; message?: string; model?: string }) => { + const model = opts.model ?? config.agent.model; + const provider = makeProvider( + config.providers, + model, + config.agent.maxTokens, + config.agent.temperature, + ); + const bus = new MessageBus(); - const agentLoop = new AgentLoop({ - bus, - provider, - workspace, - model, - maxIterations: config.agent.maxToolIterations, - contextWindowTokens: config.agent.contextWindowTokens, - braveApiKey: config.tools.web.braveApiKey, - webProxy: config.tools.web.proxy, - execConfig: config.tools.exec, - restrictToWorkspace: config.tools.restrictToWorkspace, - }); + const agentLoop = new AgentLoop({ + bus, + provider, + workspace, + model, + maxIterations: config.agent.maxToolIterations, + contextWindowTokens: config.agent.contextWindowTokens, + braveApiKey: config.tools.web.braveApiKey, + webProxy: config.tools.web.proxy, + execConfig: config.tools.exec, + restrictToWorkspace: config.tools.restrictToWorkspace, + }); - // Single-shot mode - if (opts.message) { - const result = await agentLoop.processDirect(opts.message); - console.log(result); - return; - } + // Single-shot mode + if (opts.message) { + const result = await agentLoop.processDirect(opts.message); + console.log(result); + return; + } - // Interactive mode - console.info(pc.green('nanobot interactive mode. Type your message, Ctrl+C to exit.')); + // Interactive mode + console.info(pc.green('nanobot interactive mode. Type your message, Ctrl+C to exit.')); - const rl = createInterface({ input: process.stdin, output: process.stdout }); + const rl = createInterface({ input: process.stdin, output: process.stdout }); - const promptUser = () => { - rl.question(pc.cyan('You: '), async (input) => { - const text = input.trim(); - if (!text) { - promptUser(); - return; - } - - const onProgress = async (content: string, opts?: { toolHint?: boolean }) => { - if (opts?.toolHint) { - process.stdout.write(pc.dim(` [${content}]\n`)); - } else { - process.stdout.write(pc.dim(` ${content}\n`)); - } - }; - - const result = await agentLoop.processDirect( - text, - 'cli:interactive', - 'cli', - 'interactive', - onProgress, - ); - console.log(pc.bold('Bot:'), result); + const promptUser = () => { + rl.question(pc.cyan('You: '), async (input) => { + const text = input.trim(); + if (!text) { promptUser(); - }); - }; + return; + } - rl.on('close', () => { - agentLoop.stop(); - process.exit(0); + const onProgress = async (content: string, opts?: { toolHint?: boolean }) => { + if (opts?.toolHint) { + process.stdout.write(pc.dim(` [${content}]\n`)); + } else { + process.stdout.write(pc.dim(` ${content}\n`)); + } + }; + + const result = await agentLoop.processDirect( + text, + 'cli:interactive', + 'cli', + 'interactive', + onProgress, + ); + console.log(pc.bold('Bot:'), result); + promptUser(); }); + }; - promptUser(); - }, - ); + rl.on('close', () => { + agentLoop.stop(); + process.exit(0); + }); + + promptUser(); + }); } diff --git a/src/cli/commands.ts b/src/cli/commands.ts index 7e325ee..ef5400b 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -3,15 +3,19 @@ import { Command } from 'commander'; import { loadConfig, resolveWorkspacePath } from '../config/loader.ts'; import { agentCommand } from './agent.ts'; import { gatewayCommand } from './gateway.ts'; +import pc from 'picocolors'; export function createCli(): Command { const program = new Command('nanobot') .description('nanobot — personal AI assistant') + .option('-c, --config ', 'Path to config.json') .version('1.0.0'); const globalOpts = program.opts(); const config = loadConfig(globalOpts.config); const workspace = resolveWorkspacePath(config.agent.workspacePath); + + console.info(pc.aqua(`workspace path: ${workspace}`)); mkdirSync(workspace, { recursive: true }); gatewayCommand(program, config, workspace); diff --git a/src/cli/gateway.ts b/src/cli/gateway.ts index 76726b4..92693fe 100644 --- a/src/cli/gateway.ts +++ b/src/cli/gateway.ts @@ -1,4 +1,3 @@ -import { mkdirSync } from 'node:fs'; import { Command } from 'commander'; import pc from 'picocolors'; import { AgentLoop } from '../agent/loop.ts'; @@ -11,12 +10,9 @@ import { HeartbeatService } from '../heartbeat/service.ts'; import { makeProvider } from '../provider/index.ts'; export function gatewayCommand(program: Command, config: Config, workspace: string): void { - mkdirSync(workspace, { recursive: true }); - program .command('gateway') .description('Start the full gateway: Mattermost channel, agent loop, cron, and heartbeat.') - .option('-c, --config ', 'Path to config.json') .action(async (_opts: { config?: string }) => { const provider = makeProvider( config.providers,