featuredrop 2.7.1 → 3.0.1

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.
Files changed (52) hide show
  1. package/README.md +34 -1
  2. package/dist/astro.cjs +333 -0
  3. package/dist/astro.cjs.map +1 -0
  4. package/dist/astro.d.cts +242 -0
  5. package/dist/astro.d.ts +242 -0
  6. package/dist/astro.js +329 -0
  7. package/dist/astro.js.map +1 -0
  8. package/dist/engine.cjs +552 -0
  9. package/dist/engine.cjs.map +1 -0
  10. package/dist/engine.d.cts +422 -0
  11. package/dist/engine.d.ts +422 -0
  12. package/dist/engine.js +545 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/featuredrop.cjs +208 -1
  15. package/dist/featuredrop.cjs.map +1 -1
  16. package/dist/next.cjs +336 -0
  17. package/dist/next.cjs.map +1 -0
  18. package/dist/next.d.cts +243 -0
  19. package/dist/next.d.ts +243 -0
  20. package/dist/next.js +332 -0
  21. package/dist/next.js.map +1 -0
  22. package/dist/nuxt.cjs +352 -0
  23. package/dist/nuxt.cjs.map +1 -0
  24. package/dist/nuxt.d.cts +282 -0
  25. package/dist/nuxt.d.ts +282 -0
  26. package/dist/nuxt.js +347 -0
  27. package/dist/nuxt.js.map +1 -0
  28. package/dist/preact.cjs +354 -0
  29. package/dist/preact.cjs.map +1 -1
  30. package/dist/preact.d.cts +170 -1
  31. package/dist/preact.d.ts +170 -1
  32. package/dist/preact.js +350 -1
  33. package/dist/preact.js.map +1 -1
  34. package/dist/react-hooks.cjs +82 -0
  35. package/dist/react-hooks.cjs.map +1 -1
  36. package/dist/react-hooks.d.cts +117 -1
  37. package/dist/react-hooks.d.ts +117 -1
  38. package/dist/react-hooks.js +80 -1
  39. package/dist/react-hooks.js.map +1 -1
  40. package/dist/react.cjs +354 -0
  41. package/dist/react.cjs.map +1 -1
  42. package/dist/react.d.cts +170 -1
  43. package/dist/react.d.ts +170 -1
  44. package/dist/react.js +350 -1
  45. package/dist/react.js.map +1 -1
  46. package/dist/remix.cjs +331 -0
  47. package/dist/remix.cjs.map +1 -0
  48. package/dist/remix.d.cts +305 -0
  49. package/dist/remix.d.ts +305 -0
  50. package/dist/remix.js +327 -0
  51. package/dist/remix.js.map +1 -0
  52. package/package.json +70 -2
@@ -8,6 +8,7 @@ var os = require('os');
8
8
  var zod = require('zod');
9
9
  var posthogNode = require('posthog-node');
10
10
  var moduleApi = require('module');
11
+ var fs = require('fs');
11
12
 
12
13
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
13
14
  function _interopNamespace(e) {
@@ -1234,6 +1235,184 @@ function generateRSS(manifest, options) {
1234
1235
  "</rss>"
1235
1236
  ].join("");
1236
1237
  }
