Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Initialize Sentry in your project (experimental)
**Flags:**
- `-y, --yes - Accept non-interactive defaults (requires --features outside a TTY)`
- `-n, --dry-run - Show what would happen without making changes`
- `--features <value>... - Features to enable: errors,tracing,logs,replay,metrics,profiling,sourcemaps,crons,ai-monitoring`
- `--features <value>... - Features to enable: errors,tracing,logs,replay,metrics,profiling,sourcemaps,crons,attachments,agent-tracing,mcp-observability`
- `-t, --team <value> - Team slug to create the project under`
- `--app <value> - App to initialize in a monorepo (required with --yes when multiple apps are detected)`
- `--tui - Use the Ink-based interactive UI (default). Pass --no-tui to fall back to plain log output.`
Expand Down
10 changes: 9 additions & 1 deletion packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@ const FEATURE_ALIASES = {
sourcemaps: "sourceMaps",
sourceMaps: "sourceMaps",
crons: "crons",
attachments: "attachments",
"ai-monitoring": "aiMonitoring",
aiMonitoring: "aiMonitoring",
"agent-tracing": "aiMonitoring",
agentTracing: "aiMonitoring",
"mcp-observability": "mcpObservability",
mcpObservability: "mcpObservability",
} as const;

const SUPPORTED_FEATURE_NAMES = [
Expand All @@ -69,7 +74,10 @@ const SUPPORTED_FEATURE_NAMES = [
"profiling",
"sourcemaps",
"crons",
"attachments",
"ai-monitoring",
"agent-tracing",
"mcp-observability",
] as const;

const SUPPORTED_FEATURE_TEXT = SUPPORTED_FEATURE_NAMES.join(", ");
Expand Down Expand Up @@ -334,7 +342,7 @@ export const initCommand = buildCommand<
kind: "parsed",
parse: String,
brief:
"Features to enable: errors,tracing,logs,replay,metrics,profiling,sourcemaps,crons,ai-monitoring",
"Features to enable: errors,tracing,logs,replay,metrics,profiling,sourcemaps,crons,attachments,agent-tracing,mcp-observability",
variadic: true,
optional: true,
},
Expand Down
37 changes: 22 additions & 15 deletions packages/cli/src/lib/init/clack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const FEATURE_INFO: Record<string, { label: string; description: string }> = {
"Pinpoint the functions and lines of code responsible for performance issues",
},
logs: {
label: "Logging",
label: "Logs",
description: "See logs in context with errors and performance issues",
},
metrics: {
Expand All @@ -68,11 +68,15 @@ const FEATURE_INFO: Record<string, { label: string; description: string }> = {
"Turn minified production stack traces back into your original source code",
},
crons: {
label: "Crons",
label: "Crons & Uptime Monitors",
description: "Detect failed, missed, or delayed scheduled jobs",
},
attachments: {
label: "Attachments",
description: "Link user-supplied data to captured events",
},
aiMonitoring: {
label: "AI Monitoring",
label: "Agent Tracing",
description:
"Understand AI calls, latency, token usage, cost, and failures",
},
Expand Down Expand Up @@ -104,17 +108,18 @@ export function featureDescription(id: string): string | undefined {

const FEATURE_DISPLAY_ORDER = [
"errorMonitoring",
"logs",
"sessionReplay",
"performanceMonitoring",
"logs",
"aiMonitoring",
"attachments",
"crons",
"metrics",
"mcpObservability",
"profiling",
"reactFeatures",
"sourceMaps",
"crons",
"aiMonitoring",
"mcpObservability",
"userFeedback",
"reactFeatures",
];

/** Sort features into the canonical order used by summaries and final output. */
Expand All @@ -134,7 +139,7 @@ export const STEP_LABELS: Record<string, string> = {
"select-target-app": "Selecting target application",
"resolve-dir": "Resolving project directory",
"check-existing-sentry": "Checking for existing Sentry installation",
"detect-platform": "Detecting platform and framework",
"detect-platform": "Analyzing project and Sentry features",
"ensure-sentry-project": "Setting up Sentry project",
"select-features": "Selecting features",
"plan-codemods": "Planning code modifications",
Expand Down Expand Up @@ -204,7 +209,7 @@ export const STEP_ACTIVE_LABELS: Record<string, string> = {
"select-target-app": "Selecting target application...",
"resolve-dir": "Resolving project directory...",
"check-existing-sentry": "Checking for existing Sentry setup...",
"detect-platform": "Detecting framework and platform...",
"detect-platform": "Analyzing project and Sentry support...",
"ensure-sentry-project": "Configuring Sentry project...",
"select-features": "Preparing feature selection...",
"plan-codemods": "Planning code changes...",
Expand All @@ -221,8 +226,8 @@ export const STEP_ACTIVE_LABELS: Record<string, string> = {
* Falls back to the full label if a step isn't listed here.
*/
export const STEP_LABELS_SHORT: Record<string, string> = {
"discover-context": "Analyzing project",
"detect-platform": "Detecting platform",
"discover-context": "Discovering project",
"detect-platform": "Checking Sentry support",
"ensure-sentry-project": "Setting up project",
"select-features": "Selecting features",
"plan-codemods": "Planning changes",
Expand Down Expand Up @@ -256,9 +261,11 @@ export const STEP_PROGRESS_MESSAGES: Record<string, string[]> = {
],
"detect-platform": [
"Scanning project files...",
"Identifying framework and language...",
"Analyzing project configuration...",
"Determining SDK compatibility...",
"Identifying framework and runtime...",
"Matching the Sentry SDK...",
"Searching official Sentry docs...",
"Checking feature support...",
"Validating project recommendations...",
],
};

Expand Down
11 changes: 8 additions & 3 deletions packages/cli/src/lib/init/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ const DEFAULT_FEATURES = new Set<string>(DEFAULT_FEATURE_ORDER);
const DEFAULT_FEATURE_RANK = new Map<string, number>(
DEFAULT_FEATURE_ORDER.map((feature, index) => [feature, index])
);
// Feedback setup needs an in-app placement choice this wizard cannot make yet.
const UNSUPPORTED_INIT_FEATURES = new Set(["userFeedback"]);
// These features need code-level choices this selector cannot configure yet.
const UNSUPPORTED_INIT_FEATURES = new Set([
"attachments",
"metrics",
"userFeedback",
]);
const FEATURE_SELECTION_CONTEXT =
"Based on your project, these features are available to set up.";

Expand All @@ -69,7 +73,8 @@ function normalizeFeatureSelection(features: string[]): string[] {
// the reviewed, resumed, and restored-on-Back selections identical.
if (
features.includes("aiMonitoring") ||
features.includes("mcpObservability")
features.includes("mcpObservability") ||
features.includes("profiling")
) {
normalized.add("performanceMonitoring");
}
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/lib/init/tools/agent-checkpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* No-op init tool for acknowledging durable server-side agent checkpoints.
*/

import type { AgentCheckpointData, ToolResult } from "../types.js";
import type { InitToolDefinition } from "./types.js";

/** Resume a persisted agent session without performing local work. */
export const agentCheckpointTool: InitToolDefinition<"agent-checkpoint"> = {
operation: "agent-checkpoint",
describe: (payload) => payload.detail ?? "Continuing repository analysis...",
execute: async (): Promise<ToolResult> => ({
data: { acknowledged: true } satisfies AgentCheckpointData,
ok: true,
}),
};
3 changes: 3 additions & 0 deletions packages/cli/src/lib/init/tools/registry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiError } from "../../errors.js";
import type { ToolOperation, ToolPayload, ToolResult } from "../types.js";
import { agentCheckpointTool } from "./agent-checkpoint.js";
import { applyPatchsetTool } from "./apply-patchset.js";
import {
createSentryProjectTool,
Expand All @@ -16,6 +17,7 @@ import { formatToolError, validateToolSandbox } from "./shared.js";
import type { AnyInitToolDefinition, ToolContext } from "./types.js";

const toolDefinitions = [
agentCheckpointTool,
listDirTool,
readFilesTool,
fileExistsBatchTool,
Expand All @@ -34,6 +36,7 @@ const toolRegistry = new Map<ToolOperation, AnyInitToolDefinition>(

/** Sentry API operations never inspect or mutate the local filesystem. */
const CWD_INDEPENDENT_OPERATIONS = new Set<ToolOperation>([
"agent-checkpoint",
"create-sentry-project",
"ensure-sentry-project",
]);
Expand Down
23 changes: 23 additions & 0 deletions packages/cli/src/lib/init/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type InitProtocolEnvelope = {

// Tool suspend payloads
export type ToolPayload =
| AgentCheckpointPayload
| ListDirPayload
| ReadFilesPayload
| FileExistsBatchPayload
Expand All @@ -89,6 +90,28 @@ export type ToolPayload =

export type ToolOperation = ToolPayload["operation"];

/**
* Suspend request used to continue a persisted server-side agent session.
* The CLI acknowledges it without inspecting or mutating the local project.
*/
export type AgentCheckpointPayload = {
/** Discriminator for locally handled workflow tools. */
type: "tool";
/** Stable no-op operation name. */
operation: "agent-checkpoint";
/** Optional progress copy displayed while the checkpoint is acknowledged. */
detail?: string;
/** Workflow filesystem root; not accessed by this operation. */
cwd: string;
/** Reserved operation arguments; currently always empty. */
params: Record<string, never>;
};

/** Explicit no-op acknowledgement validated by the init service boundary. */
export type AgentCheckpointData = {
acknowledged: true;
};

export type ListDirPayload = {
type: "tool";
operation: "list-dir";
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/test/commands/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,18 @@ describe("init command func", () => {
const ctx = makeContext();
await func.call(ctx, {
...DEFAULT_FLAGS,
features: ["errors,tracing,replay,sourcemaps"],
features: [
"errors,tracing,replay,sourcemaps,attachments,agent-tracing,mcp-observability",
],
});
expect(capturedArgs?.features).toEqual([
"errorMonitoring",
"performanceMonitoring",
"sessionReplay",
"sourceMaps",
"attachments",
"aiMonitoring",
"mcpObservability",
]);
});

Expand All @@ -204,14 +209,17 @@ describe("init command func", () => {
await func.call(ctx, {
...DEFAULT_FLAGS,
features: [
"errorMonitoring,performanceMonitoring,sessionReplay,sourceMaps",
"errorMonitoring,performanceMonitoring,sessionReplay,sourceMaps,attachments,aiMonitoring,mcpObservability",
],
});
expect(capturedArgs?.features).toEqual([
"errorMonitoring",
"performanceMonitoring",
"sessionReplay",
"sourceMaps",
"attachments",
"aiMonitoring",
"mcpObservability",
]);
});

Expand All @@ -223,7 +231,7 @@ describe("init command func", () => {
});
await expect(promise).rejects.toThrow(ValidationError);
await expect(promise).rejects.toThrow(
"Supported features: errors, tracing, logs, replay, metrics, profiling, sourcemaps, crons, ai-monitoring"
"Supported features: errors, tracing, logs, replay, metrics, profiling, sourcemaps, crons, attachments, ai-monitoring, agent-tracing, mcp-observability"
);
expect(runWizardSpy).not.toHaveBeenCalled();
expect(findProjectsSpy).not.toHaveBeenCalled();
Expand Down
17 changes: 12 additions & 5 deletions packages/cli/test/lib/init/clack-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ describe("featureLabel", () => {
test("returns label for known feature", () => {
expect(featureLabel("errorMonitoring")).toBe("Error Monitoring");
expect(featureLabel("performanceMonitoring")).toBe("Tracing");
expect(featureLabel("logs")).toBe("Logging");
expect(featureLabel("crons")).toBe("Crons");
expect(featureLabel("aiMonitoring")).toBe("AI Monitoring");
expect(featureLabel("logs")).toBe("Logs");
expect(featureLabel("crons")).toBe("Crons & Uptime Monitors");
expect(featureLabel("attachments")).toBe("Attachments");
expect(featureLabel("aiMonitoring")).toBe("Agent Tracing");
expect(featureLabel("mcpObservability")).toBe("MCP Observability");
expect(featureLabel("userFeedback")).toBe("User Feedback");
});

Expand Down Expand Up @@ -87,6 +89,9 @@ describe("featureDescription", () => {
expect(featureDescription("crons")).toBe(
"Detect failed, missed, or delayed scheduled jobs"
);
expect(featureDescription("attachments")).toBe(
"Link user-supplied data to captured events"
);
expect(featureDescription("aiMonitoring")).toBe(
"Understand AI calls, latency, token usage, cost, and failures"
);
Expand Down Expand Up @@ -115,16 +120,18 @@ describe("sortFeatures", () => {
"errorMonitoring",
"sourceMaps",
"crons",
"attachments",
"aiMonitoring",
"mcpObservability",
])
).toEqual([
"errorMonitoring",
"logs",
"sourceMaps",
"crons",
"aiMonitoring",
"attachments",
"crons",
"mcpObservability",
"sourceMaps",
"userFeedback",
]);
});
Expand Down
19 changes: 11 additions & 8 deletions packages/cli/test/lib/init/interactive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ describe("handleMultiSelect", () => {
});
});

test("shows defaults first, sorts optional features, and omits User Feedback", async () => {
test("shows defaults first, sorts optional features, and omits unsupported features", async () => {
const { ui, calls, respond } = createMockUI();
respond.multiselect(["sessionReplay"]);
respond.select("continue");
Expand All @@ -509,7 +509,9 @@ describe("handleMultiSelect", () => {
"sessionReplay",
"logs",
"crons",
"attachments",
"aiMonitoring",
"mcpObservability",
"userFeedback",
],
},
Expand All @@ -528,8 +530,8 @@ describe("handleMultiSelect", () => {
"sessionReplay",
"performanceMonitoring",
"aiMonitoring",
"metrics",
"crons",
"mcpObservability",
"profiling",
"sourceMaps",
]);
Expand All @@ -544,6 +546,8 @@ describe("handleMultiSelect", () => {
text: "Based on your project, these features are available to set up.",
},
]);
expect(multiselectCall?.options).not.toContain("metrics");
expect(multiselectCall?.options).not.toContain("attachments");
expect(multiselectCall?.options).not.toContain("userFeedback");

const reviewCall = calls.find((call) => call.kind === "select");
Expand Down Expand Up @@ -607,9 +611,10 @@ describe("handleMultiSelect", () => {
});

test.each([
"aiMonitoring",
"mcpObservability",
])("review includes tracing when %s enables it implicitly", async (dependencyFeature) => {
["aiMonitoring", "Agent Tracing"],
["mcpObservability", "MCP Observability"],
["profiling", "Profiling"],
])("review includes tracing when %s enables it implicitly", async (dependencyFeature, dependencyLabel) => {
const { ui, calls, respond } = createMockUI();
respond.multiselect([dependencyFeature]);
respond.select("continue");
Expand All @@ -636,9 +641,7 @@ describe("handleMultiSelect", () => {
const reviewDetails = reviewCall?.details?.map((detail) => detail.text);
expect(reviewDetails).toContain("✓ Error Monitoring");
expect(reviewDetails).toContain("✓ Tracing");
expect(reviewDetails).toContain(
`✓ ${dependencyFeature === "aiMonitoring" ? "AI Monitoring" : "MCP Observability"}`
);
expect(reviewDetails).toContain(`✓ ${dependencyLabel}`);
});

test("Back restores the normalized AI selection including Tracing", async () => {
Expand Down
Loading
Loading