enya-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (389) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.github/workflows/publish.yml +250 -0
  4. package/.gitmodules +3 -0
  5. package/Cargo.lock +3584 -0
  6. package/Cargo.toml +97 -0
  7. package/crates/enact/Cargo.toml +27 -0
  8. package/crates/enact/src/lib.rs +60 -0
  9. package/crates/enact-a2a/Cargo.toml +25 -0
  10. package/crates/enact-a2a/src/lib.rs +411 -0
  11. package/crates/enact-channels/Cargo.toml +64 -0
  12. package/crates/enact-channels/examples/README.md +80 -0
  13. package/crates/enact-channels/examples/channel_bot.rs +169 -0
  14. package/crates/enact-channels/examples/telegram-echo.rs +34 -0
  15. package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
  16. package/crates/enact-channels/src/config.rs +213 -0
  17. package/crates/enact-channels/src/lib.rs +25 -0
  18. package/crates/enact-channels/src/runtime.rs +237 -0
  19. package/crates/enact-channels/src/security/mod.rs +5 -0
  20. package/crates/enact-channels/src/security/pairing.rs +205 -0
  21. package/crates/enact-channels/src/teams.rs +601 -0
  22. package/crates/enact-channels/src/telegram.rs +2833 -0
  23. package/crates/enact-channels/src/traits.rs +200 -0
  24. package/crates/enact-channels/src/webhook.rs +262 -0
  25. package/crates/enact-channels/src/whatsapp.rs +310 -0
  26. package/crates/enact-cli/Cargo.toml +40 -0
  27. package/crates/enact-cli/src/commands/doctor.rs +62 -0
  28. package/crates/enact-cli/src/commands/mod.rs +3 -0
  29. package/crates/enact-cli/src/commands/run.rs +69 -0
  30. package/crates/enact-cli/src/commands/serve.rs +81 -0
  31. package/crates/enact-cli/src/config.rs +2 -0
  32. package/crates/enact-cli/src/main.rs +79 -0
  33. package/crates/enact-config/Cargo.toml +36 -0
  34. package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
  35. package/crates/enact-config/QUICK_REFERENCE.md +92 -0
  36. package/crates/enact-config/README.md +107 -0
  37. package/crates/enact-config/TESTING.md +161 -0
  38. package/crates/enact-config/examples/test-env-vars.rs +100 -0
  39. package/crates/enact-config/src/config.rs +399 -0
  40. package/crates/enact-config/src/encrypted_store.rs +211 -0
  41. package/crates/enact-config/src/lib.rs +298 -0
  42. package/crates/enact-config/src/secrets.rs +149 -0
  43. package/crates/enact-config/src/sync.rs +260 -0
  44. package/crates/enact-config/test-env-vars.sh +34 -0
  45. package/crates/enact-config/tests/README.md +99 -0
  46. package/crates/enact-config/tests/config_integration_test.rs +202 -0
  47. package/crates/enact-config/tests/security_test.rs +140 -0
  48. package/crates/enact-context/Cargo.toml +41 -0
  49. package/crates/enact-context/src/budget.rs +314 -0
  50. package/crates/enact-context/src/calibrator.rs +535 -0
  51. package/crates/enact-context/src/compactor.rs +392 -0
  52. package/crates/enact-context/src/condenser.rs +826 -0
  53. package/crates/enact-context/src/lib.rs +94 -0
  54. package/crates/enact-context/src/segment.rs +238 -0
  55. package/crates/enact-context/src/step_context.rs +645 -0
  56. package/crates/enact-context/src/token_counter.rs +148 -0
  57. package/crates/enact-context/src/window.rs +372 -0
  58. package/crates/enact-core/Cargo.toml +42 -0
  59. package/crates/enact-core/README.md +98 -0
  60. package/crates/enact-core/src/background/executor.rs +524 -0
  61. package/crates/enact-core/src/background/mod.rs +48 -0
  62. package/crates/enact-core/src/background/target_binding.rs +390 -0
  63. package/crates/enact-core/src/background/trigger.rs +511 -0
  64. package/crates/enact-core/src/callable/callable.rs +152 -0
  65. package/crates/enact-core/src/callable/composite.rs +817 -0
  66. package/crates/enact-core/src/callable/graph.rs +104 -0
  67. package/crates/enact-core/src/callable/llm.rs +211 -0
  68. package/crates/enact-core/src/callable/mod.rs +64 -0
  69. package/crates/enact-core/src/callable/registry.rs +206 -0
  70. package/crates/enact-core/src/context/execution_context.rs +757 -0
  71. package/crates/enact-core/src/context/invocation.rs +99 -0
  72. package/crates/enact-core/src/context/mod.rs +50 -0
  73. package/crates/enact-core/src/context/tenant.rs +175 -0
  74. package/crates/enact-core/src/context/trace.rs +127 -0
  75. package/crates/enact-core/src/flow/conditional.rs +293 -0
  76. package/crates/enact-core/src/flow/mod.rs +43 -0
  77. package/crates/enact-core/src/flow/parallel.rs +437 -0
  78. package/crates/enact-core/src/flow/repeat.rs +534 -0
  79. package/crates/enact-core/src/flow/sequential.rs +248 -0
  80. package/crates/enact-core/src/graph/checkpoint.rs +79 -0
  81. package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
  82. package/crates/enact-core/src/graph/compiled.rs +189 -0
  83. package/crates/enact-core/src/graph/edge.rs +59 -0
  84. package/crates/enact-core/src/graph/graph_schema.rs +218 -0
  85. package/crates/enact-core/src/graph/loader.rs +155 -0
  86. package/crates/enact-core/src/graph/mod.rs +18 -0
  87. package/crates/enact-core/src/graph/node/function.rs +49 -0
  88. package/crates/enact-core/src/graph/node/mod.rs +48 -0
  89. package/crates/enact-core/src/graph/schema.rs +62 -0
  90. package/crates/enact-core/src/inbox/message.rs +405 -0
  91. package/crates/enact-core/src/inbox/mod.rs +31 -0
  92. package/crates/enact-core/src/inbox/store.rs +355 -0
  93. package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
  94. package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
  95. package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
  96. package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
  97. package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
  98. package/crates/enact-core/src/kernel/error.rs +1200 -0
  99. package/crates/enact-core/src/kernel/event.rs +1394 -0
  100. package/crates/enact-core/src/kernel/execution_model.rs +831 -0
  101. package/crates/enact-core/src/kernel/execution_state.rs +189 -0
  102. package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
  103. package/crates/enact-core/src/kernel/ids.rs +2086 -0
  104. package/crates/enact-core/src/kernel/interrupt.rs +125 -0
  105. package/crates/enact-core/src/kernel/kernel.rs +1283 -0
  106. package/crates/enact-core/src/kernel/mod.rs +205 -0
  107. package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
  108. package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
  109. package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
  110. package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
  111. package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
  112. package/crates/enact-core/src/kernel/reducer.rs +808 -0
  113. package/crates/enact-core/src/kernel/replay.rs +153 -0
  114. package/crates/enact-core/src/lib.rs +413 -0
  115. package/crates/enact-core/src/memory/episodic.rs +0 -0
  116. package/crates/enact-core/src/memory/mod.rs +6 -0
  117. package/crates/enact-core/src/memory/semantic.rs +0 -0
  118. package/crates/enact-core/src/memory/trait.rs +0 -0
  119. package/crates/enact-core/src/memory/vector_db.rs +0 -0
  120. package/crates/enact-core/src/memory/working.rs +0 -0
  121. package/crates/enact-core/src/policy/execution_policy.rs +292 -0
  122. package/crates/enact-core/src/policy/filters.rs +458 -0
  123. package/crates/enact-core/src/policy/input_processor.rs +407 -0
  124. package/crates/enact-core/src/policy/long_running.rs +134 -0
  125. package/crates/enact-core/src/policy/mod.rs +193 -0
  126. package/crates/enact-core/src/policy/pii_input.rs +274 -0
  127. package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
  128. package/crates/enact-core/src/policy/tool_policy.rs +407 -0
  129. package/crates/enact-core/src/providers/mod.rs +63 -0
  130. package/crates/enact-core/src/providers/trait.rs +292 -0
  131. package/crates/enact-core/src/runner/callbacks.rs +6 -0
  132. package/crates/enact-core/src/runner/execution_runner.rs +476 -0
  133. package/crates/enact-core/src/runner/loop.rs +117 -0
  134. package/crates/enact-core/src/runner/mod.rs +58 -0
  135. package/crates/enact-core/src/runner/protected_runner.rs +280 -0
  136. package/crates/enact-core/src/signal/inmemory.rs +231 -0
  137. package/crates/enact-core/src/signal/mod.rs +108 -0
  138. package/crates/enact-core/src/streaming/event_logger.rs +195 -0
  139. package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
  140. package/crates/enact-core/src/streaming/mod.rs +108 -0
  141. package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
  142. package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
  143. package/crates/enact-core/src/streaming/protection/context.rs +136 -0
  144. package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
  145. package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
  146. package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
  147. package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
  148. package/crates/enact-core/src/streaming/sse.rs +0 -0
  149. package/crates/enact-core/src/telemetry/exporter.rs +0 -0
  150. package/crates/enact-core/src/telemetry/init.rs +0 -0
  151. package/crates/enact-core/src/telemetry/mod.rs +49 -0
  152. package/crates/enact-core/src/telemetry/spans.rs +245 -0
  153. package/crates/enact-core/src/tool/agent_tool.rs +177 -0
  154. package/crates/enact-core/src/tool/browser/mod.rs +0 -0
  155. package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
  156. package/crates/enact-core/src/tool/cost.rs +247 -0
  157. package/crates/enact-core/src/tool/discovery.rs +0 -0
  158. package/crates/enact-core/src/tool/dispatcher.rs +347 -0
  159. package/crates/enact-core/src/tool/filesystem.rs +231 -0
  160. package/crates/enact-core/src/tool/function.rs +99 -0
  161. package/crates/enact-core/src/tool/git.rs +162 -0
  162. package/crates/enact-core/src/tool/http.rs +214 -0
  163. package/crates/enact-core/src/tool/mcp/client.rs +0 -0
  164. package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
  165. package/crates/enact-core/src/tool/mod.rs +51 -0
  166. package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
  167. package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
  168. package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
  169. package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
  170. package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
  171. package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
  172. package/crates/enact-core/src/tool/shell.rs +147 -0
  173. package/crates/enact-core/src/tool/trait.rs +33 -0
  174. package/crates/enact-core/src/tool/web_search.rs +277 -0
  175. package/crates/enact-core/src/util/config.rs +0 -0
  176. package/crates/enact-core/src/util/errors.rs +0 -0
  177. package/crates/enact-core/src/util/mod.rs +6 -0
  178. package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
  179. package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
  180. package/crates/enact-core/tests/e2e_test.rs +259 -0
  181. package/crates/enact-core/tests/graph_test.rs +130 -0
  182. package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
  183. package/crates/enact-cron/Cargo.toml +28 -0
  184. package/crates/enact-cron/src/lib.rs +44 -0
  185. package/crates/enact-cron/src/schedule.rs +156 -0
  186. package/crates/enact-cron/src/store.rs +589 -0
  187. package/crates/enact-cron/src/types.rs +148 -0
  188. package/crates/enact-gateway/Cargo.toml +31 -0
  189. package/crates/enact-gateway/README.md +30 -0
  190. package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
  191. package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
  192. package/crates/enact-gateway/src/lib.rs +582 -0
  193. package/crates/enact-mcp/Cargo.toml +24 -0
  194. package/crates/enact-mcp/src/lib.rs +178 -0
  195. package/crates/enact-memory/Cargo.toml +25 -0
  196. package/crates/enact-memory/src/backend.rs +20 -0
  197. package/crates/enact-memory/src/chunker.rs +230 -0
  198. package/crates/enact-memory/src/embeddings.rs +221 -0
  199. package/crates/enact-memory/src/lib.rs +67 -0
  200. package/crates/enact-memory/src/markdown.rs +127 -0
  201. package/crates/enact-memory/src/none.rs +61 -0
  202. package/crates/enact-memory/src/sqlite.rs +276 -0
  203. package/crates/enact-memory/src/traits.rs +65 -0
  204. package/crates/enact-memory/src/vector.rs +198 -0
  205. package/crates/enact-oauth/Cargo.toml +27 -0
  206. package/crates/enact-oauth/src/lib.rs +584 -0
  207. package/crates/enact-observability/Cargo.toml +22 -0
  208. package/crates/enact-observability/src/lib.rs +197 -0
  209. package/crates/enact-providers/Cargo.toml +33 -0
  210. package/crates/enact-providers/examples/hello-agent.rs +33 -0
  211. package/crates/enact-providers/src/anthropic.rs +182 -0
  212. package/crates/enact-providers/src/azure.rs +96 -0
  213. package/crates/enact-providers/src/bridge.rs +221 -0
  214. package/crates/enact-providers/src/gemini.rs +227 -0
  215. package/crates/enact-providers/src/http.rs +78 -0
  216. package/crates/enact-providers/src/lib.rs +53 -0
  217. package/crates/enact-providers/src/openai_compatible.rs +167 -0
  218. package/crates/enact-providers/src/openrouter.rs +33 -0
  219. package/crates/enact-runner/Cargo.toml +24 -0
  220. package/crates/enact-runner/README.md +76 -0
  221. package/crates/enact-runner/src/compaction.rs +225 -0
  222. package/crates/enact-runner/src/config.rs +118 -0
  223. package/crates/enact-runner/src/lib.rs +63 -0
  224. package/crates/enact-runner/src/loop_driver.rs +414 -0
  225. package/crates/enact-runner/src/parser.rs +421 -0
  226. package/crates/enact-runner/src/retry.rs +262 -0
  227. package/crates/enact-runner/tests/integration.rs +278 -0
  228. package/crates/enact-security/Cargo.toml +22 -0
  229. package/crates/enact-security/src/audit.rs +375 -0
  230. package/crates/enact-security/src/lib.rs +37 -0
  231. package/crates/enact-security/src/policy.rs +406 -0
  232. package/crates/enact-skills/Cargo.toml +25 -0
  233. package/crates/enact-skills/src/lib.rs +506 -0
  234. package/crates/enact-tools/Cargo.toml +22 -0
  235. package/crates/enact-tools/src/file_read.rs +166 -0
  236. package/crates/enact-tools/src/file_write.rs +216 -0
  237. package/crates/enact-tools/src/git_operations.rs +513 -0
  238. package/crates/enact-tools/src/http_request.rs +417 -0
  239. package/crates/enact-tools/src/lib.rs +104 -0
  240. package/crates/enact-tools/src/security.rs +227 -0
  241. package/crates/enact-tools/src/shell.rs +191 -0
  242. package/crates/enact-tools/src/traits.rs +159 -0
  243. package/docs/Makefile +74 -0
  244. package/docs/config.toml +62 -0
  245. package/docs/content/_index.md +174 -0
  246. package/docs/content/a2a/_index.md +431 -0
  247. package/docs/content/api/_index.md +323 -0
  248. package/docs/content/channels/_index.md +160 -0
  249. package/docs/content/channels/teams.md +205 -0
  250. package/docs/content/channels/telegram.md +182 -0
  251. package/docs/content/channels/webhook.md +423 -0
  252. package/docs/content/channels/whatsapp.md +240 -0
  253. package/docs/content/cli/_index.md +261 -0
  254. package/docs/content/concepts/_index.md +273 -0
  255. package/docs/content/configuration/_index.md +241 -0
  256. package/docs/content/cron/_index.md +248 -0
  257. package/docs/content/developers/_index.md +278 -0
  258. package/docs/content/getting-started/_index.md +180 -0
  259. package/docs/content/installation/_index.md +186 -0
  260. package/docs/content/installation/uninstall.md +101 -0
  261. package/docs/content/installation/updating.md +120 -0
  262. package/docs/content/mcp/_index.md +215 -0
  263. package/docs/content/memory/_index.md +163 -0
  264. package/docs/content/oauth/_index.md +515 -0
  265. package/docs/content/providers/_index.md +206 -0
  266. package/docs/content/roadmap/_index.md +199 -0
  267. package/docs/content/security/_index.md +219 -0
  268. package/docs/content/skills/_index.md +228 -0
  269. package/docs/content/tools/_index.md +485 -0
  270. package/docs/content/troubleshooting/_index.md +259 -0
  271. package/docs/content/yaml-schema/_index.md +294 -0
  272. package/docs/static/giallo-dark.css +91 -0
  273. package/docs/static/giallo-light.css +91 -0
  274. package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
  275. package/docs/themes/tanuki/LICENSE +21 -0
  276. package/docs/themes/tanuki/README.md +166 -0
  277. package/docs/themes/tanuki/examples/blog/config.toml +58 -0
  278. package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
  279. package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
  280. package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
  281. package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
  282. package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
  283. package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
  284. package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
  285. package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
  286. package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
  287. package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
  288. package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
  289. package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
  290. package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
  291. package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
  292. package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
  293. package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
  294. package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
  295. package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
  296. package/docs/themes/tanuki/examples/book/config.toml +17 -0
  297. package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
  298. package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
  299. package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
  300. package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
  301. package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
  302. package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
  303. package/docs/themes/tanuki/examples/book/content/print.md +6 -0
  304. package/docs/themes/tanuki/examples/docs/config.toml +28 -0
  305. package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
  306. package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
  307. package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
  308. package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
  309. package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
  310. package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
  311. package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
  312. package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
  313. package/docs/themes/tanuki/examples/index.html +2104 -0
  314. package/docs/themes/tanuki/mise.toml +108 -0
  315. package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
  316. package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
  317. package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
  318. package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
  319. package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
  320. package/docs/themes/tanuki/sass/components/_code.scss +457 -0
  321. package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
  322. package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
  323. package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
  324. package/docs/themes/tanuki/sass/components/_print.scss +237 -0
  325. package/docs/themes/tanuki/sass/components/_search.scss +224 -0
  326. package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
  327. package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
  328. package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
  329. package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
  330. package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
  331. package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
  332. package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
  333. package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
  334. package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
  335. package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
  336. package/docs/themes/tanuki/sass/style.scss +47 -0
  337. package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
  338. package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
  339. package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
  340. package/docs/themes/tanuki/screenshot.png +0 -0
  341. package/docs/themes/tanuki/sentinel.kdl +59 -0
  342. package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
  343. package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
  344. package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
  345. package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
  346. package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
  347. package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
  348. package/docs/themes/tanuki/static/js/anchors.js +18 -0
  349. package/docs/themes/tanuki/static/js/app.js +274 -0
  350. package/docs/themes/tanuki/static/js/code.js +394 -0
  351. package/docs/themes/tanuki/static/js/navigation.js +778 -0
  352. package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
  353. package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
  354. package/docs/themes/tanuki/static/js/search.js +215 -0
  355. package/docs/themes/tanuki/static/js/theme.js +169 -0
  356. package/docs/themes/tanuki/static/syntax-dark.css +151 -0
  357. package/docs/themes/tanuki/static/syntax-light.css +151 -0
  358. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
  359. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
  360. package/docs/themes/tanuki/templates/404.html +52 -0
  361. package/docs/themes/tanuki/templates/base.html +428 -0
  362. package/docs/themes/tanuki/templates/blog.html +66 -0
  363. package/docs/themes/tanuki/templates/home.html +108 -0
  364. package/docs/themes/tanuki/templates/index.html +178 -0
  365. package/docs/themes/tanuki/templates/landing.html +168 -0
  366. package/docs/themes/tanuki/templates/macros/nav.html +128 -0
  367. package/docs/themes/tanuki/templates/macros/posts.html +101 -0
  368. package/docs/themes/tanuki/templates/macros/ui.html +159 -0
  369. package/docs/themes/tanuki/templates/page.html +135 -0
  370. package/docs/themes/tanuki/templates/partials/footer.html +38 -0
  371. package/docs/themes/tanuki/templates/partials/header.html +366 -0
  372. package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
  373. package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
  374. package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
  375. package/docs/themes/tanuki/templates/partials/search.html +52 -0
  376. package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
  377. package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
  378. package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
  379. package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
  380. package/docs/themes/tanuki/templates/print.html +244 -0
  381. package/docs/themes/tanuki/templates/section.html +186 -0
  382. package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
  383. package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
  384. package/docs/themes/tanuki/theme.toml +58 -0
  385. package/examples/hello-agent.rs +55 -0
  386. package/package.json +36 -0
  387. package/proto/config.proto +60 -0
  388. package/proto/events.proto +0 -0
  389. package/proto/runtime.proto +215 -0
