Skip to content
cccskills
FreePublished on GitHub

onboarding-journeys

Map onboarding from signup to the first completed output, showing cohort counts, percentage splits, last observed steps, and later activity within a chosen window. Capture replay screenshots for journey steps and arrange them in a storyboard.

OriginalShow the original description

Build the onboarding journey tree (signup to first output, with % splits, last-observed-step counts, window-bounded later activity, and example replays) and render a screenshot per step. Use when asked for an onboarding storyboard, each cohort's last recorded step or later activity within a window, or frames for each onboarding step.

BuilderIO/agent-native7,519 stars(GitHub 7,519 + cccskills 0)
Works withGoogle Cloud

Run score Not tested

See how to install

Good for

  • Building an onboarding storyboard
  • Comparing where cohorts last appeared
  • Reviewing later activity within a time window
  • Capturing replay frames for each step

Files included (1)

  • SKILL.md24.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Onboarding Journeys

The pipeline runs without a UI. Codex does all four steps from the terminal:

  1. Call get-onboarding-journey as an MCP tool of the deployed Analytics app. Save the JSON result as tree.json.
  2. Run the capture CLI from the agent-native checkout: pnpm --filter analytics journey:capture --tree tree.json --out ./frames (--per-node 2 --concurrency 3, --min-aspect / --max-aspect, --dry-run to print the plan, --upload for private attachmentRefs).
  3. Call Design's create-journey-canvas with { title, tree, frames: [{ nodeKey, exampleIndex, imageUrl?, attachmentRef?, width, height, capturedAt }], designId? } -> { designId, url, nodeCount, frameCount }. Build frames from frames/manifest.json. Design reads event timestamps and replay offsets from tree; keep sourceEventAt, replayAt, and capturedAt in the capture manifest.
  4. Lay out and review the canvas.

The tree

estimate-onboarding-journey-cost dry-runs the same access-scoped BigQuery event-page SQL as get-onboarding-journey. Pass matching dates, app, email filter, follow-up mode, event row budget, and maxTotalBytesBilled. It reports requested and allowed page counts, then dry-runs each allowed 4,000-row page, including the one-row overflow check when within budget. maxTotalBytesBilled defaults to 275 GB, reserving the full 25 GB per-query cap for each of 11 pages; raise it deliberately to allow more pages, up to 1.275 TB and 51 pages. Match this value when calling get-onboarding-journey. A 45-second deadline or a per-page dry-run timeout can return status: "incomplete", the count of unestimated allowed pages, and possiblePagesEstimatedBytes: null. Read pageBudgetLimited and the requested/allowed counts before interpreting an estimate. The sum is a possible-pages estimate, not executed or billed bytes. Data-dependent follow-up queries are excluded. An unavailable estimate fails explicitly.

get-onboarding-journey returns JourneyTree; format: "summary" returns the same event-derived journey and follow-up counts as an indented outline with no examples. Summaries skip replay reads, so coverage.sessionsWithReplay is null. Use one first to choose a window, app, maxDepth, minNodeSessions (small branches merge into an other node), and maxNodes.

To include the bounded cross-session estimate from the repository root, run: pnpm --filter analytics action get-onboarding-journey --dateFrom=2026-08-01 --dateTo=2026-08-31 --app=all --emailFilter=exclude_builder --followUpMode=person --format=summary.

