From cd2983c7afd192395dc8cfe3ff5efa135bba835f Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Tue, 5 May 2026 02:37:09 +0000 Subject: [PATCH 1/4] feat: detect AI agent harness Co-Authored-By: Claude Opus 4.7 (1M context) --- src/lib/ai.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/lib/ai.ts diff --git a/src/lib/ai.ts b/src/lib/ai.ts new file mode 100644 index 0000000..afc5725 --- /dev/null +++ b/src/lib/ai.ts @@ -0,0 +1,36 @@ +import { exists } from "./file"; + +export async function detectAgent(): Promise { + if (process.env.AI_AGENT) return process.env.AI_AGENT.toLowerCase(); + + if (process.env.CLAUDE_CODE_IS_COWORK === "1" || process.env.CLAUDE_CODE_IS_COWORK === "true") + return "claude-cowork"; + if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE) return "claude-code"; + if (process.env.CODEX_CI === "1" || process.env.CODEX_SANDBOX || process.env.CODEX_THREAD_ID) + return "codex"; + if (process.env.CURSOR_AGENT === "1" || process.env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") + return "cursor"; + if (process.env.CLINE_ACTIVE === "true") return "cline"; + if (process.env.ANTIGRAVITY_AGENT) return "antigravity"; + if (process.env.AUGMENT_AGENT === "1") return "augment"; + if (process.env.OPENCODE_CLIENT === "1") return "opencode"; + if (process.env.GEMINI_CLI === "1") return "gemini-cli"; + if (process.env.TRAE_AI_SHELL_ID) return "trae"; + if (process.env.REPL_ID) return "replit"; + if ( + process.env.COPILOT_MODEL || + process.env.COPILOT_ALLOW_ALL || + process.env.COPILOT_GITHUB_TOKEN + ) + return "github-copilot"; + + const agent = process.env.AGENT?.toLowerCase(); + if (agent === "goose" || agent === "amp") return agent; + + if (process.platform === "linux") { + const isDevin = await exists(new URL("file:///opt/.devin")); + if (isDevin) return "devin"; + } + + if (process.env.IS_SANDBOX === "yes") return "unknown-sandbox"; +} From 461b1b5afaa63e1f7d255a6e2f06dcc69fd4b8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 7 May 2026 09:59:38 +0100 Subject: [PATCH 2/4] chore: fmt --- src/lib/ai.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/ai.ts b/src/lib/ai.ts index afc5725..cfd6efc 100644 --- a/src/lib/ai.ts +++ b/src/lib/ai.ts @@ -3,13 +3,16 @@ import { exists } from "./file"; export async function detectAgent(): Promise { if (process.env.AI_AGENT) return process.env.AI_AGENT.toLowerCase(); - if (process.env.CLAUDE_CODE_IS_COWORK === "1" || process.env.CLAUDE_CODE_IS_COWORK === "true") + if (process.env.CLAUDE_CODE_IS_COWORK === "1" || process.env.CLAUDE_CODE_IS_COWORK === "true") { return "claude-cowork"; + } if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE) return "claude-code"; - if (process.env.CODEX_CI === "1" || process.env.CODEX_SANDBOX || process.env.CODEX_THREAD_ID) + if (process.env.CODEX_CI === "1" || process.env.CODEX_SANDBOX || process.env.CODEX_THREAD_ID) { return "codex"; - if (process.env.CURSOR_AGENT === "1" || process.env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") + } + if (process.env.CURSOR_AGENT === "1" || process.env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec") { return "cursor"; + } if (process.env.CLINE_ACTIVE === "true") return "cline"; if (process.env.ANTIGRAVITY_AGENT) return "antigravity"; if (process.env.AUGMENT_AGENT === "1") return "augment"; @@ -21,8 +24,9 @@ export async function detectAgent(): Promise { process.env.COPILOT_MODEL || process.env.COPILOT_ALLOW_ALL || process.env.COPILOT_GITHUB_TOKEN - ) + ) { return "github-copilot"; + } const agent = process.env.AGENT?.toLowerCase(); if (agent === "goose" || agent === "amp") return agent; From e2cde2649a5c5dc8720a5707b6e23780a3486b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 7 May 2026 14:28:48 +0100 Subject: [PATCH 3/4] feat: track CLI channel and AI agent on repo creation Pass app=cli and detected AI agent (Claude Code, Cursor, etc.) as query params when creating a repository so the server-side Repository Created event can distinguish CLI from UI and attribute which harness triggered it. Attach the agent to Prismic CLI Start/End events as well. --- src/clients/wroom.ts | 5 ++++- src/commands/repo-create.ts | 4 +++- src/tracking.ts | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/clients/wroom.ts b/src/clients/wroom.ts index f148cdd..408b5f0 100644 --- a/src/clients/wroom.ts +++ b/src/clients/wroom.ts @@ -320,11 +320,14 @@ export async function createRepository(config: { domain: string; name: string; framework: string; + agent: string | undefined; token: string | undefined; host: string; }): Promise { - const { domain, name, framework, token, host } = config; + const { domain, name, framework, agent, token, host } = config; const url = new URL("app/dashboard/repositories", getDashboardUrl(host)); + url.searchParams.set("app", "cli"); + if (agent) url.searchParams.set("agent", agent); await request(url, { method: "POST", body: { domain, name, framework, plan: "personal" }, diff --git a/src/commands/repo-create.ts b/src/commands/repo-create.ts index 3e66ef7..61ad97c 100644 --- a/src/commands/repo-create.ts +++ b/src/commands/repo-create.ts @@ -1,6 +1,7 @@ import { getAdapter } from "../adapters"; import { getHost, getToken } from "../auth"; import { checkIsDomainAvailable, createRepository } from "../clients/wroom"; +import { detectAgent } from "../lib/ai"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { UnknownRequestError } from "../lib/request"; @@ -39,9 +40,10 @@ export async function createRepo(config: { const adapter = await getAdapter().catch(() => undefined); const framework = adapter?.id ?? "other"; + const agent = await detectAgent(); try { - await createRepository({ domain, name: name ?? domain, framework, token, host }); + await createRepository({ domain, name: name ?? domain, framework, agent, token, host }); } catch (error) { if (error instanceof UnknownRequestError) { const message = await error.text(); diff --git a/src/tracking.ts b/src/tracking.ts index ede63f6..f7b6486 100644 --- a/src/tracking.ts +++ b/src/tracking.ts @@ -5,6 +5,7 @@ import * as z from "zod/mini"; import type { Profile } from "./clients/user"; import { DEFAULT_PRISMIC_HOST, env } from "./env"; +import { detectAgent } from "./lib/ai"; import { readJsonFile } from "./lib/file"; import { initSegment, trackEvent, trackIdentity } from "./lib/segment"; import { appendTrailingSlash } from "./lib/url"; @@ -13,6 +14,7 @@ const PROD_WRITE_KEY = "cGjidifKefYb6EPaGaqpt8rQXkv5TD6P"; const STAGING_WRITE_KEY = "Ng5oKJHCGpSWplZ9ymB7Pu7rm0sTDeiG"; let repository: string | undefined; +let agent: string | undefined; export async function initTracking(config: { host: string }): Promise { if (env.TEST) return; @@ -20,6 +22,7 @@ export async function initTracking(config: { host: string }): Promise { const enabled = await isTelemetryEnabled(); if (!enabled) return; const writeKey = host === DEFAULT_PRISMIC_HOST ? PROD_WRITE_KEY : STAGING_WRITE_KEY; + agent = await detectAgent(); await initSegment({ writeKey }); } @@ -39,6 +42,7 @@ export function trackCommandStart(command: string, config: { watch?: boolean } = fullCommand: process.argv.join(" "), repository, watch, + agent, }, groupId: repository ? { Repository: repository } : undefined, }); @@ -58,6 +62,7 @@ export function trackCommandEnd( repository, watch, error: errorMessage?.slice(0, 512), + agent, }, groupId: repository ? { Repository: repository } : undefined, }); From a7d6e782d64a4791978b6e3c1cf19f00168ee534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 7 May 2026 15:09:25 +0100 Subject: [PATCH 4/4] chore: gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f55047d..31f393c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ coverage !.vscode/tasks.json !.vscode/launch.json *.code-workspace + +.claude