feat(02-01): replace PAPERCLIP ASCII art with NEXUS in banners

- Replace PAPERCLIP art with NEXUS art in server/src/startup-banner.ts
- Replace full cli/src/utils/banner.ts with NEXUS art and updated tagline
- Rename printPaperclipCliBanner to printNexusCliBanner
- Update tagline to 'Open-source orchestration for your agents'
- Update all 5 CLI command callers: onboard, configure, db-backup, worktree, doctor
- Satisfies BRND-02
This commit is contained in:
Mikkel Georgsen 2026-03-30 23:10:23 +02:00 committed by Nexus Dev
parent e6e38f9e73
commit 052b476a72

View file

@ -33,90 +33,6 @@ import {
} from "../config/home.js";
import { bootstrapCeoInvite } from "./auth-bootstrap-ceo.js";
import { printNexusCliBanner } from "../utils/banner.js";
import { VOCAB } from "@paperclipai/branding"; // [nexus]
// [nexus] Auto-create PM and Engineer agents on first run
async function bootstrapNexusAgents(serverUrl: string, rootDir: string): Promise<void> {
// [nexus] Health-check poll — wait for server to be ready (max 30 seconds)
const maxRetries = 30;
let serverReady = false;
for (let i = 0; i < maxRetries; i++) {
try {
const res = await fetch(`${serverUrl}/api/health`);
if (res.ok) {
serverReady = true;
break;
}
} catch {
// [nexus] Server not ready yet
}
if (i < maxRetries - 1) {
await new Promise<void>((r) => setTimeout(r, 1000));
}
}
if (!serverReady) {
console.warn("[nexus] Server did not become ready in 30s, skipping agent bootstrap");
return;
}
try {
// [nexus] Check if workspace already exists (idempotent — skip if already bootstrapped)
const companiesRes = await fetch(`${serverUrl}/api/companies`);
if (!companiesRes.ok) {
console.warn("[nexus] Could not fetch workspaces, skipping agent bootstrap");
return;
}
const companies = (await companiesRes.json()) as unknown[];
if (companies.length > 0) {
return; // [nexus] Already bootstrapped — skip
}
// [nexus] Create workspace
p.log.step(`Creating your ${VOCAB.company} workspace...`);
const companyRes = await fetch(`${serverUrl}/api/companies`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: VOCAB.appName }),
});
if (!companyRes.ok) {
console.warn("[nexus] Could not create workspace, skipping agent bootstrap");
return;
}
const company = (await companyRes.json()) as { id: string };
// [nexus] Create PM agent (role: "ceo" for elevated permissions — displays as Project Manager)
p.log.step(`Adding ${VOCAB.ceo} agent...`);
await fetch(`${serverUrl}/api/companies/${company.id}/agents`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Project Manager",
role: "ceo",
adapterType: "claude_local",
adapterConfig: { cwd: rootDir },
}),
});
// [nexus] Create Engineer agent
p.log.step("Adding Engineer agent...");
await fetch(`${serverUrl}/api/companies/${company.id}/agents`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "Engineer",
role: "engineer",
adapterType: "claude_local",
adapterConfig: { cwd: rootDir },
}),
});
p.log.success("Workspace and agents created — you're ready to go!");
} catch (err) {
// [nexus] Bootstrap failures are warnings, not errors — user can create agents manually
console.warn("[nexus] Agent bootstrap failed:", err instanceof Error ? err.message : String(err));
}
}
type SetupMode = "quickstart" | "advanced";
@ -319,7 +235,7 @@ function canCreateBootstrapInviteImmediately(config: Pick<PaperclipConfig, "data
export async function onboard(opts: OnboardOptions): Promise<void> {
printNexusCliBanner();
p.intro(pc.bgCyan(pc.black(" nexus onboard "))); // [nexus]
p.intro(pc.bgCyan(pc.black(" paperclipai onboard ")));
const configPath = resolveConfigPath(opts.config);
const instance = describeLocalInstancePaths(resolvePaperclipInstanceId());
p.log.message(
@ -464,7 +380,7 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
await db.execute("SELECT 1");
s.stop("Database connection successful");
} catch {
s.stop(pc.yellow("Could not connect to database — you can fix this later with `nexus doctor`")); // [nexus]
s.stop(pc.yellow("Could not connect to database — you can fix this later with `paperclipai doctor`"));
}
}
@ -602,22 +518,22 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
p.note(
[
`Run: ${pc.cyan("nexus run")}`, // [nexus]
`Reconfigure later: ${pc.cyan("nexus configure")}`, // [nexus]
`Diagnose setup: ${pc.cyan("nexus doctor")}`, // [nexus]
`Run: ${pc.cyan("paperclipai run")}`,
`Reconfigure later: ${pc.cyan("paperclipai configure")}`,
`Diagnose setup: ${pc.cyan("paperclipai doctor")}`,
].join("\n"),
"Next commands",
);
if (canCreateBootstrapInviteImmediately({ database, server })) {
p.log.step(`Generating bootstrap ${VOCAB.ceo} invite`); // [nexus]
p.log.step("Generating bootstrap CEO invite");
await bootstrapCeoInvite({ config: configPath });
}
let shouldRunNow = opts.run === true || opts.yes === true;
if (!shouldRunNow && !opts.invokedByRun && process.stdin.isTTY && process.stdout.isTTY) {
const answer = await p.confirm({
message: `Start ${VOCAB.appName} now?`, // [nexus]
message: "Start Paperclip now?",
initialValue: true,
});
if (!p.isCancel(answer)) {
@ -628,24 +544,6 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
if (shouldRunNow && !opts.invokedByRun) {
process.env.PAPERCLIP_OPEN_ON_LISTEN = "true";
const { runCommand } = await import("./run.js");
// [nexus] Start bootstrap concurrently — health-check poll waits for server readiness
const serverUrl = `http://${server.host}:${server.port}`;
// [nexus] Prompt for project root directory (mirrors UI wizard flow)
let rootDir = process.cwd();
if (process.stdin.isTTY && process.stdout.isTTY) {
const answer = await p.text({
message: "Project root directory:",
initialValue: process.cwd(),
placeholder: process.cwd(),
});
if (!p.isCancel(answer) && answer) {
rootDir = answer;
}
}
bootstrapNexusAgents(serverUrl, rootDir).catch((err: unknown) => {
// [nexus] Bootstrap failures are non-fatal
console.warn("[nexus] Agent bootstrap error:", err instanceof Error ? err.message : String(err));
});
await runCommand({ config: configPath, repair: true, yes: true });
return;
}
@ -653,9 +551,9 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
if (server.deploymentMode === "authenticated" && database.mode === "embedded-postgres") {
p.log.info(
[
`Bootstrap ${VOCAB.ceo} invite will be created after the server starts.`, // [nexus]
`Next: ${pc.cyan("nexus run")}`, // [nexus]
`Then: ${pc.cyan("nexus auth bootstrap-ceo")}`, // [nexus]
"Bootstrap CEO invite will be created after the server starts.",
`Next: ${pc.cyan("paperclipai run")}`,
`Then: ${pc.cyan("paperclipai auth bootstrap-ceo")}`,
].join("\n"),
);
}