@cmodjs/core 0.2.3 → 1.0.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 (273) hide show
  1. package/docs/commands.md +58 -10
  2. package/docs/dependencies.md +12 -12
  3. package/docs/hooks.md +41 -28
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +9 -7
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +200 -174
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +248 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +66 -23
  13. package/docs/ui.md +108 -76
  14. package/jobs/check.d.ts.map +1 -1
  15. package/jobs/check.js +5 -2
  16. package/jobs/context.d.ts +7 -8
  17. package/jobs/context.d.ts.map +1 -1
  18. package/jobs/context.js +23 -73
  19. package/jobs/permissions/decide-permission.d.ts +5 -3
  20. package/jobs/permissions/decide-permission.d.ts.map +1 -1
  21. package/jobs/permissions/decide-permission.js +6 -4
  22. package/jobs/permissions/find-project-scope.d.ts +1 -1
  23. package/jobs/permissions/find-project-scope.d.ts.map +1 -1
  24. package/jobs/permissions/find-project-scope.js +8 -11
  25. package/jobs/permissions/match-target.d.ts +2 -2
  26. package/jobs/permissions/match-target.d.ts.map +1 -1
  27. package/jobs/permissions.d.ts +1 -1
  28. package/jobs/permissions.d.ts.map +1 -1
  29. package/jobs/permissions.js +6 -4
  30. package/jobs/program.d.ts.map +1 -1
  31. package/jobs/program.js +8 -3
  32. package/jobs/prompt.d.ts +4 -2
  33. package/jobs/prompt.d.ts.map +1 -1
  34. package/jobs/prompt.js +16 -15
  35. package/jobs/schedule.d.ts +2 -2
  36. package/jobs/schedule.d.ts.map +1 -1
  37. package/jobs/schedule.js +9 -9
  38. package/jobs/slash-command.d.ts +4 -4
  39. package/jobs/slash-command.d.ts.map +1 -1
  40. package/jobs/slash-command.js +14 -9
  41. package/jobs/status-line.d.ts +1 -11
  42. package/jobs/status-line.d.ts.map +1 -1
  43. package/jobs/status-line.js +8 -12
  44. package/jobs/tool.d.ts +2 -2
  45. package/jobs/tool.d.ts.map +1 -1
  46. package/jobs/tool.js +15 -9
  47. package/mod.d.ts +154 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +94 -3
  50. package/options.d.ts +2 -2
  51. package/options.js +19 -19
  52. package/package.json +3 -3
  53. package/records.d.ts +10 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +28 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +18 -6
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +66 -0
  61. package/runtime/bound.d.ts +4 -0
  62. package/runtime/bound.d.ts.map +1 -0
  63. package/runtime/bound.js +9 -0
  64. package/runtime/claude.d.ts +3 -3
  65. package/runtime/claude.d.ts.map +1 -1
  66. package/runtime/conversations.d.ts +16 -0
  67. package/runtime/conversations.d.ts.map +1 -0
  68. package/runtime/conversations.js +291 -0
  69. package/runtime/deadline.d.ts +0 -1
  70. package/runtime/deadline.d.ts.map +1 -1
  71. package/runtime/deadline.js +1 -1
  72. package/runtime/dependencies.d.ts +2 -0
  73. package/runtime/dependencies.d.ts.map +1 -1
  74. package/runtime/dependencies.js +9 -0
  75. package/runtime/engine.d.ts +17 -0
  76. package/runtime/engine.d.ts.map +1 -0
  77. package/runtime/engine.js +10 -0
  78. package/runtime/grants.d.ts +4 -0
  79. package/runtime/grants.d.ts.map +1 -1
  80. package/runtime/grants.js +63 -33
  81. package/runtime/hooks.d.ts +5 -4
  82. package/runtime/hooks.d.ts.map +1 -1
  83. package/runtime/hooks.js +10 -5
  84. package/runtime/installer.d.ts.map +1 -1
  85. package/runtime/installer.js +11 -9
  86. package/runtime/lifecycle.d.ts +3 -1
  87. package/runtime/lifecycle.d.ts.map +1 -1
  88. package/runtime/lifecycle.js +317 -186
  89. package/runtime/metadata.d.ts +3 -1
  90. package/runtime/metadata.d.ts.map +1 -1
  91. package/runtime/metadata.js +21 -11
  92. package/runtime/options.d.ts +3 -1
  93. package/runtime/options.d.ts.map +1 -1
  94. package/runtime/options.js +4 -4
  95. package/runtime/programs.d.ts +8 -0
  96. package/runtime/programs.d.ts.map +1 -1
  97. package/runtime/programs.js +39 -1
  98. package/runtime/router.d.ts +2 -1
  99. package/runtime/router.d.ts.map +1 -1
  100. package/runtime/router.js +11 -3
  101. package/runtime/sessions.d.ts +36 -0
  102. package/runtime/sessions.d.ts.map +1 -0
  103. package/runtime/sessions.js +235 -0
  104. package/runtime/state.d.ts +6 -2
  105. package/runtime/state.d.ts.map +1 -1
  106. package/runtime/state.js +46 -13
  107. package/runtime/toaster.d.ts +31 -0
  108. package/runtime/toaster.d.ts.map +1 -0
  109. package/runtime/toaster.js +176 -0
  110. package/runtime/ui.d.ts +6 -17
  111. package/runtime/ui.d.ts.map +1 -1
  112. package/runtime/ui.js +36 -112
  113. package/session/context/context.d.ts +10 -0
  114. package/session/context/context.d.ts.map +1 -0
  115. package/session/context/context.js +2 -0
  116. package/session/context/turn/message/document.d.ts +7 -0
  117. package/session/context/turn/message/document.d.ts.map +1 -0
  118. package/session/context/turn/message/document.js +1 -0
  119. package/session/context/turn/message/message.d.ts +54 -0
  120. package/session/context/turn/message/message.d.ts.map +1 -0
  121. package/session/context/turn/message/message.js +137 -0
  122. package/session/context/turn/message/note.d.ts +8 -0
  123. package/session/context/turn/message/note.d.ts.map +1 -0
  124. package/session/context/turn/message/note.js +1 -0
  125. package/session/context/turn/message/notice.d.ts +6 -0
  126. package/session/context/turn/message/notice.d.ts.map +1 -0
  127. package/session/context/turn/message/notice.js +1 -0
  128. package/session/context/turn/message/prompt.d.ts +8 -0
  129. package/session/context/turn/message/prompt.d.ts.map +1 -0
  130. package/session/context/turn/message/prompt.js +1 -0
  131. package/session/context/turn/message/reply.d.ts +6 -0
  132. package/session/context/turn/message/reply.d.ts.map +1 -0
  133. package/session/context/turn/message/reply.js +1 -0
  134. package/session/context/turn/message/sender.d.ts +14 -0
  135. package/session/context/turn/message/sender.d.ts.map +1 -0
  136. package/session/context/turn/message/sender.js +30 -0
  137. package/session/context/turn/message/skill.d.ts +7 -0
  138. package/session/context/turn/message/skill.d.ts.map +1 -0
  139. package/session/context/turn/message/skill.js +1 -0
  140. package/session/context/turn/message/tool-call.d.ts +9 -0
  141. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  142. package/session/context/turn/message/tool-call.js +1 -0
  143. package/session/context/turn/message/tool-result.d.ts +8 -0
  144. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  145. package/session/context/turn/message/tool-result.js +1 -0
  146. package/session/context/turn/turn.d.ts +12 -0
  147. package/session/context/turn/turn.d.ts.map +1 -0
  148. package/session/context/turn/turn.js +2 -0
  149. package/session/conversation.d.ts +64 -0
  150. package/session/conversation.d.ts.map +1 -0
  151. package/session/conversation.js +123 -0
  152. package/session/goal/goal.d.ts +10 -0
  153. package/session/goal/goal.d.ts.map +1 -0
  154. package/session/goal/goal.js +1 -0
  155. package/session/goal/task.d.ts +16 -0
  156. package/session/goal/task.d.ts.map +1 -0
  157. package/session/goal/task.js +3 -0
  158. package/session/session.d.ts +99 -0
  159. package/session/session.d.ts.map +1 -0
  160. package/session/session.js +1 -0
  161. package/src/jobs/check.ts +7 -4
  162. package/src/jobs/context.ts +24 -82
  163. package/src/jobs/permissions/decide-permission.ts +10 -8
  164. package/src/jobs/permissions/find-project-scope.ts +8 -14
  165. package/src/jobs/permissions/match-target.ts +3 -3
  166. package/src/jobs/permissions.ts +8 -6
  167. package/src/jobs/program.ts +7 -3
  168. package/src/jobs/prompt.ts +21 -24
  169. package/src/jobs/schedule.ts +14 -14
  170. package/src/jobs/slash-command.ts +20 -14
  171. package/src/jobs/status-line.ts +9 -21
  172. package/src/jobs/tool.ts +17 -11
  173. package/src/mod.ts +209 -97
  174. package/src/options.ts +22 -22
  175. package/src/records.ts +35 -23
  176. package/src/register.ts +18 -6
  177. package/src/runtime/announce.ts +74 -0
  178. package/src/runtime/bound.ts +12 -0
  179. package/src/runtime/claude.ts +5 -3
  180. package/src/runtime/conversations.ts +302 -0
  181. package/src/runtime/deadline.ts +2 -2
  182. package/src/runtime/dependencies.ts +12 -0
  183. package/src/runtime/engine.ts +22 -0
  184. package/src/runtime/grants.ts +66 -31
  185. package/src/runtime/hooks.ts +19 -10
  186. package/src/runtime/installer.ts +10 -9
  187. package/src/runtime/lifecycle.ts +313 -181
  188. package/src/runtime/metadata.ts +33 -11
  189. package/src/runtime/options.ts +19 -4
  190. package/src/runtime/programs.ts +40 -1
  191. package/src/runtime/router.ts +11 -4
  192. package/src/runtime/sessions.ts +242 -0
  193. package/src/runtime/state.ts +50 -14
  194. package/src/runtime/toaster.ts +218 -0
  195. package/src/runtime/ui.ts +42 -139
  196. package/src/session/context/context.ts +12 -0
  197. package/src/session/context/turn/message/document.ts +8 -0
  198. package/src/session/context/turn/message/message.ts +165 -0
  199. package/src/session/context/turn/message/note.ts +9 -0
  200. package/src/session/context/turn/message/notice.ts +7 -0
  201. package/src/session/context/turn/message/prompt.ts +9 -0
  202. package/src/session/context/turn/message/reply.ts +7 -0
  203. package/src/session/context/turn/message/sender.ts +38 -0
  204. package/src/session/context/turn/message/skill.ts +8 -0
  205. package/src/session/context/turn/message/tool-call.ts +10 -0
  206. package/src/session/context/turn/message/tool-result.ts +9 -0
  207. package/src/session/context/turn/turn.ts +14 -0
  208. package/src/session/conversation.ts +169 -0
  209. package/src/session/goal/goal.ts +9 -0
  210. package/src/session/goal/task.ts +19 -0
  211. package/src/session/session.ts +94 -0
  212. package/src/testing/fake-claude.ts +23 -18
  213. package/src/testing/fake-elements.ts +3 -2
  214. package/src/testing/fake-files.ts +1 -1
  215. package/src/testing/fake-sessions.ts +203 -0
  216. package/src/testing.ts +112 -33
  217. package/src/ui/components.ts +3 -3
  218. package/src/ui/elements.ts +22 -8
  219. package/src/ui/markdown.ts +22 -26
  220. package/src/ui/places.ts +49 -0
  221. package/src/utils/call-effects.ts +3 -3
  222. package/src/utils/changelog.ts +32 -0
  223. package/src/utils/matcher.ts +37 -0
  224. package/src/utils/metadata.ts +17 -15
  225. package/src/utils/parse-shell.ts +24 -16
  226. package/src/utils/paths.ts +15 -1
  227. package/src/utils/text.ts +4 -0
  228. package/testing/fake-claude.d.ts +4 -2
  229. package/testing/fake-claude.d.ts.map +1 -1
  230. package/testing/fake-claude.js +18 -12
  231. package/testing/fake-elements.js +4 -2
  232. package/testing/fake-files.js +1 -1
  233. package/testing/fake-sessions.d.ts +74 -0
  234. package/testing/fake-sessions.d.ts.map +1 -0
  235. package/testing/fake-sessions.js +170 -0
  236. package/testing.d.ts +33 -13
  237. package/testing.d.ts.map +1 -1
  238. package/testing.js +92 -21
  239. package/ui/components.d.ts +2 -3
  240. package/ui/components.d.ts.map +1 -1
  241. package/ui/components.js +3 -3
  242. package/ui/elements.d.ts.map +1 -1
  243. package/ui/elements.js +19 -9
  244. package/ui/markdown.d.ts +35 -33
  245. package/ui/markdown.d.ts.map +1 -1
  246. package/ui/markdown.js +14 -16
  247. package/ui/places.d.ts +43 -0
  248. package/ui/places.d.ts.map +1 -0
  249. package/ui/places.js +31 -0
  250. package/utils/call-effects.d.ts +1 -1
  251. package/utils/call-effects.d.ts.map +1 -1
  252. package/utils/call-effects.js +2 -2
  253. package/utils/changelog.d.ts +9 -0
  254. package/utils/changelog.d.ts.map +1 -0
  255. package/utils/changelog.js +29 -0
  256. package/utils/matcher.d.ts +13 -0
  257. package/utils/matcher.d.ts.map +1 -0
  258. package/utils/matcher.js +32 -0
  259. package/utils/metadata.d.ts +3 -3
  260. package/utils/metadata.js +18 -14
  261. package/utils/parse-shell.d.ts +1 -0
  262. package/utils/parse-shell.d.ts.map +1 -1
  263. package/utils/parse-shell.js +22 -13
  264. package/utils/paths.d.ts +2 -0
  265. package/utils/paths.d.ts.map +1 -1
  266. package/utils/paths.js +13 -1
  267. package/utils/text.d.ts +1 -0
  268. package/utils/text.d.ts.map +1 -1
  269. package/utils/text.js +3 -0
  270. package/src/ui/slots.ts +0 -36
  271. package/ui/slots.d.ts +0 -208
  272. package/ui/slots.d.ts.map +0 -1
  273. package/ui/slots.js +0 -19