type JourneyExample = { sessionId: string; recordingId?: string | null; ts: string; offsetMs: number | null; viewport: { width: number; height: number } | null; viewportReason?: string; replayUrl?: string };
type JourneyNode = { key: string; keyTruncated?: boolean; label: string; labelTruncated?: boolean; parentKey: string | null; depth: number; kind: "step" | "other"; n: number; pctOfRoot: number; pctOfParent: number; dropoffN: number; dropoffPct: number; deeperN: number; examples: JourneyExample[]; otherBranchCount?: number; otherBranchSummariesPartial?: true; otherBranches?: { path: string[]; pathTruncated?: boolean; key: string; keyTruncated?: boolean; sourceStepKey?: string; sourceStepKeyTruncated?: boolean; n: number; pctOfParent: number }[] };
type JourneyFollowup = { status: "complete" | "incomplete"; incompleteReason?: "journey_event_read_truncated" | "journey_event_read_invalid" | "journey_event_read_may_have_shifted" | "terminal_cohort_query_too_large" | "followup_aggregate_truncated" | "followup_aggregate_invalid" | "followup_aggregate_cost_limited" | "followup_aggregate_query_timeout" | "followup_aggregate_query_failed" | "terminal_cohort_mismatch"; observationCutoff: string; observationFollowupDurationMs: { min: number; max: number; mean: number } | null; rightCensoredAtWindowEnd: true; coverage: { journeyEventRead: { rows: number; pages: number; truncated: boolean; paginationConsistency: "stable" | "may_have_shifted" }; followupAggregateRead: { rows: number | null; queries: number; truncated: boolean; status?: "incomplete"; backendStatus?: number | null; backendReason?: string | null; backendOperation?: "submit" | "poll" | "job" | null }; cohortSessions: number | null }; laterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null }; noLaterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null } };
type JourneyPersonFollowupCounts = { canonicalPeople: number; laterActivityInSelectedSession: number; laterActivityOutsideSelectedSessionOrApp: number; laterActivityInBothSelectedAndOutside: number; laterActivityObservedAnywhere: number; noActivityObservedWithinHorizon: number; rightCensoredHorizon: number; fullyObservedCanonicalPeople: number; noActivityObservedWithinHorizonPctOfFullyObservedCanonicalPeople: number | null; identityUnavailableSessions: number; identityUnavailableSessionEvidence: { laterActivityInSelectedSession: number; laterActivityOutsideSelectedSessionOrApp: number } };
type JourneyPersonFollowup = { status: "complete" | "incomplete"; incompleteReason?: "journey_event_read_truncated" | "journey_event_read_invalid" | "journey_event_read_may_have_shifted" | "terminal_cohort_too_large" | "terminal_cohort_invalid" | "person_followup_aggregate_truncated" | "person_followup_query_cost_limited" | "person_followup_query_timeout" | "person_followup_query_failed" | "person_followup_aggregate_invalid" | "person_followup_terminal_cohort_mismatch"; horizonDays: 30; horizonMs: number; observationWatermark: string; observationFollowupDurationMs: { min: number; max: number; mean: number } | null; coverage: { journeyEventRead: { rows: number; pages: number; truncated: boolean; paginationConsistency: "stable" | "may_have_shifted" }; followupAggregateRead: { status: "complete" | "truncated" | "incomplete" | "not_run"; rows: number | null; queries: number; truncated: boolean; backendStatus?: number | null; backendReason?: string | null; backendOperation?: "submit" | "poll" | "job" | null }; terminalSessions: number | null; sessionsWithoutSelectedStep: number | null; identityJoin: { status: "complete" | "partial" | "unavailable" | "not_applicable" | "unknown"; terminalSessions: number | null; sessionsWithCanonicalIdentity: number | null; sessionsWithoutCanonicalIdentity: number | null; uniqueCanonicalPeople: number | null; coveragePct: number | null } }; total: JourneyPersonFollowupCounts | null; byTerminalStepKey: Record<string, JourneyPersonFollowupCounts> | null };
type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number | null; truncated: boolean }; nodes: JourneyNode[]; followUp: JourneyFollowup; personFollowUp?: JourneyPersonFollowup; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number | null; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };

Analytics returns cohort nodes with counts. When extending a Design storyboard with screenshots from a separate observed session, add those reference nodes only to the Design input, set referenceOnly: true, and omit the cohort metric fields. This annotation is for visual references and is not emitted by get-onboarding-journey.

