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 type { CompanyPortabilityPreviewResult } from "@paperclipai/shared";
import {
buildCompanyDashboardUrl,
buildDefaultImportAdapterOverrides,
buildDefaultImportSelectionState,
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", () => {
it("summarizes the preview with counts, selection info, and truncated examples", () => {
const preview: CompanyPortabilityPreviewResult = {
@@ -155,6 +164,10 @@ describe("renderCompanyImportPreview", () => {
logoPath: null,
requireBoardApprovalForNewAgents: false,
},
sidebar: {
agents: ["ceo"],
projects: ["alpha"],
},
agents: [
{
slug: "ceo",
@@ -291,16 +304,19 @@ describe("renderCompanyImportResult", () => {
{ slug: "cto", id: "agent-2", action: "updated", name: "CTO", reason: "replace strategy" },
{ slug: "ops", id: null, action: "skipped", name: "Ops", reason: "skip strategy" },
],
projects: [],
envInputs: [],
warnings: ["Review API keys"],
},
{
targetLabel: "Imported Co (company-123)",
companyUrl: "https://paperclip.example/PAP/dashboard",
infoMessages: ["Using claude-local adapter"],
},
);
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("Agent results");
expect(rendered).toContain("Using claude-local adapter");
@@ -350,6 +366,10 @@ describe("import selection catalog", () => {
logoPath: "images/company-logo.png",
requireBoardApprovalForNewAgents: false,
},
sidebar: {
agents: ["ceo"],
projects: ["alpha"],
},
agents: [
{
slug: "ceo",
@@ -504,6 +524,7 @@ describe("default adapter overrides", () => {
skills: false,
},
company: null,
sidebar: null,
agents: [
{
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>;
}
function openUrl(url: string): boolean {
export function openUrl(url: string): boolean {
const platform = process.platform;
try {
if (platform === "darwin") {

View File

@@ -12,6 +12,7 @@ import type {
CompanyPortabilityImportResult,
} from "@paperclipai/shared";
import { ApiRequestError } from "../../client/http.js";
import { openUrl } from "../../client/board-auth.js";
import { readZipArchive } from "./zip.js";
import {
addCommonClientOptions,
@@ -654,7 +655,7 @@ export function renderCompanyImportPreview(
export function renderCompanyImportResult(
result: CompanyPortabilityImportResult,
meta: { targetLabel: string; infoMessages?: string[] },
meta: { targetLabel: string; companyUrl?: string; infoMessages?: string[] },
): string {
const lines: string[] = [
`${pc.bold("Target")} ${meta.targetLabel}`,
@@ -662,6 +663,10 @@ export function renderCompanyImportResult(
`${pc.bold("Agents")} ${summarizeImportAgentResults(result.agents)}`,
];
if (meta.companyUrl) {
lines.splice(1, 0, `${pc.bold("URL")} ${meta.companyUrl}`);
}
appendPreviewExamples(
lines,
"Agent results",
@@ -713,6 +718,15 @@ export function resolveCompanyImportApiPath(input: {
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: {
yes?: boolean;
interactive: boolean;
@@ -1298,6 +1312,18 @@ export function registerCompanyCommands(program: Command): void {
if (!imported) {
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) {
printOutput(imported, { json: true });
} else {
@@ -1305,10 +1331,24 @@ export function registerCompanyCommands(program: Command): void {
"Import Result",
renderCompanyImportResult(imported, {
targetLabel,
companyUrl,
infoMessages: adapterMessages,
}),
{ 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) {
handleCommandError(err);