@@ -0,0 +1,163 @@
1
+ +++
2
+ title = "Memory"
3
+ weight = 9
4
+ +++
5
+
6
+ # Memory System
7
+
8
+ The `enact-memory` crate provides local-first memory backends for agents with semantic search capabilities.
9
+
10
+ ## Backends
11
+
12
+ | Backend | Description | Use Case |
13
+ |---------|-------------|----------|
14
+ | `sqlite` | SQLite with FTS5 full-text search | Production, persistent storage |
15
+ | `markdown` | Markdown files in workspace | Development, version control |
16
+ | `none` | No persistence | Testing, stateless agents |
17
+
18
+ ## Features
19
+
20
+ ### Core Memory Operations
21
+
22
+ ```rust
23
+ use enact_memory::{create_memory, MemoryCategory};
24
+
25
+ let memory = create_memory("sqlite", workspace_path)?;
26
+
27
+ // Store a memory
28
+ memory.store("user_preference", "prefers dark mode", MemoryCategory::Core, None).await?;
29
+
30
+ // Recall memories (keyword search)
31
+ let results = memory.recall("dark mode", 10, None).await?;
32
+
33
+ // Get specific memory
34
+ let entry = memory.get("user_preference").await?;
35
+
36
+ // List all memories in a category
37
+ let all_core = memory.list(Some(&MemoryCategory::Core), None).await?;
38
+
39
+ // Forget a memory
40
+ memory.forget("user_preference").await?;
41
+ ```
42
+
43
+ ### Memory Categories
44
+
45
+ | Category | Purpose |
46
+ |----------|---------|
47
+ | `Core` | Long-term facts, preferences, decisions |
48
+ | `Daily` | Session logs, daily context |
49
+ | `Conversation` | Current conversation context |
50
+ | `Custom(name)` | User-defined categories |
51
+
52
+ ### Embeddings for Semantic Search
53
+
54
+ The memory system supports embeddings for semantic similarity search:
55
+
56
+ ```rust
57
+ use enact_memory::{create_embedding_provider, cosine_similarity};
58
+
59
+ // Create an embedding provider
60
+ let provider = create_embedding_provider(
61
+ "openai",
62
+ Some(&api_key),
63
+ "text-embedding-3-small",
64
+ 1536
65
+ );
66
+
67
+ // Embed text
68
+ let embedding = provider.embed_one("Hello world").await?;
69
+
70
+ // Compare embeddings
71
+ let similarity = cosine_similarity(&embedding_a, &embedding_b);
72
+ ```
73
+
74
+ #### Supported Providers
75
+
76
+ | Provider | Model | Dimensions |
77
+ |----------|-------|------------|
78
+ | `openai` | text-embedding-3-small | 1536 |
79
+ | `openai` | text-embedding-3-large | 3072 |
80
+ | `custom:URL` | Any OpenAI-compatible | Configurable |
81
+ | `none` | N/A (keyword-only) | 0 |
82
+
83
+ ### Text Chunking
84
+
85
+ For large documents, use the chunker to split into semantic chunks:
86
+
87
+ ```rust
88
+ use enact_memory::chunk_markdown;
89
+
90
+ let chunks = chunk_markdown(document_text, 512); // 512 tokens per chunk
91
+
92
+ for chunk in chunks {
93
+ println!("Chunk {}: {}", chunk.index, chunk.content);
94
+ if let Some(heading) = chunk.heading {
95
+ println!(" Under heading: {}", heading);
96
+ }
97
+ }
98
+ ```
99
+
100
+ The chunker:
101
+ - Splits on markdown headings (`#`, `##`, `###`)
102
+ - Falls back to paragraph boundaries
103
+ - Then to line boundaries
104
+ - Preserves heading context in each chunk
105
+
106
+ ### Hybrid Search (Vector + Keyword)
107
+
108
+ Combine semantic and keyword search for best results:
109
+
110
+ ```rust
111
+ use enact_memory::hybrid_merge;
112
+
113
+ let vector_results = vec![("doc1".into(), 0.9), ("doc2".into(), 0.7)];
114
+ let keyword_results = vec![("doc2".into(), 10.0), ("doc3".into(), 5.0)];
115
+
116
+ let merged = hybrid_merge(
117
+ &vector_results,
118
+ &keyword_results,
119
+ 0.7, // vector weight
120
+ 0.3, // keyword weight
121
+ 10 // limit
122
+ );
123
+
124
+ for result in merged {
125
+ println!("{}: final_score={}", result.id, result.final_score);
126
+ }
127
+ ```
128
+
129
+ ### Vector Operations
130
+
131
+ ```rust
132
+ use enact_memory::{vec_to_bytes, bytes_to_vec};
133
+
134
+ // Serialize for storage
135
+ let bytes = vec_to_bytes(&embedding);
136
+
137
+ // Deserialize
138
+ let restored = bytes_to_vec(&bytes);
139
+ ```
140
+
141
+ ## Configuration
142
+
143
+ ```yaml
144
+ # In agent config
145
+ memory:
146
+ backend: sqlite # sqlite, markdown, none
147
+ embeddings:
148
+ provider: openai
149
+ model: text-embedding-3-small
150
+ dimensions: 1536
151
+ ```
152
+
153
+ ## Session Scoping
154
+
155
+ Memories can be scoped to sessions for conversation context:
156
+
157
+ ```rust
158
+ // Store with session
159
+ memory.store("topic", "discussing rust", MemoryCategory::Conversation, Some("session-123")).await?;
160
+
161
+ // Recall within session
162
+ let results = memory.recall("rust", 10, Some("session-123")).await?;
163
+ ```
@@ -0,0 +1,515 @@
1
+ +++
2
+ title = "OAuth Authentication"
3
+ weight = 16
4
+ +++
5
+
6
+ # OAuth Authentication
7
+
8
+ The `enact-oauth` crate provides OAuth 2.0 authentication with device-code flow, enabling secure authentication with OpenAI and other providers without storing API keys in environment variables.
9
+
10
+ ## Overview
11
+
12
+ OAuth support includes:
13
+
14
+ - **Device-code flow** - User-friendly authentication via browser
15
+ - **PKCE support** - Secure code exchange
16
+ - **Token refresh** - Automatic access token renewal
17
+ - **Local storage** - Secure token storage on disk
18
+ - **Account ID extraction** - JWT token parsing
19
+
20
+ ## Supported Providers
21
+
22
+ | Provider | Flow | Status |
23
+ |----------|------|--------|
24
+ | OpenAI | Device-code | ✅ Complete |
25
+ | Anthropic | OAuth 2.0 | ✅ Supported via generic flow |
26
+ | Custom | OAuth 2.0 | ✅ Supported |
27
+
28
+ ## Quick Start
29
+
30
+ ### Device-Code Flow (Recommended)
31
+
32
+ ```rust
33
+ use enact_oauth::{start_device_code_flow, poll_device_code_tokens};
34
+ use reqwest::Client;
35
+
36
+ #[tokio::main]
37
+ async fn main() -> anyhow::Result<()> {
38
+ let client = Client::new();
39
+
40
+ // Step 1: Start device-code flow
41
+ let device = start_device_code_flow(&client).await?;
42
+
43
+ println!("Go to: {}", device.verification_uri);
44
+ println!("Enter code: {}", device.user_code);
45
+ println!("Expires in: {} seconds", device.expires_in);
46
+
47
+ // Step 2: Poll for tokens
48
+ // User must visit URL and enter code in their browser
49
+ let tokens = poll_device_code_tokens(&client, &device).await?;
50
+
51
+ println!("Access token: {}", tokens.access_token);
52
+ println!("Refresh token: {:?}", tokens.refresh_token);
53
+ println!("Expires at: {:?}", tokens.expires_at);
54
+
55
+ // Step 3: Save tokens securely
56
+ // See Token Storage section below
57
+
58
+ Ok(())
59
+ }
60
+ ```
61
+
62
+ ### User Experience
63
+
64
+ ```
65
+ $ enact login
66
+
67
+ To authenticate with OpenAI:
68
+
69
+ 1. Visit: https://auth.openai.com/device
70
+ 2. Enter code: ABCD-EFGH
71
+
72
+ Waiting for authentication...
73
+ ✓ Successfully authenticated!
74
+ Account ID: user-abc123
75
+ ```
76
+
77
+ ## Token Types
78
+
79
+ ### TokenSet
80
+
81
+ ```rust
82
+ use enact_oauth::TokenSet;
83
+
84
+ let tokens = TokenSet {
85
+ access_token: "eyJhbG...".to_string(),
86
+ refresh_token: Some("dGhpcyBpcyBh...".to_string()),
87
+ id_token: Some("eyJhbG...".to_string()),
88
+ expires_at: Some(chrono::Utc::now() + chrono::Duration::hours(1)),
89
+ token_type: Some("Bearer".to_string()),
90
+ scope: Some("openid profile".to_string()),
91
+ };
92
+
93
+ // Check if token is expiring soon
94
+ let expiring = tokens.is_expiring_within(Duration::from_secs(300));
95
+ ```
96
+
97
+ ## Token Refresh
98
+
99
+ Automatically refresh access tokens:
100
+
101
+ ```rust
102
+ use enact_oauth::refresh_access_token;
103
+
104
+ async fn get_valid_token(client: &Client) -> anyhow::Result<String> {
105
+ let tokens = load_stored_tokens()?; // Your storage function
106
+
107
+ // Check if token needs refresh
108
+ if tokens.is_expiring_within(Duration::from_secs(300)) {
109
+ if let Some(refresh_token) = &tokens.refresh_token {
110
+ let new_tokens = refresh_access_token(client, refresh_token).await?;
111
+
112
+ // Save new tokens
113
+ save_tokens(&new_tokens)?;
114
+
115
+ return Ok(new_tokens.access_token);
116
+ }
117
+ }
118
+
119
+ Ok(tokens.access_token)
120
+ }
121
+ ```
122
+
123
+ ## Token Storage
124
+
125
+ ### Local File Storage
126
+
127
+ ```rust
128
+ use enact_oauth::TokenStorage;
129
+ use std::path::Path;
130
+
131
+ // Create storage
132
+ let storage = TokenStorage::new(Path::new("~/.config/enact/tokens"));
133
+
134
+ // Save tokens
135
+ storage.save_token(
136
+ "openai", // provider
137
+ "default", // profile name
138
+ &tokens
139
+ )?;
140
+
141
+ // Load tokens
142
+ if let Some(tokens) = storage.load_token("openai", "default")? {
143
+ println!("Access token: {}", tokens.access_token);
144
+ }
145
+ ```
146
+
147
+ ### Secure Storage (Recommended)
148
+
149
+ ```rust
150
+ use enact_oauth::TokenStorage;
151
+ use std::path::PathBuf;
152
+
153
+ fn get_token_dir() -> PathBuf {
154
+ dirs::config_dir()
155
+ .expect("Could not find config directory")
156
+ .join("enact")
157
+ .join("tokens")
158
+ }
159
+
160
+ let storage = TokenStorage::new(&get_token_dir());
161
+
162
+ // Tokens are stored as JSON files:
163
+ // ~/.config/enact/tokens/openai_default.json
164
+ ```
165
+
166
+ ## PKCE (Proof Key for Code Exchange)
167
+
168
+ PKCE adds security to OAuth flows:
169
+
170
+ ```rust
171
+ use enact_oauth::{generate_pkce_state, build_authorize_url};
172
+
173
+ // Generate PKCE parameters
174
+ let pkce = generate_pkce_state();
175
+
176
+ println!("Code verifier: {}", pkce.code_verifier);
177
+ println!("Code challenge: {}", pkce.code_challenge);
178
+ println!("State: {}", pkce.state);
179
+
180
+ // Build authorization URL
181
+ let auth_url = build_authorize_url(&pkce);
182
+ println!("Open this URL: {}", auth_url);
183
+
184
+ // After user authorizes, exchange code for tokens
185
+ let tokens = exchange_code_for_tokens(&client, &code, &pkce
186
+ ).await?;
187
+ ```
188
+
189
+ ## Complete Example
190
+
191
+ ```rust
192
+ use enact_oauth::*;
193
+ use reqwest::Client;
194
+ use std::path::Path;
195
+
196
+ #[tokio::main]
197
+ async fn main() -> anyhow::Result<()> {
198
+ let client = Client::new();
199
+ let storage = TokenStorage::new(Path::new("~/.config/enact/tokens"));
200
+
201
+ // Try to load existing token
202
+ if let Some(tokens) = storage.load_token("openai", "default")? {
203
+ if !tokens.is_expiring_within(Duration::from_secs(300)) {
204
+ println!("Using cached token");
205
+ return Ok(());
206
+ }
207
+
208
+ // Try to refresh
209
+ if let Some(refresh) = &tokens.refresh_token {
210
+ match refresh_access_token(&client, refresh).await {
211
+ Ok(new_tokens) => {
212
+ storage.save_token("openai", "default", &new_tokens)?;
213
+ println!("Token refreshed successfully");
214
+ return Ok(());
215
+ }
216
+ Err(e) => {
217
+ eprintln!("Refresh failed: {}, starting new auth flow", e);
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ // Start new device-code flow
224
+ let device = start_device_code_flow(&client).await?;
225
+
226
+ println!("\n=== Authentication Required ===");
227
+ println!("Visit: {}", device.verification_uri);
228
+ println!("Code: {}", device.user_code);
229
+ println!("Expires in {} seconds\n", device.expires_in);
230
+
231
+ // Poll for completion
232
+ let tokens = poll_device_code_tokens(&client, &device).await?;
233
+
234
+ // Extract account info
235
+ if let Some(account_id) = extract_account_id_from_jwt(&tokens.access_token) {
236
+ println!("Authenticated as: {}", account_id);
237
+ }
238
+
239
+ // Save tokens
240
+ storage.save_token("openai", "default", &tokens)?;
241
+ println!("Tokens saved successfully");
242
+
243
+ Ok(())
244
+ }
245
+ ```
246
+
247
+ ## CLI Integration
248
+
249
+ ```rust
250
+ // In your CLI
251
+ #[derive(Subcommand)]
252
+ enum Commands {
253
+ /// Authenticate with a provider
254
+ Login {
255
+ /// Provider name (openai, anthropic)
256
+ #[arg(default_value = "openai")]
257
+ provider: String,
258
+
259
+ /// Profile name
260
+ #[arg(default_value = "default")]
261
+ profile: String,
262
+ },
263
+
264
+ /// List authenticated profiles
265
+ Profiles,
266
+
267
+ /// Remove authentication
268
+ Logout {
269
+ provider: String,
270
+ profile: String,
271
+ },
272
+ }
273
+
274
+ async fn login(provider: &str, profile: &str) -> anyhow::Result<()> {
275
+ let client = Client::new();
276
+ let storage = TokenStorage::new(&get_token_dir());
277
+
278
+ match provider {
279
+ "openai" => {
280
+ let device = start_device_code_flow(&client).await?;
281
+
282
+ println!("Go to: {}", device.verification_uri);
283
+ println!("Enter code: {}", device.user_code);
284
+
285
+ let tokens = poll_device_code_tokens(&client, &device).await?;
286
+ storage.save_token(provider, profile, &tokens)?;
287
+
288
+ println!("✓ Successfully authenticated with OpenAI");
289
+ }
290
+ _ => anyhow::bail!("Unsupported provider: {}", provider),
291
+ }
292
+
293
+ Ok(())
294
+ }
295
+ ```
296
+
297
+ ## Configuration
298
+
299
+ ### Environment Variables
300
+
301
+ ```bash
302
+ # Token storage directory
303
+ export ENACT_TOKEN_DIR="$HOME/.config/enact/tokens"
304
+
305
+ # Default provider
306
+ export ENACT_DEFAULT_PROVIDER="openai"
307
+
308
+ # Provider-specific settings
309
+ export ENACT_OPENAI_CLIENT_ID="app_EMoamEEZ73f0CkXaXp7hrann"
310
+ ```
311
+
312
+ ### YAML Configuration
313
+
314
+ ```yaml
315
+ # config.yaml
316
+ oauth:
317
+ default_provider: openai
318
+
319
+ providers:
320
+ openai:
321
+ client_id: "app_EMoamEEZ73f0CkXaXp7hrann"
322
+ authorize_url: "https://auth.openai.com/oauth/authorize"
323
+ token_url: "https://auth.openai.com/oauth/token"
324
+ device_code_url: "https://auth.openai.com/oauth/device/code"
325
+
326
+ anthropic:
327
+ client_id: "your-client-id"
328
+ authorize_url: "https://auth.anthropic.com/oauth/authorize"
329
+ token_url: "https://auth.anthropic.com/oauth/token"
330
+
331
+ storage:
332
+ type: file
333
+ path: "~/.config/enact/tokens"
334
+ ```
335
+
336
+ ## Security Considerations
337
+
338
+ ### Token Storage
339
+
340
+ - ✅ Tokens stored in user's home directory
341
+ - ✅ File permissions should be `600` (user read/write only)
342
+ - ✅ Never commit tokens to version control
343
+ - ✅ Use OS keychain when available (future enhancement)
344
+
345
+ ```rust
346
+ // Set restrictive permissions (Unix)
347
+ #[cfg(unix)]
348
+ use std::os::unix::fs::PermissionsExt;
349
+
350
+ let mut perms = std::fs::metadata(&token_path)?.permissions();
351
+ perms.set_mode(0o600);
352
+ std::fs::set_permissions(&token_path, perms)?;
353
+ ```
354
+
355
+ ### Token Lifecycle
356
+
357
+ 1. **Short-lived access tokens** (typically 1 hour)
358
+ 2. **Long-lived refresh tokens** (can be revoked)
359
+ 3. **Automatic refresh** before expiration
360
+ 4. **Secure storage** with proper permissions
361
+
362
+ ### Best Practices
363
+
364
+ ```rust
365
+ // 1. Always check token expiration
366
+ if tokens.is_expiring_within(Duration::from_secs(300)) {
367
+ // Refresh token
368
+ }
369
+
370
+ // 2. Handle refresh failures gracefully
371
+ match refresh_access_token(&client, refresh_token).await {
372
+ Ok(new_tokens) => { /* use new tokens */ }
373
+ Err(_) => {
374
+ // Start new auth flow
375
+ initiate_device_code_flow().await?;
376
+ }
377
+ }
378
+
379
+ // 3. Never log tokens
380
+ // ❌ BAD
381
+ println!("Token: {}", tokens.access_token);
382
+
383
+ // ✅ GOOD
384
+ println!("Token obtained successfully");
385
+ ```
386
+
387
+ ## Testing
388
+
389
+ ```bash
390
+ # Run OAuth tests
391
+ cargo test -p enact-oauth
392
+
393
+ # Run with output
394
+ cargo test -p enact-oauth -- --nocapture
395
+ ```
396
+
397
+ ### Mock Testing
398
+
399
+ ```rust
400
+ #[cfg(test)]
401
+ mod tests {
402
+ use super::*;
403
+
404
+ #[test]
405
+ fn test_pkce_generation() {
406
+ let pkce = generate_pkce_state();
407
+ assert!(!pkce.code_verifier.is_empty());
408
+ assert!(!pkce.code_challenge.is_empty());
409
+ assert!(!pkce.state.is_empty());
410
+ }
411
+
412
+ #[test]
413
+ fn test_parse_redirect_url() {
414
+ let code = parse_code_from_redirect(
415
+ "http://localhost:1455/auth/callback?code=abc123&state=xyz",
416
+ Some("xyz"),
417
+ ).unwrap();
418
+ assert_eq!(code, "abc123");
419
+ }
420
+
421
+ #[test]
422
+ fn test_extract_account_from_jwt() {
423
+ let jwt = "eyJhbG..."; // Valid JWT with account_id claim
424
+ let account = extract_account_id_from_jwt(jwt);
425
+ assert!(account.is_some());
426
+ }
427
+ }
428
+ ```
429
+
430
+ ## Troubleshooting
431
+
432
+ ### "Device code expired"
433
+
434
+ The user didn't complete authentication in time:
435
+
436
+ ```rust
437
+ match poll_device_code_tokens(&client, &device).await {
438
+ Ok(tokens) => { /* success */ }
439
+ Err(e) if e.to_string().contains("expired") => {
440
+ println!("Authentication timed out. Please try again.");
441
+ }
442
+ Err(e) => {
443
+ eprintln!("Authentication failed: {}", e);
444
+ }
445
+ }
446
+ ```
447
+
448
+ ### "Access denied"
449
+
450
+ User denied the authorization request:
451
+
452
+ ```rust
453
+ Err(e) if e.to_string().contains("access_denied") => {
454
+ println!("Authorization was denied by user.");
455
+ }
456
+ ```
457
+
458
+ ### Token refresh failing
459
+
460
+ Refresh token may be revoked or expired:
461
+
462
+ ```rust
463
+ match refresh_access_token(&client, refresh_token).await {
464
+ Ok(tokens) => { /* success */ }
465
+ Err(_) => {
466
+ // Clear stored tokens and re-authenticate
467
+ storage.remove_token("openai", "default")?;
468
+ println!("Please run 'enact login' again.");
469
+ }
470
+ }
471
+ ```
472
+
473
+ ## Provider-Specific Notes
474
+
475
+ ### OpenAI
476
+
477
+ - Client ID: `app_EMoamEEZ73f0CkXaXp7hrann` (fixed)
478
+ - Device-code flow: Supported ✅
479
+ - PKCE: Supported ✅
480
+ - Scopes: `openid profile email offline_access`
481
+ - Account ID: Extractable from JWT claims
482
+
483
+ ### Anthropic
484
+
485
+ - Requires separate OAuth application registration
486
+ - Client ID provided after registration
487
+ - Same flow as OpenAI with different endpoints
488
+
489
+ ## Comparison: OAuth vs API Keys
490
+
491
+ | Feature | OAuth | API Keys |
492
+ |---------|-------|----------|
493
+ | **Security** | ✅ Token rotation, limited scope | ⚠️ Single key, full access |
494
+ | **User Experience** | 🔐 Browser auth, no copy-paste | ⌨️ Manual key management |
495
+ | **Revocation** | ✅ Can revoke without changing code | ❌ Must regenerate and update |
496
+ | **Expiration** | ✅ Short-lived tokens | ❌ Never expires (risky) |
497
+ | **Setup Complexity** | Medium (one-time browser auth) | Low (copy-paste key) |
498
+ | **CI/CD** | ⚠️ Requires token storage | ✅ Simple env var |
499
+
500
+ **Recommendation**: Use OAuth for interactive usage, API keys for CI/CD.
501
+
502
+ ## Future Enhancements
503
+
504
+ - [ ] OS keychain integration (macOS Keychain, Windows Credential Manager, Linux Secret Service)
505
+ - [ ] Browser-based OAuth flow (authorization code)
506
+ - [ ] Multiple profile support
507
+ - [ ] Token encryption at rest
508
+ - [ ] Automatic background refresh
509
+ - [ ] Provider discovery (OIDC)
510
+
511
+ ## Resources
512
+
513
+ - [enact-oauth crate](/docs/crates/enact-oauth/)
514
+ - [OAuth 2.0 Device Authorization Grant (RFC 8628)](https://tools.ietf.org/html/rfc8628)
515
+ - [PKCE (RFC 7636)](https://tools.ietf.org/html/rfc7636)