From 4aec845cb6904491a1e13af598e026f978efc9e5 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Tue, 9 Jun 2026 00:12:07 +0800 Subject: [PATCH 1/2] chore(cli): record resolved output_format on cli_command_executed --- apps/cli-go/cmd/db.go | 3 +++ apps/cli-go/cmd/root.go | 5 +++-- apps/cli-go/internal/telemetry/events.go | 11 ++++++++--- .../telemetry/legacy-command-instrumentation.ts | 8 ++++++-- .../legacy-command-instrumentation.unit.test.ts | 10 ++++++++++ .../cli/src/next/auth/platform-api.layer.unit.test.ts | 2 ++ .../src/shared/telemetry/command-instrumentation.ts | 7 +++++-- .../telemetry/command-instrumentation.unit.test.ts | 7 +++++++ apps/cli/src/shared/telemetry/event-catalog.ts | 1 + 9 files changed, 45 insertions(+), 9 deletions(-) diff --git a/apps/cli-go/cmd/db.go b/apps/cli-go/cmd/db.go index 223f4ec63b..adb526c4f3 100644 --- a/apps/cli-go/cmd/db.go +++ b/apps/cli-go/cmd/db.go @@ -308,6 +308,9 @@ without the envelope.`, outputFormat = "table" } } + // db query resolves --output into a command-local flag, so mirror the + // resolved value onto the global that telemetry's output_format reads. + utils.OutputFormat.Value = outputFormat if flag := cmd.Flags().Lookup("linked"); flag != nil && flag.Changed { return query.RunLinked(cmd.Context(), sql, flags.ProjectRef, outputFormat, agentMode, os.Stdout) } diff --git a/apps/cli-go/cmd/root.go b/apps/cli-go/cmd/root.go index 3fc341b370..60cc3a0b71 100644 --- a/apps/cli-go/cmd/root.go +++ b/apps/cli-go/cmd/root.go @@ -175,8 +175,9 @@ func Execute() { if service := telemetry.FromContext(executedCmd.Context()); service != nil { ensureProjectGroupsCached(executedCmd.Context(), service) _ = service.Capture(executedCmd.Context(), telemetry.EventCommandExecuted, map[string]any{ - telemetry.PropExitCode: exitCode(err), - telemetry.PropDurationMs: time.Since(startedAt).Milliseconds(), + telemetry.PropExitCode: exitCode(err), + telemetry.PropDurationMs: time.Since(startedAt).Milliseconds(), + telemetry.PropOutputFormat: utils.OutputFormat.Value, }, nil) _ = service.Close() } diff --git a/apps/cli-go/internal/telemetry/events.go b/apps/cli-go/internal/telemetry/events.go index 524f1e759b..2ee362dc17 100644 --- a/apps/cli-go/internal/telemetry/events.go +++ b/apps/cli-go/internal/telemetry/events.go @@ -11,9 +11,10 @@ import "context" const ( // - EventCommandExecuted: sent after a CLI command finishes, whether it // succeeds or fails. This helps measure command usage, failure rates, and - // runtime. Event-specific properties are PropExitCode (process exit code) - // and PropDurationMs (command runtime in milliseconds). Related groups: - // none added directly by this event. + // runtime. Event-specific properties are PropExitCode (process exit code), + // PropDurationMs (command runtime in milliseconds), and PropOutputFormat + // (the resolved output format the command used). Related groups: none + // added directly by this event. EventCommandExecuted = "cli_command_executed" // - EventProjectLinked: sent after the local CLI directory is linked to a // Supabase project. This helps measure project-linking adoption and connect @@ -109,6 +110,10 @@ const ( PropExitCode = "exit_code" // PropDurationMs is the command runtime in milliseconds. PropDurationMs = "duration_ms" + // PropOutputFormat is the resolved output format the command used (for + // example "pretty", "json", "yaml"), after agent auto-detection and any + // command-local override are applied. + PropOutputFormat = "output_format" ) // Group identifiers associate events with higher-level entities in PostHog. diff --git a/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts b/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts index b89fb45906..3cff665683 100644 --- a/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts +++ b/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.ts @@ -4,12 +4,14 @@ import { getCommandRuntimeCommand, getCommandRuntimeSpanName, } from "../../shared/runtime/command-runtime.service.ts"; +import { Output } from "../../shared/output/output.service.ts"; import { withAnalyticsContext } from "../../shared/telemetry/analytics-context.ts"; import { Analytics } from "../../shared/telemetry/analytics.service.ts"; import { EventCommandExecuted, PropDurationMs, PropExitCode, + PropOutputFormat, } from "../../shared/telemetry/event-catalog.ts"; interface LegacyCommandInstrumentationOptions = never> { @@ -114,6 +116,7 @@ function withLegacyCommandAnalyticsImplementation( self: Effect.Effect, -) => Effect.Effect; +) => Effect.Effect; export function withLegacyCommandInstrumentation>( options: LegacyCommandInstrumentationOptions, ): ( self: Effect.Effect, -) => Effect.Effect; +) => Effect.Effect; export function withLegacyCommandInstrumentation>( options?: LegacyCommandInstrumentationOptions, ) { diff --git a/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.unit.test.ts b/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.unit.test.ts index 52c002f60c..05e50a801e 100644 --- a/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.unit.test.ts +++ b/apps/cli/src/legacy/telemetry/legacy-command-instrumentation.unit.test.ts @@ -4,6 +4,7 @@ import { commandRuntimeLayer } from "../../shared/runtime/command-runtime.layer. import { CurrentAnalyticsContext } from "../../shared/telemetry/analytics-context.ts"; import { Analytics } from "../../shared/telemetry/analytics.service.ts"; import { withLegacyCommandInstrumentation } from "./legacy-command-instrumentation.ts"; +import { mockOutput } from "../../../tests/helpers/mocks.ts"; function mockContextualAnalytics() { const captured: Array<{ @@ -46,6 +47,7 @@ describe("withLegacyCommandInstrumentation", () => { }).pipe( withLegacyCommandInstrumentation(), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["backups", "list"]), @@ -73,6 +75,7 @@ describe("withLegacyCommandInstrumentation", () => { flags: { projectRef: Option.some("abcdefghijklmnopqrst") }, }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["secrets", "list", "--project-ref", "abcdefghijklmnopqrst"]), @@ -99,6 +102,7 @@ describe("withLegacyCommandInstrumentation", () => { flags: { envFile: Option.some("/path/to/.env") }, }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["secrets", "set", "--env-file=/path/to/.env"]), @@ -125,6 +129,7 @@ describe("withLegacyCommandInstrumentation", () => { }, }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed([ @@ -157,6 +162,7 @@ describe("withLegacyCommandInstrumentation", () => { safeFlags: ["project-ref"], }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["link", "--project-ref", "abcdefghijklmnopqrst"]), @@ -180,6 +186,7 @@ describe("withLegacyCommandInstrumentation", () => { return Effect.void.pipe( withLegacyCommandInstrumentation({ flags: {} }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide(Stdio.layerTest({ args: Effect.succeed(["backups", "list"]) })), Effect.provide(commandRuntimeLayer(["backups", "list"])), Effect.tap(() => @@ -196,6 +203,7 @@ describe("withLegacyCommandInstrumentation", () => { return withLegacyCommandInstrumentation()(Effect.fail(new Error("boom"))).pipe( Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide(Stdio.layerTest({ args: Effect.succeed(["backups", "list"]) })), Effect.provide(commandRuntimeLayer(["backups", "list"])), Effect.exit, @@ -215,6 +223,7 @@ describe("withLegacyCommandInstrumentation", () => { return Effect.sync(() => "ok").pipe( withLegacyCommandInstrumentation({ analytics: false }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide(Stdio.layerTest({ args: Effect.succeed(["telemetry", "enable"]) })), Effect.provide(commandRuntimeLayer(["telemetry", "enable"])), Effect.tap(() => @@ -236,6 +245,7 @@ describe("withLegacyCommandInstrumentation", () => { }, }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed([ diff --git a/apps/cli/src/next/auth/platform-api.layer.unit.test.ts b/apps/cli/src/next/auth/platform-api.layer.unit.test.ts index ec8a0bfde6..0004e26729 100644 --- a/apps/cli/src/next/auth/platform-api.layer.unit.test.ts +++ b/apps/cli/src/next/auth/platform-api.layer.unit.test.ts @@ -13,6 +13,7 @@ import { withCommandInstrumentation } from "../../shared/telemetry/command-instr import { Credentials } from "./credentials.service.ts"; import { PlatformApi } from "./platform-api.service.ts"; import { makePlatformApiServices } from "./platform-api.layer.ts"; +import { mockOutput } from "../../../tests/helpers/mocks.ts"; function httpClientLayer( handler: ( @@ -243,6 +244,7 @@ describe("platformApiLayer", () => { Effect.provide(layer), Effect.provide(runtimeLayer), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["branches", "list"]), diff --git a/apps/cli/src/shared/telemetry/command-instrumentation.ts b/apps/cli/src/shared/telemetry/command-instrumentation.ts index d0495ec847..1b77bc35a3 100644 --- a/apps/cli/src/shared/telemetry/command-instrumentation.ts +++ b/apps/cli/src/shared/telemetry/command-instrumentation.ts @@ -4,6 +4,7 @@ import { getCommandRuntimeCommand, getCommandRuntimeSpanName, } from "../runtime/command-runtime.service.ts"; +import { Output } from "../output/output.service.ts"; import { withAnalyticsContext } from "./analytics-context.ts"; import { Analytics } from "./analytics.service.ts"; @@ -99,6 +100,7 @@ function withCommandAnalyticsImplementation( self: Effect.Effect, -) => Effect.Effect; +) => Effect.Effect; export function withCommandInstrumentation>( options: CommandInstrumentationOptions, ): ( self: Effect.Effect, -) => Effect.Effect; +) => Effect.Effect; export function withCommandInstrumentation>( options?: CommandInstrumentationOptions, ) { diff --git a/apps/cli/src/shared/telemetry/command-instrumentation.unit.test.ts b/apps/cli/src/shared/telemetry/command-instrumentation.unit.test.ts index 541f1b3209..13b6f6d999 100644 --- a/apps/cli/src/shared/telemetry/command-instrumentation.unit.test.ts +++ b/apps/cli/src/shared/telemetry/command-instrumentation.unit.test.ts @@ -4,6 +4,7 @@ import { commandRuntimeLayer } from "../runtime/command-runtime.layer.ts"; import { CurrentAnalyticsContext } from "./analytics-context.ts"; import { Analytics } from "./analytics.service.ts"; import { withCommandInstrumentation } from "./command-instrumentation.ts"; +import { mockOutput } from "../../../tests/helpers/mocks.ts"; function mockContextualAnalytics() { const captured: Array<{ @@ -46,6 +47,7 @@ describe("withCommandInstrumentation", () => { }).pipe( withCommandInstrumentation({ analytics: false }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["branches", "list"]), @@ -68,6 +70,7 @@ describe("withCommandInstrumentation", () => { }).pipe( withCommandInstrumentation(), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["start", "--detach", "--exclude=auth"]), @@ -99,6 +102,7 @@ describe("withCommandInstrumentation", () => { const program = withCommandInstrumentation()(Effect.fail(new Error("boom"))).pipe( Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["login"]), @@ -133,6 +137,7 @@ describe("withCommandInstrumentation", () => { allowedFlagValues: ["exclude", "mode", "stack"], }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed([ @@ -177,6 +182,7 @@ describe("withCommandInstrumentation", () => { allowedFlagValues: ["token", "name", "noBrowser"], }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["login", "--name", "my-machine", "--no-browser"]), @@ -202,6 +208,7 @@ describe("withCommandInstrumentation", () => { return Effect.sync(() => "ok").pipe( withCommandInstrumentation({ analytics: false }), Effect.provide(analytics.layer), + Effect.provide(mockOutput({ format: "text" }).layer), Effect.provide( Stdio.layerTest({ args: Effect.succeed(["telemetry", "enable"]), diff --git a/apps/cli/src/shared/telemetry/event-catalog.ts b/apps/cli/src/shared/telemetry/event-catalog.ts index f85f44e2bd..f35296517e 100644 --- a/apps/cli/src/shared/telemetry/event-catalog.ts +++ b/apps/cli/src/shared/telemetry/event-catalog.ts @@ -28,6 +28,7 @@ export const PropCommand = "command"; export const PropFlags = "flags"; export const PropExitCode = "exit_code"; export const PropDurationMs = "duration_ms"; +export const PropOutputFormat = "output_format"; export const GroupOrganization = "organization"; export const GroupProject = "project"; From 5555f265b5c65143d80476e6813babb35421a793 Mon Sep 17 00:00:00 2001 From: Pamela Chia Date: Tue, 9 Jun 2026 00:14:54 +0800 Subject: [PATCH 2/2] chore(cli): is_agent no longer classifies CI as an agent (Go/TS parity) --- apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts b/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts index da0062158d..84f3f43ee1 100644 --- a/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts +++ b/apps/cli/src/legacy/telemetry/legacy-analytics.layer.ts @@ -161,7 +161,7 @@ export const legacyAnalyticsLayer = Layer.effect( const loadLinkedProject = makeLoadLinkedProject(fs, path); - const isAgent = Option.isSome(aiTool.name) || runtime.isCi; + const isAgent = Option.isSome(aiTool.name); const envSignals = collectEnvSignals(); const baseProperties = stripUndefined({