diff --git a/src/ops.mjs b/src/ops.mjs index 27fdf4f..f8d2921 100644 --- a/src/ops.mjs +++ b/src/ops.mjs @@ -1,6 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; +import crypto from 'node:crypto'; const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); @@ -112,6 +113,36 @@ export function cmdInit(force = false, cwd = process.cwd()) { } } + // 5. Create torch-config.json with random namespace if missing + const configPath = path.join(cwd, 'torch-config.json'); + if (!fs.existsSync(configPath)) { + try { + const exampleConfigPath = path.join(PKG_ROOT, 'torch-config.example.json'); + if (fs.existsSync(exampleConfigPath)) { + const exampleConfig = JSON.parse(fs.readFileSync(exampleConfigPath, 'utf8')); + + // Generate random namespace + const randomSuffix = crypto.randomBytes(4).toString('hex'); + const newNamespace = `torch-${randomSuffix}`; + + if (exampleConfig.nostrLock) { + exampleConfig.nostrLock.namespace = newNamespace; + } else { + exampleConfig.nostrLock = { namespace: newNamespace }; + } + + fs.writeFileSync(configPath, JSON.stringify(exampleConfig, null, 2), 'utf8'); + console.log(`Created ${path.relative(cwd, configPath)} with namespace "${newNamespace}"`); + } else { + console.warn(`Warning: Could not find ${exampleConfigPath} to generate torch-config.json`); + } + } catch (err) { + console.error(`Failed to create torch-config.json: ${err.message}`); + } + } else { + console.log(`Skipped ${path.relative(cwd, configPath)} (exists)`); + } + console.log('\nInitialization complete.'); console.log('You can now customize the files in torch/ and torch/prompts/.'); } diff --git a/test/ops.test.mjs b/test/ops.test.mjs index c1ceecc..69312d4 100644 --- a/test/ops.test.mjs +++ b/test/ops.test.mjs @@ -121,3 +121,17 @@ test('torch-lock check respects local roster', async () => { const result = JSON.parse(output); assert.ok(result.available.includes('custom-agent-007'), 'Custom agent found in roster'); }); + +test('cmdInit creates torch-config.json with random namespace', async () => { + const projectRoot = path.join(tempBase, 'project_config_init'); + fs.mkdirSync(projectRoot, { recursive: true }); + + await cmdInit(false, projectRoot); + + const configPath = path.join(projectRoot, 'torch-config.json'); + assert.ok(fs.existsSync(configPath), 'torch-config.json created'); + + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + assert.match(config.nostrLock.namespace, /^torch-[a-f0-9]{8}$/, 'Namespace should match torch-'); + assert.notStrictEqual(config.nostrLock.namespace, 'torch-example', 'Namespace should not be default'); +});