An other node keeps its aggregate metrics and includes up to 20 original root-to-branch label paths, source keys to distinguish identical paths, each branch's session count, and its percentage of the direct parent. The percentages are not recalculated against the journey root. Across one tree, branch summaries are limited to 200 entries and 64 KiB of serialized detail. otherBranchCount records the full number of merged branches, and otherBranchSummariesPartial: true marks when those bounds omit any details. These summaries have no screenshots or example sessions. To fit Design's bounded input, labels/path segments longer than 300 characters and keys longer than 2,048 characters end with a stable hash suffix and set the matching labelTruncated, pathTruncated, keyTruncated, or sourceStepKeyTruncated flag. key remains the full path identity when it fits; sourceStepKey carries the final raw step key used to distinguish identical display paths. Counts stay within the 200,000-event read cap; aggregate counts and direct-parent percentages are not recalculated or reduced for display.

Saved outputs require source-confirmed completion events. Clips uses recording_ready; recording_started, transcript-only recording_completed, and clip_viewed are not saved outputs. Slides uses explicit generation_completed; generation_request_accepted, generation_outcome_unresolved, generation_failed, generation_stuck, generation_cancelled, and generation_abandoned remain distinct attempt steps. An unresolved event can carry persisted_output: true, but it still does not substitute for generation_completed. deck_edited, output_viewed, and current deck state also cannot substitute for that event. A missing terminal event stays absent. Generation and recording starts remain attempt steps, and retry attempts that reuse a deck's output ID remain separate by their exact attempt ID. A sessionless Clips completion or Slides lifecycle event is attached only when its exact output and attempt pair maps to one distinct eligible onboarding session (recording_started for Clips; generation_started, generation_request_accepted, or output_viewed for Slides); missing or ambiguous matches stay unattributed. Output and attempt IDs are never included in the returned tree.

