Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gong-cli-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@outlit/cli": patch
---

Add Gong as a CLI setup provider and route setup calls through Core's Gong OAuth provider key.
16 changes: 12 additions & 4 deletions packages/cli/src/commands/integrations/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
ProviderCredentialType,
ProviderSetupMode,
} from "../../lib/providers"
import { normalizeProviderInput, PROVIDER_NAMES } from "../../lib/providers"
import { normalizeProviderInput, PROVIDER_NAMES, resolveProvider } from "../../lib/providers"
import { createSpinner } from "../../lib/spinner"
import { openBrowser } from "../../lib/tty"
import { waitForIntegrationConnection } from "./wait-for-connection"
Expand Down Expand Up @@ -197,7 +197,7 @@ async function setupApiKeyProvider(

try {
connectData = (await client.callTool("outlit_connect_integration", {
provider: capability.cliName,
provider: resolveCapabilityProviderId(capability),
config,
...(force ? { force: true } : {}),
})) as ConnectResponse
Expand Down Expand Up @@ -242,7 +242,7 @@ async function setupOAuthProvider(

try {
connectData = (await client.callTool("outlit_connect_integration", {
provider: capability.cliName,
provider: resolveCapabilityProviderId(capability),
...(force ? { force: true } : {}),
})) as ConnectResponse
} catch (err) {
Expand Down Expand Up @@ -346,7 +346,7 @@ async function setupProviderFollowUp(

try {
const result = (await client.callTool("outlit_integration_setup_step", {
provider: capability.cliName,
provider: resolveCapabilityProviderId(capability),
step: step.id,
...(config ? { config } : {}),
})) as Record<string, unknown>
Expand Down Expand Up @@ -398,6 +398,14 @@ function defaultSetupMode(authType: ProviderAuthType): ProviderSetupMode {
return authType === "api_key" ? "direct_api_key" : "browser_auth"
}

function resolveCapabilityProviderId(capability: SetupCapability): string {
if (capability.providerId && capability.providerId !== capability.cliName) {
return capability.providerId
}

return resolveProvider(capability.cliName).id
}

async function fetchProviderCapability(
client: OutlitClient,
provider: string,
Expand Down
15 changes: 14 additions & 1 deletion packages/cli/src/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface ProviderPostConnectStep {

export interface ProviderCapability {
cliName: string
/** Public provider ID returned by Core. The CLI should pass cliName/providerId back unchanged. */
/** Public provider ID returned by Core. Internal provider aliases are resolved before setup calls. */
providerId?: string
displayName: string
category: ProviderCategory
Expand All @@ -54,6 +54,7 @@ export const PROVIDER_NAMES = [
"clerk",
"fireflies",
"gmail",
"gong",
"google-calendar",
"google-mail",
"granola",
Expand All @@ -66,9 +67,21 @@ export const PROVIDER_NAMES = [
"supabase",
] as const

const PROVIDER_ID_OVERRIDES: Partial<Record<(typeof PROVIDER_NAMES)[number], string>> = {
gong: "gong-oauth",
}

export function normalizeProviderInput(input: string): string {
return input
.trim()
.toLowerCase()
.replace(/[\s_]+/g, "-")
}

export function resolveProvider(input: string): { cliName: string; id: string } {
const cliName = normalizeProviderInput(input)
return {
cliName,
id: PROVIDER_ID_OVERRIDES[cliName as (typeof PROVIDER_NAMES)[number]] ?? cliName,
}
}
26 changes: 25 additions & 1 deletion packages/cli/tests/commands/integrations/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,13 @@ const mockCallTool = mock(async (toolName: string, params: Record<string, unknow

if (nextConnectResponse) return nextConnectResponse

const connectUrlProvider = params.provider === "gong-oauth" ? "gong" : params.provider

return {
sessionId: "sess_123",
connectionId: `${params.provider}-org_123`,
alreadyConnected: false,
connectUrl: `https://app.outlit.ai/integrations?provider=${params.provider}`,
connectUrl: `https://app.outlit.ai/integrations?provider=${connectUrlProvider}`,
}
})

Expand Down Expand Up @@ -197,6 +199,28 @@ describe("integrations setup", () => {
expect(parsed.nextActions).toContain("outlit integrations status --session sess_123 --json")
})

test("starts Gong OAuth setup through the internal Core provider id", async () => {
const { default: setupCmd } = await import("../../../src/commands/integrations/setup")
const parsed = await captureStdout<{
status: string
provider: string
connectUrl: string
sessionId: string
}>(() =>
setupCmd.run!({
args: { provider: "gong", json: true },
} as Parameters<NonNullable<typeof setupCmd.run>>[0]),
)

expect(mockCallTool).toHaveBeenCalledWith("outlit_connect_integration", {
provider: "gong-oauth",
})
expect(parsed.status).toBe("awaiting_auth")
expect(parsed.provider).toBe("gong")
expect(parsed.connectUrl).toBe("https://app.outlit.ai/integrations?provider=gong")
expect(parsed.sessionId).toBe("sess_123")
})

test("waits for OAuth setup to complete in interactive mode", async () => {
setInteractive()
const logSpy = spyOn(console, "log").mockImplementation(() => {})
Expand Down
13 changes: 11 additions & 2 deletions packages/cli/tests/lib/providers.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { normalizeProviderInput, PROVIDER_NAMES } from "../../src/lib/providers"
import { normalizeProviderInput, PROVIDER_NAMES, resolveProvider } from "../../src/lib/providers"

describe("PROVIDER_NAMES", () => {
test("is sorted alphabetically", () => {
Expand All @@ -11,10 +11,10 @@ describe("PROVIDER_NAMES", () => {
expect(PROVIDER_NAMES).toContain("gmail")
expect(PROVIDER_NAMES).toContain("google-mail")
expect(PROVIDER_NAMES).toContain("stripe")
expect(PROVIDER_NAMES).toContain("gong")
expect(PROVIDER_NAMES).toContain("pylon")
expect(PROVIDER_NAMES).toContain("salesforce")
expect(PROVIDER_NAMES.every((provider) => !provider.endsWith("-api-key"))).toBe(true)
expect(PROVIDER_NAMES).not.toContain("gong")
})
})

Expand All @@ -26,3 +26,12 @@ describe("normalizeProviderInput", () => {
expect(normalizeProviderInput("stripe")).toBe("stripe")
})
})

describe("resolveProvider", () => {
test("maps Gong's public CLI key to Core's internal provider id", () => {
expect(resolveProvider("gong")).toEqual({
cliName: "gong",
id: "gong-oauth",
})
})
})