Open imported company after import

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-03-23 16:41:52 -05:00
parent f9927bdaaa
commit b5fde733b0
3 changed files with 63 additions and 2 deletions

View file

@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { CompanyPortabilityPreviewResult } from "@paperclipai/shared"; import type { CompanyPortabilityPreviewResult } from "@paperclipai/shared";
import { import {
buildCompanyDashboardUrl,
buildDefaultImportAdapterOverrides, buildDefaultImportAdapterOverrides,
buildDefaultImportSelectionState, buildDefaultImportSelectionState,
buildImportSelectionCatalog, buildImportSelectionCatalog,
@ -101,6 +102,14 @@ describe("resolveCompanyImportApplyConfirmationMode", () => {
}); });
}); });
describe("buildCompanyDashboardUrl", () => {
it("preserves the configured base path when building a dashboard URL", () => {
expect(buildCompanyDashboardUrl("https://paperclip.example/app/", "PAP")).toBe(
"https://paperclip.example/app/PAP/dashboard",
);
});
});
describe("renderCompanyImportPreview", () => { describe("renderCompanyImportPreview", () => {
it("summarizes the preview with counts, selection info, and truncated examples", () => { it("summarizes the preview with counts, selection info, and truncated examples", () => {
const preview: CompanyPortabilityPreviewResult = { const preview: CompanyPortabilityPreviewResult = {
@ -155,6 +164,10 @@ describe("renderCompanyImportPreview", () => {
logoPath: null, logoPath: null,
requireBoardApprovalForNewAgents: false, requireBoardApprovalForNewAgents: false,
}, },
sidebar: {
agents: ["ceo"],
projects: ["alpha"],
},
agents: [ agents: [
{ {
slug: "ceo", slug: "ceo",
@ -291,16 +304,19 @@ describe("renderCompanyImportResult", () => {
{ slug: "cto", id: "agent-2", action: "updated", name: "CTO", reason: "replace strategy" }, { slug: "cto", id: "agent-2", action: "updated", name: "CTO", reason: "replace strategy" },
{ slug: "ops", id: null, action: "skipped", name: "Ops", reason: "skip strategy" }, { slug: "ops", id: null, action: "skipped", name: "Ops", reason: "skip strategy" },
], ],
projects: [],
envInputs: [], envInputs: [],
warnings: ["Review API keys"], warnings: ["Review API keys"],
}, },
{ {
targetLabel: "Imported Co (company-123)", targetLabel: "Imported Co (company-123)",
companyUrl: "https://paperclip.example/PAP/dashboard",
infoMessages: ["Using claude-local adapter"], infoMessages: ["Using claude-local adapter"],
}, },
); );
expect(rendered).toContain("Company"); expect(rendered).toContain("Company");
expect(rendered).toContain("https://paperclip.example/PAP/dashboard");
expect(rendered).toContain("3 agents total (1 created, 1 updated, 1 skipped)"); expect(rendered).toContain("3 agents total (1 created, 1 updated, 1 skipped)");
expect(rendered).toContain("Agent results"); expect(rendered).toContain("Agent results");
expect(rendered).toContain("Using claude-local adapter"); expect(rendered).toContain("Using claude-local adapter");
@ -350,6 +366,10 @@ describe("import selection catalog", () => {
logoPath: "images/company-logo.png", logoPath: "images/company-logo.png",
requireBoardApprovalForNewAgents: false, requireBoardApprovalForNewAgents: false,
}, },
sidebar: {
agents: ["ceo"],
projects: ["alpha"],
},
agents: [ agents: [
{ {
slug: "ceo", slug: "ceo",
@ -504,6 +524,7 @@ describe("default adapter overrides", () => {
skills: false, skills: false,
}, },
company: null, company: null,
sidebar: null,
agents: [ agents: [
{ {
slug: "legacy-agent", slug: "legacy-agent",

View file

@ -169,7 +169,7 @@ async function requestJson<T>(url: string, init?: RequestInit): Promise<T> {
return response.json() as Promise<T>; return response.json() as Promise<T>;
} }
function openUrl(url: string): boolean { export function openUrl(url: string): boolean {
const platform = process.platform; const platform = process.platform;
try { try {
if (platform === "darwin") { if (platform === "darwin") {

View file

@ -12,6 +12,7 @@ import type {
CompanyPortabilityImportResult, CompanyPortabilityImportResult,
} from "@paperclipai/shared"; } from "@paperclipai/shared";
import { ApiRequestError } from "../../client/http.js"; import { ApiRequestError } from "../../client/http.js";
import { openUrl } from "../../client/board-auth.js";
import { readZipArchive } from "./zip.js"; import { readZipArchive } from "./zip.js";
import { import {
addCommonClientOptions, addCommonClientOptions,
@ -654,7 +655,7 @@ export function renderCompanyImportPreview(
export function renderCompanyImportResult( export function renderCompanyImportResult(
result: CompanyPortabilityImportResult, result: CompanyPortabilityImportResult,
meta: { targetLabel: string; infoMessages?: string[] }, meta: { targetLabel: string; companyUrl?: string; infoMessages?: string[] },
): string { ): string {
const lines: string[] = [ const lines: string[] = [
`${pc.bold("Target")} ${meta.targetLabel}`, `${pc.bold("Target")} ${meta.targetLabel}`,
@ -662,6 +663,10 @@ export function renderCompanyImportResult(
`${pc.bold("Agents")} ${summarizeImportAgentResults(result.agents)}`, `${pc.bold("Agents")} ${summarizeImportAgentResults(result.agents)}`,
]; ];
if (meta.companyUrl) {
lines.splice(1, 0, `${pc.bold("URL")} ${meta.companyUrl}`);
}
appendPreviewExamples( appendPreviewExamples(
lines, lines,
"Agent results", "Agent results",
@ -713,6 +718,15 @@ export function resolveCompanyImportApiPath(input: {
return input.dryRun ? "/api/companies/import/preview" : "/api/companies/import"; return input.dryRun ? "/api/companies/import/preview" : "/api/companies/import";
} }
export function buildCompanyDashboardUrl(apiBase: string, issuePrefix: string): string {
const url = new URL(apiBase);
const normalizedPrefix = issuePrefix.trim().replace(/^\/+|\/+$/g, "");
url.pathname = `${url.pathname.replace(/\/+$/, "")}/${normalizedPrefix}/dashboard`;
url.search = "";
url.hash = "";
return url.toString();
}
export function resolveCompanyImportApplyConfirmationMode(input: { export function resolveCompanyImportApplyConfirmationMode(input: {
yes?: boolean; yes?: boolean;
interactive: boolean; interactive: boolean;
@ -1298,6 +1312,18 @@ export function registerCompanyCommands(program: Command): void {
if (!imported) { if (!imported) {
throw new Error("Import request returned no data."); throw new Error("Import request returned no data.");
} }
let companyUrl: string | undefined;
if (!ctx.json) {
try {
const importedCompany = await ctx.api.get<Company>(`/api/companies/${imported.company.id}`);
const issuePrefix = importedCompany?.issuePrefix?.trim();
if (issuePrefix) {
companyUrl = buildCompanyDashboardUrl(ctx.api.apiBase, issuePrefix);
}
} catch {
companyUrl = undefined;
}
}
if (ctx.json) { if (ctx.json) {
printOutput(imported, { json: true }); printOutput(imported, { json: true });
} else { } else {
@ -1305,10 +1331,24 @@ export function registerCompanyCommands(program: Command): void {
"Import Result", "Import Result",
renderCompanyImportResult(imported, { renderCompanyImportResult(imported, {
targetLabel, targetLabel,
companyUrl,
infoMessages: adapterMessages, infoMessages: adapterMessages,
}), }),
{ interactive: interactiveView }, { interactive: interactiveView },
); );
if (interactiveView && companyUrl) {
const openImportedCompany = await p.confirm({
message: "Open the imported company in your browser?",
initialValue: true,
});
if (!p.isCancel(openImportedCompany) && openImportedCompany) {
if (openUrl(companyUrl)) {
p.log.info(`Opened ${companyUrl}`);
} else {
p.log.warn(`Could not open your browser automatically. Open this URL manually:\n${companyUrl}`);
}
}
}
} }
} catch (err) { } catch (err) {
handleCommandError(err); handleCommandError(err);