The journey projection also retains integration_setup_exposed, integration_method_clicked, and integration_method_outcome as separate integration:<flow>:... steps. These record setup exposure, the selected method, and its outcome without adding those events to cohort denominators. Keep flow: "chat_setup" separate from first-run onboarding method steps; the same connection method can appear in both flows. Sessions with those events but no onboarding cohort event appear under the optional standaloneSetup tree; its counts and percentages have their own root denominator. For a standalone storyboard, extract standaloneSetup and pass it as the top-level tree to journey:capture; the capture CLI reads top-level nodes.

  • Nodes come parents first. key is the path of step keys joined with >; pctOf* are percents (0-100). dropoffN / dropoffPct count sessions whose last observed step is this node; they do not establish that a user exited.
  • For each node, n = dropoffN + sum(returned child n) + deeperN. dropoffN is sessions whose last observed step is that node; it is not a confirmed exit: a blocked tracker or an event outside the window reads the same. deeperN counts sessions with a later observed step that is not represented as a child, including paths past maxDepth or a node-list cap.
  • followUp groups onboarding sessions by the key of their terminal selected step. laterRecordedActivityWithinWindow counts sessions with any later native Analytics event in the same session; noLaterRecordedActivityWithinWindow counts the remaining sessions. The read applies the same authenticated user/org scope, date window, app, session-level email identity, test exclusion, and Builder.io email filter as the journey read. It uses one frozen observationCutoff for every event page and the single aggregate query. observationFollowupDurationMs summarizes the time from each terminal selected step to that cutoff. rightCensoredAtWindowEnd: true means the no-later count is right-censored: no later event was recorded before the cutoff; this is not an abandonment or churn outcome. The aggregate contains counts only and does not expose the session IDs or member identity keys used internally. The consistency field reports may_have_shifted when the event read uses multiple OFFSET pages; a late-arriving event can change page membership inside a historical window too. The action reads 4,000-row pages up to maxEventRows (at most 200,000), plus one overflow row to detect truncation. Each BigQuery page keeps a 25 GB billed-byte cap; by default, maxTotalBytesBilled reserves that cap for 11 pages (275 GB), and can be raised deliberately to 1.275 TB (51 pages). Event page reads also stop at a 45-second deadline. If the aggregate budget or deadline cuts a read, coverage.truncated and notes identify why; read them before interpreting counts. A truncated event sample skips replay reads and returns sessionsWithReplay: null because replay availability is unknown. If a read truncates, page boundaries may have shifted, or the terminal cohort cannot be covered in one query under the 800,000-character SQL limit or 50,000-token parser limit, status is incomplete and all new cohort counts and follow-up duration are null; incompleteReason identifies the limiting read. Do not report percentages from that partial result. Existing journey counts and denominators remain independent of this follow-up read.
  • A failed event read fails the action with journey_events_read_timeout, journey_events_read_cost_limited, or journey_events_read_failed. When BigQuery provides a safe failure detail, the error includes its submit, poll, or job phase, HTTP status, and allowlisted reason without returning SQL or rows. A failed aggregate keeps the journey tree but sets every new aggregate count to null; it is not a zero. Read status and coverage before using either follow-up mode.
  • Set followUpMode: "person" to add personFollowUp without changing the session tree or same-session followUp. Each direct properties.auth_user_id is counted once and assigned to that person's latest terminal selected step in the requested cohort. No email, anonymous, user-key, or organization pseudo-ID fallback is used for cross-session matching. The query searches later events across first-party apps under the same authenticated org, Builder.io email, and test-identity filters. Selected-session/app and outside-session/app activity are overlapping evidence classes; laterActivityInBothSelectedAndOutside reports their overlap, and laterActivityObservedAnywhere is their union. A member with activity in either class is not counted as having no activity. The result also separates no activity after a fully observed 30-day horizon, right-censored horizons, and identity-unavailable sessions. Identity-unavailable session evidence can report activity known from that exact session ID, but those sessions are not people and never enter person percentages.
  • personFollowUp.observationWatermark freezes both event and receive time at a UTC minute boundary, so repeated requests in that minute use the same read boundary. observationFollowupDurationMs summarizes capped follow-up among canonical people. noActivityObservedWithinHorizonPctOfFullyObservedCanonicalPeople uses only identified people whose complete 30-day horizon elapsed. It measures bounded Analytics coverage, never abandonment or permanent churn. Sessions without a selected journey step appear in coverage.sessionsWithoutSelectedStep and are not eligible no-activity observations; a missing next step does not mean no later activity. Read coverage.identityJoin and coverage.followupAggregateRead; if a raw read is incomplete or BigQuery rejects its billed-byte cap, counts and percentages are null with status: "incomplete".
  • maxDepth defaults to 8 and is bounded at 40. Request maxDepth: 40 for a deeper pass. When sessions continue past the requested depth, coverage.truncated is true and the boundary node's deeperN says how many continuations were omitted; the summary outline calls this out explicitly. maxNodes can also remove child branches, so read coverage.truncated and notes before interpreting deeperN as depth-only continuation.
  • pctOfRoot uses the returned app's rootN; query each app separately when comparing conversion. An app: "all" result has one combined denominator and must not be used as an individual app's percentage base.
  • Builder connection lifecycle events accept canonical and legacy event-name aliases and collapse duplicate aliases in a row sequence. Custom-key setup includes first-run credential_validated / credential_saved outcomes and the bounded provider validation/save events. Unrecognized flow or outcome values remain unknown; they are not counted as failures.
  • A session is an analytics session id. One that began before dateFrom starts mid-journey, so start the window a day early.
  • Read coverage before using the numbers. truncated: true means the event read hit maxEventRows, maxTotalBytesBilled, or its 45-second deadline, a session went past maxDepth, or the node list hit maxNodes; notes says which. Never report a truncated tree as the whole window.
  • The event row cap is shared by onboarding and standalone setup. If it is hit, standaloneSetup may be an empty, truncated tree because standalone events were beyond the read boundary; do not interpret that as zero standalone use.
  • An example with recordingId: null has no replay the caller can open; an omitted recordingId means replay availability was not checked. Its viewportReason says why the viewport is unknown (no_recording, recording_unknown, not_captured, unreadable).
  • offsetMs is milliseconds from the recording's startedAt, not the first rrweb event.
  • replayUrl uses that same offset in atMs; the session page converts it to the first-event-relative player clock after replay events load.
  • Test identities are always excluded; emailFilter defaults to exclude_builder, as in the onboarding metrics. A session with any excluded identity is dropped whole.

