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";
|
} from "../config/home.js";
|
||||||
import { bootstrapCeoInvite } from "./auth-bootstrap-ceo.js";
|
import { bootstrapCeoInvite } from "./auth-bootstrap-ceo.js";
|
||||||
import { printNexusCliBanner } from "../utils/banner.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";
|
type SetupMode = "quickstart" | "advanced";
|
||||||
|
|
||||||
|
|
@ -319,7 +235,7 @@ function canCreateBootstrapInviteImmediately(config: Pick<PaperclipConfig, "data
|
||||||
|
|
||||||
export async function onboard(opts: OnboardOptions): Promise<void> {
|
export async function onboard(opts: OnboardOptions): Promise<void> {
|
||||||
printNexusCliBanner();
|
printNexusCliBanner();
|
||||||
p.intro(pc.bgCyan(pc.black(" nexus onboard "))); // [nexus]
|
p.intro(pc.bgCyan(pc.black(" paperclipai onboard ")));
|
||||||
const configPath = resolveConfigPath(opts.config);
|
const configPath = resolveConfigPath(opts.config);
|
||||||
const instance = describeLocalInstancePaths(resolvePaperclipInstanceId());
|
const instance = describeLocalInstancePaths(resolvePaperclipInstanceId());
|
||||||
p.log.message(
|
p.log.message(
|
||||||
|
|
@ -464,7 +380,7 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
|
||||||
await db.execute("SELECT 1");
|
await db.execute("SELECT 1");
|
||||||
s.stop("Database connection successful");
|
s.stop("Database connection successful");
|
||||||
} catch {
|
} 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(
|
p.note(
|
||||||
[
|
[
|
||||||
`Run: ${pc.cyan("nexus run")}`, // [nexus]
|
`Run: ${pc.cyan("paperclipai run")}`,
|
||||||
`Reconfigure later: ${pc.cyan("nexus configure")}`, // [nexus]
|
`Reconfigure later: ${pc.cyan("paperclipai configure")}`,
|
||||||
`Diagnose setup: ${pc.cyan("nexus doctor")}`, // [nexus]
|
`Diagnose setup: ${pc.cyan("paperclipai doctor")}`,
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Next commands",
|
"Next commands",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (canCreateBootstrapInviteImmediately({ database, server })) {
|
if (canCreateBootstrapInviteImmediately({ database, server })) {
|
||||||
p.log.step(`Generating bootstrap ${VOCAB.ceo} invite`); // [nexus]
|
p.log.step("Generating bootstrap CEO invite");
|
||||||
await bootstrapCeoInvite({ config: configPath });
|
await bootstrapCeoInvite({ config: configPath });
|
||||||
}
|
}
|
||||||
|
|
||||||
let shouldRunNow = opts.run === true || opts.yes === true;
|
let shouldRunNow = opts.run === true || opts.yes === true;
|
||||||
if (!shouldRunNow && !opts.invokedByRun && process.stdin.isTTY && process.stdout.isTTY) {
|
if (!shouldRunNow && !opts.invokedByRun && process.stdin.isTTY && process.stdout.isTTY) {
|
||||||
const answer = await p.confirm({
|
const answer = await p.confirm({
|
||||||
message: `Start ${VOCAB.appName} now?`, // [nexus]
|
message: "Start Paperclip now?",
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (!p.isCancel(answer)) {
|
if (!p.isCancel(answer)) {
|
||||||
|
|
@ -628,24 +544,6 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
|
||||||
if (shouldRunNow && !opts.invokedByRun) {
|
if (shouldRunNow && !opts.invokedByRun) {
|
||||||
process.env.PAPERCLIP_OPEN_ON_LISTEN = "true";
|
process.env.PAPERCLIP_OPEN_ON_LISTEN = "true";
|
||||||
const { runCommand } = await import("./run.js");
|
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 });
|
await runCommand({ config: configPath, repair: true, yes: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -653,9 +551,9 @@ export async function onboard(opts: OnboardOptions): Promise<void> {
|
||||||
if (server.deploymentMode === "authenticated" && database.mode === "embedded-postgres") {
|
if (server.deploymentMode === "authenticated" && database.mode === "embedded-postgres") {
|
||||||
p.log.info(
|
p.log.info(
|
||||||
[
|
[
|
||||||
`Bootstrap ${VOCAB.ceo} invite will be created after the server starts.`, // [nexus]
|
"Bootstrap CEO invite will be created after the server starts.",
|
||||||
`Next: ${pc.cyan("nexus run")}`, // [nexus]
|
`Next: ${pc.cyan("paperclipai run")}`,
|
||||||
`Then: ${pc.cyan("nexus auth bootstrap-ceo")}`, // [nexus]
|
`Then: ${pc.cyan("paperclipai auth bootstrap-ceo")}`,
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue