mdcontext 0.0.1 → 0.2.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 (337) hide show
  1. package/.changeset/README.md +28 -0
  2. package/.changeset/config.json +11 -0
  3. package/.claude/settings.local.json +25 -0
  4. package/.github/workflows/ci.yml +83 -0
  5. package/.github/workflows/claude-code-review.yml +44 -0
  6. package/.github/workflows/claude.yml +85 -0
  7. package/.github/workflows/release.yml +113 -0
  8. package/.tldrignore +112 -0
  9. package/BACKLOG.md +338 -0
  10. package/CONTRIBUTING.md +186 -0
  11. package/NOTES/NOTES +44 -0
  12. package/README.md +434 -11
  13. package/biome.json +36 -0
  14. package/cspell.config.yaml +14 -0
  15. package/dist/chunk-23UPXDNL.js +3044 -0
  16. package/dist/chunk-2W7MO2DL.js +1366 -0
  17. package/dist/chunk-3NUAZGMA.js +1689 -0
  18. package/dist/chunk-7TOWB2XB.js +366 -0
  19. package/dist/chunk-7XOTOADQ.js +3065 -0
  20. package/dist/chunk-AH2PDM2K.js +3042 -0
  21. package/dist/chunk-BNXWSZ63.js +3742 -0
  22. package/dist/chunk-BTL5DJVU.js +3222 -0
  23. package/dist/chunk-HDHYG7E4.js +104 -0
  24. package/dist/chunk-HLR4KZBP.js +3234 -0
  25. package/dist/chunk-IP3FRFEB.js +1045 -0
  26. package/dist/chunk-KHU56VDO.js +3042 -0
  27. package/dist/chunk-KRYIFLQR.js +88 -0
  28. package/dist/chunk-LBSDNLEM.js +287 -0
  29. package/dist/chunk-MNTQ7HCP.js +2643 -0
  30. package/dist/chunk-MUJELQQ6.js +1387 -0
  31. package/dist/chunk-MXJGMSLV.js +2199 -0
  32. package/dist/chunk-N6QJGC3Z.js +2636 -0
  33. package/dist/chunk-OBELGBPM.js +1713 -0
  34. package/dist/chunk-OT7R5XTA.js +3192 -0
  35. package/dist/chunk-P7X4RA2T.js +106 -0
  36. package/dist/chunk-PIDUQNC2.js +3185 -0
  37. package/dist/chunk-POGCDIH4.js +3187 -0
  38. package/dist/chunk-PSIEOQGZ.js +3043 -0
  39. package/dist/chunk-PVRT3IHA.js +3238 -0
  40. package/dist/chunk-QNN4TT23.js +1430 -0
  41. package/dist/chunk-RE3R45RJ.js +3042 -0
  42. package/dist/chunk-S7E6TFX6.js +803 -0
  43. package/dist/chunk-SG6GLU4U.js +1378 -0
  44. package/dist/chunk-SJCDV2ST.js +274 -0
  45. package/dist/chunk-SYE5XLF3.js +104 -0
  46. package/dist/chunk-T5VLYBZD.js +103 -0
  47. package/dist/chunk-TOQB7VWU.js +3238 -0
  48. package/dist/chunk-VFNMZ4ZQ.js +3228 -0
  49. package/dist/chunk-VVTGZNBT.js +1629 -0
  50. package/dist/chunk-W7Q4RFEV.js +104 -0
  51. package/dist/chunk-XTYYVRLO.js +3190 -0
  52. package/dist/chunk-Y6MDYVJD.js +3063 -0
  53. package/dist/cli/main.d.ts +1 -0
  54. package/dist/cli/main.js +5458 -0
  55. package/dist/index.d.ts +653 -0
  56. package/dist/index.js +79 -0
  57. package/dist/mcp/server.d.ts +1 -0
  58. package/dist/mcp/server.js +472 -0
  59. package/dist/schema-BAWSG7KY.js +22 -0
  60. package/dist/schema-E3QUPL26.js +20 -0
  61. package/dist/schema-EHL7WUT6.js +20 -0
  62. package/docs/019-USAGE.md +625 -0
  63. package/docs/020-current-implementation.md +364 -0
  64. package/docs/021-DOGFOODING-FINDINGS.md +175 -0
  65. package/docs/BACKLOG.md +80 -0
  66. package/docs/CONFIG.md +1123 -0
  67. package/docs/DESIGN.md +439 -0
  68. package/docs/ERRORS.md +383 -0
  69. package/docs/PROJECT.md +88 -0
  70. package/docs/ROADMAP.md +407 -0
  71. package/docs/summarization.md +320 -0
  72. package/docs/test-links.md +9 -0
  73. package/justfile +40 -0
  74. package/package.json +74 -9
  75. package/pnpm-workspace.yaml +5 -0
  76. package/research/INDEX.md +315 -0
  77. package/research/code-review/README.md +90 -0
  78. package/research/code-review/cli-error-handling-review.md +979 -0
  79. package/research/code-review/code-review-validation-report.md +464 -0
  80. package/research/code-review/main-ts-review.md +1128 -0
  81. package/research/config-analysis/01-current-implementation.md +470 -0
  82. package/research/config-analysis/02-strategy-recommendation.md +428 -0
  83. package/research/config-analysis/03-task-candidates.md +715 -0
  84. package/research/config-analysis/033-research-configuration-management.md +828 -0
  85. package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
  86. package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
  87. package/research/config-docs/SUMMARY.md +357 -0
  88. package/research/config-docs/TEST-RESULTS.md +776 -0
  89. package/research/config-docs/TODO.md +542 -0
  90. package/research/config-docs/analysis.md +744 -0
  91. package/research/config-docs/fix-validation.md +502 -0
  92. package/research/config-docs/help-audit.md +264 -0
  93. package/research/config-docs/help-system-analysis.md +890 -0
  94. package/research/dogfood/consolidated-tool-evaluation.md +373 -0
  95. package/research/dogfood/strategy-a/a-synthesis.md +184 -0
  96. package/research/dogfood/strategy-a/a1-docs.md +226 -0
  97. package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
  98. package/research/dogfood/strategy-a/a3-llm.md +164 -0
  99. package/research/dogfood/strategy-b/b-synthesis.md +228 -0
  100. package/research/dogfood/strategy-b/b1-architecture.md +207 -0
  101. package/research/dogfood/strategy-b/b2-gaps.md +258 -0
  102. package/research/dogfood/strategy-b/b3-workflows.md +250 -0
  103. package/research/dogfood/strategy-c/c-synthesis.md +451 -0
  104. package/research/dogfood/strategy-c/c1-explorer.md +192 -0
  105. package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
  106. package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
  107. package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
  108. package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
  109. package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
  110. package/research/effect-cli-error-handling.md +845 -0
  111. package/research/effect-errors-as-values.md +943 -0
  112. package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
  113. package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
  114. package/research/errors-task-analysis/embeddings-analysis.md +709 -0
  115. package/research/errors-task-analysis/index-search-analysis.md +812 -0
  116. package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
  117. package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
  118. package/research/issue-review.md +603 -0
  119. package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
  120. package/research/llm-summarization/alternative-providers-2026.md +1428 -0
  121. package/research/llm-summarization/anthropic-2026.md +367 -0
  122. package/research/llm-summarization/claude-cli-integration.md +1706 -0
  123. package/research/llm-summarization/cli-integration-patterns.md +3155 -0
  124. package/research/llm-summarization/openai-2026.md +473 -0
  125. package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
  126. package/research/llm-summarization/opencode-cli-integration.md +1552 -0
  127. package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
  128. package/research/llm-summarization/prototype-results.md +56 -0
  129. package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
  130. package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
  131. package/research/mdcontext-error-analysis.md +521 -0
  132. package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
  133. package/research/mdcontext-pudding/01-index-embed.md +956 -0
  134. package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
  135. package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
  136. package/research/mdcontext-pudding/02-search.md +970 -0
  137. package/research/mdcontext-pudding/03-context.md +779 -0
  138. package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
  139. package/research/mdcontext-pudding/04-tree.md +704 -0
  140. package/research/mdcontext-pudding/05-config.md +1038 -0
  141. package/research/mdcontext-pudding/06-links-summary.txt +87 -0
  142. package/research/mdcontext-pudding/06-links.md +679 -0
  143. package/research/mdcontext-pudding/07-stats.md +693 -0
  144. package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
  145. package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
  146. package/research/mdcontext-pudding/README.md +168 -0
  147. package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
  148. package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
  149. package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
  150. package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
  151. package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
  152. package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
  153. package/research/research-quality-review.md +834 -0
  154. package/research/semantic-search/002-research-embedding-models.md +490 -0
  155. package/research/semantic-search/003-research-rag-alternatives.md +523 -0
  156. package/research/semantic-search/004-research-vector-search.md +841 -0
  157. package/research/semantic-search/032-research-semantic-search.md +427 -0
  158. package/research/semantic-search/embedding-text-analysis.md +156 -0
  159. package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
  160. package/research/semantic-search/query-processing-analysis.md +207 -0
  161. package/research/semantic-search/root-cause-and-solution.md +114 -0
  162. package/research/semantic-search/threshold-validation-report.md +69 -0
  163. package/research/semantic-search/vector-search-analysis.md +63 -0
  164. package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
  165. package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
  166. package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
  167. package/research/task-management-2026/03-lightweight-file-based.md +567 -0
  168. package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
  169. package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
  170. package/research/task-management-2026/linear/02-api-integrations.md +930 -0
  171. package/research/task-management-2026/linear/03-ai-features.md +368 -0
  172. package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
  173. package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
  174. package/research/test-path-issues.md +276 -0
  175. package/review/ALP-76/1-error-type-design.md +962 -0
  176. package/review/ALP-76/2-error-handling-patterns.md +906 -0
  177. package/review/ALP-76/3-error-presentation.md +624 -0
  178. package/review/ALP-76/4-test-coverage.md +625 -0
  179. package/review/ALP-76/5-migration-completeness.md +440 -0
  180. package/review/ALP-76/6-effect-best-practices.md +755 -0
  181. package/scripts/apply-branch-protection.sh +47 -0
  182. package/scripts/branch-protection-templates.json +79 -0
  183. package/scripts/prototype-summarization.ts +346 -0
  184. package/scripts/rebuild-hnswlib.js +58 -0
  185. package/scripts/setup-branch-protection.sh +64 -0
  186. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
  187. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
  188. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
  189. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
  190. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  191. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  192. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
  193. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
  194. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
  195. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
  196. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
  197. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
  198. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
  199. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
  200. package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
  201. package/src/cli/argv-preprocessor.test.ts +210 -0
  202. package/src/cli/argv-preprocessor.ts +202 -0
  203. package/src/cli/cli.test.ts +627 -0
  204. package/src/cli/commands/backlinks.ts +54 -0
  205. package/src/cli/commands/config-cmd.ts +642 -0
  206. package/src/cli/commands/context.ts +285 -0
  207. package/src/cli/commands/duplicates.ts +122 -0
  208. package/src/cli/commands/embeddings.ts +529 -0
  209. package/src/cli/commands/index-cmd.ts +480 -0
  210. package/src/cli/commands/index.ts +16 -0
  211. package/src/cli/commands/links.ts +52 -0
  212. package/src/cli/commands/search.ts +1281 -0
  213. package/src/cli/commands/stats.ts +149 -0
  214. package/src/cli/commands/tree.ts +128 -0
  215. package/src/cli/config-layer.ts +176 -0
  216. package/src/cli/error-handler.test.ts +235 -0
  217. package/src/cli/error-handler.ts +655 -0
  218. package/src/cli/flag-schemas.ts +341 -0
  219. package/src/cli/help.ts +588 -0
  220. package/src/cli/index.ts +9 -0
  221. package/src/cli/main.ts +435 -0
  222. package/src/cli/options.ts +41 -0
  223. package/src/cli/shared-error-handling.ts +199 -0
  224. package/src/cli/typo-suggester.test.ts +105 -0
  225. package/src/cli/typo-suggester.ts +130 -0
  226. package/src/cli/utils.ts +259 -0
  227. package/src/config/file-provider.test.ts +320 -0
  228. package/src/config/file-provider.ts +273 -0
  229. package/src/config/index.ts +72 -0
  230. package/src/config/integration.test.ts +667 -0
  231. package/src/config/precedence.test.ts +277 -0
  232. package/src/config/precedence.ts +451 -0
  233. package/src/config/schema.test.ts +414 -0
  234. package/src/config/schema.ts +603 -0
  235. package/src/config/service.test.ts +320 -0
  236. package/src/config/service.ts +243 -0
  237. package/src/config/testing.test.ts +264 -0
  238. package/src/config/testing.ts +110 -0
  239. package/src/core/index.ts +1 -0
  240. package/src/core/types.ts +113 -0
  241. package/src/duplicates/detector.test.ts +183 -0
  242. package/src/duplicates/detector.ts +414 -0
  243. package/src/duplicates/index.ts +18 -0
  244. package/src/embeddings/embedding-namespace.test.ts +300 -0
  245. package/src/embeddings/embedding-namespace.ts +947 -0
  246. package/src/embeddings/heading-boost.test.ts +222 -0
  247. package/src/embeddings/hnsw-build-options.test.ts +198 -0
  248. package/src/embeddings/hyde.test.ts +272 -0
  249. package/src/embeddings/hyde.ts +264 -0
  250. package/src/embeddings/index.ts +10 -0
  251. package/src/embeddings/openai-provider.ts +414 -0
  252. package/src/embeddings/pricing.json +22 -0
  253. package/src/embeddings/provider-constants.ts +204 -0
  254. package/src/embeddings/provider-errors.test.ts +967 -0
  255. package/src/embeddings/provider-errors.ts +565 -0
  256. package/src/embeddings/provider-factory.test.ts +240 -0
  257. package/src/embeddings/provider-factory.ts +225 -0
  258. package/src/embeddings/provider-integration.test.ts +788 -0
  259. package/src/embeddings/query-preprocessing.test.ts +187 -0
  260. package/src/embeddings/semantic-search-threshold.test.ts +508 -0
  261. package/src/embeddings/semantic-search.ts +1270 -0
  262. package/src/embeddings/types.ts +359 -0
  263. package/src/embeddings/vector-store.ts +708 -0
  264. package/src/embeddings/voyage-provider.ts +313 -0
  265. package/src/errors/errors.test.ts +845 -0
  266. package/src/errors/index.ts +533 -0
  267. package/src/index/ignore-patterns.test.ts +354 -0
  268. package/src/index/ignore-patterns.ts +305 -0
  269. package/src/index/index.ts +4 -0
  270. package/src/index/indexer.ts +684 -0
  271. package/src/index/storage.ts +260 -0
  272. package/src/index/types.ts +147 -0
  273. package/src/index/watcher.ts +189 -0
  274. package/src/index.ts +30 -0
  275. package/src/integration/search-keyword.test.ts +678 -0
  276. package/src/mcp/server.ts +612 -0
  277. package/src/parser/index.ts +1 -0
  278. package/src/parser/parser.test.ts +291 -0
  279. package/src/parser/parser.ts +394 -0
  280. package/src/parser/section-filter.test.ts +277 -0
  281. package/src/parser/section-filter.ts +392 -0
  282. package/src/search/__tests__/hybrid-search.test.ts +650 -0
  283. package/src/search/bm25-store.ts +366 -0
  284. package/src/search/cross-encoder.test.ts +253 -0
  285. package/src/search/cross-encoder.ts +406 -0
  286. package/src/search/fuzzy-search.test.ts +419 -0
  287. package/src/search/fuzzy-search.ts +273 -0
  288. package/src/search/hybrid-search.ts +448 -0
  289. package/src/search/path-matcher.test.ts +276 -0
  290. package/src/search/path-matcher.ts +33 -0
  291. package/src/search/query-parser.test.ts +260 -0
  292. package/src/search/query-parser.ts +319 -0
  293. package/src/search/searcher.test.ts +280 -0
  294. package/src/search/searcher.ts +724 -0
  295. package/src/search/wink-bm25.d.ts +30 -0
  296. package/src/summarization/cli-providers/claude.ts +202 -0
  297. package/src/summarization/cli-providers/detection.test.ts +273 -0
  298. package/src/summarization/cli-providers/detection.ts +118 -0
  299. package/src/summarization/cli-providers/index.ts +8 -0
  300. package/src/summarization/cost.test.ts +139 -0
  301. package/src/summarization/cost.ts +102 -0
  302. package/src/summarization/error-handler.test.ts +127 -0
  303. package/src/summarization/error-handler.ts +111 -0
  304. package/src/summarization/index.ts +102 -0
  305. package/src/summarization/pipeline.test.ts +498 -0
  306. package/src/summarization/pipeline.ts +231 -0
  307. package/src/summarization/prompts.test.ts +269 -0
  308. package/src/summarization/prompts.ts +133 -0
  309. package/src/summarization/provider-factory.test.ts +396 -0
  310. package/src/summarization/provider-factory.ts +178 -0
  311. package/src/summarization/types.ts +184 -0
  312. package/src/summarize/budget-bugs.test.ts +620 -0
  313. package/src/summarize/formatters.ts +419 -0
  314. package/src/summarize/index.ts +20 -0
  315. package/src/summarize/summarizer.test.ts +275 -0
  316. package/src/summarize/summarizer.ts +597 -0
  317. package/src/summarize/verify-bugs.test.ts +238 -0
  318. package/src/types/huggingface-transformers.d.ts +66 -0
  319. package/src/utils/index.ts +1 -0
  320. package/src/utils/tokens.test.ts +142 -0
  321. package/src/utils/tokens.ts +186 -0
  322. package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
  323. package/tests/fixtures/cli/.mdcontext/config.json +8 -0
  324. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
  325. package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
  326. package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
  327. package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
  328. package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
  329. package/tests/fixtures/cli/README.md +9 -0
  330. package/tests/fixtures/cli/api-reference.md +11 -0
  331. package/tests/fixtures/cli/getting-started.md +11 -0
  332. package/tests/integration/embed-index.test.ts +712 -0
  333. package/tests/integration/search-context.test.ts +469 -0
  334. package/tests/integration/search-semantic.test.ts +522 -0
  335. package/tsconfig.json +26 -0
  336. package/vitest.config.ts +16 -0
  337. package/vitest.setup.ts +12 -0
@@ -0,0 +1,1504 @@
1
+ # Effect for CLI Development and Configuration Management
2
+
3
+ ## Executive Summary
4
+
5
+ This research evaluates Effect's ecosystem for building CLI applications and managing configuration. Effect provides a comprehensive, type-safe foundation through `@effect/cli` for command-line interfaces and its built-in `Config` module for configuration management.
6
+
7
+ **Key Findings**:
8
+
9
+ - `@effect/cli` offers a declarative, composable approach to CLI development with automatic help generation, wizard mode, and shell completions
10
+ - Effect's `Config` module provides type-safe configuration loading with `ConfigProvider` abstraction for multiple sources
11
+ - The `Layer` system enables elegant dependency injection and configuration composition
12
+ - Effect's `Redacted` type provides first-class secrets handling
13
+ - Real-world adoption includes Effect's own tooling, VS Code extension, and Discord bot
14
+
15
+ **Recommendation**: Effect is a strong candidate for mdcontext's CLI and config layer, offering superior type safety and composability compared to traditional approaches. However, it requires commitment to the Effect paradigm throughout the application.
16
+
17
+ ---
18
+
19
+ ## 1. @effect/cli - Command-Line Interface Framework
20
+
21
+ ### Overview
22
+
23
+ `@effect/cli` is a declarative framework for building type-safe CLI applications. It provides:
24
+
25
+ - Hierarchical command structures (commands and subcommands)
26
+ - Type-safe argument and option parsing
27
+ - Automatic help documentation generation
28
+ - Interactive wizard mode
29
+ - Shell completion generation (bash, zsh, fish, sh)
30
+ - Built-in logging level control
31
+
32
+ **Installation**:
33
+
34
+ ```bash
35
+ npm install @effect/cli @effect/platform @effect/platform-node effect
36
+ ```
37
+
38
+ ### Core Concepts
39
+
40
+ #### Commands
41
+
42
+ Commands are the fundamental building blocks. Each command has:
43
+
44
+ - **Name**: Identifier for invocation
45
+ - **Configuration**: Options and arguments
46
+ - **Handler**: Effect-returning function
47
+
48
+ ```typescript
49
+ import { Args, Command, Options } from "@effect/cli";
50
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
51
+ import { Console, Effect } from "effect";
52
+
53
+ // Define a simple command
54
+ const greet = Command.make(
55
+ "greet", // Command name
56
+ { name: Args.text({ name: "name" }) }, // Arguments
57
+ ({ name }) => Console.log(`Hello, ${name}!`), // Handler
58
+ );
59
+
60
+ // Run the CLI
61
+ const cli = Command.run(greet, {
62
+ name: "My CLI",
63
+ version: "1.0.0",
64
+ });
65
+
66
+ cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain);
67
+ ```
68
+
69
+ #### Arguments (Args)
70
+
71
+ Arguments are positional values passed to commands.
72
+
73
+ ```typescript
74
+ import { Args } from "@effect/cli";
75
+
76
+ // Basic types
77
+ const textArg = Args.text({ name: "message" });
78
+ const numberArg = Args.integer({ name: "count" });
79
+ const floatArg = Args.float({ name: "value" });
80
+ const boolArg = Args.boolean({ name: "flag" });
81
+ const dateArg = Args.date({ name: "when" });
82
+
83
+ // File system
84
+ const fileArg = Args.file({ name: "input" });
85
+ const dirArg = Args.directory({ name: "output" });
86
+ const pathArg = Args.path({ name: "location" });
87
+
88
+ // File content (reads and returns content)
89
+ const fileContent = Args.fileContent({ name: "config" });
90
+ const fileText = Args.fileText({ name: "readme" });
91
+
92
+ // Modifiers
93
+ const optionalArg = Args.text({ name: "desc" }).pipe(Args.optional);
94
+ const repeatedArg = Args.text({ name: "files" }).pipe(Args.repeated);
95
+ const withDefault = Args.integer({ name: "port" }).pipe(Args.withDefault(3000));
96
+ const validated = Args.text({ name: "email" }).pipe(
97
+ Args.validate((v) => v.includes("@"), "Must be a valid email"),
98
+ );
99
+
100
+ // Choice from predefined values
101
+ const format = Args.choice("format", ["json", "yaml", "toml"]);
102
+ ```
103
+
104
+ #### Options
105
+
106
+ Options are named flags with optional values.
107
+
108
+ ```typescript
109
+ import { Options } from "@effect/cli";
110
+
111
+ // Boolean flags
112
+ const verbose = Options.boolean("verbose").pipe(Options.withAlias("v"));
113
+ const debug = Options.boolean("debug").pipe(
114
+ Options.withDefault(false),
115
+ Options.withDescription("Enable debug mode"),
116
+ );
117
+
118
+ // Valued options
119
+ const port = Options.integer("port").pipe(
120
+ Options.withAlias("p"),
121
+ Options.withDefault(8080),
122
+ );
123
+
124
+ const host = Options.text("host").pipe(Options.withDefault("localhost"));
125
+
126
+ // Choice options
127
+ const logLevel = Options.choice("log-level", [
128
+ "debug",
129
+ "info",
130
+ "warn",
131
+ "error",
132
+ ]).pipe(Options.withDefault("info"));
133
+
134
+ // Optional values
135
+ const config = Options.file("config").pipe(
136
+ Options.withAlias("c"),
137
+ Options.optional,
138
+ );
139
+
140
+ // Repeated options (--include a --include b)
141
+ const includes = Options.text("include").pipe(
142
+ Options.withAlias("i"),
143
+ Options.repeated,
144
+ );
145
+
146
+ // Sensitive data (not shown in help/logs)
147
+ const apiKey = Options.redacted("api-key");
148
+ const secret = Options.secret("token"); // Deprecated, use redacted
149
+
150
+ // Key-value maps (--header "Content-Type: application/json")
151
+ const headers = Options.keyValueMap("header");
152
+
153
+ // Schema validation
154
+ import { Schema } from "effect";
155
+ const email = Options.text("email").pipe(
156
+ Options.withSchema(Schema.String.pipe(Schema.includes("@"))),
157
+ );
158
+ ```
159
+
160
+ #### Subcommands
161
+
162
+ Commands can be nested to create hierarchical CLI structures.
163
+
164
+ ```typescript
165
+ import { Args, Command, Options } from "@effect/cli";
166
+ import { Console, Effect } from "effect";
167
+
168
+ // Define subcommands
169
+ const add = Command.make(
170
+ "add",
171
+ {
172
+ files: Args.text({ name: "pathspec" }).pipe(Args.repeated),
173
+ verbose: Options.boolean("verbose").pipe(Options.withAlias("v")),
174
+ },
175
+ ({ files, verbose }) =>
176
+ Effect.gen(function* () {
177
+ if (verbose) yield* Console.log(`Adding ${files.length} files`);
178
+ yield* Console.log(`git add ${files.join(" ")}`);
179
+ }),
180
+ );
181
+
182
+ const commit = Command.make(
183
+ "commit",
184
+ {
185
+ message: Options.text("message").pipe(Options.withAlias("m")),
186
+ amend: Options.boolean("amend").pipe(Options.withDefault(false)),
187
+ },
188
+ ({ message, amend }) =>
189
+ Console.log(`git commit ${amend ? "--amend" : ""} -m "${message}"`),
190
+ );
191
+
192
+ const clone = Command.make(
193
+ "clone",
194
+ {
195
+ url: Args.text({ name: "repository" }),
196
+ directory: Args.text({ name: "directory" }).pipe(Args.optional),
197
+ },
198
+ ({ url, directory }) =>
199
+ Console.log(`git clone ${url}${directory ? ` ${directory}` : ""}`),
200
+ );
201
+
202
+ // Parent command with subcommands
203
+ const git = Command.make("git", {}).pipe(
204
+ Command.withSubcommands([add, commit, clone]),
205
+ );
206
+
207
+ // Run
208
+ const cli = Command.run(git, {
209
+ name: "minigit",
210
+ version: "1.0.0",
211
+ });
212
+ ```
213
+
214
+ #### Accessing Parent Command Context
215
+
216
+ Subcommands can access their parent's configuration:
217
+
218
+ ```typescript
219
+ const child = Command.make(
220
+ "child",
221
+ { childArg: Args.text({ name: "value" }) },
222
+ ({ childArg }) =>
223
+ // Access parent command's parsed config
224
+ parent.pipe(
225
+ Effect.flatMap((parentConfig) =>
226
+ Console.log(
227
+ `Parent verbose: ${parentConfig.verbose}, Child arg: ${childArg}`,
228
+ ),
229
+ ),
230
+ ),
231
+ );
232
+
233
+ const parent = Command.make("parent", {
234
+ verbose: Options.boolean("verbose"),
235
+ }).pipe(Command.withSubcommands([child]));
236
+ ```
237
+
238
+ ### Built-in Features
239
+
240
+ Every `@effect/cli` application automatically includes:
241
+
242
+ | Flag | Description |
243
+ | ----------------------- | ------------------------------------------------ |
244
+ | `--version` | Display application version |
245
+ | `-h, --help` | Show help documentation |
246
+ | `--wizard` | Interactive command builder |
247
+ | `--completions [shell]` | Generate shell completions (bash, sh, fish, zsh) |
248
+ | `--log-level` | Set minimum log level for handlers |
249
+
250
+ #### Wizard Mode
251
+
252
+ The `--wizard` flag provides an interactive prompt-based interface for building commands:
253
+
254
+ ```bash
255
+ $ mycli --wizard
256
+ Wizard Mode for CLI Application: My CLI (v1.0.0)
257
+
258
+ Instructions
259
+ The wizard mode will assist you with constructing commands for My CLI (v1.0.0).
260
+ Please answer all prompts provided by the wizard.
261
+
262
+ COMMAND: greet
263
+ ? Enter name: Alice
264
+
265
+ Wizard Mode Complete!
266
+ You may now execute your command directly with the following options and arguments:
267
+ greet Alice
268
+
269
+ ? Would you like to run the command? (y/n)
270
+ ```
271
+
272
+ #### Shell Completions
273
+
274
+ ```bash
275
+ # Generate completions
276
+ mycli --completions bash > ~/.bash_completion.d/mycli
277
+ mycli --completions zsh > ~/.zsh/completions/_mycli
278
+ mycli --completions fish > ~/.config/fish/completions/mycli.fish
279
+ ```
280
+
281
+ ### Complete CLI Example
282
+
283
+ ```typescript
284
+ import { Args, Command, Options } from "@effect/cli";
285
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
286
+ import { Console, Effect, Option } from "effect";
287
+
288
+ // Color codes for ANSI
289
+ const colors = {
290
+ red: "\x1b[31m",
291
+ green: "\x1b[32m",
292
+ blue: "\x1b[34m",
293
+ yellow: "\x1b[33m",
294
+ reset: "\x1b[0m",
295
+ bold: "\x1b[1m",
296
+ } as const;
297
+
298
+ type Color = keyof typeof colors;
299
+
300
+ // Define arguments and options
301
+ const text = Args.text({ name: "text" });
302
+ const bold = Options.boolean("bold").pipe(Options.withAlias("b"));
303
+ const color = Options.choice("color", [
304
+ "red",
305
+ "green",
306
+ "blue",
307
+ "yellow",
308
+ ] as const).pipe(Options.withAlias("c"), Options.optional);
309
+ const count = Args.integer({ name: "count" }).pipe(Args.withDefault(1));
310
+
311
+ // Echo command - prints text with optional formatting
312
+ const echo = Command.make(
313
+ "echo",
314
+ { text, bold, color },
315
+ ({ text, bold, color }) => {
316
+ let output = text;
317
+
318
+ // Apply color if specified
319
+ if (Option.isSome(color)) {
320
+ output = `${colors[color.value]}${output}${colors.reset}`;
321
+ }
322
+
323
+ // Apply bold if specified
324
+ if (bold) {
325
+ output = `${colors.bold}${output}${colors.reset}`;
326
+ }
327
+
328
+ return Console.log(output);
329
+ },
330
+ );
331
+
332
+ // Repeat command - repeats the echo command multiple times
333
+ const repeat = Command.make("repeat", { count }, ({ count }) =>
334
+ echo.pipe(
335
+ Effect.flatMap((config) => Effect.repeatN(echo.handler(config), count - 1)),
336
+ ),
337
+ );
338
+
339
+ // Main command with subcommands
340
+ const main = echo.pipe(Command.withSubcommands([repeat]));
341
+
342
+ // Create and run CLI
343
+ const cli = Command.run(main, {
344
+ name: "Echo CLI",
345
+ version: "1.0.0",
346
+ });
347
+
348
+ cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain);
349
+ ```
350
+
351
+ Usage:
352
+
353
+ ```bash
354
+ # Basic echo
355
+ $ echo-cli "Hello, World!"
356
+ Hello, World!
357
+
358
+ # With formatting
359
+ $ echo-cli "Hello" --bold --color green
360
+ Hello # (in bold green)
361
+
362
+ # Repeat subcommand
363
+ $ echo-cli repeat 3 "Hi"
364
+ Hi
365
+ Hi
366
+ Hi
367
+
368
+ # Help
369
+ $ echo-cli --help
370
+ # Shows auto-generated help
371
+
372
+ # Wizard mode
373
+ $ echo-cli --wizard
374
+ # Interactive prompt for building command
375
+ ```
376
+
377
+ ---
378
+
379
+ ## 2. @effect/platform - Platform Abstractions
380
+
381
+ ### Overview
382
+
383
+ `@effect/platform` provides platform-independent abstractions for:
384
+
385
+ - **FileSystem** - File and directory operations
386
+ - **Terminal** - stdin/stdout interaction
387
+ - **Command** - Process execution
388
+ - **Path** - Cross-platform path utilities
389
+ - **KeyValueStore** - Persistent storage
390
+ - **PlatformLogger** - File-based logging
391
+
392
+ Platform-specific implementations:
393
+
394
+ - `@effect/platform-node` - Node.js/Deno
395
+ - `@effect/platform-bun` - Bun runtime
396
+ - `@effect/platform-browser` - Browser environments
397
+
398
+ ### FileSystem
399
+
400
+ ```typescript
401
+ import { FileSystem } from "@effect/platform";
402
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
403
+ import { Effect } from "effect";
404
+
405
+ const program = Effect.gen(function* () {
406
+ const fs = yield* FileSystem.FileSystem;
407
+
408
+ // Check existence
409
+ const exists = yield* fs.exists("./config.json");
410
+
411
+ // Read file
412
+ const content = yield* fs.readFileString("./config.json", "utf8");
413
+
414
+ // Write file
415
+ yield* fs.writeFileString("./output.txt", "Hello, World!");
416
+
417
+ // Read directory
418
+ const files = yield* fs.readDirectory("./src");
419
+
420
+ // Create directory
421
+ yield* fs.makeDirectory("./dist", { recursive: true });
422
+
423
+ // Copy file
424
+ yield* fs.copyFile("./src/index.ts", "./dist/index.ts");
425
+
426
+ // Remove file/directory
427
+ yield* fs.remove("./temp", { recursive: true });
428
+
429
+ // Watch for changes
430
+ const watcher = yield* fs.watch("./src");
431
+ // watcher is a Stream of file system events
432
+
433
+ // Temporary files (auto-cleaned with Scope)
434
+ const tempFile = yield* fs.makeTempFileScoped();
435
+ yield* fs.writeFileString(tempFile, "temp content");
436
+ // File is deleted when scope closes
437
+ });
438
+
439
+ NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)));
440
+ ```
441
+
442
+ ### Terminal
443
+
444
+ ```typescript
445
+ import { Terminal } from "@effect/platform";
446
+ import { NodeRuntime, NodeTerminal } from "@effect/platform-node";
447
+ import { Effect } from "effect";
448
+
449
+ const program = Effect.gen(function* () {
450
+ const terminal = yield* Terminal.Terminal;
451
+
452
+ // Read a line from stdin
453
+ const name = yield* terminal.readLine;
454
+
455
+ // Display output
456
+ yield* terminal.display(`Hello, ${name}!\n`);
457
+
458
+ // Log (with newline)
459
+ yield* Terminal.log("This is a log message");
460
+ });
461
+
462
+ NodeRuntime.runMain(program.pipe(Effect.provide(NodeTerminal.layer)));
463
+ ```
464
+
465
+ ### Command (Process Execution)
466
+
467
+ ```typescript
468
+ import { Command } from "@effect/platform";
469
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
470
+ import { Effect, Stream, String, pipe } from "effect";
471
+
472
+ // Helper to collect stream as string
473
+ const streamToString = <E, R>(
474
+ stream: Stream.Stream<Uint8Array, E, R>,
475
+ ): Effect.Effect<string, E, R> =>
476
+ stream.pipe(Stream.decodeText(), Stream.runFold(String.empty, String.concat));
477
+
478
+ const program = Effect.gen(function* () {
479
+ // Simple command execution
480
+ const exitCode = yield* Command.make("echo", "Hello").pipe(Command.exitCode);
481
+
482
+ // Capture stdout as string
483
+ const output = yield* Command.make("ls", "-la").pipe(Command.string);
484
+ console.log(output);
485
+
486
+ // Capture stdout as lines
487
+ const lines = yield* Command.make("cat", "file.txt").pipe(Command.lines);
488
+
489
+ // Stream stdout to process.stdout
490
+ yield* Command.make("cat", "./large-file.txt").pipe(
491
+ Command.stdout("inherit"),
492
+ Command.exitCode,
493
+ );
494
+
495
+ // Full process control
496
+ const [exitCode2, stdout, stderr] = yield* pipe(
497
+ Command.make("npm", "install"),
498
+ Command.start,
499
+ Effect.flatMap((process) =>
500
+ Effect.all(
501
+ [
502
+ process.exitCode,
503
+ streamToString(process.stdout),
504
+ streamToString(process.stderr),
505
+ ],
506
+ { concurrency: 3 },
507
+ ),
508
+ ),
509
+ );
510
+
511
+ // Working directory and environment
512
+ yield* Command.make("npm", "test").pipe(
513
+ Command.workingDirectory("./packages/core"),
514
+ Command.env({ NODE_ENV: "test" }),
515
+ Command.exitCode,
516
+ );
517
+
518
+ // Pipe commands together
519
+ yield* Command.make("cat", "file.txt").pipe(
520
+ Command.pipeTo(Command.make("grep", "pattern")),
521
+ Command.string,
522
+ );
523
+ });
524
+
525
+ NodeRuntime.runMain(
526
+ Effect.scoped(program).pipe(Effect.provide(NodeContext.layer)),
527
+ );
528
+ ```
529
+
530
+ ---
531
+
532
+ ## 3. Effect Config Module
533
+
534
+ ### Overview
535
+
536
+ Effect provides a built-in configuration system with:
537
+
538
+ - Type-safe config primitives
539
+ - `ConfigProvider` abstraction for loading from various sources
540
+ - Composition and transformation combinators
541
+ - First-class secrets handling with `Redacted`
542
+
543
+ ### Basic Configuration
544
+
545
+ ```typescript
546
+ import { Effect, Config } from "effect";
547
+
548
+ const program = Effect.gen(function* () {
549
+ // Basic types
550
+ const host = yield* Config.string("HOST");
551
+ const port = yield* Config.number("PORT");
552
+ const debug = yield* Config.boolean("DEBUG");
553
+ const timeout = yield* Config.duration("TIMEOUT"); // "1s", "500ms", etc.
554
+ const logLevel = yield* Config.logLevel("LOG_LEVEL"); // LogLevel type
555
+ const apiUrl = yield* Config.url("API_URL"); // URL type
556
+
557
+ console.log(`Server: ${host}:${port}`);
558
+ });
559
+
560
+ // Run with environment variables:
561
+ // HOST=localhost PORT=8080 npx tsx index.ts
562
+ Effect.runPromise(program);
563
+ ```
564
+
565
+ ### Config Combinators
566
+
567
+ ```typescript
568
+ import { Effect, Config, ConfigError } from "effect";
569
+
570
+ const program = Effect.gen(function* () {
571
+ // Default values
572
+ const port = yield* Config.number("PORT").pipe(Config.withDefault(3000));
573
+
574
+ // Optional values
575
+ const apiKey = yield* Config.string("API_KEY").pipe(
576
+ Config.optional, // Returns Option<string>
577
+ );
578
+
579
+ // Validation
580
+ const port2 = yield* Config.number("PORT").pipe(
581
+ Config.validate({
582
+ message: "Port must be between 1 and 65535",
583
+ validation: (n) => n >= 1 && n <= 65535,
584
+ }),
585
+ );
586
+
587
+ // Transformation
588
+ const portString = yield* Config.number("PORT").pipe(
589
+ Config.map((n) => n.toString()),
590
+ );
591
+
592
+ // Nested configuration (reads SERVER_HOST, SERVER_PORT)
593
+ const serverHost = yield* Config.nested(Config.string("HOST"), "SERVER");
594
+ const serverPort = yield* Config.nested(Config.number("PORT"), "SERVER");
595
+
596
+ // Combine multiple configs
597
+ const [host, port3] = yield* Config.all([
598
+ Config.string("HOST"),
599
+ Config.number("PORT"),
600
+ ]);
601
+
602
+ // Struct-based combination
603
+ const serverConfig = yield* Config.all({
604
+ host: Config.string("HOST"),
605
+ port: Config.number("PORT"),
606
+ debug: Config.boolean("DEBUG").pipe(Config.withDefault(false)),
607
+ });
608
+ // serverConfig: { host: string, port: number, debug: boolean }
609
+
610
+ // Fallback to alternative config
611
+ const port4 = yield* Config.number("PORT").pipe(
612
+ Config.orElse(() => Config.number("SERVER_PORT")),
613
+ );
614
+
615
+ // Array of values (comma-separated)
616
+ const hosts = yield* Config.array(Config.string("HOSTS"));
617
+ // HOSTS=a,b,c => ["a", "b", "c"]
618
+
619
+ // Set of values
620
+ const tags = yield* Config.hashSet(Config.string("TAGS"));
621
+
622
+ // Map of values
623
+ const headers = yield* Config.hashMap(Config.string("HEADERS"));
624
+ });
625
+ ```
626
+
627
+ ### Sensitive Data with Redacted
628
+
629
+ ```typescript
630
+ import { Effect, Config, Redacted } from "effect";
631
+
632
+ const program = Effect.gen(function* () {
633
+ // Load sensitive value as Redacted
634
+ const apiKey = yield* Config.redacted("API_KEY");
635
+ // apiKey: Redacted<string>
636
+
637
+ // Safe to log - shows "<redacted>"
638
+ console.log(`API Key: ${apiKey}`); // "API Key: <redacted>"
639
+
640
+ // Access actual value when needed
641
+ const actualKey = Redacted.value(apiKey);
642
+ // actualKey: string
643
+
644
+ // Use in HTTP request (unwrap at point of use)
645
+ yield* makeApiCall({
646
+ headers: {
647
+ Authorization: `Bearer ${Redacted.value(apiKey)}`,
648
+ },
649
+ });
650
+ });
651
+ ```
652
+
653
+ ### ConfigProvider
654
+
655
+ ConfigProvider is the abstraction for loading configuration from various sources.
656
+
657
+ ```typescript
658
+ import { Effect, Config, ConfigProvider, Layer } from "effect";
659
+
660
+ // Default: loads from environment variables
661
+ // No setup needed, this is the default behavior
662
+
663
+ // From a Map (useful for testing)
664
+ const testProvider = ConfigProvider.fromMap(
665
+ new Map([
666
+ ["HOST", "localhost"],
667
+ ["PORT", "8080"],
668
+ ["DEBUG", "true"],
669
+ ]),
670
+ );
671
+
672
+ // From JSON object
673
+ const jsonProvider = ConfigProvider.fromJson({
674
+ HOST: "localhost",
675
+ PORT: 8080,
676
+ DEBUG: true,
677
+ SERVER: {
678
+ HOST: "api.example.com",
679
+ PORT: 443,
680
+ },
681
+ });
682
+
683
+ // From environment with custom delimiters
684
+ const envProvider = ConfigProvider.fromEnv({
685
+ pathDelim: "__", // For nested: SERVER__HOST
686
+ seqDelim: "|", // For arrays: HOSTS=a|b|c
687
+ });
688
+
689
+ // Provider combinators
690
+ const combinedProvider = ConfigProvider.fromMap(
691
+ new Map([["A", "from-map"]]),
692
+ ).pipe(
693
+ // Fall back to environment if not found in map
694
+ ConfigProvider.orElse(() => ConfigProvider.fromEnv()),
695
+ );
696
+
697
+ // Nested provider (all keys prefixed)
698
+ const nestedProvider = ConfigProvider.fromEnv().pipe(
699
+ ConfigProvider.nested("MYAPP"),
700
+ );
701
+ // Reads MYAPP_HOST instead of HOST
702
+
703
+ // Case conversion
704
+ const snakeCaseProvider = ConfigProvider.fromEnv().pipe(
705
+ ConfigProvider.snakeCase, // Converts camelCase keys to SNAKE_CASE
706
+ );
707
+
708
+ const constantCaseProvider = ConfigProvider.fromEnv().pipe(
709
+ ConfigProvider.constantCase, // Converts to CONSTANT_CASE
710
+ );
711
+
712
+ // Use a custom provider
713
+ const program = Effect.gen(function* () {
714
+ const host = yield* Config.string("HOST");
715
+ const port = yield* Config.number("PORT");
716
+ return { host, port };
717
+ });
718
+
719
+ // Option 1: withConfigProvider
720
+ Effect.runPromise(Effect.withConfigProvider(program, testProvider));
721
+
722
+ // Option 2: Layer.setConfigProvider
723
+ const configLayer = Layer.setConfigProvider(testProvider);
724
+ Effect.runPromise(program.pipe(Effect.provide(configLayer)));
725
+ ```
726
+
727
+ ### Schema-Based Configuration
728
+
729
+ ```typescript
730
+ import { Effect, Config, Schema } from "effect";
731
+
732
+ // Define a schema for configuration
733
+ const ServerConfigSchema = Schema.Struct({
734
+ host: Schema.String,
735
+ port: Schema.Number.pipe(Schema.between(1, 65535)),
736
+ debug: Schema.optional(Schema.Boolean, { default: () => false }),
737
+ });
738
+
739
+ type ServerConfig = Schema.Schema.Type<typeof ServerConfigSchema>;
740
+
741
+ // Use Schema.Config to load and validate
742
+ const serverConfig = Schema.Config("SERVER", ServerConfigSchema);
743
+
744
+ const program = Effect.gen(function* () {
745
+ const config = yield* serverConfig;
746
+ // config: ServerConfig (fully typed and validated)
747
+ console.log(`Server: ${config.host}:${config.port}`);
748
+ });
749
+ ```
750
+
751
+ ---
752
+
753
+ ## 4. Layer System for Configuration
754
+
755
+ ### Overview
756
+
757
+ Effect's Layer system provides:
758
+
759
+ - Dependency injection with full type safety
760
+ - Composable service construction
761
+ - Resource lifecycle management
762
+ - Configuration-to-service pipelines
763
+
764
+ ### Basic Layer Pattern
765
+
766
+ ```typescript
767
+ import { Context, Effect, Layer } from "effect";
768
+
769
+ // 1. Define a service interface using Context.Tag
770
+ class Config extends Context.Tag("Config")<
771
+ Config,
772
+ {
773
+ readonly host: string;
774
+ readonly port: number;
775
+ readonly debug: boolean;
776
+ }
777
+ >() {}
778
+
779
+ // 2. Create a Layer that provides the service
780
+ const ConfigLive = Layer.succeed(Config, {
781
+ host: "localhost",
782
+ port: 8080,
783
+ debug: false,
784
+ });
785
+
786
+ // 3. Create a program that uses the service
787
+ const program = Effect.gen(function* () {
788
+ const config = yield* Config;
789
+ console.log(`Server: ${config.host}:${config.port}`);
790
+ });
791
+
792
+ // 4. Provide the layer and run
793
+ Effect.runPromise(program.pipe(Effect.provide(ConfigLive)));
794
+ ```
795
+
796
+ ### Config-Based Layers
797
+
798
+ ```typescript
799
+ import { Context, Effect, Layer, Config } from "effect";
800
+
801
+ // Service definition
802
+ class AppConfig extends Context.Tag("AppConfig")<
803
+ AppConfig,
804
+ {
805
+ readonly host: string;
806
+ readonly port: number;
807
+ readonly debug: boolean;
808
+ readonly apiKey: string;
809
+ }
810
+ >() {}
811
+
812
+ // Layer that loads from Config
813
+ const AppConfigLive = Layer.effect(
814
+ AppConfig,
815
+ Effect.gen(function* () {
816
+ const host = yield* Config.string("HOST").pipe(
817
+ Config.withDefault("localhost"),
818
+ );
819
+ const port = yield* Config.number("PORT").pipe(Config.withDefault(3000));
820
+ const debug = yield* Config.boolean("DEBUG").pipe(
821
+ Config.withDefault(false),
822
+ );
823
+ const apiKey = yield* Config.string("API_KEY");
824
+
825
+ return { host, port, debug, apiKey };
826
+ }),
827
+ );
828
+
829
+ // Use the config service
830
+ const program = Effect.gen(function* () {
831
+ const config = yield* AppConfig;
832
+ yield* Effect.log(`Starting server on ${config.host}:${config.port}`);
833
+ });
834
+
835
+ // Run with environment variables
836
+ Effect.runPromise(program.pipe(Effect.provide(AppConfigLive)));
837
+ ```
838
+
839
+ ### Layer Composition
840
+
841
+ ```typescript
842
+ import { Context, Effect, Layer, Config } from "effect";
843
+
844
+ // Config service
845
+ class AppConfig extends Context.Tag("AppConfig")<
846
+ AppConfig,
847
+ { readonly logLevel: string; readonly dbUrl: string }
848
+ >() {}
849
+
850
+ const AppConfigLive = Layer.effect(
851
+ AppConfig,
852
+ Effect.gen(function* () {
853
+ const logLevel = yield* Config.string("LOG_LEVEL").pipe(
854
+ Config.withDefault("info"),
855
+ );
856
+ const dbUrl = yield* Config.string("DATABASE_URL");
857
+ return { logLevel, dbUrl };
858
+ }),
859
+ );
860
+
861
+ // Logger service (depends on Config)
862
+ class Logger extends Context.Tag("Logger")<
863
+ Logger,
864
+ { readonly log: (message: string) => Effect.Effect<void> }
865
+ >() {}
866
+
867
+ const LoggerLive = Layer.effect(
868
+ Logger,
869
+ Effect.gen(function* () {
870
+ const config = yield* AppConfig; // Depends on AppConfig
871
+ return {
872
+ log: (message) =>
873
+ Effect.gen(function* () {
874
+ console.log(`[${config.logLevel.toUpperCase()}] ${message}`);
875
+ }),
876
+ };
877
+ }),
878
+ );
879
+
880
+ // Database service (depends on Config and Logger)
881
+ class Database extends Context.Tag("Database")<
882
+ Database,
883
+ { readonly query: (sql: string) => Effect.Effect<unknown[]> }
884
+ >() {}
885
+
886
+ const DatabaseLive = Layer.effect(
887
+ Database,
888
+ Effect.gen(function* () {
889
+ const config = yield* AppConfig;
890
+ const logger = yield* Logger;
891
+
892
+ return {
893
+ query: (sql) =>
894
+ Effect.gen(function* () {
895
+ yield* logger.log(`Executing: ${sql}`);
896
+ // Simulate DB query
897
+ return [{ id: 1, name: "test" }];
898
+ }),
899
+ };
900
+ }),
901
+ );
902
+
903
+ // Compose layers
904
+ // Method 1: Layer.provide (sequential - inner depends on outer)
905
+ const LoggerWithConfig = Layer.provide(LoggerLive, AppConfigLive);
906
+
907
+ // Method 2: Layer.merge (parallel - combine independent outputs)
908
+ const ConfigAndLogger = Layer.merge(AppConfigLive, LoggerLive);
909
+
910
+ // Method 3: Layer.mergeAll (merge multiple layers)
911
+ const AllServices = Layer.mergeAll(AppConfigLive, LoggerLive, DatabaseLive);
912
+
913
+ // Method 4: Build full dependency graph
914
+ const MainLayer = DatabaseLive.pipe(
915
+ Layer.provide(LoggerLive),
916
+ Layer.provide(AppConfigLive),
917
+ );
918
+
919
+ // Program using all services
920
+ const program = Effect.gen(function* () {
921
+ const db = yield* Database;
922
+ const logger = yield* Logger;
923
+
924
+ yield* logger.log("Starting application");
925
+ const results = yield* db.query("SELECT * FROM users");
926
+ yield* logger.log(`Found ${results.length} users`);
927
+ });
928
+
929
+ // Run with composed layer
930
+ Effect.runPromise(program.pipe(Effect.provide(MainLayer)));
931
+ ```
932
+
933
+ ### Testing with Layers
934
+
935
+ ```typescript
936
+ import { Context, Effect, Layer, Config, ConfigProvider } from "effect";
937
+
938
+ // Test layer with mock config provider
939
+ const TestConfigProvider = Layer.setConfigProvider(
940
+ ConfigProvider.fromMap(
941
+ new Map([
942
+ ["LOG_LEVEL", "debug"],
943
+ ["DATABASE_URL", "sqlite::memory:"],
944
+ ]),
945
+ ),
946
+ );
947
+
948
+ // Test layer for Logger (mock implementation)
949
+ const LoggerTest = Layer.succeed(Logger, {
950
+ log: (message) =>
951
+ Effect.sync(() => {
952
+ // Collect logs for assertions instead of printing
953
+ testLogs.push(message);
954
+ }),
955
+ });
956
+
957
+ // Run tests with test layers
958
+ const testProgram = program.pipe(
959
+ Effect.provide(
960
+ Layer.mergeAll(TestConfigProvider, AppConfigLive, LoggerTest, DatabaseLive),
961
+ ),
962
+ );
963
+ ```
964
+
965
+ ---
966
+
967
+ ## 5. CLI + Config Integration Pattern
968
+
969
+ ### Complete Integration Example
970
+
971
+ This example shows how to integrate CLI options with Effect's config system.
972
+
973
+ ```typescript
974
+ import { Args, Command, Options } from "@effect/cli";
975
+ import { FileSystem } from "@effect/platform";
976
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
977
+ import {
978
+ Context,
979
+ Effect,
980
+ Layer,
981
+ Config,
982
+ ConfigProvider,
983
+ Schema,
984
+ Redacted,
985
+ } from "effect";
986
+
987
+ // ============================================
988
+ // 1. Define Configuration Schema
989
+ // ============================================
990
+
991
+ const AppConfigSchema = Schema.Struct({
992
+ host: Schema.String.pipe(
993
+ Schema.propertySignature,
994
+ Schema.withDefault(() => "localhost"),
995
+ ),
996
+ port: Schema.Number.pipe(
997
+ Schema.between(1, 65535),
998
+ Schema.propertySignature,
999
+ Schema.withDefault(() => 3000),
1000
+ ),
1001
+ logLevel: Schema.Literal("debug", "info", "warn", "error").pipe(
1002
+ Schema.propertySignature,
1003
+ Schema.withDefault(() => "info" as const),
1004
+ ),
1005
+ apiKey: Schema.optional(Schema.String),
1006
+ });
1007
+
1008
+ type AppConfig = Schema.Schema.Type<typeof AppConfigSchema>;
1009
+
1010
+ // ============================================
1011
+ // 2. Define Config Service
1012
+ // ============================================
1013
+
1014
+ class AppConfigService extends Context.Tag("AppConfigService")<
1015
+ AppConfigService,
1016
+ AppConfig
1017
+ >() {}
1018
+
1019
+ // Layer that loads config from Effect's Config system
1020
+ const AppConfigFromEnv = Layer.effect(
1021
+ AppConfigService,
1022
+ Effect.gen(function* () {
1023
+ const host = yield* Config.string("HOST").pipe(
1024
+ Config.withDefault("localhost"),
1025
+ );
1026
+ const port = yield* Config.number("PORT").pipe(Config.withDefault(3000));
1027
+ const logLevel = yield* Config.string("LOG_LEVEL").pipe(
1028
+ Config.withDefault("info"),
1029
+ );
1030
+ const apiKey = yield* Config.string("API_KEY").pipe(Config.optional);
1031
+
1032
+ return { host, port, logLevel, apiKey } as AppConfig;
1033
+ }),
1034
+ );
1035
+
1036
+ // ============================================
1037
+ // 3. Define CLI Options
1038
+ // ============================================
1039
+
1040
+ const hostOption = Options.text("host").pipe(
1041
+ Options.withAlias("H"),
1042
+ Options.withDescription("Server host"),
1043
+ Options.optional,
1044
+ );
1045
+
1046
+ const portOption = Options.integer("port").pipe(
1047
+ Options.withAlias("p"),
1048
+ Options.withDescription("Server port"),
1049
+ Options.optional,
1050
+ );
1051
+
1052
+ const logLevelOption = Options.choice("log-level", [
1053
+ "debug",
1054
+ "info",
1055
+ "warn",
1056
+ "error",
1057
+ ] as const).pipe(
1058
+ Options.withAlias("l"),
1059
+ Options.withDescription("Log level"),
1060
+ Options.optional,
1061
+ );
1062
+
1063
+ const configFileOption = Options.file("config").pipe(
1064
+ Options.withAlias("c"),
1065
+ Options.withDescription("Path to config file"),
1066
+ Options.optional,
1067
+ );
1068
+
1069
+ // ============================================
1070
+ // 4. Create Config Layer from CLI + File + Env
1071
+ // ============================================
1072
+
1073
+ interface CliOptions {
1074
+ host: typeof hostOption extends Options.Options<infer A> ? A : never;
1075
+ port: typeof portOption extends Options.Options<infer A> ? A : never;
1076
+ logLevel: typeof logLevelOption extends Options.Options<infer A> ? A : never;
1077
+ configFile: typeof configFileOption extends Options.Options<infer A>
1078
+ ? A
1079
+ : never;
1080
+ }
1081
+
1082
+ const makeConfigLayer = (cliOptions: CliOptions) =>
1083
+ Layer.effect(
1084
+ AppConfigService,
1085
+ Effect.gen(function* () {
1086
+ const fs = yield* FileSystem.FileSystem;
1087
+
1088
+ // 1. Start with defaults
1089
+ let config: AppConfig = {
1090
+ host: "localhost",
1091
+ port: 3000,
1092
+ logLevel: "info",
1093
+ apiKey: undefined,
1094
+ };
1095
+
1096
+ // 2. Load from config file if specified
1097
+ if (cliOptions.configFile._tag === "Some") {
1098
+ const fileContent = yield* fs.readFileString(
1099
+ cliOptions.configFile.value,
1100
+ );
1101
+ const fileConfig = JSON.parse(fileContent);
1102
+ config = { ...config, ...fileConfig };
1103
+ }
1104
+
1105
+ // 3. Override with environment variables
1106
+ const envHost = yield* Config.string("HOST").pipe(Config.optional);
1107
+ const envPort = yield* Config.number("PORT").pipe(Config.optional);
1108
+ const envLogLevel = yield* Config.string("LOG_LEVEL").pipe(
1109
+ Config.optional,
1110
+ );
1111
+ const envApiKey = yield* Config.string("API_KEY").pipe(Config.optional);
1112
+
1113
+ if (envHost._tag === "Some") config.host = envHost.value;
1114
+ if (envPort._tag === "Some") config.port = envPort.value;
1115
+ if (envLogLevel._tag === "Some")
1116
+ config.logLevel = envLogLevel.value as AppConfig["logLevel"];
1117
+ if (envApiKey._tag === "Some") config.apiKey = envApiKey.value;
1118
+
1119
+ // 4. Override with CLI flags (highest priority)
1120
+ if (cliOptions.host._tag === "Some") config.host = cliOptions.host.value;
1121
+ if (cliOptions.port._tag === "Some") config.port = cliOptions.port.value;
1122
+ if (cliOptions.logLevel._tag === "Some")
1123
+ config.logLevel = cliOptions.logLevel.value;
1124
+
1125
+ return config;
1126
+ }),
1127
+ );
1128
+
1129
+ // ============================================
1130
+ // 5. Define Commands
1131
+ // ============================================
1132
+
1133
+ const serve = Command.make(
1134
+ "serve",
1135
+ {
1136
+ host: hostOption,
1137
+ port: portOption,
1138
+ logLevel: logLevelOption,
1139
+ configFile: configFileOption,
1140
+ },
1141
+ (cliOptions) =>
1142
+ Effect.gen(function* () {
1143
+ const config = yield* AppConfigService;
1144
+
1145
+ yield* Effect.log(`Starting server on ${config.host}:${config.port}`);
1146
+ yield* Effect.log(`Log level: ${config.logLevel}`);
1147
+
1148
+ // Server implementation...
1149
+ }).pipe(Effect.provide(makeConfigLayer(cliOptions))),
1150
+ );
1151
+
1152
+ const check = Command.make(
1153
+ "check",
1154
+ {
1155
+ configFile: configFileOption,
1156
+ },
1157
+ ({ configFile }) =>
1158
+ Effect.gen(function* () {
1159
+ yield* Effect.log("Checking configuration...");
1160
+
1161
+ const config = yield* AppConfigService;
1162
+ yield* Effect.log(`Host: ${config.host}`);
1163
+ yield* Effect.log(`Port: ${config.port}`);
1164
+ yield* Effect.log(`Log Level: ${config.logLevel}`);
1165
+ yield* Effect.log(`API Key: ${config.apiKey ? "***" : "not set"}`);
1166
+ }).pipe(
1167
+ Effect.provide(
1168
+ makeConfigLayer({
1169
+ host: { _tag: "None" },
1170
+ port: { _tag: "None" },
1171
+ logLevel: { _tag: "None" },
1172
+ configFile,
1173
+ }),
1174
+ ),
1175
+ ),
1176
+ );
1177
+
1178
+ const main = Command.make("myapp", {}).pipe(
1179
+ Command.withSubcommands([serve, check]),
1180
+ );
1181
+
1182
+ // ============================================
1183
+ // 6. Run CLI
1184
+ // ============================================
1185
+
1186
+ const cli = Command.run(main, {
1187
+ name: "myapp",
1188
+ version: "1.0.0",
1189
+ });
1190
+
1191
+ cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain);
1192
+ ```
1193
+
1194
+ Usage:
1195
+
1196
+ ```bash
1197
+ # Use defaults
1198
+ myapp serve
1199
+
1200
+ # Use config file
1201
+ myapp serve --config ./myapp.json
1202
+
1203
+ # Override with CLI flags
1204
+ myapp serve --host 0.0.0.0 --port 9000 --log-level debug
1205
+
1206
+ # Environment variables work too
1207
+ HOST=prod.example.com PORT=443 myapp serve
1208
+
1209
+ # Check config resolution
1210
+ myapp check --config ./myapp.json
1211
+ ```
1212
+
1213
+ ---
1214
+
1215
+ ## 6. Real-World Examples
1216
+
1217
+ ### Effect's Own CLI Template
1218
+
1219
+ The Effect team maintains an official CLI template in their examples repository:
1220
+
1221
+ **Source**: [Effect-TS/examples - CLI Template](https://github.com/Effect-TS/examples)
1222
+
1223
+ Features:
1224
+
1225
+ - Single-file bundling with tsup
1226
+ - Pre-configured for @effect/cli
1227
+ - Recommended project structure
1228
+
1229
+ ### Effect Discord Bot
1230
+
1231
+ The Effect team maintains a Discord bot built with Effect:
1232
+
1233
+ **Source**: [Effect-TS/discord-bot](https://github.com/Effect-TS/discord-bot)
1234
+
1235
+ Demonstrates:
1236
+
1237
+ - Effect service architecture
1238
+ - Layer composition
1239
+ - Real-world Effect patterns
1240
+
1241
+ ### create-effect-app
1242
+
1243
+ The official scaffolding tool for Effect projects:
1244
+
1245
+ **Source**: [create-effect-app](https://effect.website/docs/getting-started/create-effect-app/)
1246
+
1247
+ ```bash
1248
+ # Create new Effect project
1249
+ npx create-effect-app
1250
+
1251
+ # Options:
1252
+ # - Basic template (single package)
1253
+ # - Monorepo template (multi-package)
1254
+ # - CLI template (command-line app)
1255
+ ```
1256
+
1257
+ ---
1258
+
1259
+ ## 7. Comparison with Previous Research
1260
+
1261
+ ### vs. c12/citty (from 033-research-configuration-management.md)
1262
+
1263
+ | Aspect | Effect Ecosystem | c12/citty (UnJS) |
1264
+ | ------------------ | ------------------------ | ----------------- |
1265
+ | **CLI Framework** | @effect/cli | citty |
1266
+ | **Config Loading** | ConfigProvider | c12 |
1267
+ | **Validation** | Effect Schema | Zod |
1268
+ | **Type Safety** | Excellent (Effect types) | Good (TypeScript) |
1269
+ | **DI/Services** | Layer system | Manual |
1270
+ | **Error Handling** | Effect errors | Try/catch |
1271
+ | **Learning Curve** | Steep (Effect paradigm) | Moderate |
1272
+ | **Bundle Size** | Larger (full Effect) | Smaller |
1273
+ | **Ecosystem** | Effect ecosystem | UnJS ecosystem |
1274
+
1275
+ ### When to Choose Effect
1276
+
1277
+ Choose Effect when:
1278
+
1279
+ - Building a larger CLI with complex service dependencies
1280
+ - Already using Effect elsewhere in the application
1281
+ - Type-safe error handling is critical
1282
+ - Need sophisticated resource management (Scope)
1283
+ - Want unified testing approach with Effect
1284
+
1285
+ Choose c12/citty when:
1286
+
1287
+ - Building a simpler CLI
1288
+ - Want minimal dependencies
1289
+ - Not already invested in Effect
1290
+ - Prefer imperative style
1291
+ - Need faster onboarding for contributors
1292
+
1293
+ ---
1294
+
1295
+ ## 8. Recommendations for mdcontext
1296
+
1297
+ ### Option A: Full Effect (Recommended if committing to Effect)
1298
+
1299
+ Use Effect throughout the application:
1300
+
1301
+ ```
1302
+ @effect/cli - CLI framework
1303
+ @effect/platform - File system, process execution
1304
+ effect Config - Configuration loading
1305
+ effect Layer - Dependency injection
1306
+ effect Schema - Validation
1307
+ ```
1308
+
1309
+ **Pros**:
1310
+
1311
+ - Unified paradigm
1312
+ - Superior type safety
1313
+ - Excellent composability
1314
+ - Built-in resource management
1315
+
1316
+ **Cons**:
1317
+
1318
+ - Steep learning curve
1319
+ - Larger bundle
1320
+ - All-in commitment
1321
+
1322
+ ### Option B: Hybrid Approach
1323
+
1324
+ Use Effect for core functionality, simpler tools for CLI:
1325
+
1326
+ ```
1327
+ citty - CLI framework (lighter)
1328
+ c12 - Config loading
1329
+ effect - Core business logic
1330
+ zod - Validation
1331
+ ```
1332
+
1333
+ **Pros**:
1334
+
1335
+ - Lower barrier to entry
1336
+ - Smaller CLI bundle
1337
+ - Flexibility
1338
+
1339
+ **Cons**:
1340
+
1341
+ - Mixed paradigms
1342
+ - Manual integration needed
1343
+
1344
+ ### Recommended: Start with Option A
1345
+
1346
+ Given that mdcontext is exploring Effect for its core functionality, using the full Effect ecosystem for CLI and config provides:
1347
+
1348
+ 1. **Consistency**: Same patterns throughout
1349
+ 2. **Type Safety**: End-to-end typed effects
1350
+ 3. **Testing**: Unified mocking with Layers
1351
+ 4. **Future-Proofing**: Effect ecosystem is growing
1352
+
1353
+ Migration path:
1354
+
1355
+ 1. Start with @effect/cli for command structure
1356
+ 2. Use Effect Config for environment/file loading
1357
+ 3. Build service Layers for business logic
1358
+ 4. Use @effect/platform for file system operations
1359
+
1360
+ ---
1361
+
1362
+ ## 9. Code Templates
1363
+
1364
+ ### Minimal CLI with Config
1365
+
1366
+ ```typescript
1367
+ // src/cli.ts
1368
+ import { Args, Command, Options } from "@effect/cli";
1369
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
1370
+ import { Effect, Config, Layer, Context } from "effect";
1371
+
1372
+ // Config service
1373
+ class AppConfig extends Context.Tag("AppConfig")<
1374
+ AppConfig,
1375
+ {
1376
+ readonly verbose: boolean;
1377
+ readonly outputDir: string;
1378
+ }
1379
+ >() {}
1380
+
1381
+ const AppConfigLive = Layer.effect(
1382
+ AppConfig,
1383
+ Effect.gen(function* () {
1384
+ const verbose = yield* Config.boolean("VERBOSE").pipe(
1385
+ Config.withDefault(false),
1386
+ );
1387
+ const outputDir = yield* Config.string("OUTPUT_DIR").pipe(
1388
+ Config.withDefault("./dist"),
1389
+ );
1390
+ return { verbose, outputDir };
1391
+ }),
1392
+ );
1393
+
1394
+ // CLI command
1395
+ const build = Command.make(
1396
+ "build",
1397
+ {
1398
+ input: Args.file({ name: "input" }),
1399
+ verbose: Options.boolean("verbose").pipe(
1400
+ Options.withAlias("v"),
1401
+ Options.optional,
1402
+ ),
1403
+ output: Options.directory("output").pipe(
1404
+ Options.withAlias("o"),
1405
+ Options.optional,
1406
+ ),
1407
+ },
1408
+ ({ input, verbose, output }) =>
1409
+ Effect.gen(function* () {
1410
+ const config = yield* AppConfig;
1411
+
1412
+ const isVerbose =
1413
+ verbose._tag === "Some" ? verbose.value : config.verbose;
1414
+ const outputDir =
1415
+ output._tag === "Some" ? output.value : config.outputDir;
1416
+
1417
+ if (isVerbose) {
1418
+ yield* Effect.log(`Building ${input} to ${outputDir}`);
1419
+ }
1420
+
1421
+ // Build logic...
1422
+ }).pipe(Effect.provide(AppConfigLive)),
1423
+ );
1424
+
1425
+ const cli = Command.run(build, { name: "mybuild", version: "1.0.0" });
1426
+
1427
+ cli(process.argv).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain);
1428
+ ```
1429
+
1430
+ ### Config File Support
1431
+
1432
+ ```typescript
1433
+ // src/config.ts
1434
+ import { FileSystem } from "@effect/platform";
1435
+ import { Effect, Config, ConfigProvider, Layer, Context, Schema } from "effect";
1436
+
1437
+ // Config schema
1438
+ const ConfigSchema = Schema.Struct({
1439
+ logLevel: Schema.Literal("debug", "info", "warn", "error"),
1440
+ watch: Schema.Struct({
1441
+ enabled: Schema.Boolean,
1442
+ debounce: Schema.Number,
1443
+ }),
1444
+ output: Schema.Struct({
1445
+ format: Schema.Literal("json", "markdown"),
1446
+ dir: Schema.String,
1447
+ }),
1448
+ });
1449
+
1450
+ type AppConfigType = Schema.Schema.Type<typeof ConfigSchema>;
1451
+
1452
+ // Load config from file + env
1453
+ const loadConfigFromFile = (path: string) =>
1454
+ Effect.gen(function* () {
1455
+ const fs = yield* FileSystem.FileSystem;
1456
+ const content = yield* fs.readFileString(path);
1457
+ const json = JSON.parse(content);
1458
+ return Schema.decodeUnknownSync(ConfigSchema)(json);
1459
+ });
1460
+
1461
+ // ConfigProvider that tries file first, then env
1462
+ const makeConfigProvider = (configPath?: string) =>
1463
+ Effect.gen(function* () {
1464
+ if (configPath) {
1465
+ const fileConfig = yield* loadConfigFromFile(configPath);
1466
+ return ConfigProvider.fromJson(fileConfig);
1467
+ }
1468
+ return ConfigProvider.fromEnv();
1469
+ });
1470
+ ```
1471
+
1472
+ ---
1473
+
1474
+ ## References
1475
+
1476
+ ### Official Documentation
1477
+
1478
+ - [Effect Documentation](https://effect.website/)
1479
+ - [@effect/cli API](https://effect-ts.github.io/effect/docs/cli)
1480
+ - [Effect Configuration](https://effect.website/docs/configuration/)
1481
+ - [@effect/platform](https://effect.website/docs/platform/introduction/)
1482
+
1483
+ ### GitHub
1484
+
1485
+ - [Effect-TS/effect](https://github.com/Effect-TS/effect)
1486
+ - [Effect-TS/examples](https://github.com/Effect-TS/examples)
1487
+ - [@effect/cli README](https://github.com/Effect-TS/effect/blob/main/packages/cli/README.md)
1488
+ - [@effect/platform README](https://github.com/Effect-TS/effect/blob/main/packages/platform/README.md)
1489
+
1490
+ ### NPM
1491
+
1492
+ - [@effect/cli](https://www.npmjs.com/package/@effect/cli)
1493
+ - [@effect/platform](https://www.npmjs.com/package/@effect/platform)
1494
+ - [@effect/platform-node](https://www.npmjs.com/package/@effect/platform-node)
1495
+
1496
+ ### Community
1497
+
1498
+ - [Effect Discord](https://discord.gg/effect-ts)
1499
+ - [Effect Solutions](https://www.effect.solutions/)
1500
+ - [DeepWiki - Effect CLI Framework](https://deepwiki.com/Effect-TS/effect/8.1-cli-framework)
1501
+
1502
+ ---
1503
+
1504
+ _Research completed: 2026-01-21_