Capture

The CLI mints a two-hour recording-scoped replay link with create-session-replay-agent-link, reads the token-gated manifest and every chunk, then renders each recording once with the local @rrweb/replay player in headless Chromium. Each JourneyTree offsetMs is relative to the recording's startedAt; the CLI maps startedAt + offsetMs to rrweb's first-event-relative playhead for seeking, viewport, and route evaluation while preserving the original offset in frame metadata. If the target precedes the first replay event, report a frame failure instead of clamping the seek to zero. The CLI uses a native browser screenshot so dialogs and other top-layer content remain visible. The token and replay events stay in memory; the CLI writes PNGs and manifest.json with frames, failures (explicit, with a reason), and skipped. With --extract-prompts, it also writes the private local prompt-provenance.json sidecar. Manifest chunk counts and ordering must be complete; the CLI reads bounded batches only through the requested offsets and validates every fetched chunk. Fix failures or report them; do not paint over a missing frame.

sourceEventAt preserves the JourneyTree example's original event timestamp; replayAt is startedAt + offsetMs, and capturedAt is when the CLI rendered the PNG. Every visible iframe must have a corresponding recorded child document by the requested replay time. If the child document is missing, the frame is listed as a failure with code replay_iframe_content_unavailable and counts in diagnostics. If unsupported 3D projection, rounded ancestor clipping, unsupported CSS clip-path shapes, unrecognized legacy CSS clip values, masks, or visibility-altering filters prevent the audit from verifying visibility, the frame is listed with code replay_iframe_visibility_unverifiable; do not treat uncertainty as either missing content or a successful audit. The check covers every visible iframe because replay can omit its original source attribute while rebuilding an isolated frame.

Authenticate to the deployed app, never a local database: run npx -y @agent-native/core@latest connect https://analytics.agent-native.com --client codex (the CLI reads the bearer it writes to ~/.codex/config.toml), or pass --token / set AGENT_NATIVE_TOKEN. --app-url overrides the app. Frame mode needs a deployment that includes /sessions/:id?frame=1 and its bounded capture_through_ms query parameter.

The default --capture-mode offline fetches replay chunks through the recording-scoped token without app cookies or a referrer, then renders in an offline browser. Remote images and fonts are not fetched; successful frames carry assetStatus: "not_fetched", and the manifest says remoteAssets: "not-fetched". This mode is useful when the recorded page has no remote media.

Use --capture-mode browser when recorded images, posters, video frames, or iframes matter. The CLI opens the direct recording-scoped agent-access link in a fresh browser context with empty storage and no app bearer headers. The Analytics frame loads a recording once, maps each JourneyTree recording-start offset to rrweb's playhead, and loads only the ordered replay prefix through the largest requested offset. Browser capture caps the manifest at 4 MiB, the materialized prefix at 2,000 chunks, 100,000 events, 64 MiB of declared or received chunk data, and each chunk response at 12 MiB. It then uses the existing bounded screenshot asset preflight. Each successful frame says assetStatus: "preflighted"; a blocked, unreadable, missing, or oversized asset is an explicit frame failure with assetStatus: "preflight_failed". Browser asset requests never receive the CLI's Analytics bearer or browser cookies. The manifest sets remoteAssets: "browser-preflight-per-frame" to describe the capture path; each frame status describes what happened for that seek. Before navigation, the CLI routes requests from every page in the isolated browser context and blocks private, loopback, and metadata destinations outside the exact Analytics app origin. Redirect destinations pass through the same check before Chromium connects. A temporary loopback SOCKS tunnel resolves each destination once, validates every returned address, and connects only to those numeric addresses; Chromium cannot bypass the tunnel for loopback hosts or do its own destination DNS lookup. Its resolver exception is limited to the exact loopback SOCKS listener. WebSockets are blocked. Cross-origin requests are GET/HEAD only, have authorization, proxy-authorization, and referrer headers removed, and are blocked if they carry cookies; Origin is preserved for CORS. Chromium performs TLS and CORS itself, and the CLI never forwards its Analytics bearer or cookies to a recorded origin. New output directories use mode 0700; existing directories must already be private and are never chmodded. Each output file uses mode 0600.

