smartpipe-cli 1.4.0rc2__tar.gz → 1.4.0rc3__tar.gz

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 (255) hide show
  1. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/CHANGELOG.md +45 -0
  2. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/CITATION.cff +1 -1
  3. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/CONTRIBUTING.md +6 -5
  4. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/PKG-INFO +32 -11
  5. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/README.md +30 -10
  6. smartpipe_cli-1.4.0rc3/demo/video/README.md +23 -0
  7. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/Main.tsx +26 -7
  8. smartpipe_cli-1.4.0rc3/demo/video/src/Narration.tsx +94 -0
  9. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/Root.tsx +17 -2
  10. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/components/Background.tsx +3 -10
  11. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/components/Terminal.tsx +69 -4
  12. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/config.ts +10 -6
  13. smartpipe_cli-1.4.0rc3/demo/video/src/narration/durations.json +13 -0
  14. smartpipe_cli-1.4.0rc3/demo/video/src/narration/lines.json +13 -0
  15. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/scenes/Close.tsx +1 -2
  16. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/scenes/CostHonesty.tsx +27 -42
  17. smartpipe_cli-1.4.0rc3/demo/video/src/scenes/Graph.tsx +236 -0
  18. smartpipe_cli-1.4.0rc3/demo/video/src/scenes/Hook.tsx +47 -0
  19. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/scenes/Multimodal.tsx +150 -31
  20. smartpipe_cli-1.4.0rc3/demo/video/src/scenes/ScaleA.tsx +43 -0
  21. smartpipe_cli-1.4.0rc3/demo/video/src/scenes/ScaleB.tsx +42 -0
  22. smartpipe_cli-1.4.0rc3/docs/assets/demo-thumb.jpg +0 -0
  23. smartpipe_cli-1.4.0rc3/docs/assets/graph-hero.png +0 -0
  24. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/concepts/models-and-providers.md +17 -3
  25. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/README.md +3 -2
  26. smartpipe_cli-1.4.0rc3/docs/cookbook/knowledge-graph.md +131 -0
  27. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/demo/index.html +1 -1
  28. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/index.md +8 -6
  29. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/privacy.md +24 -10
  30. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/reference/cli.md +34 -9
  31. smartpipe_cli-1.4.0rc3/docs/verbs/graph.md +217 -0
  32. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/mkdocs.yml +8 -1
  33. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/pyproject.toml +4 -1
  34. smartpipe_cli-1.4.0rc3/src/smartpipe/cli/auth_cmd.py +268 -0
  35. smartpipe_cli-1.4.0rc3/src/smartpipe/cli/config_cmd.py +1205 -0
  36. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/doctor_cmd.py +2 -1
  37. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/root.py +1 -1
  38. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/screens.py +1 -0
  39. smartpipe_cli-1.4.0rc3/src/smartpipe/config/authflow.py +202 -0
  40. smartpipe_cli-1.4.0rc3/src/smartpipe/config/credentials.py +241 -0
  41. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/display.py +38 -16
  42. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/doctor.py +21 -19
  43. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/picker.py +263 -13
  44. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/state_cache.py +50 -3
  45. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/store.py +31 -2
  46. smartpipe_cli-1.4.0rc3/src/smartpipe/config/verify.py +288 -0
  47. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/container.py +3 -1
  48. smartpipe_cli-1.4.0rc3/src/smartpipe/io/richui.py +112 -0
  49. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/catalogs.py +62 -7
  50. smartpipe_cli-1.4.0rc3/src/smartpipe/models/keycheck.py +108 -0
  51. smartpipe_cli-1.4.0rc2/demo/video/README.md +0 -17
  52. smartpipe_cli-1.4.0rc2/demo/video/src/scenes/Hook.tsx +0 -40
  53. smartpipe_cli-1.4.0rc2/demo/video/src/scenes/Scale.tsx +0 -47
  54. smartpipe_cli-1.4.0rc2/docs/assets/demo-thumb.jpg +0 -0
  55. smartpipe_cli-1.4.0rc2/src/smartpipe/cli/auth_cmd.py +0 -78
  56. smartpipe_cli-1.4.0rc2/src/smartpipe/cli/config_cmd.py +0 -564
  57. smartpipe_cli-1.4.0rc2/src/smartpipe/config/credentials.py +0 -118
  58. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/.gitignore +0 -0
  59. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/LICENSE +0 -0
  60. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/NOTICE +0 -0
  61. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/components/Caption.tsx +0 -0
  62. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/components/FileGlyph.tsx +0 -0
  63. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/components/SceneFrame.tsx +0 -0
  64. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/components/Wordmark.tsx +0 -0
  65. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/font.ts +0 -0
  66. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/index.ts +0 -0
  67. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/demo/video/src/scenes/ColdOpen.tsx +0 -0
  68. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/comparison.md +0 -0
  69. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/concepts/feeding-smartpipe.md +0 -0
  70. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/concepts/output-formats.md +0 -0
  71. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/concepts/pipes-and-items.md +0 -0
  72. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/concepts/structured-output.md +0 -0
  73. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/concepts/the-item.md +0 -0
  74. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/contract-extraction.md +0 -0
  75. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/customer-feedback.md +0 -0
  76. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/invoice-reconciliation.md +0 -0
  77. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/live-monitoring.md +0 -0
  78. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/log-triage.md +0 -0
  79. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/meeting-digest.md +0 -0
  80. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/ranking-documents.md +0 -0
  81. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/stream-enrichment.md +0 -0
  82. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/training-data-prep.md +0 -0
  83. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/video-qa.md +0 -0
  84. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/cookbook/visualizing-results.md +0 -0
  85. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/inputs/files.md +0 -0
  86. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/install.md +0 -0
  87. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/learn/1-first-pipeline.md +0 -0
  88. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/learn/2-structured-data.md +0 -0
  89. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/learn/3-files-and-media.md +0 -0
  90. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/learn/4-free-verbs.md +0 -0
  91. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/learn/5-scale-and-cost.md +0 -0
  92. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/learn/6-pipelines-that-last.md +0 -0
  93. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/quickstart.md +0 -0
  94. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/reference/agents.md +0 -0
  95. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/reference/custom-verbs.md +0 -0
  96. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/reference/sem-files.md +0 -0
  97. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/troubleshooting.md +0 -0
  98. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/cluster.md +0 -0
  99. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/diff.md +0 -0
  100. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/distinct.md +0 -0
  101. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/embed.md +0 -0
  102. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/extend.md +0 -0
  103. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/filter.md +0 -0
  104. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/getschema.md +0 -0
  105. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/join.md +0 -0
  106. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/map.md +0 -0
  107. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/outliers.md +0 -0
  108. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/readable.md +0 -0
  109. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/reduce.md +0 -0
  110. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/sample.md +0 -0
  111. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/sort.md +0 -0
  112. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/split.md +0 -0
  113. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/summarize.md +0 -0
  114. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/top-k.md +0 -0
  115. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/docs/verbs/where.md +0 -0
  116. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/qa/README.md +0 -0
  117. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/qa/fixtures/README.md +0 -0
  118. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/__init__.py +0 -0
  119. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/__main__.py +0 -0
  120. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/assets/probe.png +0 -0
  121. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/assets/probe.txt +0 -0
  122. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/assets/probe.wav +0 -0
  123. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/__init__.py +0 -0
  124. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/cache_cmd.py +0 -0
  125. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/chart_cmd.py +0 -0
  126. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/cite_cmd.py +0 -0
  127. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/cluster_cmd.py +0 -0
  128. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/completions.py +0 -0
  129. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/diff_cmd.py +0 -0
  130. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/distinct_cmd.py +0 -0
  131. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/echo_cmd.py +0 -0
  132. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/embed_cmd.py +0 -0
  133. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/extend_cmd.py +0 -0
  134. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/filter_cmd.py +0 -0
  135. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/getschema_cmd.py +0 -0
  136. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/graph_cmd.py +0 -0
  137. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/input_options.py +0 -0
  138. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/interrupts.py +0 -0
  139. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/join_cmd.py +0 -0
  140. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/map_cmd.py +0 -0
  141. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/outliers_cmd.py +0 -0
  142. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/probe_cmd.py +0 -0
  143. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/read_cmd.py +0 -0
  144. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/readable_cmd.py +0 -0
  145. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/reduce_cmd.py +0 -0
  146. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/run_cmd.py +0 -0
  147. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/sample_cmd.py +0 -0
  148. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/schema_cmd.py +0 -0
  149. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/schema_workshop.py +0 -0
  150. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/sem_file.py +0 -0
  151. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/sort_cmd.py +0 -0
  152. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/split_cmd.py +0 -0
  153. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/summarize_cmd.py +0 -0
  154. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/top_k_cmd.py +0 -0
  155. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/update_cmd.py +0 -0
  156. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/usage_cmd.py +0 -0
  157. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/where_cmd.py +0 -0
  158. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/cli/write_cmd.py +0 -0
  159. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/__init__.py +0 -0
  160. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/config/paths.py +0 -0
  161. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/core/__init__.py +0 -0
  162. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/core/errors.py +0 -0
  163. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/core/install_channel.py +0 -0
  164. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/core/jsontools.py +0 -0
  165. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/core/versions.py +0 -0
  166. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/__init__.py +0 -0
  167. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/aggregate.py +0 -0
  168. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/blocking.py +0 -0
  169. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/chart.py +0 -0
  170. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/chunking.py +0 -0
  171. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/clustering.py +0 -0
  172. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/graphkg.py +0 -0
  173. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/graphout.py +0 -0
  174. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/predicate.py +0 -0
  175. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/preview.py +0 -0
  176. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/progressbar.py +0 -0
  177. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/prompts.py +0 -0
  178. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/ranking.py +0 -0
  179. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/repair.py +0 -0
  180. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/runner.py +0 -0
  181. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/schema.py +0 -0
  182. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/schema_dsl.py +0 -0
  183. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/schema_repl.py +0 -0
  184. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/tally.py +0 -0
  185. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/temporal.py +0 -0
  186. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/timebin.py +0 -0
  187. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/units.py +0 -0
  188. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/engine/windows.py +0 -0
  189. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/__init__.py +0 -0
  190. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/arrow_menu.py +0 -0
  191. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/csvrows.py +0 -0
  192. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/diagnostics.py +0 -0
  193. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/inputs.py +0 -0
  194. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/items.py +0 -0
  195. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/leaderboard.py +0 -0
  196. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/metering.py +0 -0
  197. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/preview.py +0 -0
  198. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/progress.py +0 -0
  199. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/readers.py +0 -0
  200. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/render.py +0 -0
  201. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/text.py +0 -0
  202. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/tty.py +0 -0
  203. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/update_check.py +0 -0
  204. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/usage.py +0 -0
  205. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/io/writers.py +0 -0
  206. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/__init__.py +0 -0
  207. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/anthropic_adapter.py +0 -0
  208. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/base.py +0 -0
  209. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/budget.py +0 -0
  210. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/cache.py +0 -0
  211. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/gemini_native.py +0 -0
  212. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/http_support.py +0 -0
  213. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/jina.py +0 -0
  214. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/local_embed.py +0 -0
  215. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/local_ner.py +0 -0
  216. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/ocr.py +0 -0
  217. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/ollama.py +0 -0
  218. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/openai_codex.py +0 -0
  219. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/openai_compat.py +0 -0
  220. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/openai_oauth.py +0 -0
  221. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/resolve.py +0 -0
  222. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/retry.py +0 -0
  223. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/stt.py +0 -0
  224. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/models/windows.py +0 -0
  225. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/parsing/__init__.py +0 -0
  226. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/parsing/detect.py +0 -0
  227. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/parsing/extract.py +0 -0
  228. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/py.typed +0 -0
  229. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/__init__.py +0 -0
  230. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/chart.py +0 -0
  231. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/cluster.py +0 -0
  232. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/common.py +0 -0
  233. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/convert.py +0 -0
  234. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/diff.py +0 -0
  235. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/distinct.py +0 -0
  236. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/embed.py +0 -0
  237. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/extend.py +0 -0
  238. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/filter.py +0 -0
  239. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/getschema.py +0 -0
  240. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/graph.py +0 -0
  241. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/graphfull.py +0 -0
  242. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/join.py +0 -0
  243. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/map.py +0 -0
  244. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/outliers.py +0 -0
  245. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/oversize.py +0 -0
  246. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/readable.py +0 -0
  247. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/reduce.py +0 -0
  248. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/sample.py +0 -0
  249. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/sortverb.py +0 -0
  250. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/split.py +0 -0
  251. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/summarize.py +0 -0
  252. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/top_k.py +0 -0
  253. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/where.py +0 -0
  254. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/src/smartpipe/verbs/write.py +0 -0
  255. {smartpipe_cli-1.4.0rc2 → smartpipe_cli-1.4.0rc3}/tests/corpus/README.md +0 -0
@@ -5,11 +5,20 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+
8
9
  ## [1.4.0] — 2026-07-08
9
10
 
10
11
  The identity release.
11
12
 
12
13
  ### Knowledge graphs (the highlight)
14
+ - **The showcase**: a full verb page with the modality matrix and a
15
+ live-rendering Mermaid sample, a cookbook chapter written from real
16
+ runs against the downloadable playground corpus, the README highlight
17
+ with a screenshot of the case graph, agent SKILL recipes, and `graph`
18
+ on the welcome screen. Found en route: the playground download in the
19
+ docs pointed at the source tarball (no media inside) - all three
20
+ sites now fetch the actual 26 MB release asset.
21
+
13
22
  - **`smartpipe graph --fast` turns a corpus into a knowledge graph for
14
23
  exactly $0.** A local zero-shot NER model (GLiNER-small over ONNX -
15
24
  no new dependencies, no torch, a one-time ~190 MB download like the
@@ -256,6 +265,42 @@ The identity release.
256
265
  media-embedding capability.
257
266
 
258
267
  ### Setup that reads the room
268
+ - **Rich now backs `config show` and `doctor`.** It is a deliberate core
269
+ dependency under the no-extras policy, imported only while those human-facing
270
+ screens render so `--help`, pipes, and `NO_COLOR` keep their existing contracts.
271
+ - **The interactive config wizard can go backward.** Model, embedding, OCR, and
272
+ final review screens expose explicit Back rows; returning to a stage restores
273
+ its pre-stage checkpoint, and typed model prompts accept `back`, `b`, `no`, or
274
+ `n` instead of accidentally saving `ollama/n`.
275
+
276
+
277
+ - **Discarding config is terminal and interruption is ordinary.** Choosing
278
+ discard no longer opens the shell-completion prompt after saying "Not saved";
279
+ Ctrl-C or EOF at any Click prompt exits 130 instead of showing the internal-bug
280
+ screen.
281
+
282
+ - **`auth login` now connects every provider, not just ChatGPT.** Pick
283
+ from the full list - OpenAI appears twice because it really is two
284
+ wires (API key vs ChatGPT login, and the login wire has no
285
+ embeddings) - paste your key at a masked prompt after the create-a-key
286
+ URL, and it validates live before storing (with an honest
287
+ "store anyway - the provider may be having a bad minute" escape).
288
+ Keys live at `~/.local/share/smartpipe/auth.json`, owner-only
289
+ permissions from the first byte, masked everywhere they surface, and
290
+ the environment variable always wins. `auth list` shows what's
291
+ connected and which source is live; `auth logout` removes.
292
+ - **Setup walks three questions in order: text model, embeddings,
293
+ document OCR.** Every stage shows every provider - connected ones
294
+ badged, unconnected ones connectable inline without leaving the flow.
295
+ The embedding stage only lists wires that embed; OCR is optional and
296
+ skippable in one keypress. At the end, one consented verification
297
+ pass (~5 tiny requests) probes what your models can actually do -
298
+ and if the basic text check fails, smartpipe reports a setup fault
299
+ instead of pretending your model lacks modalities. Results feed the
300
+ capability chips you see in every picker from then on, alongside a
301
+ public model registry and your own declarations for self-hosted
302
+ models.
303
+
259
304
  - **The schema workshop.** `smartpipe schema` with no arguments at a
260
305
  terminal opens a small interactive draft loop - your schema pinned up
261
306
  top as colored braces, commands scrolling below: `/add name type`,
@@ -7,5 +7,5 @@ authors:
7
7
  given-names: Prabal
8
8
  repository-code: "https://github.com/prabal-rje/smartpipe"
9
9
  license: Apache-2.0
10
- version: 1.4.0rc2
10
+ version: 1.4.0rc3
11
11
  date-released: '2026-07-08'
@@ -9,7 +9,7 @@ Requires [uv](https://docs.astral.sh/uv/) and Python ≥ 3.11 (uv can fetch one
9
9
 
10
10
  ```console
11
11
  $ git clone https://github.com/prabal-rje/smartpipe && cd smartpipe
12
- $ uv sync --all-extras # creates .venv with dev + optional deps
12
+ $ uv sync --all-extras # creates .venv with the project and dev tools
13
13
  $ uv run smartpipe # welcome screen = working install
14
14
  ```
15
15
 
@@ -64,12 +64,13 @@ Reverting to built-in search is emptying the three values again.
64
64
 
65
65
  ## New dependencies
66
66
 
67
- Core install weight is a feature: the runtime dependency list is `click`, `httpx`,
68
- `jsonschema`, `tomli-w` a snapshot test guards it. Anything heavier goes behind an
69
- optional extra, and heavy imports stay function-local (startup time is budgeted).
67
+ Core install weight is a feature, but D46 deliberately ships the complete
68
+ multimodal surface in core: there are no optional extras. The frozen dependency
69
+ snapshot makes every direct or transitive addition an explicit review decision.
70
+ Heavy imports stay function-local so startup remains budgeted.
70
71
 
71
72
  **New heavy import? Function-local or justify.** `--help` must never import
72
- `httpx`, `jsonschema`, `anthropic`, or `markitdown` —
73
+ `httpx`, `jsonschema`, `anthropic`, `markitdown`, or `rich` —
73
74
  `tests/test_startup_imports.py` is the enforcement (it runs `-X importtime` and
74
75
  fails on any banned module). `make startup` gives an advisory wall-clock number;
75
76
  the import test is the deterministic gate.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smartpipe-cli
3
- Version: 1.4.0rc2
3
+ Version: 1.4.0rc3
4
4
  Summary: Semantic pipes and queries for your terminal - PDFs, images, audio, video, and text through Unix verbs that understand.
5
5
  Project-URL: Homepage, https://github.com/prabal-rje/smartpipe
6
6
  Project-URL: Changelog, https://github.com/prabal-rje/smartpipe/blob/main/CHANGELOG.md
@@ -32,6 +32,7 @@ Requires-Dist: matplotlib>=3.8
32
32
  Requires-Dist: pdfminer-six>=20231228
33
33
  Requires-Dist: plotext>=5
34
34
  Requires-Dist: pypdf>=5.0
35
+ Requires-Dist: rich>=13.9
35
36
  Requires-Dist: tomli-w>=1.0
36
37
  Description-Content-Type: text/markdown
37
38
 
@@ -48,9 +49,9 @@ Description-Content-Type: text/markdown
48
49
  Run PDFs, images, audio, video, and text through Unix verbs that understand
49
50
  their input.
50
51
 
51
- [![smartpipe demo - click to play with sound and controls](https://github.com/prabal-rje/smartpipe/releases/download/demo-assets/smartpipe-teaser-v3.gif)](https://prabal-rje.github.io/smartpipe/demo/)
52
+ [![smartpipe demo - click to play with sound and controls](https://github.com/prabal-rje/smartpipe/releases/download/demo-assets/smartpipe-teaser-v4.gif)](https://prabal-rje.github.io/smartpipe/demo/)
52
53
 
53
- *[▶ Watch the full 70-second demo](https://prabal-rje.github.io/smartpipe/demo/) - sound, controls, no download.*
54
+ *[▶ Watch the full 86-second demo](https://prabal-rje.github.io/smartpipe/demo/) - sound, controls, no download.*
54
55
 
55
56
  ## Install
56
57
 
@@ -139,6 +140,7 @@ into ordinary Unix tools. Every verb is documented at
139
140
  | [`distinct`][distinct] | fold near-duplicates; `--exact` is free | `sort -u`, by meaning |
140
141
  | [`diff`][diff] | what distinguishes two sets | the post-incident answer |
141
142
  | [`outliers`][outliers] | the items least like the rest | novelty, surfaced |
143
+ | [`graph`][graph] | corpus → entity/relationship graph; `--fast` is free | the case wall, with citations |
142
144
 
143
145
  **Free verbs** never call a model. Run them first to cut the corpus before any
144
146
  paid stage:
@@ -154,9 +156,10 @@ paid stage:
154
156
  | [`chart`][cli] | terminal bars, SVG, facets, time series | quick plots |
155
157
 
156
158
  Some semantic verbs have a **conditionally free mode**: `join --on` (key
157
- equality, no prompt), `distinct --exact` (hash-only folding), `map`/`extend`
158
- `--dry-run` (compose without sending), and `smartpipe schema` with a
159
- braces/DSL expression. Each stays at zero model calls by construction.
159
+ equality, no prompt), `distinct --exact` (hash-only folding), `graph --fast`
160
+ (local NER, on-device), `map`/`extend` `--dry-run` (compose without sending),
161
+ and `smartpipe schema` with a braces/DSL expression. Each stays at zero model
162
+ calls by construction.
160
163
 
161
164
  ## A one-minute tour
162
165
 
@@ -192,6 +195,9 @@ smartpipe map "Extract {vendor string, invoice_number string, total number}" 'in
192
195
  # 8. video RAG, no vector database: index a folder of recordings once, ask any day
193
196
  smartpipe embed 'sessions/**/*.mp4' > sessions.embeddings
194
197
  smartpipe top_k 3 --near "user gives up after the coupon fails" < sessions.embeddings
198
+
199
+ # 9. the corpus as a knowledge graph - $0: local NER, zero model calls
200
+ smartpipe graph --fast 'case-files/*.md' --save case.html
195
201
  ```
196
202
 
197
203
  A `.txt` on a pipe arrives one item per line; `--as file` treats the whole
@@ -206,6 +212,18 @@ Numbers 7 and 8 are full recipes -
206
212
  New to this? The [Learn track][quickstart] starts at zero and assumes nothing, including
207
213
  what a "model" is.
208
214
 
215
+ ### Highlight: the knowledge graph
216
+
217
+ Number 9 is new: `graph --fast` turns a folder of mixed files into an
218
+ interactive, **cited** knowledge graph without a single model call - a local
219
+ NER model finds the entities you name, co-occurrence weights the edges, and
220
+ hovering any edge shows the files behind it. A focus prompt upgrades the
221
+ strongest edges to model-read relations. Full story:
222
+ [the `graph` page](https://prabal-rje.github.io/smartpipe/verbs/graph/) and
223
+ [the cookbook recipe](docs/cookbook/knowledge-graph.md).
224
+
225
+ ![smartpipe graph --save case.html: an interactive knowledge graph with per-edge source citations](docs/assets/graph-hero.png)
226
+
209
227
  ## Try it on real files
210
228
 
211
229
  No corpus handy? [smartpipe-playground][playground] ships 26 MB of CC0 /
@@ -213,8 +231,8 @@ public-domain practice files - invoices, reports, photos, recordings, screen
213
231
  sessions, and JSONL data:
214
232
 
215
233
  ```bash
216
- curl -L https://github.com/prabal-rje/smartpipe-playground/archive/refs/tags/v1.tar.gz | tar xz
217
- cd smartpipe-playground-1
234
+ curl -L https://github.com/prabal-rje/smartpipe-playground/releases/download/v1/smartpipe-playground-v1.tar.gz | tar xz
235
+ cd smartpipe-playground
218
236
 
219
237
  smartpipe map "Extract {vendor, invoice_number, total number}" 'invoices/*.pdf'
220
238
  smartpipe filter "the customer sounds frustrated" 'recordings/*.mp3'
@@ -236,9 +254,11 @@ For chat, [Ollama][ollama] runs models on your machine. Any cloud model sends
236
254
  that run's data to its provider - `gpt-5.4-mini`, `claude-opus-4-8`,
237
255
  `gemini-3.1-flash-lite`, `mistral-large-latest`, `openrouter/…`.
238
256
 
239
- API keys come from environment variables and are never stored. ChatGPT
240
- subscribers can run `smartpipe auth login` instead. `smartpipe usage` keeps
241
- local run and token totals; see [Privacy & security][privacy] for the details.
257
+ API keys come from environment variables, or `smartpipe auth login` stores
258
+ one securely for you. ChatGPT subscribers can log in instead of using a key.
259
+ `smartpipe usage` keeps local run and token totals; see
260
+ [Privacy & security][privacy] for where credentials live and how to remove
261
+ them.
242
262
 
243
263
  ## Unix behavior
244
264
 
@@ -303,6 +323,7 @@ lives in [`qa/`](qa/README.md). The CLI surface is a SemVer contract.
303
323
  [distinct]: https://prabal-rje.github.io/smartpipe/verbs/distinct/
304
324
  [diff]: https://prabal-rje.github.io/smartpipe/verbs/diff/
305
325
  [outliers]: https://prabal-rje.github.io/smartpipe/verbs/outliers/
326
+ [graph]: https://prabal-rje.github.io/smartpipe/verbs/graph/
306
327
  [where]: https://prabal-rje.github.io/smartpipe/verbs/where/
307
328
  [summarize]: https://prabal-rje.github.io/smartpipe/verbs/summarize/
308
329
  [sort]: https://prabal-rje.github.io/smartpipe/verbs/sort/
@@ -11,9 +11,9 @@
11
11
  Run PDFs, images, audio, video, and text through Unix verbs that understand
12
12
  their input.
13
13
 
14
- [![smartpipe demo - click to play with sound and controls](https://github.com/prabal-rje/smartpipe/releases/download/demo-assets/smartpipe-teaser-v3.gif)](https://prabal-rje.github.io/smartpipe/demo/)
14
+ [![smartpipe demo - click to play with sound and controls](https://github.com/prabal-rje/smartpipe/releases/download/demo-assets/smartpipe-teaser-v4.gif)](https://prabal-rje.github.io/smartpipe/demo/)
15
15
 
16
- *[▶ Watch the full 70-second demo](https://prabal-rje.github.io/smartpipe/demo/) - sound, controls, no download.*
16
+ *[▶ Watch the full 86-second demo](https://prabal-rje.github.io/smartpipe/demo/) - sound, controls, no download.*
17
17
 
18
18
  ## Install
19
19
 
@@ -102,6 +102,7 @@ into ordinary Unix tools. Every verb is documented at
102
102
  | [`distinct`][distinct] | fold near-duplicates; `--exact` is free | `sort -u`, by meaning |
103
103
  | [`diff`][diff] | what distinguishes two sets | the post-incident answer |
104
104
  | [`outliers`][outliers] | the items least like the rest | novelty, surfaced |
105
+ | [`graph`][graph] | corpus → entity/relationship graph; `--fast` is free | the case wall, with citations |
105
106
 
106
107
  **Free verbs** never call a model. Run them first to cut the corpus before any
107
108
  paid stage:
@@ -117,9 +118,10 @@ paid stage:
117
118
  | [`chart`][cli] | terminal bars, SVG, facets, time series | quick plots |
118
119
 
119
120
  Some semantic verbs have a **conditionally free mode**: `join --on` (key
120
- equality, no prompt), `distinct --exact` (hash-only folding), `map`/`extend`
121
- `--dry-run` (compose without sending), and `smartpipe schema` with a
122
- braces/DSL expression. Each stays at zero model calls by construction.
121
+ equality, no prompt), `distinct --exact` (hash-only folding), `graph --fast`
122
+ (local NER, on-device), `map`/`extend` `--dry-run` (compose without sending),
123
+ and `smartpipe schema` with a braces/DSL expression. Each stays at zero model
124
+ calls by construction.
123
125
 
124
126
  ## A one-minute tour
125
127
 
@@ -155,6 +157,9 @@ smartpipe map "Extract {vendor string, invoice_number string, total number}" 'in
155
157
  # 8. video RAG, no vector database: index a folder of recordings once, ask any day
156
158
  smartpipe embed 'sessions/**/*.mp4' > sessions.embeddings
157
159
  smartpipe top_k 3 --near "user gives up after the coupon fails" < sessions.embeddings
160
+
161
+ # 9. the corpus as a knowledge graph - $0: local NER, zero model calls
162
+ smartpipe graph --fast 'case-files/*.md' --save case.html
158
163
  ```
159
164
 
160
165
  A `.txt` on a pipe arrives one item per line; `--as file` treats the whole
@@ -169,6 +174,18 @@ Numbers 7 and 8 are full recipes -
169
174
  New to this? The [Learn track][quickstart] starts at zero and assumes nothing, including
170
175
  what a "model" is.
171
176
 
177
+ ### Highlight: the knowledge graph
178
+
179
+ Number 9 is new: `graph --fast` turns a folder of mixed files into an
180
+ interactive, **cited** knowledge graph without a single model call - a local
181
+ NER model finds the entities you name, co-occurrence weights the edges, and
182
+ hovering any edge shows the files behind it. A focus prompt upgrades the
183
+ strongest edges to model-read relations. Full story:
184
+ [the `graph` page](https://prabal-rje.github.io/smartpipe/verbs/graph/) and
185
+ [the cookbook recipe](docs/cookbook/knowledge-graph.md).
186
+
187
+ ![smartpipe graph --save case.html: an interactive knowledge graph with per-edge source citations](docs/assets/graph-hero.png)
188
+
172
189
  ## Try it on real files
173
190
 
174
191
  No corpus handy? [smartpipe-playground][playground] ships 26 MB of CC0 /
@@ -176,8 +193,8 @@ public-domain practice files - invoices, reports, photos, recordings, screen
176
193
  sessions, and JSONL data:
177
194
 
178
195
  ```bash
179
- curl -L https://github.com/prabal-rje/smartpipe-playground/archive/refs/tags/v1.tar.gz | tar xz
180
- cd smartpipe-playground-1
196
+ curl -L https://github.com/prabal-rje/smartpipe-playground/releases/download/v1/smartpipe-playground-v1.tar.gz | tar xz
197
+ cd smartpipe-playground
181
198
 
182
199
  smartpipe map "Extract {vendor, invoice_number, total number}" 'invoices/*.pdf'
183
200
  smartpipe filter "the customer sounds frustrated" 'recordings/*.mp3'
@@ -199,9 +216,11 @@ For chat, [Ollama][ollama] runs models on your machine. Any cloud model sends
199
216
  that run's data to its provider - `gpt-5.4-mini`, `claude-opus-4-8`,
200
217
  `gemini-3.1-flash-lite`, `mistral-large-latest`, `openrouter/…`.
201
218
 
202
- API keys come from environment variables and are never stored. ChatGPT
203
- subscribers can run `smartpipe auth login` instead. `smartpipe usage` keeps
204
- local run and token totals; see [Privacy & security][privacy] for the details.
219
+ API keys come from environment variables, or `smartpipe auth login` stores
220
+ one securely for you. ChatGPT subscribers can log in instead of using a key.
221
+ `smartpipe usage` keeps local run and token totals; see
222
+ [Privacy & security][privacy] for where credentials live and how to remove
223
+ them.
205
224
 
206
225
  ## Unix behavior
207
226
 
@@ -266,6 +285,7 @@ lives in [`qa/`](qa/README.md). The CLI surface is a SemVer contract.
266
285
  [distinct]: https://prabal-rje.github.io/smartpipe/verbs/distinct/
267
286
  [diff]: https://prabal-rje.github.io/smartpipe/verbs/diff/
268
287
  [outliers]: https://prabal-rje.github.io/smartpipe/verbs/outliers/
288
+ [graph]: https://prabal-rje.github.io/smartpipe/verbs/graph/
269
289
  [where]: https://prabal-rje.github.io/smartpipe/verbs/where/
270
290
  [summarize]: https://prabal-rje.github.io/smartpipe/verbs/summarize/
271
291
  [sort]: https://prabal-rje.github.io/smartpipe/verbs/sort/
@@ -0,0 +1,23 @@
1
+ # smartpipe demo video
2
+
3
+ A self-contained [Remotion](https://remotion.dev) project that renders the
4
+ 86-second smartpipe demo (1920x1080, 30 fps): eight scenes - cold open, the
5
+ hook, multimodal, cost honesty, index, search, graph, close - all driven by
6
+ one reusable `<Terminal>` component and the frame-math constants in
7
+ `src/config.ts` (tune scene lengths there). Run `npm install` once, then
8
+ `npm run studio` to preview in the browser or `npm run render` to produce
9
+ `out/smartpipe-demo.mp4`; each scene is also registered as its own
10
+ composition (`ColdOpen`, `Hook`, `Multimodal`, `CostHonesty`, `ScaleA`,
11
+ `ScaleB`, `Graph`, `Close`), so `npx remotion render Hook out/hook.mp4`
12
+ re-renders one scene independently. The narrated cut is the `MainNarrated`
13
+ composition: the script text lives in `src/narration/lines.json`, and
14
+ `RIME_API_KEY=... node scripts/fetch-narration.mjs` synthesizes the
15
+ voiceover wavs (Rime.AI, speaker cupola) into the gitignored
16
+ `public/narration/` and refreshes `src/narration/durations.json` - fetch
17
+ before rendering `MainNarrated`. The music bed is
18
+ `public/silicon-prism-waltz.m4a` (60 s, looped over the 86 s cut with a
19
+ fade-out, held far below the voice); if you ever need a
20
+ no-strings-attached replacement, `node scripts/make-music.mjs` (ffmpeg
21
+ required) synthesizes an original ambient track from scratch into
22
+ `public/music.m4a` - royalty-free by construction - and you can point the
23
+ `<Audio>` element in `src/Main.tsx` at it.
@@ -5,22 +5,34 @@ import { Background } from "./components/Background";
5
5
  import { Close } from "./scenes/Close";
6
6
  import { ColdOpen } from "./scenes/ColdOpen";
7
7
  import { CostHonesty } from "./scenes/CostHonesty";
8
+ import { Graph } from "./scenes/Graph";
8
9
  import { Hook } from "./scenes/Hook";
9
10
  import { Multimodal } from "./scenes/Multimodal";
10
- import { Scale } from "./scenes/Scale";
11
+ import { ScaleA } from "./scenes/ScaleA";
12
+ import { ScaleB } from "./scenes/ScaleB";
13
+ import { duckFactor, Narration } from "./Narration";
11
14
 
12
- /** The full ~70s cut: one persistent background, six scenes in series. */
13
- export const Main: React.FC = () => {
15
+ /** Music bed base gain. Owner order (2026-07-09): "lower BG volume much
16
+ * more" 0.55 0.14, about −12 dB. Texture, never presence. */
17
+ const MUSIC_BASE = 0.14;
18
+
19
+ export type MainProps = { narrated?: boolean };
20
+
21
+ /** The full 86s cut: one persistent background, eight scenes in series.
22
+ * With `narrated`, the Rime voiceover plays on top, a subtitle band shows
23
+ * the spoken words, and the music ducks a further few dB under speech. */
24
+ export const Main: React.FC<MainProps> = ({ narrated = false }) => {
14
25
  return (
15
26
  <AbsoluteFill>
16
- {/* Music bed: Silicon Prism Waltz (60s, looped to cover the 70s cut).
27
+ {/* Music bed: Silicon Prism Waltz (60s, looped to cover the 86s cut).
17
28
  Volume envelope: quick fade-in, steady bed, fade-out over the close.
18
29
  A fully synthesized fallback lives at scripts/make-music.mjs. */}
19
30
  <Audio
20
31
  loop
21
32
  src={staticFile("silicon-prism-waltz.m4a")}
22
33
  volume={(f) =>
23
- 0.55 *
34
+ MUSIC_BASE *
35
+ (narrated ? duckFactor(f) : 1) *
24
36
  interpolate(
25
37
  f,
26
38
  [0, 36, TOTAL_FRAMES - 96, TOTAL_FRAMES - 8],
@@ -43,13 +55,20 @@ export const Main: React.FC = () => {
43
55
  <Series.Sequence durationInFrames={SCENE.cost}>
44
56
  <CostHonesty />
45
57
  </Series.Sequence>
46
- <Series.Sequence durationInFrames={SCENE.scale}>
47
- <Scale />
58
+ <Series.Sequence durationInFrames={SCENE.scaleA}>
59
+ <ScaleA />
60
+ </Series.Sequence>
61
+ <Series.Sequence durationInFrames={SCENE.scaleB}>
62
+ <ScaleB />
63
+ </Series.Sequence>
64
+ <Series.Sequence durationInFrames={SCENE.graph}>
65
+ <Graph />
48
66
  </Series.Sequence>
49
67
  <Series.Sequence durationInFrames={SCENE.close}>
50
68
  <Close />
51
69
  </Series.Sequence>
52
70
  </Series>
71
+ {narrated ? <Narration /> : null}
53
72
  </AbsoluteFill>
54
73
  );
55
74
  };
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import {
3
+ AbsoluteFill,
4
+ Audio,
5
+ interpolate,
6
+ Sequence,
7
+ staticFile,
8
+ useCurrentFrame,
9
+ } from "remotion";
10
+ import { COLORS, FPS } from "./config";
11
+ import { MONO } from "./font";
12
+ import lines from "./narration/lines.json";
13
+ import durations from "./narration/durations.json";
14
+
15
+ /**
16
+ * The Rime.AI voiceover: one wav per line of the committed script
17
+ * (src/narration/lines.json), fetched by scripts/fetch-narration.mjs into
18
+ * public/narration/ (gitignored). Each line also renders as a subtitle band
19
+ * below the scene caption cards, and duckFactor() dips the music bed a few
20
+ * dB while anyone is speaking.
21
+ */
22
+
23
+ type Line = { id: string; at: number; text: string };
24
+
25
+ const LINES: readonly Line[] = lines;
26
+ const DURATIONS: Record<string, number> = durations;
27
+
28
+ /** Narration mix gain: the clips peak near −1 dBFS — leave a little headroom. */
29
+ const NARRATION_GAIN = 0.85;
30
+
31
+ /** Music multiplier while the voice speaks (≈ −4.4 dB on top of the bed). */
32
+ const VOICE_DUCK = 0.6;
33
+
34
+ const clamp = { extrapolateLeft: "clamp", extrapolateRight: "clamp" } as const;
35
+
36
+ const clipFrames = (id: string): number => {
37
+ const seconds = DURATIONS[id];
38
+ if (seconds === undefined) {
39
+ throw new Error(`no duration for narration clip "${id}" — run scripts/fetch-narration.mjs`);
40
+ }
41
+ return Math.ceil(seconds * FPS);
42
+ };
43
+
44
+ /** 1 in silence, VOICE_DUCK under speech, with 12-frame ramps either side. */
45
+ export const duckFactor = (frame: number): number => {
46
+ const coverage = Math.max(
47
+ ...LINES.map((line) => {
48
+ const end = line.at + clipFrames(line.id);
49
+ return interpolate(frame, [line.at - 12, line.at, end, end + 12], [0, 1, 1, 0], clamp);
50
+ }),
51
+ 0,
52
+ );
53
+ return 1 - (1 - VOICE_DUCK) * coverage;
54
+ };
55
+
56
+ /** The spoken words, as a dim mono subtitle band under the caption cards. */
57
+ const SubtitleLine: React.FC<{ text: string; duration: number }> = ({
58
+ text,
59
+ duration,
60
+ }) => {
61
+ const frame = useCurrentFrame();
62
+ const opacity = interpolate(frame, [0, 6, duration - 8, duration], [0, 1, 1, 0], clamp);
63
+ return (
64
+ <AbsoluteFill style={{ justifyContent: "flex-end", alignItems: "center" }}>
65
+ <div
66
+ style={{
67
+ marginBottom: 18,
68
+ fontFamily: MONO,
69
+ fontSize: 24,
70
+ color: COLORS.dim,
71
+ letterSpacing: 0.3,
72
+ opacity: opacity * 0.9,
73
+ textShadow: "0 2px 12px rgba(0,0,0,0.85)",
74
+ }}
75
+ >
76
+ {text}
77
+ </div>
78
+ </AbsoluteFill>
79
+ );
80
+ };
81
+
82
+ export const Narration: React.FC = () => (
83
+ <AbsoluteFill>
84
+ {LINES.map((line) => {
85
+ const duration = clipFrames(line.id) + 10;
86
+ return (
87
+ <Sequence key={line.id} from={line.at} durationInFrames={duration}>
88
+ <Audio src={staticFile(`narration/${line.id}.wav`)} volume={NARRATION_GAIN} />
89
+ <SubtitleLine text={line.text} duration={duration} />
90
+ </Sequence>
91
+ );
92
+ })}
93
+ </AbsoluteFill>
94
+ );
@@ -6,9 +6,11 @@ import { Main } from "./Main";
6
6
  import { Close } from "./scenes/Close";
7
7
  import { ColdOpen } from "./scenes/ColdOpen";
8
8
  import { CostHonesty } from "./scenes/CostHonesty";
9
+ import { Graph } from "./scenes/Graph";
9
10
  import { Hook } from "./scenes/Hook";
10
11
  import { Multimodal } from "./scenes/Multimodal";
11
- import { Scale } from "./scenes/Scale";
12
+ import { ScaleA } from "./scenes/ScaleA";
13
+ import { ScaleB } from "./scenes/ScaleB";
12
14
 
13
15
  /** Standalone scene = background + scene, so each renders on its own. */
14
16
  const standalone = (Scene: React.FC): React.FC => {
@@ -26,7 +28,9 @@ const SCENES: readonly { id: string; component: React.FC; duration: number }[] =
26
28
  { id: "Hook", component: standalone(Hook), duration: SCENE.hook },
27
29
  { id: "Multimodal", component: standalone(Multimodal), duration: SCENE.multimodal },
28
30
  { id: "CostHonesty", component: standalone(CostHonesty), duration: SCENE.cost },
29
- { id: "Scale", component: standalone(Scale), duration: SCENE.scale },
31
+ { id: "ScaleA", component: standalone(ScaleA), duration: SCENE.scaleA },
32
+ { id: "ScaleB", component: standalone(ScaleB), duration: SCENE.scaleB },
33
+ { id: "Graph", component: standalone(Graph), duration: SCENE.graph },
30
34
  { id: "Close", component: standalone(Close), duration: SCENE.close },
31
35
  ];
32
36
 
@@ -41,6 +45,17 @@ export const RemotionRoot: React.FC = () => {
41
45
  width={WIDTH}
42
46
  height={HEIGHT}
43
47
  />
48
+ {/* The narrated cut: same timeline + Rime voiceover, subtitles, ducked
49
+ music. Needs public/narration/*.wav — run scripts/fetch-narration.mjs. */}
50
+ <Composition
51
+ id="MainNarrated"
52
+ component={Main}
53
+ durationInFrames={TOTAL_FRAMES}
54
+ fps={FPS}
55
+ width={WIDTH}
56
+ height={HEIGHT}
57
+ defaultProps={{ narrated: true }}
58
+ />
44
59
  {SCENES.map((s) => (
45
60
  <Composition
46
61
  key={s.id}
@@ -3,8 +3,9 @@ import { AbsoluteFill } from "remotion";
3
3
  import { COLORS } from "../config";
4
4
 
5
5
  /**
6
- * The stage every scene plays on: near-black, a whisper of a dot grid,
7
- * two barely-there color glows, scanlines, and a vignette.
6
+ * The stage every scene plays on: a FLAT near-black ground (owner ruling,
7
+ * 2026-07-09: no glowy background gradient), a whisper of a dot grid,
8
+ * scanlines, and a vignette.
8
9
  */
9
10
  export const Background: React.FC = () => {
10
11
  return (
@@ -18,14 +19,6 @@ export const Background: React.FC = () => {
18
19
  backgroundPosition: "14px 14px",
19
20
  }}
20
21
  />
21
- {/* ambient color glows */}
22
- <AbsoluteFill
23
- style={{
24
- background:
25
- "radial-gradient(820px 560px at 22% 18%, rgba(34,211,238,0.07), transparent 70%)," +
26
- "radial-gradient(900px 620px at 82% 78%, rgba(52,211,153,0.055), transparent 70%)",
27
- }}
28
- />
29
22
  {/* scanlines */}
30
23
  <AbsoluteFill
31
24
  style={{
@@ -8,14 +8,24 @@ import { MONO } from "../font";
8
8
  *
9
9
  * Lines are declared with a `kind` and a start frame (`at`, relative to the
10
10
  * enclosing <Sequence>):
11
- * - "cmd" → typed character-by-character behind a block caret
12
- * - "out" → stdout rows that cascade in with a small spring
13
- * - "note" → dimmed stderr diagnostics in yellow (receipts, run notes)
11
+ * - "cmd" → typed character-by-character behind a block caret
12
+ * - "out" → stdout rows that cascade in with a small spring
13
+ * - "note" → dimmed stderr diagnostics in yellow (receipts, run notes)
14
+ * - "ord" → the cyan `#N` ordinal above a human block (io/writers.py)
15
+ * - "field" → one `key: value` block line — key dim, value normal; with
16
+ * `dim` the whole line is the dimmed `__` spine treatment
17
+ * - "wave" → a small audio peak-envelope garnish (io/preview.py's look)
18
+ * - "pair" → a text snippet plus a dim trailing glyph (embed-row picture)
19
+ * `gap` renders the blank-line separation between blocks.
14
20
  */
15
21
  export type TerminalLine =
16
22
  | { kind: "cmd"; text: string; at: number; cps?: number }
17
23
  | { kind: "out"; text: string; at: number }
18
- | { kind: "note"; text: string; at: number };
24
+ | { kind: "note"; text: string; at: number; gap?: boolean }
25
+ | { kind: "ord"; text: string; at: number; gap?: boolean }
26
+ | { kind: "field"; k: string; v: string; at: number; dim?: boolean }
27
+ | { kind: "wave"; text: string; at: number }
28
+ | { kind: "pair"; left: string; right: string; at: number };
19
29
 
20
30
  type Span = { text: string; color: string; weight?: number };
21
31
 
@@ -238,12 +248,15 @@ export const Terminal: React.FC<TerminalProps> = ({
238
248
  extrapolateLeft: "clamp",
239
249
  extrapolateRight: "clamp",
240
250
  });
251
+ const gap =
252
+ (line.kind === "ord" || line.kind === "note") && line.gap === true;
241
253
  const style: React.CSSProperties = {
242
254
  fontSize,
243
255
  lineHeight: `${lineHeight}px`,
244
256
  transform: `translateY(${(1 - p) * 15}px)`,
245
257
  opacity,
246
258
  whiteSpace: "pre",
259
+ marginTop: gap ? Math.round(lineHeight * 0.5) : 0,
247
260
  };
248
261
  if (line.kind === "note") {
249
262
  return (
@@ -255,6 +268,58 @@ export const Terminal: React.FC<TerminalProps> = ({
255
268
  </div>
256
269
  );
257
270
  }
271
+ if (line.kind === "ord") {
272
+ return (
273
+ <div
274
+ key={i}
275
+ style={{ ...style, color: COLORS.cyan, fontWeight: 700 }}
276
+ >
277
+ {line.text}
278
+ </div>
279
+ );
280
+ }
281
+ if (line.kind === "field") {
282
+ // io/render.py `_line`: the key label dim, the value normal; a
283
+ // `dim` line is the `__` spine — dimmed whole, metadata-quiet.
284
+ return (
285
+ <div key={i} style={style}>
286
+ <span style={{ color: line.dim ? COLORS.ghost : COLORS.dim }}>
287
+ {line.k}:{" "}
288
+ </span>
289
+ <span style={{ color: line.dim ? COLORS.faint : COLORS.text }}>
290
+ {line.v}
291
+ </span>
292
+ </div>
293
+ );
294
+ }
295
+ if (line.kind === "pair") {
296
+ return (
297
+ <div key={i} style={style}>
298
+ <span style={{ color: COLORS.text }}>{line.left}</span>
299
+ <span style={{ color: COLORS.faint, opacity: 0.8 }}>
300
+ {" "}
301
+ {line.right}
302
+ </span>
303
+ </div>
304
+ );
305
+ }
306
+ if (line.kind === "wave") {
307
+ // io/preview.py's audio garnish: a peak envelope, nested + quiet.
308
+ return (
309
+ <div
310
+ key={i}
311
+ style={{
312
+ ...style,
313
+ color: COLORS.cyanDim,
314
+ opacity: opacity * 0.55,
315
+ letterSpacing: 1,
316
+ }}
317
+ >
318
+ {" "}
319
+ {line.text}
320
+ </div>
321
+ );
322
+ }
258
323
  return (
259
324
  <div key={i} style={style}>
260
325
  <Spans spans={colorizeOut(line.text)} />