@cmodjs/core 0.3.0 → 1.5.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 (280) hide show
  1. package/docs/commands.md +61 -10
  2. package/docs/dependencies.md +16 -14
  3. package/docs/hooks.md +64 -29
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +13 -9
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +227 -175
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +311 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +68 -23
  13. package/docs/ui.md +116 -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 +14 -4
  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 +178 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +127 -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 +13 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +33 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +34 -5
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +68 -0
  61. package/runtime/before.d.ts +32 -0
  62. package/runtime/before.d.ts.map +1 -0
  63. package/runtime/before.js +214 -0
  64. package/runtime/bound.d.ts +4 -0
  65. package/runtime/bound.d.ts.map +1 -0
  66. package/runtime/bound.js +9 -0
  67. package/runtime/claude.d.ts +4 -2
  68. package/runtime/claude.d.ts.map +1 -1
  69. package/runtime/conversations.d.ts +17 -0
  70. package/runtime/conversations.d.ts.map +1 -0
  71. package/runtime/conversations.js +563 -0
  72. package/runtime/deadline.d.ts +0 -1
  73. package/runtime/deadline.d.ts.map +1 -1
  74. package/runtime/deadline.js +1 -1
  75. package/runtime/dependencies.d.ts +2 -0
  76. package/runtime/dependencies.d.ts.map +1 -1
  77. package/runtime/dependencies.js +10 -1
  78. package/runtime/engine.d.ts +13 -0
  79. package/runtime/engine.d.ts.map +1 -0
  80. package/runtime/engine.js +10 -0
  81. package/runtime/grants.d.ts +4 -0
  82. package/runtime/grants.d.ts.map +1 -1
  83. package/runtime/grants.js +68 -31
  84. package/runtime/hooks.d.ts +5 -4
  85. package/runtime/hooks.d.ts.map +1 -1
  86. package/runtime/hooks.js +29 -9
  87. package/runtime/installer.d.ts +1 -1
  88. package/runtime/installer.d.ts.map +1 -1
  89. package/runtime/installer.js +17 -14
  90. package/runtime/lifecycle.d.ts +3 -1
  91. package/runtime/lifecycle.d.ts.map +1 -1
  92. package/runtime/lifecycle.js +414 -212
  93. package/runtime/metadata.d.ts +3 -1
  94. package/runtime/metadata.d.ts.map +1 -1
  95. package/runtime/metadata.js +25 -14
  96. package/runtime/options.d.ts +3 -1
  97. package/runtime/options.d.ts.map +1 -1
  98. package/runtime/options.js +4 -4
  99. package/runtime/programs.d.ts +8 -0
  100. package/runtime/programs.d.ts.map +1 -1
  101. package/runtime/programs.js +39 -1
  102. package/runtime/router.d.ts +2 -1
  103. package/runtime/router.d.ts.map +1 -1
  104. package/runtime/router.js +11 -3
  105. package/runtime/sessions.d.ts +58 -0
  106. package/runtime/sessions.d.ts.map +1 -0
  107. package/runtime/sessions.js +433 -0
  108. package/runtime/state.d.ts +6 -2
  109. package/runtime/state.d.ts.map +1 -1
  110. package/runtime/state.js +46 -13
  111. package/runtime/toaster.d.ts +24 -0
  112. package/runtime/toaster.d.ts.map +1 -0
  113. package/runtime/toaster.js +286 -0
  114. package/runtime/ui.d.ts +6 -17
  115. package/runtime/ui.d.ts.map +1 -1
  116. package/runtime/ui.js +62 -118
  117. package/session/context/context.d.ts +12 -0
  118. package/session/context/context.d.ts.map +1 -0
  119. package/session/context/context.js +2 -0
  120. package/session/context/turn/message/document.d.ts +7 -0
  121. package/session/context/turn/message/document.d.ts.map +1 -0
  122. package/session/context/turn/message/document.js +1 -0
  123. package/session/context/turn/message/message.d.ts +55 -0
  124. package/session/context/turn/message/message.d.ts.map +1 -0
  125. package/session/context/turn/message/message.js +178 -0
  126. package/session/context/turn/message/note.d.ts +8 -0
  127. package/session/context/turn/message/note.d.ts.map +1 -0
  128. package/session/context/turn/message/note.js +1 -0
  129. package/session/context/turn/message/notice.d.ts +6 -0
  130. package/session/context/turn/message/notice.d.ts.map +1 -0
  131. package/session/context/turn/message/notice.js +1 -0
  132. package/session/context/turn/message/prompt.d.ts +8 -0
  133. package/session/context/turn/message/prompt.d.ts.map +1 -0
  134. package/session/context/turn/message/prompt.js +1 -0
  135. package/session/context/turn/message/reply.d.ts +6 -0
  136. package/session/context/turn/message/reply.d.ts.map +1 -0
  137. package/session/context/turn/message/reply.js +1 -0
  138. package/session/context/turn/message/sender.d.ts +14 -0
  139. package/session/context/turn/message/sender.d.ts.map +1 -0
  140. package/session/context/turn/message/sender.js +30 -0
  141. package/session/context/turn/message/skill.d.ts +7 -0
  142. package/session/context/turn/message/skill.d.ts.map +1 -0
  143. package/session/context/turn/message/skill.js +1 -0
  144. package/session/context/turn/message/tool-call.d.ts +9 -0
  145. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  146. package/session/context/turn/message/tool-call.js +1 -0
  147. package/session/context/turn/message/tool-result.d.ts +8 -0
  148. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  149. package/session/context/turn/message/tool-result.js +1 -0
  150. package/session/context/turn/turn.d.ts +13 -0
  151. package/session/context/turn/turn.d.ts.map +1 -0
  152. package/session/context/turn/turn.js +2 -0
  153. package/session/conversation.d.ts +73 -0
  154. package/session/conversation.d.ts.map +1 -0
  155. package/session/conversation.js +146 -0
  156. package/session/goal/goal.d.ts +11 -0
  157. package/session/goal/goal.d.ts.map +1 -0
  158. package/session/goal/goal.js +1 -0
  159. package/session/goal/task.d.ts +16 -0
  160. package/session/goal/task.d.ts.map +1 -0
  161. package/session/goal/task.js +3 -0
  162. package/session/session.d.ts +158 -0
  163. package/session/session.d.ts.map +1 -0
  164. package/session/session.js +2 -0
  165. package/src/jobs/check.ts +7 -4
  166. package/src/jobs/context.ts +24 -82
  167. package/src/jobs/permissions/decide-permission.ts +10 -8
  168. package/src/jobs/permissions/find-project-scope.ts +8 -14
  169. package/src/jobs/permissions/match-target.ts +3 -3
  170. package/src/jobs/permissions.ts +8 -6
  171. package/src/jobs/program.ts +13 -4
  172. package/src/jobs/prompt.ts +21 -24
  173. package/src/jobs/schedule.ts +14 -14
  174. package/src/jobs/slash-command.ts +20 -14
  175. package/src/jobs/status-line.ts +9 -21
  176. package/src/jobs/tool.ts +17 -11
  177. package/src/mod.ts +252 -97
  178. package/src/options.ts +22 -22
  179. package/src/records.ts +40 -23
  180. package/src/register.ts +34 -5
  181. package/src/runtime/announce.ts +76 -0
  182. package/src/runtime/before.ts +200 -0
  183. package/src/runtime/bound.ts +12 -0
  184. package/src/runtime/claude.ts +7 -2
  185. package/src/runtime/conversations.ts +579 -0
  186. package/src/runtime/deadline.ts +2 -2
  187. package/src/runtime/dependencies.ts +13 -1
  188. package/src/runtime/engine.ts +23 -0
  189. package/src/runtime/grants.ts +69 -29
  190. package/src/runtime/hooks.ts +39 -14
  191. package/src/runtime/installer.ts +16 -14
  192. package/src/runtime/lifecycle.ts +413 -201
  193. package/src/runtime/metadata.ts +37 -14
  194. package/src/runtime/options.ts +19 -4
  195. package/src/runtime/programs.ts +40 -1
  196. package/src/runtime/router.ts +11 -4
  197. package/src/runtime/sessions.ts +447 -0
  198. package/src/runtime/state.ts +50 -14
  199. package/src/runtime/toaster.ts +334 -0
  200. package/src/runtime/ui.ts +65 -143
  201. package/src/session/context/context.ts +14 -0
  202. package/src/session/context/turn/message/document.ts +8 -0
  203. package/src/session/context/turn/message/message.ts +203 -0
  204. package/src/session/context/turn/message/note.ts +9 -0
  205. package/src/session/context/turn/message/notice.ts +7 -0
  206. package/src/session/context/turn/message/prompt.ts +9 -0
  207. package/src/session/context/turn/message/reply.ts +7 -0
  208. package/src/session/context/turn/message/sender.ts +38 -0
  209. package/src/session/context/turn/message/skill.ts +8 -0
  210. package/src/session/context/turn/message/tool-call.ts +10 -0
  211. package/src/session/context/turn/message/tool-result.ts +9 -0
  212. package/src/session/context/turn/turn.ts +15 -0
  213. package/src/session/conversation.ts +194 -0
  214. package/src/session/goal/goal.ts +10 -0
  215. package/src/session/goal/task.ts +19 -0
  216. package/src/session/session.ts +141 -0
  217. package/src/testing/fake-claude.ts +25 -17
  218. package/src/testing/fake-elements.ts +3 -2
  219. package/src/testing/fake-files.ts +1 -1
  220. package/src/testing/fake-sessions.ts +277 -0
  221. package/src/testing.ts +138 -35
  222. package/src/ui/components.ts +3 -3
  223. package/src/ui/elements.ts +22 -8
  224. package/src/ui/markdown.ts +22 -26
  225. package/src/ui/places.ts +49 -0
  226. package/src/utils/call-effects.ts +5 -5
  227. package/src/utils/changelog.ts +32 -0
  228. package/src/utils/disposable.ts +3 -0
  229. package/src/utils/matcher.ts +37 -0
  230. package/src/utils/metadata.ts +17 -15
  231. package/src/utils/parse-shell.ts +9 -8
  232. package/src/utils/paths.ts +21 -4
  233. package/src/utils/text.ts +4 -0
  234. package/testing/fake-claude.d.ts +5 -1
  235. package/testing/fake-claude.d.ts.map +1 -1
  236. package/testing/fake-claude.js +21 -12
  237. package/testing/fake-elements.js +4 -2
  238. package/testing/fake-files.js +1 -1
  239. package/testing/fake-sessions.d.ts +81 -0
  240. package/testing/fake-sessions.d.ts.map +1 -0
  241. package/testing/fake-sessions.js +249 -0
  242. package/testing.d.ts +34 -13
  243. package/testing.d.ts.map +1 -1
  244. package/testing.js +115 -23
  245. package/ui/components.d.ts +2 -3
  246. package/ui/components.d.ts.map +1 -1
  247. package/ui/components.js +3 -3
  248. package/ui/elements.d.ts.map +1 -1
  249. package/ui/elements.js +19 -9
  250. package/ui/markdown.d.ts +35 -33
  251. package/ui/markdown.d.ts.map +1 -1
  252. package/ui/markdown.js +14 -16
  253. package/ui/places.d.ts +43 -0
  254. package/ui/places.d.ts.map +1 -0
  255. package/ui/places.js +31 -0
  256. package/utils/call-effects.d.ts +2 -1
  257. package/utils/call-effects.d.ts.map +1 -1
  258. package/utils/call-effects.js +3 -3
  259. package/utils/changelog.d.ts +9 -0
  260. package/utils/changelog.d.ts.map +1 -0
  261. package/utils/changelog.js +29 -0
  262. package/utils/disposable.d.ts +4 -0
  263. package/utils/disposable.d.ts.map +1 -0
  264. package/utils/disposable.js +1 -0
  265. package/utils/matcher.d.ts +13 -0
  266. package/utils/matcher.d.ts.map +1 -0
  267. package/utils/matcher.js +32 -0
  268. package/utils/metadata.d.ts +3 -3
  269. package/utils/metadata.js +18 -14
  270. package/utils/parse-shell.js +9 -7
  271. package/utils/paths.d.ts +4 -1
  272. package/utils/paths.d.ts.map +1 -1
  273. package/utils/paths.js +19 -4
  274. package/utils/text.d.ts +1 -0
  275. package/utils/text.d.ts.map +1 -1
  276. package/utils/text.js +3 -0
  277. package/src/ui/slots.ts +0 -36
  278. package/ui/slots.d.ts +0 -208
  279. package/ui/slots.d.ts.map +0 -1
  280. package/ui/slots.js +0 -19
