effect 4.0.0-beta.10 → 4.0.0-beta.12

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 (189) hide show
  1. package/dist/Channel.d.ts +7 -7
  2. package/dist/Config.d.ts +157 -0
  3. package/dist/Config.d.ts.map +1 -1
  4. package/dist/Config.js +56 -1
  5. package/dist/Config.js.map +1 -1
  6. package/dist/Effect.d.ts +296 -8
  7. package/dist/Effect.d.ts.map +1 -1
  8. package/dist/Effect.js +72 -0
  9. package/dist/Effect.js.map +1 -1
  10. package/dist/ErrorReporter.d.ts +376 -0
  11. package/dist/ErrorReporter.d.ts.map +1 -0
  12. package/dist/ErrorReporter.js +246 -0
  13. package/dist/ErrorReporter.js.map +1 -0
  14. package/dist/Fiber.d.ts +2 -2
  15. package/dist/Fiber.d.ts.map +1 -1
  16. package/dist/Fiber.js.map +1 -1
  17. package/dist/Graph.d.ts.map +1 -1
  18. package/dist/Graph.js +3 -6
  19. package/dist/Graph.js.map +1 -1
  20. package/dist/LogLevel.d.ts +5 -0
  21. package/dist/LogLevel.d.ts.map +1 -1
  22. package/dist/LogLevel.js.map +1 -1
  23. package/dist/Logger.d.ts +25 -91
  24. package/dist/Logger.d.ts.map +1 -1
  25. package/dist/Logger.js +2 -3
  26. package/dist/Logger.js.map +1 -1
  27. package/dist/Queue.d.ts.map +1 -1
  28. package/dist/Queue.js +0 -1
  29. package/dist/Queue.js.map +1 -1
  30. package/dist/Random.d.ts +17 -0
  31. package/dist/Random.d.ts.map +1 -1
  32. package/dist/Random.js +17 -0
  33. package/dist/Random.js.map +1 -1
  34. package/dist/References.d.ts +3 -3
  35. package/dist/References.d.ts.map +1 -1
  36. package/dist/Schema.d.ts +3 -1
  37. package/dist/Schema.d.ts.map +1 -1
  38. package/dist/SchemaAST.d.ts.map +1 -1
  39. package/dist/SchemaAST.js +2 -1
  40. package/dist/SchemaAST.js.map +1 -1
  41. package/dist/Stream.d.ts +5 -5
  42. package/dist/index.d.ts +4 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/internal/effect.js +98 -33
  47. package/dist/internal/effect.js.map +1 -1
  48. package/dist/internal/hashMap.js +2 -2
  49. package/dist/internal/hashMap.js.map +1 -1
  50. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  51. package/dist/unstable/ai/LanguageModel.js +86 -14
  52. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  53. package/dist/unstable/ai/McpSchema.d.ts +112 -36
  54. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  55. package/dist/unstable/ai/McpSchema.js +47 -10
  56. package/dist/unstable/ai/McpSchema.js.map +1 -1
  57. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  58. package/dist/unstable/ai/McpServer.js +33 -6
  59. package/dist/unstable/ai/McpServer.js.map +1 -1
  60. package/dist/unstable/ai/Tool.d.ts +16 -0
  61. package/dist/unstable/ai/Tool.d.ts.map +1 -1
  62. package/dist/unstable/ai/Tool.js +14 -0
  63. package/dist/unstable/ai/Tool.js.map +1 -1
  64. package/dist/unstable/cli/CliOutput.js +37 -6
  65. package/dist/unstable/cli/CliOutput.js.map +1 -1
  66. package/dist/unstable/cli/Command.d.ts +199 -7
  67. package/dist/unstable/cli/Command.d.ts.map +1 -1
  68. package/dist/unstable/cli/Command.js +116 -6
  69. package/dist/unstable/cli/Command.js.map +1 -1
  70. package/dist/unstable/cli/HelpDoc.d.ts +60 -2
  71. package/dist/unstable/cli/HelpDoc.d.ts.map +1 -1
  72. package/dist/unstable/cli/internal/command.d.ts +11 -1
  73. package/dist/unstable/cli/internal/command.d.ts.map +1 -1
  74. package/dist/unstable/cli/internal/command.js +33 -8
  75. package/dist/unstable/cli/internal/command.js.map +1 -1
  76. package/dist/unstable/cli/internal/completions/CommandDescriptor.js +7 -2
  77. package/dist/unstable/cli/internal/completions/CommandDescriptor.js.map +1 -1
  78. package/dist/unstable/cli/internal/parser.js +10 -2
  79. package/dist/unstable/cli/internal/parser.js.map +1 -1
  80. package/dist/unstable/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  81. package/dist/unstable/cluster/ClusterWorkflowEngine.js +2 -2
  82. package/dist/unstable/cluster/ClusterWorkflowEngine.js.map +1 -1
  83. package/dist/unstable/http/Headers.d.ts.map +1 -1
  84. package/dist/unstable/http/Headers.js +27 -10
  85. package/dist/unstable/http/Headers.js.map +1 -1
  86. package/dist/unstable/http/HttpClient.d.ts +28 -4
  87. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  88. package/dist/unstable/http/HttpClient.js.map +1 -1
  89. package/dist/unstable/http/HttpEffect.d.ts +3 -8
  90. package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
  91. package/dist/unstable/http/HttpEffect.js +25 -31
  92. package/dist/unstable/http/HttpEffect.js.map +1 -1
  93. package/dist/unstable/http/HttpMiddleware.d.ts.map +1 -1
  94. package/dist/unstable/http/HttpMiddleware.js +4 -8
  95. package/dist/unstable/http/HttpMiddleware.js.map +1 -1
  96. package/dist/unstable/http/HttpServerError.d.ts +14 -27
  97. package/dist/unstable/http/HttpServerError.d.ts.map +1 -1
  98. package/dist/unstable/http/HttpServerError.js +37 -44
  99. package/dist/unstable/http/HttpServerError.js.map +1 -1
  100. package/dist/unstable/http/HttpServerRespondable.d.ts +2 -2
  101. package/dist/unstable/http/HttpServerRespondable.d.ts.map +1 -1
  102. package/dist/unstable/http/HttpServerRespondable.js +5 -5
  103. package/dist/unstable/http/HttpServerRespondable.js.map +1 -1
  104. package/dist/unstable/http/HttpServerResponse.d.ts +2 -1
  105. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  106. package/dist/unstable/http/HttpServerResponse.js +2 -0
  107. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  108. package/dist/unstable/http/internal/preResponseHandler.d.ts +2 -0
  109. package/dist/unstable/http/internal/preResponseHandler.d.ts.map +1 -0
  110. package/dist/unstable/http/internal/preResponseHandler.js +10 -0
  111. package/dist/unstable/http/internal/preResponseHandler.js.map +1 -0
  112. package/dist/unstable/httpapi/HttpApiBuilder.d.ts +1 -1
  113. package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
  114. package/dist/unstable/httpapi/HttpApiBuilder.js +1 -1
  115. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  116. package/dist/unstable/httpapi/HttpApiError.d.ts +11 -0
  117. package/dist/unstable/httpapi/HttpApiError.d.ts.map +1 -1
  118. package/dist/unstable/httpapi/HttpApiError.js +29 -9
  119. package/dist/unstable/httpapi/HttpApiError.js.map +1 -1
  120. package/dist/unstable/observability/OtlpLogger.d.ts.map +1 -1
  121. package/dist/unstable/observability/OtlpLogger.js +7 -4
  122. package/dist/unstable/observability/OtlpLogger.js.map +1 -1
  123. package/dist/unstable/reactivity/Atom.js +1 -1
  124. package/dist/unstable/reactivity/Atom.js.map +1 -1
  125. package/dist/unstable/reactivity/AtomRegistry.d.ts +6 -0
  126. package/dist/unstable/reactivity/AtomRegistry.d.ts.map +1 -1
  127. package/dist/unstable/reactivity/AtomRegistry.js +22 -1
  128. package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
  129. package/dist/unstable/rpc/RpcSchema.d.ts +13 -0
  130. package/dist/unstable/rpc/RpcSchema.d.ts.map +1 -1
  131. package/dist/unstable/rpc/RpcSchema.js +14 -0
  132. package/dist/unstable/rpc/RpcSchema.js.map +1 -1
  133. package/dist/unstable/rpc/RpcSerialization.d.ts.map +1 -1
  134. package/dist/unstable/rpc/RpcSerialization.js +34 -9
  135. package/dist/unstable/rpc/RpcSerialization.js.map +1 -1
  136. package/dist/unstable/rpc/RpcServer.d.ts +0 -7
  137. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  138. package/dist/unstable/rpc/RpcServer.js +9 -10
  139. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  140. package/dist/unstable/workflow/WorkflowEngine.d.ts +6 -0
  141. package/dist/unstable/workflow/WorkflowEngine.d.ts.map +1 -1
  142. package/dist/unstable/workflow/WorkflowEngine.js +131 -0
  143. package/dist/unstable/workflow/WorkflowEngine.js.map +1 -1
  144. package/package.json +1 -1
  145. package/src/Channel.ts +9 -9
  146. package/src/Config.ts +171 -9
  147. package/src/Effect.ts +315 -8
  148. package/src/ErrorReporter.ts +459 -0
  149. package/src/Fiber.ts +9 -2
  150. package/src/Graph.ts +16 -6
  151. package/src/LogLevel.ts +6 -0
  152. package/src/Logger.ts +28 -95
  153. package/src/Queue.ts +0 -1
  154. package/src/Random.ts +18 -0
  155. package/src/References.ts +4 -4
  156. package/src/Schema.ts +1 -1
  157. package/src/SchemaAST.ts +2 -1
  158. package/src/Stream.ts +7 -7
  159. package/src/index.ts +5 -0
  160. package/src/internal/effect.ts +205 -49
  161. package/src/internal/hashMap.ts +2 -2
  162. package/src/unstable/ai/LanguageModel.ts +117 -16
  163. package/src/unstable/ai/McpSchema.ts +57 -11
  164. package/src/unstable/ai/McpServer.ts +44 -6
  165. package/src/unstable/ai/Tool.ts +15 -0
  166. package/src/unstable/cli/CliOutput.ts +45 -6
  167. package/src/unstable/cli/Command.ts +298 -11
  168. package/src/unstable/cli/HelpDoc.ts +68 -2
  169. package/src/unstable/cli/internal/command.ts +47 -11
  170. package/src/unstable/cli/internal/completions/CommandDescriptor.ts +7 -2
  171. package/src/unstable/cli/internal/parser.ts +11 -3
  172. package/src/unstable/cluster/ClusterWorkflowEngine.ts +2 -2
  173. package/src/unstable/http/Headers.ts +28 -13
  174. package/src/unstable/http/HttpClient.ts +45 -10
  175. package/src/unstable/http/HttpEffect.ts +30 -44
  176. package/src/unstable/http/HttpMiddleware.ts +4 -14
  177. package/src/unstable/http/HttpServerError.ts +42 -45
  178. package/src/unstable/http/HttpServerRespondable.ts +6 -6
  179. package/src/unstable/http/HttpServerResponse.ts +3 -1
  180. package/src/unstable/http/internal/preResponseHandler.ts +15 -0
  181. package/src/unstable/httpapi/HttpApiBuilder.ts +2 -1
  182. package/src/unstable/httpapi/HttpApiError.ts +30 -9
  183. package/src/unstable/observability/OtlpLogger.ts +9 -5
  184. package/src/unstable/reactivity/Atom.ts +1 -1
  185. package/src/unstable/reactivity/AtomRegistry.ts +29 -1
  186. package/src/unstable/rpc/RpcSchema.ts +17 -0
  187. package/src/unstable/rpc/RpcSerialization.ts +44 -9
  188. package/src/unstable/rpc/RpcServer.ts +14 -19
  189. package/src/unstable/workflow/WorkflowEngine.ts +178 -0
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @since 4.0.0
3
3
  */
4
+ import type { NonEmptyArray, NonEmptyReadonlyArray } from "../../Array.ts"
4
5
  import * as Console from "../../Console.ts"
5
6
  import * as Effect from "../../Effect.ts"
6
7
  import type * as FileSystem from "../../FileSystem.ts"
@@ -11,9 +12,9 @@ import type { Pipeable } from "../../Pipeable.ts"
11
12
  import * as Predicate from "../../Predicate.ts"
12
13
  import * as References from "../../References.ts"
13
14
  import * as Result from "../../Result.ts"
14
- import type * as ServiceMap from "../../ServiceMap.ts"
15
+ import * as ServiceMap from "../../ServiceMap.ts"
15
16
  import * as Terminal from "../../Terminal.ts"
16
- import type { Simplify } from "../../Types.ts"
17
+ import type { NoInfer, Simplify } from "../../Types.ts"
17
18
  import type { ChildProcessSpawner } from "../process/ChildProcessSpawner.ts"
18
19
  import * as CliError from "./CliError.ts"
19
20
  import * as CliOutput from "./CliOutput.ts"
@@ -94,16 +95,45 @@ export interface Command<Name extends string, Input, E = never, R = never> exten
94
95
  */
95
96
  readonly description: string | undefined
96
97
 
98
+ /**
99
+ * An optional short description used when listing subcommands.
100
+ */
101
+ readonly shortDescription: string | undefined
102
+
103
+ /**
104
+ * Optional usage examples for the command.
105
+ */
106
+ readonly examples: ReadonlyArray<Command.Example>
107
+
97
108
  /**
98
109
  * The subcommands available under this command.
99
110
  */
100
- readonly subcommands: ReadonlyArray<Command.Any>
111
+ readonly subcommands: ReadonlyArray<{
112
+ readonly group: string | undefined
113
+ readonly commands: NonEmptyReadonlyArray<Command.Any>
114
+ }>
115
+
116
+ /**
117
+ * Custom annotations associated with this command.
118
+ */
119
+ readonly annotations: ServiceMap.ServiceMap<never>
101
120
  }
102
121
 
103
122
  /**
104
123
  * @since 4.0.0
105
124
  */
106
125
  export declare namespace Command {
126
+ /**
127
+ * Represents a concrete usage example for a command.
128
+ *
129
+ * @since 4.0.0
130
+ * @category models
131
+ */
132
+ export interface Example {
133
+ readonly command: string
134
+ readonly description?: string | undefined
135
+ }
136
+
107
137
  /**
108
138
  * Configuration object for defining command flags, arguments, and nested structures.
109
139
  *
@@ -209,6 +239,25 @@ export declare namespace Command {
209
239
  * @category models
210
240
  */
211
241
  export type Any = Command<string, unknown, unknown, unknown>
242
+
243
+ /**
244
+ * A grouped set of subcommands used by `Command.withSubcommands`.
245
+ *
246
+ * @since 4.0.0
247
+ * @category models
248
+ */
249
+ export interface SubcommandGroup<Commands extends ReadonlyArray<Any> = ReadonlyArray<Any>> {
250
+ readonly group: string
251
+ readonly commands: Commands
252
+ }
253
+
254
+ /**
255
+ * Entry type accepted by `Command.withSubcommands`.
256
+ *
257
+ * @since 4.0.0
258
+ * @category models
259
+ */
260
+ export type SubcommandEntry = Any | SubcommandGroup<ReadonlyArray<Any>>
212
261
  }
213
262
 
214
263
  /**
@@ -644,6 +693,57 @@ export const withHandler: {
644
693
  handler: (value: A) => Effect.Effect<void, E, R>
645
694
  ): Command<Name, A, E, R> => makeCommand({ ...toImpl(self), handle: handler }))
646
695
 
696
+ interface SubcommandGroupInternal {
697
+ readonly group: string | undefined
698
+ readonly commands: NonEmptyReadonlyArray<Command.Any>
699
+ }
700
+
701
+ const normalizeSubcommandEntries = (
702
+ entries: ReadonlyArray<Command.SubcommandEntry>
703
+ ): {
704
+ readonly flat: ReadonlyArray<Command.Any>
705
+ readonly groups: ReadonlyArray<SubcommandGroupInternal>
706
+ } => {
707
+ const flat: Array<Command.Any> = []
708
+ const grouped = new Map<string | undefined, NonEmptyArray<Command.Any>>()
709
+
710
+ const addToGroup = (group: string | undefined, command: Command.Any): void => {
711
+ flat.push(command)
712
+ const existing = grouped.get(group)
713
+ if (existing) {
714
+ existing.push(command)
715
+ } else {
716
+ grouped.set(group, [command])
717
+ }
718
+ }
719
+
720
+ for (const entry of entries) {
721
+ if (isCommand(entry)) {
722
+ addToGroup(undefined, entry)
723
+ continue
724
+ }
725
+ for (const command of entry.commands) {
726
+ addToGroup(entry.group, command)
727
+ }
728
+ }
729
+
730
+ const groups: Array<SubcommandGroupInternal> = []
731
+ const ungroupedCommands = grouped.get(undefined)
732
+
733
+ if (ungroupedCommands && ungroupedCommands.length > 0) {
734
+ groups.push({ group: undefined, commands: ungroupedCommands })
735
+ }
736
+
737
+ for (const [group, commands] of grouped) {
738
+ if (group === undefined) {
739
+ continue
740
+ }
741
+ groups.push({ group, commands })
742
+ }
743
+
744
+ return { flat, groups }
745
+ }
746
+
647
747
  /**
648
748
  * Adds subcommands to a command, creating a hierarchical command structure.
649
749
  *
@@ -717,7 +817,7 @@ export const withSubcommands: {
717
817
  * @since 4.0.0
718
818
  * @category combinators
719
819
  */
