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:
parent
e6e38f9e73
commit
052b476a72
1 changed files with 10 additions and 112 deletions
|
|
@ -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"),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue