@sentia-labs/cli
v0.1.1
Published
Sentia SDK and CLI for AI-native product research, evidence, decisions, and calibration.
Readme
sentia
The Sentia SDK + CLI. One package, three surfaces:
- Browser SDK — capture real product usage (page views, clicks, rage/dead
clicks, and your own
exposure/outcomeevents) from your web app. - Server SDK — send the same events from a Node backend.
- CLI —
sentiacommand for research, populations, and artifacts (needs a secret API key).
npm install sentiaQuick start (browser)
import { Sentia } from '@sentia-labs/cli/browser';
export const sentia = new Sentia({
writeKey: process.env.NEXT_PUBLIC_SENTIA_WRITE_KEY, // public, write-only
});Your write key is a public, write-only credential — safe to ship in a client bundle, exactly like a Stripe publishable key or a Segment write key. It can only send events; it cannot read or export anything. Generate one in Settings → Developer. Reading/exporting data uses a separate secret API key (see CLI & command APIs).
Autocapture (page views, clicks, rage-clicks, dead-clicks) starts automatically.
Tag any element with data-sentia-id="checkout-button" to get stable component
attribution in the friction reports.
Closing the calibration loop
This is the point of the SDK: tie a shipped decision back to the simulation
that predicted it. When a real user is shown the shipped variant, record an
exposure; when they hit the success metric, record an outcome.
// Decision key is minted per simulation. Copy it from the report's
// "Instrument this decision" panel.
sentia.exposure('dec_9f2a1c04b7e3', 'variant_a');
// Later, when the user completes the success action:
sentia.outcome('activation_rate', 1);Sentia measures the real conversion rate for that decision and scores it against the simulation's forecast — your Decision Hit Rate.
Full event API
sentia.identify('user_123', { plan: 'pro' });
sentia.group('acct_42', { seats: 20 });
sentia.track('checkout_started', { cart_value: 129 });
sentia.exposure('dec_...', 'variant_b');
sentia.outcome('revenue', 129, { unit: 'usd' });
sentia.feedback('The date picker was confusing');
await sentia.flush(); // force-send the queue (also runs on page unload)Consent & privacy
Consent controls (opt-out API):
sentia.optOut(); // stop all capture; clears the queue; persisted
sentia.optIn(); // resume; persisted across reloads
sentia.hasOptedOut(); // -> boolean- Global Privacy Control & Do Not Track are honored automatically. If the
browser sets
navigator.globalPrivacyControl(GPC, legally enforceable under CCPA/CPRA) or Do Not Track, the SDK starts opted out. - Consent-first ("no capture until you say so"): initialize with
new Sentia({ writeKey, optOut: true }), then callsentia.optIn()once the user consents. A persisted user choice always wins over defaults.
Privacy properties of the transport:
- No cookies are sent on ingest (the request is cross-origin and credential-less by design).
- Client IP is never attached to events. Sentia does no IP-based geo-lookup; the raw IP is dropped at the edge (it survives only on the security audit trail, where an IP is defensible).
- Events post as
text/plainwith the write key in the body — a CORS "simple request", so there's no preflight and it works from any customer domain. - On page unload, the queue is flushed via
navigator.sendBeacon(hooked tovisibilitychange/pagehide, never the bfcache-breakingbeforeunload), so the last events before a user leaves aren't lost.
Server (Node) usage
import { Sentia } from '@sentia-labs/cli/node';
const sentia = new Sentia({ writeKey: process.env.SENTIA_WRITE_KEY });
sentia.exposure('dec_9f2a1c04b7e3', 'variant_a', {}, { userId: 'user_123' });
sentia.outcome('activation_rate', 1, {}, { userId: 'user_123' });
await sentia.close(); // flush before the process exitsThe server SDK skips browser autocapture and never touches the DOM.
CLI & command APIs
The sentia CLI and the command methods (runResearch, createPopulation, …)
require a secret API key (sk_...), never the public write key:
const sentia = new Sentia({ apiKey: process.env.SENTIA_API_KEY });
await sentia.runResearch({ question: 'Where will buyers hesitate?', sourceIds: [...] });Passing an SDK write key to a command method throws — write keys can only send events.
Bundle size
The capture core is a thin fetch/sendBeacon client. The heavier autocapture
engine (posthog-js, run capture-only and first-party) is lazy-loaded via a
dynamic import(), so it lands in its own chunk and only when autocapture is on —
your initial bundle stays small.
Configuration
| Option | Default | Notes |
| ----------------- | --------------------------- | -------------------------------------------- |
| writeKey | — | Public, write-only. Required to send events. |
| apiKey | — | Secret. Required for CLI/command APIs only. |
| baseUrl | https://api.sentialabs.ai | Ingest host. |
| optOut | false | Start opted out (consent-first). |
| flushAt | 10 | Flush after N queued events. |
| flushIntervalMs | 10000 | Flush at least this often. |
| autocapture | true | false to disable, or an options object. |
| context | {} | Default context merged into every event. |
