apptvty 0.3.1 → 0.4.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.
@@ -119,6 +119,28 @@ var ApptvtyClient = class {
119
119
  this.warn("Failed to log impression (billing may be delayed):", err);
120
120
  }
121
121
  }
122
+ // ─── Programmatic Indexing ───────────────────────────────────────────────────
123
+ /**
124
+ * Manually push text or markdown directly into your site's RAG vector database.
125
+ * This instantly embeds the content for AI agents to query, bypassing the
126
+ * need for a scheduled HTML crawl. This is ideal for Client-Side Rendered (CSR)
127
+ * Apps or for injecting private institutional facts/databases.
128
+ *
129
+ * @example
130
+ * const result = await client.pushKnowledge({
131
+ * title: "Capitol Technology University Profile",
132
+ * content: "Here are all the detailed specs, majors, and acceptances...",
133
+ * url: "https://thecollegeradar.com/institution/499" // Optional reference
134
+ * });
135
+ */
136
+ async pushKnowledge(params) {
137
+ const payload = {
138
+ title: params.title,
139
+ content: params.content,
140
+ url: params.url
141
+ };
142
+ return this.post(`/v1/sites/${this.siteId}/knowledge`, payload);
143
+ }
122
144
  // ─── Analytics (for coding agents) ───────────────────────────────────────────
123
145
  // These allow agents to check activity, logs, and errors without a human.
124
146
  /** Get 30-day traffic overview (requests, AI %, crawlers, queries). */
@@ -366,8 +388,12 @@ var ApptvtyClient = class {
366
388
  let dashboardUrl = (typeof process !== "undefined" ? process.env?.DASHBOARD_URL : void 0) ?? "https://dashboard.apptvty.com/login";
367
389
  try {
368
390
  const json = JSON.parse(text);
391
+ if (json?.error?.code === "INSUFFICIENT_BALANCE") {
392
+ throw new ApptvtyApiError(402, path, text);
393
+ }
369
394
  dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;
370
- } catch {
395
+ } catch (e) {
396
+ if (e instanceof ApptvtyApiError) throw e;
371
397
  }
372
398
  throw new ApptvtyTrialExpiredError(dashboardUrl);
373
399
  }
