@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/docs/ui.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# UI
|
|
2
2
|
|
|
3
|
-
A mod draws in four
|
|
3
|
+
A mod draws in four ways: its own panes, the rows Claude Code draws (places), the blocks of Claude's replies (markdown places), and short messages: toasts, questions, and notices. All of it lives on `mod.ui`.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
mod.ui.pane(pane: Pane<State>): PaneHandle
|
|
7
|
-
mod.ui.render<
|
|
8
|
-
mod.ui.toast
|
|
9
|
-
mod.ui.
|
|
7
|
+
mod.ui.render<P extends Place>(place: P, Component: (props: PlaceProps<P>) => RenderElement): Disposable
|
|
8
|
+
mod.ui.toast: Toast
|
|
9
|
+
mod.ui.copy(text: string): Promise<void>
|
|
10
10
|
mod.ui.ask(question: string, options?: readonly string[] | AskOptions): Promise<string>
|
|
11
11
|
mod.ui.scroll(args: UiScrollArgs): Promise<UiScrollResult>
|
|
12
|
+
mod.ui.notice(callId: string, text: string | undefined): void
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
Claude Mod Manager (cmod) draws the mod again whenever a value in `mod.state` changes. A render reads `mod.state` and returns what to show. It never draws by hand.
|
|
@@ -45,13 +46,13 @@ export function Count({ label, count }: { readonly label: string; readonly count
|
|
|
45
46
|
}
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
An element
|
|
49
|
+
An element built outside a render, at module load or in a hook, holds its props until a render draws it. So a hook can build a toast's `description` and the band draws it later.
|
|
49
50
|
|
|
50
51
|
`drawWith(table, draw, markdown?)` is the function cmod draws a render with. A mod never calls it.
|
|
51
52
|
|
|
52
53
|
## Components
|
|
53
54
|
|
|
54
|
-
`ui/components.js` exports controls built from the elements and Claude Code's theme colors, so they follow the person's theme and look the same in every mod. Each is a component like the ones a mod writes: a function of props, used in a pane's `render` or a
|
|
55
|
+
`ui/components.js` exports controls built from the elements and Claude Code's theme colors, so they follow the person's theme and look the same in every mod. Each is a component like the ones a mod writes: a function of props, used in a pane's `render` or a place's component.
|
|
55
56
|
|
|
56
57
|
```ts
|
|
57
58
|
Tabs(props: { tabs: readonly { key: string; label: string }[]; selected: string; onSelect(key: string): void; children? })
|
|
@@ -62,7 +63,7 @@ Tooltip(props: { text: string; children? })
|
|
|
62
63
|
Help(props: { text: string; isOpen: boolean; onOpenChange(isOpen: boolean): void })
|
|
63
64
|
Dialog(props: { title: string; children?; actions })
|
|
64
65
|
Pagination(props: { page: number; pages: number; onPage(page: number): void })
|
|
65
|
-
|
|
66
|
+
Progress(props: { value: number; width?: number })
|
|
66
67
|
```
|
|
67
68
|
|
|
68
69
|
- `Tabs` draws a row of tabs over `children`. The selected tab's number is in the accent color and its label bold and underlined. Each other tab is a `Button` whose hotkey is its number, so a pane that holds the keyboard switches tabs by number, Tab, or a click. A hotkey is one digit, so only the first nine tabs have a number.
|
|
@@ -72,7 +73,7 @@ ProgressBar(props: { done: number; total: number; width?: number })
|
|
|
72
73
|
- `Help` draws a `?` that shows `text` the same way. A press of `?` keeps the card open until the next press, so the keyboard reaches it too. Keep `isOpen` in `mod.state`.
|
|
73
74
|
- `Dialog` lays out a dialog's title, body, and `actions`, the buttons in a row. Put the safe action first, as the `primary` Button with `autoFocus`.
|
|
74
75
|
- `Pagination` draws `p: Previous 2 of 5 n: Next`. `p` and `n` are its hotkeys, and an end it cannot pass is dim text.
|
|
75
|
-
- `
|
|
76
|
+
- `Progress` fills `value` percent of `width` columns, 24 unless given. Put the count or a label beside it in a `Text`, or pass it as a loading toast's `description`.
|
|
76
77
|
- `Tabs`, `Toggle`, `Help`, and `Pagination` hold no state of their own. Keep their value in `mod.state` and set it in the callback, as in React's controlled components.
|
|
77
78
|
|
|
78
79
|
A pane opened with `focus`, `closeOnEscape`, and `holdToasts` is a dialog. Claude Code shows it as a tab of the dock, beside the mod's other panes, and Escape closes it:
|
|
@@ -114,7 +115,7 @@ Open it with `mod.ui.pane(confirmPane).open({ focus: true })`. It draws as:
|
|
|
114
115
|
[ Keep it ] [ Delete ]
|
|
115
116
|
```
|
|
116
117
|
|
|
117
|
-
A pane with `Tabs`, a `Tooltip`, a `
|
|
118
|
+
A pane with `Tabs`, a `Tooltip`, a `Progress` beside its count, `Pagination`, and a `Toggle` with `Help` draws as:
|
|
118
119
|
|
|
119
120
|
```text
|
|
120
121
|
1: Controls 2: Split 3: Files
|
|
@@ -248,141 +249,166 @@ export const notes = defineMod({
|
|
|
248
249
|
})
|
|
249
250
|
```
|
|
250
251
|
|
|
251
|
-
##
|
|
252
|
+
## Places: change a row Claude Code draws
|
|
252
253
|
|
|
253
|
-
`mod.ui.render(
|
|
254
|
+
`mod.ui.render(place, Component)` draws in place of one of Claude Code's own rows. `mod.js` exports each place as a constant. A message type is a place too, so `Reply` is where Claude Code draws a reply:
|
|
254
255
|
|
|
255
|
-
|
|
|
256
|
+
| Place | What Claude Code draws there |
|
|
256
257
|
| --- | --- |
|
|
257
|
-
| `
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
270
|
-
| `
|
|
271
|
-
|
|
272
|
-
`Component` gets `
|
|
258
|
+
| `Reply` | One text block of Claude's reply |
|
|
259
|
+
| `Prompt` | A prompt row: the person's prompt, a task notification, or another session's message |
|
|
260
|
+
| `ToolCall` | A tool call's row |
|
|
261
|
+
| `ToolResult` | A tool call's result |
|
|
262
|
+
| `Notice` | A dim status line under the logo |
|
|
263
|
+
| `ToolGroup` | The condensed row of a group of tool calls |
|
|
264
|
+
| `ToolProgress` | A tool's progress line |
|
|
265
|
+
| `CommandOutput` | A slash command's output |
|
|
266
|
+
| `AskUserQuestion` | The dialog the AskUserQuestion tool opens |
|
|
267
|
+
| `Spinner` | The spinner line while Claude works |
|
|
268
|
+
| `TurnDuration` | The line that closes a turn |
|
|
269
|
+
| `SessionMode` | The session's modes |
|
|
270
|
+
| `PromptHint` | The prompt's hint line, such as `? for shortcuts` |
|
|
271
|
+
| `AbovePrompt` | The band above the prompt, where surveys draw |
|
|
272
|
+
|
|
273
|
+
`Component` gets `PlaceProps<P>`: the props Claude Code draws that row with, plus `Default`.
|
|
273
274
|
|
|
274
275
|
- `Default` draws what Claude Code, and the renders of other mods, would draw. Pass it props to change what it draws, such as `<Default hint="…" />`. Return `<Default />` to change nothing.
|
|
275
276
|
- A render adds to what it wraps. In `AbovePrompt`, drawing `<Default />` and a line under it keeps the lines of Claude Code and other mods.
|
|
276
277
|
- A render must draw the same for the same props. One that calls `Default` a different number of times on a second draw falls back to Claude Code's drawing.
|
|
277
|
-
- A render that throws falls back to Claude Code's drawing
|
|
278
|
-
- A mod renders each
|
|
279
|
-
- `
|
|
280
|
-
-
|
|
278
|
+
- A render that throws, or draws differently on a second pass, falls back to Claude Code's drawing, and cmod shows the error toast `The <Place> render failed` once, with the reason under it.
|
|
279
|
+
- A mod renders each place once. A second `mod.ui.render` of one place throws `<mod>: a render of <Place> is already added. Render each place once.`
|
|
280
|
+
- `render` returns a `Disposable`. `dispose()` takes the render back, and Claude Code draws the row as before ([mod.md](mod.md#disposable)).
|
|
281
|
+
- `ToolCall` changes a call's own row. A group of calls shows the `ToolGroup` row, which Claude Code builds from the stored message, so a mod that hides a tool's input also sets `isExpanded` on `ToolGroup` to unfold the group into `ToolCall` rows.
|
|
282
|
+
- No place covers the permission dialog. `mod.ui.notice` adds a line under it instead ([Notices](#notices)).
|
|
281
283
|
|
|
282
284
|
```tsx
|
|
283
|
-
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
285
|
+
import { AbovePrompt, defineMod, PromptHint, UserPromptSubmit } from '../node_modules/@cmodjs/core/mod.js'
|
|
284
286
|
import { Box, Text } from '../node_modules/@cmodjs/core/ui/elements.js'
|
|
285
|
-
import { slots } from '../node_modules/@cmodjs/core/ui/slots.js'
|
|
286
287
|
|
|
287
288
|
export const hints = defineMod({
|
|
288
289
|
name: 'hints',
|
|
289
290
|
state: { session: { prompts: 0 } },
|
|
290
291
|
setup(mod) {
|
|
291
|
-
mod.ui.render(
|
|
292
|
-
mod.ui.render(
|
|
292
|
+
mod.ui.render(PromptHint, ({ hint, Default }) => <Default hint={hint.toUpperCase()} />)
|
|
293
|
+
mod.ui.render(AbovePrompt, ({ hasSurvey, Default }) =>
|
|
293
294
|
hasSurvey ? <Default /> : (
|
|
294
295
|
<Box flexDirection="column">
|
|
295
296
|
<Default />
|
|
296
297
|
<Text dimColor>{`Prompts: ${mod.state.session.prompts}`}</Text>
|
|
297
298
|
</Box>
|
|
298
299
|
))
|
|
299
|
-
mod.on(
|
|
300
|
+
mod.on(UserPromptSubmit, () => {
|
|
300
301
|
mod.state.session.prompts += 1
|
|
301
302
|
})
|
|
302
303
|
},
|
|
303
304
|
})
|
|
304
305
|
```
|
|
305
306
|
|
|
306
|
-
`
|
|
307
|
+
`Place` is the type of every place, and `PlaceProps` the props of one. Type a component's props as `PlaceProps<typeof PromptHint>`.
|
|
307
308
|
|
|
308
|
-
## Markdown
|
|
309
|
+
## Markdown places: change the blocks of a reply
|
|
309
310
|
|
|
310
|
-
`ui/markdown.js` exports
|
|
311
|
+
Each markdown block of a reply is a place of its own. `ui/markdown.js` exports these places, apart from the rest, because it brings the markdown reader, about 230 KB that takes some 18 ms to load. A mod that imports none of them starts without it.
|
|
311
312
|
|
|
312
|
-
|
|
|
313
|
+
| Place | Block | Props beyond `text` and `source` |
|
|
313
314
|
| --- | --- | --- |
|
|
314
|
-
| `
|
|
315
|
-
| `
|
|
316
|
-
| `
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
315
|
+
| `Heading` | A heading | `depth`, 1 to 6 |
|
|
316
|
+
| `Paragraph` | A paragraph | none |
|
|
317
|
+
| `CodeBlock` | A code block | `lang`, `meta`, `value` |
|
|
318
|
+
| `BlockQuote` | A block quote | none |
|
|
319
|
+
| `List` | A list | `ordered`, `start`, `spread` |
|
|
320
|
+
| `Table` | A table | `align` |
|
|
321
|
+
| `ThematicBreak` | A `---` line | none |
|
|
322
|
+
| `HtmlBlock` | An HTML block | `value` |
|
|
322
323
|
|
|
323
324
|
- `text` is the block's plain text, one line per list item, table row, or quoted line. `source` is the block's markdown.
|
|
324
325
|
- `Default` draws the block as Claude Code would. `<Default source="…" />` draws other markdown in its place.
|
|
325
|
-
- A markdown
|
|
326
|
+
- A markdown render draws in Claude's replies and in every `Markdown` element of the mod's panes.
|
|
326
327
|
- While a reply streams in, Claude Code draws a block that is not finished yet. The mod draws it once it is.
|
|
327
|
-
- A render that throws falls back to Claude Code's drawing of the block and
|
|
328
|
+
- A render that throws falls back to Claude Code's drawing of the block, and shows the error toast once.
|
|
328
329
|
|
|
329
330
|
```tsx
|
|
330
331
|
import type { RenderElement } from 'claude-code'
|
|
331
|
-
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
332
|
+
import { defineMod, type PlaceProps } from '../node_modules/@cmodjs/core/mod.js'
|
|
333
|
+
import { CodeBlock } from '../node_modules/@cmodjs/core/ui/markdown.js'
|
|
332
334
|
import { Text } from '../node_modules/@cmodjs/core/ui/elements.js'
|
|
333
|
-
import { markdownSlots } from '../node_modules/@cmodjs/core/ui/markdown.js'
|
|
334
|
-
import type { SlotProps } from '../node_modules/@cmodjs/core/ui/slots.js'
|
|
335
335
|
|
|
336
|
-
function MermaidBlock({ lang, value, Default }:
|
|
336
|
+
function MermaidBlock({ lang, value, Default }: PlaceProps<typeof CodeBlock>): RenderElement {
|
|
337
337
|
return lang === 'mermaid' ? <Text>{`diagram: ${value}`}</Text> : <Default />
|
|
338
338
|
}
|
|
339
339
|
|
|
340
340
|
export const diagrams = defineMod({
|
|
341
341
|
name: 'diagrams',
|
|
342
342
|
setup(mod) {
|
|
343
|
-
mod.ui.render(
|
|
343
|
+
mod.ui.render(CodeBlock, MermaidBlock)
|
|
344
344
|
},
|
|
345
345
|
})
|
|
346
346
|
```
|
|
347
347
|
|
|
348
348
|
```ts
|
|
349
|
-
markdownBlocks<
|
|
349
|
+
markdownBlocks<P extends MarkdownPlace>(text: string, place: P): Omit<PlaceProps<P>, 'Default'>[]
|
|
350
350
|
```
|
|
351
351
|
|
|
352
|
-
`markdownBlocks` returns the props of every block
|
|
353
|
-
|
|
354
|
-
`MarkdownKind`, `MarkdownReader`, and `MarkdownPiece` are the types behind `markdownSlots`. A mod never builds them.
|
|
352
|
+
`markdownBlocks`, exported from `ui/markdown.js`, returns the props of every block at one markdown place in a markdown text, such as every code block of a reply. A mod reads the blocks of a reply it got in a hook with it, and a test calls a component with real props.
|
|
355
353
|
|
|
356
|
-
|
|
354
|
+
```ts
|
|
355
|
+
import { CodeBlock, markdownBlocks } from '../node_modules/@cmodjs/core/ui/markdown.js'
|
|
357
356
|
|
|
358
|
-
|
|
357
|
+
const diagrams = markdownBlocks(reply, CodeBlock).filter((block) => block.lang === 'mermaid')
|
|
358
|
+
```
|
|
359
359
|
|
|
360
|
-
##
|
|
360
|
+
## Notices
|
|
361
361
|
|
|
362
362
|
```ts
|
|
363
|
-
mod.ui.
|
|
363
|
+
mod.ui.notice(callId: string, text: string | undefined): void
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`notice` shows `text` as one line under the permission dialog that is open for the tool call `callId`, the call's `tool_use_id`, such as `checked by safe-delete`. `undefined` removes the line, and Claude Code removes it on its own once the call is decided. A call with no open dialog takes no line.
|
|
367
|
+
|
|
368
|
+
## Toasts
|
|
369
|
+
|
|
370
|
+
`mod.ui.toast` shows the person a short message in the band above the prompt, one row per toast, under the mod's name. Its shape follows [Sonner](https://sonner.emilkowal.ski/toast):
|
|
364
371
|
|
|
365
|
-
|
|
372
|
+
```ts
|
|
373
|
+
mod.ui.toast(message: string, options?: ToastOptions): ToastId
|
|
374
|
+
mod.ui.toast.success(message, options?) / .info / .warning / .error / .loading
|
|
375
|
+
mod.ui.toast.promise<T>(task: Promise<T> | (() => Promise<T>), messages: { loading: string; success: string | ((value: T) => string); error: string | ((error: unknown) => string) }): { unwrap(): Promise<T> }
|
|
376
|
+
mod.ui.toast.dismiss(id?: ToastId): void
|
|
377
|
+
|
|
378
|
+
type ToastOptions = {
|
|
379
|
+
id?: ToastId
|
|
380
|
+
description?: string | RenderElement
|
|
381
|
+
action?: { label: string; onClick(): unknown }
|
|
382
|
+
duration?: number
|
|
383
|
+
}
|
|
366
384
|
```
|
|
367
385
|
|
|
368
|
-
|
|
386
|
+
- Each type draws its icon: `✓` success, `ℹ` info, `⚠` warning, `✗` error, and a spinner for loading.
|
|
387
|
+
- A toast stays 4 seconds once the band draws it, or `duration` ms. `Infinity` keeps it until the person closes it, and a loading toast stays until it changes. The time waits while Claude Code shows a survey over the band.
|
|
388
|
+
- A toast with the `id` of a shown toast replaces it in place, so a loading toast turns into its success or error.
|
|
389
|
+
- A text `description` shows its first line dim under the message. A press on the message unfolds the whole text and stops the time, and a second press folds it. An element `description`, such as a `Progress`, draws as it is.
|
|
390
|
+
- `action` draws a button after the message. A press that throws shows an error toast with the cause.
|
|
391
|
+
- `✕` closes the toast. The band shows the newest 3, and `+N more` shows every toast.
|
|
392
|
+
- `promise` shows `loading` while `task` runs, then `success` or `error` in the same row. `unwrap()` resolves or rejects as `task` does.
|
|
369
393
|
|
|
370
394
|
```ts
|
|
371
|
-
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
395
|
+
import { defineMod, fs, SessionStart, sessions } from '../node_modules/@cmodjs/core/mod.js'
|
|
372
396
|
|
|
373
397
|
export const indexer = defineMod({
|
|
374
398
|
name: 'indexer',
|
|
375
399
|
setup(mod) {
|
|
376
|
-
mod.on(
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
for (const [index, entry] of entries.entries()) report({ done: index + 1, total: entries.length, label: entry.name })
|
|
380
|
-
})
|
|
400
|
+
mod.on(SessionStart, async () => {
|
|
401
|
+
const indexed = fs.list(sessions.current.project).then((entries) => entries.length)
|
|
402
|
+
mod.ui.toast.promise(indexed, { loading: 'Indexing files', success: (count) => `Indexed ${count} files`, error: 'Indexing failed' })
|
|
381
403
|
})
|
|
382
404
|
},
|
|
383
405
|
})
|
|
384
406
|
```
|
|
385
407
|
|
|
408
|
+
An error cmod catches for a mod, such as a hook that throws, shows as an error toast that stays. Its `Copy` button copies the mod's name and version, the `@cmodjs/core` version, the message, the whole cause, and the stack, for a bug report.
|
|
409
|
+
|
|
410
|
+
`mod.ui.copy(text)` puts `text` on the person's clipboard. It rejects with Claude Code's reason when the terminal cannot take it.
|
|
411
|
+
|
|
386
412
|
## Questions
|
|
387
413
|
|
|
388
414
|
```ts
|
|
@@ -394,14 +420,14 @@ type AskOptions = { options?: readonly string[]; header?: string; multiSelect?:
|
|
|
394
420
|
`ask` shows a question with 2 to 4 options and resolves the answer. Fewer than two options are padded with Yes and No, and the person may type an answer of their own. `header` is a short chip beside the question, 12 characters at most. `multiSelect` lets the person pick several, and the answer comes back joined by commas.
|
|
395
421
|
|
|
396
422
|
```ts
|
|
397
|
-
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
423
|
+
import { defineMod, process, sessions, Stop } from '../node_modules/@cmodjs/core/mod.js'
|
|
398
424
|
|
|
399
425
|
export const committer = defineMod({
|
|
400
426
|
name: 'committer',
|
|
401
427
|
setup(mod) {
|
|
402
|
-
mod.on(
|
|
428
|
+
mod.on(Stop, async () => {
|
|
403
429
|
const answer = await mod.ui.ask('Commit the changes?', { options: ['Commit', 'Wait'], header: 'Commit' })
|
|
404
|
-
if (answer === 'Commit') await
|
|
430
|
+
if (answer === 'Commit') await process.run(['git', 'commit', '-am', 'wip'], { cwd: sessions.current.project })
|
|
405
431
|
})
|
|
406
432
|
},
|
|
407
433
|
})
|
|
@@ -419,6 +445,12 @@ mod.ui.scroll(args: { to: 'start' | 'end' | { key: string } | { requestId: strin
|
|
|
419
445
|
<Button label="Latest" onPress={() => mod.ui.scroll({ to: 'end', in: 'log' })} />
|
|
420
446
|
```
|
|
421
447
|
|
|
448
|
+
A [message](session.md#message)'s `id` names the row Claude Code draws it under, so a history pane brings the conversation to the message a person presses. When one row holds several messages, such as a reply with its tool calls, each `id` ends in `:<n>`, and the row is the `id` before it:
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
<Button label={message.text} onPress={() => mod.ui.scroll({ to: { requestId: message.id.split(':')[0] } })} />
|
|
452
|
+
```
|
|
453
|
+
|
|
422
454
|
## Test the UI
|
|
423
455
|
|
|
424
|
-
`tested.lines(paneId)` reads an open pane as rows of text. `tested.lines(
|
|
456
|
+
`tested.lines(paneId)` reads an open pane as rows of text. `tested.lines(place, props)` draws a place render. `tested.press(paneId, key)` presses a button. `tested.shown.toasts` lists the toasts shown now, each as `{ type, message, description? }`. [testing.md](testing.md) covers each.
|
package/jobs/check.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/jobs/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/jobs/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAQpC,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAIlF,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CA8BjM"}
|
package/jobs/check.js
CHANGED
|
@@ -2,7 +2,8 @@ import { longestMs } from '../runtime/deadline.js';
|
|
|
2
2
|
import { callEffects } from '../utils/call-effects.js';
|
|
3
3
|
import { dirname } from '../vendor.js';
|
|
4
4
|
import { listed, messageOf } from '../utils/text.js';
|
|
5
|
-
import {
|
|
5
|
+
import { engineOf } from '../runtime/engine.js';
|
|
6
|
+
import { afterCall, targetWords, withinDeadline } from './context.js';
|
|
6
7
|
import { matchTarget, targetOf } from './permissions/match-target.js';
|
|
7
8
|
const defaultMs = 60_000;
|
|
8
9
|
export function check(options) {
|
|
@@ -25,7 +26,9 @@ export function check(options) {
|
|
|
25
26
|
return [];
|
|
26
27
|
try {
|
|
27
28
|
const { command, folder } = triggered;
|
|
28
|
-
const
|
|
29
|
+
const workTree = await workspace.scope?.workTreeOf(folder);
|
|
30
|
+
const { claude } = engineOf(job.mod);
|
|
31
|
+
const { exitCode, stdout, stderr } = await withinDeadline(job.mod, deadline, command.join(' '), () => claude.process.run(command, { ...(workTree === undefined ? {} : { cwd: workTree }), timeoutMs }));
|
|
29
32
|
return exitCode === 0 ? [] : [`${command.join(' ')} exited with ${exitCode}:\n${`${stdout}${stderr}`.trim()}`];
|
|
30
33
|
}
|
|
31
34
|
catch (error) {
|
package/jobs/context.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import type { JobContext
|
|
1
|
+
import type { JobContext } from '../mod.js';
|
|
2
2
|
import { type Deadline } from '../runtime/deadline.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Call } from '../utils/call-effects.js';
|
|
4
4
|
import { type Workspace } from './permissions/find-project-scope.js';
|
|
5
5
|
import { type Target } from './permissions/match-target.js';
|
|
6
|
-
export declare function workspaceReader({ mod
|
|
7
|
-
export declare function afterCall<State extends object>(job: JobContext<State>, contextAfter: (call:
|
|
8
|
-
export declare function
|
|
9
|
-
readonly call?: ToolCall;
|
|
6
|
+
export declare function workspaceReader({ mod }: JobContext): () => Promise<Workspace>;
|
|
7
|
+
export declare function afterCall<State extends object>(job: JobContext<State>, contextAfter: (call: Call, workspace: Workspace) => Promise<readonly string[]>, failed: (error: unknown) => readonly string[]): void;
|
|
8
|
+
export declare function inFolder(call: Call, folder: string, workspace: Workspace): Promise<Call & {
|
|
10
9
|
readonly folder: string;
|
|
11
|
-
}
|
|
12
|
-
export declare function
|
|
10
|
+
}>;
|
|
11
|
+
export declare function withinDeadline<Value>(mod: object, deadline: Deadline, subject: string, run: () => Value | Promise<Value>): Promise<Value>;
|
|
13
12
|
export declare function targetWords(target: Target): string;
|
|
14
13
|
//# sourceMappingURL=context.d.ts.map
|
package/jobs/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/jobs/context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/jobs/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAItE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAA;AACpD,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAA;AACtF,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAKrE,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,EAAE,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAmB7E;AAED,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,MAAM,EAAE,GAAG,IAAI,CAW3M;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAE5H;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAEzI;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASlD"}
|
package/jobs/context.js
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
import { beforeDeadline } from '../runtime/deadline.js';
|
|
2
|
-
import {
|
|
2
|
+
import { engineOf } from '../runtime/engine.js';
|
|
3
3
|
import { toolInputOf } from '../runtime/tool-calls.js';
|
|
4
4
|
import { listed } from '../utils/text.js';
|
|
5
5
|
import { findProjectScope } from './permissions/find-project-scope.js';
|
|
6
6
|
import { targetOf } from './permissions/match-target.js';
|
|
7
|
-
export function workspaceReader({ mod
|
|
7
|
+
export function workspaceReader({ mod }) {
|
|
8
|
+
const { claude } = engineOf(mod);
|
|
8
9
|
let session;
|
|
9
10
|
return async () => {
|
|
10
|
-
session ??= readSession(
|
|
11
|
+
session ??= readSession();
|
|
11
12
|
const [{ home, scope, fs }, projectRoot, cwd] = await Promise.all([session, claude.session.root(), claude.session.cwd()]);
|
|
12
13
|
return { projectRoot, cwd, home, fs, scope };
|
|
13
14
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
async function readSession() {
|
|
16
|
+
const fs = {
|
|
17
|
+
read: (path) => claude.fs.read(path),
|
|
18
|
+
stat: (path) => claude.fs.stat(path, { resolve: true }),
|
|
19
|
+
exists: (path) => claude.fs.exists(path),
|
|
20
|
+
};
|
|
21
|
+
const home = await claude.env.home();
|
|
22
|
+
if (home === undefined)
|
|
23
|
+
throw new Error('HOME is not set, so ~ in a path has no meaning. Start Claude Code with HOME set.');
|
|
24
|
+
return { home, fs, scope: await findProjectScope(claude.plugin.root, home, fs) };
|
|
25
|
+
}
|
|
25
26
|
}
|
|
26
27
|
export function afterCall(job, contextAfter, failed) {
|
|
28
|
+
const { router, toolCalls } = engineOf(job.mod);
|
|
27
29
|
const readWorkspace = workspaceReader(job);
|
|
28
|
-
|
|
29
|
-
const before = Promise.all([
|
|
30
|
-
await
|
|
30
|
+
router.add('tool.call', async (e, next) => {
|
|
31
|
+
const before = Promise.all([toolCalls.agentOf(e.tool_use_id).then((agent) => ({ tool: e.tool, input: toolInputOf(e), ...agent })), readWorkspace()]);
|
|
32
|
+
await Promise.allSettled([before]);
|
|
31
33
|
const result = await next(e);
|
|
32
34
|
if (result.deny !== undefined || result.isError === true)
|
|
33
35
|
return result;
|
|
@@ -35,63 +37,11 @@ export function afterCall(job, contextAfter, failed) {
|
|
|
35
37
|
return added.length === 0 ? result : { ...result, context: [...(result.context ?? []), ...added] };
|
|
36
38
|
});
|
|
37
39
|
}
|
|
38
|
-
async function
|
|
39
|
-
return {
|
|
40
|
-
}
|
|
41
|
-
export function modOf(job, { call, folder }, workspace, deadline) {
|
|
42
|
-
return modWithin(job, deadline, async () => workspace.scope?.workTreeOf(call !== undefined && 'path' in call ? call.path : folder));
|
|
40
|
+
export async function inFolder(call, folder, workspace) {
|
|
41
|
+
return { ...call, folder: (await workspace.scope?.workTreeOf('path' in call ? call.path : folder)) ?? folder };
|
|
43
42
|
}
|
|
44
|
-
export function
|
|
45
|
-
|
|
46
|
-
const within = (call, task) => beforeDeadline(claude, deadline, call, task);
|
|
47
|
-
const folderOf = async (cwd) => {
|
|
48
|
-
const folder = cwd ?? (await workTree());
|
|
49
|
-
return folder === undefined ? {} : { cwd: folder };
|
|
50
|
-
};
|
|
51
|
-
const overrides = {
|
|
52
|
-
process: {
|
|
53
|
-
async run(argv, init) {
|
|
54
|
-
const folder = await folderOf(init?.cwd);
|
|
55
|
-
const timeoutMs = Math.min(init?.timeoutMs ?? deadline.ms, deadline.longestMs ?? deadline.ms);
|
|
56
|
-
return beforeDeadline(claude, { ...deadline, ms: timeoutMs }, 'mod.process.run', mod.process.run(argv, { ...init, ...folder, timeoutMs }));
|
|
57
|
-
},
|
|
58
|
-
spawn: (argv, init) => spawnWithin(claude, deadline, folderOf(init?.cwd).then((folder) => mod.process.spawn(argv, { ...init, ...folder }))),
|
|
59
|
-
},
|
|
60
|
-
fs: {
|
|
61
|
-
read: (path) => within('mod.fs.read', mod.fs.read(path)),
|
|
62
|
-
write: (path, text) => within('mod.fs.write', mod.fs.write(path, text)),
|
|
63
|
-
list: (path) => within('mod.fs.list', mod.fs.list(path)),
|
|
64
|
-
exists: (path) => within('mod.fs.exists', mod.fs.exists(path)),
|
|
65
|
-
stat: (path, options) => within('mod.fs.stat', mod.fs.stat(path, options)),
|
|
66
|
-
find: (glob) => within('mod.fs.find', mod.fs.find(glob)),
|
|
67
|
-
},
|
|
68
|
-
http: { fetch: (url, init) => within('mod.http.fetch', mod.http.fetch(url, init)) },
|
|
69
|
-
dependencies: dependencyCalls(claude, within),
|
|
70
|
-
};
|
|
71
|
-
return Object.assign(Object.create(mod), overrides);
|
|
72
|
-
}
|
|
73
|
-
function spawnWithin(claude, deadline, started) {
|
|
74
|
-
async function* pieces() {
|
|
75
|
-
const stream = await started;
|
|
76
|
-
let finish = () => undefined;
|
|
77
|
-
const running = beforeDeadline(claude, deadline, 'mod.process.spawn', new Promise((resolve) => (finish = resolve)));
|
|
78
|
-
const passed = running.then(() => new Promise(() => undefined));
|
|
79
|
-
try {
|
|
80
|
-
for (;;) {
|
|
81
|
-
const piece = await Promise.race([stream.next(), passed]);
|
|
82
|
-
if (piece.done === true)
|
|
83
|
-
return piece.value;
|
|
84
|
-
yield piece.value;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
finally {
|
|
88
|
-
finish();
|
|
89
|
-
void stream.return?.();
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const result = started.then((stream) => stream.result);
|
|
93
|
-
result.catch(() => undefined);
|
|
94
|
-
return Object.assign(pieces(), { result });
|
|
43
|
+
export function withinDeadline(mod, deadline, subject, run) {
|
|
44
|
+
return beforeDeadline(engineOf(mod).claude, deadline, subject, Promise.resolve().then(run));
|
|
95
45
|
}
|
|
96
46
|
export function targetWords(target) {
|
|
97
47
|
const { key, patterns, exclusions } = targetOf(target);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Call } from '../../utils/call-effects.js';
|
|
2
2
|
import type { Workspace } from './find-project-scope.js';
|
|
3
3
|
import { type TargetCalls, type TargetKey, type TargetOf } from './match-target.js';
|
|
4
4
|
export type Rule<Mod> = {
|
|
5
5
|
[Key in TargetKey]: TargetOf<Key> & {
|
|
6
|
-
when?: (call: TargetCalls[Key]
|
|
6
|
+
when?: (call: TargetCalls[Key] & {
|
|
7
|
+
readonly folder: string;
|
|
8
|
+
}, mod: Mod) => boolean | Promise<boolean>;
|
|
7
9
|
reason?: string;
|
|
8
10
|
};
|
|
9
11
|
}[TargetKey];
|
|
@@ -17,5 +19,5 @@ export type Verdict = {
|
|
|
17
19
|
reason?: string;
|
|
18
20
|
};
|
|
19
21
|
export declare function stricterVerdict<Result extends Verdict>(first: Result, second: Result): Result;
|
|
20
|
-
export declare function decidePermission<Mod>(rules: PermissionRules<Mod>, use:
|
|
22
|
+
export declare function decidePermission<Mod>(rules: PermissionRules<Mod>, use: Call, workspace: Workspace, mod: Mod, within: (run: () => boolean | Promise<boolean>) => Promise<boolean>): Promise<Verdict | undefined>;
|
|
21
23
|
//# sourceMappingURL=decide-permission.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decide-permission.d.ts","sourceRoot":"","sources":["../../src/jobs/permissions/decide-permission.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6C,KAAK,
|
|
1
|
+
{"version":3,"file":"decide-permission.d.ts","sourceRoot":"","sources":["../../src/jobs/permissions/decide-permission.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6C,KAAK,IAAI,EAAE,MAAM,6BAA6B,CAAA;AAClG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAuE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAIxJ,MAAM,MAAM,IAAI,CAAC,GAAG,IAAI;KACrB,GAAG,IAAI,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG;QAClC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,GAAG;YAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;QACrG,MAAM,CAAC,EAAE,MAAM,CAAA;KAChB;CACF,CAAC,SAAS,CAAC,CAAA;AAEZ,MAAM,MAAM,eAAe,CAAC,GAAG,IAAI;IAAE,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;CAAE,CAAA;AAC5E,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAA;AAC/C,MAAM,MAAM,OAAO,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAI7D,wBAAgB,eAAe,CAAC,MAAM,SAAS,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7F;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAerN"}
|
|
@@ -3,16 +3,17 @@ import { messageOf } from '../../utils/text.js';
|
|
|
3
3
|
import { callBaseOf, callEffects } from '../../utils/call-effects.js';
|
|
4
4
|
import { folderInScope, matchTarget, targetOf } from './match-target.js';
|
|
5
5
|
import { resolve } from '../../vendor.js';
|
|
6
|
+
import { inFolder } from '../context.js';
|
|
6
7
|
const wordBoundary = '\\s;&|()<>\'"`=';
|
|
7
8
|
export function stricterVerdict(first, second) {
|
|
8
9
|
return strictness.indexOf(second.decision) > strictness.indexOf(first.decision) ? second : first;
|
|
9
10
|
}
|
|
10
|
-
export async function decidePermission(rules, use, workspace,
|
|
11
|
+
export async function decidePermission(rules, use, workspace, mod, within) {
|
|
11
12
|
try {
|
|
12
13
|
const effects = callEffects(use, workspace);
|
|
13
14
|
for (const decision of ['deny', 'ask']) {
|
|
14
15
|
for (const rule of rules[decision] ?? []) {
|
|
15
|
-
const outcome = await ruleOutcome(rule, use, effects, workspace,
|
|
16
|
+
const outcome = await ruleOutcome(rule, use, effects, workspace, mod, within);
|
|
16
17
|
if (outcome === undefined)
|
|
17
18
|
continue;
|
|
18
19
|
const reason = [rule.reason, outcome.failure === undefined ? undefined : `Its when check failed: ${outcome.failure}`].filter((part) => part !== undefined).join(' ');
|
|
@@ -25,7 +26,7 @@ export async function decidePermission(rules, use, workspace, modOf) {
|
|
|
25
26
|
return { decision: 'deny', reason: `The permissions job failed, so it denies the call: ${messageOf(error)}` };
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
|
-
async function ruleOutcome(rule, use, effects, workspace,
|
|
29
|
+
async function ruleOutcome(rule, use, effects, workspace, mod, within) {
|
|
29
30
|
const matched = await matchTarget(rule, use, effects, workspace);
|
|
30
31
|
const calls = matched.length > 0 ? matched : await unparsedMatches(rule, use, effects, workspace);
|
|
31
32
|
if (calls.length === 0)
|
|
@@ -36,7 +37,8 @@ async function ruleOutcome(rule, use, effects, workspace, modOf) {
|
|
|
36
37
|
for (const { call, access, folder } of calls) {
|
|
37
38
|
const fullCall = access?.contents === undefined ? call : { ...call, ...(await access.contents()) };
|
|
38
39
|
try {
|
|
39
|
-
|
|
40
|
+
const placed = await inFolder(fullCall, folder, workspace);
|
|
41
|
+
if (await within(() => when(placed, mod)))
|
|
40
42
|
return { failure: undefined };
|
|
41
43
|
}
|
|
42
44
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-project-scope.d.ts","sourceRoot":"","sources":["../../src/jobs/permissions/find-project-scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"find-project-scope.d.ts","sourceRoot":"","sources":["../../src/jobs/permissions/find-project-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAGjE,MAAM,MAAM,YAAY,GAAG;IAAE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAAE,CAAA;AAExF,MAAM,MAAM,SAAS,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAA;CAAE,CAAA;AAE3H,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAwB1H"}
|