720
- <const Subcommands extends ReadonlyArray<Command<any, any, any, any>>>(subcommands: Subcommands): <Name extends string, Input, E, R>(
820
+ <const Subcommands extends ReadonlyArray<Command.SubcommandEntry>>(subcommands: Subcommands): <Name extends string, Input, E, R>(
721
821
  self: Command<Name, Input, E, R>
722
822
  ) => Command<
723
823
  Name,
@@ -766,7 +866,7 @@ export const withSubcommands: {
766
866
  Input,
767
867
  E,
768
868
  R,
769
- const Subcommands extends ReadonlyArray<Command<any, any, any, any>>
869
+ const Subcommands extends ReadonlyArray<Command.SubcommandEntry>
770
870
  >(self: Command<Name, Input, E, R>, subcommands: Subcommands): Command<
771
871
  Name,
772
872
  Input,
@@ -778,7 +878,7 @@ export const withSubcommands: {
778
878
  Input,
779
879
  E,
780
880
  R,
781
- const Subcommands extends ReadonlyArray<Command<any, any, any, any>>
881
+ const Subcommands extends ReadonlyArray<Command.SubcommandEntry>
782
882
  >(
783
883
  self: Command<Name, Input, E, R>,
784
884
  subcommands: Subcommands
@@ -788,10 +888,11 @@ export const withSubcommands: {
788
888
  E | ExtractSubcommandErrors<Subcommands>,
789
889
  R | Exclude<ExtractSubcommandContext<Subcommands>, CommandContext<Name>>
790
890
  > => {
791
- checkForDuplicateFlags(self, subcommands)
891
+ const normalized = normalizeSubcommandEntries(subcommands)
892
+ checkForDuplicateFlags(self, normalized.flat)
792
893
 
793
894
  const impl = toImpl(self)
794
- const byName = new Map(subcommands.map((s) => [s.name, toImpl(s)] as const))
895
+ const byName = new Map(normalized.flat.map((s) => [s.name, toImpl(s)] as const))
795
896
 
796
897
  // Internal type for routing - not exposed in public type
797
898
  type SubcommandInfo = { readonly name: string; readonly result: unknown }
@@ -832,16 +933,22 @@ export const withSubcommands: {
832
933
  name: impl.name,
833
934
  config: impl.config,
834
935
  description: impl.description,
936
+ shortDescription: impl.shortDescription,
937
+ annotations: impl.annotations,
938
+ examples: impl.examples,
835
939
  service: impl.service,
836
- subcommands,
940
+ subcommands: normalized.groups,
837
941
  parse,
838
942
  handle
839
943
  })
840
944
  })
841
945
 
842
946
  // Type extractors for subcommand arrays - T[number] gives union of all elements
843
- type ExtractSubcommandErrors<T extends ReadonlyArray<Command<any, any, any, any>>> = Error<T[number]>
844
- type ExtractSubcommandContext<T extends ReadonlyArray<Command<any, any, any, any>>> = T[number] extends
947
+ type ExtractSubcommand<T> = T extends Command<any, any, any, any> ? T
948
+ : T extends Command.SubcommandGroup<infer Commands> ? Commands[number]
949
+ : never
950
+ type ExtractSubcommandErrors<T extends ReadonlyArray<Command.SubcommandEntry>> = Error<ExtractSubcommand<T[number]>>
951
+ type ExtractSubcommandContext<T extends ReadonlyArray<Command.SubcommandEntry>> = ExtractSubcommand<T[number]> extends
845
952
  Command<any, any, any, infer R> ? R : never
846
953
 
847
954
  /**
@@ -926,6 +1033,186 @@ export const withDescription: {
926
1033
  description: string
927
1034
  ) => makeCommand({ ...toImpl(self), description }))
928
1035
 
1036
+ /**
1037
+ * Sets a short description for a command.
1038
+ *
1039
+ * Short descriptions are used when listing subcommands in help output and
1040
+ * shell completions. If no short description is provided, the full
1041
+ * `description` is used as a fallback.
1042
+ *
1043
+ * @since 4.0.0
1044
+ * @category combinators
1045
+ */
1046
+ export const withShortDescription: {
1047
+ /**
1048
+ * Sets a short description for a command.
1049
+ *
1050
+ * Short descriptions are used when listing subcommands in help output and
1051
+ * shell completions. If no short description is provided, the full
1052
+ * `description` is used as a fallback.
1053
+ *
1054
+ * @since 4.0.0
1055
+ * @category combinators
1056
+ */
1057
+ (shortDescription: string): <const Name extends string, Input, E, R>(
1058
+ self: Command<Name, Input, E, R>
1059
+ ) => Command<Name, Input, E, R>
1060
+ /**
1061
+ * Sets a short description for a command.
1062
+ *
1063
+ * Short descriptions are used when listing subcommands in help output and
1064
+ * shell completions. If no short description is provided, the full
1065
+ * `description` is used as a fallback.
1066
+ *
1067
+ * @since 4.0.0
1068
+ * @category combinators
1069
+ */
1070
+ <const Name extends string, Input, E, R>(self: Command<Name, Input, E, R>, shortDescription: string): Command<Name, Input, E, R>
1071
+ } = dual(2, <const Name extends string, Input, E, R>(
1072
+ self: Command<Name, Input, E, R>,
1073
+ shortDescription: string
1074
+ ) => makeCommand({ ...toImpl(self), shortDescription }))
1075
+
1076
+ /**
1077
+ * Adds a custom annotation to a command.
1078
+ *
1079
+ * @since 4.0.0
1080
+ * @category combinators
1081
+ */
1082
+ export const annotate: {
1083
+ /**
1084
+ * Adds a custom annotation to a command.
1085
+ *
1086
+ * @since 4.0.0
1087
+ * @category combinators
1088
+ */
1089
+ <I, S>(service: ServiceMap.Service<I, S>, value: NoInfer<S>): <Name extends string, Input, E, R>(
1090
+ self: Command<Name, Input, E, R>
1091
+ ) => Command<Name, Input, E, R>
1092
+ /**
1093
+ * Adds a custom annotation to a command.
1094
+ *
1095
+ * @since 4.0.0
1096
+ * @category combinators
1097
+ */
1098
+ <Name extends string, Input, E, R, I, S>(
1099
+ self: Command<Name, Input, E, R>,
1100
+ service: ServiceMap.Service<I, S>,
1101
+ value: NoInfer<S>
1102
+ ): Command<Name, Input, E, R>
1103
+ } = dual(3, <Name extends string, Input, E, R, I, S>(
1104
+ self: Command<Name, Input, E, R>,
1105
+ service: ServiceMap.Service<I, S>,
1106
+ value: NoInfer<S>
1107
+ ) => {
1108
+ const impl = toImpl(self)
1109
+ return makeCommand({ ...impl, annotations: ServiceMap.add(impl.annotations, service, value) })
1110
+ })
1111
+
1112
+ /**
1113
+ * Merges a ServiceMap of annotations into a command.
1114
+ *
1115
+ * @since 4.0.0
1116
+ * @category combinators
1117
+ */
1118
+ export const annotateMerge: {
1119
+ /**
1120
+ * Merges a ServiceMap of annotations into a command.
1121
+ *
1122
+ * @since 4.0.0
1123
+ * @category combinators
1124
+ */
1125
+ <I>(annotations: ServiceMap.ServiceMap<I>): <Name extends string, Input, E, R>(
1126
+ self: Command<Name, Input, E, R>
1127
+ ) => Command<Name, Input, E, R>
1128
+ /**
1129
+ * Merges a ServiceMap of annotations into a command.
1130
+ *
1131
+ * @since 4.0.0
1132
+ * @category combinators
1133
+ */
1134
+ <Name extends string, Input, E, R, I>(self: Command<Name, Input, E, R>, annotations: ServiceMap.ServiceMap<I>): Command<Name, Input, E, R>
1135
+ } = dual(2, <Name extends string, Input, E, R, I>(
1136
+ self: Command<Name, Input, E, R>,
1137
+ annotations: ServiceMap.ServiceMap<I>
1138
+ ) => {
1139
+ const impl = toImpl(self)
1140
+ return makeCommand({ ...impl, annotations: ServiceMap.merge(impl.annotations, annotations) })
1141
+ })
1142
+
1143
+ /**
1144
+ * Sets usage examples for a command.
1145
+ *
1146
+ * Examples are exposed in structured `HelpDoc` data and rendered by the
1147
+ * default formatter in an `EXAMPLES` section.
1148
+ *
1149
+ * @example
1150
+ * ```ts
1151
+ * import { Command } from "effect/unstable/cli"
1152
+ *
1153
+ * const login = Command.make("login").pipe(
1154
+ * Command.withExamples([
1155
+ * { command: "myapp login", description: "Log in with browser OAuth" },
1156
+ * { command: "myapp login --token sbp_abc123", description: "Log in with a token" }
1157
+ * ])
1158
+ * )
1159
+ * ```
1160
+ *
1161
+ * @since 4.0.0
1162
+ * @category combinators
1163
+ */
1164
+ export const withExamples: {
1165
+ /**
1166
+ * Sets usage examples for a command.
1167
+ *
1168
+ * Examples are exposed in structured `HelpDoc` data and rendered by the
1169
+ * default formatter in an `EXAMPLES` section.
1170
+ *
1171
+ * @example
1172
+ * ```ts
1173
+ * import { Command } from "effect/unstable/cli"
1174
+ *
1175
+ * const login = Command.make("login").pipe(
1176
+ * Command.withExamples([
1177
+ * { command: "myapp login", description: "Log in with browser OAuth" },
1178
+ * { command: "myapp login --token sbp_abc123", description: "Log in with a token" }
1179
+ * ])
1180
+ * )
1181
+ * ```
1182
+ *
1183
+ * @since 4.0.0
1184
+ * @category combinators
1185
+ */
1186
+ (examples: ReadonlyArray<Command.Example>): <const Name extends string, Input, E, R>(
1187
+ self: Command<Name, Input, E, R>
1188
+ ) => Command<Name, Input, E, R>
1189
+ /**
1190
+ * Sets usage examples for a command.
1191
+ *
1192
+ * Examples are exposed in structured `HelpDoc` data and rendered by the
1193
+ * default formatter in an `EXAMPLES` section.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * import { Command } from "effect/unstable/cli"
1198
+ *
1199
+ * const login = Command.make("login").pipe(
1200
+ * Command.withExamples([
1201
+ * { command: "myapp login", description: "Log in with browser OAuth" },
1202
+ * { command: "myapp login --token sbp_abc123", description: "Log in with a token" }
1203
+ * ])
1204
+ * )
1205
+ * ```
1206
+ *
1207
+ * @since 4.0.0
1208
+ * @category combinators
1209
+ */
1210
+ <const Name extends string, Input, E, R>(self: Command<Name, Input, E, R>, examples: ReadonlyArray<Command.Example>): Command<Name, Input, E, R>
1211
+ } = dual(2, <const Name extends string, Input, E, R>(
1212
+ self: Command<Name, Input, E, R>,
1213
+ examples: ReadonlyArray<Command.Example>
1214
+ ) => makeCommand({ ...toImpl(self), examples }))
1215
+
929
1216
  /* ========================================================================== */
930
1217
  /* Providing Services */
931
1218
  /* ========================================================================== */
@@ -2,6 +2,9 @@
2
2
  * @since 4.0.0
3
3
  */
4
4
 
5
+ import type { NonEmptyReadonlyArray } from "../../Array.ts"
6
+ import type * as ServiceMap from "../../ServiceMap.ts"
7
+
5
8
  /**
6
9
  * Structured representation of help documentation for a command.
7
10
  * This data structure is independent of formatting, allowing for
@@ -9,11 +12,13 @@
9
12
  *
10
13
  * @example
11
14
  * ```ts
15
+ * import { ServiceMap } from "effect"
12
16
  * import type * as HelpDoc from "effect/unstable/cli/HelpDoc"
13
17
  *
14
18
  * const deployCommandHelp: HelpDoc.HelpDoc = {
15
19
  * description: "Deploy your application to the cloud",
16
20
  * usage: "myapp deploy [options] <target>",
21
+ * annotations: ServiceMap.empty(),
17
22
  * flags: [
18
23
  * {
19
24
  * name: "verbose",
@@ -62,6 +67,11 @@ export interface HelpDoc {
62
67
  */
63
68
  readonly flags: ReadonlyArray<FlagDoc>
64
69
 
70
+ /**
71
+ * Custom command annotations.
72
+ */
73
+ readonly annotations: ServiceMap.ServiceMap<never>
74
+
65
75
  /**
66
76
  * List of positional arguments for this command
67
77
  */
@@ -70,7 +80,30 @@ export interface HelpDoc {
70
80
  /**
71
81
  * Optional list of subcommands if this is a parent command
72
82
  */
73
- readonly subcommands?: ReadonlyArray<SubcommandDoc>
83
+ readonly subcommands?: ReadonlyArray<SubcommandGroupDoc>
84
+
85
+ /**
86
+ * Optional concrete usage examples for the command
87
+ */
88
+ readonly examples?: ReadonlyArray<ExampleDoc>
89
+ }
90
+
91
+ /**
92
+ * Documentation for a command usage example
93
+ *
94
+ * @since 4.0.0
95
+ * @category models
96
+ */
97
+ export interface ExampleDoc {
98
+ /**
99
+ * Command line invocation example
100
+ */
101
+ readonly command: string
102
+
103
+ /**
104
+ * Optional explanation for the example
105
+ */
106
+ readonly description?: string | undefined
74
107
  }
75
108
 
76
109
  /**
@@ -132,15 +165,18 @@ export interface FlagDoc {
132
165
  *
133
166
  * @example
134
167
  * ```ts
168
+ * import { ServiceMap } from "effect"
135
169
  * import type { HelpDoc } from "effect/unstable/cli"
136
170
  *
137
171
  * const deploySubcommand: HelpDoc.SubcommandDoc = {
138
172
  * name: "deploy",
173
+ * shortDescription: "Deploy app",
139
174
  * description: "Deploy the application to the cloud"
140
175
  * }
141
176
  *
142
177
  * const buildSubcommand: HelpDoc.SubcommandDoc = {
143
178
  * name: "build",
179
+ * shortDescription: undefined,
144
180
  * description: "Build the application for production"
145
181
  * }
146
182
  *
@@ -148,8 +184,12 @@ export interface FlagDoc {
148
184
  * const mainCommandHelp: HelpDoc.HelpDoc = {
149
185
  * description: "Cloud deployment tool",
150
186
  * usage: "myapp <command> [options]",
187
+ * annotations: ServiceMap.empty(),
151
188
  * flags: [],
152
- * subcommands: [deploySubcommand, buildSubcommand]
189
+ * subcommands: [{
190
+ * group: undefined,
191
+ * commands: [deploySubcommand, buildSubcommand]
192
+ * }]
153
193
  * }
154
194
  * ```
155
195
  *
@@ -162,17 +202,42 @@ export interface SubcommandDoc {
162
202
  */
163
203
  readonly name: string
164
204
 
205
+ /**
206
+ * Optional short description of what the subcommand does.
207
+ */
208
+ readonly shortDescription: string | undefined
209
+
165
210
  /**
166
211
  * Brief description of what the subcommand does
167
212
  */
168
213
  readonly description: string
169
214
  }
170
215
 
216
+ /**
217
+ * Documentation for a grouped subcommand listing
218
+ *
219
+ * @since 4.0.0
220
+ * @category models
221
+ */
222
+ export interface SubcommandGroupDoc {
223
+ /**
224
+ * Group name used in help output.
225
+ * Undefined means the default ungrouped section.
226
+ */
227
+ readonly group: string | undefined
228
+
229
+ /**
230
+ * Subcommands in this group.
231
+ */
232
+ readonly commands: NonEmptyReadonlyArray<SubcommandDoc>
233
+ }
234
+
171
235
  /**
172
236
  * Documentation for a positional argument
173
237
  *
174
238
  * @example
175
239
  * ```ts
240
+ * import { ServiceMap } from "effect"
176
241
  * import type { HelpDoc } from "effect/unstable/cli"
177
242
  *
178
243
  * const sourceArg: HelpDoc.ArgDoc = {
@@ -195,6 +260,7 @@ export interface SubcommandDoc {
195
260
  * const copyCommandHelp: HelpDoc.HelpDoc = {
196
261
  * description: "Copy files from source to destination",
197
262
  * usage: "copy <source> [files...]",
263
+ * annotations: ServiceMap.empty(),
198
264
  * flags: [],
199
265
  * args: [sourceArg, filesArg]
200
266
  * }
@@ -5,13 +5,14 @@
5
5
  * Internal implementation details for CLI commands.
6
6
  * Public API is in ../Command.ts
7
7
  */
8
+ import * as Arr from "../../../Array.ts"
8
9
  import * as Effect from "../../../Effect.ts"
9
10
  import { YieldableProto } from "../../../internal/core.ts"
10
11
  import { pipeArguments } from "../../../Pipeable.ts"
11
12
  import * as Predicate from "../../../Predicate.ts"
12
13
  import * as ServiceMap from "../../../ServiceMap.ts"
13
14
  import * as CliError from "../CliError.ts"
14
- import type { ArgDoc, FlagDoc, HelpDoc, SubcommandDoc } from "../HelpDoc.ts"
15
+ import type { ArgDoc, ExampleDoc, FlagDoc, HelpDoc, SubcommandGroupDoc } from "../HelpDoc.ts"
15
16
  import * as Param from "../Param.ts"
16
17
  import * as Primitive from "../Primitive.ts"
17
18
  import { type ConfigInternal, reconstructTree } from "./config.ts"
@@ -22,6 +23,11 @@ import { type ConfigInternal, reconstructTree } from "./config.ts"
22
23
 
23
24
  import type { Command, CommandContext, Environment, ParsedTokens } from "../Command.ts"
24
25
 
26
+ interface SubcommandGroup {
27
+ readonly group: string | undefined
28
+ readonly commands: Arr.NonEmptyReadonlyArray<Command<any, unknown, unknown, unknown>>
29
+ }
30
+
25
31
  /**
26
32
  * Internal implementation interface with all the machinery.
27
33
  * Use toImpl() to access from internal code.
@@ -29,6 +35,7 @@ import type { Command, CommandContext, Environment, ParsedTokens } from "../Comm
29
35
  export interface CommandInternal<Name extends string, Input, E, R> extends Command<Name, Input, E, R> {
30
36
  readonly config: ConfigInternal
31
37
  readonly service: ServiceMap.Service<CommandContext<Name>, Input>
38
+ readonly annotations: ServiceMap.ServiceMap<never>
32
39
  readonly parse: (input: ParsedTokens) => Effect.Effect<Input, CliError.CliError, Environment>
33
40
  readonly handle: (
34
41
  input: Input,
@@ -80,8 +87,11 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
80
87
  readonly name: Name
81
88
  readonly config: ConfigInternal
82
89
  readonly service?: ServiceMap.Service<CommandContext<Name>, Input> | undefined
90
+ readonly annotations?: ServiceMap.ServiceMap<never> | undefined
83
91
  readonly description?: string | undefined
84
- readonly subcommands?: ReadonlyArray<Command<any, unknown, unknown, unknown>> | undefined
92
+ readonly shortDescription?: string | undefined
93
+ readonly examples?: ReadonlyArray<Command.Example> | undefined
94
+ readonly subcommands?: ReadonlyArray<SubcommandGroup> | undefined
85
95
  readonly parse?: ((input: ParsedTokens) => Effect.Effect<Input, CliError.CliError, Environment>) | undefined
86
96
  readonly handle?:
87
97
  | ((input: Input, commandPath: ReadonlyArray<string>) => Effect.Effect<void, E, R | Environment>)
@@ -89,6 +99,8 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
89
99
  }): Command<Name, Input, E, R> => {
90
100
  const service = options.service ?? ServiceMap.Service<CommandContext<Name>, Input>(`${TypeId}/${options.name}`)
91
101
  const config = options.config
102
+ const annotations = options.annotations ?? ServiceMap.empty()
103
+ const subcommands = options.subcommands ?? []
92
104
 
93
105
  const handle = (
94
106
  input: Input,
@@ -123,8 +135,7 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
123
135
  }
124
136
 
125
137
  let usage = commandPath.length > 0 ? commandPath.join(" ") : options.name
126
- const subcommands = options.subcommands ?? []
127
- if (subcommands.length > 0) {
138
+ if (subcommands.some((group) => group.commands.length > 0)) {
128
139
  usage += " <subcommand>"
129
140
  }
130
141
  usage += " [flags]"
@@ -147,24 +158,38 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
147
158
  }
148
159
  }
149
160
 
150
- const subcommandDocs: Array<SubcommandDoc> = subcommands.map((sub) => ({
151
- name: sub.name,
152
- description: sub.description ?? ""
153
- }))
161
+ const subcommandDocs: Array<SubcommandGroupDoc> = []
162
+
163
+ for (const group of subcommands) {
164
+ subcommandDocs.push({
165
+ group: group.group,
166
+ commands: Arr.map(group.commands, (subcommand) => ({
167
+ name: subcommand.name,
168
+ shortDescription: subcommand.shortDescription,
169
+ description: subcommand.description ?? ""
170
+ }))
171
+ })
172
+ }
173
+
174
+ const examples: ReadonlyArray<ExampleDoc> = options.examples ?? []
154
175
 
155
176
  return {
156
177
  description: options.description ?? "",
157
178
  usage,
158
179
  flags,
180
+ annotations,
159
181
  ...(args.length > 0 && { args }),
160
- ...(subcommandDocs.length > 0 && { subcommands: subcommandDocs })
182
+ ...(subcommandDocs.length > 0 && { subcommands: subcommandDocs }),
183
+ ...(examples.length > 0 && { examples })
161
184
  }
162
185
  }
163
186
 
164
187
  return Object.assign(Object.create(Proto), {
165
188
  [TypeId]: TypeId,
166
189
  name: options.name,
167
- subcommands: options.subcommands ?? [],
190
+ examples: options.examples ?? [],
191
+ annotations,
192
+ subcommands,
168
193
  config,
169
194
  service,
170
195
  parse,
@@ -172,6 +197,9 @@ export const makeCommand = <const Name extends string, Input, E, R>(options: {
172
197
  buildHelpDoc,
173
198
  ...(Predicate.isNotUndefined(options.description)
174
199
  ? { description: options.description }
200
+ : {}),
201
+ ...(Predicate.isNotUndefined(options.shortDescription)
202
+ ? { shortDescription: options.shortDescription }
175
203
  : {})
176
204
  })
177
205
  }
@@ -255,7 +283,15 @@ export const getHelpForCommandPath = <Name extends string, Input, E, R>(
255
283
  // Navigate through the command path to find the target command
256
284
  for (let i = 1; i < commandPath.length; i++) {
257
285
  const subcommandName = commandPath[i]
258
- const subcommand = currentCommand.subcommands.find((sub) => sub.name === subcommandName)
286
+ let subcommand: Command.Any | undefined = undefined
287
+
288
+ for (const group of currentCommand.subcommands) {
289
+ subcommand = group.commands.find((sub) => sub.name === subcommandName)
290
+ if (subcommand) {
291
+ break
292
+ }
293
+ }
294
+
259
295
  if (subcommand) {
260
296
  currentCommand = subcommand
261
297
  }