diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index a2f3b30..d8a3c00 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -175,6 +175,8 @@ jobs: # Upload skill-menu.json (used by the wizard to discover available skills) echo "Uploading skill-menu.json..." gh release upload "$RELEASE_TAG" dist/skills/skill-menu.json --clobber + # Upload the orchestrator agent prompts + menu (flat asset names) + gh release upload "$RELEASE_TAG" dist/agents/agent-menu.json dist/agents/agents-*.md --clobber # Upload each bundled group (one JSON of every variant, in place of per-variant ZIPs) for file in dist/skills/*.json; do filename=$(basename "$file") diff --git a/scripts/dev-server.js b/scripts/dev-server.js index 01795bb..4d949ae 100644 --- a/scripts/dev-server.js +++ b/scripts/dev-server.js @@ -56,7 +56,7 @@ const agentsDir = path.join(distDir, 'agents'); const exampleAppsDir = path.join(repoRoot, 'example-apps'); const localSkillsUrl = `http://localhost:${PORT}/skills`; -const localAgentsUrl = `http://localhost:${PORT}/agents`; +const localAgentsUrl = `http://localhost:${PORT}`; // `generateManifest` reads SKILLS_BASE_URL from process.env, and the agent build // reads AGENTS_BASE_URL. Partial rebuilds run in this process, so set both here — @@ -301,9 +301,9 @@ function createServer() { return; } - const agentMatch = req.url?.match(/^\/agents\/([\w-]+)\/([\w-]+\.md)$/); + const agentMatch = req.url?.match(/^\/(agents-[\w-]+\.md)$/); if (agentMatch) { - serveFile(res, path.join(agentsDir, agentMatch[1], agentMatch[2]), 'text/markdown; charset=utf-8'); + serveFile(res, path.join(agentsDir, agentMatch[1]), 'text/markdown; charset=utf-8'); return; } @@ -323,7 +323,7 @@ function createServer() { } res.writeHead(404, { 'Content-Type': 'text/plain', ...NO_CACHE_HEADERS }); - res.end('Not found. Available endpoints:\n /skill-menu.json\n /skills-mcp-resources.zip\n /skills/{id}.zip\n /skills/{group}.json\n /agent-menu.json\n /agents/{flow}/{type}.md'); + res.end('Not found. Available endpoints:\n /skill-menu.json\n /skills-mcp-resources.zip\n /skills/{id}.zip\n /skills/{group}.json\n /agent-menu.json\n /agents-{flow}-{type}.md'); }); server.listen(PORT, () => { @@ -332,7 +332,7 @@ function createServer() { console.log(`📍 Individual skill: http://localhost:${PORT}/skills/{id}.zip`); console.log(`📦 Bundled group: http://localhost:${PORT}/skills/{group}.json`); console.log(`📋 Skills menu: http://localhost:${PORT}/skill-menu.json`); - console.log(`🤖 Agent prompt: http://localhost:${PORT}/agents/{flow}/{type}.md`); + console.log(`🤖 Agent prompt: http://localhost:${PORT}/agents-{flow}-{type}.md`); console.log(`📋 Agents menu: http://localhost:${PORT}/agent-menu.json`); }); diff --git a/scripts/lib/agent-generator.js b/scripts/lib/agent-generator.js index 93e8aac..d50d37f 100644 --- a/scripts/lib/agent-generator.js +++ b/scripts/lib/agent-generator.js @@ -14,9 +14,14 @@ import fs from 'fs'; import path from 'path'; +import { REPO_URL } from './constants.js'; -const DEFAULT_AGENTS_BASE_URL = - 'https://github.com/PostHog/context-mill/releases/latest/download/agents'; +/** Release assets are a flat namespace: agent prompts live at the release root, like skills. */ +function defaultAgentsBaseUrl(version) { + return version && version !== 'dev' + ? `${REPO_URL}/releases/download/v${version}` + : `${REPO_URL}/releases/latest/download`; +} /** * The agent prompts available in source: one { flow, id } per @@ -74,7 +79,7 @@ function assertFlowMatches(sourcePath, flow) { export function buildAgents({ configDir, distDir, baseUrl, version = 'dev' }) { const agentsSourceDir = path.join(configDir, 'agents'); const agentsDistDir = path.join(distDir, 'agents'); - const resolvedBase = (baseUrl || DEFAULT_AGENTS_BASE_URL).replace(/\/+$/, ''); + const resolvedBase = (baseUrl || defaultAgentsBaseUrl(version)).replace(/\/+$/, ''); fs.mkdirSync(agentsDistDir, { recursive: true }); @@ -83,9 +88,9 @@ export function buildAgents({ configDir, distDir, baseUrl, version = 'dev' }) { for (const { flow, id } of entries) { const sourcePath = path.join(agentsSourceDir, flow, `${id}.md`); assertFlowMatches(sourcePath, flow); - fs.mkdirSync(path.join(agentsDistDir, flow), { recursive: true }); - fs.copyFileSync(sourcePath, path.join(agentsDistDir, flow, `${id}.md`)); - agents.push({ id, flow, downloadUrl: `${resolvedBase}/${flow}/${id}.md` }); + const assetName = `agents-${flow}-${id}.md`; + fs.copyFileSync(sourcePath, path.join(agentsDistDir, assetName)); + agents.push({ id, flow, downloadUrl: `${resolvedBase}/${assetName}` }); } const menu = { version: '1.0', buildVersion: version, agents }; @@ -95,7 +100,7 @@ export function buildAgents({ configDir, distDir, baseUrl, version = 'dev' }) { ); // Reconcile: drop dist files and folders whose source markdown was removed. - const keep = new Set(entries.map(e => path.join(e.flow, `${e.id}.md`))); + const keep = new Set(entries.map(e => `agents-${e.flow}-${e.id}.md`)); keep.add('agent-menu.json'); const walk = dir => { for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) { diff --git a/scripts/lib/tests/agent-generator-flow.test.js b/scripts/lib/tests/agent-generator-flow.test.js index 8e4e54d..c51c811 100644 --- a/scripts/lib/tests/agent-generator-flow.test.js +++ b/scripts/lib/tests/agent-generator-flow.test.js @@ -30,7 +30,7 @@ describe('buildAgents flow frontmatter', () => { expect(count).toBe(1); const menu = JSON.parse(readFileSync(join(distDir, 'agents', 'agent-menu.json'), 'utf8')); expect(menu.agents).toEqual([ - { id: 'task', flow: 'my-flow', downloadUrl: 'http://x/my-flow/task.md' }, + { id: 'task', flow: 'my-flow', downloadUrl: 'http://x/agents-my-flow-task.md' }, ]); });