@ddt-tools/cli 0.2.0 → 0.2.4

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 (203) hide show
  1. package/dist/advise-tests-YNMKVJCD.js +87 -0
  2. package/dist/advise-tests-YNMKVJCD.js.map +1 -0
  3. package/dist/ai-NTNPYEKZ.js +86 -0
  4. package/dist/ai-NTNPYEKZ.js.map +1 -0
  5. package/dist/anonymize-LERTWUQO.js +139 -0
  6. package/dist/anonymize-LERTWUQO.js.map +1 -0
  7. package/dist/approval-GGZGKIU4.js +73 -0
  8. package/dist/approval-GGZGKIU4.js.map +1 -0
  9. package/dist/approval-chain-GWJKZHVU.js +118 -0
  10. package/dist/approval-chain-GWJKZHVU.js.map +1 -0
  11. package/dist/audit-log-2PH55BU4.js +159 -0
  12. package/dist/audit-log-2PH55BU4.js.map +1 -0
  13. package/dist/backlog-QNXGOUF4.js +76 -0
  14. package/dist/backlog-QNXGOUF4.js.map +1 -0
  15. package/dist/bisect-W3XKKRWG.js +111 -0
  16. package/dist/bisect-W3XKKRWG.js.map +1 -0
  17. package/dist/bookmarks-XVOGXGMC.js +107 -0
  18. package/dist/bookmarks-XVOGXGMC.js.map +1 -0
  19. package/dist/branch-S3I2IJGQ.js +103 -0
  20. package/dist/branch-S3I2IJGQ.js.map +1 -0
  21. package/dist/build-MP3JQEFO.js +20 -0
  22. package/dist/build-MP3JQEFO.js.map +1 -0
  23. package/dist/catalog-3J3NFNXP.js +137 -0
  24. package/dist/catalog-3J3NFNXP.js.map +1 -0
  25. package/dist/changelog-ZQAH3ULB.js +216 -0
  26. package/dist/changelog-ZQAH3ULB.js.map +1 -0
  27. package/dist/chunk-2FT6HXKS.js +55 -0
  28. package/dist/chunk-2FT6HXKS.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/chunk-DGUM43GV.js.map +1 -0
  31. package/dist/chunk-DL3V7UJ2.js +25 -0
  32. package/dist/chunk-DL3V7UJ2.js.map +1 -0
  33. package/dist/chunk-VM2H4LAO.js +15 -0
  34. package/dist/chunk-VM2H4LAO.js.map +1 -0
  35. package/dist/chunk-XFXG347C.js +40 -0
  36. package/dist/chunk-XFXG347C.js.map +1 -0
  37. package/dist/cli.js +499 -19402
  38. package/dist/cli.js.map +1 -1
  39. package/dist/compare-P7JOV76O.js +379 -0
  40. package/dist/compare-P7JOV76O.js.map +1 -0
  41. package/dist/compare-profiles-H33CXZPD.js +219 -0
  42. package/dist/compare-profiles-H33CXZPD.js.map +1 -0
  43. package/dist/completion-ZSNCQKJ2.js +89 -0
  44. package/dist/completion-ZSNCQKJ2.js.map +1 -0
  45. package/dist/connection-CDGVEFUC.js +148 -0
  46. package/dist/connection-CDGVEFUC.js.map +1 -0
  47. package/dist/cost-estimate-S2MKHT2H.js +321 -0
  48. package/dist/cost-estimate-S2MKHT2H.js.map +1 -0
  49. package/dist/data-compare-46ZI7KHL.js +128 -0
  50. package/dist/data-compare-46ZI7KHL.js.map +1 -0
  51. package/dist/data-fit-WGEPLD5S.js +127 -0
  52. package/dist/data-fit-WGEPLD5S.js.map +1 -0
  53. package/dist/deploy-status-4H5KJFRC.js +58 -0
  54. package/dist/deploy-status-4H5KJFRC.js.map +1 -0
  55. package/dist/design-ILX3ZSWW.js +135 -0
  56. package/dist/design-ILX3ZSWW.js.map +1 -0
  57. package/dist/diagnose-WPUL67E4.js +150 -0
  58. package/dist/diagnose-WPUL67E4.js.map +1 -0
  59. package/dist/discover-DEO2R5T6.js +78 -0
  60. package/dist/discover-DEO2R5T6.js.map +1 -0
  61. package/dist/docs-QNY3MUVO.js +183 -0
  62. package/dist/docs-QNY3MUVO.js.map +1 -0
  63. package/dist/drift-FDRNPWQA.js +233 -0
  64. package/dist/drift-FDRNPWQA.js.map +1 -0
  65. package/dist/drift-gate-6BWWWMHW.js +103 -0
  66. package/dist/drift-gate-6BWWWMHW.js.map +1 -0
  67. package/dist/error-lookup-4R3Y4RBC.js +56 -0
  68. package/dist/error-lookup-4R3Y4RBC.js.map +1 -0
  69. package/dist/errorReporting-3LPE2IJY.js +109 -0
  70. package/dist/errorReporting-3LPE2IJY.js.map +1 -0
  71. package/dist/exec-JOLH5LPT.js +122 -0
  72. package/dist/exec-JOLH5LPT.js.map +1 -0
  73. package/dist/explain-NS26WE2Y.js +189 -0
  74. package/dist/explain-NS26WE2Y.js.map +1 -0
  75. package/dist/explorer-GSYYYOAL.js +58 -0
  76. package/dist/explorer-GSYYYOAL.js.map +1 -0
  77. package/dist/extract-4LWEZG4O.js +152 -0
  78. package/dist/extract-4LWEZG4O.js.map +1 -0
  79. package/dist/features-KQV4OFIZ.js +54 -0
  80. package/dist/features-KQV4OFIZ.js.map +1 -0
  81. package/dist/feedback-CBLGXUEG.js +158 -0
  82. package/dist/feedback-CBLGXUEG.js.map +1 -0
  83. package/dist/find-SMXRCZ76.js +176 -0
  84. package/dist/find-SMXRCZ76.js.map +1 -0
  85. package/dist/format-HMGG6MY3.js +277 -0
  86. package/dist/format-HMGG6MY3.js.map +1 -0
  87. package/dist/generate-W7VLBDLI.js +160 -0
  88. package/dist/generate-W7VLBDLI.js.map +1 -0
  89. package/dist/graph-YYL5UYCJ.js +168 -0
  90. package/dist/graph-YYL5UYCJ.js.map +1 -0
  91. package/dist/history-GDRFP4PG.js +184 -0
  92. package/dist/history-GDRFP4PG.js.map +1 -0
  93. package/dist/hosts-DRFZTMIJ.js +45 -0
  94. package/dist/hosts-DRFZTMIJ.js.map +1 -0
  95. package/dist/impact-A4NU6CB2.js +63 -0
  96. package/dist/impact-A4NU6CB2.js.map +1 -0
  97. package/dist/import-2RNYDL4E.js +79 -0
  98. package/dist/import-2RNYDL4E.js.map +1 -0
  99. package/dist/index.cjs +11 -5
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.js +8 -2
  102. package/dist/index.js.map +1 -1
  103. package/dist/init-EAOGNGXI.js +54 -0
  104. package/dist/init-EAOGNGXI.js.map +1 -0
  105. package/dist/install-hooks-G3Y5LVXK.js +109 -0
  106. package/dist/install-hooks-G3Y5LVXK.js.map +1 -0
  107. package/dist/license-Z5YSC7XQ.js +43 -0
  108. package/dist/license-Z5YSC7XQ.js.map +1 -0
  109. package/dist/lineage-C5CGVP36.js +555 -0
  110. package/dist/lineage-C5CGVP36.js.map +1 -0
  111. package/dist/lint-AQFPZ3WG.js +144 -0
  112. package/dist/lint-AQFPZ3WG.js.map +1 -0
  113. package/dist/mcp-F7FND5X7.js +343 -0
  114. package/dist/mcp-F7FND5X7.js.map +1 -0
  115. package/dist/migrate-from-dbt-K4ELOWUD.js +156 -0
  116. package/dist/migrate-from-dbt-K4ELOWUD.js.map +1 -0
  117. package/dist/migrate-platform-E7VZFPO5.js +91 -0
  118. package/dist/migrate-platform-E7VZFPO5.js.map +1 -0
  119. package/dist/optimize-WUJ5ZN5Y.js +109 -0
  120. package/dist/optimize-WUJ5ZN5Y.js.map +1 -0
  121. package/dist/perf-UULZSREY.js +200 -0
  122. package/dist/perf-UULZSREY.js.map +1 -0
  123. package/dist/pii-QHU32VML.js +146 -0
  124. package/dist/pii-QHU32VML.js.map +1 -0
  125. package/dist/pilot-BR6GVK32.js +29 -0
  126. package/dist/pilot-BR6GVK32.js.map +1 -0
  127. package/dist/pr-comment-2FOA3EXG.js +81 -0
  128. package/dist/pr-comment-2FOA3EXG.js.map +1 -0
  129. package/dist/preview-XNY422OU.js +46 -0
  130. package/dist/preview-XNY422OU.js.map +1 -0
  131. package/dist/profile-SQTBNKYS.js +98 -0
  132. package/dist/profile-SQTBNKYS.js.map +1 -0
  133. package/dist/promote-FSGUPIPD.js +417 -0
  134. package/dist/promote-FSGUPIPD.js.map +1 -0
  135. package/dist/publish-AYCRMCE2.js +739 -0
  136. package/dist/publish-AYCRMCE2.js.map +1 -0
  137. package/dist/purge-Y5IOTXKA.js +56 -0
  138. package/dist/purge-Y5IOTXKA.js.map +1 -0
  139. package/dist/query-log-SDDGMJLJ.js +112 -0
  140. package/dist/query-log-SDDGMJLJ.js.map +1 -0
  141. package/dist/refactor-TC7S43F2.js +5809 -0
  142. package/dist/refactor-TC7S43F2.js.map +1 -0
  143. package/dist/refresh-MDJYOYV5.js +39 -0
  144. package/dist/refresh-MDJYOYV5.js.map +1 -0
  145. package/dist/replay-E4664A5K.js +118 -0
  146. package/dist/replay-E4664A5K.js.map +1 -0
  147. package/dist/revert-QWQWCJJB.js +111 -0
  148. package/dist/revert-QWQWCJJB.js.map +1 -0
  149. package/dist/review-7CAVLD67.js +164 -0
  150. package/dist/review-7CAVLD67.js.map +1 -0
  151. package/dist/rollback-suggest-C6D5YFCA.js +79 -0
  152. package/dist/rollback-suggest-C6D5YFCA.js.map +1 -0
  153. package/dist/safer-alternative-QR4QEFUV.js +84 -0
  154. package/dist/safer-alternative-QR4QEFUV.js.map +1 -0
  155. package/dist/safety-OFWUFLK4.js +165 -0
  156. package/dist/safety-OFWUFLK4.js.map +1 -0
  157. package/dist/savings-MEBE4TXI.js +95 -0
  158. package/dist/savings-MEBE4TXI.js.map +1 -0
  159. package/dist/scan-secrets-XCUBMLHL.js +54 -0
  160. package/dist/scan-secrets-XCUBMLHL.js.map +1 -0
  161. package/dist/schema-7JZIG6QR.js +447 -0
  162. package/dist/schema-7JZIG6QR.js.map +1 -0
  163. package/dist/script-BMYVBHFR.js +167 -0
  164. package/dist/script-BMYVBHFR.js.map +1 -0
  165. package/dist/search-TA3C3AZT.js +151 -0
  166. package/dist/search-TA3C3AZT.js.map +1 -0
  167. package/dist/seed-W4Q3L2IU.js +101 -0
  168. package/dist/seed-W4Q3L2IU.js.map +1 -0
  169. package/dist/sketch-6B2V6FJV.js +83 -0
  170. package/dist/sketch-6B2V6FJV.js.map +1 -0
  171. package/dist/snapshot-YMVS322L.js +171 -0
  172. package/dist/snapshot-YMVS322L.js.map +1 -0
  173. package/dist/snippets-EVTN63OU.js +74 -0
  174. package/dist/snippets-EVTN63OU.js.map +1 -0
  175. package/dist/standards-FGJW3CQL.js +238 -0
  176. package/dist/standards-FGJW3CQL.js.map +1 -0
  177. package/dist/suggest-V3LVIFZ5.js +44 -0
  178. package/dist/suggest-V3LVIFZ5.js.map +1 -0
  179. package/dist/suggest-constraints-EX2FCWOQ.js +154 -0
  180. package/dist/suggest-constraints-EX2FCWOQ.js.map +1 -0
  181. package/dist/suite-YTQ3CNX5.js +85 -0
  182. package/dist/suite-YTQ3CNX5.js.map +1 -0
  183. package/dist/telemetry-KOIY3NEQ.js +90 -0
  184. package/dist/telemetry-KOIY3NEQ.js.map +1 -0
  185. package/dist/template-MUJ6X6LN.js +396 -0
  186. package/dist/template-MUJ6X6LN.js.map +1 -0
  187. package/dist/test-XFSQHR2S.js +169 -0
  188. package/dist/test-XFSQHR2S.js.map +1 -0
  189. package/dist/trial-GFTGYCR3.js +31 -0
  190. package/dist/trial-GFTGYCR3.js.map +1 -0
  191. package/dist/validate-LFDEZFFH.js +107 -0
  192. package/dist/validate-LFDEZFFH.js.map +1 -0
  193. package/dist/verify-KRDYOJCR.js +76 -0
  194. package/dist/verify-KRDYOJCR.js.map +1 -0
  195. package/dist/watch-FSG23RR3.js +80 -0
  196. package/dist/watch-FSG23RR3.js.map +1 -0
  197. package/dist/xcompare-U4TXTTIR.js +87 -0
  198. package/dist/xcompare-U4TXTTIR.js.map +1 -0
  199. package/package.json +2 -2
  200. package/dist/cli.cjs +0 -19298
  201. package/dist/cli.cjs.map +0 -1
  202. package/dist/cli.d.cts +0 -1
  203. package/dist/cli.d.ts +0 -1
