From 0f3fd3209f997664ba38a6caa2f8fd90e7b42090 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 23:19:25 +0000 Subject: [PATCH] feat: random namespace generation in init This commit updates the `npx torch-lock init` command to automatically generate a `torch-config.json` file if it doesn't already exist. The generated configuration includes a randomized `nostrLock.namespace` (e.g., `torch-a1b2c3d4`) to ensure unique discovery topics for each installation, preventing message collisions across different repositories. - Modified `src/ops.mjs` to import `crypto` and implement the config generation logic in `cmdInit`. - Added a test case in `test/ops.test.mjs` to verify the random namespace generation. - Uses `torch-config.example.json` as a template. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- src/ops.mjs | 31 +++++++++++++++++++++++++++++++ test/ops.test.mjs | 14 ++++++++++++++ 2 files changed, 45 insertions(+) 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'); +});