Codync
免費、開源的 1:1 替代方案,取代 Grok Bot 與 Muse。從 iPhone、Mac 或 Linux 桌面將 Claude Code、Codex、Cursor 與 40+ 程式碼代理人作為持續機器人傳訊。群組聊天、主題串…
AI 翻譯
尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
A Next.js reference app for reopt Data SDK, demonstrating browser and server analytics, consent, identity, and session replay.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A public, production-shaped Next.js reference for
@reopt-ai/data-sdk-client
and
@reopt-ai/data-sdk-server.
Use it alongside the reopt Data documentation to
see browser, server, proxy, consent, identity, and delayed-event patterns in a
complete application.
Production example: data-sdk-example.reopt.ai.
Arc Supply is a fictional workspace-goods store created for this example. Its products, accounts, orders, brand, and imagery are illustrative; no purchase is real and the assets must not be used to represent reopt or a real merchant.
server-only module./ingest proxy that shares browser and server identity.Requirements: Node.js 22.22.1 or newer and pnpm 12.15.0.
corepack enable
pnpm install
pnpm dev
Open http://localhost:4100. Credentials are optional: without a write key, the SDK becomes a no-op and the shop keeps working.
To connect a project:
cp .env.example .env.local
# Fill in values issued for your reopt Data project.
pnpm dev
| Variable | Purpose | Requirement | Browser-visible |
|---|---|---|---|
REOPT_DATA_BASE_URL | reopt Data endpoint | Defaults to https://data.reopt.ai | Indirectly |
REOPT_DATA_WRITE_KEY | Public project write key | Optional; analytics fail open | Yes |
REOPT_DATA_CLIENT_ID | Server API client identifier | Optional, paired with secret | No |
REOPT_DATA_CLIENT_SECRET | Server API client secret | Optional, paired with client ID | Never |
REOPT_DATA_PROJECT_ID | Project used by round-trip verification | Verification only | No |
BETTER_AUTH_SECRET | Example session-signing secret | Production; at least 32 characters | No |
BETTER_AUTH_URL | Public origin of this application | Production | Yes |
REOPT_DATA_EXAMPLE_DIAGNOSTICS | Enables diagnostic UI and routes in production | Optional; defaults to false | No |
Do not add NEXT_PUBLIC_ to server credentials. Production startup rejects an
unsafe auth URL, an unsafe reopt Data URL, or an incomplete server credential
pair. HTTP service URLs are accepted only for localhost development.
For the production deployment, set
BETTER_AUTH_URL=https://data-sdk-example.reopt.ai and
REOPT_DATA_ASSET_PREFIX=https://data-sdk-example.reopt.ai/_next/static so
authentication, page metadata, and uploaded source maps use the public origin.
Environment changes take effect after redeploying the application.
With reopt-data and this repository next to each other, run the complete local
loop from this repository:
pnpm dev:stack
It links the four Data SDK packages, performs an initial dependency-aware
build, keeps their dist/ output watched, starts reopt-data with dev:min,
and starts this app. If .reopt-local.json exists, its first project is loaded
into the child-process environment without printing credential values. The
command does not create or reset Data resources and does not run a cron drainer.
Use pnpm sdk:mode to inspect the active source, pnpm sdk:tarball for a
package-fidelity check, and pnpm sdk:npm before handing off or deploying this
standalone repository.
The storefront's live events share a low-cardinality vocabulary for category,
price band, currency, checkout mode, value band, and funnel stage. That makes a
real visit immediately useful in property-filtered funnels such as
product.viewed → cart.added → checkout.started → order.completed.
For acquisition and channel comparisons, preview the synthetic dataset first:
pnpm analytics:seed
The command is dry-run by default. To apply it, start the configured storefront in one terminal and replay the plan in another:
pnpm dev
# another terminal
pnpm analytics:seed -- --apply
The development storefront must have REOPT_DATA_WRITE_KEY configured and
diagnostics enabled. The command drives the real UI through isolated browser
contexts, so the browser SDK opens genuine sessions and server-confirmed orders
join them normally. It does not use a privileged data-insertion shortcut.
One run sends synthetic, non-PII analytics journeys across paid search, email,
paid social, organic search, referral, and direct traffic. Client-side journey
events carry the same demo_run_id and cohort label for correlation; filter
the first funnel step by either property to isolate a run or cohort. Each
journey gets a unique browser identity, starts with an attributable $pageview,
and intentionally drops visitors between commerce stages so funnel and
conversion-rate differences are visible. Re-running creates a new run; it
never resets or deletes existing project data. Use --base-url to target a
different development storefront.
flowchart LR
Env[Deployment configuration] --> Resolver[Public project resolver]
Request[Request] --> Resolver
Resolver --> Layout[Next.js layout]
Layout --> Browser[Browser SDK]
Browser -->|/ingest/api/track| Proxy[Next.js proxy]
Proxy --> Data[data.reopt.ai]
Request --> Server[Server SDK]
Secrets[server-only credentials] --> Server
Server --> Data
LocalOutbox[Development outbox] --> Node[Node SDK worker]
Node --> Data
proxy.ts seeds the browser-readable device cookie and exposes first-party
ingest. Server-side events resolve the same request identity. Delayed local
events record deviceId on each row so a worker batch can contain multiple
visitors safely.
Diagnostics are enabled automatically in development. The lazy SDK button,
payload and identity drawer, runtime switches, /lab, and /api/boom are all
absent from production unless the deployment explicitly sets
REOPT_DATA_EXAMPLE_DIAGNOSTICS=true. Enable that only for an isolated,
controlled example deployment because diagnostic payloads can contain visitor
event data.
| Route | Integration exercised |
|---|---|
/ | Storefront and integration overview |
/products | Query navigation and automatic page views |
/products/[slug] | Manual page views, register(), normalization, and track() |
/cart | Browser cart events |
/checkout | Server Action and Route Handler conversion paths |
/orders | Request-scoped orders and demo outbox rows |
/account | identify() on sign-in and reset() on sign-out |
/guide | Capability-to-source map and installed npm package versions |
/lab | Diagnostics-only exceptions, Web Vitals, consent, and queue controls |
/debug/errors | Every exception shape, with breadcrumbs and a failing route handler |
/lab/ai | AI SDK telemetry: $ai_trace, $ai_generation, $ai_span, feedback |
The diagnostics settings exercise automatic page-view ownership, exception capture, exception breadcrumbs, an external consent manager, tracing headers, missing bootstrap, missing write key, and debug logging.
/debug/errors)Six buttons, one per shape the issue detail renders differently:
| Button | What it exercises |
|---|---|
| Throw TypeError | window.onerror, an in-app stack frame |
| Unhandled rejection | unhandledrejection, a different listener from onerror |
| Capture handled | captureException(error, { level: "warning", fingerprint }) — its own grouping |
| Throw a string | A non-Error throw the SDK has to synthesize an Error for |
| Throw a cause chain | cause three deep, which the detail shows as a chain |
| Trigger server 500 | /api/debug/error → onRequestError in instrumentation.ts |
Turn Exception capture on in the SDK settings first: four of the six throw
rather than report, and with capture off nothing reaches the server. Turn
Exception breadcrumbs on as well to send $exception_steps — every button
records a step before it acts, so whichever one throws carries the trail of the
clicks before it.
/lab/ai)The assistant is a generateText call with one tool, made from a Server
Action. The call site's only analytics line is telemetry.integrations: the
integration created by createReoptAiTelemetry() in
lib/reopt/ai-telemetry.ts sees every model
step and tool execution through the AI SDK's own hooks.
| Event | Where it comes from |
|---|---|
$ai_trace | withAiTrace("assistant-turn") in lib/shop/assistant.ts wraps the turn |
$ai_span | captureAiSpan() for the catalogue read, then one per lookupProduct run |
$ai_generation | One per model step, automatic |
$ai_feedback | Browser track() with the trace id the action returned |
Three decisions the page makes visible:
getReopt(). The integration resolves its
client on every callback, and a streamed response ends after the handler has
returned, when the request store may be gone. The visitor travels in
runtimeContext instead, switched on per key in includeRuntimeContext, and
the context hook moves it onto the event's device and profile.registerTelemetry(). Next bundles ai into each server
module graph, and the AI SDK's global registry is module state of ai, so a
registration made in one graph is invisible to a call made in another.
Passing telemetry.integrations is explicit, works from every graph, and is
what the unit test hands a fake client through.AI_GATEWAY_API_KEY a scripted
MockLanguageModelV4 from ai/test answers through the same generateText,
so a fresh checkout produces real telemetry with made-up token counts. With
the key, the string model id resolves through the Vercel AI Gateway and the
events carry its reported cost.Without server credentials the integration's client source returns null and
it records nothing; the assistant still answers.
lib/reopt/feature-map.ts is the source of truth
for the in-app /guide page. Update it and this table together whenever an SDK
capability changes.
| Area | API | Reference |
|---|---|---|
| Browser | sessionReplay.publicAssets | components/reopt/replay-analytics-provider.tsx · scripts/build-replay-assets.mjs |
| Browser | sessionReplay / setConsent("replay") / flushReplay() | components/reopt/replay-lab.tsx · components/reopt/analytics-provider.tsx |
| Browser | <ReoptProvider config bootstrap> | app/layout.tsx · components/reopt/analytics-provider.tsx |
| Browser | <ReoptPageView /> / pageView() | components/reopt/analytics-provider.tsx · components/reopt/manual-page-view.tsx |
| Browser | <ReoptWebVitals /> | components/reopt/analytics-provider.tsx · components/reopt/web-vitals-table.tsx |
| Browser | normalizePath | lib/reopt/normalize-path.ts |
| Browser | init({ properties }) / register() | components/reopt/analytics-provider.tsx · components/reopt/manual-page-view.tsx |
| Browser | track() | components/reopt/product-view-event.tsx · components/reopt/checkout-view-event.tsx · components/shop/add-to-cart.tsx · components/shop/cart-lines.tsx |
| Browser | identify() / reset() | components/shop/account-panel.tsx |
| Browser | getDeviceId() | components/shop/checkout-form.tsx |
| Browser | capture.exceptions / captureException() | components/reopt/instrumentation-lab.tsx |
| Browser | captureException(error, { level, fingerprint }) | components/reopt/error-lab.tsx |
| Browser | capture.exceptionSteps / |
lib/reopt/tenants.ts is safe for proxy.ts because
it exposes only the public write key and endpoint. The separate
lib/reopt/credentials.ts imports server-only.
Replace the public adapter with a host-aware project store if your application
serves multiple projects; keep credentials behind the same server boundary.
The banner decides analytics and marketing together, but
app/api/consent/route.ts records only
analytics in the consent cookie, because that is the category the proxy's
opt-out reads. The server SDK sends an event with any other consentCategory
only when that cookie grants the category, and refuses an undecided one with
consent_denied. This app tracks no such events on the server. If yours does,
record each category the banner decides in the same cookie.
Checkout records the request-verified identity with each demo outbox row.
During development, pnpm forward reads .reopt-example/outbox.json and sends
pending rows through createReoptNode().
The file is a teaching aid, not a production queue. Production keeps demo rows only in process memory so filesystem availability can never affect checkout. Replace the demo outbox with a durable database or queue before adopting this pattern in a real application.
The diagnostic test suite injects the devtool recorder as
ReoptClientConfig.fetch and ReoptClientConfig.observe. It inspects the
sanitized payload and enqueue-time lifecycle produced by the SDK and still
allows the request to reach ingest; it does not make a stubbed request look
successful. The status bar reports the profile (anonymous or signed in)
separately from the signed analytics session, without displaying either
identifier. The recorder and drawer live behind the diagnostic client boundary,
so the production default requests neither.
reopt-data.config.mjs at the root links this repository to its reopt-data
project, the way prisma.config.ts links a schema to a database. It was written
by reopt-data link and holds ids and paths only — keys stay in the environment:
export default defineConfig({
projectId: "…",
organizationId: "…",
events: {
file: "./reopt-data.events.json",
types: "./lib/reopt-events.d.ts",
},
sourcemaps: {
dir: ".next/static",
urlPrefix: "http://localhost:4100/_next/static",
},
});
Every reopt-data command reads its --project-id, --file, --dir … from
here when the flag is absent, so the scripts below carry no ids.
reopt-data config show prints what resolved.
reopt-data.events.json declares the app's navigation, product, cart, checkout,
order, and diagnostic events — including the complete
product.viewed → cart.added → checkout.started → order.completed funnel —
with their display names, statuses, conversion flag, and low-cardinality rollup
properties. The file is the truth: CI pushes it, so the catalogue is bound to
the same commit as the code that emits the events, instead of waiting for
someone to open the dashboard.
pnpm events:verify # CI gate — exit 8 when the file and the server disagree
pnpm events:push # apply the file (metric-changing and expensive changes included)
pnpm events:types # regenerate lib/reopt-events.d.ts for a typed track()
reopt-data.events.lock.json records the last sync so reopt-data event diff
can tell an edit in this file from an edit made in the console. An event removed
from the file is archived, never deleted.
A production stack names a minified chunk, which is not somewhere anyone can go
and fix. Two build steps make it readable, wired here as postbuild:
reopt-data sourcemap inject
reopt-data sourcemap upload --release "$VERCEL_GIT_COMMIT_SHA"
Both come from @reopt-ai/data-cli;
the build directory and URL prefix come from the config. The upload reads each
chunk's trailing //# sourceMappingURL to find its map — not the chunk's
name plus .map, which is wrong under turbopack — and uploads it under the URL
a stack frame will actually name. Only maps the server does not already have
are sent.
sourcemap inject appends a content-derived id to each chunk and the SDK
stamps it onto frames, so the maps keep matching when the host, the base path,
or the preview URL changes. It is optional; skip it and matching falls back to
the URL.
This app is one project with its own build, so it uploads by URL. A deployment that renders many projects — a brand front end on each brand's own domain — uploads once for the whole bundle instead, keyed by path:
reopt-data sourcemap upload --path-prefix /_next/static --host-app studio-web --quiet
The origin is the part that differs per brand, so it is the part the ref leaves
out. Each project then points at the host app in its settings. --quiet keeps
progress off the build log; the summary and any failure still print.
next.config.ts sets productionBrowserSourceMaps: true — without it the
production build emits no browser maps and there is nothing to upload. Note
that it also serves those maps publicly.
postbuild decides what to do from the environment
(scripts/upload-sourcemaps.mjs):
| Environment | What runs |
|---|---|
| no credentials | --dry-run — a clone still builds |
REOPT_DATA_ORG_KEY (or the platform key) | a real upload |
…plus REOPT_DATA_DELETE_MAPS=1 | a real upload, then --delete-after-upload |
REOPT_DATA_ASSET_PREFIX | overrides the config's URL prefix |
Delete the maps after uploading them, in production. next.config.ts
serves them publicly, which means the deployment hands anyone the original
source next to the bundle that minified it. That is deliberate here — this app
exists to be read, and the devtool demo shows maps resolving — but it is not
what you want in your own deployment.
--delete-after-upload removes each .map the run actually stored. A map that
failed to upload is left alone: it is the only copy, and the next run needs it
to retry. A partial failure exits 6, so CI notices.
Setting REOPT_DATA_DELETE_MAPS=1 without credentials prints a warning and
still does nothing — a dry run stores nothing, so there is nothing to delete.
pnpm check # formatting, lint, types, unit tests, feature map and Git history safety
pnpm e2e # development server, fail-open and integration contracts
pnpm e2e:production # secure default plus explicit diagnostic production run
pnpm e2e:roundtrip # optional browser → ingest → Query API verification
The round-trip suite requires REOPT_DATA_WRITE_KEY,
REOPT_DATA_PROJECT_ID, REOPT_DATA_CLIENT_ID, and
REOPT_DATA_CLIENT_SECRET; otherwise it skips. To validate an existing
deployment, set SHOP_DEPLOYED_URL=https://... and run pnpm e2e:deployed.
This repository intentionally does not use GitHub Actions. Contributors run the required checks locally and report results in the pull request.
[email protected] / shop-demo-1234[email protected] / shop-demo-1234The accounts and all commerce data are recreated after a process restart. This is an SDK integration reference, not a commerce starter: replace its in-memory authentication, carts, orders, and outbox with your own persistence, authorization, privacy, rate limiting, and operational controls.
The reopt-skills repository ships
two agent skills for the Data SDK. They install into Claude Code, Codex, Cursor,
and other agents through the skills CLI and treat this repository as the
assembled reference app — the place to see multi-tenant resolution, the ingest
proxy, consent, identity, and the devtool working together in one Next.js app.
npx skills add reopt-ai/reopt-skills/data-sdk-install # first install or upgrade
npx skills add reopt-ai/reopt-skills/data-sdk-review # read-only integration audit
data-sdk-install pins a reopt/data-sdk-agent-rules block into the
consumer project's AGENTS.md, connects existing credentials (it never
creates Data resources), wires the Next.js proxy / bootstrap / provider, and
routes everything else to the installed package READMEs.data-sdk-review audits an existing integration for credential
boundaries, proxy and bootstrap behavior, identity, consent, delivery, and
production devtool exposure without editing code.Both skills are version-gated against the SDK floor recorded in COMPATIBILITY.md. When the skills point you here, start from the capability map and the patterns worth copying.
Read CONTRIBUTING.md before proposing a change, SECURITY.md before reporting a vulnerability, and CODE_OF_CONDUCT.md for community expectations.
This example is available under the MIT License.
/debug/replay is available only when diagnostics are enabled. Enable the project's replay setting in the Data console, enable the replay SDK flag in the lab, then explicitly agree to record. A run ID correlates the ordinary replay.lab.started event with the session. Use the layout and input controls, capture a synthetic error, flush, and open Session replay in that project. Layout changes emit replay.lab.layout_changed; synthetic errors use captureException(). Select either in the replay timeline to jump to that moment, or filter the timeline to errors. Timeline events are scoped to the recording time range and analytics session; simultaneous tabs can contribute events. Withdraw consent to discard pending DOM data.
The default policy masks input values and all visible text, including CSS-generated content, blocks data-reopt-replay-block subtrees, removes URL query strings, and excludes unapproved image/font resources, canvas, console and network bodies. The layout control also updates a CSS rule so incremental masking can be inspected. Use synthetic values in this lab. Recordings require an existing materialized analytics session and a server upload grant; a successful analytics flush alone does not prove replay acceptance.
pnpm dev:stack respects explicitly supplied environment variables before ignored local fixture values, so an isolated Data test stack can be exercised without rewriting saved project credentials. Hand off in pnpm sdk:npm mode.
The replay lab explicitly configures one public product image and JetBrains Mono
font through sessionReplay.publicAssets. lib/reopt/replay-public-assets.json
is generated from reviewed repository files with node scripts/build-replay-assets.mjs
and loaded only by the diagnostic provider. Image pixels/font metadata are not
masked: never add visitor uploads, avatars or personalized assets. Files are
embedded in private recordings; replay makes no requests to the original site.
The font license is in public/fonts/replay/OFL.txt.
In the Data console, search recordings by creation time, session ID, duration, page path and errors. Path/error conditions use events within the recording's analytics session and time range, including simultaneous tabs. Sparse searches may need Continue search to inspect older recordings. The roundtrip checks that the embedded image decodes and the embedded font loads inside the player.
The consent lab and e2e/consent-withdrawal.spec.ts exercise a queued event followed by
withdrawal in the same browser task. Re-granting allows new events but must not
revive the revoked event. The fix shipped in client 0.8.0, so the spec runs in
the default suite. Package tests additionally cover project-scoped
offline recovery, concurrent unload delivery, final in-flight flushes, anonymous
request isolation, credential rotation, and transport cancellation.
pnpm verify:full needs Docker and the sibling reopt-data checkout (or
REOPT_DATA_PATH). It creates a disposable Data stack, project and credentials,
then runs development E2E, both production configurations and the materialized
query roundtrip. It enables replay only in that fixture and drains only its
queues. Both roundtrips, including the session replay recording in the
development and diagnostic production runs, read the one fixture named by
REOPT_ROUNDTRIP_TEST_FIXTURE through e2e/isolated.ts, which refuses a
fixture from another run or a non-loopback origin. Existing developer servers, saved project credentials and deployments
are not used. Run, ingest and query request ids remain available on failure.
pnpm e2e remains available for standalone browser contracts.
pnpm verify:quick accepts only the exact SDK overrides managed by sdk:local
and sdk:tarball; secret and Git-history checks remain active. pnpm check
and pnpm public:safety still require npm mode for handoff/publishing.
Switching away from npm saves the exact workspace and lockfile under ignored
.sdk-local; pnpm sdk:npm restores both and installs with a frozen lockfile.
If package configuration changes during development, restoration stops with a
reconciliation error rather than overwriting those edits.
Candidate regressions in e2e/candidate run only with local/tarball packages
until their SDK fixes are published, then move into e2e/; published npm
contracts remain executable independently. e2e/offline-recovery.spec.ts, promoted
with client 0.8.0, closes two disconnected tabs, opens two new documents and
checks that both stored event ids reach real ingest once and disappear from the
durable journal; it skips without a reachable project.
addExceptionStep()components/reopt/error-lab.tsx |
| Browser | consent.persist:false / setConsent() | components/reopt/consent-banner.tsx · app/api/consent/route.ts · e2e/consent-withdrawal.spec.ts |
| Browser | config.fetch / config.observe | lib/reopt/devtools.ts · components/reopt/diagnostic-analytics-provider.tsx · components/reopt/devtools-drawer.tsx |
| Browser | Retry-After quota hold / RecordedBatch.deferred | e2e/quota-hold.spec.ts |
| Browser | reopt-sdk request header | e2e/analytics-contract.spec.ts |
| Browser | flush() / pauseTracking() / resumeTracking() | components/reopt/instrumentation-lab.tsx · e2e/offline-recovery.spec.ts |
| Server | createReopt({ writeKey, credentials, getProfileId }) | lib/reopt/server.ts |
| Server | getBootstrap() | app/layout.tsx |
| Server | getReopt().track() | app/actions.ts · app/api/orders/route.ts |
| Server | createOnRequestError() | instrumentation.ts · app/api/boom/route.ts |
| Server | createReoptAiTelemetry() / telemetry.integrations | lib/reopt/ai-telemetry.ts · lib/shop/assistant-model.ts |
| Server | withAiTrace() / captureAiSpan() / runtimeContext | lib/shop/assistant.ts · app/lab/ai/actions.ts |
| Browser | track("$ai_feedback") | components/reopt/ai-lab.tsx |
| Proxy | reoptProxy({ writeKey: resolver, proxy: true }) | proxy.ts · e2e/security-boundaries.spec.ts |
| Node | createReoptNode() + identity.deviceId | scripts/forward.ts · lib/shop/outbox.ts |
| Test | window.__reoptDevtools | e2e/*.spec.ts |
| Test | ingest requestId → Query API requestId | e2e/roundtrip.spec.ts · lib/reopt/scenarios.ts |