* fix(db): remove unique constraint on folder to support multi-channel agents * ci: implement automated skill drift detection and self-healing PRs * fix: align registration logic with Gavriel's feedback and fix build/test issues from Daniel Mi * style: conform to prettier standards for CI validation * test: fix branch naming inconsistency in CI (master vs main) * fix(ci): robust module resolution by removing file extensions in scripts * refactor(ci): simplify skill validation by removing redundant combination tests * style: conform skills-engine to prettier, unify logging in index.ts and cleanup unused imports * refactor: extract multi-channel DB changes to separate branch Move channel column, folder suffix logic, and related migrations to feat/multi-channel-db-v2 for independent review. This PR now contains only CI/CD optimizations, Prettier formatting, and logging improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
/**
|
|
* Step: mounts — Write mount allowlist config file.
|
|
* Replaces 07-configure-mounts.sh
|
|
*/
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
|
|
import { logger } from '../src/logger.js';
|
|
import { isRoot } from './platform.js';
|
|
import { emitStatus } from './status.js';
|
|
|
|
function parseArgs(args: string[]): { empty: boolean; json: string } {
|
|
let empty = false;
|
|
let json = '';
|
|
for (let i = 0; i < args.length; i++) {
|
|
if (args[i] === '--empty') empty = true;
|
|
if (args[i] === '--json' && args[i + 1]) {
|
|
json = args[i + 1];
|
|
i++;
|
|
}
|
|
}
|
|
return { empty, json };
|
|
}
|
|
|
|
export async function run(args: string[]): Promise<void> {
|
|
const { empty, json } = parseArgs(args);
|
|
const homeDir = os.homedir();
|
|
const configDir = path.join(homeDir, '.config', 'nanoclaw');
|
|
const configFile = path.join(configDir, 'mount-allowlist.json');
|
|
|
|
if (isRoot()) {
|
|
logger.warn(
|
|
'Running as root — mount allowlist will be written to root home directory',
|
|
);
|
|
}
|
|
|
|
fs.mkdirSync(configDir, { recursive: true });
|
|
|
|
let allowedRoots = 0;
|
|
let nonMainReadOnly = 'true';
|
|
|
|
if (empty) {
|
|
logger.info('Writing empty mount allowlist');
|
|
const emptyConfig = {
|
|
allowedRoots: [],
|
|
blockedPatterns: [],
|
|
nonMainReadOnly: true,
|
|
};
|
|
fs.writeFileSync(configFile, JSON.stringify(emptyConfig, null, 2) + '\n');
|
|
} else if (json) {
|
|
// Validate JSON with JSON.parse (not piped through shell)
|
|
let parsed: { allowedRoots?: unknown[]; nonMainReadOnly?: boolean };
|
|
try {
|
|
parsed = JSON.parse(json);
|
|
} catch {
|
|
logger.error('Invalid JSON input');
|
|
emitStatus('CONFIGURE_MOUNTS', {
|
|
PATH: configFile,
|
|
ALLOWED_ROOTS: 0,
|
|
NON_MAIN_READ_ONLY: 'unknown',
|
|
STATUS: 'failed',
|
|
ERROR: 'invalid_json',
|
|
LOG: 'logs/setup.log',
|
|
});
|
|
process.exit(4);
|
|
return; // unreachable but satisfies TS
|
|
}
|
|
|
|
fs.writeFileSync(configFile, JSON.stringify(parsed, null, 2) + '\n');
|
|
allowedRoots = Array.isArray(parsed.allowedRoots)
|
|
? parsed.allowedRoots.length
|
|
: 0;
|
|
nonMainReadOnly = parsed.nonMainReadOnly === false ? 'false' : 'true';
|
|
} else {
|
|
// Read from stdin
|
|
logger.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');
|
|
emitStatus('CONFIGURE_MOUNTS', {
|
|
PATH: configFile,
|
|
ALLOWED_ROOTS: 0,
|
|
NON_MAIN_READ_ONLY: 'unknown',
|
|
STATUS: 'failed',
|
|
ERROR: 'invalid_json',
|
|
LOG: 'logs/setup.log',
|
|
});
|
|
process.exit(4);
|
|
return;
|
|
}
|
|
|
|
fs.writeFileSync(configFile, JSON.stringify(parsed, null, 2) + '\n');
|
|
allowedRoots = Array.isArray(parsed.allowedRoots)
|
|
? parsed.allowedRoots.length
|
|
: 0;
|
|
nonMainReadOnly = parsed.nonMainReadOnly === false ? 'false' : 'true';
|
|
}
|
|
|
|
logger.info(
|
|
{ configFile, allowedRoots, nonMainReadOnly },
|
|
'Allowlist configured',
|
|
);
|
|
|
|
emitStatus('CONFIGURE_MOUNTS', {
|
|
PATH: configFile,
|
|
ALLOWED_ROOTS: allowedRoots,
|
|
NON_MAIN_READ_ONLY: nonMainReadOnly,
|
|
STATUS: 'success',
|
|
LOG: 'logs/setup.log',
|
|
});
|
|
}
|