@@ -1,4 +1,4 @@
1
- export type MetadataFormat = { readonly kind: 'frontmatter' } | { readonly kind: 'comment'; readonly prefix: string } | { readonly kind: 'sidecar' }
1
+ export type MetadataFormat = { readonly type: 'frontmatter' } | { readonly type: 'comment'; readonly prefix: string } | { readonly type: 'sidecar' }
2
2
 
3
3
  export type ReadMetadata = {
4
4
  readonly metadata: Readonly<Record<string, string>>
@@ -26,12 +26,12 @@ export function formatOf(path: string, firstLine: string | undefined): MetadataF
26
26
  const name = path.slice(path.lastIndexOf('/') + 1)
27
27
  const dot = name.lastIndexOf('.')
28
28
  const extension = dot <= 0 ? '' : name.slice(dot + 1).toLowerCase()
29
- if (markdown.includes(extension)) return { kind: 'frontmatter' }
30
- if (hashComments.includes(extension) || hashCommentNames.includes(name)) return { kind: 'comment', prefix: '#' }
31
- if (slashComments.includes(extension)) return { kind: 'comment', prefix: '//' }
32
- if (dashComments.includes(extension)) return { kind: 'comment', prefix: '--' }
33
- if (extension === '' && firstLine?.startsWith('#!') === true) return { kind: 'comment', prefix: '#' }
34
- return { kind: 'sidecar' }
29
+ if (markdown.includes(extension)) return { type: 'frontmatter' }
30
+ if (hashComments.includes(extension) || hashCommentNames.includes(name)) return { type: 'comment', prefix: '#' }
31
+ if (slashComments.includes(extension)) return { type: 'comment', prefix: '//' }
32
+ if (dashComments.includes(extension)) return { type: 'comment', prefix: '--' }
33
+ if (extension === '' && firstLine?.startsWith('#!') === true) return { type: 'comment', prefix: '#' }
34
+ return { type: 'sidecar' }
35
35
  }