--extract-prompts is available only with browser mode. It reads at most 12 user-role message text blocks whose character geometry intersects the replay viewport and rectangular overflow clips at each requested seek, removes controls and hidden text, redacts credential assignments and cookie headers, omits SQL and base64 payloads, and caps the run at 200 snapshots, 12 messages per snapshot, 2,000 characters per message, and 8,000 characters per snapshot. The sidecar records how many additional snapshots were omitted and how many planned snapshots could not be recorded. It writes the result to prompt-provenance.json with restrictive local file permissions. The sidecar keeps the JourneyTree source-event timestamp separate from the observed seek offset, rrweb playhead, and extraction time. Each snapshot also says whether its frame capture or upload succeeded and includes the failure reason when it did not. This is visible user-role text at the seek, not proof of an exact source event or attempt-ID relationship. Unsupported transform, mask, or rounded clipping geometry fails the extraction explicitly. It is never uploaded, including when --upload stores PNGs through the private attachment action.

Keep recorded URLs and CSS intact for rrweb playback; network controls belong at the capture boundary. --upload stores PNGs through the private upload action, which checks recording access again. Design's own sandboxed template previews opt into the cooperative iframe recorder, so their child DOM can be replayed without granting the parent access to the preview document or credentials.

Viewport comes from the recording's first rrweb Meta event, stored by replay ingest in session_recordings.metadata.viewport (first and last). Older recordings have none.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

a2a-assets

FreeSummary

Generate, refine, export, and place brand images or videos from an Assets library into another app. The skill covers choosing a library, handling image batches and video runs, and updating selected assets after feedback.

  • Generate brand images for slides or another app
  • Refine an existing asset after feedback
  • Create and export a video asset
BuilderIO/agent-native7.5KUpdated Oct 26, 2026

account-health

FreeSummary

Prepare a customer account health, renewal, or contract usage readout. Verify the account identity, use approved metric definitions, compare contracted capacity with actual usage, and trace findings to sources, time windows, and known gaps.

  • Preparing a named customer's account health review
  • Comparing contracted seats or capacity with usage
  • Gathering adoption signals before a renewal or QBR
BuilderIO/agent-native7.5KUpdated Oct 26, 2026

actions

FreeSummary

Create focused app actions that agents can call as tools and frontend code can use through client hooks. Use them to add provider integrations or fetch UI data, with validated inputs and structured results.

  • Adding a new app operation
  • Connecting an external provider API
  • Fetching data for a frontend view
BuilderIO/agent-native7.5KUpdated Oct 26, 2026

adding-a-feature

FreeSummary

Use this checklist when adding a feature or integration to an agent-native app. It covers the user interface, agent-callable actions, instructions, and app-state sync so both the user and agent can work with the capability consistently.

  • Planning a new feature across UI and agent actions
  • Checking that agent changes appear in the UI
  • Documenting actions and feature state for the agent
BuilderIO/agent-native7.5KUpdated Oct 26, 2026

adhoc-analysis

FreeSummary

Investigate complex questions by combining data from multiple sources, then summarize patterns, metrics, and evidence in a clear report. Save requested analyses as dashboards with instructions for future refreshes.

  • Comparing CRM deals with call records
  • Investigating customer health across systems
  • Saving a repeatable performance analysis
BuilderIO/agent-native7.5KUpdated Oct 26, 2026

admin-surfaces

FreeSummary

Use the Analytics admin surfaces to inspect and change workspace feature flags, review dashboard ownership and usage activity, or browse and edit databases connected from other agent-native apps. The skill guides administrators to the relevant view and actions.

  • Review dashboard owners, views, and edits
  • Change a workspace feature flag for an app
  • Browse or edit a connected app database
BuilderIO/agent-native7.5KUpdated Oct 26, 2026

See all skills by BuilderIO

Report a problem with this skill