#!/usr/bin/env node /** * Serve deterministic visual-review states through the real authenticated * Admin routes. The fixture is deliberately process-local: it binds only to a * literal loopback address, uses generated local credentials, keeps every * store in one temporary SQLite file, and removes that directory on shutdown. */ import { createHash, randomBytes } from 'node:crypto'; import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { createServer } from 'node:http'; import { tmpdir } from 'node:os'; import { basename, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { assertNodeVersion, loadTsModule } from './lib/offline-harness.mjs'; const DEFAULT_HOST = '127.0.0.1'; const WORKSPACE_ID = 'TVISUAL'; const LOCAL_SLACK_TOKEN = 'xoxb-local-admin-visual-fixture'; const LOCAL_SLACK_SECRET = 'local-admin-visual-signing-secret'; const LOCAL_SLACK_BOT_ID = 'UVISUALBOT'; const ENV_KEYS = [ 'SLACK_API_URL', 'SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET', 'SLACK_BOT_USER_ID', ]; export const CANONICAL_ADMIN_VISUAL_STATES = Object.freeze({ settingsUpdates: Object.freeze({ path: '/admin/settings/updates', actions: Object.freeze(['Preview report']) }), settingsProviders: Object.freeze({ path: '/admin/settings/providers', actions: Object.freeze([]) }), settingsCodingAgents: Object.freeze({ path: '/admin/settings/agents-clients', actions: Object.freeze([]) }), settingsConnectedApps: Object.freeze({ path: '/admin/settings/connected-apps', actions: Object.freeze([]) }), settingsBrowser: Object.freeze({ path: '/admin/settings/browser', actions: Object.freeze([]) }), agentInstructions: Object.freeze({ path: '/admin/agents/agent_research', actions: Object.freeze([]) }), agentBlankDescription: Object.freeze({ path: '/admin/agents/agent_customer', actions: Object.freeze([]) }), agentMemory: Object.freeze({ path: '/admin/agents/agent_research', actions: Object.freeze(['Memory']) }), agentSchedules: Object.freeze({ path: '/admin/agents/agent_release', actions: Object.freeze(['Schedules']) }), agentWebsites: Object.freeze({ path: '/admin/agents/agent_research', actions: Object.freeze(['Websites']) }), channelsIndex: Object.freeze({ path: '/admin/channels', actions: Object.freeze([]) }), channelDetail: Object.freeze({ path: '/admin/channels/TVISUAL/C_RELEASES', actions: Object.freeze([]) }), channelAdvanced: Object.freeze({ path: '/admin/channels/TVISUAL/C_RELEASES', actions: Object.freeze(['Advanced']), }), team: Object.freeze({ path: '/admin/team', actions: Object.freeze([]) }), }); export const CANONICAL_SLACK_AUTH_VISUAL_STATES = Object.freeze({ signIn: Object.freeze({ path: '/__admin_visual_fixture/auth/sign-in' }), setupCreate: Object.freeze({ path: '/__admin_visual_fixture/auth/setup-create' }), setupConnected: Object.freeze({ path: '/__admin_visual_fixture/auth/setup-connected' }), setupApproval: Object.freeze({ path: '/__admin_visual_fixture/auth/setup-approval' }), setupOwner: Object.freeze({ path: '/__admin_visual_fixture/auth/setup-owner' }), accessDenied: Object.freeze({ path: '/__admin_visual_fixture/auth/access-denied' }), ownerComplete: Object.freeze({ path: '/__admin_visual_fixture/auth/owner-complete' }), recovery: Object.freeze({ path: '/__admin_visual_fixture/auth/recovery' }), }); export const CANONICAL_CONNECTOR_VISUAL_STATES = Object.freeze({ setup: Object.freeze({ path: '/__admin_visual_fixture/connectors/setup' }), waiting: Object.freeze({ path: '/__admin_visual_fixture/connectors/waiting' }), success: Object.freeze({ path: '/__admin_visual_fixture/connectors/success' }), }); const VISUAL_FIXTURE_AGENT_IDS = Object.freeze(['agent_customer', 'agent_release', 'agent_research']); const SANDBOX_VISUAL_DEPLOY_SOURCES = Object.freeze({ 'redeploy-command': 'command', 'redeploy-dashboard': 'workers-builds', 'redeploy-unknown': 'unknown', }); export const VISUAL_ENVIRONMENT_STATUS = Object.freeze({ schemaVersion: 'chickpea-environment-status/v1', generatedAt: '2026-09-01T12:00:00.000Z', registryRevision: 12, selectedTarget: 'amber', targets: Object.freeze(['amber', 'cobalt'].map((target, index) => Object.freeze({ target, health: index === 0 ? 'ready' : index === 1 ? 'unreachable' : 'expired_claim', sourceSha: '1234567890abcdef1234567890abcdef12345678', dirty: index === 0, servingVersion: `version-${target}`, transport: 'events', workspaceAlias: `env-${target}-workspace`, workspaceLabel: `${target} workspace`, appAlias: `env-${target}-slack-app`, appLabel: `${target} app`, claim: index === 0 ? Object.freeze({ holderId: 'holder-0123456789abcdef', leaseAgeMs: 180_000, expiresAt: '2026-09-01T20:00:00.000Z', }) : null, verifierLock: Object.freeze(index === 0 ? { status: 'live', ownerRunId: 'visual-run-amber' } : { status: 'clear' }), schemaGeneration: 'd1:0002_mcp_oauth;do:v9', lastAttestedRevision: index === 0 ? '1234567890abcdef1234567890abcdef12345678-dirty' : null, recoveryAction: index === 0 ? 'No recovery needed.' : index === 1 ? 'Check cobalt Worker and Slack transport reachability.' : 'Run npm run env -- reclaim fern.', credentialToken: 'xoxb-visual-do-not-expose', }))), sandbox: Object.freeze({ archiveDate: '2027-01-15T00:00:00.000Z', daysUntilArchive: 136, warning: 'none', warningDays: Object.freeze([45, 30, 14]), unusedWorkspaceSlots: 2, integrationHeadroom: 37, browserProfilePath: '/private/visual-browser-profile', }), }); const VISUAL_CHANNELS = Object.freeze([ { id: 'C_RELEASES', name: 'release-room', is_private: false, is_member: true, is_archived: false }, { id: 'C_SUPPORT', name: 'customer-support', is_private: false, is_member: false, is_archived: false }, { id: 'C_CUSTOMER', name: 'customer-insights', is_private: true, is_member: true, is_archived: false }, { id: 'C_UNASSIGNED', name: 'product-feedback', is_private: false, is_member: true, is_archived: false }, { id: 'C_ARCHIVED', name: 'research-archive', is_private: true, is_member: true, is_archived: true }, { id: 'C_DISCOVERED', name: 'design-critique', is_private: false, is_member: true, is_archived: false }, ]); let activeFixture = false; async function seedVisualSlackOwner(identity) { const operation = await identity.createAuthOperation({ id: 'first_owner_visual', kind: 'first_owner_claim', expectedSlackTeamId: WORKSPACE_ID, expectedSlackUserId: 'UVISUALOWNER', chickpeaRole: 'owner', capabilityHash: 'a'.repeat(64), expiresAt: Date.now() + 60_000, }); await identity.createOwnerClaim({ operationId: operation.id, slackTeamId: WORKSPACE_ID, slackUserId: 'UVISUALOWNER', }); await identity.advanceAuthOperation({ operationId: operation.id, capabilityHash: 'a'.repeat(64), step: 1, betterAuthUserId: 'ba_user_visual', betterAuthOrganizationId: '11111111-1111-4111-8111-111111111111', betterAuthMembershipId: 'ba_member_visual', }); return identity.claimOwner({ operationId: operation.id, organizationId: 'org_oss', slackTeamId: WORKSPACE_ID, slackUserId: 'UVISUALOWNER', displayName: 'Visual Owner', betterAuthUserId: 'ba_user_visual', betterAuthMembershipId: 'ba_member_visual', }); } async function seedVisualProposal(management, store, owner) { const agent = await store.getAgent('agent_research'); const instructions = 'Synthetic frozen instructions. \n' + 'Check the complete value. '.repeat(600) + '\n' + 'long-token-'.repeat(30) + '\nEnd of frozen instructions.'; const scope = `slack:${WORKSPACE_ID}:C_RELEASES:1800000000.000100:agent:agent_chickpea`; await management.putChangeSetProposal({ proposalId: 'proposal_visual_research', organizationId: owner.membership.organizationId, actorUserId: owner.user.id, actorMembershipId: owner.membership.id, originKey: scope, approvalScopeKey: scope, idempotencyKey: 'visual-proposal', guideVersion: 'visual-fixture', authoringReason: 'agent_edit', digest: createHash('sha256').update(instructions).digest('hex'), operations: [{ itemId: 'instructions', kind: 'update_agent', agentId: agent.id, expectedRevision: agent.revision, patch: { instructions, description: 'Synthetic proposal for local visual review.' } }], preview: { summary: 'Synthetic update preview', changes: [], missingSetup: [] }, targetRevisions: { [`agent:${agent.id}`]: agent.revision }, at: 1, }); } async function seedVisualTeam(identity, provisionSlackInteractionMember, owner) { const fixtures = [ { slackUserId: 'UVISUALADMIN', displayName: 'Alex Admin', role: 'admin', status: 'active' }, { slackUserId: 'UVISUALMEMBER', displayName: 'Maya Member', role: 'member', status: 'active' }, { slackUserId: 'UVISUALOWNER2', displayName: 'Olive Owner', role: 'owner', status: 'active' }, { slackUserId: 'UVISUALSUSPENDED', displayName: 'Sam Suspended', role: 'member', status: 'suspended' }, { slackUserId: 'UVISUALREMOVED', displayName: 'Rae Removed', role: 'member', status: 'removed' }, ]; for (const fixture of fixtures) { const provisioned = await provisionSlackInteractionMember({ identity, slackTeamId: WORKSPACE_ID, botUserId: LOCAL_SLACK_BOT_ID, user: { id: fixture.slackUserId, teamId: WORKSPACE_ID, displayName: fixture.displayName, deleted: false, bot: false, appUser: false, restricted: false, ultraRestricted: false, stranger: false, }, }); const membership = provisioned.resolution?.membership; if (!membership) throw new Error(`Could not seed visual member ${fixture.slackUserId}.`); if (fixture.role !== membership.role) { await identity.updateMembershipAuthority({ membershipId: membership.id, role: fixture.role, actorMembershipId: owner.membership.id, authenticationSurface: 'better_auth', correlationId: `visual-role-${fixture.slackUserId}`, reasonCode: 'owner_changed_member_role', }); } if (fixture.status !== 'active') { await identity.updateMembershipAuthority({ membershipId: membership.id, status: fixture.status, actorMembershipId: owner.membership.id, authenticationSurface: 'better_auth', correlationId: `visual-status-${fixture.slackUserId}`, reasonCode: 'admin_deactivated_member', }); } } } function assertLoopbackHost(host) { if (host !== '127.0.0.1' && host !== '::1') { throw new Error(`Admin visual fixture host must be a literal loopback address, received ${host}.`); } } function loopbackAddress(address) { return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'; } function urlHost(address) { return address.includes(':') ? `[${address}]` : address; } function visualAgents() { const slackPresence = (id, handle, avatarHash) => ({ requestedHandle: handle, normalizedHandle: handle, desiredState: 'active', health: 'healthy', avatar: { kind: 'generated', revision: 1, seed: id, url: `https://secure.gravatar.com/avatar/${avatarHash}?s=192&d=identicon`, }, }); return [ { id: 'agent_customer', revision: 1, name: 'Customer Insights', description: '', instructions: 'Summarize customer feedback and distinguish recurring evidence from anecdotes.', enabled: false, model: 'anthropic/claude-opus-5-5', modelEffort: 'xhigh', slackPresence: slackPresence('agent_customer', 'customer-insights', 'b6a1f4b55c4c81e3da8a5f3c12f2ef7f'), skills: [], mcpServers: [], apiConnections: [], repositories: [], }, { id: 'agent_release', revision: 1, name: 'Release Scribe', description: 'Keeps launches concise, decision-ready, and explicit about risk.', instructions: 'Turn release activity into concise, decision-ready updates with explicit risks.', enabled: true, slackPresence: slackPresence('agent_release', 'release-scribe', '205e460b479e2e5b48aec07710c08d50'), skills: [{ name: 'release-readiness', description: 'Review release readiness and launch risk.', instructions: 'Name blockers, owners, and the safest next step.', enabled: true, }], mcpServers: [], apiConnections: [], repositories: [], }, { id: 'agent_research', revision: 1, name: 'Research Partner', description: 'Investigates product questions with evidence and clearly marked uncertainty.', instructions: 'Investigate product questions, cite the available evidence, and call out uncertainty.', enabled: true, model: 'local-stub/visual-review', slackPresence: slackPresence('agent_research', 'research-partner', 'f9879d71855b5ff21e4963273a886bfc'), skills: [{ name: 'research-brief', description: 'Build a sourced research brief.', instructions: 'Separate fact, inference, and open questions.', enabled: true, }], mcpServers: [{ id: 'linear', displayName: 'Linear', url: 'https://mcp.linear.app/mcp', transport: 'streamable-http', authMode: 'none', headerNames: [], enabled: true, lifecycleStatus: 'ready', statusText: 'Connected · 2 tools', discoveredTools: [{ name: 'search' }, { name: 'create' }], allowedTools: ['search', 'create'], presetId: 'linear', }], apiConnections: [], repositories: [{ id: 'repo_research', installationId: 42, accountLogin: 'acme', fullName: 'acme/research', enabled: true, }], }, ]; } async function seedConfig(store, runtimeContract) { const channels = [ { workspaceId: WORKSPACE_ID, channelId: 'C_RELEASES', label: 'release-room', lifecycle: 'active', agentId: 'agent_release', }, { workspaceId: WORKSPACE_ID, channelId: 'C_SUPPORT', label: 'customer-support', lifecycle: 'active', agentId: 'agent_research', }, { workspaceId: WORKSPACE_ID, channelId: 'C_CUSTOMER', label: 'customer-insights', lifecycle: 'active', agentId: null, }, { workspaceId: WORKSPACE_ID, channelId: 'C_UNASSIGNED', label: 'product-feedback', lifecycle: 'active', agentId: null, }, { workspaceId: WORKSPACE_ID, channelId: 'C_ARCHIVED', label: 'research-archive', lifecycle: 'archived', agentId: 'agent_research', }, ]; for (const { agentId, ...channel } of channels) { await store.putChannel(channel); if (agentId) { await store.putAgentChannelGrant({ workspaceId: channel.workspaceId, channelId: channel.channelId, agentId, status: 'active', createdByMembershipId: 'membership_visual_owner', channelLabel: channel.label, }); } } const installation = await store.ensureWorkspaceInstallation({ workspaceId: WORKSPACE_ID, transportMode: 'direct', runtimeContract, defaultAgentId: 'agent_research', teamId: WORKSPACE_ID, appId: 'AVISUAL', botUserId: LOCAL_SLACK_BOT_ID, }); await store.updateWorkspaceInstallation(WORKSPACE_ID, { health: 'healthy', }, installation.revision); } async function seedConnections(store, ownerMembershipId) { const account = (slug, providerId, label, toolkit, allowedCapabilities, options = {}) => ({ id: `connection_${slug}`, workspaceId: WORKSPACE_ID, ownerKind: options.ownerKind ?? 'member', ...(options.ownerKind === 'team' ? {} : { ownerMembershipId }), createdByMembershipId: ownerMembershipId, providerId, label: `${label} · ${options.ownerKind === 'team' ? 'Team' : 'Personal'}`, identity: { accountName: options.accountName ?? 'member@acme.test' }, policy: { kind: 'managed', adapterId: 'composio', toolkit, principalRef: options.ownerKind === 'team' ? `chickpea:organization:${WORKSPACE_ID}` : `chickpea:membership:${ownerMembershipId}`, accountRef: `ca_visual_${slug}`, allowedCapabilities, }, secretRefId: `secret_visual_${slug}`, lifecycle: options.lifecycle ?? 'ready', }); const accounts = [ account('drive', 'google', 'Google Drive', 'googledrive', [ 'drive.files.search', 'drive.files.metadata', 'drive.files.read', 'drive.files.create', ]), account('gmail', 'google', 'Gmail', 'gmail', [ 'gmail.profile.read', 'gmail.messages.search', 'gmail.drafts.create', 'gmail.messages.send', ]), account('calendar', 'google', 'Google Calendar', 'googlecalendar', [ 'calendar.calendars.list', 'calendar.events.list', 'calendar.events.create', 'calendar.events.update', 'calendar.events.delete', ]), account('team_gmail_needs_attention', 'google', 'Gmail', 'gmail', [ 'gmail.profile.read', 'gmail.messages.search', 'gmail.drafts.create', 'gmail.messages.send', ], { ownerKind: 'team', lifecycle: 'needs_attention', accountName: 'team@acme.test', }), ]; for (const connection of accounts) await store.putConnectionAccount(connection); for (const [agentId, connection] of [ ['agent_research', accounts[0]], ['agent_research', accounts[1]], ['agent_release', accounts[3]], ]) { await store.putAgentConnectionBinding({ agentId, connectionAccountId: connection.id, providerId: connection.providerId, allowedCapabilities: connection.policy.allowedCapabilities, enabled: true, }); } } const VISUAL_NOTION_PAGES = Object.freeze([ ['Q4 Roadmap', 'page'], ['Sales Playbook', 'page'], ['Engineering Wiki', 'page'], ['Customer Interviews', 'database'], ['Launch Checklist', 'page'], ['Finance Ops', 'page'], ].map(([label, kind], index) => ({ id: `0000000${index + 1}-0000-4000-8000-00000000000${index + 1}`, kind, label, }))); /** * With --notion-sign-ins: one team Notion sign-in that the research Agent * signed and the release Agent reuses with two pages chosen, plus a Scout * Agent without Notion, so the reuse form, the row summary, the page picker * and the shared-change confirm can be reviewed. A member principal, who * sees only Agents they created, also gets one personal sign-in that Scout * and a Notes Agent share. */ async function seedNotionSignIns(store, ownerMembership, { MANAGED_CONNECTOR_CATALOG }, principalRole) { const capabilities = MANAGED_CONNECTOR_CATALOG.capabilities('notion', 'write').map(({ id }) => id); const grantSummary = { items: VISUAL_NOTION_PAGES.map(({ id, kind, label }) => ({ id, type: kind, label })), truncated: false, }; const shareSignIn = async ({ ownerKind, accountRef, person, agents }) => { const policy = { kind: 'managed', adapterId: 'composio', toolkit: 'notion', principalRef: ownerKind === 'team' ? `chickpea:organization:${ownerMembership.organizationId}` : `chickpea:membership:${ownerMembership.id}`, accountRef, allowedCapabilities: capabilities, signIn: { person, accessLane: 'write', signedBy: ownerMembership.id }, grantSummary, }; const account = (agentId) => ({ id: `connection_notion_${agentId.replace('agent_', '')}`, workspaceId: WORKSPACE_ID, ownerKind, ...(ownerKind === 'member' ? { ownerMembershipId: ownerMembership.id } : {}), createdByMembershipId: ownerMembership.id, providerId: 'notion', label: `Notion · ${ownerKind === 'team' ? 'Team' : 'Personal'}`, policy, secretRefId: `secret_visual_notion_${agentId}`, lifecycle: 'ready', }); const binding = (agentId, notionPages) => ({ agentId, connectionAccountId: account(agentId).id, providerId: 'notion', allowedCapabilities: capabilities, enabled: true, ...(notionPages ? { resourceConstraints: { notionPages } } : {}), }); const [[signerId], [joinerId, joinerPages]] = agents; const signer = await store.putConnectionAccount(account(signerId), 0); await store.putAgentConnectionBinding(binding(signerId)); const { kind: _kind, allowedCapabilities: _capabilities, ...shared } = policy; await store.applySignInGroup({ adapterId: 'composio', accountRef, members: [{ connectionAccountId: signer.id, expectedRevision: signer.revision }], shared, join: { account: account(joinerId), binding: binding(joinerId, joinerPages) }, }); }; const agent = (id, name) => store.createAgent({ id, name, instructions: 'Research questions in Notion.', enabled: true, lifecycle: 'active', configurationGeneration: 1, creatorMembershipId: ownerMembership.id, editPolicy: 'creator_and_admins', skills: [], mcpServers: [], apiConnections: [], repositories: [], }); await agent('agent_scout', 'Scout'); await shareSignIn({ ownerKind: 'team', accountRef: 'ca_visual_notion_team', person: 'notion:visual:owner', agents: [['agent_research'], ['agent_release', [VISUAL_NOTION_PAGES[0].id, VISUAL_NOTION_PAGES[3].id]]], }); if (principalRole !== 'member') return; await agent('agent_notes', 'Notes'); await shareSignIn({ ownerKind: 'member', accountRef: 'ca_visual_notion_personal', person: 'notion:visual:member', agents: [['agent_scout'], ['agent_notes']], }); } /** Answers for Notion only: its pages, and a sign-in that is never opened. */ function visualNotionProviders({ createManagedConnectionProviderRegistry }) { const listing = () => ({ pages: VISUAL_NOTION_PAGES.map((page) => ({ ...page })), complete: true }); return createManagedConnectionProviderRegistry([{ id: 'composio', availability: ({ toolkit }) => toolkit === 'notion' ? { status: 'ready', missingConfiguration: [] } : { status: 'unavailable', missingConfiguration: ['api_key_missing'] }, async authorize() { throw new Error('The Admin visual fixture does not open Notion.'); }, async validate() { return { person: 'notion:visual:owner', pages: listing() }; }, async listPages() { return listing(); }, async execute() { return { data: {} }; }, async revoke() {}, }]); } async function seedSettings(settings) { await settings.setSetting('slack.teamName', 'Acme Design'); } async function seedMemory(memory) { await memory.putAgentMemory({ agentId: 'agent_research', expectedRevision: 0, body: [ 'Prefer short evidence summaries for product and engineering leads.', 'Separate verified fact, inference, and unresolved questions.', 'Check focused tests, rollout risk, owner, and rollback before launch.', ].join('\n'), }); } async function seedSchedules(store, routines, ownerMembershipId, RoutineService) { const definitions = [ { id: 'routine_visual_daily_release', name: 'Daily launch readiness digest with a long scannable name', scheduleInput: '0 9 * * 1-5', nextRunAt: Date.UTC(2026, 7, 26, 16), pause: false, referenceState: 'active', }, { id: 'routine_visual_weekly_summary', name: 'Weekly customer summary', scheduleInput: '0 14 * * 5', nextRunAt: Date.UTC(2026, 7, 28, 21), pause: true, referenceState: 'active', }, { id: 'routine_visual_dependency', name: 'Dependency follow-up', scheduleInput: '*/30 * * * *', nextRunAt: Date.UTC(2026, 7, 25, 20, 30), pause: false, referenceState: 'needs_attention', }, ]; for (const item of definitions) { const service = new RoutineService(routines, { routineId: () => item.id }); let routine = await service.save({ action: 'create', actorId: 'UVISUALOWNER', workspaceId: WORKSPACE_ID, channelId: 'C_RELEASES', definition: { name: item.name, description: 'Visual schedule fixture.', taskText: 'Post a concise visual-fixture update.', triggerKind: 'schedule', scheduleInput: item.scheduleInput, scheduleJson: JSON.stringify({ version: 1, kind: 'cron', expression: item.scheduleInput }), timezone: 'America/Los_Angeles', outputPolicy: 'post', authorityMode: 'live_channel_v1', }, nextRunAt: item.nextRunAt, projectedDailyStarts: 1, reservations: [{ windowStart: item.nextRunAt, count: 1 }], sourceVisibility: 'public', }, `visual-schedule:${item.id}`); if (item.pause) { routine = await service.control({ routineId: routine.id, expectedVersion: routine.version, action: 'pause', actorId: 'UVISUALOWNER', actorClass: 'operator', reasonCode: 'admin_control', idempotencyKey: `visual-schedule-pause:${item.id}`, }); } await store.putAgentScheduleReference({ scheduleId: routine.id, agentId: 'agent_release', workspaceId: WORKSPACE_ID, channelId: 'C_RELEASES', createdByMembershipId: ownerMembershipId, runsAsMembershipId: ownerMembershipId, authorityReceiptId: `receipt_${item.id}`, requiredConnectionAccountIds: [], state: item.referenceState, }); } } function fakeSlackResponse(pathname, body) { if (pathname.endsWith('/conversations.list')) { return { ok: true, channels: VISUAL_CHANNELS, response_metadata: { next_cursor: '' }, }; } if (pathname.endsWith('/auth.test')) { return { ok: true, app_id: 'AVISUAL', team: 'Acme Design', team_id: WORKSPACE_ID, user_id: LOCAL_SLACK_BOT_ID, bot_id: 'B_VISUAL', }; } if (pathname.endsWith('/users.info')) { const visualProfiles = { UVISUALOWNER: ['Pejman Pour-Moezzi', 'pejman.pourmoezzi', 'pejman@example.com'], UVISUALADMIN: ['Alex Admin', 'alex.admin', 'alex@example.com'], UVISUALMEMBER: ['Maya Member', 'maya.member', 'maya@example.com'], UVISUALOWNER2: ['Olive Owner', 'olive.owner', 'olive@example.com'], UVISUALSUSPENDED: ['Sam Suspended', 'sam.suspended', 'sam@example.com'], UVISUALREMOVED: ['Rae Removed', 'rae.removed', 'rae@example.com'], }; const profile = visualProfiles[body.user]; if (profile) { return { ok: true, user: { id: body.user, team_id: WORKSPACE_ID, name: profile[1], profile: { display_name: profile[1], real_name: profile[0], email: profile[2], image_192: 'https://secure.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=192&d=identicon', }, }, }; } if (body.user !== LOCAL_SLACK_BOT_ID) { return { ok: false, error: 'user_not_found' }; } return { ok: true, user: { id: LOCAL_SLACK_BOT_ID, team_id: WORKSPACE_ID, is_bot: true, profile: { display_name: 'Chickpea', real_name: 'Chickpea' }, }, }; } if (pathname.endsWith('/conversations.info')) { const channel = VISUAL_CHANNELS .find((candidate) => candidate.id === body.channel); return channel ? { ok: true, channel } : { ok: false, error: 'channel_not_found' }; } return { ok: false, error: 'fixture_endpoint_not_found' }; } async function nodeRequest(req, res, app, baseUrl, platformEnv) { try { const chunks = []; for await (const chunk of req) chunks.push(Buffer.from(chunk)); const body = chunks.length ? Buffer.concat(chunks) : undefined; const request = new Request(new URL(req.url ?? '/', baseUrl), { method: req.method, headers: req.headers, ...(body ? { body } : {}), }); const response = await app.fetch(request, platformEnv); res.statusCode = response.status; for (const [name, value] of response.headers) res.setHeader(name, value); const bytes = Buffer.from(await response.arrayBuffer()); res.end(bytes); } catch (error) { res.statusCode = 500; res.setHeader('content-type', 'text/plain; charset=utf-8'); res.end(error instanceof Error ? error.message : String(error)); } } function listen(server, port, host) { return new Promise((resolveListen, reject) => { server.once('error', reject); server.listen(port, host, () => { server.off('error', reject); resolveListen(); }); }); } function closeServer(server) { return new Promise((resolveClose, reject) => { if (!server.listening) { resolveClose(); return; } server.close((error) => (error ? reject(error) : resolveClose())); }); } /** Start one disposable, loopback-only real Admin fixture. */ export async function startAdminVisualFixture(options = {}) { const host = options.host ?? DEFAULT_HOST; const port = options.port ?? 0; const runtimeContract = options.runtimeContract ?? 'legacy'; const onboardingStage = options.onboardingStage ?? null; const principalRole = options.principalRole ?? 'owner'; const updateState = options.updateState ?? null; const sandboxState = options.sandboxState ?? null; if (updateState !== null && !['available', 'current', 'failed', 'unversioned', 'no-release'].includes(updateState)) { throw new Error('Unknown update visual state.'); } if (sandboxState !== null && !Object.hasOwn(SANDBOX_VISUAL_DEPLOY_SOURCES, sandboxState)) { throw new Error('Unknown sandbox visual state.'); } assertLoopbackHost(host); if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) { throw new Error(`Admin visual fixture port must be an integer from 0 to 65535, received ${port}.`); } if (runtimeContract !== 'legacy' && runtimeContract !== 'chickpea-v1') { throw new Error(`Admin visual fixture runtime contract must be legacy or chickpea-v1, received ${runtimeContract}.`); } if (onboardingStage !== null && ![ 'choose_provider', 'choose_model', 'try', ].includes(onboardingStage)) { throw new Error(`Unknown onboarding visual stage ${onboardingStage}.`); } if (!['owner', 'admin', 'member'].includes(principalRole)) { throw new Error(`Unknown Admin visual principal role ${principalRole}.`); } if (activeFixture) throw new Error('Only one Admin visual fixture can run in a process at a time.'); activeFixture = true; const stateDirectory = mkdtempSync(join(tmpdir(), 'chickpea-admin-visual-')); const stateDbPath = join(stateDirectory, 'state.db'); const previousEnv = new Map(ENV_KEYS.map((key) => [key, process.env[key]])); const stores = []; let server; let closed = false; const cleanup = async () => { if (closed) return; closed = true; // Start refusing new requests immediately, but do not yield before // releasing process-local resources. npm and the child Node process both // receive an interactive signal, so a first-await cleanup can be cut off // before its temporary database is removed. const serverClosing = server ? closeServer(server) : Promise.resolve(); try { for (const store of stores.reverse()) { try { store.close?.(); } catch { /* finish closing the remaining handles */ } } } finally { for (const [key, value] of previousEnv) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } rmSync(stateDirectory, { recursive: true, force: true }); activeFixture = false; } await serverClosing; }; try { assertNodeVersion(); const { Hono } = await import('hono'); const { createAdminRoutes } = await loadTsModule('src/admin/routes.ts'); const { renderSlackAccessDeniedPage, renderSlackOwnerCompletePage, renderSlackRecoveryPage, renderSlackSetupPage, renderSlackSignInPage, } = await loadTsModule('src/admin/page.ts'); const { renderManagedConnectionSetupPage, renderManagedConnectionSuccessPage, renderManagedConnectionWaitingPage, } = await loadTsModule('src/management/connector-landing-page.ts'); const { buildSlackAppManifest, slackManifestPrefillUrl, } = await loadTsModule('src/slack/app-manifest.ts'); const { SqliteConfigStore } = await loadTsModule('src/config/store.ts'); const { SqliteSettingsStore } = await loadTsModule('src/config/settings-store.ts'); const { PROVIDER_KEY_SETTING_KEYS } = await loadTsModule('src/config/provider-keys.ts'); const { SqliteMemoryStateStore } = await loadTsModule('src/memory/store.ts'); const { agentRoutineDeletion, SqliteRoutineStore } = await loadTsModule('src/routines/store.ts'); const { RoutineService } = await loadTsModule('src/routines/service.ts'); const { SqliteUsageStore } = await loadTsModule('src/usage/store.ts'); const { SqliteWorkStore } = await loadTsModule('src/work/store.ts'); const { SqliteSlackStateStore } = await loadTsModule('src/slack/claim-store.ts'); const { SqliteAgentSnapshotStore } = await loadTsModule('src/config/snapshot-store.ts'); const { SqliteIdentityStore } = await loadTsModule('src/identity/store.ts'); const { SqliteManagementStore } = await loadTsModule('src/management/store.ts'); const { provisionSlackInteractionMember } = await loadTsModule('src/auth/slack-admission.ts'); const { beginOnboardingJourney, selectOnboardingProvider, startOnboardingTry, } = await loadTsModule('src/config/onboarding-state.ts'); const { generateCredentialKeyring } = await loadTsModule('src/slack/credential-keyring.ts'); const { writeSlackInstallationCredentials } = await loadTsModule('src/slack/installation-credentials.ts'); const { WORKSPACE_SLACK_INSTALLATION_ID } = await loadTsModule('src/config/types.ts'); const { invalidateSlackChannelsCache, } = await loadTsModule('src/slack/channels.ts'); const { invalidateStoredSlackCredentials, } = await loadTsModule('src/slack/credentials.ts'); invalidateSlackChannelsCache(); invalidateStoredSlackCredentials(); const store = new SqliteConfigStore(stateDbPath, agentRoutineDeletion, { agents: visualAgents(), grants: [], }); const settings = new SqliteSettingsStore(stateDbPath); const memory = new SqliteMemoryStateStore(stateDbPath); const routines = new SqliteRoutineStore(stateDbPath); const usage = new SqliteUsageStore(stateDbPath); const work = new SqliteWorkStore(stateDbPath); const slackState = new SqliteSlackStateStore(stateDbPath); const snapshots = new SqliteAgentSnapshotStore(stateDbPath); const identity = new SqliteIdentityStore(stateDbPath); const management = new SqliteManagementStore(stateDbPath); stores.push(store, settings, memory, routines, usage, work, slackState, snapshots, identity, management); await seedConfig(store, runtimeContract); await seedSettings(settings); if (onboardingStage) { if (onboardingStage === 'choose_model' || onboardingStage === 'try') { await settings.setSetting( PROVIDER_KEY_SETTING_KEYS.anthropic, 'sk-ant-local-admin-visual-fixture', ); } let onboarding = await beginOnboardingJourney(settings, Date.now()); if (onboardingStage === 'choose_model' || onboardingStage === 'try') { onboarding = await selectOnboardingProvider(settings, { expectedRevision: onboarding.revision, workspaceId: WORKSPACE_ID, providerId: 'anthropic', }); } if (onboardingStage === 'try') { await startOnboardingTry(settings, { expectedRevision: onboarding.revision, agentId: 'agent_chickpea', modelId: 'anthropic/claude-sonnet-5', slackUserId: 'UVISUAL', tryStartedAt: Date.now(), }); } } const slackCredentials = { state: identity, keyring: generateCredentialKeyring(), }; await writeSlackInstallationCredentials( slackCredentials, WORKSPACE_SLACK_INSTALLATION_ID, null, { botToken: LOCAL_SLACK_TOKEN, signingSecret: LOCAL_SLACK_SECRET, botUserId: LOCAL_SLACK_BOT_ID, appId: 'AVISUAL', teamId: WORKSPACE_ID, }, ); await seedMemory(memory); const owner = await seedVisualSlackOwner(identity); await seedVisualProposal(management, store, owner); await seedConnections(store, owner.membership.id); if (options.notionSignIns) { await seedNotionSignIns(store, owner.membership, await loadTsModule('src/connections/catalog/index.ts'), principalRole); } await seedSchedules(store, routines, owner.membership.id, RoutineService); await seedVisualTeam(identity, provisionSlackInteractionMember, owner); const adminToken = `visual-${randomBytes(18).toString('base64url')}`; const app = new Hono(); const authManifest = buildSlackAppManifest({ kind: 'workspace_app', origin: 'https://chickpea.example', }); const authSetup = (state) => ({ id: 'setup_visual', state, revision: 4, destination: '/admin/channels', createdAt: 1, updatedAt: 2, expiresAt: Date.now() + 86_400_000, appId: 'AVISUAL', manifestFingerprint: 'f'.repeat(64), credentialRevision: 'slackrev_visual', }); const authPages = { 'sign-in': renderSlackSignInPage('/admin/channels'), 'setup-create': renderSlackSetupPage({ setup: authSetup('awaiting_app_creation'), destination: '/admin/channels', manifest: authManifest, manifestPrefillUrl: slackManifestPrefillUrl(authManifest), }), 'setup-connected': renderSlackSetupPage({ setup: authSetup('awaiting_app_creation'), destination: '/admin/channels', manifest: authManifest, manifestPrefillUrl: slackManifestPrefillUrl(authManifest), gatewayState: 'connected', }), 'setup-approval': renderSlackSetupPage({ setup: authSetup('approval_pending'), destination: '/admin/channels', manifest: authManifest, manifestPrefillUrl: slackManifestPrefillUrl(authManifest), }), 'setup-owner': renderSlackSetupPage({ setup: authSetup('bot_installed'), destination: '/admin/channels', manifest: authManifest, manifestPrefillUrl: slackManifestPrefillUrl(authManifest), }), 'access-denied': renderSlackAccessDeniedPage({ purpose: 'login', destination: '/admin/channels', reason: 'user_mismatch', workspace: { teamId: WORKSPACE_ID, teamName: 'Acme' }, }), 'owner-complete': renderSlackOwnerCompletePage('/admin/channels'), recovery: renderSlackRecoveryPage({ stage: 'credentials', expectedAppId: 'AVISUAL', expectedTeamId: WORKSPACE_ID, }), }; const sprout = { id: 'agent_sprout', revision: 1, name: 'Sprout', description: '', instructions: 'Help the requester research customer relationships.', enabled: true, skills: [], mcpServers: [], apiConnections: [], repositories: [], }; const connectorSetup = { setupOperationId: 'setup_visual_connector', action: 'managed_connection', target: { kind: 'managed_connection', targetId: 'agent:agent_sprout:managed:hubspot:member', targetLabel: 'HubSpot', provider: 'hubspot', agentId: sprout.id, agentName: sprout.name, ownerKind: 'member', accessLane: 'read', presetId: 'hubspot-managed', }, actor: { membershipId: owner.membership.id, organizationId: owner.membership.organizationId, slackTeamId: WORKSPACE_ID, slackUserId: owner.user.slackUserId, }, status: 'pending', createdAt: Date.now(), expiresAt: Date.now() + 15 * 60_000, }; const sproutAvatarPath = resolve('assets/chickpea-avatars/agent-defaults/01-sage.png'); const connectorPageInput = (status) => ({ setup: { ...connectorSetup, status }, agent: sprout, avatarUrl: '/__admin_visual_fixture/connectors/sprout.png', }); let connectorVisualConnected = false; app.get('/__admin_visual_fixture/login', (c) => { if (c.req.query('token') !== adminToken) return c.text('Not found', 404); c.header('Set-Cookie', `chickpea_visual_token=${adminToken}; Path=/; HttpOnly; SameSite=Lax`); c.header('Cache-Control', 'no-store'); return c.redirect(c.req.query('destination') || '/admin/team'); }); app.get('/__admin_visual_fixture/auth/:state', (c) => { const html = authPages[c.req.param('state')]; if (!html) return c.notFound(); c.header('Cache-Control', 'no-store'); return c.html(html); }); app.get('/__admin_visual_fixture/connectors/sprout.png', (c) => { c.header('Cache-Control', 'public, max-age=3600'); c.header('Content-Type', 'image/png'); return c.body(readFileSync(sproutAvatarPath)); }); app.get('/__admin_visual_fixture/connectors/:state', (c) => { const state = c.req.param('state'); if (state === 'setup') { connectorVisualConnected = false; return c.html(renderManagedConnectionSetupPage(connectorPageInput('pending'))); } if (state === 'waiting') { return c.html(connectorVisualConnected ? renderManagedConnectionSuccessPage(connectorPageInput('completed')) : renderManagedConnectionWaitingPage(connectorPageInput('pending'))); } if (state === 'success') { return c.html(renderManagedConnectionSuccessPage(connectorPageInput('completed'))); } return c.notFound(); }); app.post('/setup/setup_visual_connector/authorize', (c) => { if (c.req.header('x-requested-with') === 'chickpea-setup') { return c.json({ authorizationUrl: 'https://example.com/chickpea-connector-sign-in' }); } return c.redirect('/__admin_visual_fixture/connectors/waiting'); }); app.post('/setup/setup_visual_connector/managed/poll', (c) => { connectorVisualConnected = true; return c.json({ ok: true, status: 'connected' }); }); app.post('/setup/setup_visual_connector/cancel', (c) => c.redirect('/__admin_visual_fixture/connectors/setup')); app.post('/__admin_visual_fixture/slack/:method', async (c) => { if (c.req.header('authorization') !== `Bearer ${LOCAL_SLACK_TOKEN}`) { return c.json({ ok: false, error: 'invalid_auth' }); } const body = await c.req.parseBody(); const result = fakeSlackResponse(c.req.path, body); return c.json(result, result.error === 'fixture_endpoint_not_found' ? 404 : 200); }); // Visual-only response fixtures run AFTER the real authorization gate. // They never become production configuration or contact GitHub. app.use('/admin/api/installation/*', async (c, next) => { await next(); if (!updateState || c.res.status !== 200) return; if (c.req.path.endsWith('/updates')) { const release = { version: updateState === 'current' ? '0.1.0' : '0.1.1', notes: 'A small reliability update.\n\n• Clearer setup guidance\n• Improvements to scheduled replies', publishedAt: '2026-09-07T12:00:00Z', url: 'https://github.com/pejmanjohn/chickpea/releases/tag/v0.1.1' }; c.res = Response.json({ status: updateState, checkedAt: '2026-09-07T12:00:00Z', ...(updateState === 'no-release' ? {} : { release }), ...(updateState === 'failed' ? { error: 'network', lastSuccessfulCheckAt: '2026-09-06T12:00:00Z' } : {}) }); } else if (c.req.path.endsWith('/support') && updateState !== 'unversioned') { const body = await c.res.json(); c.res = Response.json({ report: body.report.replace('Application version: development', 'Application version: 0.1.0').replace('Source commit: unknown', `Source commit: ${'a'.repeat(40)}`).replace('Deployment: Node', 'Deployment: Cloudflare') }); } }); // The fixture runs on Node, where the coding sandbox is unsupported. This // presents the Cloudflare redeploy-required status for visual review only. app.use('/admin/api/sandbox/status', async (c, next) => { await next(); if (!sandboxState || c.req.method !== 'GET' || c.res.status !== 200) return; const body = await c.res.json(); c.res = Response.json({ ...body, target: 'cloudflare', installRequested: true, installed: false, deploySource: SANDBOX_VISUAL_DEPLOY_SOURCES[sandboxState], unmetPrerequisites: ['sandbox_binding', ...body.unmetPrerequisites.filter((item) => item !== 'cloudflare_target')], workersPaidNote: 'Requires Workers Paid. Real containers run on your Cloudflare account; a typical session costs about 1 cent. ' + 'Workspace checkpoints also need R2 enabled on the account; the free tier is enough.', checkpointsNote: null, }); }); // The Websites tab reads display-safe login rows for an Agent. The fixture // answers for its own Agents after the real authorization gate, so the // state renders without a Browserbase project or stored sign-ins. app.use('/admin/api/agents/:agentId/website-logins', async (c, next) => { await next(); if (c.req.method !== 'GET' || (c.res.status !== 200 && c.res.status !== 404)) return; const agentId = decodeURIComponent(c.req.path.split('/')[4] ?? ''); if (!VISUAL_FIXTURE_AGENT_IDS.includes(agentId)) return; const now = Date.now(); c.res = Response.json({ logins: agentId === 'agent_research' ? [ { loginId: `wl_${'a1'.repeat(16)}`, host: 'acme.com', label: 'Acme team', ownerKind: 'team', method: 'credentials', username: 'qa-team@acme.com', lastUsedAt: now - 2 * 3_600_000, level: 'act', enabled: true, }, { loginId: `wl_${'b2'.repeat(16)}`, host: 'admin.acme.com', label: 'Acme admin', ownerKind: 'team', method: 'handoff', lastUsedAt: now - 6 * 86_400_000, level: 'check', enabled: true, }, ] : [], }); }); // Connected apps reads the person's Better Auth grants, which the fixture // has none of: the real route answers 409 after authorization, and the // fixture answers with sample apps in its place. const day = 86_400_000; const visualIso = (ago) => new Date(Date.now() - ago).toISOString(); let visualConnectedApps = [ { appId: 'https://chatgpt.com/oauth/client.json', name: 'ChatGPT', connectedAt: visualIso(9 * day), lastActiveAt: visualIso(2 * 3_600_000) }, { appId: 'https://claude.ai/oauth/claude-code-client-metadata', name: 'Claude Code', connectedAt: visualIso(30 * day), lastActiveAt: visualIso(3 * day) }, { appId: 'visual_client_pipeline', name: 'Pipeline reporter', nameFromApp: true, connectedAt: visualIso(day), lastActiveAt: null }, ]; app.use('/admin/api/connected-apps/*', async (c, next) => { await next(); if (c.req.method !== 'POST' || c.res.status !== 409) return; const { appId } = await c.req.json(); const found = visualConnectedApps.find((candidate) => candidate.appId === appId); visualConnectedApps = visualConnectedApps.filter((candidate) => candidate !== found); c.res = found ? Response.json({ name: found.name }) : Response.json({ error: 'not_found' }, { status: 404 }); }); app.use('/admin/api/connected-apps', async (c, next) => { await next(); if (c.req.method !== 'GET' || c.res.status !== 409) return; c.res = Response.json({ apps: visualConnectedApps }); }); app.use('/admin/api/installation', async (c, next) => { await next(); if (!updateState || updateState === 'unversioned' || c.res.status !== 200) return; const body = await c.res.json(); c.res = Response.json({ ...body, identity: { version: '0.1.0', sourceCommit: 'a'.repeat(40) }, deployment: 'cloudflare' }); }); const managedConnectionProviders = options.notionSignIns ? visualNotionProviders(await loadTsModule('src/connections/managed.ts')) : undefined; app.route('/', createAdminRoutes({ ...(managedConnectionProviders ? { managedConnectionProviders } : {}), updateFetch: async () => new Response('', { status: 404 }), store, settings, memory, routines, usage, work, slackState, snapshots, identity, slackCredentials, management, usageAdminUi: true, authService: { async authenticateRequest(request) { const cookies = request.headers.get('cookie') || ''; const cookieAuthenticated = cookies.split(';').some((part) => part.trim() === `chickpea_visual_token=${adminToken}`); if (request.headers.get('authorization') !== `Bearer ${adminToken}` && !cookieAuthenticated) { throw new Error('Authentication unavailable.'); } return { userId: owner.user.id, membershipId: owner.membership.id, organizationId: owner.membership.organizationId, role: principalRole, authenticatorKind: 'visual_slack_session', credentialId: 'visual_session', correlationId: 'visual_request', machine: false, }; }, }, knownProviders: new Set(onboardingStage ? ['local-stub', 'cloudflare'] : ['local-stub']), runtimeDrain: async () => ({ drained: true, categories: { admittedTurns: 0, claimedTurns: 0, pendingDeliveries: 0, executingRuns: 0, admittingOrRunningRoutineOccurrences: 0, }, }), environmentStatus: async () => VISUAL_ENVIRONMENT_STATUS, })); const platformEnv = onboardingStage ? { AI: { models() { return []; } } } : undefined; let baseUrl = ''; server = createServer((req, res) => void nodeRequest(req, res, app, baseUrl, platformEnv)); await listen(server, port, host); const bound = server.address(); if (!bound || typeof bound === 'string' || !loopbackAddress(bound.address)) { throw new Error(`Admin visual fixture did not bind to loopback: ${String(bound)}.`); } baseUrl = `http://${urlHost(bound.address)}:${bound.port}`; const control = await identity.getAuthControl(); await identity.updateAuthControl({ expectedRevision: control.revision, canonicalAdminOrigin: baseUrl, }); await identity.updateOrganizationAuth({ organizationId: owner.membership.organizationId, authMode: 'slack_active', canonicalAdminOrigin: baseUrl, }); // The API endpoint is process-local. Slack secrets stay exclusively in the // encrypted fixture revision and never become runtime environment inputs. process.env.SLACK_API_URL = `${baseUrl}/__admin_visual_fixture/slack`; return { address: bound.address, adminToken, baseUrl, runtimeContract, onboardingStage, authStates: CANONICAL_SLACK_AUTH_VISUAL_STATES, connectorStates: CANONICAL_CONNECTOR_VISUAL_STATES, canonicalStates: CANONICAL_ADMIN_VISUAL_STATES, stateDbPath, stateDirectory, close: cleanup, }; } catch (error) { await cleanup(); throw error; } } function parseCliArgs(args) { const parsed = { host: DEFAULT_HOST, port: 0, runtimeContract: 'legacy', onboardingStage: null }; for (let index = 0; index < args.length; index += 1) { const value = args[index]; if (value === '--host') parsed.host = args[++index]; else if (value === '--port') parsed.port = Number(args[++index]); else if (value === '--runtime-contract') parsed.runtimeContract = args[++index]; else if (value === '--onboarding-stage') parsed.onboardingStage = args[++index]; else if (value === '--update-state') parsed.updateState = args[++index]; else if (value === '--sandbox-state') parsed.sandboxState = args[++index]; else if (value === '--principal-role') parsed.principalRole = args[++index]; else if (value === '--notion-sign-ins') parsed.notionSignIns = true; else throw new Error(`Unknown argument: ${value}`); } return parsed; } async function runCli() { const fixture = await startAdminVisualFixture(parseCliArgs(process.argv.slice(2))); // Install cleanup before advertising readiness to a parent process or user. let stopping = false; const cleanupOnExit = () => void fixture.close(); process.once('exit', cleanupOnExit); const stop = async (signal) => { if (stopping) return; stopping = true; console.log(`\n${signal}: stopping Admin visual fixture…`); await fixture.close(); process.off('exit', cleanupOnExit); process.exitCode = 0; }; process.once('SIGINT', () => void stop('SIGINT')); process.once('SIGTERM', () => void stop('SIGTERM')); // npm can relay terminal shutdown as SIGHUP after its wrapper exits. process.once('SIGHUP', () => void stop('SIGHUP')); console.log(`Admin visual fixture: ${fixture.baseUrl}`); console.log(`Runtime contract: ${fixture.runtimeContract}`); if (fixture.onboardingStage) console.log(`Onboarding stage: ${fixture.onboardingStage}`); console.log(`Local login token: ${fixture.adminToken}`); console.log('Canonical states (production URLs; perform the listed UI actions after load):'); for (const [name, state] of Object.entries(fixture.canonicalStates)) { const actions = state.actions.length ? ` then ${state.actions.join(' → ')}` : ''; console.log(` ${name}: ${fixture.baseUrl}${state.path}${actions}`); } console.log('Connector landing states:'); for (const [name, state] of Object.entries(fixture.connectorStates)) { console.log(` ${name}: ${fixture.baseUrl}${state.path}`); } console.log(`Temporary state: ${fixture.stateDbPath}`); console.log('Press Ctrl+C to stop and remove the temporary state.'); } const invokedPath = process.argv[1] ? resolve(process.argv[1]) : ''; if (invokedPath && resolve(fileURLToPath(import.meta.url)) === invokedPath) { runCli().catch((error) => { console.error(`${basename(fileURLToPath(import.meta.url))}:`, error); process.exitCode = 1; }); }