feat(skill): wire bd mail delegate via bb shim
This commit is contained in:
parent
dcca324bfb
commit
d1b81250b2
4 changed files with 147 additions and 1 deletions
68
skills/beadboard-driver/scripts/bb-mail-shim.mjs
Normal file
68
skills/beadboard-driver/scripts/bb-mail-shim.mjs
Normal file
|
|
@ -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 <message-id>');
|
||||||
|
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 <message-id>');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
runBbAgent(['ack', '--agent', agent, '--message', messageId, ...args.slice(2)]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
runBbAgent([subcommand, ...args.slice(1)]);
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,48 @@
|
||||||
#!/usr/bin/env node
|
#!/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';
|
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() {
|
async function main() {
|
||||||
|
const shimPath = join(__dirname, 'bb-mail-shim.mjs');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bdPath = await findCommandInPath('bd');
|
const bdPath = await findCommandInPath('bd');
|
||||||
if (!bdPath) {
|
if (!bdPath) {
|
||||||
|
|
@ -20,6 +60,7 @@ async function main() {
|
||||||
bd: { available: false, path: null },
|
bd: { available: false, path: null },
|
||||||
},
|
},
|
||||||
bb: null,
|
bb: null,
|
||||||
|
mail: null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|
@ -41,6 +82,10 @@ async function main() {
|
||||||
bd: { available: true, path: bdPath },
|
bd: { available: true, path: bdPath },
|
||||||
},
|
},
|
||||||
bb,
|
bb,
|
||||||
|
mail: {
|
||||||
|
configured: false,
|
||||||
|
reason: 'bb not available — mail delegate requires bb agent commands',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|
@ -49,6 +94,8 @@ async function main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mail = configureMailDelegate(bdPath, shimPath);
|
||||||
|
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
`${JSON.stringify(
|
`${JSON.stringify(
|
||||||
{
|
{
|
||||||
|
|
@ -58,6 +105,7 @@ async function main() {
|
||||||
bd: { available: true, path: bdPath },
|
bd: { available: true, path: bdPath },
|
||||||
},
|
},
|
||||||
bb,
|
bb,
|
||||||
|
mail,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|
@ -75,6 +123,7 @@ async function main() {
|
||||||
bd: { available: false, path: null },
|
bd: { available: false, path: null },
|
||||||
},
|
},
|
||||||
bb: null,
|
bb: null,
|
||||||
|
mail: null,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|
|
||||||
27
tests/skills/beadboard-driver/bb-mail-shim.test.ts
Normal file
27
tests/skills/beadboard-driver/bb-mail-shim.test.ts
Normal file
|
|
@ -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;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -58,7 +58,7 @@ test('session-preflight succeeds with fake bd and BB_REPO', async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await runPreflight({
|
const result = await runPreflight({
|
||||||
PATH: toolsDir,
|
PATH: `${toolsDir}${path.delimiter}${process.env.PATH || ''}`,
|
||||||
BB_REPO: repo,
|
BB_REPO: repo,
|
||||||
BB_SKILL_HOME: path.join(root, 'home'),
|
BB_SKILL_HOME: path.join(root, 'home'),
|
||||||
BB_SKIP_PROBE: '1',
|
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.ok, true);
|
||||||
assert.equal(result.bb.source, 'env');
|
assert.equal(result.bb.source, 'env');
|
||||||
assert.equal(result.tools.bd.available, true);
|
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/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue