Skip to content

Provider Integrations

Use the provider integration surface when Pets data is needed from an app runtime rather than an interactive terminal. This is the recommended path for Raycast, Alfred, VS Code extensions, desktop apps, and agent launchers that may not inherit a login-shell PATH.

Intro Prompt

pets intro writes the project prompt to stdout and has no clipboard side effects by default:

bash
pets intro

Use --copy only for terminal convenience:

bash
pets intro --copy

Use --json when a provider needs structured data:

bash
pets intro --json

The JSON payload includes:

json
{
  "schemaVersion": 1,
  "cwd": "/Users/alice/src/app",
  "generatedAt": "2026-05-25T09:00:00.000Z",
  "prompt": "You have access to the Pets CLI...",
  "warnings": [],
  "configuredPlugins": ["github"],
  "missingConfiguration": [],
  "plugins": [],
  "totalTools": 12
}

Providers should copy prompt with their own clipboard or UI APIs.

Importable API

Providers that can depend on the openpets package should import the API instead of shelling out:

ts
import { buildIntroPrompt, listTools, listPrompts } from "openpets"

const intro = await buildIntroPrompt({ cwd: projectFolder })
await Clipboard.copy(intro.prompt)

const tools = await listTools({ cwd: projectFolder })
const prompts = await listPrompts({ cwd: projectFolder })

These helpers run inside the provider process, so they do not require pets or bun to be discoverable on the app process PATH.

schemaVersion is stable for the provider contract. Use generatedAt and cwd for cache keys and diagnostics, not for prompt display.

Raycast-Style Example

ts
import { Clipboard, showToast, Toast } from "@raycast/api"
import { buildIntroPrompt } from "openpets"

export default async function Command() {
  try {
    const projectFolder = "/Users/alice/src/app"
    const { prompt, warnings } = await buildIntroPrompt({ cwd: projectFolder })

    await Clipboard.copy(prompt)

    await showToast({
      style: warnings.length > 0 ? Toast.Style.Failure : Toast.Style.Success,
      title: warnings.length > 0 ? "Copied with warnings" : "Copied Pets prompt",
      message: warnings[0]
    })
  } catch (error) {
    await showToast({
      style: Toast.Style.Failure,
      title: "Could not load Pets prompt",
      message: error instanceof Error ? error.message : String(error)
    })
  }
}

Subprocess Fallback

If a provider cannot import the main API, use the bundled JSON runner so errors are classified consistently:

ts
import { ProviderIntegrationError, runPetsJson } from "openpets"

try {
  const intro = await runPetsJson({
    cwd: projectFolder,
    petsBin: "/absolute/path/to/pets",
    args: ["intro", "--json"],
    env: {
      ...process.env,
      PATH: "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
    }
  })

  await Clipboard.copy(intro.prompt)
} catch (error) {
  if (error instanceof ProviderIntegrationError) {
    if (error.code === "PETS_NOT_FOUND") {
      // Ask the user to configure an absolute Pets launcher path.
    } else if (error.code === "BUN_NOT_FOUND") {
      // The launcher was found, but its Bun runtime was not.
    } else if (error.code === "INVALID_JSON") {
      // The command probably missed --json or printed unexpected output.
    }
  }
}

If a provider cannot import any openpets helpers, prefer stdout-first commands and resolve executables explicitly:

ts
import { execFile } from "node:child_process"
import { promisify } from "node:util"

const execFileAsync = promisify(execFile)

const { stdout } = await execFileAsync("/absolute/path/to/pets", ["intro", "--json"], {
  cwd: projectFolder,
  env: {
    ...process.env,
    PATH: "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
  }
})

const intro = JSON.parse(stdout)

When reporting failures, distinguish the launcher that failed. ENOENT for pets means the Pets launcher was not found. ENOENT for bun means a launcher script found Pets but could not find Bun. A JSON parse error usually means the subprocess printed human-readable output instead of --json.