docs: clean up Paperclip references and update Aider+Pi handoff (Sam & Claude)
Remove Paperclip name from descriptive comments in setup/agent-cli-check.ts and setup/onboarding.ts. Fix stale SQLite reference in doc/CONTROLPLANE-AIDER-PI.md. Answer open questions and check off completed tasks in the Aider+Pi handoff doc. --- Build: pass | Tests: not run (Linux)
This commit is contained in:
parent
405dd6f9d9
commit
05fc2c30d7
4 changed files with 129 additions and 93 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
);
|
||||
|
||||
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<void> {
|
|||
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<void> {
|
|||
}
|
||||
}
|
||||
|
||||
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<void> {
|
|||
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<void> {
|
|||
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<void> {
|
|||
'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(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue