Compare commits
3 Commits
9ac92ed536
...
47c4db53af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47c4db53af | ||
|
|
1dd953d17a | ||
|
|
e915dd2922 |
@@ -4,6 +4,7 @@ import pc from 'picocolors';
|
||||
import { AgentLoop } from '../agent/loop.ts';
|
||||
import { MessageBus } from '../bus/queue.ts';
|
||||
import { makeProvider } from '../provider/index.ts';
|
||||
import { ensureWorkspace } from './utils.ts';
|
||||
import type { Config } from '../config/types.ts';
|
||||
|
||||
export function agentCommand(program: Command, config: Config, workspace: string): void {
|
||||
@@ -14,6 +15,7 @@ export function agentCommand(program: Command, config: Config, workspace: string
|
||||
.option('-m, --message <text>', 'Single message to process (non-interactive)')
|
||||
.option('-M, --model <model>', 'Model override')
|
||||
.action(async (opts: { config?: string; message?: string; model?: string }) => {
|
||||
ensureWorkspace(workspace);
|
||||
console.info(pc.magenta(`workspace path: ${workspace}`));
|
||||
|
||||
const model = opts.model ?? config.agent.model;
|
||||
|
||||
@@ -3,23 +3,18 @@ import { loadConfig, resolveWorkspacePath } from '../config/loader.ts';
|
||||
import { agentCommand } from './agent.ts';
|
||||
import { gatewayCommand } from './gateway.ts';
|
||||
import { onboardCommand } from './onboard.ts';
|
||||
import { ensureWorkspace } from './utils.ts';
|
||||
|
||||
export function createCli(): Command {
|
||||
const program = new Command('nanobot')
|
||||
.description('nanobot — personal AI assistant')
|
||||
.version('1.0.0');
|
||||
|
||||
// Register onboard command first (doesn't need config/workspace)
|
||||
onboardCommand(program);
|
||||
|
||||
// load config and get workspace
|
||||
const globalOpts = program.opts();
|
||||
const config = loadConfig(globalOpts.config);
|
||||
const workspace = resolveWorkspacePath(config.agent.workspacePath);
|
||||
|
||||
ensureWorkspace(workspace);
|
||||
|
||||
gatewayCommand(program, config, workspace);
|
||||
agentCommand(program, config, workspace);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { MattermostChannel } from '../channels/mattermost.ts';
|
||||
import { CronService } from '../cron/service.ts';
|
||||
import { HeartbeatService } from '../heartbeat/service.ts';
|
||||
import { makeProvider } from '../provider/index.ts';
|
||||
import { ensureWorkspace } from './utils.ts';
|
||||
|
||||
import type { Config } from '../config/types.ts';
|
||||
|
||||
@@ -16,6 +17,7 @@ export function gatewayCommand(program: Command, config: Config, workspace: stri
|
||||
.option('-c, --config <path>', 'Path to config.json')
|
||||
.description('Start the full gateway: Mattermost channel, agent loop, cron, and heartbeat.')
|
||||
.action(async (_opts: { config?: string }) => {
|
||||
ensureWorkspace(workspace);
|
||||
console.info(pc.magenta(`workspace path: ${workspace}`));
|
||||
|
||||
const provider = makeProvider(
|
||||
|
||||
@@ -5,13 +5,19 @@ import pc from 'picocolors';
|
||||
import { ConfigSchema, type Config } from '../config/types.ts';
|
||||
import { ensureWorkspace, resolvePath, checkWorkspaceEmpty, syncTemplates } from './utils.ts';
|
||||
|
||||
function logCreated(item: string) {
|
||||
console.info(pc.green(` ✓ Created ${item}`));
|
||||
}
|
||||
|
||||
export function onboardCommand(program: Command): void {
|
||||
program
|
||||
.command('onboard [path]')
|
||||
.description('Initialize a new nanobot workspace with config and templates')
|
||||
.action(async (rawPath?: string) => {
|
||||
try {
|
||||
const targetPath = resolvePath(rawPath ?? '~/.config/nanobot');
|
||||
const defaultConfig: Config = ConfigSchema.parse({});
|
||||
|
||||
const targetPath = resolvePath(rawPath ?? defaultConfig.agent.workspacePath);
|
||||
const configPath = join(targetPath, 'config.json');
|
||||
|
||||
console.info(pc.blue('Initializing nanobot workspace...'));
|
||||
@@ -21,18 +27,17 @@ export function onboardCommand(program: Command): void {
|
||||
checkWorkspaceEmpty(targetPath);
|
||||
|
||||
// Create workspace directory
|
||||
ensureWorkspace(targetPath);
|
||||
console.info(pc.green('✓ Created workspace directory'));
|
||||
ensureWorkspace(targetPath, true);
|
||||
logCreated('workspace directory')
|
||||
|
||||
// Write default config
|
||||
const defaultConfig: Config = ConfigSchema.parse({});
|
||||
writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf8');
|
||||
console.info(pc.green('✓ Created config.json'));
|
||||
logCreated('config.json')
|
||||
|
||||
// Sync templates
|
||||
const createdFiles = syncTemplates(targetPath);
|
||||
for (const file of createdFiles) {
|
||||
console.info(pc.dim(` Created ${file}`));
|
||||
logCreated(file)
|
||||
}
|
||||
|
||||
console.info();
|
||||
@@ -40,10 +45,13 @@ export function onboardCommand(program: Command): void {
|
||||
console.info();
|
||||
console.info(pc.bold('Next steps:'));
|
||||
console.info(` 1. Edit ${pc.cyan(configPath)} to add your API keys`);
|
||||
console.info(` 2. Customize ${pc.cyan(join(targetPath, 'USER.md'))} with your preferences`);
|
||||
console.info(` 2. Customize ${pc.cyan(join(targetPath, 'USER.md'))} and ${pc.cyan(join(targetPath, 'SOUL.md'))} with your preferences`);
|
||||
console.info(` 3. Start chatting: ${pc.cyan('bun run nanobot agent')}`);
|
||||
console.info();
|
||||
console.info(pc.dim('For Mattermost integration, configure the channels.mattermost section in config.json'));
|
||||
console.info(` -- For gateway mode:`);
|
||||
console.info(` 1. Edit ${pc.cyan(configPath)} to add your channel config (Mattermost)`);
|
||||
console.info(` 2. Connect your agent: ${pc.cyan('bun run nanobot gateway')}`);
|
||||
console.info();
|
||||
} catch (err) {
|
||||
console.error(pc.red(String(err)));
|
||||
process.exit(1);
|
||||
|
||||
@@ -11,19 +11,24 @@ export function resolvePath(raw: string): string {
|
||||
return resolve(raw);
|
||||
}
|
||||
|
||||
export function ensureWorkspace(rawPath: string): string {
|
||||
export function ensureWorkspace(rawPath: string, createIfMissing = false): string {
|
||||
const path = resolvePath(rawPath);
|
||||
if (!existsSync(path)) {
|
||||
if (createIfMissing) {
|
||||
mkdirSync(path, { recursive: true });
|
||||
} else {
|
||||
console.error(pc.red(`Workspace does not exist: ${path}\nRun 'nanobot onboard' to initialize.`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function syncTemplates(workspacePath: string): string[] {
|
||||
// Get project root relative to this file
|
||||
// Get project root relative to this file (src/cli/utils.ts)
|
||||
const currentFile = fileURLToPath(import.meta.url);
|
||||
const srcDir = dirname(currentFile);
|
||||
const projectRoot = resolve(srcDir, '..');
|
||||
const projectRoot = resolve(srcDir, '..', '..');
|
||||
const templatesDir = resolve(projectRoot, 'templates');
|
||||
|
||||
if (!existsSync(templatesDir)) {
|
||||
|
||||
@@ -20,9 +20,9 @@ export function loadConfig(configPath?: string): Config {
|
||||
let json: unknown;
|
||||
try {
|
||||
json = JSON.parse(raw);
|
||||
} catch {
|
||||
} catch(error) {
|
||||
console.error(`Failed to parse config at ${path}`);
|
||||
return ConfigSchema.parse({});
|
||||
throw error
|
||||
}
|
||||
|
||||
// Apply NANOBOT_ env var overrides before validation
|
||||
|
||||
@@ -112,6 +112,7 @@ export type HeartbeatConfig = z.infer<typeof HeartbeatConfigSchema>;
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const ConfigSchema = z.object({
|
||||
providers: ProvidersConfigSchema.default(() => ({})),
|
||||
agent: AgentConfigSchema.default(() => ({
|
||||
model: 'anthropic/claude-sonnet-4-5',
|
||||
workspacePath: '~/.config/nanobot',
|
||||
@@ -120,13 +121,12 @@ export const ConfigSchema = z.object({
|
||||
temperature: 0.7,
|
||||
maxToolIterations: 40,
|
||||
})),
|
||||
providers: ProvidersConfigSchema.default(() => ({})),
|
||||
heartbeat: HeartbeatConfigSchema.default(() => ({ enabled: false, intervalMinutes: 30 })),
|
||||
channels: ChannelsConfigSchema.default(() => ({ sendProgress: true, sendToolHints: true })),
|
||||
tools: ToolsConfigSchema.default(() => ({
|
||||
exec: { timeout: 120, denyPatterns: [], restrictToWorkspace: false },
|
||||
web: {},
|
||||
restrictToWorkspace: false,
|
||||
})),
|
||||
heartbeat: HeartbeatConfigSchema.default(() => ({ enabled: false, intervalMinutes: 30 })),
|
||||
});
|
||||
export type Config = z.infer<typeof ConfigSchema>;
|
||||
|
||||
Reference in New Issue
Block a user