package/src/mod.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import type {
2
- AgentSpawnArgs,
3
- AgentSpawnResult,
4
2
  AskOptions,
5
3
  ClassicHookInputs,
6
4
  CmodDependencies,
7
- FsEntry,
8
- FsStat,
9
5
  FsStatOptions,
10
6
  HookStream,
11
7
  HttpInit,
@@ -21,26 +17,45 @@ import type {
21
17
  ProcessSpawnRequest,
22
18
  ProcessSpawnResult,
23
19
  RenderElement,
24
- SessionMessage,
25
- Timer,
26
- ToastOptions,
20
+ Settings,
27
21
  UiScrollArgs,
28
22
  UiScrollResult,
29
23
  } from 'claude-code'
30
24
  import type { Step } from './installer.js'
31
25
  import { checkOptions, type Option, type Options, type OptionValues } from './options.js'
32
- import type { FlagPermission, ListPermission } from './records.js'
33
- import type { Claude } from './runtime/claude.js'
34
- import type { RoutedEvent } from './runtime/hooks.js'
35
- import type { RoutedHook } from './runtime/router.js'
36
- import type { ToolCalls } from './runtime/tool-calls.js'
26
+ import { bound } from './runtime/bound.js'
27
+ import { Mod as modSender } from './session/context/turn/message/sender.js'
28
+ import type { Sessions } from './session/session.js'
37
29
  import type { Pane } from './ui/define-pane.js'
38
- import type { Slot, SlotProps } from './ui/slots.js'
30
+ import type { Place, PlaceProps } from './ui/places.js'
31
+ import type { Matcher } from './utils/matcher.js'
39
32
 
40
- export type ProgressStep = {
41
- readonly done: number
42
- readonly total: number
43
- readonly label?: string
33
+ export type Disposable = {
34
+ dispose(): void
35
+ }
36
+
37
+ export type ToastId = string | number
38
+
39
+ export type ToastType = 'default' | 'success' | 'info' | 'warning' | 'error' | 'loading'
40
+
41
+ export type ToastOptions = {
42
+ readonly id?: ToastId
43
+ readonly description?: string | RenderElement
44
+ readonly action?: { readonly label: string; readonly onClick: () => unknown }
45
+ readonly duration?: number
46
+ }
47
+
48
+ type ToastMessage<Value> = string | ((value: Value) => string)
49
+
50
+ export type Toast = {
51
+ (message: string, options?: ToastOptions): ToastId
52
+ success(message: string, options?: ToastOptions): ToastId
53
+ info(message: string, options?: ToastOptions): ToastId
54
+ warning(message: string, options?: ToastOptions): ToastId
55
+ error(message: string, options?: ToastOptions): ToastId
56
+ loading(message: string, options?: ToastOptions): ToastId
57
+ promise<T>(task: Promise<T> | (() => Promise<T>), messages: { readonly loading: string; readonly success: ToastMessage<T>; readonly error: ToastMessage<unknown> }): { unwrap(): Promise<T> }
58
+ dismiss(id?: ToastId): void
44
59
  }
45
60
 
46
61
  export type PaneHandle = {
@@ -50,30 +65,47 @@ export type PaneHandle = {
50
65
  readonly isOpen: boolean
51
66
  }
52
67
 
53
- export type { Claude, RoutedEvent, RoutedHook, ToolCalls }
54
- export { longestMs } from './runtime/deadline.js'
55
- export { notInstalled } from './runtime/dependencies.js'
56
- export { messageOf } from './utils/text.js'
68
+ export const SessionStart = 'SessionStart'
69
+ export const SessionEnd = 'SessionEnd'
70
+ export const UserPromptSubmit = 'UserPromptSubmit'
71
+ export const InstructionsLoaded = 'InstructionsLoaded'
72
+ export const PreToolUse = 'PreToolUse'
73
+ export const PermissionRequest = 'PermissionRequest'
74
+ export const PermissionDenied = 'PermissionDenied'
75
+ export const PostToolUse = 'PostToolUse'
76
+ export const PostToolUseFailure = 'PostToolUseFailure'
77
+ export const PostToolBatch = 'PostToolBatch'
78
+ export const SubagentStart = 'SubagentStart'
79
+ export const SubagentStop = 'SubagentStop'
80
+ export const Notification = 'Notification'
81
+ export const PreCompact = 'PreCompact'
82
+ export const Stop = 'Stop'
83
+ export const StopFailure = 'StopFailure'
84
+ export const CwdChanged = 'CwdChanged'
85
+ export const FileChanged = 'FileChanged'
57
86
 
58
- export type ModEvent =
59
- | 'SessionStart'
60
- | 'SessionEnd'
61
- | 'UserPromptSubmit'
62
- | 'InstructionsLoaded'
63
- | 'PreToolUse'
64
- | 'PermissionRequest'
65
- | 'PermissionDenied'
66
- | 'PostToolUse'
67
- | 'PostToolUseFailure'
68
- | 'PostToolBatch'
69
- | 'SubagentStart'
70
- | 'SubagentStop'
71
- | 'Notification'
72
- | 'PreCompact'
73
- | 'Stop'
74
- | 'StopFailure'
75
- | 'CwdChanged'
76
- | 'FileChanged'
87
+ export const hooks = [
88
+ SessionStart,
89
+ SessionEnd,
90
+ UserPromptSubmit,
91
+ InstructionsLoaded,
92
+ PreToolUse,
93
+ PermissionRequest,
94
+ PermissionDenied,
95
+ PostToolUse,
96
+ PostToolUseFailure,
97
+ PostToolBatch,
98
+ SubagentStart,
99
+ SubagentStop,
100
+ Notification,
101
+ PreCompact,
102
+ Stop,
103
+ StopFailure,
104
+ CwdChanged,
105
+ FileChanged,
106
+ ] as const
107
+
108
+ export type Hook = (typeof hooks)[number]
77
109
 
78
110
  export type HookAnswer = {
79
111
  continue?: boolean
@@ -83,7 +115,7 @@ export type HookAnswer = {
83
115
  decision?: 'block'
84
116
  reason?: string
85
117
  hookSpecificOutput?: {
86
- hookEventName?: ModEvent
118
+ hookEventName?: Hook
87
119
  additionalContext?: string
88
120
  permissionDecision?: 'allow' | 'deny' | 'ask'
89
121
  permissionDecisionReason?: string
@@ -119,20 +151,14 @@ type HookInputs = Omit<ClassicHookInputs, 'PreToolUse' | 'PostToolUse' | 'PostTo
119
151
  CwdChanged: Omit<ClassicHookInputs['CwdChanged'], 'transcript_path'>
120
152
  }
121
153
 
122
- export type HookInput<E extends ModEvent> = HookInputs[E]
154
+ export type HookInput<H extends Hook> = HookInputs[H]
123
155
 
124
- export type ModHook<E extends ModEvent> = (input: HookInput<E>) => HookAnswer | void | Promise<HookAnswer | void>
156
+ export type HookListener<H extends Hook> = (input: HookInput<H>) => HookAnswer | void | Promise<HookAnswer | void>
125
157
 
126
158
  export type HookOptions = {
127
159
  readonly timeoutMs?: number
128
160
  }
129
161
 
130
- export type ScrollArgs = Omit<UiScrollArgs, 'to'> & { readonly to: Exclude<UiScrollArgs['to'], { requestId: string }> }
131
-
132
- export type ModClaude = Claude & {
133
- on<N extends RoutedEvent>(event: N, hook: RoutedHook<N>): void
134
- }
135
-
136
162
  export type Metadata = Readonly<Record<string, string>>
137
163
 
138
164
  export type FileMatch = {
@@ -142,71 +168,136 @@ export type FileMatch = {
142
168
  readonly error?: string
143
169
  }
144
170
 
145
- type SessionMessages = {
146
- (): Promise<SessionMessage[]>
147
- (args: { readonly agentId: string }): Promise<SessionMessage[] | { readonly deny: string }>
171
+ export type Entry = {
172
+ readonly name: string
173
+ readonly isFile: boolean
174
+ readonly isFolder: boolean
175
+ readonly isLink: boolean
176
+ readonly size: number
177
+ readonly modifiedAt: number
148
178
  }
149
179
 
150
- export type Mod<State extends object = Record<never, never>, Declared extends Options = Options> = {
151
- readonly name: string
152
- readonly state: Readonly<State>
153
- readonly options: OptionValues<Declared>
154
- readonly dataFolder: string
155
- on<E extends ModEvent>(event: E, hook: ModHook<E>, options?: HookOptions): void
156
- use<Handle>(job: Job<Handle, State>): Handle
157
- every(ms: number, hook: () => unknown): Timer
158
- readonly ui: {
159
- pane(pane: Pane<State>): PaneHandle
160
- render<S extends Slot>(slot: S, Component: (props: SlotProps<S>) => RenderElement): void
161
- toast(text: string, options?: ToastOptions): void
162
- progress<T>(title: string, task: (report: (step: ProgressStep) => void) => Promise<T>): Promise<T>
163
- ask(question: string, options?: readonly string[] | AskOptions): Promise<string>
164
- scroll(args: ScrollArgs): Promise<UiScrollResult>
165
- }
166
- readonly process: {
167
- run(argv: readonly string[], init?: ProcessRunInit): Promise<ProcessRunResult>
168
- spawn(argv: readonly string[], init?: Omit<ProcessSpawnRequest, 'argv'>): HookStream<ProcessSpawnChunk, ProcessSpawnResult>
169
- }
180
+ export type Stat = Omit<Entry, 'name'> & {
181
+ readonly realPath?: string
182
+ }
183
+
184
+ export type Allowed = {
185
+ allows(value: string): boolean
186
+ }
187
+
188
+ export type Permissions = {
189
+ readonly conversation: boolean
190
+ readonly prompt: boolean
191
+ readonly model: boolean
192
+ readonly tasks: boolean
193
+ readonly tools: boolean
194
+ readonly approve: boolean
195
+ readonly network: Allowed
196
+ readonly run: Allowed
197
+ readonly files: Allowed
198
+ readonly config: Allowed
199
+ }
200
+
201
+ export type SettingsFile = {
202
+ read(): Promise<Settings>
203
+ update(values: Readonly<Record<string, unknown>>): Promise<void>
204
+ }
205
+
206
+ export type Imports = {
207
+ readonly sessions: Sessions
208
+ readonly settings: { readonly user: SettingsFile; readonly project: SettingsFile; readonly local: SettingsFile }
209
+ readonly model: { complete(request: ModelCompleteRequest, options?: ModelCompleteOptions): Promise<ModelCompleteResult> }
170
210
  readonly fs: {
171
211
  read(path: string): Promise<string>
172
212
  write(path: string, text: string): Promise<void>
173
- list(path?: string): Promise<FsEntry[]>
174
213
  exists(path: string): Promise<boolean>
175
- stat(path: string, options?: FsStatOptions): Promise<FsStat>
176
214
  find(glob: string | readonly string[]): Promise<readonly FileMatch[]>
215
+ list(path?: string): Promise<Entry[]>
216
+ stat(path: string, options?: FsStatOptions): Promise<Stat>
177
217
  }
178
218
  readonly metadata: {
179
219
  read(path: string): Promise<Metadata>
180
220
  update(path: string, change: (metadata: Record<string, string>) => void): Promise<void>
181
221
  }
182
- readonly http: {
183
- fetch(url: string, init?: HttpInit): Promise<HttpResponse>
222
+ readonly process: {
223
+ run(argv: readonly string[], init?: ProcessRunInit): Promise<ProcessRunResult>
224
+ spawn(argv: readonly string[], init?: Omit<ProcessSpawnRequest, 'argv'>): HookStream<ProcessSpawnChunk, ProcessSpawnResult>
225
+ }
226
+ readonly http: { fetch(url: string, init?: HttpInit): Promise<HttpResponse> }
227
+ }
228
+
229
+ export const sessions: Imports['sessions'] = {
230
+ get current() {
231
+ return bound().sessions.current
232
+ },
233
+ list: (options) => bound().sessions.list(options),
234
+ search: (text, options) => bound().sessions.search(text, options),
235
+ }
236
+
237
+ export const settings: Imports['settings'] = {
238
+ user: { read: () => bound().settings.user.read(), update: (values) => bound().settings.user.update(values) },
239
+ project: { read: () => bound().settings.project.read(), update: (values) => bound().settings.project.update(values) },
240
+ local: { read: () => bound().settings.local.read(), update: (values) => bound().settings.local.update(values) },
241
+ }
242
+
243
+ export const model: Imports['model'] = {
244
+ complete: (request, options) => bound().model.complete(request, options),
245
+ }
246
+
247
+ export const fs: Imports['fs'] = {
248
+ read: (path) => bound().fs.read(path),
249
+ write: (path, text) => bound().fs.write(path, text),
250
+ exists: (path) => bound().fs.exists(path),
251
+ find: (glob) => bound().fs.find(glob),
252
+ list: (path) => bound().fs.list(path),
253
+ stat: (path, options) => bound().fs.stat(path, options),
254
+ }
255
+
256
+ export const metadata: Imports['metadata'] = {
257
+ read: (path) => bound().metadata.read(path),
258
+ update: (path, change) => bound().metadata.update(path, change),
259
+ }
260
+
261
+ export const process: Imports['process'] = {
262
+ run: (argv, init) => bound().process.run(argv, init),
263
+ spawn: (argv, init) => bound().process.spawn(argv, init),
264
+ }
265
+
266
+ export const http: Imports['http'] = {
267
+ fetch: (url, init) => bound().http.fetch(url, init),
268
+ }
269
+
270
+ export type Mod<State extends object = Record<never, never>, Declared extends Options = Options> = {
271
+ readonly name: string
272
+ readonly folder: string
273
+ readonly dataFolder: string
274
+ readonly state: Readonly<State>
275
+ readonly options: OptionValues<Declared>
276
+ readonly permissions: Permissions
277
+ on<H extends Hook>(hook: H, listener: HookListener<H>, options?: HookOptions): Disposable
278
+ on<H extends Hook>(hook: H, matcher: Matcher<HookInput<H>>, listener: HookListener<H>, options?: HookOptions): Disposable
279
+ every(ms: number, listener: () => unknown): Disposable
280
+ after(ms: number, listener: () => unknown): Disposable
281
+ use<Handle>(job: Job<Handle, State>): Handle
282
+ log(text: string): void
283
+ readonly ui: {
284
+ pane(pane: Pane<State>): PaneHandle
285
+ render<P extends Place>(place: P, Component: (props: PlaceProps<P>) => RenderElement): Disposable
286
+ readonly toast: Toast
287
+ ask(question: string, options?: readonly string[] | AskOptions): Promise<string>
288
+ copy(text: string): Promise<void>
289
+ scroll(args: UiScrollArgs): Promise<UiScrollResult>
290
+ notice(callId: string, text: string | undefined): void
184
291
  }
185
292
  readonly settings: {
186
293
  page(page: Pane<State>): void
187
294
  open(pageId?: string): Promise<void>
188
295
  }
189
- readonly session: {
190
- messages: SessionMessages
191
- append(text: string): Promise<void>
192
- submit(text: string): Promise<void>
193
- }
194
- readonly agent: {
195
- spawn(args: AgentSpawnArgs): Promise<AgentSpawnResult>
196
- }
197
- readonly model: {
198
- complete(request: ModelCompleteRequest, options?: ModelCompleteOptions): Promise<ModelCompleteResult>
199
- }
200
- readonly permissions: {
201
- has(name: ListPermission, value: string): boolean
202
- has(name: FlagPermission): boolean
203
- }
204
- readonly projectRoot: string
205
- readonly cwd: string
206
296
  readonly dependencies: CmodDependencies
207
- readonly claude: ModClaude
208
297
  }
209
298
 
299
+ export const Mod = modSender
300
+
210
301
  type StateGroups = {
211
302
  readonly memory?: object
212
303
  readonly session?: object
@@ -224,14 +315,13 @@ export type ModDefinition<State extends StateGroups = Record<never, never>, Name
224
315
  readonly options?: Declared
225
316
  readonly api?: Name extends keyof CmodDependencies ? Api<CmodDependencies[Name], State> : { readonly [method: string]: (input: never, mod: Mod<State>) => unknown }
226
317
  readonly installer?: readonly Step<State>[]
227
- setup(mod: Mod<State, Declared>): void | Promise<void>
318
+ setup(mod: Mod<State, Declared>): unknown
228
319
  }
229
320
 
230
321
  export type JobContext<State extends object = Record<never, never>> = {
231
322
  readonly mod: Mod<State>
232
323
  announce(feature: string): void
233
- reserveName(kind: string, name: string, taken: string): void
234
- readonly toolCalls: ToolCalls
324
+ reserveName(type: string, name: string, taken: string): Disposable
235
325
  }
236
326
 
237
327
  export type Job<Handle, State extends object = Record<never, never>> = (context: JobContext<State>) => Handle
@@ -241,3 +331,25 @@ export function defineMod<State extends StateGroups = Record<never, never>, Name
241
331
  if (definition.options !== undefined) checkOptions(definition.name, definition.options)
242
332
  return definition
243
333
  }
334
+
335
+ export type { Matcher } from './utils/matcher.js'
336
+ export type { Place, PlaceProps } from './ui/places.js'
337
+ export { AbovePrompt, AskUserQuestion, CommandOutput, PromptHint, SessionMode, Spinner, ToolGroup, ToolProgress, TurnDuration } from './ui/places.js'
338
+ export { Session, type SessionEvent, type SessionPayload, type SessionScope, type SessionSend, type Sessions, type TaskStartArgs, type Usage } from './session/session.js'
339
+ export { ContextEnd, ContextStart, type Context } from './session/context/context.js'
340
+ export { TurnEnd, TurnStart, type Turn } from './session/context/turn/turn.js'
341
+ export { Message, type MessageType } from './session/context/turn/message/message.js'
342
+ export { Prompt } from './session/context/turn/message/prompt.js'
343
+ export { Note } from './session/context/turn/message/note.js'
344
+ export { Reply } from './session/context/turn/message/reply.js'
345
+ export { ToolCall } from './session/context/turn/message/tool-call.js'
346
+ export { ToolResult } from './session/context/turn/message/tool-result.js'
347
+ export { Skill } from './session/context/turn/message/skill.js'
348
+ export { Document } from './session/context/turn/message/document.js'
349
+ export { Notice } from './session/context/turn/message/notice.js'
350
+ export { Channel, Person, Schedule, Sdk, type Sender, type SenderType } from './session/context/turn/message/sender.js'
351
+ export { GoalChange, type Goal } from './session/goal/goal.js'
352
+ export { Task, TaskEnd, TaskStart } from './session/goal/task.js'
353
+ export { longestMs } from './runtime/deadline.js'
354
+ export { notInstalled } from './runtime/dependencies.js'
355
+ export { messageOf } from './utils/text.js'
package/src/options.ts CHANGED
@@ -1,11 +1,11 @@
1
- export type OptionKind = 'text' | 'secret' | 'number' | 'toggle' | 'choice' | 'folder' | 'file' | 'list'
1
+ export type OptionType = 'text' | 'secret' | 'number' | 'toggle' | 'choice' | 'folder' | 'file' | 'list'
2
2
 
3
3
  export type OptionValue = string | number | boolean | readonly string[]
4
4
 
5
5
  type Described = { readonly title: string; readonly description: string }
6
6
 
7
7
  export type Option<Value extends OptionValue = OptionValue> = Described & {
8
- readonly kind: OptionKind
8
+ readonly type: OptionType
9
9
  readonly default?: Value
10
10
  readonly min?: number
11
11
  readonly max?: number
@@ -27,17 +27,17 @@ export type UserConfigField = Described & {
27
27
  }
28
28
 
29
29
  export const option = {
30
- text: (field: Described & { readonly default?: string }): Option<string> => ({ ...field, kind: 'text' }),
31
- secret: (field: Described): Option<string> => ({ ...field, kind: 'secret' }),
32
- number: (field: Described & { readonly default?: number; readonly min?: number; readonly max?: number }): Option<number> => ({ ...field, kind: 'number' }),
33
- toggle: (field: Described & { readonly default?: boolean }): Option<boolean> => ({ ...field, kind: 'toggle', default: field.default ?? false }),
34
- choice: <const Choice extends string>(choices: readonly Choice[], field: Described & { readonly default?: Choice }): Option<Choice> => ({ ...field, kind: 'choice', choices }),
35
- folder: (field: Described & { readonly default?: string }): Option<string> => ({ ...field, kind: 'folder' }),
36
- file: (field: Described & { readonly default?: string }): Option<string> => ({ ...field, kind: 'file' }),
37
- list: (field: Described & { readonly default?: readonly string[] }): Option<readonly string[]> => ({ ...field, kind: 'list' }),
30
+ text: (field: Described & { readonly default?: string }): Option<string> => ({ ...field, type: 'text' }),
31
+ secret: (field: Described): Option<string> => ({ ...field, type: 'secret' }),
32
+ number: (field: Described & { readonly default?: number; readonly min?: number; readonly max?: number }): Option<number> => ({ ...field, type: 'number' }),
33
+ toggle: (field: Described & { readonly default?: boolean }): Option<boolean> => ({ ...field, type: 'toggle', default: field.default ?? false }),
34
+ choice: <const Choice extends string>(choices: readonly Choice[], field: Described & { readonly default?: Choice }): Option<Choice> => ({ ...field, type: 'choice', choices }),
35
+ folder: (field: Described & { readonly default?: string }): Option<string> => ({ ...field, type: 'folder' }),
36
+ file: (field: Described & { readonly default?: string }): Option<string> => ({ ...field, type: 'file' }),
37
+ list: (field: Described & { readonly default?: readonly string[] }): Option<readonly string[]> => ({ ...field, type: 'list' }),
38
38
  }
39
39
 
40
- const types: Readonly<Record<OptionKind, UserConfigField['type']>> = { text: 'string', secret: 'string', number: 'number', toggle: 'boolean', choice: 'string', folder: 'directory', file: 'file', list: 'string' }
40
+ const types: Readonly<Record<OptionType, UserConfigField['type']>> = { text: 'string', secret: 'string', number: 'number', toggle: 'boolean', choice: 'string', folder: 'directory', file: 'file', list: 'string' }
41
41
 
42
42
  const optionKey = /^[A-Za-z_]\w*$/
43
43
 
@@ -46,8 +46,8 @@ export function checkOptions(name: string, options: Options): Options {
46
46
  const at = `${name}: options.${key}`
47
47
  if (!optionKey.test(key)) throw new Error(`${at} is not an option name. Use letters, digits, and _, starting with a letter, as Claude Code passes each option on as CLAUDE_PLUGIN_OPTION_${key.toUpperCase()}.`)
48
48
  if (declared.title.trim() === '') throw new Error(`${at} needs a title, the label /config shows.`)
49
- if (declared.kind === 'choice' && (declared.choices ?? []).length === 0) throw new Error(`${at} is a choice with no choices. Give option.choice(['a', 'b'], { ... }).`)
50
- if (declared.kind === 'choice' && declared.default !== undefined && !(declared.choices ?? []).includes(declared.default as string)) throw new Error(`${at} defaults to ${String(declared.default)}, which is not one of its choices: ${(declared.choices ?? []).join(', ')}.`)
49
+ if (declared.type === 'choice' && (declared.choices ?? []).length === 0) throw new Error(`${at} is a choice with no choices. Give option.choice(['a', 'b'], { ... }).`)
50
+ if (declared.type === 'choice' && declared.default !== undefined && !(declared.choices ?? []).includes(declared.default as string)) throw new Error(`${at} defaults to ${String(declared.default)}, which is not one of its choices: ${(declared.choices ?? []).join(', ')}.`)
51
51
  }
52
52
  return options
53
53
  }
@@ -57,12 +57,12 @@ export function userConfigOf(options: Options): Record<string, UserConfigField>
57
57
  Object.entries(options).map(([key, declared]) => [
58
58
  key,
59
59
  {
60
- type: types[declared.kind],
60
+ type: types[declared.type],
61
61
  title: declared.title,
62
62
  description: declared.description,
63
- ...(declared.kind === 'secret' ? { sensitive: true } : {}),
64
- ...(declared.kind === 'list' ? { multiple: true } : {}),
65
- ...(declared.kind === 'choice' ? { options: declared.choices } : {}),
63
+ ...(declared.type === 'secret' ? { sensitive: true } : {}),
64
+ ...(declared.type === 'list' ? { multiple: true } : {}),
65
+ ...(declared.type === 'choice' ? { options: declared.choices } : {}),
66
66
  ...(declared.default === undefined ? {} : { default: declared.default }),
67
67
  ...(declared.min === undefined ? {} : { min: declared.min }),
68
68
  ...(declared.max === undefined ? {} : { max: declared.max }),
@@ -76,16 +76,16 @@ export function isUnset(value: unknown): boolean {
76
76
  }
77
77
 
78
78
  export function fitsOption(declared: Option, value: unknown): string | undefined {
79
- const kind = declared.kind
80
- if (kind === 'toggle') return typeof value === 'boolean' ? undefined : 'takes true or false'
81
- if (kind === 'number') {
79
+ const type = declared.type
80
+ if (type === 'toggle') return typeof value === 'boolean' ? undefined : 'takes true or false'
81
+ if (type === 'number') {
82
82
  if (typeof value !== 'number' || !Number.isFinite(value)) return 'takes a number'
83
83
  if (declared.min !== undefined && value < declared.min) return `takes ${declared.min} or more`
84
84
  if (declared.max !== undefined && value > declared.max) return `takes ${declared.max} or less`
85
85
  return undefined
86
86
  }
87
- if (kind === 'list') return Array.isArray(value) && value.every((item) => typeof item === 'string') ? undefined : 'takes a list of text'
87
+ if (type === 'list') return Array.isArray(value) && value.every((item) => typeof item === 'string') ? undefined : 'takes a list of text'
88
88
  if (typeof value !== 'string') return 'takes text'
89
- if (kind === 'choice' && !(declared.choices ?? []).includes(value)) return `takes one of: ${(declared.choices ?? []).join(', ')}`
89
+ if (type === 'choice' && !(declared.choices ?? []).includes(value)) return `takes one of: ${(declared.choices ?? []).join(', ')}`
90
90
  return undefined
91
91
  }
package/src/records.ts CHANGED
@@ -19,23 +19,30 @@ export type Steps = {
19
19
  permissions?: readonly string[]
20
20
  }
21
21
 
22
- export const listPermissions = ['network', 'run', 'files'] as const
23
- export const flagPermissions = ['conversation', 'prompt', 'model', 'agents', 'tools', 'config', 'approve'] as const
22
+ export const listPermissions = ['network', 'run', 'files', 'config'] as const
23
+ export const flagPermissions = ['conversation', 'prompt', 'model', 'tasks', 'tools', 'approve'] as const
24
24
  export type ListPermission = (typeof listPermissions)[number]
25
25
  export type FlagPermission = (typeof flagPermissions)[number]
26
26
  const permissionNames: readonly string[] = [...listPermissions, ...flagPermissions]
27
27
  const hostName = /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$/
28
28
  const programName = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/
29
+ const settingName = /^[A-Za-z][A-Za-z0-9]*$/
30
+ const listItemHint: Readonly<Record<ListPermission, string>> = {
31
+ network: 'a host alone, such as "api.github.com", or a socket path, such as "/var/run/docker.sock"',
32
+ run: 'a program\'s command, such as "gh"',
33
+ files: 'a path that starts with ~/ or /, such as "~/.zshrc"',
34
+ config: 'a Claude Code setting\'s name, such as "cleanupPeriodDays"',
35
+ }
29
36
 
30
37
  export type ReadFile = (path: string) => Promise<string | undefined>
31
38
 
32
39
  export type RunnerEvent =
33
- | { kind: 'progress'; done: number; total: number; label: string }
34
- | { kind: 'log'; text: string }
35
- | { kind: 'needs-consent'; sha256: string; install: string; uninstall: string; keys: string; permissions: readonly string[] }
36
- | { kind: 'done'; name: string; version?: string }
37
- | { kind: 'missing'; name: string }
38
- | { kind: 'failed'; code: number; message: string }
40
+ | { type: 'progress'; done: number; total: number; label: string }
41
+ | { type: 'log'; text: string }
42
+ | { type: 'needs-consent'; sha256: string; install: string; uninstall: string; keys: string; permissions: readonly string[] }
43
+ | { type: 'done'; name: string; version?: string }
44
+ | { type: 'missing'; name: string }
45
+ | { type: 'failed'; code: number; message: string }
39
46
 
40
47
  export const pluginName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/
41
48
 
@@ -153,12 +160,12 @@ function readPermissions(steps: Record<string, unknown>): string[] {
153
160
  items.push('run:*')
154
161
  continue
155
162
  }
156
- const example = name === 'network' ? '["api.github.com"]' : name === 'run' ? '["gh"], or "*" for any program' : '["~/.zshrc"]'
163
+ const example = { network: '["api.github.com"]', run: '["gh"], or "*" for any program', files: '["~/.zshrc"]', config: '["cleanupPeriodDays"]' }[name]
157
164
  if (!Array.isArray(value) || value.length === 0 || !value.every((each) => typeof each === 'string')) throw new Error(`${at} sets "${name}" to ${JSON.stringify(value)}. Write a list, such as "${name}": ${example}.`)
158
- for (const each of value as string[]) {
165
+ for (const each of value) {
159
166
  const isPath = /^(~\/|\/)/.test(each) && !each.split('/').includes('..')
160
- const isValid = name === 'network' ? hostName.test(each) || isPath : name === 'run' ? programName.test(each) : isPath
161
- if (!isValid) throw new Error(`${at} lists "${each}" under "${name}". Write ${name === 'network' ? 'a host alone, such as "api.github.com", or a socket path, such as "/var/run/docker.sock"' : name === 'run' ? 'a program\'s command, such as "gh"' : 'a path that starts with ~/ or /, such as "~/.zshrc"'}.`)
167
+ const isValid = { network: hostName.test(each) || isPath, run: programName.test(each), files: isPath, config: settingName.test(each) }[name]
168
+ if (isValid !== true) throw new Error(`${at} lists "${each}" under "${name}". Write ${listItemHint[name as ListPermission]}.`)
162
169
  items.push(`${name}:${each}`)
163
170
  }
164
171
  }
@@ -176,17 +183,17 @@ export function permissionWords(item: string): string {
176
183
  case 'files':
177
184
  return `Change ${target}`
178
185
  case 'conversation':
179
- return 'Read this conversation'
186
+ return 'Read your Claude Code conversations'
180
187
  case 'prompt':
181
188
  return 'Add text Claude reads and start turns'
182
189
  case 'model':
183
190
  return 'Ask a model, which uses your plan'
184
- case 'agents':
185
- return 'Start agents'
191
+ case 'tasks':
192
+ return 'Start tasks that work beside Claude'
186
193
  case 'tools':
187
194
  return "Use and change Claude's tool calls"
188
195
  case 'config':
189
- return 'Change your Claude Code settings'
196
+ return `Change ${target} in your Claude Code settings`
190
197
  case 'approve':
191
198
  return "Approve Claude's tool calls for you"
192
199
  default:
@@ -196,6 +203,8 @@ export function permissionWords(item: string): string {
196
203
 
197
204
  export const settingsPagesMethod = 'cmod:settingsPages'
198
205
 
206
+ export const changelogPage = 'changelog'
207
+
199
208
  export const openPageMethod = 'cmod:openPage'
200
209
 
201
210
  export const pendingStepsMethod = 'cmod:pendingSteps'
@@ -240,7 +249,10 @@ function readKeys(keys: unknown): Steps {
240
249
  return { keys: commands }
241
250
  }
242
251
 
252
+ export const coreVersion = '1.0.0'
253
+
243
254
  export function oldestCmodFor(steps: Steps): string {
255
+ if (steps.permissions?.some((item) => item === 'tasks' || item.startsWith('config:')) === true) return '1.0.0'
244
256
  if (steps.permissions !== undefined) return '0.2.0'
245
257
  return Object.keys(steps.keys ?? {}).length > 0 ? '0.1.12' : '0.0.0'
246
258
  }
@@ -303,7 +315,7 @@ export async function scriptsSha256(steps: Steps, files: { read: ReadFile; list:
303
315
  }
304
316
 
305
317
  export function formatEvent(event: RunnerEvent): string {
306
- switch (event.kind) {
318
+ switch (event.type) {
307
319
  case 'progress':
308
320
  return `progress ${event.done} ${event.total}${event.label === '' ? '' : ` ${event.label}`}`
309
321
  case 'log':
@@ -321,19 +333,19 @@ export function formatEvent(event: RunnerEvent): string {
321
333
 
322
334
  export function parseEvent(line: string): RunnerEvent {
323
335
  const progress = /^progress (\d+) (\d+)(?: (.*))?$/.exec(line)
324
- if (progress !== null) return { kind: 'progress', done: Number(progress[1]), total: Number(progress[2]), label: progress[3] ?? '' }
336
+ if (progress !== null) return { type: 'progress', done: Number(progress[1]), total: Number(progress[2]), label: progress[3] ?? '' }
325
337
  const consent = /^needs-consent (\S+)\t([^\t]*)\t([^\t]*)(?:\t(.*))?$/.exec(line)
326
338
  if (consent !== null) {
327
339
  const [keys = '', ...permissions] = (consent[4] ?? '').split('\t')
328
- return { kind: 'needs-consent', sha256: consent[1] as string, install: consent[2] as string, uninstall: consent[3] as string, keys, permissions: permissions.filter((item) => item !== '') }
340
+ return { type: 'needs-consent', sha256: consent[1] as string, install: consent[2] as string, uninstall: consent[3] as string, keys, permissions: permissions.filter((item) => item !== '') }
329
341
  }
330
342
  const done = /^done (\S+)(?: (\S+))?$/.exec(line)
331
- if (done !== null) return { kind: 'done', name: done[1] as string, ...(done[2] === undefined ? {} : { version: done[2] }) }
343
+ if (done !== null) return { type: 'done', name: done[1] as string, ...(done[2] === undefined ? {} : { version: done[2] }) }
332
344
  const missing = /^missing (\S+)$/.exec(line)
333
- if (missing !== null) return { kind: 'missing', name: missing[1] as string }
345
+ if (missing !== null) return { type: 'missing', name: missing[1] as string }
334
346
  const failed = /^failed (-?\d+)(?:\t(.*))?$/.exec(line)
335
- if (failed !== null) return { kind: 'failed', code: Number(failed[1]), message: failed[2] ?? '' }
336
- return { kind: 'log', text: line.replace(/^log /, '') }
347
+ if (failed !== null) return { type: 'failed', code: Number(failed[1]), message: failed[2] ?? '' }
348
+ return { type: 'log', text: line.replace(/^log /, '') }
337
349
  }
338
350
 
339
351
  export function isAtLeast(version: string, minimum: string): boolean {