@@ -1179,7 +1205,8 @@ function withApptvty(config, next) {
1179
1205
  const crawlerInfo = detectCrawler(userAgent);
1180
1206
  const scraperService = detectScraperService(userAgent);
1181
1207
  const aiCrawlerParam = parseBoolParam(request.nextUrl.searchParams.get("ai_crawler"), false);
1182
- const isCrawler = crawlerInfo.isAi || aiCrawlerParam || scraperService.isScraperService;
1208
+ const isAi = crawlerInfo.isAi || aiCrawlerParam;
1209
+ const isScraper = isAi || scraperService.isScraperService || crawlerInfo.name === "unknown_bot";
1183
1210
  if (request.nextUrl.pathname === "/api/apptvty/verify") {
1184
1211
  const challenge = request.nextUrl.searchParams.get("challenge");
1185
1212
  if (challenge) {
@@ -1224,11 +1251,12 @@ function withApptvty(config, next) {
1224
1251
  ip_address: getClientIp(headers),
1225
1252
  user_agent: userAgent,
1226
1253
  referrer: request.headers.get("referer"),
1227
- is_ai_crawler: crawlerInfo.isAi,
1254
+ is_ai_crawler: isAi,
1228
1255
  crawler_type: crawlerInfo.name,
1229
1256
  crawler_organization: crawlerInfo.organization,
1230
1257
  confidence_score: crawlerInfo.confidence,
1231
- scraper_service: scraperService.name
1258
+ scraper_service: scraperService.name,
1259
+ attribution_id: request.nextUrl.searchParams.get("atid")
1232
1260
  };
1233
1261
  const isInternalRequest = request.headers.get("x-apptvty-internal") === "true";
1234
1262
  if (!isInternalRequest && !pathname.startsWith(queryPath)) {
@@ -1237,46 +1265,73 @@ function withApptvty(config, next) {
1237
1265
  event.waitUntil(logger.flush());
1238
1266
  }
1239
1267
  }
1240
- if (isCrawler && !isInternalRequest && !pathname.startsWith(queryPath)) {
1268
+ if (!isInternalRequest && !pathname.startsWith(queryPath) && response.status === 200) {
1241
1269
  try {
1242
- const proxyReq = new Request(request.url, {
1243
- headers: new Headers(request.headers)
1244
- });
1245
- proxyReq.headers.set("x-apptvty-internal", "true");
1246
- const res = await fetch(proxyReq);
1247
- const contentType = res.headers.get("content-type") ?? "";
1248
- if (contentType.includes("text/html")) {
1249
- const html = await res.text();
1250
- let markdown = convertHtmlToMarkdown(html);
1251
- const pageAds = await client.getAdsForPage({ site_id: config.siteId, page_path: pathname });
1252
- if (pageAds.ads && pageAds.ads.length > 0) {
1253
- const ad = pageAds.ads[0];
1254
- markdown += `
1270
+ const pageAds = await client.getAdsForPage({ site_id: config.siteId, page_path: pathname });
1271
+ if (pageAds.ads && pageAds.ads.length > 0) {
1272
+ const ad = pageAds.ads[0];
1273
+ client.logImpression({
1274
+ impression_id: ad.impression_id,
1275
+ site_id: config.siteId,
1276
+ page_path: pathname,
1277
+ agent_ua: userAgent,
1278
+ agent_ip: getClientIp(headers),
1279
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1280
+ }).catch(() => {
1281
+ });
1282
+ if (isAi || scraperService.isScraperService) {
1283
+ const proxyReq = new Request(request.url, { headers: new Headers(request.headers) });
1284
+ proxyReq.headers.set("x-apptvty-internal", "true");
1285
+ const res = await fetch(proxyReq);
1286
+ const contentType = res.headers.get("content-type") ?? "";
1287
+ if (contentType.includes("text/html")) {
1288
+ const html = await res.text();
1289
+ let markdown = convertHtmlToMarkdown(html);
1290
+ markdown += `
1255
1291
 
1256
1292
  ---
1257
1293
  > **Sponsored:** [${ad.text}](${ad.url}) - ${ad.advertiser}
1258
1294
  `;
1259
- client.logImpression({
1260
- impression_id: ad.impression_id,
1261
- site_id: config.siteId,
1262
- page_path: pathname,
1263
- agent_ua: userAgent,
1264
- agent_ip: getClientIp(headers),
1265
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1266
- }).catch(() => {
1267
- });
1295
+ return new import_server.NextResponse(markdown, {
1296
+ status: res.status,
1297
+ headers: {
1298
+ "Content-Type": "text/markdown",
1299
+ "X-Apptvty-AEO": "true",
1300
+ "X-Sponsored-Content": `${ad.text}; url=${ad.url}`
1301
+ }
1302
+ });
1303
+ }
1268
1304
  }
1269
- return new import_server.NextResponse(markdown, {
1270
- status: res.status,
1271
- headers: {
1272
- "Content-Type": "text/markdown",
1273
- "X-Apptvty-AEO": "true"
1305
+ const originalHeaders = headersToRecord(response.headers);
1306
+ if (originalHeaders["content-type"]?.includes("text/html")) {
1307
+ const html = await response.text();
1308
+ const jsonLd = `
1309
+ <script type="application/ld+json">{"@context":"https://schema.org","@type":"CreativeWork","author":{"@type":"Organization","name":"${ad.advertiser}"},"mainEntityOfPage":{"@type":"WebPage","@id":"${ad.url}"},"headline":"Sponsored: ${ad.text}"}</script>
1310
+ `;
1311
+ const stealthDiv = `
1312
+ <div style="display:none !important;visibility:hidden;height:0;width:0;overflow:hidden;" aria-hidden="true" data-apptvty-ad="${ad.impression_id}">Sponsored by ${ad.advertiser}: <a href="${ad.url}">${ad.text}</a></div>
1313
+ `;
1314
+ let modifiedHtml = html;
1315
+ if (html.includes("</head>")) {
1316
+ modifiedHtml = html.replace("</head>", `${jsonLd}</head>`);
1274
1317
  }
1275
- });
1318
+ if (modifiedHtml.includes("</body>")) {
1319
+ modifiedHtml = modifiedHtml.replace("</body>", `${stealthDiv}</body>`);
1320
+ } else {
1321
+ modifiedHtml += stealthDiv;
1322
+ }
1323
+ return new import_server.NextResponse(modifiedHtml, {
1324
+ status: response.status,
1325
+ headers: {
1326
+ ...originalHeaders,
1327
+ "X-Sponsored-Content": `${ad.text}; url=${ad.url}`
1328
+ }
1329
+ });
1330
+ }
1331
+ response.headers.set("X-Sponsored-Content", `${ad.text}; url=${ad.url}`);
1276
1332
  }
1277
- return res;
1278
1333
  } catch (err) {
1279
- if (config.debug) console.warn("[apptvty] Markdown proxy failed:", err);
1334
+ if (config.debug) console.warn("[apptvty] Stealth injection failed:", err);
1280
1335
  }
1281
1336
  }
1282
1337
  return response;