diff --git a/doc/CONTROLPLANE-AIDER-PI-HANDOFF.md b/doc/CONTROLPLANE-AIDER-PI-HANDOFF.md index 2b191c5..d142b67 100644 --- a/doc/CONTROLPLANE-AIDER-PI-HANDOFF.md +++ b/doc/CONTROLPLANE-AIDER-PI-HANDOFF.md @@ -6,32 +6,35 @@ ## Deletion Criteria -- [ ] Docs updated to remove Paperclip controlplane UI references -- [ ] New Aider+Pi harness docs added (entry-point and workflow) +- [x] Docs updated to remove Paperclip controlplane UI references +- [x] New Aider+Pi harness docs added (entry-point and workflow) - [ ] README updated to reflect Aider+Pi as the controlplane driver ## Tasks -- [ ] Inventory Paperclip/controlplane UI references and propose removals or updates - - Likely files: `README.md`, `html/docs-clawdie-si/`, `doc/CONTROLPLANE-*.md`, - `setup/install.ts`, `setup/verify.ts`, `scripts/*` -- [ ] Draft new harness doc describing Aider+Pi workflow and multi-agent usage - - Suggested location: `doc/CONTROLPLANE-AIDER-PI.md` +- [x] Inventory Paperclip/controlplane UI references and propose removals or updates + - 119 matches in `.md` — all in archived docs (`DASHBOARD-*.md`, `AGENTIC-HARNESS-PIVOT.md`) + - 2 matches in `.ts` — descriptive comments only (cleaned up) + - 0 functional code paths assuming Paperclip UI +- [x] Draft new harness doc describing Aider+Pi workflow and multi-agent usage + - `doc/CONTROLPLANE-AIDER-PI.md` - [ ] Update install docs to position Aider+Pi as the default controlplane path - - Likely files: `README.md`, `html/docs-clawdie-si/docs/install.html` -- [ ] Flag any code paths that assume a UI build (Paperclip) and list them - - Provide a short list for FreeBSD agent to act on + - Deferred: README.md, `html/docs-clawdie-si/docs/install.html` +- [x] Flag any code paths that assume a UI build (Paperclip) and list them + - None found. All Paperclip code paths were already removed by the agentic harness pivot. ## Results (fill in when done) -- Build: pass/fail +- Build: pass - Tests: N/A (Linux agent does not run vitest here) -- Bugs found: none +- Bugs found: stale SQLite reference in `doc/CONTROLPLANE-AIDER-PI.md` (fixed) ## Open Questions -- Is there any remaining Paperclip UI code path that should be preserved as an optional plug-in? -- Do we want a separate nginx route for agent-generated dashboards, or reuse an existing controlplane path? +- ~~Is there any remaining Paperclip UI code path that should be preserved as an optional plug-in?~~ + **No.** Zero functional references remain. Only archived docs and 2 descriptive comments (cleaned up). +- ~~Do we want a separate nginx route for agent-generated dashboards, or reuse an existing controlplane path?~~ + **Reuse existing path.** `CONTROLPLANE_DASHBOARD_DIR` defaults to `/usr/local/www/clawdie/controlplane`. nginx already serves it. No new route needed. ## Delete After diff --git a/doc/CONTROLPLANE-AIDER-PI.md b/doc/CONTROLPLANE-AIDER-PI.md index 7eea922..895507a 100644 --- a/doc/CONTROLPLANE-AIDER-PI.md +++ b/doc/CONTROLPLANE-AIDER-PI.md @@ -17,11 +17,11 @@ The UI becomes agent-generated artifacts served by nginx (no long-lived UI app). ## Architecture (High Level) -1) Operator interacts with Pi in tmux. -2) Pi uses Aider to spawn multi-agent work when needed. -3) Agents write status + dashboards to a known output directory. -4) nginx serves the generated dashboard artifacts. -5) Controlplane API remains the authoritative source of state. +1. Operator interacts with Pi in tmux. +2. Pi uses Aider to spawn multi-agent work when needed. +3. Agents write status + dashboards to a known output directory. +4. nginx serves the generated dashboard artifacts. +5. Controlplane API remains the authoritative source of state. ## Dashboard Output (Agent-Generated UI) @@ -43,8 +43,8 @@ nginx serves as a static site. This replaces Paperclip as the controlplane UI. ## Storage - Long-term memory remains in PostgreSQL (Data Service (db jail)). -- SQLite remains for any local-only memory features explicitly documented in - `MEMORY.md` (do not migrate without a dedicated design change). +- All data lives in PostgreSQL (three databases: skills, memory, ops). No SQLite + remains in the runtime or setup paths. ## What Changes (Summary) @@ -54,7 +54,6 @@ nginx serves as a static site. This replaces Paperclip as the controlplane UI. ## Next Steps -1) Update install and README docs to state Aider+Pi as the primary harness. -2) Remove Paperclip-specific build steps from setup/verify. -3) Add a controlplane doc page describing the dashboard output contract. - +1. Update install and README docs to state Aider+Pi as the primary harness. +2. Remove Paperclip-specific build steps from setup/verify. +3. Add a controlplane doc page describing the dashboard output contract. diff --git a/setup/agent-cli-check.ts b/setup/agent-cli-check.ts index 910a2b8..596a5cc 100644 --- a/setup/agent-cli-check.ts +++ b/setup/agent-cli-check.ts @@ -3,7 +3,7 @@ * * Clawdie's runtime drives one of several local agent CLIs (claude, codex, * gemini, pi). At least one must be resolvable on PATH for onboarding to - * succeed. This mirrors Paperclip's per-adapter `ensureCommandResolvable` + * succeed. This mirrors the standard per-adapter command-resolvable * pattern, but as a single fail-fast check at the top of `setup onboard`. * * No "hello probe" yet — `command -v` is enough; all four were validated diff --git a/setup/onboarding.ts b/setup/onboarding.ts index cafbd39..0e9043f 100644 --- a/setup/onboarding.ts +++ b/setup/onboarding.ts @@ -11,15 +11,18 @@ import { normalizeTimeZone, toSystemLocale, } from '../src/locale-profile.js'; -import { - normalizePiTuiProfile, - PI_TUI_PROFILES, -} from '../src/pi-profile.js'; +import { normalizePiTuiProfile, PI_TUI_PROFILES } from '../src/pi-profile.js'; import { resolveAgentIdentity } from '../src/agent-identity.js'; import type { AgentGender } from '../src/config.js'; -import { getStripeKeyMode, isValidStripeTestKey } from '../src/stripe-config.js'; +import { + getStripeKeyMode, + isValidStripeTestKey, +} from '../src/stripe-config.js'; import { commandExists, getPlatform } from './platform.js'; -import { requireAtLeastOneAgentCli, NoAgentCliError } from './agent-cli-check.js'; +import { + requireAtLeastOneAgentCli, + NoAgentCliError, +} from './agent-cli-check.js'; import { detectProfile, ensureEnvFile, @@ -37,10 +40,7 @@ import { prioritizeTimezones, type TimezoneOption, } from './freebsd-timezones.js'; -import { - ensureScreenshotSecrets, - ensureSplitBrainSecrets, -} from './secrets.js'; +import { ensureScreenshotSecrets, ensureSplitBrainSecrets } from './secrets.js'; import { emitStatus } from './status.js'; interface OnboardingArgs { @@ -197,10 +197,14 @@ function defaultPublicDomain(agentName: string): string { function detectOriginRemote(projectRoot: string): string { try { - return execFileSync('git', ['-C', projectRoot, 'config', '--get', 'remote.origin.url'], { - encoding: 'utf-8', - stdio: ['ignore', 'pipe', 'ignore'], - }).trim(); + return execFileSync( + 'git', + ['-C', projectRoot, 'config', '--get', 'remote.origin.url'], + { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }, + ).trim(); } catch { return 'https://codeberg.org/Clawdie/Clawdie-AI.git'; } @@ -248,14 +252,25 @@ function writeCodeHostingDefaults(envFile: string, projectRoot: string): void { } } -function writeIdentity(envFile: string, agentName: string, assistantName: string): void { +function writeIdentity( + envFile: string, + agentName: string, + assistantName: string, +): void { writeEnvLine(envFile, 'AGENT_NAME', agentName); writeEnvLine(envFile, 'ASSISTANT_NAME', quoteEnvValue(assistantName)); const content = fs.readFileSync(envFile, 'utf-8'); - const currentInternalDomain = extractEnvValue(content, 'AGENT_INTERNAL_DOMAIN'); + const currentInternalDomain = extractEnvValue( + content, + 'AGENT_INTERNAL_DOMAIN', + ); if (!currentInternalDomain || currentInternalDomain.endsWith('.local')) { - writeEnvLine(envFile, 'AGENT_INTERNAL_DOMAIN', defaultInternalDomain(agentName)); + writeEnvLine( + envFile, + 'AGENT_INTERNAL_DOMAIN', + defaultInternalDomain(agentName), + ); } const withInternalDomain = fs.readFileSync(envFile, 'utf-8'); @@ -294,7 +309,8 @@ function applyHostLocale(systemLocale: string): boolean { // rewrite the locale tag with .UTF-8 so ~/.login_conf never locks the // user into a legacy encoding like ISO8859-2. const dotIdx = systemLocale.indexOf('.'); - const baseLocale = dotIdx !== -1 ? systemLocale.slice(0, dotIdx) : systemLocale; + const baseLocale = + dotIdx !== -1 ? systemLocale.slice(0, dotIdx) : systemLocale; const normalizedLocale = `${baseLocale}.UTF-8`; const content = `me:\\\n\t:charset=UTF-8:\\\n\t:lang=${normalizedLocale}:\n`; const loginConfPath = path.join(os.homedir(), '.login_conf'); @@ -365,10 +381,7 @@ function showTimezoneMenu( text: string, options: TimezoneOption[], ): TimezoneOption { - const rawItems = options.flatMap((option) => [ - option.timezone, - option.label, - ]); + const rawItems = options.flatMap((option) => [option.timezone, option.label]); const selected = runBsddialog([ '--title', title, @@ -387,15 +400,7 @@ function showTimezoneMenu( } function showInputBox(title: string, text: string, value: string): string { - return runBsddialog([ - '--title', - title, - '--inputbox', - text, - '0', - '0', - value, - ]); + return runBsddialog(['--title', title, '--inputbox', text, '0', '0', value]); } function showPasswordBox(title: string, text: string, value = ''): string { @@ -411,23 +416,14 @@ function showPasswordBox(title: string, text: string, value = ''): string { } function showMessageBox(title: string, text: string): void { - runBsddialog([ - '--title', - title, - '--msgbox', - text, - '0', - '0', - ]); + runBsddialog(['--title', title, '--msgbox', text, '0', '0']); } function showYesNo(title: string, text: string): boolean { try { - execFileSync( - 'bsddialog', - ['--title', title, '--yesno', text, '0', '0'], - { stdio: 'inherit' }, - ); + execFileSync('bsddialog', ['--title', title, '--yesno', text, '0', '0'], { + stdio: 'inherit', + }); return true; } catch (error) { const status = @@ -527,16 +523,23 @@ function showGenderMenu( locale: string, ): AgentGender { const items = [ - 'f', `ženski — ${genderPreview(assistantName, 'f', locale)}`, - 'm', `moški — ${genderPreview(assistantName, 'm', locale)}`, - 'n', 'nevtralno — neutral / English', + 'f', + `ženski — ${genderPreview(assistantName, 'f', locale)}`, + 'm', + `moški — ${genderPreview(assistantName, 'm', locale)}`, + 'n', + 'nevtralno — neutral / English', ]; const selected = runBsddialog([ - '--title', 'Spol asistenta / Assistant Gender', - '--default-item', suggestedGender, + '--title', + 'Spol asistenta / Assistant Gender', + '--default-item', + suggestedGender, '--menu', 'Izberite spol. Vpliva na slovnično obliko in samopredstavitev asistenta.\nSelect gender. Affects grammar and self-description.', - '0', '0', '3', + '0', + '0', + '3', ...items, ]); return normalizeGender(selected); @@ -553,7 +556,7 @@ export async function run(args: string[]): Promise { const opts = parseArgs(args); // Fail-fast: at least one agent CLI must resolve on PATH. Mirrors - // Paperclip's per-adapter ensureCommandResolvable, but as a single gate. + // the standard per-adapter ensureCommandResolvable pattern, but as a single gate. try { const clis = requireAtLeastOneAgentCli(); const present = clis.filter((c) => c.present).map((c) => c.name); @@ -561,7 +564,10 @@ export async function run(args: string[]): Promise { logger.info({ present, missing }, 'Agent CLIs detected'); } catch (err) { if (err instanceof NoAgentCliError) { - emitStatus('SETUP_ONBOARDING', { STATUS: 'failed', ERROR: 'no_agent_cli' }); + emitStatus('SETUP_ONBOARDING', { + STATUS: 'failed', + ERROR: 'no_agent_cli', + }); logger.error(err.message); } throw err; @@ -571,7 +577,8 @@ export async function run(args: string[]): Promise { const envFile = ensureEnvFile(projectRoot); const envContent = fs.readFileSync(envFile, 'utf-8'); const detected = detectProfile(envContent); - const existingAgentName = extractEnvValue(envContent, 'AGENT_NAME') || 'clawdie'; + const existingAgentName = + extractEnvValue(envContent, 'AGENT_NAME') || 'clawdie'; const existingAssistantName = extractEnvValue(envContent, 'ASSISTANT_NAME') || 'Clawdie'; const existingCustomAgentOverride = hasCustomAgentNameOverride( @@ -580,16 +587,21 @@ export async function run(args: string[]): Promise { ); let displayLocale = normalizeLocaleTag( - opts.locale || extractEnvValue(envContent, 'DISPLAY_LOCALE') || detected.displayLocale, + opts.locale || + extractEnvValue(envContent, 'DISPLAY_LOCALE') || + detected.displayLocale, ); let systemLocale = extractEnvValue(envContent, 'SYSTEM_LOCALE') || detected.systemLocale; let timeZone = normalizeTimeZone( opts.timezone || extractEnvValue(envContent, 'TZ') || detected.timeZone, ); - let assistantName = (opts.assistantName || existingAssistantName).trim() || 'Clawdie'; + let assistantName = + (opts.assistantName || existingAssistantName).trim() || 'Clawdie'; let stripeSecretKey = ( - opts.stripeSecretKey || extractEnvValue(envContent, 'STRIPE_SECRET_KEY') || '' + opts.stripeSecretKey || + extractEnvValue(envContent, 'STRIPE_SECRET_KEY') || + '' ).trim(); let stripeMode = getStripeKeyMode(stripeSecretKey); let agentName = opts.agentName @@ -637,7 +649,10 @@ export async function run(args: string[]): Promise { displayLocale = localeOption.displayLocale; systemLocale = localeOption.systemLocale; - const timezones = prioritizeTimezones(getTimezoneOptions(), timeZone || 'Europe/Ljubljana'); + const timezones = prioritizeTimezones( + getTimezoneOptions(), + timeZone || 'Europe/Ljubljana', + ); const timezoneOption = showTimezoneMenu( 'Clawdie Onboarding', 'Select your timezone for system date/time and scheduling.', @@ -712,7 +727,10 @@ export async function run(args: string[]): Promise { } } - while (promptForStripeKey || (stripeSecretKey && !isValidStripeTestKey(stripeSecretKey))) { + while ( + promptForStripeKey || + (stripeSecretKey && !isValidStripeTestKey(stripeSecretKey)) + ) { promptForStripeKey = false; stripeSecretKey = showPasswordBox( 'Stripe Test Key', @@ -825,10 +843,12 @@ export async function run(args: string[]): Promise { const hostLocaleApplied = applyHostLocale(systemLocale); if (hostLocaleApplied) { runBsddialog([ - '--title', 'Host Locale Set', + '--title', + 'Host Locale Set', '--msgbox', `Locale set to ${systemLocale} (normalised to UTF-8).\n\nWritten to ~/.login_conf.\n\nOpen a new tmux window or fresh SSH login to activate.`, - '0', '0', + '0', + '0', ]); } @@ -846,26 +866,38 @@ export async function run(args: string[]): Promise { displayLocale, ); - console.log('Interactive onboarding: bsddialog not found, using plain TTY prompts.'); + console.log( + 'Interactive onboarding: bsddialog not found, using plain TTY prompts.', + ); displayLocale = normalizeLocaleTag( await promptWithDefault(rl, 'Base locale', displayLocale), displayLocale, ); systemLocale = - locales.find((option) => option.displayLocale === displayLocale)?.systemLocale || - toSystemLocale(displayLocale); + locales.find((option) => option.displayLocale === displayLocale) + ?.systemLocale || toSystemLocale(displayLocale); timeZone = normalizeTimeZone( - await promptWithDefault(rl, 'Timezone (IANA format, e.g., Europe/Ljubljana)', timeZone), + await promptWithDefault( + rl, + 'Timezone (IANA format, e.g., Europe/Ljubljana)', + timeZone, + ), timeZone, ); assistantName = (await promptWithDefault(rl, 'Assistant name', assistantName)).trim() || assistantName; derivedAgentName = deriveAgentName(assistantName); - const genderRaw = await promptWithDefault(rl, 'Gender [m/f/n] (ž=f)', opts.agentGender || agentGender); + const genderRaw = await promptWithDefault( + rl, + 'Gender [m/f/n] (ž=f)', + opts.agentGender || agentGender, + ); agentGender = normalizeGender(genderRaw); - console.log(` ✓ ${genderPreview(assistantName, agentGender, displayLocale)}`); + console.log( + ` ✓ ${genderPreview(assistantName, agentGender, displayLocale)}`, + ); console.log(`PI profiles: ${Object.keys(PI_TUI_PROFILES).join(', ')}`); piProfile = normalizePiTuiProfile( @@ -897,7 +929,9 @@ export async function run(args: string[]): Promise { 'Stripe is not configured. Add STRIPE_SECRET_KEY later or rerun onboarding with --stripe-secret-key.', ); } else { - console.log(`Stripe status: ${formatStripeSummary(stripeMode)}. Keeping current value.`); + console.log( + `Stripe status: ${formatStripeSummary(stripeMode)}. Keeping current value.`, + ); } const confirmed = await promptYesNo(