@cmodjs/core 0.3.0 → 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.
- package/docs/commands.md +50 -10
- package/docs/dependencies.md +12 -12
- package/docs/hooks.md +39 -27
- package/docs/index.md +29 -25
- package/docs/install-steps.md +9 -7
- package/docs/jobs.md +59 -111
- package/docs/mod.md +200 -174
- package/docs/options.md +2 -2
- package/docs/permissions.md +27 -16
- package/docs/session.md +248 -0
- package/docs/state.md +11 -7
- package/docs/testing.md +65 -22
- package/docs/ui.md +108 -76
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +5 -2
- package/jobs/context.d.ts +7 -8
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +23 -73
- package/jobs/permissions/decide-permission.d.ts +5 -3
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +6 -4
- package/jobs/permissions/find-project-scope.d.ts +1 -1
- package/jobs/permissions/find-project-scope.d.ts.map +1 -1
- package/jobs/permissions/find-project-scope.js +8 -11
- package/jobs/permissions/match-target.d.ts +2 -2
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions.d.ts +1 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +6 -4
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +8 -3
- package/jobs/prompt.d.ts +4 -2
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +16 -15
- package/jobs/schedule.d.ts +2 -2
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +9 -9
- package/jobs/slash-command.d.ts +4 -4
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +14 -9
- package/jobs/status-line.d.ts +1 -11
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +8 -12
- package/jobs/tool.d.ts +2 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +15 -9
- package/mod.d.ts +154 -75
- package/mod.d.ts.map +1 -1
- package/mod.js +94 -3
- package/options.d.ts +2 -2
- package/options.js +19 -19
- package/package.json +3 -3
- package/records.d.ts +10 -8
- package/records.d.ts.map +1 -1
- package/records.js +28 -17
- package/register.d.ts.map +1 -1
- package/register.js +17 -5
- package/runtime/announce.d.ts +18 -0
- package/runtime/announce.d.ts.map +1 -0
- package/runtime/announce.js +66 -0
- package/runtime/bound.d.ts +4 -0
- package/runtime/bound.d.ts.map +1 -0
- package/runtime/bound.js +9 -0
- package/runtime/claude.d.ts +3 -2
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +16 -0
- package/runtime/conversations.d.ts.map +1 -0
- package/runtime/conversations.js +291 -0
- package/runtime/deadline.d.ts +0 -1
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +1 -1
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +9 -0
- package/runtime/engine.d.ts +17 -0
- package/runtime/engine.d.ts.map +1 -0
- package/runtime/engine.js +10 -0
- package/runtime/grants.d.ts +4 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +63 -26
- package/runtime/hooks.d.ts +5 -4
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +10 -5
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +11 -9
- package/runtime/lifecycle.d.ts +3 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +316 -185
- package/runtime/metadata.d.ts +3 -1
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +21 -11
- package/runtime/options.d.ts +3 -1
- package/runtime/options.d.ts.map +1 -1
- package/runtime/options.js +4 -4
- package/runtime/programs.d.ts +8 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +39 -1
- package/runtime/router.d.ts +2 -1
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +11 -3
- package/runtime/sessions.d.ts +36 -0
- package/runtime/sessions.d.ts.map +1 -0
- package/runtime/sessions.js +235 -0
- package/runtime/state.d.ts +6 -2
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +46 -13
- package/runtime/toaster.d.ts +31 -0
- package/runtime/toaster.d.ts.map +1 -0
- package/runtime/toaster.js +176 -0
- package/runtime/ui.d.ts +6 -17
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +36 -112
- package/session/context/context.d.ts +10 -0
- package/session/context/context.d.ts.map +1 -0
- package/session/context/context.js +2 -0
- package/session/context/turn/message/document.d.ts +7 -0
- package/session/context/turn/message/document.d.ts.map +1 -0
- package/session/context/turn/message/document.js +1 -0
- package/session/context/turn/message/message.d.ts +54 -0
- package/session/context/turn/message/message.d.ts.map +1 -0
- package/session/context/turn/message/message.js +137 -0
- package/session/context/turn/message/note.d.ts +8 -0
- package/session/context/turn/message/note.d.ts.map +1 -0
- package/session/context/turn/message/note.js +1 -0
- package/session/context/turn/message/notice.d.ts +6 -0
- package/session/context/turn/message/notice.d.ts.map +1 -0
- package/session/context/turn/message/notice.js +1 -0
- package/session/context/turn/message/prompt.d.ts +8 -0
- package/session/context/turn/message/prompt.d.ts.map +1 -0
- package/session/context/turn/message/prompt.js +1 -0
- package/session/context/turn/message/reply.d.ts +6 -0
- package/session/context/turn/message/reply.d.ts.map +1 -0
- package/session/context/turn/message/reply.js +1 -0
- package/session/context/turn/message/sender.d.ts +14 -0
- package/session/context/turn/message/sender.d.ts.map +1 -0
- package/session/context/turn/message/sender.js +30 -0
- package/session/context/turn/message/skill.d.ts +7 -0
- package/session/context/turn/message/skill.d.ts.map +1 -0
- package/session/context/turn/message/skill.js +1 -0
- package/session/context/turn/message/tool-call.d.ts +9 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -0
- package/session/context/turn/message/tool-call.js +1 -0
- package/session/context/turn/message/tool-result.d.ts +8 -0
- package/session/context/turn/message/tool-result.d.ts.map +1 -0
- package/session/context/turn/message/tool-result.js +1 -0
- package/session/context/turn/turn.d.ts +12 -0
- package/session/context/turn/turn.d.ts.map +1 -0
- package/session/context/turn/turn.js +2 -0
- package/session/conversation.d.ts +64 -0
- package/session/conversation.d.ts.map +1 -0
- package/session/conversation.js +123 -0
- package/session/goal/goal.d.ts +10 -0
- package/session/goal/goal.d.ts.map +1 -0
- package/session/goal/goal.js +1 -0
- package/session/goal/task.d.ts +16 -0
- package/session/goal/task.d.ts.map +1 -0
- package/session/goal/task.js +3 -0
- package/session/session.d.ts +99 -0
- package/session/session.d.ts.map +1 -0
- package/session/session.js +1 -0
- package/src/jobs/check.ts +7 -4
- package/src/jobs/context.ts +24 -82
- package/src/jobs/permissions/decide-permission.ts +10 -8
- package/src/jobs/permissions/find-project-scope.ts +8 -14
- package/src/jobs/permissions/match-target.ts +3 -3
- package/src/jobs/permissions.ts +8 -6
- package/src/jobs/program.ts +7 -3
- package/src/jobs/prompt.ts +21 -24
- package/src/jobs/schedule.ts +14 -14
- package/src/jobs/slash-command.ts +20 -14
- package/src/jobs/status-line.ts +9 -21
- package/src/jobs/tool.ts +17 -11
- package/src/mod.ts +209 -97
- package/src/options.ts +22 -22
- package/src/records.ts +35 -23
- package/src/register.ts +17 -5
- package/src/runtime/announce.ts +74 -0
- package/src/runtime/bound.ts +12 -0
- package/src/runtime/claude.ts +4 -2
- package/src/runtime/conversations.ts +302 -0
- package/src/runtime/deadline.ts +2 -2
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/engine.ts +22 -0
- package/src/runtime/grants.ts +66 -24
- package/src/runtime/hooks.ts +19 -10
- package/src/runtime/installer.ts +10 -9
- package/src/runtime/lifecycle.ts +312 -180
- package/src/runtime/metadata.ts +33 -11
- package/src/runtime/options.ts +19 -4
- package/src/runtime/programs.ts +40 -1
- package/src/runtime/router.ts +11 -4
- package/src/runtime/sessions.ts +242 -0
- package/src/runtime/state.ts +50 -14
- package/src/runtime/toaster.ts +218 -0
- package/src/runtime/ui.ts +42 -139
- package/src/session/context/context.ts +12 -0
- package/src/session/context/turn/message/document.ts +8 -0
- package/src/session/context/turn/message/message.ts +165 -0
- package/src/session/context/turn/message/note.ts +9 -0
- package/src/session/context/turn/message/notice.ts +7 -0
- package/src/session/context/turn/message/prompt.ts +9 -0
- package/src/session/context/turn/message/reply.ts +7 -0
- package/src/session/context/turn/message/sender.ts +38 -0
- package/src/session/context/turn/message/skill.ts +8 -0
- package/src/session/context/turn/message/tool-call.ts +10 -0
- package/src/session/context/turn/message/tool-result.ts +9 -0
- package/src/session/context/turn/turn.ts +14 -0
- package/src/session/conversation.ts +169 -0
- package/src/session/goal/goal.ts +9 -0
- package/src/session/goal/task.ts +19 -0
- package/src/session/session.ts +94 -0
- package/src/testing/fake-claude.ts +22 -16
- package/src/testing/fake-elements.ts +3 -2
- package/src/testing/fake-files.ts +1 -1
- package/src/testing/fake-sessions.ts +203 -0
- package/src/testing.ts +112 -33
- package/src/ui/components.ts +3 -3
- package/src/ui/elements.ts +22 -8
- package/src/ui/markdown.ts +22 -26
- package/src/ui/places.ts +49 -0
- package/src/utils/call-effects.ts +3 -3
- package/src/utils/changelog.ts +32 -0
- package/src/utils/matcher.ts +37 -0
- package/src/utils/metadata.ts +17 -15
- package/src/utils/parse-shell.ts +9 -8
- package/src/utils/paths.ts +15 -1
- package/src/utils/text.ts +4 -0
- package/testing/fake-claude.d.ts +4 -1
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +18 -11
- package/testing/fake-elements.js +4 -2
- package/testing/fake-files.js +1 -1
- package/testing/fake-sessions.d.ts +74 -0
- package/testing/fake-sessions.d.ts.map +1 -0
- package/testing/fake-sessions.js +170 -0
- package/testing.d.ts +33 -13
- package/testing.d.ts.map +1 -1
- package/testing.js +92 -21
- package/ui/components.d.ts +2 -3
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +3 -3
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +19 -9
- package/ui/markdown.d.ts +35 -33
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +14 -16
- package/ui/places.d.ts +43 -0
- package/ui/places.d.ts.map +1 -0
- package/ui/places.js +31 -0
- package/utils/call-effects.d.ts +1 -1
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +2 -2
- package/utils/changelog.d.ts +9 -0
- package/utils/changelog.d.ts.map +1 -0
- package/utils/changelog.js +29 -0
- package/utils/matcher.d.ts +13 -0
- package/utils/matcher.d.ts.map +1 -0
- package/utils/matcher.js +32 -0
- package/utils/metadata.d.ts +3 -3
- package/utils/metadata.js +18 -14
- package/utils/parse-shell.js +9 -7
- package/utils/paths.d.ts +2 -0
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +13 -1
- package/utils/text.d.ts +1 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/src/ui/slots.ts +0 -36
- package/ui/slots.d.ts +0 -208
- package/ui/slots.d.ts.map +0 -1
- package/ui/slots.js +0 -19
package/src/ui/places.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Frozen, RenderComponent, RenderElement, RenderPropsOf } from 'claude-code'
|
|
2
|
+
import type { BlockProps, MarkdownPlace } from './markdown.js'
|
|
3
|
+
|
|
4
|
+
export const ToolGroup = 'toolGroup'
|
|
5
|
+
export const ToolProgress = 'toolProgress'
|
|
6
|
+
export const CommandOutput = 'commandOutput'
|
|
7
|
+
export const AskUserQuestion = 'askUserQuestion'
|
|
8
|
+
export const Spinner = 'spinner'
|
|
9
|
+
export const TurnDuration = 'turnDuration'
|
|
10
|
+
export const SessionMode = 'sessionMode'
|
|
11
|
+
export const PromptHint = 'promptHint'
|
|
12
|
+
export const AbovePrompt = 'abovePrompt'
|
|
13
|
+
|
|
14
|
+
const components = {
|
|
15
|
+
prompt: 'UserMessage',
|
|
16
|
+
reply: 'AssistantMessage',
|
|
17
|
+
toolCall: 'ToolUse',
|
|
18
|
+
toolResult: 'ToolResult',
|
|
19
|
+
notice: 'InfoNotice',
|
|
20
|
+
[ToolGroup]: 'ToolGroup',
|
|
21
|
+
[ToolProgress]: 'ToolProgress',
|
|
22
|
+
[CommandOutput]: 'CommandOutput',
|
|
23
|
+
[AskUserQuestion]: 'AskUserQuestion',
|
|
24
|
+
[Spinner]: 'Spinner',
|
|
25
|
+
[TurnDuration]: 'TurnDuration',
|
|
26
|
+
[SessionMode]: 'SessionMode',
|
|
27
|
+
[PromptHint]: 'PromptHint',
|
|
28
|
+
[AbovePrompt]: 'AbovePrompt',
|
|
29
|
+
} as const satisfies Readonly<Record<string, Exclude<RenderComponent, 'Pane'>>>
|
|
30
|
+
|
|
31
|
+
export type ComponentPlace = keyof typeof components
|
|
32
|
+
|
|
33
|
+
export type Place = ComponentPlace | MarkdownPlace
|
|
34
|
+
|
|
35
|
+
type ComponentProps = { readonly [P in ComponentPlace]: RenderPropsOf[(typeof components)[P]] }
|
|
36
|
+
|
|
37
|
+
type PropsOf<P extends Place> = P extends ComponentPlace ? ComponentProps[P] : P extends MarkdownPlace<infer Type> ? BlockProps<Type> : never
|
|
38
|
+
|
|
39
|
+
type DefaultOf<P extends Place> = P extends ComponentPlace ? Partial<Omit<ComponentProps[P], 'onScreen'>> : { readonly source?: string }
|
|
40
|
+
|
|
41
|
+
export type PlaceProps<P extends Place> = Frozen<PropsOf<P>> & { readonly Default: (props: Frozen<DefaultOf<P>>) => RenderElement }
|
|
42
|
+
|
|
43
|
+
export function componentOf(place: Place): Exclude<RenderComponent, 'Pane'> | undefined {
|
|
44
|
+
return typeof place === 'string' && Object.hasOwn(components, place) ? components[place] : undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function nameOf(place: Place): string {
|
|
48
|
+
return typeof place === 'string' ? `${place.charAt(0).toUpperCase()}${place.slice(1)}` : place.name
|
|
49
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { parseShell, type ShellCommand } from './parse-shell.js'
|
|
2
|
-
import { expandHome, type FileSystem } from './paths.js'
|
|
2
|
+
import { expandHome, ifMissing, type FileSystem } from './paths.js'
|
|
3
3
|
import { resolve } from '../vendor.js'
|
|
4
4
|
|
|
5
5
|
export type CallBase = { tool: string; input: Readonly<Record<string, unknown>>; agentId?: string; agentType?: string }
|
|
6
6
|
export type CommandCall = CallBase & { commands: [string, ...string[]][]; isFullyParsed: boolean }
|
|
7
7
|
export type FileCall = CallBase & { path: string; content?: string; previousContent?: string }
|
|
8
8
|
export type FetchCall = CallBase & { url: string }
|
|
9
|
-
export type
|
|
9
|
+
export type Call = CommandCall | FileCall | FetchCall | CallBase
|
|
10
10
|
|
|
11
11
|
export type Contents = { content?: string; previousContent?: string }
|
|
12
12
|
export type FileAccess = { path: string; contents: (() => Promise<Contents>) | undefined }
|
|
@@ -79,7 +79,7 @@ async function editedContents(use: CallBase, path: string, fs: FileSystem): Prom
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
async function previousContentOf(path: string, fs: FileSystem): Promise<string | undefined> {
|
|
82
|
-
const stat = await fs.stat(path).catch((
|
|
82
|
+
const stat = await fs.stat(path).catch(ifMissing(undefined))
|
|
83
83
|
return stat?.kind === 'file' ? fs.read(path) : undefined
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type ChangelogSection = { readonly version: string; readonly date?: string; readonly text: string }
|
|
2
|
+
|
|
3
|
+
export const unreleased = 'Unreleased'
|
|
4
|
+
|
|
5
|
+
const heading = /^##\s+\[?([^\]\s]+)\]?(?:\s+-\s+(\S+))?\s*$/
|
|
6
|
+
|
|
7
|
+
const linkReference = /^\[[^\]]+\]:\s/
|
|
8
|
+
|
|
9
|
+
export function changelogSections(markdown: string): ChangelogSection[] {
|
|
10
|
+
const sections: { readonly version: string; readonly date: string | undefined; readonly lines: string[] }[] = []
|
|
11
|
+
for (const line of markdown.split('\n')) {
|
|
12
|
+
const match = heading.exec(line)
|
|
13
|
+
if (match !== null) sections.push({ version: match[1] as string, date: match[2], lines: [] })
|
|
14
|
+
else if (!linkReference.test(line)) sections.at(-1)?.lines.push(line)
|
|
15
|
+
}
|
|
16
|
+
return sections.map(({ version, date, lines }) => ({ version, ...(date === undefined ? {} : { date }), text: lines.join('\n').trim() }))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function changeLines(text: string): string[] {
|
|
20
|
+
let group: string | undefined
|
|
21
|
+
return text.split('\n').flatMap((line) => {
|
|
22
|
+
const trimmed = line.trim()
|
|
23
|
+
const subheading = /^###\s+(.+)$/.exec(trimmed)?.[1]
|
|
24
|
+
if (subheading !== undefined) {
|
|
25
|
+
group = subheading
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
const item = /^[-*]\s+(.+)$/.exec(trimmed)?.[1] ?? trimmed
|
|
29
|
+
if (item === '') return []
|
|
30
|
+
return [group === undefined ? item : `${group}: ${item}`]
|
|
31
|
+
})
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type Matcher<Value> = Value extends string
|
|
2
|
+
? string | RegExp
|
|
3
|
+
: Value extends number | boolean | null | undefined
|
|
4
|
+
? Value
|
|
5
|
+
: Value extends readonly (infer Item)[]
|
|
6
|
+
? Matcher<Item>
|
|
7
|
+
: Value extends (...args: never[]) => unknown
|
|
8
|
+
? never
|
|
9
|
+
: Value extends object
|
|
10
|
+
? { readonly [Key in keyof Value]?: Matcher<Value[Key]> }
|
|
11
|
+
: Value
|
|
12
|
+
|
|
13
|
+
export type SentMatcher = string | number | boolean | null | { readonly regexp: string; readonly flags: string } | { readonly [key: string]: SentMatcher } | readonly SentMatcher[]
|
|
14
|
+
|
|
15
|
+
export function matches(value: unknown, matcher: unknown): boolean {
|
|
16
|
+
if (matcher instanceof RegExp) return matcher.test(String(value))
|
|
17
|
+
if (Array.isArray(matcher)) return matcher.some((each) => matches(value, each))
|
|
18
|
+
if (typeof matcher !== 'object' || matcher === null) return value === matcher
|
|
19
|
+
if (Array.isArray(value)) return value.some((each) => matches(each, matcher))
|
|
20
|
+
if (typeof value !== 'object' || value === null) return false
|
|
21
|
+
return Object.entries(matcher).every(([key, wanted]) => wanted === undefined || matches((value as Record<string, unknown>)[key], wanted))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function sentMatcher(matcher: unknown): SentMatcher {
|
|
25
|
+
if (matcher instanceof RegExp) return { regexp: matcher.source, flags: matcher.flags }
|
|
26
|
+
if (Array.isArray(matcher)) return matcher.map(sentMatcher)
|
|
27
|
+
if (typeof matcher !== 'object' || matcher === null) return matcher as SentMatcher
|
|
28
|
+
return Object.fromEntries(Object.entries(matcher).filter(([, value]) => value !== undefined).map(([key, value]) => [key, sentMatcher(value)]))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function receivedMatcher(sent: SentMatcher): unknown {
|
|
32
|
+
if (Array.isArray(sent)) return sent.map(receivedMatcher)
|
|
33
|
+
if (typeof sent !== 'object' || sent === null) return sent
|
|
34
|
+
const fields = sent as Readonly<Record<string, unknown>>
|
|
35
|
+
if (typeof fields['regexp'] === 'string' && typeof fields['flags'] === 'string' && Object.keys(fields).length === 2) return new RegExp(fields['regexp'], fields['flags'])
|
|
36
|
+
return Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, receivedMatcher(value as SentMatcher)]))
|
|
37
|
+
}
|
package/src/utils/metadata.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type MetadataFormat = { readonly
|
|
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 {
|
|
30
|
-
if (hashComments.includes(extension) || hashCommentNames.includes(name)) return {
|
|
31
|
-
if (slashComments.includes(extension)) return {
|
|
32
|
-
if (dashComments.includes(extension)) return {
|
|
33
|
-
if (extension === '' && firstLine?.startsWith('#!') === true) return {
|
|
34
|
-
return {
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
package/src/utils/parse-shell.ts
CHANGED
|
@@ -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 = {
|
|
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 ? {
|
|
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?.
|
|
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?.
|
|
673
|
-
if (flags.some((flag) => inlineFlags.includes(flag)) || stdin?.
|
|
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?.
|
|
705
|
-
if (stdin?.
|
|
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 {
|
package/src/utils/paths.ts
CHANGED
|
@@ -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>
|
|
@@ -18,7 +32,7 @@ export function relativePath(base: string, path: string): string | undefined {
|
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
export async function realPathOf(path: string, fs: FileSystem): Promise<string | undefined> {
|
|
21
|
-
const stat = await fs.stat(path).catch((
|
|
35
|
+
const stat = await fs.stat(path).catch(ifMissing(undefined))
|
|
22
36
|
if (stat !== undefined || path === '/') return stat?.realPath
|
|
23
37
|
const folder = await realPathOf(dirname(path), fs)
|
|
24
38
|
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
|
}
|
package/testing/fake-claude.d.ts
CHANGED
|
@@ -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,12 @@ 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'];
|
|
31
33
|
};
|
|
32
34
|
session: {
|
|
33
35
|
messages?: Claude['session']['messages'];
|
|
34
36
|
append?: Claude['session']['append'];
|
|
37
|
+
send?: Claude['session']['send'];
|
|
35
38
|
};
|
|
36
39
|
prompt: {
|
|
37
40
|
submit?: Claude['prompt']['submit'];
|
|
@@ -60,7 +63,6 @@ export type ShownSchedule = {
|
|
|
60
63
|
readonly prompt: string;
|
|
61
64
|
};
|
|
62
65
|
export type Shown = {
|
|
63
|
-
readonly toasts: string[];
|
|
64
66
|
readonly notes: string[];
|
|
65
67
|
readonly prompts: string[];
|
|
66
68
|
readonly logs: string[];
|
|
@@ -79,6 +81,7 @@ export type FakeClaude = {
|
|
|
79
81
|
readonly store: Map<string, unknown>;
|
|
80
82
|
settle(): Promise<void>;
|
|
81
83
|
};
|
|
84
|
+
export declare function calledAnswer(caller: string, answer: EventResult<'cmod.call'>): unknown;
|
|
82
85
|
export declare function fakeClaude(plugin: {
|
|
83
86
|
readonly name: string;
|
|
84
87
|
readonly root: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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"}
|
package/testing/fake-claude.js
CHANGED
|
@@ -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
|
|
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
|
},
|
|
@@ -88,6 +93,7 @@ export function fakeClaude(plugin) {
|
|
|
88
93
|
...[...unplacedPanes].map((id) => ({ id, title: id, isShown: false, isFocused: false, isPlaced: false })),
|
|
89
94
|
],
|
|
90
95
|
scroll: rejected('ui.scroll', () => fakes.ui.scroll ?? (async () => ({}))),
|
|
96
|
+
copy: rejected('ui.copy', () => fakes.ui.copy ?? (async () => ({ isCopied: true }))),
|
|
91
97
|
resolve: () => elements,
|
|
92
98
|
},
|
|
93
99
|
process: {
|
|
@@ -95,14 +101,14 @@ export function fakeClaude(plugin) {
|
|
|
95
101
|
spawn: faked('process.spawn', () => fakes.process.spawn),
|
|
96
102
|
},
|
|
97
103
|
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 ??
|
|
102
|
-
stat: rejected('fs.stat', () => fakes.fs.stat),
|
|
104
|
+
read: rejected('fs.read', () => fakes.fs.read ?? emptyDisk.read),
|
|
105
|
+
write: rejected('fs.write', () => fakes.fs.write ?? emptyDisk.write),
|
|
106
|
+
list: rejected('fs.list', () => fakes.fs.list ?? emptyDisk.list),
|
|
107
|
+
exists: rejected('fs.exists', () => fakes.fs.exists ?? emptyDisk.exists),
|
|
108
|
+
stat: rejected('fs.stat', () => fakes.fs.stat ?? emptyDisk.stat),
|
|
103
109
|
},
|
|
104
110
|
http: { fetch: rejected('http.fetch', () => fakes.http.fetch) },
|
|
105
|
-
settings: { read: rejected('settings.read', () => fakes.settings.read) },
|
|
111
|
+
settings: { read: rejected('settings.read', () => fakes.settings.read ?? (async () => ({}))) },
|
|
106
112
|
config: {
|
|
107
113
|
list: rejected('config.list', () => fakes.config.list ?? (async () => [])),
|
|
108
114
|
},
|
|
@@ -130,10 +136,11 @@ export function fakeClaude(plugin) {
|
|
|
130
136
|
root: async () => plugin.root,
|
|
131
137
|
cwd: async () => plugin.root,
|
|
132
138
|
model: async () => 'test-model',
|
|
133
|
-
usage: async () => ({ context: { window: 200000 }, rateLimits:
|
|
139
|
+
usage: async () => ({ startedAt: Date.parse('2026-01-01T00:00:00.000Z'), context: { window: 200000 }, rateLimits: [], cost: { usd: 0 } }),
|
|
134
140
|
surfaces: async () => ['terminal'],
|
|
135
141
|
messages: rejected('session.messages', () => fakes.session.messages),
|
|
136
142
|
append: rejected('session.append', () => fakes.session.append ?? appendNote),
|
|
143
|
+
send: rejected('session.send', () => fakes.session.send ?? (async () => ({ isDelivered: true }))),
|
|
137
144
|
},
|
|
138
145
|
prompt: { submit: rejected('prompt.submit', () => fakes.prompt.submit ?? submitPrompt) },
|
|
139
146
|
model: { complete: rejected('model.complete', () => fakes.model.complete) },
|
package/testing/fake-elements.js
CHANGED
|
@@ -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
|
|
123
|
-
|
|
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 }, () => '');
|
package/testing/fake-files.js
CHANGED
|
@@ -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(
|
|
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,74 @@
|
|
|
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
|
+
export type MessageDraft = {
|
|
13
|
+
readonly type: typeof Prompt;
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly from?: Sender;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: typeof Note;
|
|
18
|
+
readonly text: string;
|
|
19
|
+
readonly from?: Sender;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: typeof Reply;
|
|
22
|
+
readonly text: string;
|
|
23
|
+
} | {
|
|
24
|
+
readonly type: typeof ToolCall;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly input?: Readonly<Record<string, unknown>>;
|
|
27
|
+
readonly callId?: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly type: typeof ToolResult;
|
|
30
|
+
readonly callId: string;
|
|
31
|
+
readonly text: string;
|
|
32
|
+
readonly isError?: boolean;
|
|
33
|
+
} | {
|
|
34
|
+
readonly type: typeof Skill;
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly text: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly type: typeof Document;
|
|
39
|
+
readonly path: string;
|
|
40
|
+
readonly text: string;
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: typeof Notice;
|
|
43
|
+
readonly text: string;
|
|
44
|
+
};
|
|
45
|
+
export type FakeSession = Partial<Omit<SessionData, 'id'>> & {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly messages?: readonly MessageDraft[];
|
|
48
|
+
readonly goal?: string;
|
|
49
|
+
};
|
|
50
|
+
export type FakeSessions = {
|
|
51
|
+
answer(caller: string, method: string, input: unknown): unknown;
|
|
52
|
+
add(draft: MessageDraft, task?: string): Message[];
|
|
53
|
+
endTurn(): void;
|
|
54
|
+
compact(): void;
|
|
55
|
+
startTask(task: {
|
|
56
|
+
readonly title: string;
|
|
57
|
+
readonly agentType?: string;
|
|
58
|
+
readonly model?: string;
|
|
59
|
+
readonly openedBy?: string;
|
|
60
|
+
}): string;
|
|
61
|
+
start(id: string): void;
|
|
62
|
+
readonly sent: {
|
|
63
|
+
readonly session: string;
|
|
64
|
+
readonly type: string;
|
|
65
|
+
readonly text: string;
|
|
66
|
+
readonly as?: string;
|
|
67
|
+
}[];
|
|
68
|
+
};
|
|
69
|
+
export declare function fakeSessions({ project, given, push }: {
|
|
70
|
+
readonly project: () => string;
|
|
71
|
+
readonly given: readonly FakeSession[];
|
|
72
|
+
readonly push: (mod: string, session: string, change: SessionChange) => void;
|
|
73
|
+
}): FakeSessions;
|
|
74
|
+
//# 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":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,6CAA6C,CAAA;AACtE,OAAO,EAAE,OAAO,EAAqC,MAAM,4CAA4C,CAAA;AACvG,OAAO,EAAE,IAAI,EAAE,MAAM,yCAAyC,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,0CAA0C,CAAA;AAChE,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,2CAA2C,CAAA;AAE9F,OAAO,EAAE,KAAK,EAAE,MAAM,0CAA0C,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,8CAA8C,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gDAAgD,CAAA;AAC3E,OAAO,EAA2F,KAAK,aAAa,EAAE,KAAK,WAAW,EAAgC,MAAM,4BAA4B,CAAA;AAKxM,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACvI;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAChH;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChF;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3D,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,GAAG;IAC3D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAcD,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;IAC/D,GAAG,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAAA;IAClD,OAAO,IAAI,IAAI,CAAA;IACf,OAAO,IAAI,IAAI,CAAA;IACf,SAAS,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAA;IACrI,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAClH,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;CAAE,GAAG,YAAY,CAqI7N"}
|