v2: make v2 the main entry point, move v1 to src/v1/
- Move all v1 files (index, router, container-runner, db, ipc, types, logger, channels/registry, and all utilities) to src/v1/ as a fully self-contained archive with no shared dependencies - Rename v2 files to remove -v2 suffix (index-v2.ts → index.ts, etc.) - Update all imports across v2 source, tests, and setup files - Migrate shared utilities (config, env, container-runtime, mount-security, timezone, group-folder) from pino logger to v2 log module - Migrate setup/ files from logger to log with argument order swap - Container agent-runner: move v1 entry to v1/, rename v2 to index.ts - Update setup skill to offer all 13 v2 channels - Install all Chat SDK adapter packages - dist/index.js now runs v2; dist/v1/index.js runs v1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
import { execSync } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import { commandExists } from './platform.js';
|
||||
import { emitStatus } from './status.js';
|
||||
|
||||
@@ -101,31 +101,31 @@ export async function run(args: string[]): Promise<void> {
|
||||
|
||||
// Build
|
||||
let buildOk = false;
|
||||
logger.info({ runtime }, 'Building container');
|
||||
log.info('Building container', { runtime });
|
||||
try {
|
||||
execSync(`${buildCmd} -t ${image} .`, {
|
||||
cwd: path.join(projectRoot, 'container'),
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
buildOk = true;
|
||||
logger.info('Container build succeeded');
|
||||
log.info('Container build succeeded');
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Container build failed');
|
||||
log.error('Container build failed', { err });
|
||||
}
|
||||
|
||||
// Test
|
||||
let testOk = false;
|
||||
if (buildOk) {
|
||||
logger.info('Testing container');
|
||||
log.info('Testing container');
|
||||
try {
|
||||
const output = execSync(
|
||||
`echo '{}' | ${runCmd} run -i --rm --entrypoint /bin/echo ${image} "Container OK"`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
|
||||
);
|
||||
testOk = output.includes('Container OK');
|
||||
logger.info({ testOk }, 'Container test result');
|
||||
log.info('Container test result', { testOk });
|
||||
} catch {
|
||||
logger.error('Container test failed');
|
||||
log.error('Container test failed');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ import path from 'path';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
import { STORE_DIR } from '../src/config.js';
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import { commandExists, getPlatform, isHeadless, isWSL } from './platform.js';
|
||||
import { emitStatus } from './status.js';
|
||||
|
||||
export async function run(_args: string[]): Promise<void> {
|
||||
const projectRoot = process.cwd();
|
||||
|
||||
logger.info('Starting environment check');
|
||||
log.info('Starting environment check');
|
||||
|
||||
const platform = getPlatform();
|
||||
const wsl = isWSL();
|
||||
@@ -66,7 +66,8 @@ export async function run(_args: string[]): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
log.info(
|
||||
'Environment check complete',
|
||||
{
|
||||
platform,
|
||||
wsl,
|
||||
@@ -76,7 +77,6 @@ export async function run(_args: string[]): Promise<void> {
|
||||
hasAuth,
|
||||
hasRegisteredGroups,
|
||||
},
|
||||
'Environment check complete',
|
||||
);
|
||||
|
||||
emitStatus('CHECK_ENVIRONMENT', {
|
||||
|
||||
@@ -11,7 +11,7 @@ import path from 'path';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
import { STORE_DIR } from '../src/config.js';
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import { emitStatus } from './status.js';
|
||||
|
||||
function parseArgs(args: string[]): { list: boolean; limit: number } {
|
||||
@@ -71,7 +71,7 @@ async function syncGroups(projectRoot: string): Promise<void> {
|
||||
fs.existsSync(authDir) && fs.readdirSync(authDir).length > 0;
|
||||
|
||||
if (!hasWhatsAppAuth) {
|
||||
logger.info('WhatsApp auth not found — skipping group sync');
|
||||
log.info('WhatsApp auth not found — skipping group sync');
|
||||
emitStatus('SYNC_GROUPS', {
|
||||
BUILD: 'skipped',
|
||||
SYNC: 'skipped',
|
||||
@@ -84,7 +84,7 @@ async function syncGroups(projectRoot: string): Promise<void> {
|
||||
}
|
||||
|
||||
// Build TypeScript first
|
||||
logger.info('Building TypeScript');
|
||||
log.info('Building TypeScript');
|
||||
let buildOk = false;
|
||||
try {
|
||||
execSync('npm run build', {
|
||||
@@ -92,9 +92,9 @@ async function syncGroups(projectRoot: string): Promise<void> {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
buildOk = true;
|
||||
logger.info('Build succeeded');
|
||||
log.info('Build succeeded');
|
||||
} catch {
|
||||
logger.error('Build failed');
|
||||
log.error('Build failed');
|
||||
emitStatus('SYNC_GROUPS', {
|
||||
BUILD: 'failed',
|
||||
SYNC: 'skipped',
|
||||
@@ -107,7 +107,7 @@ async function syncGroups(projectRoot: string): Promise<void> {
|
||||
}
|
||||
|
||||
// Run sync script via a temp file to avoid shell escaping issues with node -e
|
||||
logger.info('Fetching group metadata');
|
||||
log.info('Fetching group metadata');
|
||||
let syncOk = false;
|
||||
try {
|
||||
const syncScript = `
|
||||
@@ -189,12 +189,12 @@ sock.ev.on('connection.update', async (update) => {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
syncOk = output.includes('SYNCED:');
|
||||
logger.info({ output: output.trim() }, 'Sync output');
|
||||
log.info('Sync output', { output: output.trim() });
|
||||
} finally {
|
||||
try { fs.unlinkSync(tmpScript); } catch { /* ignore cleanup errors */ }
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Sync failed');
|
||||
log.error('Sync failed', { err });
|
||||
}
|
||||
|
||||
// Count groups in DB using better-sqlite3 (no sqlite3 CLI)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Setup CLI entry point.
|
||||
* Usage: npx tsx setup/index.ts --step <name> [args...]
|
||||
*/
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import { emitStatus } from './status.js';
|
||||
|
||||
const STEPS: Record<
|
||||
@@ -47,7 +47,7 @@ async function main(): Promise<void> {
|
||||
await mod.run(stepArgs);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
logger.error({ err, step: stepName }, 'Setup step failed');
|
||||
log.error('Setup step failed', { err, step: stepName });
|
||||
emitStatus(stepName.toUpperCase(), {
|
||||
STATUS: 'failed',
|
||||
ERROR: message,
|
||||
|
||||
@@ -6,7 +6,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import { isRoot } from './platform.js';
|
||||
import { emitStatus } from './status.js';
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
const configFile = path.join(configDir, 'mount-allowlist.json');
|
||||
|
||||
if (isRoot()) {
|
||||
logger.warn(
|
||||
log.warn(
|
||||
'Running as root — mount allowlist will be written to root home directory',
|
||||
);
|
||||
}
|
||||
@@ -40,9 +40,9 @@ export async function run(args: string[]): Promise<void> {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
|
||||
if (fs.existsSync(configFile) && !force) {
|
||||
logger.info(
|
||||
{ configFile },
|
||||
log.info(
|
||||
'Mount allowlist already exists — skipping (use --force to overwrite)',
|
||||
{ configFile },
|
||||
);
|
||||
emitStatus('CONFIGURE_MOUNTS', {
|
||||
PATH: configFile,
|
||||
@@ -58,7 +58,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
let nonMainReadOnly = 'true';
|
||||
|
||||
if (empty) {
|
||||
logger.info('Writing empty mount allowlist');
|
||||
log.info('Writing empty mount allowlist');
|
||||
const emptyConfig = {
|
||||
allowedRoots: [],
|
||||
blockedPatterns: [],
|
||||
@@ -71,7 +71,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
try {
|
||||
parsed = JSON.parse(json);
|
||||
} catch {
|
||||
logger.error('Invalid JSON input');
|
||||
log.error('Invalid JSON input');
|
||||
emitStatus('CONFIGURE_MOUNTS', {
|
||||
PATH: configFile,
|
||||
ALLOWED_ROOTS: 0,
|
||||
@@ -91,13 +91,13 @@ export async function run(args: string[]): Promise<void> {
|
||||
nonMainReadOnly = parsed.nonMainReadOnly === false ? 'false' : 'true';
|
||||
} else {
|
||||
// Read from stdin
|
||||
logger.info('Reading mount allowlist from stdin');
|
||||
log.info('Reading mount allowlist from stdin');
|
||||
const input = fs.readFileSync(0, 'utf-8');
|
||||
let parsed: { allowedRoots?: unknown[]; nonMainReadOnly?: boolean };
|
||||
try {
|
||||
parsed = JSON.parse(input);
|
||||
} catch {
|
||||
logger.error('Invalid JSON from stdin');
|
||||
log.error('Invalid JSON from stdin');
|
||||
emitStatus('CONFIGURE_MOUNTS', {
|
||||
PATH: configFile,
|
||||
ALLOWED_ROOTS: 0,
|
||||
@@ -117,9 +117,9 @@ export async function run(args: string[]): Promise<void> {
|
||||
nonMainReadOnly = parsed.nonMainReadOnly === false ? 'false' : 'true';
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{ configFile, allowedRoots, nonMainReadOnly },
|
||||
log.info(
|
||||
'Allowlist configured',
|
||||
{ configFile, allowedRoots, nonMainReadOnly },
|
||||
);
|
||||
|
||||
emitStatus('CONFIGURE_MOUNTS', {
|
||||
|
||||
@@ -8,9 +8,9 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { STORE_DIR } from '../src/config.ts';
|
||||
import { initDatabase, setRegisteredGroup } from '../src/db.ts';
|
||||
import { initDatabase, setRegisteredGroup } from '../src/v1/db.ts';
|
||||
import { isValidGroupFolder } from '../src/group-folder.ts';
|
||||
import { logger } from '../src/logger.ts';
|
||||
import { log } from '../src/log.js';
|
||||
import { emitStatus } from './status.ts';
|
||||
|
||||
interface RegisterArgs {
|
||||
@@ -90,7 +90,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
process.exit(4);
|
||||
}
|
||||
|
||||
logger.info(parsed, 'Registering channel');
|
||||
log.info('Registering channel', parsed);
|
||||
|
||||
// Ensure data and store directories exist (store/ may not exist on
|
||||
// fresh installs that skip WhatsApp auth, which normally creates it)
|
||||
@@ -109,7 +109,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
isMain: parsed.isMain,
|
||||
});
|
||||
|
||||
logger.info('Wrote registration to SQLite');
|
||||
log.info('Wrote registration to SQLite');
|
||||
|
||||
// Create group folders
|
||||
fs.mkdirSync(path.join(projectRoot, 'groups', parsed.folder, 'logs'), {
|
||||
@@ -133,9 +133,9 @@ export async function run(args: string[]): Promise<void> {
|
||||
: path.join(projectRoot, 'groups', 'global', 'CLAUDE.md');
|
||||
if (fs.existsSync(templatePath)) {
|
||||
fs.copyFileSync(templatePath, groupClaudeMdPath);
|
||||
logger.info(
|
||||
{ file: groupClaudeMdPath, template: templatePath },
|
||||
log.info(
|
||||
'Created CLAUDE.md from template',
|
||||
{ file: groupClaudeMdPath, template: templatePath },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -143,9 +143,9 @@ export async function run(args: string[]): Promise<void> {
|
||||
// Update assistant name in CLAUDE.md files if different from default
|
||||
let nameUpdated = false;
|
||||
if (parsed.assistantName !== 'Andy') {
|
||||
logger.info(
|
||||
{ from: 'Andy', to: parsed.assistantName },
|
||||
log.info(
|
||||
'Updating assistant name',
|
||||
{ from: 'Andy', to: parsed.assistantName },
|
||||
);
|
||||
|
||||
const groupsDir = path.join(projectRoot, 'groups');
|
||||
@@ -163,7 +163,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
`You are ${parsed.assistantName}`,
|
||||
);
|
||||
fs.writeFileSync(mdFile, content);
|
||||
logger.info({ file: mdFile }, 'Updated CLAUDE.md');
|
||||
log.info('Updated CLAUDE.md', { file: mdFile });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
} else {
|
||||
fs.writeFileSync(envFile, `ASSISTANT_NAME="${parsed.assistantName}"\n`);
|
||||
}
|
||||
logger.info('Set ASSISTANT_NAME in .env');
|
||||
log.info('Set ASSISTANT_NAME in .env');
|
||||
nameUpdated = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import {
|
||||
getPlatform,
|
||||
getNodePath,
|
||||
@@ -26,18 +26,18 @@ export async function run(_args: string[]): Promise<void> {
|
||||
const nodePath = getNodePath();
|
||||
const homeDir = os.homedir();
|
||||
|
||||
logger.info({ platform, nodePath, projectRoot }, 'Setting up service');
|
||||
log.info('Setting up service', { platform, nodePath, projectRoot });
|
||||
|
||||
// Build first
|
||||
logger.info('Building TypeScript');
|
||||
log.info('Building TypeScript');
|
||||
try {
|
||||
execSync('npm run build', {
|
||||
cwd: projectRoot,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
logger.info('Build succeeded');
|
||||
log.info('Build succeeded');
|
||||
} catch {
|
||||
logger.error('Build failed');
|
||||
log.error('Build failed');
|
||||
emitStatus('SETUP_SERVICE', {
|
||||
SERVICE_TYPE: 'unknown',
|
||||
NODE_PATH: nodePath,
|
||||
@@ -113,15 +113,15 @@ function setupLaunchd(
|
||||
</plist>`;
|
||||
|
||||
fs.writeFileSync(plistPath, plist);
|
||||
logger.info({ plistPath }, 'Wrote launchd plist');
|
||||
log.info('Wrote launchd plist', { plistPath });
|
||||
|
||||
try {
|
||||
execSync(`launchctl load ${JSON.stringify(plistPath)}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
logger.info('launchctl load succeeded');
|
||||
log.info('launchctl load succeeded');
|
||||
} catch {
|
||||
logger.warn('launchctl load failed (may already be loaded)');
|
||||
log.warn('launchctl load failed (may already be loaded)');
|
||||
}
|
||||
|
||||
// Verify
|
||||
@@ -168,7 +168,7 @@ function killOrphanedProcesses(projectRoot: string): void {
|
||||
execSync(`pkill -f '${projectRoot}/dist/index\\.js' || true`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
logger.info('Stopped any orphaned nanoclaw processes');
|
||||
log.info('Stopped any orphaned nanoclaw processes');
|
||||
} catch {
|
||||
// pkill not available or no orphans
|
||||
}
|
||||
@@ -215,13 +215,13 @@ function setupSystemd(
|
||||
if (runningAsRoot) {
|
||||
unitPath = '/etc/systemd/system/nanoclaw.service';
|
||||
systemctlPrefix = 'systemctl';
|
||||
logger.info('Running as root — installing system-level systemd unit');
|
||||
log.info('Running as root — installing system-level systemd unit');
|
||||
} else {
|
||||
// Check if user-level systemd session is available
|
||||
try {
|
||||
execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
|
||||
} catch {
|
||||
logger.warn(
|
||||
log.warn(
|
||||
'systemd user session not available — falling back to nohup wrapper',
|
||||
);
|
||||
setupNohupFallback(projectRoot, nodePath, homeDir);
|
||||
@@ -253,12 +253,12 @@ StandardError=append:${projectRoot}/logs/nanoclaw.error.log
|
||||
WantedBy=${runningAsRoot ? 'multi-user.target' : 'default.target'}`;
|
||||
|
||||
fs.writeFileSync(unitPath, unit);
|
||||
logger.info({ unitPath }, 'Wrote systemd unit');
|
||||
log.info('Wrote systemd unit', { unitPath });
|
||||
|
||||
// Detect stale docker group before starting (user systemd only)
|
||||
const dockerGroupStale = !runningAsRoot && checkDockerGroupStale();
|
||||
if (dockerGroupStale) {
|
||||
logger.warn(
|
||||
log.warn(
|
||||
'Docker group not active in systemd session — user was likely added to docker group mid-session',
|
||||
);
|
||||
}
|
||||
@@ -271,11 +271,11 @@ WantedBy=${runningAsRoot ? 'multi-user.target' : 'default.target'}`;
|
||||
if (!runningAsRoot) {
|
||||
try {
|
||||
execSync('loginctl enable-linger', { stdio: 'ignore' });
|
||||
logger.info('Enabled loginctl linger for current user');
|
||||
log.info('Enabled loginctl linger for current user');
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
{ err },
|
||||
log.warn(
|
||||
'loginctl enable-linger failed — service may stop on SSH logout',
|
||||
{ err },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -284,19 +284,19 @@ WantedBy=${runningAsRoot ? 'multi-user.target' : 'default.target'}`;
|
||||
try {
|
||||
execSync(`${systemctlPrefix} daemon-reload`, { stdio: 'ignore' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'systemctl daemon-reload failed');
|
||||
log.error('systemctl daemon-reload failed', { err });
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(`${systemctlPrefix} enable nanoclaw`, { stdio: 'ignore' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'systemctl enable failed');
|
||||
log.error('systemctl enable failed', { err });
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(`${systemctlPrefix} start nanoclaw`, { stdio: 'ignore' });
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'systemctl start failed');
|
||||
log.error('systemctl start failed', { err });
|
||||
}
|
||||
|
||||
// Verify
|
||||
@@ -326,7 +326,7 @@ function setupNohupFallback(
|
||||
nodePath: string,
|
||||
homeDir: string,
|
||||
): void {
|
||||
logger.warn('No systemd detected — generating nohup wrapper script');
|
||||
log.warn('No systemd detected — generating nohup wrapper script');
|
||||
|
||||
const wrapperPath = path.join(projectRoot, 'start-nanoclaw.sh');
|
||||
const pidFile = path.join(projectRoot, 'nanoclaw.pid');
|
||||
@@ -362,7 +362,7 @@ function setupNohupFallback(
|
||||
const wrapper = lines.join('\n') + '\n';
|
||||
|
||||
fs.writeFileSync(wrapperPath, wrapper, { mode: 0o755 });
|
||||
logger.info({ wrapperPath }, 'Wrote nohup wrapper script');
|
||||
log.info('Wrote nohup wrapper script', { wrapperPath });
|
||||
|
||||
emitStatus('SETUP_SERVICE', {
|
||||
SERVICE_TYPE: 'nohup',
|
||||
|
||||
@@ -7,7 +7,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { isValidTimezone } from '../src/timezone.js';
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import { emitStatus } from './status.js';
|
||||
|
||||
export async function run(args: string[]): Promise<void> {
|
||||
@@ -53,7 +53,7 @@ export async function run(args: string[]): Promise<void> {
|
||||
} else {
|
||||
fs.writeFileSync(envFile, `TZ=${resolvedTz}\n`);
|
||||
}
|
||||
logger.info({ timezone: resolvedTz }, 'Set TZ in .env');
|
||||
log.info('Set TZ in .env', { timezone: resolvedTz });
|
||||
}
|
||||
|
||||
emitStatus('TIMEZONE', {
|
||||
|
||||
@@ -13,7 +13,7 @@ import Database from 'better-sqlite3';
|
||||
|
||||
import { STORE_DIR } from '../src/config.js';
|
||||
import { readEnvFile } from '../src/env.js';
|
||||
import { logger } from '../src/logger.js';
|
||||
import { log } from '../src/log.js';
|
||||
import {
|
||||
getPlatform,
|
||||
getServiceManager,
|
||||
@@ -27,7 +27,7 @@ export async function run(_args: string[]): Promise<void> {
|
||||
const platform = getPlatform();
|
||||
const homeDir = os.homedir();
|
||||
|
||||
logger.info('Starting verification');
|
||||
log.info('Starting verification');
|
||||
|
||||
// 1. Check service status
|
||||
let service = 'not_found';
|
||||
@@ -80,7 +80,7 @@ export async function run(_args: string[]): Promise<void> {
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info({ service }, 'Service status');
|
||||
log.info('Service status', { service });
|
||||
|
||||
// 2. Check container runtime
|
||||
let containerRuntime = 'none';
|
||||
@@ -174,7 +174,7 @@ export async function run(_args: string[]): Promise<void> {
|
||||
? 'success'
|
||||
: 'failed';
|
||||
|
||||
logger.info({ status, channelAuth }, 'Verification complete');
|
||||
log.info('Verification complete', { status, channelAuth });
|
||||
|
||||
emitStatus('VERIFY', {
|
||||
SERVICE: service,
|
||||
|
||||
Reference in New Issue
Block a user