diff --git a/skills/beadboard-driver/scripts/bb-mail-shim.mjs b/skills/beadboard-driver/scripts/bb-mail-shim.mjs new file mode 100644 index 0000000..4238070 --- /dev/null +++ b/skills/beadboard-driver/scripts/bb-mail-shim.mjs @@ -0,0 +1,68 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; + +function getAgentName() { + const agent = (process.env.BB_AGENT || process.env.BD_ACTOR || '').trim(); + if (!agent) { + console.error( + 'bb-mail-shim: agent identity required.\n' + + 'Set BB_AGENT before running bd mail, e.g. export BB_AGENT=silver-scribe', + ); + process.exit(1); + } + return agent; +} + +function runBbAgent(args) { + const result = spawnSync('bb', ['agent', ...args], { stdio: 'inherit', shell: false }); + if (result.error) { + if (result.error.code === 'ENOENT') { + console.error('bb-mail-shim: bb command not found in PATH.'); + } else { + console.error(`bb-mail-shim: failed to execute bb: ${result.error.message}`); + } + process.exit(1); + } + process.exit(result.status ?? 0); +} + +const args = process.argv.slice(2); +const subcommand = args[0]; +const agent = getAgentName(); + +if (!subcommand || subcommand === '--help' || subcommand === '-h') { + console.log('Usage: bd mail inbox|send|read|ack ... (delegated via bb-mail-shim)'); + process.exit(0); +} + +switch (subcommand) { + case 'inbox': + runBbAgent(['inbox', '--agent', agent, ...args.slice(1)]); + break; + case 'send': { + const rest = args.slice(1); + const hasFrom = rest.includes('--from'); + runBbAgent(['send', ...(hasFrom ? [] : ['--from', agent]), ...rest]); + break; + } + case 'read': { + const messageId = args[1]; + if (!messageId) { + console.error('bb-mail-shim: read requires '); + process.exit(1); + } + runBbAgent(['read', '--agent', agent, '--message', messageId, ...args.slice(2)]); + break; + } + case 'ack': { + const messageId = args[1]; + if (!messageId) { + console.error('bb-mail-shim: ack requires '); + process.exit(1); + } + runBbAgent(['ack', '--agent', agent, '--message', messageId, ...args.slice(2)]); + break; + } + default: + runBbAgent([subcommand, ...args.slice(1)]); +} diff --git a/skills/beadboard-driver/scripts/session-preflight.mjs b/skills/beadboard-driver/scripts/session-preflight.mjs index bb916a4..23ed4a9 100644 --- a/skills/beadboard-driver/scripts/session-preflight.mjs +++ b/skills/beadboard-driver/scripts/session-preflight.mjs @@ -1,8 +1,48 @@ #!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + import { findCommandInPath, resolveBbPath } from './lib/driver-lib.mjs'; +const __dirname = dirname(fileURLToPath(import.meta.url)); + +function configureMailDelegate(bdPath, shimPath) { + if (!existsSync(shimPath)) { + return { + configured: false, + reason: `shim not found at ${shimPath}`, + }; + } + + const delegateCmd = `node ${shimPath}`; + const result = spawnSync(bdPath, ['config', 'set', 'mail.delegate', delegateCmd], { + stdio: 'pipe', + shell: false, + }); + + if (result.status !== 0) { + const stderr = result.stderr?.toString().trim() || ''; + return { + configured: false, + reason: `bd config set failed: ${stderr || 'non-zero exit'}`, + delegate: delegateCmd, + }; + } + + return { + configured: true, + delegate: delegateCmd, + shim_path: shimPath, + note: 'Set BB_AGENT to your agent name before using bd mail.', + }; +} + async function main() { + const shimPath = join(__dirname, 'bb-mail-shim.mjs'); + try { const bdPath = await findCommandInPath('bd'); if (!bdPath) { @@ -20,6 +60,7 @@ async function main() { bd: { available: false, path: null }, }, bb: null, + mail: null, }, null, 2, @@ -41,6 +82,10 @@ async function main() { bd: { available: true, path: bdPath }, }, bb, + mail: { + configured: false, + reason: 'bb not available — mail delegate requires bb agent commands', + }, }, null, 2, @@ -49,6 +94,8 @@ async function main() { return; } + const mail = configureMailDelegate(bdPath, shimPath); + process.stdout.write( `${JSON.stringify( { @@ -58,6 +105,7 @@ async function main() { bd: { available: true, path: bdPath }, }, bb, + mail, }, null, 2, @@ -75,6 +123,7 @@ async function main() { bd: { available: false, path: null }, }, bb: null, + mail: null, }, null, 2, diff --git a/tests/skills/beadboard-driver/bb-mail-shim.test.ts b/tests/skills/beadboard-driver/bb-mail-shim.test.ts new file mode 100644 index 0000000..9c5c4dc --- /dev/null +++ b/tests/skills/beadboard-driver/bb-mail-shim.test.ts @@ -0,0 +1,27 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { execFile } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const shimPath = path.resolve('skills/beadboard-driver/scripts/bb-mail-shim.mjs'); + +test('bb-mail-shim fails with helpful error when bb is unavailable', async () => { + await assert.rejects( + async () => { + await execFileAsync(process.execPath, [shimPath, 'inbox'], { + env: { + ...process.env, + PATH: '', + BB_AGENT: 'silver-scribe', + }, + }); + }, + (error: any) => { + const stderr = String(error.stderr || ''); + assert.match(stderr, /bb command not found/i); + return true; + }, + ); +}); diff --git a/tests/skills/beadboard-driver/session-preflight.test.ts b/tests/skills/beadboard-driver/session-preflight.test.ts index e41ecd6..6355843 100644 --- a/tests/skills/beadboard-driver/session-preflight.test.ts +++ b/tests/skills/beadboard-driver/session-preflight.test.ts @@ -58,7 +58,7 @@ test('session-preflight succeeds with fake bd and BB_REPO', async () => { } const result = await runPreflight({ - PATH: toolsDir, + PATH: `${toolsDir}${path.delimiter}${process.env.PATH || ''}`, BB_REPO: repo, BB_SKILL_HOME: path.join(root, 'home'), BB_SKIP_PROBE: '1', @@ -68,5 +68,7 @@ test('session-preflight succeeds with fake bd and BB_REPO', async () => { assert.equal(result.bb.ok, true); assert.equal(result.bb.source, 'env'); assert.equal(result.tools.bd.available, true); + assert.equal(result.mail.configured, true, JSON.stringify(result)); + assert.match(String(result.mail.delegate), /node .*bb-mail-shim\.mjs/); }); });