atune 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { AtuneAccount } from "./capture.js";
2
+ export type GetAccount = (request: Request) => AtuneAccount | null | Promise<AtuneAccount | null>;
3
+ type OptionalImporter = (moduleName: string) => Promise<unknown>;
4
+ export type ResolveAccountOptions = {
5
+ getAccount?: GetAccount;
6
+ importer?: OptionalImporter;
7
+ };
8
+ export declare function resolveAtuneAccount(request: Request, options?: ResolveAccountOptions): Promise<AtuneAccount | null>;
9
+ export declare function resolveClerk(importer?: OptionalImporter): Promise<AtuneAccount | null>;
10
+ export declare function resolveNextAuth(importer?: OptionalImporter): Promise<AtuneAccount | null>;
11
+ export declare function resolveSupabase(importer?: OptionalImporter): Promise<AtuneAccount | null>;
12
+ export {};
package/dist/auth.js ADDED
@@ -0,0 +1,56 @@
1
+ const optionalImport = (moduleName) => import(/* webpackIgnore: true */ /* turbopackIgnore: true */ moduleName);
2
+ export async function resolveAtuneAccount(request, options = {}) {
3
+ if (options.getAccount)
4
+ return options.getAccount(request);
5
+ const importer = options.importer ?? optionalImport;
6
+ return (await resolveClerk(importer)) ?? (await resolveNextAuth(importer)) ?? (await resolveSupabase(importer)) ?? null;
7
+ }
8
+ export async function resolveClerk(importer = optionalImport) {
9
+ try {
10
+ const mod = (await importer("@clerk/nextjs/server"));
11
+ const session = await mod.auth?.();
12
+ if (session?.userId)
13
+ return { accountId: session.orgId ?? session.userId, userId: session.userId };
14
+ }
15
+ catch { }
16
+ return null;
17
+ }
18
+ export async function resolveNextAuth(importer = optionalImport) {
19
+ try {
20
+ const mod = (await importer("next-auth"));
21
+ const session = mod.getServerSession ? await mod.getServerSession() : mod.auth ? await mod.auth() : null;
22
+ const user = session?.user;
23
+ if (!user)
24
+ return null;
25
+ const account = user.orgId ?? user.organizationId ?? user.teamId ?? user.tenantId ?? user.companyId ?? user.id ?? user.sub ?? user.email;
26
+ if (!account)
27
+ return null;
28
+ const userId = user.id ?? user.sub ?? user.email;
29
+ return { accountId: String(account), userId: userId == null ? undefined : String(userId) };
30
+ }
31
+ catch { }
32
+ return null;
33
+ }
34
+ export async function resolveSupabase(importer = optionalImport) {
35
+ try {
36
+ const nextHeaders = (await importer("next/headers"));
37
+ const supabaseMod = (await importer("@supabase/ssr"));
38
+ const store = await nextHeaders.cookies?.();
39
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
40
+ const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
41
+ if (!store || !supabaseUrl || !supabaseKey || !supabaseMod.createServerClient)
42
+ return null;
43
+ const supabase = supabaseMod.createServerClient(supabaseUrl, supabaseKey, {
44
+ cookies: { getAll: () => store.getAll(), setAll: () => { } },
45
+ });
46
+ const { data } = await supabase.auth.getSession();
47
+ const user = data.session?.user;
48
+ if (!user)
49
+ return null;
50
+ const meta = { ...(user.app_metadata ?? {}), ...(user.user_metadata ?? {}) };
51
+ const account = meta.org_id ?? meta.organization_id ?? meta.team_id ?? meta.tenant_id ?? meta.company_id ?? user.id;
52
+ return { accountId: String(account), userId: user.id };
53
+ }
54
+ catch { }
55
+ return null;
56
+ }
@@ -0,0 +1,15 @@
1
+ export declare function normalizeRoute(pathname: string): string;
2
+ export declare function captureEventName(method: string, pathname: string): string;
3
+ export type AtuneAccount = {
4
+ accountId: string;
5
+ userId?: string;
6
+ };
7
+ export type CaptureEvent = {
8
+ event: string;
9
+ accountId: string;
10
+ userId?: string;
11
+ properties: {
12
+ statusCode: number;
13
+ };
14
+ };
15
+ export declare function buildCaptureEvent(account: AtuneAccount | null, method: string, pathname: string, statusCode: number): CaptureEvent | null;
@@ -0,0 +1,31 @@
1
+ // Canonical capture logic for Atune's auto-instrumenting middleware.
2
+ // Behavior only: identity traits never flow through capture.
3
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+ const CUID = /^c[a-z0-9]{20,}$/i;
5
+ const LONG_HEX = /^[0-9a-f]{16,}$/i;
6
+ const NUMERIC = /^\d+$/;
7
+ export function normalizeRoute(pathname) {
8
+ const clean = (pathname || "/").split("?")[0].split("#")[0];
9
+ const segments = clean.split("/").filter(Boolean);
10
+ if (segments.length === 0)
11
+ return "/";
12
+ const normalized = segments.map((seg) => {
13
+ if (NUMERIC.test(seg) || UUID.test(seg) || CUID.test(seg) || LONG_HEX.test(seg))
14
+ return ":id";
15
+ return seg.toLowerCase();
16
+ });
17
+ return "/" + normalized.join("/");
18
+ }
19
+ export function captureEventName(method, pathname) {
20
+ return `http.${(method || "GET").toLowerCase()}.${normalizeRoute(pathname)}`;
21
+ }
22
+ export function buildCaptureEvent(account, method, pathname, statusCode) {
23
+ if (!account?.accountId)
24
+ return null;
25
+ return {
26
+ event: captureEventName(method, pathname),
27
+ accountId: account.accountId,
28
+ userId: account.userId,
29
+ properties: { statusCode },
30
+ };
31
+ }
@@ -0,0 +1,18 @@
1
+ export type InitOptions = {
2
+ cwd?: string;
3
+ apiKey?: string;
4
+ atuneUrl?: string;
5
+ install?: boolean;
6
+ log?: (message: string) => void;
7
+ };
8
+ export type InitResult = {
9
+ framework: "next" | "unknown";
10
+ auth: "clerk" | "next-auth" | "supabase" | "unknown";
11
+ changedFiles: string[];
12
+ installed: boolean;
13
+ manualFallback: boolean;
14
+ };
15
+ export declare function initProject(options?: InitOptions): Promise<InitResult>;
16
+ export declare function detectAuth(deps: Record<string, string>): InitResult["auth"];
17
+ export declare function patchNextMiddleware(cwd: string, auth: InitResult["auth"]): string | null;
18
+ export declare function appendEnv(cwd: string, apiKey: string, atuneUrl: string): string | null;
@@ -0,0 +1,126 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import readline from "node:readline/promises";
5
+ import { stdin as input, stdout as output } from "node:process";
6
+ export async function initProject(options = {}) {
7
+ const cwd = options.cwd ?? process.cwd();
8
+ const log = options.log ?? console.log;
9
+ const pkg = readPackage(cwd);
10
+ const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
11
+ const framework = deps.next ? "next" : "unknown";
12
+ const auth = detectAuth(deps);
13
+ const changedFiles = [];
14
+ if (framework !== "next") {
15
+ log("Atune could not auto-wire this framework yet. Manually call createAtuneMiddleware() around your server requests.");
16
+ return { framework, auth, changedFiles, installed: false, manualFallback: true };
17
+ }
18
+ if (options.install !== false) {
19
+ installSdk(cwd);
20
+ }
21
+ const middlewarePath = patchNextMiddleware(cwd, auth);
22
+ if (middlewarePath)
23
+ changedFiles.push(middlewarePath);
24
+ const apiKey = options.apiKey ?? (await promptForApiKey());
25
+ const envPath = appendEnv(cwd, apiKey, options.atuneUrl ?? "https://useatune.com");
26
+ if (envPath)
27
+ changedFiles.push(envPath);
28
+ log("Atune is capturing. Add identify() on your login route to name your users.");
29
+ return { framework, auth, changedFiles, installed: options.install !== false, manualFallback: false };
30
+ }
31
+ export function detectAuth(deps) {
32
+ if (deps["@clerk/nextjs"])
33
+ return "clerk";
34
+ if (deps["next-auth"])
35
+ return "next-auth";
36
+ if (deps["@supabase/ssr"] || deps["@supabase/supabase-js"])
37
+ return "supabase";
38
+ return "unknown";
39
+ }
40
+ export function patchNextMiddleware(cwd, auth) {
41
+ const srcProxy = path.join(cwd, "src", "proxy.ts");
42
+ const rootProxy = path.join(cwd, "proxy.ts");
43
+ const rootMiddleware = path.join(cwd, "middleware.ts");
44
+ const target = [srcProxy, rootProxy, rootMiddleware].find((file) => existsSync(file)) ?? rootProxy;
45
+ const existing = existsSync(target) ? readFileSync(target, "utf8") : "";
46
+ if (existing.includes("atune") && existing.includes("createAtuneMiddleware"))
47
+ return null;
48
+ const next = existing ? patchExistingMiddleware(existing, auth) : freshMiddleware(auth);
49
+ writeFileSync(target, next);
50
+ return path.relative(cwd, target);
51
+ }
52
+ export function appendEnv(cwd, apiKey, atuneUrl) {
53
+ const envPath = path.join(cwd, ".env.local");
54
+ const existing = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
55
+ const additions = [];
56
+ if (!/^ATUNE_API_KEY=/m.test(existing))
57
+ additions.push(`ATUNE_API_KEY=${apiKey}`);
58
+ if (!/^ATUNE_URL=/m.test(existing))
59
+ additions.push(`ATUNE_URL=${atuneUrl}`);
60
+ if (additions.length === 0)
61
+ return null;
62
+ const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
63
+ writeFileSync(envPath, `${existing}${prefix}${additions.join("\n")}\n`);
64
+ return ".env.local";
65
+ }
66
+ function patchExistingMiddleware(source, auth) {
67
+ let next = source;
68
+ if (!next.includes("createAtuneMiddleware")) {
69
+ next = `import { createAtuneMiddleware } from "atune";\n${next}`;
70
+ }
71
+ if (!next.includes("const atuneMiddleware = createAtuneMiddleware();")) {
72
+ next = next.replace(/((?:import[^\n]+\n)+)/, "$1\nconst atuneMiddleware = createAtuneMiddleware();\n");
73
+ }
74
+ if (auth === "clerk" && next.includes("clerkMiddleware") && !next.includes("return atuneMiddleware(request);")) {
75
+ next = next.replace(/\n\s*}\);\s*\n/, "\n return atuneMiddleware(request);\n});\n");
76
+ return next;
77
+ }
78
+ return `${next.trimEnd()}\n\n// Atune capture is installed. If this proxy returns a custom response,\n// call atuneMiddleware(request, response) before returning it.\n`;
79
+ }
80
+ function freshMiddleware(auth) {
81
+ if (auth === "clerk") {
82
+ return `import { clerkMiddleware } from "@clerk/nextjs/server";
83
+ import { createAtuneMiddleware } from "atune";
84
+
85
+ const atuneMiddleware = createAtuneMiddleware();
86
+
87
+ export default clerkMiddleware(async (_auth, request) => {
88
+ return atuneMiddleware(request);
89
+ });
90
+
91
+ export const config = {
92
+ matcher: ["/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)"],
93
+ };
94
+ `;
95
+ }
96
+ return `import { createAtuneMiddleware } from "atune";
97
+
98
+ const atuneMiddleware = createAtuneMiddleware();
99
+
100
+ export default function proxy(request: Request) {
101
+ return atuneMiddleware(request);
102
+ }
103
+
104
+ export const config = {
105
+ matcher: ["/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)"],
106
+ };
107
+ `;
108
+ }
109
+ function readPackage(cwd) {
110
+ const pkgPath = path.join(cwd, "package.json");
111
+ if (!existsSync(pkgPath))
112
+ return {};
113
+ return JSON.parse(readFileSync(pkgPath, "utf8"));
114
+ }
115
+ function installSdk(cwd) {
116
+ execFileSync("npm", ["i", "atune"], { cwd, stdio: "inherit" });
117
+ }
118
+ async function promptForApiKey() {
119
+ const rl = readline.createInterface({ input, output });
120
+ try {
121
+ return (await rl.question("Paste your ATUNE_API_KEY: ")).trim();
122
+ }
123
+ finally {
124
+ rl.close();
125
+ }
126
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { initProject } from "./cli-core.js";
3
+ import { startMcpServer } from "./mcp.js";
4
+ const [, , command] = process.argv;
5
+ if (command === "mcp") {
6
+ startMcpServer();
7
+ }
8
+ else if (command === "init") {
9
+ initProject().catch((err) => {
10
+ console.error(err instanceof Error ? err.message : String(err));
11
+ process.exit(1);
12
+ });
13
+ }
14
+ else {
15
+ console.log("Usage: npx atune init\n npx atune mcp");
16
+ process.exit(command ? 1 : 0);
17
+ }
@@ -0,0 +1,20 @@
1
+ import type { AtuneAccount } from "./capture.js";
2
+ export type IdentifyTraits = {
3
+ name?: string;
4
+ email?: string;
5
+ };
6
+ export type GroupTraits = {
7
+ company?: string;
8
+ };
9
+ export type AtuneRequestOptions = {
10
+ accountId?: string;
11
+ apiKey?: string;
12
+ url?: string;
13
+ fetch?: typeof fetch;
14
+ timeoutMs?: number;
15
+ };
16
+ export declare function identify(userId: string, traits?: IdentifyTraits, options?: AtuneRequestOptions): Promise<void>;
17
+ export declare function group(accountId: string, traits?: GroupTraits, options?: AtuneRequestOptions): Promise<void>;
18
+ export declare function postEvent(account: AtuneAccount, event: string, properties: Record<string, unknown>, options?: AtuneRequestOptions): Promise<void>;
19
+ export declare function eventsEndpoint(url?: string): string;
20
+ export declare function identifyEndpoint(url?: string): string;
package/dist/client.js ADDED
@@ -0,0 +1,51 @@
1
+ export async function identify(userId, traits = {}, options = {}) {
2
+ if (!userId)
3
+ return;
4
+ await postIdentify({ accountId: options.accountId ?? userId, userId, traits }, options);
5
+ }
6
+ export async function group(accountId, traits = {}, options = {}) {
7
+ if (!accountId)
8
+ return;
9
+ await postIdentify({ accountId, traits }, options);
10
+ }
11
+ export function postEvent(account, event, properties, options = {}) {
12
+ const body = { accountId: account.accountId, userId: account.userId, event, properties };
13
+ return postJson(eventsEndpoint(options.url), body, options);
14
+ }
15
+ async function postIdentify(body, options) {
16
+ await postJson(identifyEndpoint(options.url), body, options);
17
+ }
18
+ async function postJson(endpoint, body, options) {
19
+ const apiKey = options.apiKey ?? process.env.ATUNE_API_KEY;
20
+ if (!apiKey || !endpoint)
21
+ return;
22
+ const controller = new AbortController();
23
+ const timeoutId = setTimeout(() => controller.abort(), options.timeoutMs ?? 1000);
24
+ try {
25
+ await (options.fetch ?? fetch)(endpoint, {
26
+ method: "POST",
27
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
28
+ body: JSON.stringify(body),
29
+ signal: controller.signal,
30
+ });
31
+ }
32
+ finally {
33
+ clearTimeout(timeoutId);
34
+ }
35
+ }
36
+ export function eventsEndpoint(url = process.env.ATUNE_URL ?? "") {
37
+ return endpointFor(url, "/api/events");
38
+ }
39
+ export function identifyEndpoint(url = process.env.ATUNE_URL ?? "") {
40
+ return endpointFor(url, "/api/identify");
41
+ }
42
+ function endpointFor(rawUrl, path) {
43
+ if (!rawUrl)
44
+ return "";
45
+ const url = rawUrl.replace(/\/$/, "");
46
+ if (url.endsWith("/api/events"))
47
+ return path === "/api/events" ? url : url.replace(/\/api\/events$/, path);
48
+ if (url.endsWith("/api/identify"))
49
+ return path === "/api/identify" ? url : url.replace(/\/api\/identify$/, path);
50
+ return `${url}${path}`;
51
+ }
@@ -0,0 +1,10 @@
1
+ export { buildCaptureEvent, captureEventName, normalizeRoute } from "./capture.js";
2
+ export type { AtuneAccount, CaptureEvent } from "./capture.js";
3
+ export { identify, group } from "./client.js";
4
+ export type { AtuneRequestOptions, GroupTraits, IdentifyTraits } from "./client.js";
5
+ export { understand, understandEndpoint } from "./understand.js";
6
+ export type { UnderstandCitation, UnderstandOptions, UnderstandResult } from "./understand.js";
7
+ export { createAtuneMiddleware } from "./middleware.js";
8
+ export type { AtuneMiddlewareOptions } from "./middleware.js";
9
+ export { resolveAtuneAccount, resolveClerk, resolveNextAuth, resolveSupabase } from "./auth.js";
10
+ export type { GetAccount, ResolveAccountOptions } from "./auth.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { buildCaptureEvent, captureEventName, normalizeRoute } from "./capture.js";
2
+ export { identify, group } from "./client.js";
3
+ export { understand, understandEndpoint } from "./understand.js";
4
+ export { createAtuneMiddleware } from "./middleware.js";
5
+ export { resolveAtuneAccount, resolveClerk, resolveNextAuth, resolveSupabase } from "./auth.js";
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function startMcpServer(input?: NodeJS.ReadStream & {
2
+ fd: 0;
3
+ }, output?: NodeJS.WriteStream & {
4
+ fd: 1;
5
+ }): void;
package/dist/mcp.js ADDED
@@ -0,0 +1,88 @@
1
+ import { understand } from "./understand.js";
2
+ export function startMcpServer(input = process.stdin, output = process.stdout) {
3
+ input.setEncoding("utf8");
4
+ let buffer = "";
5
+ input.on("data", (chunk) => {
6
+ buffer += chunk;
7
+ const lines = buffer.split(/\r?\n/);
8
+ buffer = lines.pop() ?? "";
9
+ for (const line of lines) {
10
+ if (line.trim())
11
+ void handleLine(line, output);
12
+ }
13
+ });
14
+ }
15
+ async function handleLine(line, output) {
16
+ let request;
17
+ try {
18
+ request = JSON.parse(line);
19
+ }
20
+ catch {
21
+ write(output, { jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
22
+ return;
23
+ }
24
+ try {
25
+ if (request.method === "initialize") {
26
+ write(output, {
27
+ jsonrpc: "2.0",
28
+ id: request.id ?? null,
29
+ result: {
30
+ protocolVersion: "2024-11-05",
31
+ capabilities: { tools: {} },
32
+ serverInfo: { name: "atune", version: "0.1.0" },
33
+ },
34
+ });
35
+ return;
36
+ }
37
+ if (request.method === "tools/list") {
38
+ write(output, {
39
+ jsonrpc: "2.0",
40
+ id: request.id ?? null,
41
+ result: {
42
+ tools: [
43
+ {
44
+ name: "understand",
45
+ description: "Ask Atune a grounded, cited question about a customer entity.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ entity: {
50
+ type: "string",
51
+ description: "One of: account:<externalId>, user:<accountExternalId>/<userExternalId>, feature:<key>, portfolio",
52
+ },
53
+ question: { type: "string" },
54
+ },
55
+ required: ["entity", "question"],
56
+ },
57
+ },
58
+ ],
59
+ },
60
+ });
61
+ return;
62
+ }
63
+ if (request.method === "tools/call") {
64
+ const name = request.params?.name;
65
+ const args = (request.params?.arguments ?? {});
66
+ if (name !== "understand" || typeof args.entity !== "string" || typeof args.question !== "string") {
67
+ write(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32602, message: "Invalid understand tool arguments" } });
68
+ return;
69
+ }
70
+ const result = await understand(args.entity, args.question);
71
+ write(output, {
72
+ jsonrpc: "2.0",
73
+ id: request.id ?? null,
74
+ result: { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result },
75
+ });
76
+ return;
77
+ }
78
+ if (request.id != null) {
79
+ write(output, { jsonrpc: "2.0", id: request.id, error: { code: -32601, message: "Method not found" } });
80
+ }
81
+ }
82
+ catch (err) {
83
+ write(output, { jsonrpc: "2.0", id: request.id ?? null, error: { code: -32000, message: err instanceof Error ? err.message : String(err) } });
84
+ }
85
+ }
86
+ function write(output, message) {
87
+ output.write(`${JSON.stringify(message)}\n`);
88
+ }
@@ -0,0 +1,8 @@
1
+ import { type AtuneRequestOptions } from "./client.js";
2
+ import { type GetAccount } from "./auth.js";
3
+ export type AtuneMiddlewareOptions = AtuneRequestOptions & {
4
+ getAccount?: GetAccount;
5
+ };
6
+ type MaybeResponse = Response | undefined | null;
7
+ export declare function createAtuneMiddleware(options?: AtuneMiddlewareOptions): (request: Request, response?: MaybeResponse | Promise<MaybeResponse>) => Promise<Response>;
8
+ export {};
@@ -0,0 +1,45 @@
1
+ import { buildCaptureEvent } from "./capture.js";
2
+ import { postEvent } from "./client.js";
3
+ import { resolveAtuneAccount } from "./auth.js";
4
+ export function createAtuneMiddleware(options = {}) {
5
+ return async function atuneMiddleware(request, response) {
6
+ const resolvedResponse = (await response) ?? (await nextResponse());
7
+ captureAfterResponse(request, resolvedResponse, options);
8
+ return resolvedResponse;
9
+ };
10
+ }
11
+ function captureAfterResponse(request, response, options) {
12
+ try {
13
+ const method = request.method || "GET";
14
+ const pathname = requestPathname(request);
15
+ const statusCode = response.status || 200;
16
+ void Promise.resolve()
17
+ .then(async () => {
18
+ const account = await resolveAtuneAccount(request, { getAccount: options.getAccount });
19
+ const payload = buildCaptureEvent(account, method, pathname, statusCode);
20
+ if (!payload)
21
+ return;
22
+ await postEvent({ accountId: payload.accountId, userId: payload.userId }, payload.event, payload.properties, options);
23
+ })
24
+ .catch(() => { });
25
+ }
26
+ catch { }
27
+ }
28
+ function requestPathname(request) {
29
+ try {
30
+ return new URL(request.url).pathname;
31
+ }
32
+ catch {
33
+ return "/";
34
+ }
35
+ }
36
+ async function nextResponse() {
37
+ try {
38
+ const moduleName = "next/server";
39
+ const mod = (await import(moduleName));
40
+ return mod.NextResponse?.next?.() ?? new Response(null, { status: 200 });
41
+ }
42
+ catch {
43
+ return new Response(null, { status: 200 });
44
+ }
45
+ }
@@ -0,0 +1,20 @@
1
+ export type UnderstandCitation = {
2
+ signal: string;
3
+ value: string;
4
+ when: string;
5
+ };
6
+ export type UnderstandResult = {
7
+ knows: boolean;
8
+ answer: string;
9
+ confidence: number;
10
+ citations: UnderstandCitation[];
11
+ recommendedNext: string | null;
12
+ signals: Record<string, unknown>;
13
+ };
14
+ export type UnderstandOptions = {
15
+ apiKey?: string;
16
+ url?: string;
17
+ fetch?: typeof fetch;
18
+ };
19
+ export declare function understand(entity: string, question: string, options?: UnderstandOptions): Promise<UnderstandResult>;
20
+ export declare function understandEndpoint(rawUrl: string): string;
@@ -0,0 +1,31 @@
1
+ export async function understand(entity, question, options = {}) {
2
+ const apiKey = options.apiKey ?? process.env.ATUNE_API_KEY;
3
+ const endpoint = understandEndpoint(options.url ?? process.env.ATUNE_URL ?? "");
4
+ if (!apiKey)
5
+ throw new Error("ATUNE_API_KEY is required");
6
+ if (!endpoint)
7
+ throw new Error("ATUNE_URL is required");
8
+ const response = await (options.fetch ?? fetch)(endpoint, {
9
+ method: "POST",
10
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
11
+ body: JSON.stringify({ entity, question }),
12
+ });
13
+ if (!response.ok) {
14
+ throw new Error(`Atune understand() failed with ${response.status}: ${await response.text()}`);
15
+ }
16
+ return (await response.json());
17
+ }
18
+ export function understandEndpoint(rawUrl) {
19
+ if (!rawUrl)
20
+ return "";
21
+ const url = rawUrl.replace(/\/$/, "");
22
+ if (url.endsWith("/api/v1/understand"))
23
+ return url;
24
+ if (url.endsWith("/api/v1"))
25
+ return `${url}/understand`;
26
+ if (url.endsWith("/api/events"))
27
+ return url.replace(/\/api\/events$/, "/api/v1/understand");
28
+ if (url.endsWith("/api/identify"))
29
+ return url.replace(/\/api\/identify$/, "/api/v1/understand");
30
+ return `${url}/api/v1/understand`;
31
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "atune",
3
+ "version": "0.1.0",
4
+ "description": "Atune server-side SDK and installer — capture, identify, and understand() your customers.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/KrohnChris/Atune.git",
9
+ "directory": "packages/sdk"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "bin": {
15
+ "atune": "./dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "vitest run"
24
+ },
25
+ "peerDependencies": {
26
+ "next": ">=14"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "next": {
30
+ "optional": true
31
+ }
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5",
35
+ "vitest": "^3.2.6"
36
+ },
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.js"
41
+ },
42
+ "./capture": {
43
+ "types": "./dist/capture.d.ts",
44
+ "import": "./dist/capture.js"
45
+ },
46
+ "./understand": {
47
+ "types": "./dist/understand.d.ts",
48
+ "import": "./dist/understand.js"
49
+ }
50
+ }
51
+ }