36
36
 
37
37
  type Region = { readonly start: number; readonly end: number; readonly lines: readonly string[] }
@@ -42,21 +42,21 @@ type Block = { readonly start: number; readonly end: number; readonly entries: r
42
42
 
43
43
  export function readMetadata(text: string, format: MetadataFormat): ReadMetadata {
44
44
  const lines = text.split('\n')
45
- if (format.kind === 'comment') {
45
+ if (format.type === 'comment') {
46
46
  const region = commentRegion(lines, format.prefix)
47
47
  return { metadata: region === undefined ? {} : asRecord(mapEntries(region.lines, region.start + 2, 0)) }
48
48
  }
49
- const region = format.kind === 'frontmatter' ? frontmatterRegion(lines) : { start: -1, end: lines.length, lines }
49
+ const region = format.type === 'frontmatter' ? frontmatterRegion(lines) : { start: -1, end: lines.length, lines }
50
50
  if (region === undefined) return { metadata: {} }
51
51
  const block = metadataBlock(region)
52
- const name = format.kind === 'frontmatter' ? topLevelScalar(region, 'name') : undefined
52
+ const name = format.type === 'frontmatter' ? topLevelScalar(region, 'name') : undefined
53
53
  return { metadata: block === undefined ? {} : asRecord(block.entries), ...(name === undefined ? {} : { name }) }
54
54
  }
55
55
 
56
56
  export function writeMetadata(text: string, format: MetadataFormat, metadata: Readonly<Record<string, string>>): string {
57
57
  const lines = text.split('\n')
58
58
  const entries = Object.entries(metadata)
59
- if (format.kind === 'comment') {
59
+ if (format.type === 'comment') {
60
60
  const { prefix } = format
61
61
  const block = entries.length === 0 ? [] : [`${prefix} /// metadata`, ...entries.map(([key, value]) => `${prefix} ${scalar(key)}: ${scalar(value)}`), `${prefix} ///`]
62
62
  const region = commentRegion(lines, prefix)
@@ -66,7 +66,7 @@ export function writeMetadata(text: string, format: MetadataFormat, metadata: Re
66
66
  return [...lines.slice(0, at), ...block, ...lines.slice(at)].join('\n')
67
67
  }
68
68
  const map = entries.length === 0 ? [] : ['metadata:', ...entries.map(([key, value]) => ` ${scalar(key)}: ${scalar(value)}`)]
69
- if (format.kind === 'sidecar') {
69
+ if (format.type === 'sidecar') {
70
70
  const block = metadataBlock({ start: -1, end: lines.length, lines })
71
71
  const rest = block === undefined ? lines : [...lines.slice(0, block.start), ...lines.slice(block.end)]
72
72
  const kept = rest.join('\n').trim()
@@ -206,7 +206,8 @@ function unquoted(text: string, lineNumber: number): string {
206
206
  if (!text.endsWith('"') || text.length < 2) throw new MetadataError(lineNumber, `${text} opens a double quote that never closes.`)
207
207
  try {
208
208
  return JSON.parse(text) as string
209
- } catch {
209
+ } catch (error) {
210
+ if (!(error instanceof SyntaxError)) throw error
210
211
  throw new MetadataError(lineNumber, `${text} is not a double-quoted text. Escape a " inside it as \\".`)
211
212
  }
212
213
  }
@@ -224,8 +225,9 @@ function topLevelScalar(region: Region, key: string): string | undefined {
224
225
  if (text === '') return undefined
225
226
  try {
226
227
  return unquoted(text, region.start + 2 + at)
227
- } catch {
228
- return undefined
228
+ } catch (error) {
229
+ if (error instanceof MetadataError) return undefined
230
+ throw error
229
231
  }
230
232
  }
231
233
 
@@ -18,7 +18,7 @@ type Scan = { text: string; markers: Marker[]; end: number }
18
18
  type Redirect = { operator: string; target: Word }
19
19
  type Pipe = { writer: number | undefined }
20
20
  type Command = { words: Word[]; redirects: Redirect[]; pipe: Pipe | undefined }
21
- type Stdin = { kind: 'text'; text: string } | { kind: 'pipe' }
21
+ type Stdin = { type: 'text'; text: string } | { type: 'pipe' }
22
22
  type Access = 'reads' | 'writes'
23
23
  type OptionSpec = { valued?: string; optional?: string; longValued?: readonly string[] }
24
24
  type Option = { name: string; value: Word | undefined }
@@ -211,7 +211,8 @@ function parseLine(line: string, folder: string, result: ParsedShell): void {
211
211
  try {
212
212
  const { text, markers } = scan(line, 0, false)
213
213
  walk(parse(text, (name) => (name === '' ? undefined : `$${name}`)), markers, folder, result)
214
- } catch {
214
+ } catch (error) {
215
+ if (!(error instanceof SyntaxError) && !(error instanceof Error && error.message.startsWith('Bad substitution: '))) throw error
215
216
  result.isFullyParsed = false
216
217
  }
217
218
  }
@@ -546,7 +547,7 @@ function finish(command: Command, operator: string, folder: string, result: Pars
546
547
  const [first, ...args] = words
547
548
  if (first === undefined || first.text === 'for' || first.text === 'select') return folder
548
549
  const here = command.redirects.findLast((redirect) => redirect.operator === '<<<')
549
- const stdin: Stdin | undefined = here !== undefined ? { kind: 'text', text: here.target.text } : command.pipe !== undefined ? { kind: 'pipe' } : undefined
550
+ const stdin: Stdin | undefined = here !== undefined ? { type: 'text', text: here.target.text } : command.pipe !== undefined ? { type: 'pipe' } : undefined
550
551
  const reader = result.commands.length
551
552
  const changed = addCommand(first, args, folder, stdin, result)
552
553
  const read = result.commands[reader]
@@ -629,7 +630,7 @@ function findStarts(args: Word[]): Word[] {
629
630
 
630
631
  function addWrapper(first: Word, program: string, args: Word[], folder: string, stdin: Stdin | undefined, result: ParsedShell): void {
631
632
  const wrapper = wrappers.get(program)!
632
- if (program === 'xargs' && stdin?.kind !== 'text') result.isFullyParsed = false
633
+ if (program === 'xargs' && stdin?.type !== 'text') result.isFullyParsed = false
633
634
  const { options, operands } = readArguments(args, wrapper, true)
634
635
  let start = args.length - operands.length
635
636
  if (options.some((option) => wrapper.stops?.includes(option.name) === true)) start = args.length
@@ -669,8 +670,8 @@ function addEffects(program: string, args: Word[], flags: string[], folder: stri
669
670
  else if (inlineFlags !== undefined) {
670
671
  if (program === 'perl' && readArguments(args, perl, false).options.some((option) => option.name === '-i')) addFileArguments(perl, args, folder, result)
671
672
  const script = args.find((word) => !word.text.startsWith('-') || word.text === '-')
672
- const readsCodeFromPipe = stdin?.kind === 'pipe' && (script === undefined || script.text === '-')
673
- if (flags.some((flag) => inlineFlags.includes(flag)) || stdin?.kind === 'text' || readsCodeFromPipe) result.isFullyParsed = false
673
+ const readsCodeFromPipe = stdin?.type === 'pipe' && (script === undefined || script.text === '-')
674
+ if (flags.some((flag) => inlineFlags.includes(flag)) || stdin?.type === 'text' || readsCodeFromPipe) result.isFullyParsed = false
674
675
  }
675
676
  }
676
677
 
@@ -701,8 +702,8 @@ function addFetches(fetcher: OptionSpec, args: Word[], result: ParsedShell): voi
701
702
  }
702
703
 
703
704
  function addStdinCode(stdin: Stdin | undefined, folder: string, result: ParsedShell): void {
704
- if (stdin?.kind === 'text') parseLine(stdin.text, folder, result)
705
- if (stdin?.kind === 'pipe') result.isFullyParsed = false
705
+ if (stdin?.type === 'text') parseLine(stdin.text, folder, result)
706
+ if (stdin?.type === 'pipe') result.isFullyParsed = false
706
707
  }
707
708
 
708
709
  function addCopy(program: CopyProgram, args: Word[], folder: string, result: ParsedShell): void {
@@ -1,4 +1,18 @@
1
1
  import { basename, dirname, resolve } from '../vendor.js'
2
+ import { messageOf } from './text.js'
3
+
4
+ const missingPath = /(?:^|: )(?:ENOENT|ENOTDIR)\b/
5
+
6
+ export function isMissing(error: unknown): boolean {
7
+ return missingPath.test(messageOf(error))
8
+ }
9
+
10
+ export function ifMissing<Value>(value: Value): (error: unknown) => Value {
11
+ return (error) => {
12
+ if (isMissing(error)) return value
13
+ throw error
14
+ }
15
+ }
2
16
 
3
17
  export type FileSystem = {
4
18
  read: (path: string) => Promise<string>
@@ -6,9 +20,12 @@ export type FileSystem = {
6
20
  exists: (path: string) => Promise<boolean>
7
21
  }
8
22
 
9
- export function expandHome(path: string, home: string): string {
10
- const prefix = /^(~|\$HOME)(?=\/|$)/.exec(path)?.[0]
11
- return prefix === undefined ? path : `${home}${path.slice(prefix.length)}`
23
+ export const homePrefix = /^(~|\$HOME|\$CLAUDE_CONFIG_DIR)(?=\/|$)/
24
+
25
+ export function expandHome(path: string, home: string, configRoot = `${home}/.claude`): string {
26
+ const prefix = homePrefix.exec(path)?.[0]
27
+ if (prefix === undefined) return path
28
+ return `${prefix === '$CLAUDE_CONFIG_DIR' ? configRoot : home}${path.slice(prefix.length)}`
12
29
  }
13
30
 
14
31
  export function relativePath(base: string, path: string): string | undefined {
@@ -18,7 +35,7 @@ export function relativePath(base: string, path: string): string | undefined {
18
35
  }
19
36
 
20
37
  export async function realPathOf(path: string, fs: FileSystem): Promise<string | undefined> {
21
- const stat = await fs.stat(path).catch(() => undefined)
38
+ const stat = await fs.stat(path).catch(ifMissing(undefined))
22
39
  if (stat !== undefined || path === '/') return stat?.realPath
23
40
  const folder = await realPathOf(dirname(path), fs)
24
41
  return folder === undefined ? undefined : resolve(folder, basename(path))
package/src/utils/text.ts CHANGED
@@ -3,6 +3,10 @@ export function listed(items: readonly string[]): string {
3
3
  return `${items.slice(0, -1).join(', ')} and ${items.at(-1)}`
4
4
  }
5
5
 
6
+ export function firstLine(text: string): string {
7
+ return text.split('\n').find((line) => line.trim() !== '')?.trim() ?? ''
8
+ }
9
+
6
10
  export function messageOf(error: unknown): string {
7
11
  return error instanceof Error ? error.message : String(error)
8
12
  }
@@ -1,3 +1,4 @@
1
+ import type { EventResult } from 'claude-code';
1
2
  import type { Claude } from '../runtime/claude.js';
2
3
  export type TestCall = {
3
4
  readonly call: string;
@@ -28,10 +29,13 @@ export type Fakes = {
28
29
  ask?: Claude['ui']['ask'];
29
30
  open?: Claude['ui']['open'];
30
31
  scroll?: Claude['ui']['scroll'];
32
+ copy?: Claude['ui']['copy'];
33
+ focus?: Claude['ui']['focus'];
31
34
  };
32
35
  session: {
33
36
  messages?: Claude['session']['messages'];
34
37
  append?: Claude['session']['append'];
38
+ send?: Claude['session']['send'];
35
39
  };
36
40
  prompt: {
37
41
  submit?: Claude['prompt']['submit'];
@@ -60,7 +64,6 @@ export type ShownSchedule = {
60
64
  readonly prompt: string;
61
65
  };
62
66
  export type Shown = {
63
- readonly toasts: string[];
64
67
  readonly notes: string[];
65
68
  readonly prompts: string[];
66
69
  readonly logs: string[];
@@ -79,6 +82,7 @@ export type FakeClaude = {
79
82
  readonly store: Map<string, unknown>;
80
83
  settle(): Promise<void>;
81
84
  };
85
+ export declare function calledAnswer(caller: string, answer: EventResult<'cmod.call'>): unknown;
82
86
  export declare function fakeClaude(plugin: {
83
87
  readonly name: string;
84
88
  readonly root: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAGlD,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAC/E,EAAE,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IAC7J,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IACzC,QAAQ,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IAC/C,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IAC3C,EAAE,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAC/F,OAAO,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAC3F,MAAM,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAA;IAC/C,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAA;KAAE,CAAA;IACjD,KAAK,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAC3E,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;IAC7E,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;IACvC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;KAAE,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnG,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAA;IACzC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB,CAAA;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAsK/F"}
1
+ {"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,16 @@
1
+ import { isObject } from '../records.js';
1
2
  import { elements } from './fake-elements.js';
3
+ import { fakeFiles } from './fake-files.js';
4
+ export function calledAnswer(caller, answer) {
5
+ if (isObject(answer) && typeof answer['deny'] === 'string')
6
+ throw new Error(`${caller}: $.cmod.call: ${answer['deny']}`);
7
+ return isObject(answer) ? answer['value'] : undefined;
8
+ }
2
9
  export function fakeClaude(plugin) {
3
10
  const calls = [];
4
11
  const fakes = { process: {}, fs: {}, http: {}, settings: {}, config: {}, ui: {}, session: {}, prompt: {}, model: {}, agent: {}, clock: {}, cmod: {}, tool: {} };
5
- const shown = { toasts: [], notes: [], prompts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [], schedules: [] };
12
+ const emptyDisk = fakeFiles({});
13
+ const shown = { notes: [], prompts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [], schedules: [] };
6
14
  const unplacedPanes = new Set();
7
15
  let focusedPane;
8
16
  const store = new Map();
@@ -49,9 +57,6 @@ export function fakeClaude(plugin) {
49
57
  const claude = {
50
58
  plugin,
51
59
  ui: {
52
- toast: (text) => {
53
- shown.toasts.push(text);
54
- },
55
60
  status: (text) => {
56
61
  shown.statuses.push(text);
57
62
  },
@@ -66,6 +71,7 @@ export function fakeClaude(plugin) {
66
71
  calls.push({ call: 'ui.open', args: [pane] });
67
72
  const answer = fakes.ui.open === undefined ? { isPlaced: true } : await fakes.ui.open(pane);
68
73
  if (answer.isPlaced) {
74
+ shown.openPanes.delete(pane.id);
69
75
  shown.openPanes.add(pane.id);
70
76
  unplacedPanes.delete(pane.id);
71
77
  if (pane.focus === true)
@@ -84,10 +90,12 @@ export function fakeClaude(plugin) {
84
90
  focusedPane = undefined;
85
91
  },
86
92
  panes: async () => [
87
- ...[...shown.openPanes].map((id) => ({ id, title: id, isShown: true, isFocused: id === focusedPane, isPlaced: true })),
93
+ ...[...shown.openPanes].map((id) => ({ id, title: id, isShown: id === [...shown.openPanes].at(-1), isFocused: id === focusedPane, isPlaced: true })),
88
94
  ...[...unplacedPanes].map((id) => ({ id, title: id, isShown: false, isFocused: false, isPlaced: false })),
89
95
  ],
90
96
  scroll: rejected('ui.scroll', () => fakes.ui.scroll ?? (async () => ({}))),
97
+ copy: rejected('ui.copy', () => fakes.ui.copy ?? (async () => ({ isCopied: true }))),
98
+ focus: rejected('ui.focus', () => fakes.ui.focus ?? (async () => ({}))),
91
99
  resolve: () => elements,
92
100
  },
93
101
  process: {
@@ -95,14 +103,14 @@ export function fakeClaude(plugin) {
95
103
  spawn: faked('process.spawn', () => fakes.process.spawn),
96
104
  },
97
105
  fs: {
98
- read: rejected('fs.read', () => fakes.fs.read),
99
- write: rejected('fs.write', () => fakes.fs.write),
100
- list: rejected('fs.list', () => fakes.fs.list),
101
- exists: rejected('fs.exists', () => fakes.fs.exists ?? (async () => false)),
102
- stat: rejected('fs.stat', () => fakes.fs.stat),
106
+ read: rejected('fs.read', () => fakes.fs.read ?? emptyDisk.read),
107
+ write: rejected('fs.write', () => fakes.fs.write ?? emptyDisk.write),
108
+ list: rejected('fs.list', () => fakes.fs.list ?? emptyDisk.list),
109
+ exists: rejected('fs.exists', () => fakes.fs.exists ?? emptyDisk.exists),
110
+ stat: rejected('fs.stat', () => fakes.fs.stat ?? emptyDisk.stat),
103
111
  },
104
112
  http: { fetch: rejected('http.fetch', () => fakes.http.fetch) },
105
- settings: { read: rejected('settings.read', () => fakes.settings.read) },
113
+ settings: { read: rejected('settings.read', () => fakes.settings.read ?? (async () => ({}))) },
106
114
  config: {
107
115
  list: rejected('config.list', () => fakes.config.list ?? (async () => [])),
108
116
  },
@@ -130,10 +138,11 @@ export function fakeClaude(plugin) {
130
138
  root: async () => plugin.root,
131
139
  cwd: async () => plugin.root,
132
140
  model: async () => 'test-model',
133
- usage: async () => ({ context: { window: 200000 }, rateLimits: {}, cost: { usd: 0 } }),
141
+ usage: async () => ({ startedAt: Date.parse('2026-01-01T00:00:00.000Z'), context: { window: 200000 }, rateLimits: [], cost: { usd: 0 } }),
134
142
  surfaces: async () => ['terminal'],
135
143
  messages: rejected('session.messages', () => fakes.session.messages),
136
144
  append: rejected('session.append', () => fakes.session.append ?? appendNote),
145
+ send: rejected('session.send', () => fakes.session.send ?? (async () => ({ isDelivered: true }))),
137
146
  },
138
147
  prompt: { submit: rejected('prompt.submit', () => fakes.prompt.submit ?? submitPrompt) },
139
148
  model: { complete: rejected('model.complete', () => fakes.model.complete) },
@@ -119,8 +119,10 @@ function sideBySide(children, width, gap) {
119
119
  function minWidthOf(node) {
120
120
  if (typeof node === 'string')
121
121
  return 0;
122
- const minWidth = insideOf(node).props['minWidth'];
123
- return typeof minWidth === 'number' ? minWidth : 0;
122
+ const { props } = insideOf(node);
123
+ if (props['flexShrink'] === 0)
124
+ return Number.POSITIVE_INFINITY;
125
+ return typeof props['minWidth'] === 'number' ? props['minWidth'] : 0;
124
126
  }
125
127
  function blankRows(count) {
126
128
  return Array.from({ length: count }, () => '');
@@ -37,7 +37,7 @@ export function fakeFiles(files, links = {}) {
37
37
  },
38
38
  list: async (path) => {
39
39
  if (path === undefined)
40
- throw new Error('fs.list() lists the working folder, which the fake files do not know. Name the folder, such as fs.list(mod.cwd).');
40
+ throw new Error('fs.list() lists the working folder, which the fake files do not know. Name the folder, such as fs.list(sessions.current.cwd).');
41
41
  const folder = realPathOf(path);
42
42
  if (kindOf(folder) !== 'dir')
43
43
  throw new Error(`ENOENT: no such directory, list '${path}'`);
@@ -0,0 +1,81 @@
1
+ import { Document } from '../session/context/turn/message/document.js';
2
+ import { Message } from '../session/context/turn/message/message.js';
3
+ import { Note } from '../session/context/turn/message/note.js';
4
+ import { Notice } from '../session/context/turn/message/notice.js';
5
+ import { Prompt } from '../session/context/turn/message/prompt.js';
6
+ import { Reply } from '../session/context/turn/message/reply.js';
7
+ import { type Sender } from '../session/context/turn/message/sender.js';
8
+ import { Skill } from '../session/context/turn/message/skill.js';
9
+ import { ToolCall } from '../session/context/turn/message/tool-call.js';
10
+ import { ToolResult } from '../session/context/turn/message/tool-result.js';
11
+ import { type SessionChange, type SessionData } from '../session/conversation.js';
12
+ import type { Place } from '../runtime/before.js';
13
+ import type { Claude } from '../runtime/claude.js';
14
+ export type MessageDraft = {
15
+ readonly type: typeof Prompt;
16
+ readonly text: string;
17
+ readonly from?: Sender;
18
+ } | {
19
+ readonly type: typeof Note;
20
+ readonly text: string;
21
+ readonly from?: Sender;
22
+ } | {
23
+ readonly type: typeof Reply;
24
+ readonly text: string;
25
+ } | {
26
+ readonly type: typeof ToolCall;
27
+ readonly name: string;
28
+ readonly input?: Readonly<Record<string, unknown>>;
29
+ readonly callId?: string;
30
+ } | {
31
+ readonly type: typeof ToolResult;
32
+ readonly callId: string;
33
+ readonly text: string;
34
+ readonly isError?: boolean;
35
+ } | {
36
+ readonly type: typeof Skill;
37
+ readonly name: string;
38
+ readonly text: string;
39
+ } | {
40
+ readonly type: typeof Document;
41
+ readonly path: string;
42
+ readonly text: string;
43
+ } | {
44
+ readonly type: typeof Notice;
45
+ readonly text: string;
46
+ };
47
+ export type FakeSession = Partial<Omit<SessionData, 'id'>> & {
48
+ readonly id: string;
49
+ readonly messages?: readonly MessageDraft[];
50
+ readonly goal?: string;
51
+ };
52
+ export type FakeSessions = {
53
+ answer(caller: string, method: string, input: unknown): unknown;
54
+ add(draft: MessageDraft, task?: string): Message[];
55
+ place(): Place;
56
+ wants(mod: string, event: string): boolean;
57
+ endTurn(): void;
58
+ compact(): void;
59
+ startTask(task: {
60
+ readonly title: string;
61
+ readonly agentType?: string;
62
+ readonly model?: string;
63
+ readonly openedBy?: string;
64
+ readonly parent?: string;
65
+ }): string;
66
+ start(id: string): void;
67
+ readonly sent: {
68
+ readonly session: string;
69
+ readonly task?: string;
70
+ readonly type: string;
71
+ readonly text: string;
72
+ readonly as?: string;
73
+ }[];
74
+ };
75
+ export declare function fakeSessions({ project, given, push, callTool }: {
76
+ readonly project: () => string;
77
+ readonly given: readonly FakeSession[];
78
+ readonly push: (mod: string, session: string, change: SessionChange) => void;
79
+ readonly callTool: Claude['tool']['call'];
80
+ }): FakeSessions;
81
+ //# sourceMappingURL=fake-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-sessions.d.ts","sourceRoot":"","sources":["../src/testing/fake-sessions.ts"],"names":[],"mappings":"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"}