// Bypass mode: the owner's opt-in to let an AI agent press the owner-only buttons too (Submit for review, releases and // rollouts, testers, review replies, prices, deletes, cancels, console final buttons). Default OFF. Stored per machine // in ~/.storebound/settings.json, never in a project's config or repo: // storebound settings bypass on | off | status // When ON: the MCP server lists `owner_step` (runs one owner command, dry run first), `storebound api` sends owner-only // operations with --yes --confirm , console plans tell the agent it may press buttons.final. Every owner // action an agent runs is appended to /.storebound/owner-actions.log. `bypass off` restores the old behaviour // at once. An AI client may still ask the owner itself for legal agreements, passwords and 2FA (its own rules). import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; export interface Settings { bypass?: { on: boolean; since: string } } export const settingsFile = (env: Record = process.env): string => join(env.STOREBOUND_HOME ?? join(homedir(), '.storebound'), 'settings.json'); export function readSettings(env: Record = process.env): Settings { try { return JSON.parse(readFileSync(settingsFile(env), 'utf8')) as Settings; } catch { return {}; } } export function writeSettings(s: Settings, env: Record = process.env): void { const f = settingsFile(env); mkdirSync(dirname(f), { recursive: true }); writeFileSync(f, `${JSON.stringify(s, null, 2)}\n`); } /** Whether the owner turned bypass mode on for this machine. */ export const bypassOn = (env: Record = process.env): boolean => readSettings(env).bypass?.on === true; export function setBypass(on: boolean, env: Record = process.env): Settings { const s = readSettings(env); s.bypass = { on, since: new Date().toISOString() }; writeSettings(s, env); return s; } /** Owner-only commands `owner_step` may run in bypass mode (CLI words). Everything else has its own MCP tool. */ export const OWNER_COMMANDS = [ 'apple submit', 'apple resubmit', 'apple iap-submit', 'apple release', 'apple preorder', 'play release', 'play rollout', 'reviews', 'apple testflight', 'play products', 'play subscriptions', 'apple iap', 'apple subscriptions', 'prices', ] as const; export type OwnerCommand = typeof OWNER_COMMANDS[number]; export const BYPASS_NOTICE = 'bypass mode is ON (storebound settings bypass off turns it off): the agent runs owner steps itself; each one is logged to .storebound/owner-actions.log'; /** One line per owner action an agent ran: when, what, the result. */ export function logOwnerAction(root: string, entry: { command: string; dry: boolean; exit: number; summary?: string }): string { const f = join(root, '.storebound', 'owner-actions.log'); if (!existsSync(dirname(f))) mkdirSync(dirname(f), { recursive: true }); appendFileSync(f, `${new Date().toISOString()}\t${entry.dry ? 'dry' : 'RAN'}\texit ${entry.exit}\t${entry.command}${entry.summary ? `\t${entry.summary.replace(/\s+/g, ' ').slice(0, 300)}` : ''}\n`); return f; }