1238
+ var CLAUDE_SKILL = `# FeatureDrop \u2014 Setup & Configuration Skill
1239
+
1240
+ ## What it is
1241
+ Open-source product adoption toolkit. Changelogs, badges, tours, checklists, feedback.
1242
+ Zero dependencies. < 3 kB core. MIT licensed.
1243
+
1244
+ ## Quick Setup
1245
+ 1. \`npm install featuredrop\`
1246
+ 2. Create features.json manifest
1247
+ 3. Wrap root in \`<FeatureDropProvider manifest={features} storage={new LocalStorageAdapter()}>\`
1248
+ 4. Drop components or use hooks
1249
+
1250
+ ## Imports (ALWAYS use subpath imports)
1251
+ \`\`\`
1252
+ featuredrop \u2014 core functions (isNew, getNewFeatures, createManifest)
1253
+ featuredrop/react \u2014 components + hooks (NewBadge, ChangelogWidget, Tour, Checklist)
1254
+ featuredrop/react/hooks \u2014 headless hooks only (useChangelog, useTour, useChecklist, useNewFeature)
1255
+ featuredrop/adapters \u2014 storage (PostgresAdapter, RedisAdapter, IndexedDBAdapter, etc.)
1256
+ featuredrop/engine \u2014 AdoptionEngine (smart timing, format selection, adoption scoring)
1257
+ featuredrop/schema \u2014 Zod validation (featureEntrySchema, validateManifest)
1258
+ featuredrop/testing \u2014 test helpers (createMockManifest, createMockStorage, createTestProvider)
1259
+ featuredrop/tailwind \u2014 Tailwind plugin (featureDropPlugin)
1260
+ \`\`\`
1261
+
1262
+ ## Hooks Reference (prefer these for custom UI)
1263
+ \`\`\`
1264
+ useNewFeature(sidebarKey) \u2192 { isNew, feature, dismiss }
1265
+ useNewCount() \u2192 number
1266
+ useChangelog() \u2192 { features, newFeatures, newCount, dismiss, dismissAll, markAllSeen, getByCategory }
1267
+ useTour(id) \u2192 { currentStep, stepIndex, totalSteps, isActive, start, next, prev, skip, complete }
1268
+ useChecklist(id) \u2192 { tasks, progress, isComplete, completeTask, resetChecklist }
1269
+ useSurvey(id) \u2192 { isOpen, show, hide, askLater, submitted, canShow }
1270
+ useSmartFeature(id) \u2192 { show, format, feature, dismiss, confidence, reason }
1271
+ useAdoptionScore() \u2192 { score, grade, breakdown, recommendations }
1272
+ useFeatureDrop() \u2192 full provider context (low-level)
1273
+ \`\`\`
1274
+
1275
+ ## Components (ready-made UI with headless render prop mode)
1276
+ NewBadge, ChangelogWidget, ChangelogPage, Tour, Checklist, Spotlight, SpotlightChain,
1277
+ Hotspot, TooltipGroup, Banner, Toast, AnnouncementModal, Survey, FeedbackWidget,
1278
+ FeatureRequestButton, FeatureRequestForm, SmartAnnouncement
1279
+
1280
+ ## Manifest Format
1281
+ \`\`\`json
1282
+ {
1283
+ "id": "dark-mode",
1284
+ "label": "Dark Mode",
1285
+ "description": "Toggle between light and dark themes.",
1286
+ "releasedAt": "2026-02-20",
1287
+ "showNewUntil": "2026-04-20",
1288
+ "category": "ui",
1289
+ "priority": "normal",
1290
+ "type": "feature"
1291
+ }
1292
+ \`\`\`
1293
+
1294
+ ## Storage Adapters
1295
+ - LocalStorageAdapter (browser default, zero-config)
1296
+ - MemoryAdapter (testing, SSR)
1297
+ - IndexedDBAdapter (offline-first PWAs)
1298
+ - PostgresAdapter, RedisAdapter, MySQLAdapter, MongoAdapter, SQLiteAdapter (server)
1299
+ - SupabaseAdapter (Supabase with optional realtime)
1300
+ - RemoteAdapter (HTTP API with retry + circuit breaker)
1301
+ - HybridAdapter (local + remote with batched sync)
1302
+
1303
+ ## Rules
1304
+ - Always use subpath imports (never bare 'featuredrop' for React code)
1305
+ - Prefer hooks over components when user has a custom design system
1306
+ - Features auto-expire via showNewUntil \u2014 don't build manual expiry logic
1307
+ - Zero production dependencies must be maintained
1308
+ - TypeScript strict \u2014 no any types
1309
+ `;
1310
+ var CURSORRULES = `When working with FeatureDrop:
1311
+ - FeatureDrop is a product adoption library (changelogs, badges, tours, checklists, feedback)
1312
+ - ALWAYS import from subpaths: 'featuredrop/react', 'featuredrop/react/hooks', 'featuredrop/adapters'
1313
+ - For custom UI: use hooks from 'featuredrop/react/hooks' (useChangelog, useTour, useChecklist, useSmartFeature)
1314
+ - For ready-made UI: use components from 'featuredrop/react' (NewBadge, ChangelogWidget, Tour, SmartAnnouncement)
1315
+ - Features are defined in JSON: { id, label, description, releasedAt, showNewUntil? }
1316
+ - Wrap app in <FeatureDropProvider manifest={features} storage={new LocalStorageAdapter()}>
1317
+ - Storage default: LocalStorageAdapter. Server: PostgresAdapter, RedisAdapter from 'featuredrop/adapters'
1318
+ - Engine: import { createAdoptionEngine } from 'featuredrop/engine' for smart timing + format selection
1319
+ - Core < 3 kB, React ~12 kB, zero production deps, fully tree-shakable
1320
+ - All components support headless mode via render props
1321
+ - For shadcn projects: use hooks from 'featuredrop/react/hooks' + shadcn primitives
1322
+ - TypeScript strict mode, no 'any' types
1323
+ - Zero production dependencies \u2014 do not add external deps
1324
+ `;
1325
+ var MCP_CONFIG = {
1326
+ featuredrop: {
1327
+ command: "npx",
1328
+ args: ["@featuredrop/mcp"]
1329
+ }
1330
+ };
1331
+ async function runAiSetup(cwd) {
1332
+ const root = cwd ?? process.cwd();
1333
+ const result = { detected: [], created: [], skipped: [] };
1334
+ const claudeDir = path.join(root, ".claude");
1335
+ const claudeSkillsDir = path.join(claudeDir, "skills");
1336
+ const claudeSkillPath = path.join(claudeSkillsDir, "featuredrop.md");
1337
+ if (fs.existsSync(claudeDir) || fs.existsSync(path.join(root, "CLAUDE.md"))) {
1338
+ result.detected.push("Claude Code");
1339
+ if (fs.existsSync(claudeSkillPath)) {
1340
+ result.skipped.push(claudeSkillPath);
1341
+ } else {
1342
+ await promises.mkdir(claudeSkillsDir, { recursive: true });
1343
+ await promises.writeFile(claudeSkillPath, CLAUDE_SKILL, "utf8");
1344
+ result.created.push(claudeSkillPath);
1345
+ }
1346
+ const claudeSettingsDir = claudeDir;
1347
+ const claudeMcpPath = path.join(claudeSettingsDir, "settings.json");
1348
+ if (!fs.existsSync(claudeMcpPath)) {
1349
+ const settings = { mcpServers: MCP_CONFIG };
1350
+ await promises.writeFile(claudeMcpPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1351
+ result.created.push(claudeMcpPath);
1352
+ } else {
1353
+ try {
1354
+ const existing = JSON.parse(await promises.readFile(claudeMcpPath, "utf8"));
1355
+ const mcpServers = existing.mcpServers ?? {};
1356
+ if (!mcpServers.featuredrop) {
1357
+ mcpServers.featuredrop = MCP_CONFIG.featuredrop;
1358
+ existing.mcpServers = mcpServers;
1359
+ await promises.writeFile(claudeMcpPath, JSON.stringify(existing, null, 2) + "\n", "utf8");
1360
+ result.created.push(`${claudeMcpPath} (merged)`);
1361
+ } else {
1362
+ result.skipped.push(claudeMcpPath);
1363
+ }
1364
+ } catch {
1365
+ result.skipped.push(`${claudeMcpPath} (parse error)`);
1366
+ }
1367
+ }
1368
+ }
1369
+ const cursorDir = path.join(root, ".cursor");
1370
+ const cursorRulesPath = path.join(root, ".cursorrules");
1371
+ const cursorMcpPath = path.join(cursorDir, "mcp.json");
1372
+ if (fs.existsSync(cursorDir) || fs.existsSync(cursorRulesPath)) {
1373
+ result.detected.push("Cursor");
1374
+ if (fs.existsSync(cursorRulesPath)) {
1375
+ const content = await promises.readFile(cursorRulesPath, "utf8");
1376
+ if (content.includes("FeatureDrop")) {
1377
+ result.skipped.push(cursorRulesPath);
1378
+ } else {
1379
+ await promises.writeFile(cursorRulesPath, content + "\n\n" + CURSORRULES, "utf8");
1380
+ result.created.push(`${cursorRulesPath} (appended)`);
1381
+ }
1382
+ } else {
1383
+ await promises.writeFile(cursorRulesPath, CURSORRULES, "utf8");
1384
+ result.created.push(cursorRulesPath);
1385
+ }
1386
+ if (fs.existsSync(cursorDir)) {
1387
+ if (!fs.existsSync(cursorMcpPath)) {
1388
+ const config = { servers: MCP_CONFIG };
1389
+ await promises.mkdir(cursorDir, { recursive: true });
1390
+ await promises.writeFile(cursorMcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
1391
+ result.created.push(cursorMcpPath);
1392
+ } else {
1393
+ result.skipped.push(cursorMcpPath);
1394
+ }
1395
+ }
1396
+ }
1397
+ const vscodeDir = path.join(root, ".vscode");
1398
+ const vscodeMcpPath = path.join(vscodeDir, "mcp.json");
1399
+ if (fs.existsSync(vscodeDir)) {
1400
+ result.detected.push("VS Code");
1401
+ if (!fs.existsSync(vscodeMcpPath)) {
1402
+ const config = { servers: MCP_CONFIG };
1403
+ await promises.writeFile(vscodeMcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
1404
+ result.created.push(vscodeMcpPath);
1405
+ } else {
1406
+ result.skipped.push(vscodeMcpPath);
1407
+ }
1408
+ }
1409
+ if (result.detected.length === 0) {
1410
+ result.detected.push("(none detected \u2014 creating .cursorrules as default)");
1411
+ await promises.writeFile(cursorRulesPath, CURSORRULES, "utf8");
1412
+ result.created.push(cursorRulesPath);
1413
+ }
1414
+ return result;
1415
+ }
1237
1416
 
1238
1417
  // src/cli.ts
1239
1418
  function parseArgs(argv) {
@@ -1247,7 +1426,8 @@ function parseArgs(argv) {
1247
1426
  "stats",
1248
1427
  "doctor",
1249
1428
  "generate-rss",
1250
- "generate-changelog"
1429
+ "generate-changelog",
1430
+ "ai-setup"
1251
1431
  ]);
1252
1432
  const command = allowed.has(commandRaw) ? commandRaw : "help";
1253
1433
  const parsed = { command };
@@ -1287,6 +1467,7 @@ function printHelp() {
1287
1467
  console.log(" featuredrop doctor [--pattern features/**/*.md] [--cwd .]");
1288
1468
  console.log(" featuredrop generate-rss [--pattern features/**/*.md] [--out featuredrop.rss.xml] [--title ...] [--link ...] [--description ...] [--cwd .]");
1289
1469
  console.log(" featuredrop generate-changelog [--pattern features/**/*.md] [--out CHANGELOG.generated.md] [--cwd .]");
1470
+ console.log(" featuredrop ai-setup [--cwd .] Detect AI tools and create context files");
1290
1471
  }
1291
1472
  async function promptForLabelIfNeeded(label) {
1292
1473
  if (label?.trim()) return label.trim();
@@ -1333,6 +1514,32 @@ async function run() {
1333
1514
  await shutdownPostHog();
1334
1515
  return;
1335
1516
  }
1517
+ if (args.command === "ai-setup") {
1518
+ const result = await runAiSetup(args.cwd);
1519
+ if (result.detected.length > 0) {
1520
+ console.log(`Detected: ${result.detected.join(", ")}`);
1521
+ }
1522
+ for (const path of result.created) {
1523
+ console.log(` Created: ${path}`);
1524
+ }
1525
+ for (const path of result.skipped) {
1526
+ console.log(` Skipped: ${path} (already exists)`);
1527
+ }
1528
+ if (result.created.length === 0 && result.skipped.length > 0) {
1529
+ console.log("All AI context files already up to date.");
1530
+ }
1531
+ posthog?.capture({
1532
+ distinctId,
1533
+ event: "cli_ai_setup",
1534
+ properties: {
1535
+ detected: result.detected,
1536
+ files_created: result.created.length,
1537
+ files_skipped: result.skipped.length
1538
+ }
1539
+ });
1540
+ await shutdownPostHog();
1541
+ return;
1542
+ }
1336
1543
  if (args.command === "add") {
1337
1544
  const label = await promptForLabelIfNeeded(args.label);
1338
1545
  const result = await addFeatureEntry({