@@ -0,0 +1,54 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/features.ts
4
+ import { Command } from "commander";
5
+ import { features as featuresApi, license as licenseApi } from "@ddt-tools/core";
6
+ function featuresCommand() {
7
+ const cmd = new Command("features");
8
+ cmd.description(
9
+ "Browse DDT features. Free users see paid features too \u2014 with copy explaining what each one does and how to unlock it."
10
+ );
11
+ cmd.command("list").description("List every feature with its tier and your current access state.").action(async () => {
12
+ const license = await licenseApi.loadLicense();
13
+ const rows = featuresApi.listFeatureAvailability(license);
14
+ const nameWidth = Math.max(...rows.map((r) => r.feature.name.length), 4);
15
+ let lastTier = "";
16
+ for (const row of rows) {
17
+ if (row.feature.tier !== lastTier) {
18
+ if (lastTier !== "") process.stdout.write("\n");
19
+ process.stdout.write(`\u2500\u2500 ${row.feature.tier.toUpperCase()} \u2500\u2500
20
+ `);
21
+ lastTier = row.feature.tier;
22
+ }
23
+ const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
24
+ process.stdout.write(
25
+ ` ${pad(row.state, 22)} ${pad(row.feature.name, nameWidth)} ${row.feature.summary}
26
+ `
27
+ );
28
+ }
29
+ process.stdout.write(
30
+ "\nRun `ddt features show <id>` for the full pitch. `ddt features upgrade` shows the path to the next tier.\n"
31
+ );
32
+ });
33
+ cmd.command("show <id>").description("Print the full pitch + unlock path for one feature.").action(async (id) => {
34
+ const feature = featuresApi.getFeature(id);
35
+ if (!feature) {
36
+ console.error(`Unknown feature id: "${id}". Run \`ddt features list\` to see all ids.`);
37
+ process.exitCode = 1;
38
+ return;
39
+ }
40
+ const license = await licenseApi.loadLicense();
41
+ const available = featuresApi.isFeatureAvailable(feature, license);
42
+ const body = available ? featuresApi.unlockedFeatureMessage(feature) : featuresApi.lockedFeatureMessage(feature);
43
+ process.stdout.write(body + "\n");
44
+ });
45
+ cmd.command("upgrade").description("Show what each paid tier would unlock for your current license.").action(async () => {
46
+ const license = await licenseApi.loadLicense();
47
+ process.stdout.write(featuresApi.upgradeSummary(license) + "\n");
48
+ });
49
+ return cmd;
50
+ }
51
+ export {
52
+ featuresCommand
53
+ };
54
+ //# sourceMappingURL=features-KQV4OFIZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/features.ts"],"sourcesContent":["/**\n * `ddt features` — browse the DDT feature catalog regardless of tier.\n *\n * The point: every user can see every feature, with copy that explains\n * what it does and exactly what would unlock it. Better UX than silent\n * gates (\"why doesn't `--map-rewrite-strings` work?\") or surprise\n * paywalls at the command line.\n *\n * Subcommands:\n * ddt features list Show all features with state (✅ / 🔒 / 🚧).\n * ddt features show <id> Full pitch + unlock path for one feature.\n * ddt features upgrade What each paid tier would unlock for you.\n */\nimport { Command } from 'commander';\nimport { features as featuresApi, license as licenseApi } from '@ddt-tools/core';\n\nexport function featuresCommand(): Command {\n const cmd = new Command('features');\n cmd.description(\n 'Browse DDT features. Free users see paid features too — with copy explaining what each one does and how to unlock it.',\n );\n\n cmd\n .command('list')\n .description('List every feature with its tier and your current access state.')\n .action(async () => {\n const license = await licenseApi.loadLicense();\n const rows = featuresApi.listFeatureAvailability(license);\n\n const nameWidth = Math.max(...rows.map((r) => r.feature.name.length), 4);\n\n let lastTier = '';\n for (const row of rows) {\n if (row.feature.tier !== lastTier) {\n if (lastTier !== '') process.stdout.write('\\n');\n process.stdout.write(`── ${row.feature.tier.toUpperCase()} ──\\n`);\n lastTier = row.feature.tier;\n }\n const pad = (s: string, w: number): string => s + ' '.repeat(Math.max(0, w - s.length));\n process.stdout.write(\n ` ${pad(row.state, 22)} ${pad(row.feature.name, nameWidth)} ${row.feature.summary}\\n`,\n );\n }\n process.stdout.write(\n '\\nRun `ddt features show <id>` for the full pitch. `ddt features upgrade` shows the path to the next tier.\\n',\n );\n });\n\n cmd\n .command('show <id>')\n .description('Print the full pitch + unlock path for one feature.')\n .action(async (id: string) => {\n const feature = featuresApi.getFeature(id);\n if (!feature) {\n console.error(`Unknown feature id: \"${id}\". Run \\`ddt features list\\` to see all ids.`);\n process.exitCode = 1;\n return;\n }\n const license = await licenseApi.loadLicense();\n const available = featuresApi.isFeatureAvailable(feature, license);\n const body = available\n ? featuresApi.unlockedFeatureMessage(feature)\n : featuresApi.lockedFeatureMessage(feature);\n process.stdout.write(body + '\\n');\n });\n\n cmd\n .command('upgrade')\n .description('Show what each paid tier would unlock for your current license.')\n .action(async () => {\n const license = await licenseApi.loadLicense();\n process.stdout.write(featuresApi.upgradeSummary(license) + '\\n');\n });\n\n return cmd;\n}\n"],"mappings":";;;AAaA,SAAS,eAAe;AACxB,SAAS,YAAY,aAAa,WAAW,kBAAkB;AAExD,SAAS,kBAA2B;AACzC,QAAM,MAAM,IAAI,QAAQ,UAAU;AAClC,MAAI;AAAA,IACF;AAAA,EACF;AAEA,MACG,QAAQ,MAAM,EACd,YAAY,iEAAiE,EAC7E,OAAO,YAAY;AAClB,UAAM,UAAU,MAAM,WAAW,YAAY;AAC7C,UAAM,OAAO,YAAY,wBAAwB,OAAO;AAExD,UAAM,YAAY,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ,KAAK,MAAM,GAAG,CAAC;AAEvE,QAAI,WAAW;AACf,eAAW,OAAO,MAAM;AACtB,UAAI,IAAI,QAAQ,SAAS,UAAU;AACjC,YAAI,aAAa,GAAI,SAAQ,OAAO,MAAM,IAAI;AAC9C,gBAAQ,OAAO,MAAM,gBAAM,IAAI,QAAQ,KAAK,YAAY,CAAC;AAAA,CAAO;AAChE,mBAAW,IAAI,QAAQ;AAAA,MACzB;AACA,YAAM,MAAM,CAAC,GAAW,MAAsB,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC;AACtF,cAAQ,OAAO;AAAA,QACb,KAAK,IAAI,IAAI,OAAO,EAAE,CAAC,KAAK,IAAI,IAAI,QAAQ,MAAM,SAAS,CAAC,KAAK,IAAI,QAAQ,OAAO;AAAA;AAAA,MACtF;AAAA,IACF;AACA,YAAQ,OAAO;AAAA,MACb;AAAA,IACF;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,WAAW,EACnB,YAAY,qDAAqD,EACjE,OAAO,OAAO,OAAe;AAC5B,UAAM,UAAU,YAAY,WAAW,EAAE;AACzC,QAAI,CAAC,SAAS;AACZ,cAAQ,MAAM,wBAAwB,EAAE,8CAA8C;AACtF,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,UAAU,MAAM,WAAW,YAAY;AAC7C,UAAM,YAAY,YAAY,mBAAmB,SAAS,OAAO;AACjE,UAAM,OAAO,YACT,YAAY,uBAAuB,OAAO,IAC1C,YAAY,qBAAqB,OAAO;AAC5C,YAAQ,OAAO,MAAM,OAAO,IAAI;AAAA,EAClC,CAAC;AAEH,MACG,QAAQ,SAAS,EACjB,YAAY,iEAAiE,EAC7E,OAAO,YAAY;AAClB,UAAM,UAAU,MAAM,WAAW,YAAY;AAC7C,YAAQ,OAAO,MAAM,YAAY,eAAe,OAAO,IAAI,IAAI;AAAA,EACjE,CAAC;AAEH,SAAO;AACT;","names":[]}
@@ -0,0 +1,158 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/feedback.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import os from "os";
7
+ import { Command } from "commander";
8
+ import { license } from "@ddt-tools/core";
9
+ var DEFAULT_INGEST_URL = process.env.DDT_FEEDBACK_URL ?? "https://sdt-support.sdt-ddt-tools.workers.dev/ingest";
10
+ var OUTBOX_DIR = path.join(os.homedir(), ".ddt", "feedback");
11
+ function feedbackCommand() {
12
+ const cmd = new Command("feedback");
13
+ cmd.description("Send a feedback / bug / feature-request ticket to the DDT team.").argument("<message...>", "The feedback body (quote to keep as one arg).").option("--category <kind>", "Category: bug | feature | question | docs | other", "other").option(
14
+ "--contact <email>",
15
+ "Reply-to email (optional; otherwise we use the license email if any)."
16
+ ).option("--no-context", "Skip auto-capture of CLI version / OS / recent commands.").option("--dry-run", "Print the payload without sending or queueing.").option("--url <url>", `Override the ingest endpoint. Default ${DEFAULT_INGEST_URL}.`).action(async (messageParts, opts) => {
17
+ const body = (messageParts ?? []).join(" ").trim();
18
+ if (!body) {
19
+ console.error("Empty feedback body \u2014 nothing to send.");
20
+ process.exitCode = 1;
21
+ return;
22
+ }
23
+ const payload = await buildPayload(body, opts);
24
+ if (opts.dryRun) {
25
+ console.log(JSON.stringify(payload, null, 2));
26
+ return;
27
+ }
28
+ try {
29
+ await sendOrQueue(payload, String(opts.url ?? DEFAULT_INGEST_URL));
30
+ console.log("Feedback sent. Thank you!");
31
+ console.log(" You should hear back within 1 business day for Pro+ accounts.");
32
+ if (!payload.licenseEmail) {
33
+ console.log(" Tip: install a license (`ddt license activate`) so we can reach you.");
34
+ }
35
+ const extended = await license.extendTrial(1);
36
+ if (extended) {
37
+ console.log(
38
+ ` Feedback bonus: +1 trial day (now expires ${extended.expiresAt.slice(0, 10)}).`
39
+ );
40
+ }
41
+ } catch (err) {
42
+ const message = err instanceof Error ? err.message : String(err);
43
+ console.warn(`Couldn't reach the ingest endpoint: ${message}`);
44
+ const queued = await queueLocally(payload);
45
+ console.log(` Queued locally at ${queued}. Will retry on next \`ddt feedback\` run.`);
46
+ }
47
+ });
48
+ return cmd;
49
+ }
50
+ async function buildPayload(body, opts) {
51
+ const category = normalizeCategory(opts.category);
52
+ const payload = {
53
+ body,
54
+ product: "ddt",
55
+ productVersion: await readCliVersion(),
56
+ os: `${os.platform()} ${os.release()}`,
57
+ nodeVersion: process.version,
58
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
59
+ category
60
+ };
61
+ if (opts.contact) payload.contact = String(opts.contact);
62
+ if (opts.context !== false) {
63
+ payload.licenseEmail = await readLicenseEmail();
64
+ payload.recentCommands = await readRecentCommands();
65
+ }
66
+ return payload;
67
+ }
68
+ async function readCliVersion() {
69
+ try {
70
+ const pkgPath = path.join(
71
+ path.dirname(new URL(import.meta.url).pathname),
72
+ "..",
73
+ "..",
74
+ "package.json"
75
+ );
76
+ const raw = await fs.readFile(pkgPath, "utf8");
77
+ const obj = JSON.parse(raw);
78
+ return obj.version ?? "unknown";
79
+ } catch {
80
+ return "unknown";
81
+ }
82
+ }
83
+ async function readLicenseEmail() {
84
+ try {
85
+ const licensePath = path.join(os.homedir(), ".ddt", "license");
86
+ const raw = await fs.readFile(licensePath, "utf8");
87
+ const parts = raw.trim().split(".");
88
+ if (parts.length < 2) return void 0;
89
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
90
+ if (typeof payload === "object" && payload !== null) {
91
+ const sub = payload.sub;
92
+ if (typeof sub === "string") return sub;
93
+ }
94
+ return void 0;
95
+ } catch {
96
+ return void 0;
97
+ }
98
+ }
99
+ async function readRecentCommands() {
100
+ try {
101
+ const logPath = path.join(os.homedir(), ".ddt", "recent.log");
102
+ const raw = await fs.readFile(logPath, "utf8");
103
+ return raw.split(/\r?\n/).filter(Boolean).slice(-5);
104
+ } catch {
105
+ return void 0;
106
+ }
107
+ }
108
+ function normalizeCategory(input) {
109
+ const s = String(input ?? "other").toLowerCase();
110
+ if (s === "bug" || s === "feature" || s === "question" || s === "docs") return s;
111
+ return "other";
112
+ }
113
+ async function sendOrQueue(payload, url) {
114
+ await drainOutbox(url);
115
+ const res = await fetch(url, {
116
+ method: "POST",
117
+ headers: { "content-type": "application/json" },
118
+ body: JSON.stringify(payload)
119
+ });
120
+ if (!res.ok) {
121
+ throw new Error(`HTTP ${res.status}`);
122
+ }
123
+ }
124
+ async function queueLocally(payload) {
125
+ await fs.mkdir(OUTBOX_DIR, { recursive: true });
126
+ const file = path.join(OUTBOX_DIR, `${Date.now()}-${randomTag()}.json`);
127
+ await fs.writeFile(file, JSON.stringify(payload, null, 2));
128
+ return file;
129
+ }
130
+ async function drainOutbox(url) {
131
+ try {
132
+ const entries = await fs.readdir(OUTBOX_DIR);
133
+ for (const entry of entries) {
134
+ if (!entry.endsWith(".json")) continue;
135
+ const file = path.join(OUTBOX_DIR, entry);
136
+ try {
137
+ const raw = await fs.readFile(file, "utf8");
138
+ const res = await fetch(url, {
139
+ method: "POST",
140
+ headers: { "content-type": "application/json" },
141
+ body: raw
142
+ });
143
+ if (res.ok) {
144
+ await fs.unlink(file);
145
+ }
146
+ } catch {
147
+ }
148
+ }
149
+ } catch {
150
+ }
151
+ }
152
+ function randomTag() {
153
+ return Math.random().toString(36).slice(2, 8);
154
+ }
155
+ export {
156
+ feedbackCommand
157
+ };
158
+ //# sourceMappingURL=feedback-CBLGXUEG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/feedback.ts"],"sourcesContent":["/**\n * `ddt feedback \"...\"` — submit a feedback / bug / feature-request\n * ticket from inside the tool.\n *\n * Sibling of `sdt feedback`. Same shape; different default\n * file paths (`~/.ddt/...`) + default URL.\n *\n * See `docs/SUPPORT_OPERATIONS.md` for the ingest pipeline.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { Command } from 'commander';\nimport { license } from '@ddt-tools/core';\n\nconst DEFAULT_INGEST_URL =\n process.env.DDT_FEEDBACK_URL ?? 'https://sdt-support.sdt-ddt-tools.workers.dev/ingest';\nconst OUTBOX_DIR = path.join(os.homedir(), '.ddt', 'feedback');\n\ninterface FeedbackPayload {\n body: string;\n product: 'sdt' | 'ddt';\n productVersion: string;\n os: string;\n nodeVersion: string;\n ts: string;\n contact?: string;\n licenseEmail?: string;\n recentCommands?: string[];\n category?: 'bug' | 'feature' | 'question' | 'docs' | 'other';\n}\n\nexport function feedbackCommand(): Command {\n const cmd = new Command('feedback');\n cmd\n .description('Send a feedback / bug / feature-request ticket to the DDT team.')\n .argument('<message...>', 'The feedback body (quote to keep as one arg).')\n .option('--category <kind>', 'Category: bug | feature | question | docs | other', 'other')\n .option(\n '--contact <email>',\n 'Reply-to email (optional; otherwise we use the license email if any).',\n )\n .option('--no-context', 'Skip auto-capture of CLI version / OS / recent commands.')\n .option('--dry-run', 'Print the payload without sending or queueing.')\n .option('--url <url>', `Override the ingest endpoint. Default ${DEFAULT_INGEST_URL}.`)\n .action(async (messageParts: string[], opts) => {\n const body = (messageParts ?? []).join(' ').trim();\n if (!body) {\n console.error('Empty feedback body — nothing to send.');\n process.exitCode = 1;\n return;\n }\n const payload = await buildPayload(body, opts);\n if (opts.dryRun) {\n console.log(JSON.stringify(payload, null, 2));\n return;\n }\n try {\n await sendOrQueue(payload, String(opts.url ?? DEFAULT_INGEST_URL));\n console.log('Feedback sent. Thank you!');\n console.log(' You should hear back within 1 business day for Pro+ accounts.');\n if (!payload.licenseEmail) {\n console.log(' Tip: install a license (`ddt license activate`) so we can reach you.');\n }\n const extended = await license.extendTrial(1);\n if (extended) {\n console.log(\n ` Feedback bonus: +1 trial day (now expires ${extended.expiresAt.slice(0, 10)}).`,\n );\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(`Couldn't reach the ingest endpoint: ${message}`);\n const queued = await queueLocally(payload);\n console.log(` Queued locally at ${queued}. Will retry on next \\`ddt feedback\\` run.`);\n }\n });\n return cmd;\n}\n\nasync function buildPayload(\n body: string,\n opts: { category?: string; contact?: string; context?: boolean },\n): Promise<FeedbackPayload> {\n const category = normalizeCategory(opts.category);\n const payload: FeedbackPayload = {\n body,\n product: 'ddt',\n productVersion: await readCliVersion(),\n os: `${os.platform()} ${os.release()}`,\n nodeVersion: process.version,\n ts: new Date().toISOString(),\n category,\n };\n if (opts.contact) payload.contact = String(opts.contact);\n if (opts.context !== false) {\n payload.licenseEmail = await readLicenseEmail();\n payload.recentCommands = await readRecentCommands();\n }\n return payload;\n}\n\nasync function readCliVersion(): Promise<string> {\n try {\n const pkgPath = path.join(\n path.dirname(new URL(import.meta.url).pathname),\n '..',\n '..',\n 'package.json',\n );\n const raw = await fs.readFile(pkgPath, 'utf8');\n const obj = JSON.parse(raw) as { version?: string };\n return obj.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\nasync function readLicenseEmail(): Promise<string | undefined> {\n try {\n const licensePath = path.join(os.homedir(), '.ddt', 'license');\n const raw = await fs.readFile(licensePath, 'utf8');\n const parts = raw.trim().split('.');\n if (parts.length < 2) return undefined;\n const payload: unknown = JSON.parse(Buffer.from(parts[1]!, 'base64url').toString('utf8'));\n if (typeof payload === 'object' && payload !== null) {\n const sub = (payload as { sub?: unknown }).sub;\n if (typeof sub === 'string') return sub;\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function readRecentCommands(): Promise<string[] | undefined> {\n try {\n const logPath = path.join(os.homedir(), '.ddt', 'recent.log');\n const raw = await fs.readFile(logPath, 'utf8');\n return raw.split(/\\r?\\n/).filter(Boolean).slice(-5);\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeCategory(input: unknown): FeedbackPayload['category'] {\n const s = String(input ?? 'other').toLowerCase();\n if (s === 'bug' || s === 'feature' || s === 'question' || s === 'docs') return s;\n return 'other';\n}\n\nasync function sendOrQueue(payload: FeedbackPayload, url: string): Promise<void> {\n await drainOutbox(url);\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n}\n\nasync function queueLocally(payload: FeedbackPayload): Promise<string> {\n await fs.mkdir(OUTBOX_DIR, { recursive: true });\n const file = path.join(OUTBOX_DIR, `${Date.now()}-${randomTag()}.json`);\n await fs.writeFile(file, JSON.stringify(payload, null, 2));\n return file;\n}\n\nasync function drainOutbox(url: string): Promise<void> {\n try {\n const entries = await fs.readdir(OUTBOX_DIR);\n for (const entry of entries) {\n if (!entry.endsWith('.json')) continue;\n const file = path.join(OUTBOX_DIR, entry);\n try {\n const raw = await fs.readFile(file, 'utf8');\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: raw,\n });\n if (res.ok) {\n await fs.unlink(file);\n }\n } catch {\n // Keep in outbox; will retry next time.\n }\n }\n } catch {\n // No outbox; nothing to drain.\n }\n}\n\nfunction randomTag(): string {\n return Math.random().toString(36).slice(2, 8);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,176 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/commands/find.ts
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { homedir } from "os";
7
+ import { Command } from "commander";
8
+ import { features as featuresApi, license as licenseApi } from "@ddt-tools/core";
9
+ import { complete } from "@ddt-tools/core/ai";
10
+ import chalk from "chalk";
11
+ function tierBadge(tier) {
12
+ switch (tier) {
13
+ case "free":
14
+ return chalk.green("Free");
15
+ case "pro":
16
+ return chalk.cyan("Pro");
17
+ case "team":
18
+ return chalk.yellow("Team");
19
+ case "enterprise":
20
+ return chalk.magenta("Enterprise");
21
+ default:
22
+ return tier;
23
+ }
24
+ }
25
+ function findCommand() {
26
+ const cmd = new Command("find");
27
+ cmd.description("Search DDT features by keyword \u2014 find what you want to do.").argument("<query...>", 'Keywords to search for (e.g. "drift detection", "multi-team").').option("-n, --limit <n>", "Max results to show.", "5").option("--format <fmt>", "Output format: text | json.", "text").option("--tier <tier>", "Filter by tier: free | pro | team | enterprise.").option("--shipped", "Only include shipped features (exclude roadmap).").option(
28
+ "--ai",
29
+ "DSC.4: escalate to the configured AI provider when deterministic search yields low-confidence hits. Costs tokens; default off."
30
+ ).option(
31
+ "--ai-threshold <n>",
32
+ "Confidence below which --ai triggers escalation (0..1, default 0.55).",
33
+ "0.55"
34
+ ).option(
35
+ "--no-ai-cache",
36
+ "DSC.4-followup: skip the on-disk intent cache and always re-query the AI provider when --ai escalates."
37
+ ).action(async (queryParts, opts) => {
38
+ const query = queryParts.join(" ").trim();
39
+ const limit = Math.max(1, parseInt(String(opts.limit), 10) || 5);
40
+ const fmt = String(opts.format ?? "text").toLowerCase();
41
+ const tierFilter = opts.tier;
42
+ const statusFilter = opts.shipped ? "shipped" : void 0;
43
+ const licenseObj = await licenseApi.loadLicense();
44
+ let hits = [];
45
+ let stage = 1;
46
+ let tokensUsed = 0;
47
+ let cacheHit = false;
48
+ if (opts.ai) {
49
+ const threshold = Math.max(0, Math.min(1, Number(opts.aiThreshold ?? "0.55")));
50
+ const cachePath = path.join(homedir(), ".ddt", "intent-cache.json");
51
+ const useCache = opts.aiCache !== false;
52
+ let cache = featuresApi.emptyIntentCache();
53
+ if (useCache) {
54
+ try {
55
+ const body = await fs.readFile(cachePath, "utf8");
56
+ cache = featuresApi.parseIntentCache(body);
57
+ } catch {
58
+ }
59
+ const cached = featuresApi.lookupIntent(cache, query);
60
+ if (cached) {
61
+ const idSet = new Set(cached.featureIds);
62
+ const matchedFeatures = featuresApi.DDT_FEATURE_CATALOG.filter(
63
+ (f) => idSet.has(f.id)
64
+ ).sort((a, b) => cached.featureIds.indexOf(a.id) - cached.featureIds.indexOf(b.id));
65
+ hits = matchedFeatures.slice(0, limit).map((f) => ({
66
+ feature: f,
67
+ score: 1,
68
+ matchedFields: ["ai-intent-cached"]
69
+ }));
70
+ stage = 2;
71
+ tokensUsed = 0;
72
+ cacheHit = true;
73
+ }
74
+ }
75
+ if (!cacheHit) {
76
+ const result = await featuresApi.intentMatch(
77
+ query,
78
+ featuresApi.DDT_FEATURE_CATALOG,
79
+ { limit, tier: tierFilter, status: statusFilter, threshold },
80
+ (messages) => complete(messages, { feature: "find.intent" })
81
+ );
82
+ hits = [...result.hits];
83
+ stage = result.stage;
84
+ tokensUsed = result.tokensUsed;
85
+ if (useCache && stage === 2 && hits.length > 0) {
86
+ const next = featuresApi.evictExpiredIntents(
87
+ featuresApi.addIntent(
88
+ cache,
89
+ query,
90
+ hits.map((h) => h.feature.id),
91
+ tokensUsed
92
+ )
93
+ );
94
+ try {
95
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
96
+ await fs.writeFile(cachePath, featuresApi.serializeIntentCache(next), "utf8");
97
+ } catch {
98
+ }
99
+ }
100
+ }
101
+ } else {
102
+ hits = featuresApi.searchFeatures(query, featuresApi.DDT_FEATURE_CATALOG, {
103
+ limit,
104
+ tier: tierFilter,
105
+ status: statusFilter
106
+ });
107
+ }
108
+ if (fmt === "json") {
109
+ process.stdout.write(
110
+ JSON.stringify(
111
+ hits.map((h) => ({
112
+ id: h.feature.id,
113
+ name: h.feature.name,
114
+ tier: h.feature.tier,
115
+ status: h.feature.status,
116
+ score: h.score,
117
+ matchedFields: h.matchedFields,
118
+ summary: h.feature.summary,
119
+ unlockHow: h.feature.unlockHow,
120
+ useCases: h.feature.useCases,
121
+ relatedFeatures: h.feature.relatedFeatures
122
+ })),
123
+ null,
124
+ 2
125
+ ) + "\n"
126
+ );
127
+ return;
128
+ }
129
+ if (hits.length === 0) {
130
+ process.stdout.write(
131
+ chalk.dim(` No features found for "${query}".
132
+ `) + chalk.dim(` Try: ddt features list \xB7 ddt explain <topic>
133
+ `)
134
+ );
135
+ return;
136
+ }
137
+ process.stdout.write("\n");
138
+ for (const hit of hits) {
139
+ const avail = featuresApi.isFeatureAvailable(hit.feature, licenseObj);
140
+ const statusMark = hit.feature.status === "roadmap" ? chalk.dim("\u{1F6A7} roadmap") : avail ? chalk.green("\u2705 available") : chalk.dim("\u{1F512} locked");
141
+ process.stdout.write(
142
+ ` ${chalk.bold(hit.feature.name)} ${tierBadge(hit.feature.tier)} ${statusMark}
143
+ ${chalk.dim(hit.feature.summary)}
144
+ `
145
+ );
146
+ if (hit.feature.useCases && hit.feature.useCases.length > 0) {
147
+ process.stdout.write(chalk.dim(` Use when: ${hit.feature.useCases[0]}
148
+ `));
149
+ }
150
+ if (!avail && hit.feature.status === "shipped") {
151
+ process.stdout.write(chalk.dim(` Unlock: ${hit.feature.unlockHow}
152
+ `));
153
+ }
154
+ if (hit.feature.surfaces && hit.feature.surfaces.length > 0) {
155
+ process.stdout.write(
156
+ chalk.dim(` Surface: ${hit.feature.surfaces.slice(0, 2).join(", ")}
157
+ `)
158
+ );
159
+ }
160
+ process.stdout.write(chalk.dim(` ddt features show ${hit.feature.id}
161
+
162
+ `));
163
+ }
164
+ const aiNote = stage === 2 ? cacheHit ? " (AI-cached, 0 tokens)" : ` (AI-escalated, ${tokensUsed} tokens)` : "";
165
+ process.stdout.write(
166
+ chalk.dim(
167
+ ` ${hits.length} result${hits.length === 1 ? "" : "s"} for "${query}"${aiNote}. `
168
+ ) + chalk.dim("ddt features list \xB7 ddt features show <id>\n")
169
+ );
170
+ });
171
+ return cmd;
172
+ }
173
+ export {
174
+ findCommand
175
+ };
176
+ //# sourceMappingURL=find-SMXRCZ76.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/find.ts"],"sourcesContent":["/**\n * `ddt find <query>` — search the DDT feature catalog by keyword.\n *\n * DSC.1: surface any DDT capability by describing what you want to do.\n *\n * $ ddt find drift\n * $ ddt find \"multi-team catalog\"\n * $ ddt find compare --limit 3 --format json\n *\n * Reads the same catalog as `ddt features list`. Scoring favors exact\n * id/name/synonym matches over summary body matches. Results include\n * the unlock path so Free users know exactly what each feature needs.\n */\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { homedir } from 'node:os';\nimport { Command } from 'commander';\nimport { features as featuresApi, license as licenseApi } from '@ddt-tools/core';\nimport { complete } from '@ddt-tools/core/ai';\nimport chalk from 'chalk';\n\nfunction tierBadge(tier: string): string {\n switch (tier) {\n case 'free':\n return chalk.green('Free');\n case 'pro':\n return chalk.cyan('Pro');\n case 'team':\n return chalk.yellow('Team');\n case 'enterprise':\n return chalk.magenta('Enterprise');\n default:\n return tier;\n }\n}\n\nexport function findCommand(): Command {\n const cmd = new Command('find');\n cmd\n .description('Search DDT features by keyword — find what you want to do.')\n .argument('<query...>', 'Keywords to search for (e.g. \"drift detection\", \"multi-team\").')\n .option('-n, --limit <n>', 'Max results to show.', '5')\n .option('--format <fmt>', 'Output format: text | json.', 'text')\n .option('--tier <tier>', 'Filter by tier: free | pro | team | enterprise.')\n .option('--shipped', 'Only include shipped features (exclude roadmap).')\n .option(\n '--ai',\n 'DSC.4: escalate to the configured AI provider when deterministic search yields low-confidence hits. Costs tokens; default off.',\n )\n .option(\n '--ai-threshold <n>',\n 'Confidence below which --ai triggers escalation (0..1, default 0.55).',\n '0.55',\n )\n .option(\n '--no-ai-cache',\n 'DSC.4-followup: skip the on-disk intent cache and always re-query the AI provider when --ai escalates.',\n )\n .action(async (queryParts: string[], opts) => {\n const query = queryParts.join(' ').trim();\n const limit = Math.max(1, parseInt(String(opts.limit), 10) || 5);\n const fmt = String(opts.format ?? 'text').toLowerCase();\n const tierFilter = opts.tier as 'free' | 'pro' | 'team' | 'enterprise' | undefined;\n const statusFilter = opts.shipped ? ('shipped' as const) : undefined;\n\n const licenseObj = await licenseApi.loadLicense();\n let hits: ReturnType<typeof featuresApi.searchFeatures> = [];\n let stage: 1 | 2 = 1;\n let tokensUsed = 0;\n let cacheHit = false;\n if (opts.ai) {\n const threshold = Math.max(0, Math.min(1, Number(opts.aiThreshold ?? '0.55')));\n const cachePath = path.join(homedir(), '.ddt', 'intent-cache.json');\n const useCache = opts.aiCache !== false;\n let cache = featuresApi.emptyIntentCache();\n if (useCache) {\n try {\n const body = await fs.readFile(cachePath, 'utf8');\n cache = featuresApi.parseIntentCache(body);\n } catch {\n // No cache yet.\n }\n const cached = featuresApi.lookupIntent(cache, query);\n if (cached) {\n const idSet = new Set(cached.featureIds);\n const matchedFeatures = featuresApi.DDT_FEATURE_CATALOG.filter((f) =>\n idSet.has(f.id),\n ).sort((a, b) => cached.featureIds.indexOf(a.id) - cached.featureIds.indexOf(b.id));\n hits = matchedFeatures.slice(0, limit).map((f) => ({\n feature: f,\n score: 1.0,\n matchedFields: ['ai-intent-cached'],\n }));\n stage = 2;\n tokensUsed = 0;\n cacheHit = true;\n }\n }\n if (!cacheHit) {\n const result = await featuresApi.intentMatch(\n query,\n featuresApi.DDT_FEATURE_CATALOG,\n { limit, tier: tierFilter, status: statusFilter, threshold },\n (messages) => complete(messages, { feature: 'find.intent' }),\n );\n hits = [...result.hits];\n stage = result.stage;\n tokensUsed = result.tokensUsed;\n if (useCache && stage === 2 && hits.length > 0) {\n const next = featuresApi.evictExpiredIntents(\n featuresApi.addIntent(\n cache,\n query,\n hits.map((h) => h.feature.id),\n tokensUsed,\n ),\n );\n try {\n await fs.mkdir(path.dirname(cachePath), { recursive: true });\n await fs.writeFile(cachePath, featuresApi.serializeIntentCache(next), 'utf8');\n } catch {\n // Best-effort.\n }\n }\n }\n } else {\n hits = featuresApi.searchFeatures(query, featuresApi.DDT_FEATURE_CATALOG, {\n limit,\n tier: tierFilter,\n status: statusFilter,\n });\n }\n\n if (fmt === 'json') {\n process.stdout.write(\n JSON.stringify(\n hits.map((h) => ({\n id: h.feature.id,\n name: h.feature.name,\n tier: h.feature.tier,\n status: h.feature.status,\n score: h.score,\n matchedFields: h.matchedFields,\n summary: h.feature.summary,\n unlockHow: h.feature.unlockHow,\n useCases: h.feature.useCases,\n relatedFeatures: h.feature.relatedFeatures,\n })),\n null,\n 2,\n ) + '\\n',\n );\n return;\n }\n\n if (hits.length === 0) {\n process.stdout.write(\n chalk.dim(` No features found for \"${query}\".\\n`) +\n chalk.dim(` Try: ddt features list · ddt explain <topic>\\n`),\n );\n return;\n }\n\n process.stdout.write('\\n');\n for (const hit of hits) {\n const avail = featuresApi.isFeatureAvailable(hit.feature, licenseObj);\n const statusMark =\n hit.feature.status === 'roadmap'\n ? chalk.dim('🚧 roadmap')\n : avail\n ? chalk.green('✅ available')\n : chalk.dim('🔒 locked');\n\n process.stdout.write(\n ` ${chalk.bold(hit.feature.name)} ${tierBadge(hit.feature.tier)} ${statusMark}\\n` +\n ` ${chalk.dim(hit.feature.summary)}\\n`,\n );\n if (hit.feature.useCases && hit.feature.useCases.length > 0) {\n process.stdout.write(chalk.dim(` Use when: ${hit.feature.useCases[0]}\\n`));\n }\n if (!avail && hit.feature.status === 'shipped') {\n process.stdout.write(chalk.dim(` Unlock: ${hit.feature.unlockHow}\\n`));\n }\n if (hit.feature.surfaces && hit.feature.surfaces.length > 0) {\n process.stdout.write(\n chalk.dim(` Surface: ${hit.feature.surfaces.slice(0, 2).join(', ')}\\n`),\n );\n }\n process.stdout.write(chalk.dim(` ddt features show ${hit.feature.id}\\n\\n`));\n }\n\n const aiNote =\n stage === 2\n ? cacheHit\n ? ' (AI-cached, 0 tokens)'\n : ` (AI-escalated, ${tokensUsed} tokens)`\n : '';\n process.stdout.write(\n chalk.dim(\n ` ${hits.length} result${hits.length === 1 ? '' : 's'} for \"${query}\"${aiNote}. `,\n ) + chalk.dim('ddt features list · ddt features show <id>\\n'),\n );\n });\n return cmd;\n}\n"],"mappings":"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","names":[]}