@cmodjs/core 0.1.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +31 -19
- package/docs/index.md +14 -4
- package/docs/install-steps.md +30 -4
- package/docs/jobs.md +25 -9
- package/docs/mod.md +103 -37
- package/docs/options.md +93 -0
- package/docs/permissions.md +88 -0
- package/docs/state.md +4 -31
- package/docs/testing.md +13 -2
- package/installer.d.ts +10 -0
- package/installer.d.ts.map +1 -0
- package/installer.js +8 -0
- package/jobs/context.d.ts +2 -2
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +5 -3
- package/jobs/permissions.js +1 -1
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +5 -1
- package/jobs/prompt.js +5 -5
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +2 -1
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +3 -2
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +3 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +3 -2
- package/mod.d.ts +48 -10
- package/mod.d.ts.map +1 -1
- package/mod.js +3 -0
- package/options.d.ts +58 -0
- package/options.d.ts.map +1 -0
- package/options.js +66 -0
- package/package.json +1 -1
- package/records.d.ts +14 -1
- package/records.d.ts.map +1 -1
- package/records.js +106 -7
- package/register.d.ts +4 -2
- package/register.d.ts.map +1 -1
- package/register.js +8 -2
- package/runtime/claude.d.ts +5 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/grants.d.ts +28 -0
- package/runtime/grants.d.ts.map +1 -0
- package/runtime/grants.js +286 -0
- package/runtime/installer.d.ts +23 -0
- package/runtime/installer.d.ts.map +1 -0
- package/runtime/installer.js +113 -0
- package/runtime/lifecycle.d.ts +6 -3
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +220 -36
- package/runtime/metadata.d.ts +20 -0
- package/runtime/metadata.d.ts.map +1 -0
- package/runtime/metadata.js +143 -0
- package/runtime/options.d.ts +21 -0
- package/runtime/options.d.ts.map +1 -0
- package/runtime/options.js +100 -0
- package/runtime/state.d.ts +1 -0
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +5 -75
- package/src/installer.ts +17 -0
- package/src/jobs/context.ts +5 -3
- package/src/jobs/permissions.ts +1 -1
- package/src/jobs/program.ts +4 -1
- package/src/jobs/prompt.ts +5 -5
- package/src/jobs/schedule.ts +2 -1
- package/src/jobs/slash-command.ts +3 -2
- package/src/jobs/status-line.ts +3 -2
- package/src/jobs/tool.ts +3 -2
- package/src/mod.ts +46 -11
- package/src/options.ts +91 -0
- package/src/records.ts +117 -8
- package/src/register.ts +12 -3
- package/src/runtime/claude.ts +2 -0
- package/src/runtime/grants.ts +306 -0
- package/src/runtime/installer.ts +163 -0
- package/src/runtime/lifecycle.ts +220 -45
- package/src/runtime/metadata.ts +153 -0
- package/src/runtime/options.ts +102 -0
- package/src/runtime/state.ts +7 -70
- package/src/testing/fake-claude.ts +6 -1
- package/src/testing/fake-files.ts +9 -3
- package/src/testing.ts +40 -15
- package/src/utils/metadata.ts +242 -0
- package/testing/fake-claude.d.ts +4 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +5 -1
- package/testing/fake-files.d.ts.map +1 -1
- package/testing/fake-files.js +9 -3
- package/testing.d.ts +6 -2
- package/testing.d.ts.map +1 -1
- package/testing.js +29 -13
- package/utils/metadata.d.ts +21 -0
- package/utils/metadata.d.ts.map +1 -0
- package/utils/metadata.js +250 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
export type MetadataFormat = { readonly kind: 'frontmatter' } | { readonly kind: 'comment'; readonly prefix: string } | { readonly kind: 'sidecar' }
|
|
2
|
+
|
|
3
|
+
export type ReadMetadata = {
|
|
4
|
+
readonly metadata: Readonly<Record<string, string>>
|
|
5
|
+
readonly name?: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class MetadataError extends Error {
|
|
9
|
+
readonly line: number
|
|
10
|
+
|
|
11
|
+
constructor(line: number, message: string) {
|
|
12
|
+
super(`line ${line}: ${message}`)
|
|
13
|
+
this.line = line
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const markdown: readonly string[] = ['md', 'markdown', 'mdx']
|
|
18
|
+
const hashComments: readonly string[] = ['sh', 'bash', 'zsh', 'fish', 'py', 'rb', 'pl', 'r', 'yaml', 'yml', 'toml', 'ps1', 'nu', 'tcl', 'mk', 'conf', 'cfg', 'env']
|
|
19
|
+
const slashComments: readonly string[] = ['ts', 'tsx', 'mts', 'cts', 'js', 'jsx', 'mjs', 'cjs', 'go', 'rs', 'swift', 'kt', 'kts', 'java', 'c', 'h', 'cc', 'cpp', 'hpp', 'cs', 'scala', 'dart', 'zig', 'jsonc', 'json5']
|
|
20
|
+
const dashComments: readonly string[] = ['lua', 'sql', 'hs', 'elm']
|
|
21
|
+
const hashCommentNames: readonly string[] = ['Makefile', 'Dockerfile', 'Brewfile', 'Gemfile', 'Rakefile', 'Justfile', 'justfile']
|
|
22
|
+
|
|
23
|
+
export const sidecarSuffix = '.meta'
|
|
24
|
+
|
|
25
|
+
export function formatOf(path: string, firstLine: string | undefined): MetadataFormat {
|
|
26
|
+
const name = path.slice(path.lastIndexOf('/') + 1)
|
|
27
|
+
const dot = name.lastIndexOf('.')
|
|
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' }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Region = { readonly start: number; readonly end: number; readonly lines: readonly string[] }
|
|
38
|
+
|
|
39
|
+
type Entry = { readonly key: string; readonly value: string }
|
|
40
|
+
|
|
41
|
+
type Block = { readonly start: number; readonly end: number; readonly entries: readonly Entry[] }
|
|
42
|
+
|
|
43
|
+
export function readMetadata(text: string, format: MetadataFormat): ReadMetadata {
|
|
44
|
+
const lines = text.split('\n')
|
|
45
|
+
if (format.kind === 'comment') {
|
|
46
|
+
const region = commentRegion(lines, format.prefix)
|
|
47
|
+
return { metadata: region === undefined ? {} : asRecord(mapEntries(region.lines, region.start + 2, 0)) }
|
|
48
|
+
}
|
|
49
|
+
const region = format.kind === 'frontmatter' ? frontmatterRegion(lines) : { start: -1, end: lines.length, lines }
|
|
50
|
+
if (region === undefined) return { metadata: {} }
|
|
51
|
+
const block = metadataBlock(region)
|
|
52
|
+
const name = format.kind === 'frontmatter' ? topLevelScalar(region, 'name') : undefined
|
|
53
|
+
return { metadata: block === undefined ? {} : asRecord(block.entries), ...(name === undefined ? {} : { name }) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function writeMetadata(text: string, format: MetadataFormat, metadata: Readonly<Record<string, string>>): string {
|
|
57
|
+
const lines = text.split('\n')
|
|
58
|
+
const entries = Object.entries(metadata)
|
|
59
|
+
if (format.kind === 'comment') {
|
|
60
|
+
const { prefix } = format
|
|
61
|
+
const block = entries.length === 0 ? [] : [`${prefix} /// metadata`, ...entries.map(([key, value]) => `${prefix} ${scalar(key)}: ${scalar(value)}`), `${prefix} ///`]
|
|
62
|
+
const region = commentRegion(lines, prefix)
|
|
63
|
+
if (region !== undefined) return [...lines.slice(0, region.start), ...block, ...lines.slice(region.end + 1)].join('\n')
|
|
64
|
+
if (block.length === 0) return text
|
|
65
|
+
const at = lines[0]?.startsWith('#!') === true ? 1 : 0
|
|
66
|
+
return [...lines.slice(0, at), ...block, ...lines.slice(at)].join('\n')
|
|
67
|
+
}
|
|
68
|
+
const map = entries.length === 0 ? [] : ['metadata:', ...entries.map(([key, value]) => ` ${scalar(key)}: ${scalar(value)}`)]
|
|
69
|
+
if (format.kind === 'sidecar') {
|
|
70
|
+
const block = metadataBlock({ start: -1, end: lines.length, lines })
|
|
71
|
+
const rest = block === undefined ? lines : [...lines.slice(0, block.start), ...lines.slice(block.end)]
|
|
72
|
+
const kept = rest.join('\n').trim()
|
|
73
|
+
return [...(kept === '' ? [] : [kept]), ...map].join('\n') + (map.length === 0 && kept === '' ? '' : '\n')
|
|
74
|
+
}
|
|
75
|
+
const region = frontmatterRegion(lines)
|
|
76
|
+
if (region === undefined) return map.length === 0 ? text : ['---', ...map, '---', ...lines].join('\n')
|
|
77
|
+
const block = metadataBlock(region)
|
|
78
|
+
const inner = block === undefined ? [...region.lines, ...map] : [...lines.slice(region.start + 1, block.start), ...map, ...lines.slice(block.end, region.end)]
|
|
79
|
+
if (inner.every((line) => line.trim() === '')) return lines.slice(region.end + 1).join('\n')
|
|
80
|
+
return [...lines.slice(0, region.start + 1), ...inner, ...lines.slice(region.end)].join('\n')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function frontmatterRegion(lines: readonly string[]): Region | undefined {
|
|
84
|
+
if (lines[0]?.replace(/^\uFEFF/, '').trimEnd() !== '---') return undefined
|
|
85
|
+
const end = lines.findIndex((line, index) => index > 0 && line.trimEnd() === '---')
|
|
86
|
+
if (end === -1) throw new MetadataError(1, 'the frontmatter starts with --- and never ends. Close it with a --- line.')
|
|
87
|
+
return { start: 0, end, lines: lines.slice(1, end) }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function commentRegion(lines: readonly string[], prefix: string): Region | undefined {
|
|
91
|
+
const start = lines.findIndex((line) => line.trimEnd() === `${prefix} /// metadata`)
|
|
92
|
+
if (start === -1) return undefined
|
|
93
|
+
const content: string[] = []
|
|
94
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
95
|
+
const line = (lines[index] ?? '').trimEnd()
|
|
96
|
+
if (line === `${prefix} ///`) return { start, end: index, lines: content }
|
|
97
|
+
if (line !== prefix && !line.startsWith(`${prefix} `)) break
|
|
98
|
+
content.push(line.slice(prefix.length + 1))
|
|
99
|
+
}
|
|
100
|
+
throw new MetadataError(start + 1, `the metadata block never ends. Close it with a "${prefix} ///" line.`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function metadataBlock(region: Region): Block | undefined {
|
|
104
|
+
const at = region.lines.findIndex((line) => /^metadata\s*:/.test(line))
|
|
105
|
+
if (at === -1) return undefined
|
|
106
|
+
const lineNumber = region.start + 2 + at
|
|
107
|
+
const rest = withoutComment((region.lines[at] ?? '').replace(/^metadata\s*:/, '')).trim()
|
|
108
|
+
const start = region.start + 1 + at
|
|
109
|
+
if (rest.startsWith('{')) return { start, end: start + 1, entries: flowEntries(rest, lineNumber) }
|
|
110
|
+
if (rest !== '') throw new MetadataError(lineNumber, 'metadata holds a single value. Write a map of names to text, such as metadata: { mymod.key: value }.')
|
|
111
|
+
let index = at + 1
|
|
112
|
+
while (index < region.lines.length && (/^\s/.test(region.lines[index] ?? '') || (region.lines[index] ?? '').trim() === '')) index += 1
|
|
113
|
+
while (index > at + 1 && (region.lines[index - 1] ?? '').trim() === '') index -= 1
|
|
114
|
+
const children = region.lines.slice(at + 1, index)
|
|
115
|
+
return { start, end: region.start + 1 + index, entries: mapEntries(children, lineNumber + 1, indentOf(children)) }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function indentOf(lines: readonly string[]): number {
|
|
119
|
+
const first = lines.find((line) => line.trim() !== '' && !line.trim().startsWith('#'))
|
|
120
|
+
return first === undefined ? 0 : (/^\s*/.exec(first)?.[0].length ?? 0)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function mapEntries(lines: readonly string[], firstLine: number, indent: number): Entry[] {
|
|
124
|
+
const entries: Entry[] = []
|
|
125
|
+
lines.forEach((raw, index) => {
|
|
126
|
+
const lineNumber = firstLine + index
|
|
127
|
+
const line = withoutComment(raw)
|
|
128
|
+
if (line.trim() === '') return
|
|
129
|
+
const depth = /^\s*/.exec(line)?.[0].length ?? 0
|
|
130
|
+
if (depth !== indent) throw new MetadataError(lineNumber, 'a metadata value holds a list or a map. Each value is one line of text, and a list is one space-separated text.')
|
|
131
|
+
const body = line.trim()
|
|
132
|
+
if (body.startsWith('- ')) throw new MetadataError(lineNumber, 'metadata holds a list. Write a map of names to text, and a list as one space-separated text.')
|
|
133
|
+
entries.push(entryOf(body, lineNumber))
|
|
134
|
+
})
|
|
135
|
+
return entries
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function flowEntries(text: string, lineNumber: number): Entry[] {
|
|
139
|
+
if (!text.endsWith('}')) throw new MetadataError(lineNumber, 'the metadata map starts with { and does not end on the same line. Close it with }, or write one entry per line.')
|
|
140
|
+
const body = text.slice(1, -1).trim()
|
|
141
|
+
if (body === '') return []
|
|
142
|
+
return splitOutsideQuotes(body, ',', lineNumber)
|
|
143
|
+
.map((part) => part.trim())
|
|
144
|
+
.filter((part) => part !== '')
|
|
145
|
+
.map((part) => entryOf(part, lineNumber))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function entryOf(text: string, lineNumber: number): Entry {
|
|
149
|
+
const [keyText, ...rest] = splitOutsideQuotes(text, ':', lineNumber, true)
|
|
150
|
+
if (keyText === undefined || rest.length === 0) throw new MetadataError(lineNumber, `"${text}" has no ":". Write each entry as name: value.`)
|
|
151
|
+
const valueText = rest.join(':').trim()
|
|
152
|
+
if (valueText.startsWith('{') || valueText.startsWith('[') || valueText === '|' || valueText === '>') throw new MetadataError(lineNumber, `${keyText.trim()} holds a list or a map. Each value is one line of text, and a list is one space-separated text.`)
|
|
153
|
+
return { key: unquoted(keyText.trim(), lineNumber), value: unquoted(valueText, lineNumber) }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function splitOutsideQuotes(text: string, separator: string, lineNumber: number, needsSpace = false): string[] {
|
|
157
|
+
const parts: string[] = []
|
|
158
|
+
let quote: string | undefined
|
|
159
|
+
let current = ''
|
|
160
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
161
|
+
const char = text[index] as string
|
|
162
|
+
if (quote !== undefined) {
|
|
163
|
+
current += char
|
|
164
|
+
if (char === '\\' && quote === '"') {
|
|
165
|
+
current += text[index + 1] ?? ''
|
|
166
|
+
index += 1
|
|
167
|
+
} else if (char === quote) quote = undefined
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
const before = current.trimEnd().at(-1)
|
|
171
|
+
if ((char === '"' || char === "'") && (before === undefined || before === ':')) quote = char
|
|
172
|
+
const next = text[index + 1]
|
|
173
|
+
if (char === separator && (!needsSpace || next === undefined || next === ' ' || next === '\t')) {
|
|
174
|
+
parts.push(current)
|
|
175
|
+
current = ''
|
|
176
|
+
if (needsSpace) {
|
|
177
|
+
parts.push(text.slice(index + 1))
|
|
178
|
+
return parts
|
|
179
|
+
}
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
current += char
|
|
183
|
+
}
|
|
184
|
+
if (quote !== undefined) throw new MetadataError(lineNumber, `a ${quote === '"' ? 'double' : 'single'} quote never closes.`)
|
|
185
|
+
parts.push(current)
|
|
186
|
+
return parts
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function withoutComment(line: string): string {
|
|
190
|
+
let quote: string | undefined
|
|
191
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
192
|
+
const char = line[index]
|
|
193
|
+
if (quote !== undefined) {
|
|
194
|
+
if (char === '\\' && quote === '"') index += 1
|
|
195
|
+
else if (char === quote) quote = undefined
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
if (char === '"' || char === "'") quote = char
|
|
199
|
+
if (char === '#' && (index === 0 || /\s/.test(line[index - 1] ?? ''))) return line.slice(0, index)
|
|
200
|
+
}
|
|
201
|
+
return line
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function unquoted(text: string, lineNumber: number): string {
|
|
205
|
+
if (text.startsWith('"')) {
|
|
206
|
+
if (!text.endsWith('"') || text.length < 2) throw new MetadataError(lineNumber, `${text} opens a double quote that never closes.`)
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(text) as string
|
|
209
|
+
} catch {
|
|
210
|
+
throw new MetadataError(lineNumber, `${text} is not a double-quoted text. Escape a " inside it as \\".`)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (text.startsWith("'")) {
|
|
214
|
+
if (!text.endsWith("'") || text.length < 2) throw new MetadataError(lineNumber, `${text} opens a single quote that never closes.`)
|
|
215
|
+
return text.slice(1, -1).replaceAll("''", "'")
|
|
216
|
+
}
|
|
217
|
+
return text
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function topLevelScalar(region: Region, key: string): string | undefined {
|
|
221
|
+
const at = region.lines.findIndex((line) => line.startsWith(`${key}:`))
|
|
222
|
+
if (at === -1) return undefined
|
|
223
|
+
const text = withoutComment((region.lines[at] ?? '').slice(key.length + 1)).trim()
|
|
224
|
+
if (text === '') return undefined
|
|
225
|
+
try {
|
|
226
|
+
return unquoted(text, region.start + 2 + at)
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function asRecord(entries: readonly Entry[]): Readonly<Record<string, string>> {
|
|
233
|
+
return Object.freeze(Object.fromEntries(entries.map(({ key, value }) => [key, value])))
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const plain = /^[A-Za-z0-9_./+~$%^()=\\][^#]*$/
|
|
237
|
+
const special = /^(true|false|yes|no|on|off|null|~|[-+]?(\d[\d_]*)?\.?\d+([eE][-+]?\d+)?|0x[0-9a-fA-F]+|\.inf|\.nan)$/i
|
|
238
|
+
|
|
239
|
+
function scalar(text: string): string {
|
|
240
|
+
const isPlain = plain.test(text) && text === text.trim() && !text.includes(': ') && !text.endsWith(':') && !text.includes(' #') && !special.test(text) && !/["',{}[\]]/.test(text)
|
|
241
|
+
return isPlain ? text : JSON.stringify(text)
|
|
242
|
+
}
|
package/testing/fake-claude.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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
|
|
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,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAA;KAAE,CAAA;IAC1E,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,CAuK/F"}
|
package/testing/fake-claude.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { elements } from './fake-elements.js';
|
|
2
2
|
export function fakeClaude(plugin) {
|
|
3
3
|
const calls = [];
|
|
4
|
-
const fakes = { process: {}, fs: {}, http: {}, settings: {}, ui: {}, session: {}, prompt: {}, model: {}, agent: {}, clock: {}, cmod: {}, tool: {} };
|
|
4
|
+
const fakes = { process: {}, fs: {}, http: {}, settings: {}, config: {}, ui: {}, session: {}, prompt: {}, model: {}, agent: {}, clock: {}, cmod: {}, tool: {} };
|
|
5
5
|
const shown = { toasts: [], notes: [], prompts: [], logs: [], debug: [], statuses: [], openPanes: new Set(), commands: [], tools: [], schedules: [] };
|
|
6
6
|
const unplacedPanes = new Set();
|
|
7
7
|
let focusedPane;
|
|
@@ -103,6 +103,10 @@ export function fakeClaude(plugin) {
|
|
|
103
103
|
},
|
|
104
104
|
http: { fetch: rejected('http.fetch', () => fakes.http.fetch) },
|
|
105
105
|
settings: { read: rejected('settings.read', () => fakes.settings.read) },
|
|
106
|
+
config: {
|
|
107
|
+
list: rejected('config.list', () => fakes.config.list ?? (async () => [])),
|
|
108
|
+
set: rejected('config.set', () => fakes.config.set ?? (async ({ value }) => ({ value }))),
|
|
109
|
+
},
|
|
106
110
|
agent: {
|
|
107
111
|
list: rejected('agent.list', () => fakes.agent.list),
|
|
108
112
|
spawn: rejected('agent.spawn', () => fakes.agent.spawn),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-files.d.ts","sourceRoot":"","sources":["../src/testing/fake-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAGlD,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAAG,MAAM,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"fake-files.d.ts","sourceRoot":"","sources":["../src/testing/fake-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAGlD,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAuD7H"}
|
package/testing/fake-files.js
CHANGED
|
@@ -19,6 +19,9 @@ export function fakeFiles(files, links = {}) {
|
|
|
19
19
|
return below(path).length > 0 ? 'dir' : undefined;
|
|
20
20
|
};
|
|
21
21
|
const sizeOf = (path) => new TextEncoder().encode(contents.get(path) ?? '').length;
|
|
22
|
+
const modified = new Map();
|
|
23
|
+
let writes = 0;
|
|
24
|
+
const mtimeOf = (path) => modified.get(path) ?? 0;
|
|
22
25
|
return {
|
|
23
26
|
read: async (path) => {
|
|
24
27
|
const text = contents.get(realPathOf(path));
|
|
@@ -27,7 +30,10 @@ export function fakeFiles(files, links = {}) {
|
|
|
27
30
|
return text;
|
|
28
31
|
},
|
|
29
32
|
write: async (path, text) => {
|
|
30
|
-
|
|
33
|
+
const realPath = realPathOf(path);
|
|
34
|
+
contents.set(realPath, text);
|
|
35
|
+
writes += 1;
|
|
36
|
+
modified.set(realPath, writes);
|
|
31
37
|
},
|
|
32
38
|
list: async (path) => {
|
|
33
39
|
if (path === undefined)
|
|
@@ -40,7 +46,7 @@ export function fakeFiles(files, links = {}) {
|
|
|
40
46
|
const entry = resolve(folder, name);
|
|
41
47
|
if (Object.hasOwn(links, entry))
|
|
42
48
|
return { name, kind: 'other', size: 0, mtimeMs: 0, isLink: true };
|
|
43
|
-
return { name, kind: contents.has(entry) ? 'file' : 'dir', size: sizeOf(entry), mtimeMs:
|
|
49
|
+
return { name, kind: contents.has(entry) ? 'file' : 'dir', size: sizeOf(entry), mtimeMs: mtimeOf(entry), isLink: false };
|
|
44
50
|
});
|
|
45
51
|
},
|
|
46
52
|
exists: async (path) => kindOf(realPathOf(path)) !== undefined,
|
|
@@ -52,7 +58,7 @@ export function fakeFiles(files, links = {}) {
|
|
|
52
58
|
return { kind: 'other', size: 0, mtimeMs: 0, isLink };
|
|
53
59
|
if (kind === undefined)
|
|
54
60
|
throw new Error(`ENOENT: no such file or directory, stat '${path}'`);
|
|
55
|
-
return { kind, size: sizeOf(realPath), mtimeMs:
|
|
61
|
+
return { kind, size: sizeOf(realPath), mtimeMs: mtimeOf(realPath), isLink, ...(options?.resolve === true ? { realPath } : {}) };
|
|
56
62
|
},
|
|
57
63
|
};
|
|
58
64
|
}
|
package/testing.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { Args, ClassicHookInputs, CmodDependencies, EventResult, RenderComponent, UiInputArgument } from 'claude-code';
|
|
2
2
|
import type { Reply } from './jobs/slash-command.js';
|
|
3
3
|
import type { ModDefinition, ModEvent } from './mod.js';
|
|
4
|
+
import type { Options, OptionValues } from './options.js';
|
|
4
5
|
import type { RoutedEvent } from './runtime/hooks.js';
|
|
5
6
|
import { type Fakes, type Shown, type TestCall } from './testing/fake-claude.js';
|
|
6
7
|
import type { Slot, SlotProps } from './ui/slots.js';
|
|
7
8
|
export type { Fakes, Shown, TestCall } from './testing/fake-claude.js';
|
|
8
|
-
export type TestOptions<State extends object> = {
|
|
9
|
+
export type TestOptions<State extends object, Declared extends Options = Options> = {
|
|
9
10
|
readonly state?: {
|
|
10
11
|
readonly [Lifetime in keyof State]?: Partial<State[Lifetime]>;
|
|
11
12
|
};
|
|
13
|
+
readonly options?: Partial<OptionValues<Declared>>;
|
|
14
|
+
readonly permissions?: readonly string[];
|
|
12
15
|
readonly scope?: 'user' | 'project';
|
|
13
16
|
readonly projectRoot?: string;
|
|
14
17
|
readonly cwd?: string;
|
|
@@ -16,6 +19,7 @@ export type TestOptions<State extends object> = {
|
|
|
16
19
|
readonly [Name in keyof CmodDependencies]?: CmodDependencies[Name];
|
|
17
20
|
};
|
|
18
21
|
readonly files?: Readonly<Record<string, string>>;
|
|
22
|
+
readonly links?: Readonly<Record<string, string>>;
|
|
19
23
|
};
|
|
20
24
|
type FilledField = 'session_id' | 'transcript_path' | 'cwd' | 'hook_event_name' | 'tool_use_id';
|
|
21
25
|
export type TestInput<E extends ModEvent> = Omit<ClassicHookInputs[E], FilledField> & Partial<Pick<ClassicHookInputs[E], FilledField & keyof ClassicHookInputs[E]>>;
|
|
@@ -39,5 +43,5 @@ export type TestedMod<State extends object> = {
|
|
|
39
43
|
readonly fakes: Fakes;
|
|
40
44
|
readonly shown: Shown;
|
|
41
45
|
};
|
|
42
|
-
export declare function testMod<State extends object>(definition: ModDefinition<State>, options?: TestOptions<State>): TestedMod<State>;
|
|
46
|
+
export declare function testMod<State extends object, Declared extends Options = Options>(definition: ModDefinition<State, string, Declared>, options?: TestOptions<State, Declared>): TestedMod<State>;
|
|
43
47
|
//# sourceMappingURL=testing.d.ts.map
|
package/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,iBAAiB,EACjB,gBAAgB,EAGhB,WAAW,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
|
package/testing.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { storeFolder } from './records.js';
|
|
1
|
+
import { readSteps, storeFolder } from './records.js';
|
|
2
2
|
import { answerCall, notInstalled } from './runtime/dependencies.js';
|
|
3
3
|
import { toolInputOf } from './runtime/tool-calls.js';
|
|
4
4
|
import { createLifecycle } from './runtime/lifecycle.js';
|
|
5
|
+
import { MissingOptions } from './runtime/options.js';
|
|
5
6
|
import { fakeClaude } from './testing/fake-claude.js';
|
|
6
7
|
import { elements, findElement, rowsOf } from './testing/fake-elements.js';
|
|
7
8
|
import { fakeFiles } from './testing/fake-files.js';
|
|
9
|
+
import { listed } from './utils/text.js';
|
|
8
10
|
const defaultColumns = 80;
|
|
9
11
|
const defaultRows = 24;
|
|
10
12
|
const presentation = { isFullscreen: false, columns: defaultColumns };
|
|
@@ -32,25 +34,34 @@ export function testMod(definition, options = {}) {
|
|
|
32
34
|
return listAgents();
|
|
33
35
|
return [...firedAgents].map(([id, type]) => ({ id, type, description: '', status: 'running' }));
|
|
34
36
|
};
|
|
35
|
-
Object.assign(fake.fakes.fs, fakeFiles(options.files ?? {}));
|
|
37
|
+
Object.assign(fake.fakes.fs, fakeFiles(options.files ?? {}, options.links));
|
|
36
38
|
const starting = options.state;
|
|
37
39
|
const declared = Object.entries(definition.state ?? {});
|
|
38
40
|
const state = Object.fromEntries(declared.map(([lifetime, values]) => [lifetime, { ...values, ...starting?.[lifetime] }]));
|
|
39
|
-
const lifecycle = createLifecycle({ ...definition, state });
|
|
41
|
+
const lifecycle = createLifecycle({ ...definition, state }, () => true, (options.options ?? {}), false);
|
|
40
42
|
let started;
|
|
41
43
|
const start = async () => {
|
|
42
|
-
started ??= lifecycle.start(fake.claude, async (claude) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
started ??= lifecycle.start(fake.claude, async (claude) => {
|
|
45
|
+
const steps = await modSteps();
|
|
46
|
+
return {
|
|
47
|
+
name,
|
|
48
|
+
root,
|
|
49
|
+
version: '0.0.0',
|
|
50
|
+
store: storeFolder({ HOME: await claude.env.home(), XDG_DATA_HOME: await claude.env.dataHome() }),
|
|
51
|
+
isInstalled: true,
|
|
52
|
+
shouldRecord: false,
|
|
53
|
+
steps,
|
|
54
|
+
granted: options.permissions ?? steps.permissions ?? [],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
51
57
|
await started;
|
|
58
|
+
const { failure } = lifecycle;
|
|
59
|
+
if (failure instanceof MissingOptions) {
|
|
60
|
+
const given = failure.keys.map((key) => `${key}: …`).join(', ');
|
|
61
|
+
throw new Error(`${name} needs ${listed(failure.titles)}, which a person sets in /config. Give ${failure.keys.length === 1 ? 'it' : 'them'} to the tested mod: testMod(mod, { options: { ${given} } }).`);
|
|
62
|
+
}
|
|
52
63
|
if (lifecycle.phase !== 'active')
|
|
53
|
-
throw
|
|
64
|
+
throw failure ?? new Error(`${name} did not start: the lifecycle is ${lifecycle.phase}.`);
|
|
54
65
|
};
|
|
55
66
|
const route = async (event, input, below) => {
|
|
56
67
|
await start();
|
|
@@ -226,6 +237,11 @@ function plainOutput({ component, props }) {
|
|
|
226
237
|
function replyOf({ text, context }) {
|
|
227
238
|
return { ...(text === undefined ? {} : { text }), ...(context === undefined ? {} : { context: context.join('\n') }) };
|
|
228
239
|
}
|
|
240
|
+
async function modSteps() {
|
|
241
|
+
const manifest = '../../../package.json';
|
|
242
|
+
const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, () => undefined);
|
|
243
|
+
return readSteps(loaded) ?? {};
|
|
244
|
+
}
|
|
229
245
|
function installJsx() {
|
|
230
246
|
const scope = globalThis;
|
|
231
247
|
scope['h'] ??= (tag, props, ...children) => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MetadataFormat = {
|
|
2
|
+
readonly kind: 'frontmatter';
|
|
3
|
+
} | {
|
|
4
|
+
readonly kind: 'comment';
|
|
5
|
+
readonly prefix: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'sidecar';
|
|
8
|
+
};
|
|
9
|
+
export type ReadMetadata = {
|
|
10
|
+
readonly metadata: Readonly<Record<string, string>>;
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare class MetadataError extends Error {
|
|
14
|
+
readonly line: number;
|
|
15
|
+
constructor(line: number, message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare const sidecarSuffix = ".meta";
|
|
18
|
+
export declare function formatOf(path: string, firstLine: string | undefined): MetadataFormat;
|
|
19
|
+
export declare function readMetadata(text: string, format: MetadataFormat): ReadMetadata;
|
|
20
|
+
export declare function writeMetadata(text: string, format: MetadataFormat, metadata: Readonly<Record<string, string>>): string;
|
|
21
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/utils/metadata.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAEpJ,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACnD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAGxC;CACF;AAQD,eAAO,MAAM,aAAa,UAAU,CAAA;AAEpC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,CAUpF;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,YAAY,CAW/E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAyBtH"}
|