From 016f3e34e256a5a131da8673ffcdff16cadf9bd1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:45:19 +0000 Subject: [PATCH] feat: generate dashboard file and config hashtag during init This change updates the `torch init` command to: 1. Prompt the user for a Nostr hashtag (defaulting to `${namespace}-agent-lock`). 2. Explicitly save the `hashtag` to `torch-config.json` under `dashboard.hashtag` for easier editing. 3. Generate a `TORCH_DASHBOARD.md` file in the installation directory containing a link to the TORCH dashboard with the correct `hashtag`, `namespace`, and `relays` pre-filled. This improves the onboarding experience by making it easier to find and monitor the agent locks immediately after initialization. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- src/ops.mjs | 46 ++++++++++++++++++++++++++++++++++++++++++---- test/ops.test.mjs | 10 ++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/ops.mjs b/src/ops.mjs index 2ea79a8..51e999c 100644 --- a/src/ops.mjs +++ b/src/ops.mjs @@ -133,7 +133,12 @@ async function interactiveInit(cwd) { const namespaceAnswer = await rl.question(`Nostr Namespace (default: ${defaultNamespace}): `); const namespace = namespaceAnswer.trim() || defaultNamespace; - // 3. Relays + // 3. Hashtag + const defaultHashtag = `${namespace}-agent-lock`; + const hashtagAnswer = await rl.question(`Nostr Hashtag (default: ${defaultHashtag}): `); + const hashtag = hashtagAnswer.trim() || defaultHashtag; + + // 4. Relays console.log(`\nDefault Relays:\n ${DEFAULT_RELAYS.join('\n ')}`); const relaysAnswer = await rl.question('Enter relays (comma-separated) or press Enter to use defaults: '); let relays = DEFAULT_RELAYS; @@ -141,7 +146,7 @@ async function interactiveInit(cwd) { relays = relaysAnswer.split(',').map(r => r.trim()).filter(Boolean); } - return { installDir, namespace, relays }; + return { installDir, namespace, hashtag, relays }; } finally { rl.close(); } @@ -236,7 +241,7 @@ function installTorchAssets(paths, installDir) { } } -function configureTorch(cwd, paths, installDir, namespace, relays) { +function configureTorch(cwd, paths, installDir, namespace, relays, hashtag) { // 6. Create/Update torch-config.json const configPath = path.join(paths.root, 'torch-config.json'); @@ -262,6 +267,9 @@ function configureTorch(cwd, paths, installDir, namespace, relays) { configData.nostrLock.namespace = namespace; configData.nostrLock.relays = relays; + if (!configData.dashboard) configData.dashboard = {}; + configData.dashboard.hashtag = hashtag; + // Configure memory policy with correct paths if (!configData.scheduler) configData.scheduler = {}; // Always ensure memory policy exists and points to the correct scripts @@ -308,6 +316,10 @@ export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers = const config = await resolveConfiguration(cwd, mockAnswers); const { installDir, namespace, relays } = config; + // For backward compatibility with mocks that might not provide hashtag yet, + // we derive a default if missing. + const hashtag = config.hashtag || `${namespace}-agent-lock`; + const paths = getPaths(cwd, installDir); console.log(`\nInitializing torch in ${paths.torchDir}...`); @@ -315,13 +327,39 @@ export async function cmdInit(force = false, cwd = process.cwd(), mockAnswers = installAppAssets(paths.torchDir, installDir); installTorchAssets(paths, installDir); - configureTorch(cwd, paths, installDir, namespace, relays); + configureTorch(cwd, paths, installDir, namespace, relays, hashtag); + createDashboardLinkFile(paths, namespace, relays, hashtag); injectHostScriptsIfNeeded(paths, installDir); console.log('\nInitialization complete.'); console.log('You can now customize the files in ' + path.relative(cwd, paths.torchDir) + '/'); } +function createDashboardLinkFile(paths, namespace, relays, hashtag) { + const encodedRelays = relays.map(r => encodeURIComponent(r)).join('%2C'); + const dashboardUrl = `https://torch.thepr0m3th3an.net/dashboard/?hashtag=${hashtag}&namespace=${namespace}&relays=${encodedRelays}`; + + const content = `# TORCH Dashboard + +You can view the live status of your agent locks here: + +[Open Dashboard](${dashboardUrl}) + +--- +**Configuration** +This link is generated based on your \`torch-config.json\`: +- **Namespace**: \`${namespace}\` +- **Hashtag**: \`#${hashtag}\` +- **Relays**: ${relays.join(', ')} + +To change these settings, edit \`torch-config.json\` in your project root. +`; + + const filePath = path.join(paths.torchDir, 'TORCH_DASHBOARD.md'); + fs.writeFileSync(filePath, content, 'utf8'); + console.log(`Created ${path.relative(paths.root, filePath)}`); +} + function injectScriptsIntoHost(hostRoot, installDirName) { const hostPkgPath = path.join(hostRoot, 'package.json'); if (!fs.existsSync(hostPkgPath)) { diff --git a/test/ops.test.mjs b/test/ops.test.mjs index 72b0ba7..537470a 100644 --- a/test/ops.test.mjs +++ b/test/ops.test.mjs @@ -8,6 +8,7 @@ import { cmdInit, cmdUpdate } from '../src/ops.mjs'; const MOCK_CONFIG = { installDir: 'torch', namespace: 'test-namespace', + hashtag: 'test-hashtag', relays: ['wss://relay.damus.io'] }; @@ -32,6 +33,14 @@ test('cmdInit creates directory structure and files', async () => { // Check content transformation const metaPrompts = fs.readFileSync(path.join(torchDir, 'META_PROMPTS.md'), 'utf8'); assert.ok(metaPrompts.includes('torch/prompts/daily/'), 'paths replaced in META_PROMPTS'); + + // Check Dashboard file + const dashboardPath = path.join(torchDir, 'TORCH_DASHBOARD.md'); + assert.ok(fs.existsSync(dashboardPath), 'TORCH_DASHBOARD.md created'); + const dashboardContent = fs.readFileSync(dashboardPath, 'utf8'); + assert.ok(dashboardContent.includes('test-hashtag'), 'Dashboard content contains hashtag'); + assert.ok(dashboardContent.includes('test-namespace'), 'Dashboard content contains namespace'); + assert.ok(dashboardContent.includes('https://torch.thepr0m3th3an.net/dashboard/'), 'Dashboard content contains link'); }); test('cmdInit fails if directory exists without force', async () => { @@ -152,4 +161,5 @@ test('cmdInit creates torch-config.json with random namespace', async () => { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); assert.strictEqual(config.nostrLock.namespace, 'test-namespace', 'Namespace should match mock'); + assert.strictEqual(config.dashboard.hashtag, 'test-hashtag', 'Hashtag should match mock'); });