@cmodjs/core 3.0.0 → 4.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 +39 -22
- package/docs/dependencies.md +27 -20
- package/docs/index.md +21 -17
- package/docs/install-steps.md +37 -29
- package/docs/jobs.md +25 -52
- package/docs/mod.md +94 -66
- package/docs/options.md +2 -2
- package/docs/permissions.md +7 -5
- package/docs/session.md +14 -12
- package/docs/testing.md +55 -32
- package/docs/ui.md +255 -263
- package/hotkeys.d.ts +22 -0
- package/hotkeys.d.ts.map +1 -0
- package/hotkeys.js +26 -0
- package/installer.d.ts +1 -1
- package/installer.d.ts.map +1 -1
- package/installer.js +11 -6
- package/jobs/check.d.ts +2 -1
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +2 -2
- package/jobs/context.d.ts +2 -1
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +1 -1
- package/jobs/permissions.d.ts +2 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +2 -2
- package/jobs/program.js +1 -1
- package/jobs/prompt.d.ts +2 -1
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +20 -13
- package/jobs/schedule.d.ts +2 -1
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +15 -1
- package/jobs/slash-command.d.ts +6 -2
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +6 -5
- package/jobs/tool.d.ts +6 -4
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +3 -3
- package/mod.d.ts +63 -42
- package/mod.d.ts.map +1 -1
- package/mod.js +11 -1
- package/package.json +1 -1
- package/records.d.ts +14 -8
- package/records.d.ts.map +1 -1
- package/records.js +44 -31
- package/register.d.ts.map +1 -1
- package/register.js +1 -2
- package/runtime/announce.d.ts +1 -1
- package/runtime/announce.d.ts.map +1 -1
- package/runtime/announce.js +6 -7
- package/runtime/before.d.ts +2 -2
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +1 -1
- package/runtime/claude.d.ts +0 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +4 -1
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +42 -14
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +2 -1
- package/runtime/engine.d.ts +5 -0
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.js +2 -2
- package/runtime/hooks.d.ts +1 -1
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hotkeys.d.ts +15 -0
- package/runtime/hotkeys.d.ts.map +1 -0
- package/runtime/hotkeys.js +58 -0
- package/runtime/installer.d.ts +5 -2
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +23 -12
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +190 -141
- package/runtime/options.js +2 -2
- package/runtime/pages.d.ts +28 -0
- package/runtime/pages.d.ts.map +1 -0
- package/runtime/pages.js +98 -0
- package/runtime/parts.d.ts +14 -0
- package/runtime/parts.d.ts.map +1 -0
- package/runtime/parts.js +63 -0
- package/runtime/permission-check.d.ts +2 -2
- package/runtime/permission-check.d.ts.map +1 -1
- package/runtime/permission-check.js +12 -4
- package/runtime/programs.js +1 -1
- package/runtime/router.d.ts +2 -2
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +1 -1
- package/runtime/sessions.d.ts +10 -9
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +14 -5
- package/runtime/state.d.ts +2 -1
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +1 -0
- package/runtime/toaster.d.ts +1 -1
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +10 -4
- package/runtime/ui.d.ts +87 -5
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +372 -109
- package/runtime/watches.d.ts +9 -0
- package/runtime/watches.d.ts.map +1 -0
- package/runtime/watches.js +73 -0
- package/runtime/widgets.d.ts +48 -0
- package/runtime/widgets.d.ts.map +1 -0
- package/runtime/widgets.js +219 -0
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +7 -2
- package/session/session.d.ts +11 -5
- package/session/session.d.ts.map +1 -1
- package/src/hotkeys.ts +51 -0
- package/src/installer.ts +8 -4
- package/src/jobs/check.ts +4 -3
- package/src/jobs/context.ts +3 -2
- package/src/jobs/permissions.ts +4 -3
- package/src/jobs/program.ts +1 -1
- package/src/jobs/prompt.ts +23 -14
- package/src/jobs/schedule.ts +16 -2
- package/src/jobs/slash-command.ts +10 -7
- package/src/jobs/tool.ts +7 -5
- package/src/mod.ts +59 -47
- package/src/records.ts +50 -31
- package/src/register.ts +2 -3
- package/src/runtime/announce.ts +6 -7
- package/src/runtime/before.ts +4 -4
- package/src/runtime/claude.ts +0 -1
- package/src/runtime/conversations.ts +66 -20
- package/src/runtime/dependencies.ts +2 -1
- package/src/runtime/engine.ts +5 -0
- package/src/runtime/grants.ts +2 -2
- package/src/runtime/hooks.ts +1 -0
- package/src/runtime/hotkeys.ts +80 -0
- package/src/runtime/installer.ts +24 -12
- package/src/runtime/lifecycle.ts +189 -136
- package/src/runtime/options.ts +2 -2
- package/src/runtime/pages.ts +127 -0
- package/src/runtime/parts.ts +72 -0
- package/src/runtime/permission-check.ts +13 -5
- package/src/runtime/programs.ts +1 -1
- package/src/runtime/router.ts +3 -3
- package/src/runtime/sessions.ts +24 -15
- package/src/runtime/state.ts +3 -1
- package/src/runtime/toaster.ts +10 -4
- package/src/runtime/ui.ts +405 -115
- package/src/runtime/watches.ts +75 -0
- package/src/runtime/widgets.ts +287 -0
- package/src/session/context/turn/message/message.ts +7 -2
- package/src/session/session.ts +10 -6
- package/src/testing/fake-claude.ts +9 -6
- package/src/testing/fake-elements.ts +7 -4
- package/src/testing/fake-sessions.ts +6 -4
- package/src/testing.ts +203 -46
- package/src/ui/areas.ts +27 -0
- package/src/ui/elements.ts +35 -0
- package/src/ui/markdown.ts +18 -18
- package/src/ui/panes.ts +73 -0
- package/src/ui/styles.ts +53 -0
- package/src/utils/subscription.ts +1 -0
- package/testing/fake-claude.d.ts +0 -1
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +9 -5
- package/testing/fake-elements.js +7 -4
- package/testing/fake-sessions.d.ts +2 -1
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +1 -0
- package/testing.d.ts +21 -6
- package/testing.d.ts.map +1 -1
- package/testing.js +206 -45
- package/ui/areas.d.ts +29 -0
- package/ui/areas.d.ts.map +1 -0
- package/ui/areas.js +6 -0
- package/ui/elements.d.ts +3 -0
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +36 -0
- package/ui/markdown.d.ts +14 -14
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +11 -11
- package/ui/panes.d.ts +71 -0
- package/ui/panes.d.ts.map +1 -0
- package/ui/panes.js +16 -0
- package/ui/styles.d.ts +44 -0
- package/ui/styles.d.ts.map +1 -0
- package/ui/styles.js +24 -0
- package/utils/subscription.d.ts +4 -0
- package/utils/subscription.d.ts.map +1 -0
- package/jobs/status-line.d.ts +0 -6
- package/jobs/status-line.d.ts.map +0 -1
- package/jobs/status-line.js +0 -39
- package/src/jobs/status-line.ts +0 -44
- package/src/ui/define-pane.ts +0 -27
- package/src/ui/places.ts +0 -49
- package/src/utils/disposable.ts +0 -3
- package/ui/define-pane.d.ts +0 -15
- package/ui/define-pane.d.ts.map +0 -1
- package/ui/define-pane.js +0 -12
- package/ui/places.d.ts +0 -43
- package/ui/places.d.ts.map +0 -1
- package/ui/places.js +0 -31
- package/utils/disposable.d.ts +0 -4
- package/utils/disposable.d.ts.map +0 -1
- /package/utils/{disposable.js → subscription.js} +0 -0
package/src/jobs/prompt.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Deadline } from '../runtime/deadline.js'
|
|
|
3
3
|
import { engineOf } from '../runtime/engine.js'
|
|
4
4
|
import { onStateChange } from '../runtime/state.js'
|
|
5
5
|
import { modNote } from '../session/context/turn/message/note.js'
|
|
6
|
+
import type { Subscription } from '../utils/subscription.js'
|
|
6
7
|
import { listed, messageOf } from '../utils/text.js'
|
|
7
8
|
import { callEffects, type Call } from '../utils/call-effects.js'
|
|
8
9
|
import { matchTarget, targetOf, type Target } from './permissions/match-target.js'
|
|
@@ -18,13 +19,13 @@ type PromptOptions<State extends object> = {
|
|
|
18
19
|
|
|
19
20
|
const deadline: Deadline = { ms: 5000, job: 'prompt' }
|
|
20
21
|
|
|
21
|
-
export function prompt<State extends object = Record<never, never>>({ name, prompt: text, after }: PromptOptions<State>): Job<
|
|
22
|
+
export function prompt<State extends object = Record<never, never>>({ name, prompt: text, after }: PromptOptions<State>): Job<Subscription, State> {
|
|
22
23
|
if (name.trim() === '') throw new Error('prompt: give the block a name. Claude reads the text under it.')
|
|
23
24
|
const targets = after === undefined ? [] : Array.isArray(after) ? after : [after]
|
|
24
25
|
for (const target of targets) targetOf(target)
|
|
25
26
|
|
|
26
27
|
return (job) => {
|
|
27
|
-
job.reserveName('prompt', name, `prompt: ${job.mod.name} adds the name "${name}" two times. Give each prompt its own name.`)
|
|
28
|
+
const reserved = job.reserveName('prompt', name, `prompt: ${job.mod.name} adds the name "${name}" two times. Give each prompt its own name.`)
|
|
28
29
|
|
|
29
30
|
const log = (reason: string) => job.mod.log(`prompt "${name}" added nothing: ${reason}`)
|
|
30
31
|
const textFor = async (input: PromptInput) => {
|
|
@@ -42,29 +43,30 @@ export function prompt<State extends object = Record<never, never>>({ name, prom
|
|
|
42
43
|
return value === undefined ? undefined : `# ${name}\n${value}`
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
job.announce(targets.length > 0 ? `the ${name} prompt after ${listed(targets.map(targetWords))}` : `the ${name} prompt`)
|
|
47
|
+
const added = targets.length > 0 ? addAfterCalls(job, targets, headedText, log) : addKeptCurrent(job, name, () => textFor({}), log)
|
|
48
|
+
return {
|
|
49
|
+
remove() {
|
|
50
|
+
added.remove()
|
|
51
|
+
reserved.remove()
|
|
52
|
+
},
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
function addKeptCurrent<State extends object>(job: JobContext<State>, name: string, textFor: () => Promise<string | undefined>, log: (reason: string) => void):
|
|
57
|
+
function addKeptCurrent<State extends object>(job: JobContext<State>, name: string, textFor: () => Promise<string | undefined>, log: (reason: string) => void): Subscription {
|
|
56
58
|
const { claude, router } = engineOf(job.mod)
|
|
57
59
|
let given: string | undefined
|
|
58
60
|
let hasStarted = false
|
|
59
61
|
let isQueued = false
|
|
60
62
|
let updates = Promise.resolve()
|
|
61
63
|
|
|
62
|
-
router.add('classic.SessionStart', (e, next) => {
|
|
64
|
+
const starting = router.add('classic.SessionStart', (e, next) => {
|
|
63
65
|
hasStarted = e.source === 'resume' || e.source === 'compact'
|
|
64
66
|
if (!hasStarted) given = undefined
|
|
65
67
|
return next(e)
|
|
66
68
|
})
|
|
67
|
-
router.add('prompt.context', async (e, next) => {
|
|
69
|
+
const adding = router.add('prompt.context', async (e, next) => {
|
|
68
70
|
const below = await next(e)
|
|
69
71
|
hasStarted = true
|
|
70
72
|
if (below.blocks.some((block) => block.name === name)) {
|
|
@@ -85,15 +87,22 @@ function addKeptCurrent<State extends object>(job: JobContext<State>, name: stri
|
|
|
85
87
|
if (added.deny !== undefined) log(`Claude Code refused the new text: ${added.deny}`)
|
|
86
88
|
})
|
|
87
89
|
}
|
|
88
|
-
onStateChange(job.mod.state, () => {
|
|
90
|
+
const following = onStateChange(job.mod.state, () => {
|
|
89
91
|
if (isQueued) return
|
|
90
92
|
isQueued = true
|
|
91
93
|
updates = updates.then(update).catch((error: unknown) => log(messageOf(error)))
|
|
92
94
|
})
|
|
95
|
+
return {
|
|
96
|
+
remove() {
|
|
97
|
+
starting.remove()
|
|
98
|
+
adding.remove()
|
|
99
|
+
following.remove()
|
|
100
|
+
},
|
|
101
|
+
}
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
function addAfterCalls<State extends object>(job: JobContext<State>, targets: Target[], textFor: (input: PromptInput) => Promise<string | undefined>, log: (reason: string) => void):
|
|
96
|
-
afterCall(
|
|
104
|
+
function addAfterCalls<State extends object>(job: JobContext<State>, targets: Target[], textFor: (input: PromptInput) => Promise<string | undefined>, log: (reason: string) => void): Subscription {
|
|
105
|
+
return afterCall(
|
|
97
106
|
job,
|
|
98
107
|
async (use, workspace) => {
|
|
99
108
|
const effects = callEffects(use, workspace)
|
package/src/jobs/schedule.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { Job } from '../mod.js'
|
|
2
2
|
import { onStateChange } from '../runtime/state.js'
|
|
3
3
|
import { engineOf } from '../runtime/engine.js'
|
|
4
|
+
import type { Subscription } from '../utils/subscription.js'
|
|
4
5
|
|
|
5
6
|
export type ScheduledPrompt = { readonly cron: string; readonly prompt: string }
|
|
6
7
|
|
|
7
8
|
const scheduleCounts = new WeakMap<object, number>()
|
|
8
9
|
|
|
9
|
-
export function schedule<State extends object = Record<never, never>>(entry: ScheduledPrompt | ((state: State) => ScheduledPrompt | undefined)): Job<
|
|
10
|
+
export function schedule<State extends object = Record<never, never>>(entry: ScheduledPrompt | ((state: State) => ScheduledPrompt | undefined)): Job<Subscription, State> {
|
|
10
11
|
if (typeof entry !== 'function') checked(entry)
|
|
11
12
|
|
|
12
13
|
return (job) => {
|
|
@@ -51,12 +52,25 @@ export function schedule<State extends object = Record<never, never>>(entry: Sch
|
|
|
51
52
|
.then((left) => (typeof left === 'string' ? remove(left) : undefined))
|
|
52
53
|
.then(update)
|
|
53
54
|
.catch((error: unknown) => report('failed', error))
|
|
54
|
-
onStateChange(mod.state, () => {
|
|
55
|
+
const following = onStateChange(mod.state, () => {
|
|
55
56
|
if (isQueued) return
|
|
56
57
|
isQueued = true
|
|
57
58
|
updates = updates.then(update).catch((error: unknown) => report('failed', error))
|
|
58
59
|
})
|
|
59
60
|
job.announce(typeof entry === 'function' ? 'a schedule from its state' : `a schedule at ${entry.cron}`)
|
|
61
|
+
return {
|
|
62
|
+
remove() {
|
|
63
|
+
following.remove()
|
|
64
|
+
updates = updates
|
|
65
|
+
.then(async () => {
|
|
66
|
+
if (current === undefined) return
|
|
67
|
+
const { id } = current
|
|
68
|
+
current = undefined
|
|
69
|
+
await remove(id)
|
|
70
|
+
})
|
|
71
|
+
.catch((error: unknown) => report('failed', error))
|
|
72
|
+
},
|
|
73
|
+
}
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommandRunResult } from 'claude-code'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Job, Mod } from '../mod.js'
|
|
3
3
|
import { parse } from '../vendor.js'
|
|
4
4
|
import type { Deadline } from '../runtime/deadline.js'
|
|
5
5
|
import { engineOf } from '../runtime/engine.js'
|
|
@@ -9,6 +9,8 @@ import { withinDeadline } from './context.js'
|
|
|
9
9
|
|
|
10
10
|
export type CommandReply = string | { text?: string; note?: string } | undefined
|
|
11
11
|
|
|
12
|
+
export type SlashCommand = { readonly name: string; remove(): void }
|
|
13
|
+
|
|
12
14
|
const deadline: Deadline = { ms: 30000, job: 'slashCommand' }
|
|
13
15
|
|
|
14
16
|
const commandName = /^[A-Za-z0-9_-]{1,64}$/
|
|
@@ -19,11 +21,11 @@ export function slashCommand<State extends object = Record<never, never>>(option
|
|
|
19
21
|
readonly argumentHint?: string
|
|
20
22
|
readonly immediate?: true
|
|
21
23
|
readonly reply: (input: { args: string; positionals: string[] }, mod: Mod<State>) => CommandReply | void | Promise<CommandReply | void>
|
|
22
|
-
}): Job<
|
|
24
|
+
}): Job<SlashCommand, State> {
|
|
23
25
|
const { name, description, argumentHint, immediate, reply } = options
|
|
24
26
|
return (job) => {
|
|
25
27
|
const { mod, announce, reserveName } = job
|
|
26
|
-
const { claude, router, report } = engineOf(mod)
|
|
28
|
+
const { claude, router, report, answerInput } = engineOf(mod)
|
|
27
29
|
if (!commandName.test(name)) throw new Error(`${mod.name}: "${name}" is not a slash command name. Use 1 to 64 letters, digits, "_", or "-", without the slash.`)
|
|
28
30
|
const reserved = reserveName('slashCommand', name, `${mod.name}: the slash command /${name} is already added. Give each slashCommand its own name.`)
|
|
29
31
|
|
|
@@ -41,15 +43,16 @@ export function slashCommand<State extends object = Record<never, never>>(option
|
|
|
41
43
|
const answering = router.add('command.run', async (e, next) => {
|
|
42
44
|
if (e.command !== registered) return next(e)
|
|
43
45
|
try {
|
|
44
|
-
return answerOf(mod.name, await withinDeadline(mod, deadline, `/${name}`, () => reply({ args: e.args, positionals: splitWords(e.args) }, mod)))
|
|
46
|
+
return await answerInput(async () => answerOf(mod.name, await withinDeadline(mod, deadline, `/${name}`, () => reply({ args: e.args, positionals: splitWords(e.args) }, mod))), () => next(e))
|
|
45
47
|
} catch (error) {
|
|
46
48
|
return { text: `/${name} failed: ${oneLine(error)}` }
|
|
47
49
|
}
|
|
48
50
|
})
|
|
49
51
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
name,
|
|
53
|
+
remove() {
|
|
54
|
+
answering.remove()
|
|
55
|
+
reserved.remove()
|
|
53
56
|
},
|
|
54
57
|
}
|
|
55
58
|
}
|
package/src/jobs/tool.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FromSchema, JSONSchema } from 'json-schema-to-ts'
|
|
2
|
-
import type {
|
|
2
|
+
import type { Job, Mod } from '../mod.js'
|
|
3
3
|
import { Validator } from '../vendor.js'
|
|
4
4
|
import { toolDeadline } from '../runtime/deadline.js'
|
|
5
5
|
import { engineOf } from '../runtime/engine.js'
|
|
@@ -9,12 +9,14 @@ import { withinDeadline } from './context.js'
|
|
|
9
9
|
|
|
10
10
|
const toolName = /^[A-Za-z0-9_-]{1,64}$/
|
|
11
11
|
|
|
12
|
+
export type Tool = { readonly name: string; remove(): void }
|
|
13
|
+
|
|
12
14
|
export function tool<const Schema extends object = { type: 'object' }, State extends object = Record<never, never>>(options: {
|
|
13
15
|
readonly name: string
|
|
14
16
|
readonly description: string
|
|
15
17
|
readonly inputSchema?: Schema
|
|
16
18
|
readonly execute: (input: Schema extends JSONSchema ? FromSchema<Schema> : never, mod: Mod<State>) => unknown
|
|
17
|
-
}): Job<
|
|
19
|
+
}): Job<Tool, State> {
|
|
18
20
|
const { name, description, inputSchema, execute } = options
|
|
19
21
|
return (job) => {
|
|
20
22
|
const { mod, announce, reserveName } = job
|
|
@@ -50,9 +52,9 @@ export function tool<const Schema extends object = { type: 'object' }, State ext
|
|
|
50
52
|
})
|
|
51
53
|
return {
|
|
52
54
|
name: `mcp__${claude.plugin.name}__${name}`,
|
|
53
|
-
|
|
54
|
-
answering.
|
|
55
|
-
named.
|
|
55
|
+
remove() {
|
|
56
|
+
answering.remove()
|
|
57
|
+
named.remove()
|
|
56
58
|
},
|
|
57
59
|
}
|
|
58
60
|
}
|
package/src/mod.ts
CHANGED
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
ModelCompleteOptions,
|
|
10
10
|
ModelCompleteRequest,
|
|
11
11
|
ModelCompleteResult,
|
|
12
|
-
PaneOpenArgs,
|
|
13
12
|
ProcessRunInit,
|
|
14
13
|
ProcessRunResult,
|
|
15
14
|
ProcessSpawnChunk,
|
|
@@ -17,20 +16,24 @@ import type {
|
|
|
17
16
|
ProcessSpawnResult,
|
|
18
17
|
RenderElement,
|
|
19
18
|
Settings,
|
|
20
|
-
UiScrollArgs,
|
|
21
|
-
UiScrollResult,
|
|
22
19
|
} from 'claude-code'
|
|
23
|
-
import type
|
|
20
|
+
import { checkHotkeys, type Hotkey, type HotkeyInit, type Hotkeys } from './hotkeys.js'
|
|
21
|
+
import { checkSteps, type Step } from './installer.js'
|
|
24
22
|
import { checkOptions, type Option, type Options, type OptionValues } from './options.js'
|
|
25
23
|
import { bound } from './runtime/bound.js'
|
|
26
24
|
import { Mod as modSender } from './session/context/turn/message/sender.js'
|
|
27
25
|
import { sessionEvents, type BeforeMethod, type CommandRun, type CommandRunAnswer, type OnMethod, type OnMethods, type Sessions } from './session/session.js'
|
|
28
|
-
import type {
|
|
29
|
-
import type {
|
|
30
|
-
import type {
|
|
26
|
+
import type { Widget, WidgetInit } from './ui/areas.js'
|
|
27
|
+
import type { Page, PageInit, Pane, PaneInit } from './ui/panes.js'
|
|
28
|
+
import type { Kind, Style, StyleInit } from './ui/styles.js'
|
|
31
29
|
import type { Matcher } from './utils/matcher.js'
|
|
30
|
+
import type { Subscription } from './utils/subscription.js'
|
|
32
31
|
|
|
33
|
-
export type {
|
|
32
|
+
export type { Subscription }
|
|
33
|
+
|
|
34
|
+
export type Timer = { readonly isRunning: boolean; stop(): void }
|
|
35
|
+
|
|
36
|
+
export type Reservation = { remove(): void }
|
|
34
37
|
|
|
35
38
|
export type ToastId = string | number
|
|
36
39
|
|
|
@@ -56,15 +59,6 @@ export type Toast = {
|
|
|
56
59
|
dismiss(id?: ToastId): void
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
export type PaneHandle = {
|
|
60
|
-
open(options?: Readonly<Pick<PaneOpenArgs, 'focus'>>): Promise<void>
|
|
61
|
-
close(): Promise<void>
|
|
62
|
-
toggle(options?: Readonly<Pick<PaneOpenArgs, 'focus'>>): Promise<void>
|
|
63
|
-
readonly isOpen: boolean
|
|
64
|
-
readonly isShown: boolean
|
|
65
|
-
readonly isFocused: boolean
|
|
66
|
-
}
|
|
67
|
-
|
|
68
62
|
export type Metadata = Readonly<Record<string, string>>
|
|
69
63
|
|
|
70
64
|
export type FileMatch = {
|
|
@@ -138,8 +132,8 @@ export type Imports = {
|
|
|
138
132
|
readonly user: SettingsFile
|
|
139
133
|
readonly project: SettingsFile
|
|
140
134
|
readonly local: SettingsFile
|
|
141
|
-
beforeFileChange(listener: SettingsFileChangeListener):
|
|
142
|
-
beforeFileChange(matcher: Matcher<SettingsFileChange>, listener: SettingsFileChangeListener):
|
|
135
|
+
beforeFileChange(listener: SettingsFileChangeListener): Subscription
|
|
136
|
+
beforeFileChange(matcher: Matcher<SettingsFileChange>, listener: SettingsFileChangeListener): Subscription
|
|
143
137
|
}
|
|
144
138
|
readonly model: { complete(request: ModelCompleteRequest, options?: ModelCompleteOptions): Promise<ModelCompleteResult> }
|
|
145
139
|
readonly fs: {
|
|
@@ -149,7 +143,7 @@ export type Imports = {
|
|
|
149
143
|
find(glob: string | readonly string[]): Promise<readonly FileMatch[]>
|
|
150
144
|
list(path?: string): Promise<Entry[]>
|
|
151
145
|
stat(path: string, options?: FsStatOptions): Promise<Stat>
|
|
152
|
-
onChange(matcher: { readonly path: string | readonly string[] }, listener: (change: FileChange) => unknown):
|
|
146
|
+
onChange(matcher: { readonly path: string | readonly string[] }, listener: (change: FileChange) => unknown): Subscription
|
|
153
147
|
}
|
|
154
148
|
readonly metadata: {
|
|
155
149
|
read(path: string): Promise<Metadata>
|
|
@@ -160,6 +154,7 @@ export type Imports = {
|
|
|
160
154
|
spawn(argv: readonly string[], init?: Omit<ProcessSpawnRequest, 'argv'>): HookStream<ProcessSpawnChunk, ProcessSpawnResult>
|
|
161
155
|
}
|
|
162
156
|
readonly http: { fetch(url: string, init?: HttpInit): Promise<HttpResponse> }
|
|
157
|
+
readonly clipboard: { write(text: string): Promise<void> }
|
|
163
158
|
}
|
|
164
159
|
|
|
165
160
|
export const sessions: Imports['sessions'] = {
|
|
@@ -168,7 +163,7 @@ export const sessions: Imports['sessions'] = {
|
|
|
168
163
|
},
|
|
169
164
|
list: (options) => bound().sessions.list(options),
|
|
170
165
|
search: (text, matcher, options) => bound().sessions.search(text, matcher, options),
|
|
171
|
-
...(Object.fromEntries(Object.keys(sessionEvents).map((named) => [`on${named}`, (...args: unknown[]) => (bound().sessions[`on${named}` as keyof OnMethods] as (...each: unknown[]) =>
|
|
166
|
+
...(Object.fromEntries(Object.keys(sessionEvents).map((named) => [`on${named}`, (...args: unknown[]) => (bound().sessions[`on${named}` as keyof OnMethods] as (...each: unknown[]) => Subscription)(...args)])) as OnMethods),
|
|
172
167
|
}
|
|
173
168
|
|
|
174
169
|
export const settings: Imports['settings'] = {
|
|
@@ -181,8 +176,8 @@ export const settings: Imports['settings'] = {
|
|
|
181
176
|
export const commands: Imports['commands'] = {
|
|
182
177
|
list: () => bound().commands.list(),
|
|
183
178
|
run: (name, args) => bound().commands.run(name, args),
|
|
184
|
-
beforeRun: ((...args: never[]) => (bound().commands.beforeRun as (...each: never[]) =>
|
|
185
|
-
onRun: ((...args: never[]) => (bound().commands.onRun as (...each: never[]) =>
|
|
179
|
+
beforeRun: ((...args: never[]) => (bound().commands.beforeRun as (...each: never[]) => Subscription)(...args)) as Imports['commands']['beforeRun'],
|
|
180
|
+
onRun: ((...args: never[]) => (bound().commands.onRun as (...each: never[]) => Subscription)(...args)) as Imports['commands']['onRun'],
|
|
186
181
|
}
|
|
187
182
|
|
|
188
183
|
export const config: Imports['config'] = {
|
|
@@ -217,30 +212,32 @@ export const http: Imports['http'] = {
|
|
|
217
212
|
fetch: (url, init) => bound().http.fetch(url, init),
|
|
218
213
|
}
|
|
219
214
|
|
|
220
|
-
export
|
|
215
|
+
export const clipboard: Imports['clipboard'] = {
|
|
216
|
+
write: (text) => bound().clipboard.write(text),
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export type Mod<State extends object = Record<never, never>, Declared extends Options = Options, Keys extends Hotkeys = Hotkeys> = {
|
|
221
220
|
readonly name: string
|
|
222
221
|
readonly folder: string
|
|
223
222
|
readonly dataFolder: string
|
|
224
223
|
readonly state: Readonly<State>
|
|
225
224
|
readonly options: OptionValues<Declared>
|
|
226
225
|
readonly permissions: Permissions
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
readonly hotkeys: { readonly [Key in keyof Keys]: Hotkey }
|
|
227
|
+
every(ms: number, listener: () => unknown): Timer
|
|
228
|
+
after(ms: number, listener: () => unknown): Timer
|
|
229
229
|
use<Handle>(job: Job<Handle, State>): Handle
|
|
230
230
|
log(text: string, options?: { readonly to?: 'debug' | 'transcript' }): void
|
|
231
|
-
readonly
|
|
232
|
-
|
|
233
|
-
render<P extends Place>(place: P, Component: (props: PlaceProps<P>) => RenderElement): Disposable
|
|
234
|
-
readonly toast: Toast
|
|
235
|
-
ask(question: string, options?: readonly string[] | AskOptions): Promise<string>
|
|
236
|
-
copy(text: string): Promise<void>
|
|
237
|
-
scroll(args: UiScrollArgs): Promise<UiScrollResult>
|
|
238
|
-
notice(callId: string, text: string | undefined): void
|
|
239
|
-
}
|
|
231
|
+
readonly panes: { add<Params = undefined>(pane: PaneInit<State, Params>): Pane<Params> }
|
|
232
|
+
readonly sidebar: { add<Params = undefined>(page: PageInit<State, Params>): Page<Params> }
|
|
240
233
|
readonly settings: {
|
|
241
|
-
|
|
242
|
-
open(
|
|
234
|
+
add<Params = undefined>(page: PageInit<State, Params>): Page<Params>
|
|
235
|
+
open(): Promise<void>
|
|
243
236
|
}
|
|
237
|
+
readonly widgets: { add(widget: WidgetInit<State>): Widget }
|
|
238
|
+
readonly styles: { add<K extends Kind>(style: StyleInit<K, State>): Style }
|
|
239
|
+
readonly toast: Toast
|
|
240
|
+
ask(question: string, options?: readonly string[] | AskOptions): Promise<string>
|
|
244
241
|
readonly dependencies: CmodDependencies
|
|
245
242
|
}
|
|
246
243
|
|
|
@@ -253,36 +250,51 @@ type StateGroups = {
|
|
|
253
250
|
readonly global?: object
|
|
254
251
|
}
|
|
255
252
|
|
|
256
|
-
type
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
type AnyApi = { readonly [method: string]: (input: never) => unknown } | void
|
|
254
|
+
|
|
255
|
+
type Api<Name extends string> = 0 extends 1 & CmodDependencies
|
|
256
|
+
? AnyApi
|
|
257
|
+
: Name extends keyof CmodDependencies
|
|
258
|
+
? { readonly [Method in keyof CmodDependencies[Name]]: CmodDependencies[Name][Method] extends (input: infer Input) => infer Result ? (input: Input) => Result | Awaited<Result> : never }
|
|
259
|
+
: AnyApi
|
|
259
260
|
|
|
260
|
-
export type ModDefinition<State extends StateGroups = Record<never, never>, Name extends string = string, Declared extends Options = Record<never, Option>> = {
|
|
261
|
+
export type ModDefinition<State extends StateGroups = Record<never, never>, Name extends string = string, Declared extends Options = Record<never, Option>, Keys extends Hotkeys = Record<never, HotkeyInit>> = {
|
|
261
262
|
readonly name: Name
|
|
262
263
|
readonly state?: State
|
|
263
264
|
readonly options?: Declared
|
|
264
|
-
readonly
|
|
265
|
+
readonly hotkeys?: Keys
|
|
265
266
|
readonly installer?: readonly Step<State>[]
|
|
266
|
-
setup(mod: Mod<State, Declared>):
|
|
267
|
+
setup(mod: Mod<State, Declared, Keys>): Api<Name> | Promise<Api<Name>>
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
export type JobContext<State extends object = Record<never, never>> = {
|
|
270
271
|
readonly mod: Mod<State>
|
|
271
272
|
announce(feature: string): void
|
|
272
|
-
reserveName(type: string, name: string, taken: string):
|
|
273
|
+
reserveName(type: string, name: string, taken: string): Reservation
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
export type Job<Handle, State extends object = Record<never, never>> = (context: JobContext<State>) => Handle
|
|
276
277
|
|
|
277
|
-
export function defineMod<State extends StateGroups = Record<never, never>, Name extends string = string, const Declared extends Options = Record<never, Option
|
|
278
|
+
export function defineMod<State extends StateGroups = Record<never, never>, Name extends string = string, const Declared extends Options = Record<never, Option>, const Keys extends Hotkeys = Record<never, HotkeyInit>>(
|
|
279
|
+
definition: ModDefinition<State, Name, Declared, Keys>,
|
|
280
|
+
): ModDefinition<State, Name, Declared, Keys> {
|
|
278
281
|
if (definition.name.trim() === '') throw new Error('defineMod: the mod needs a name, such as the name in .claude-plugin/plugin.json.')
|
|
279
282
|
if (definition.options !== undefined) checkOptions(definition.name, definition.options)
|
|
283
|
+
if (definition.hotkeys !== undefined) checkHotkeys(definition.name, definition.hotkeys)
|
|
284
|
+
if (definition.installer !== undefined) checkSteps(definition.name, definition.installer)
|
|
280
285
|
return definition
|
|
281
286
|
}
|
|
282
287
|
|
|
283
288
|
export type { Matcher } from './utils/matcher.js'
|
|
284
|
-
export type {
|
|
285
|
-
export {
|
|
289
|
+
export type { Combo, Hotkey, HotkeyInit, Key } from './hotkeys.js'
|
|
290
|
+
export type { OpenOptions, Page, PageInit, PageProps, Pane, PaneInit, PaneProps, ScrollOptions, ScrollTarget } from './ui/panes.js'
|
|
291
|
+
export type { Kind, Row, Style, StyleInit, StyleProps } from './ui/styles.js'
|
|
292
|
+
export type { Block } from './ui/markdown.js'
|
|
293
|
+
export { AskUserQuestion, CommandOutput, ToolGroup, ToolProgress } from './ui/styles.js'
|
|
294
|
+
export { AbovePrompt, BelowPrompt, Dashboard, TurnEnd, Working, type Area, type Widget, type WidgetContext, type WidgetInit } from './ui/areas.js'
|
|
295
|
+
export type { Step } from './installer.js'
|
|
296
|
+
export type { SlashCommand } from './jobs/slash-command.js'
|
|
297
|
+
export type { Tool } from './jobs/tool.js'
|
|
286
298
|
export { Session, type AnswerOptions, type BeforeMethods, type Surface, type Listener, type ListenerTime, type OnMethods, type ReadOptions, type RecordPart, type Refusal, type SearchOptions, type SessionScope, type SessionSend, type Sessions, type CurrentSession, type Placement, type ProjectSetup, type SessionStartAsked, type SessionStarted, type SessionEnded, type ModelChange, type CwdChange, type DirectoryAdd, type Worktree, type Notification, type Elicitation, type ElicitationResponse, type McpServer, type PermissionUpdate, type PermissionCheck, type PermissionRequest, type PermissionDenial, type BatchedCall, type ToolBatch, type Todo, type ReplyDisplay, type TaskOwner, type TaskStartArgs, type ToolCallArgs, type Usage, type CommandRun, type CommandRunAnswer } from './session/session.js'
|
|
287
299
|
export type { Context } from './session/context/context.js'
|
|
288
300
|
export type { Turn } from './session/context/turn/turn.js'
|
package/src/records.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkHotkeys, type HotkeyInit, type Hotkeys } from './hotkeys.js'
|
|
1
2
|
import { listed, messageOf } from './utils/text.js'
|
|
2
3
|
|
|
3
4
|
export type InstallRecord = {
|
|
@@ -15,7 +16,7 @@ export type Steps = {
|
|
|
15
16
|
install?: string
|
|
16
17
|
uninstall?: string
|
|
17
18
|
program?: string
|
|
18
|
-
|
|
19
|
+
hotkeys?: Hotkeys
|
|
19
20
|
permissions?: readonly string[]
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -39,7 +40,7 @@ export type ReadFile = (path: string) => Promise<string | undefined>
|
|
|
39
40
|
export type RunnerEvent =
|
|
40
41
|
| { type: 'progress'; done: number; total: number; label: string }
|
|
41
42
|
| { type: 'log'; text: string }
|
|
42
|
-
| { type: 'needs-consent'; sha256: string; install: string; uninstall: string;
|
|
43
|
+
| { type: 'needs-consent'; sha256: string; install: string; uninstall: string; hotkeys: string; permissions: readonly string[] }
|
|
43
44
|
| { type: 'done'; name: string; version?: string }
|
|
44
45
|
| { type: 'missing'; name: string }
|
|
45
46
|
| { type: 'retries'; when: 'changed' | 'later' }
|
|
@@ -139,7 +140,7 @@ export function readSteps(pkg: unknown): Steps | undefined {
|
|
|
139
140
|
...readCommand(steps, 'install'),
|
|
140
141
|
...readCommand(steps, 'uninstall'),
|
|
141
142
|
...(program === undefined ? {} : { program }),
|
|
142
|
-
...
|
|
143
|
+
...readHotkeys(steps['hotkeys']),
|
|
143
144
|
...(permissions.length === 0 ? {} : { permissions }),
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -173,6 +174,12 @@ function readPermissions(steps: Record<string, unknown>): string[] {
|
|
|
173
174
|
return items
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
export const grantFix = 'Turn it on in Settings.'
|
|
178
|
+
|
|
179
|
+
export const installFix = 'Install it in Plugins.'
|
|
180
|
+
|
|
181
|
+
export const secretFix = 'Set it in /config, where Claude Code keeps it in your keychain.'
|
|
182
|
+
|
|
176
183
|
export function permissionWords(item: string): string {
|
|
177
184
|
const split = item.indexOf(':')
|
|
178
185
|
const target = item.slice(split + 1)
|
|
@@ -211,12 +218,6 @@ export function isSettingCovered(granted: Iterable<string>, path: string): boole
|
|
|
211
218
|
return [...granted].some((each) => item === each || item.startsWith(`${each}.`))
|
|
212
219
|
}
|
|
213
220
|
|
|
214
|
-
export const settingsPagesMethod = 'cmod:settingsPages'
|
|
215
|
-
|
|
216
|
-
export const changelogPage = 'changelog'
|
|
217
|
-
|
|
218
|
-
export const openPageMethod = 'cmod:openPage'
|
|
219
|
-
|
|
220
221
|
export const pendingStepsMethod = 'cmod:pendingSteps'
|
|
221
222
|
|
|
222
223
|
export const finishStepsMethod = 'cmod:finishSteps'
|
|
@@ -239,6 +240,14 @@ export function consentPath(store: string): string {
|
|
|
239
240
|
return `${store}/consent.json`
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
export function pluginsChangedPath(store: string): string {
|
|
244
|
+
return `${store}/plugins-changed`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function widgetsPath(store: string): string {
|
|
248
|
+
return `${store}/widgets.json`
|
|
249
|
+
}
|
|
250
|
+
|
|
242
251
|
export function parseConsent(value: unknown, path: string): Record<string, string[]> {
|
|
243
252
|
if (value === undefined) return Object.create(null)
|
|
244
253
|
if (!isObject(value) || Object.values(value).some((items) => !Array.isArray(items) || items.some((item) => typeof item !== 'string'))) {
|
|
@@ -247,29 +256,35 @@ export function parseConsent(value: unknown, path: string): Record<string, strin
|
|
|
247
256
|
return Object.assign(Object.create(null), value) as Record<string, string[]>
|
|
248
257
|
}
|
|
249
258
|
|
|
250
|
-
function
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const command = typeof value === 'string' ? /^\/([A-Za-z0-9][A-Za-z0-9._:-]*)$/.exec(value)?.[1] : undefined
|
|
256
|
-
if (key.trim() === '' || command === undefined) throw new Error(`package.json "cmod.keys" binds "${key}" to ${JSON.stringify(value)}. Bind each key to one of the mod's commands, such as "shift+tab": "/mode".`)
|
|
257
|
-
commands[key] = command
|
|
258
|
-
}
|
|
259
|
-
return { keys: commands }
|
|
259
|
+
function readHotkeys(hotkeys: unknown): Steps {
|
|
260
|
+
if (hotkeys === undefined) return {}
|
|
261
|
+
const fix = 'cmod check writes it from the hotkeys defineMod declares, so run cmod check.'
|
|
262
|
+
if (!isObject(hotkeys) || !Object.values(hotkeys).every((each) => isObject(each) && typeof each['title'] === 'string' && typeof each['default'] === 'string')) throw new Error(`package.json "cmod.hotkeys" is not a map of hotkeys to their title and default combo. ${fix}`)
|
|
263
|
+
return { hotkeys: checkHotkeys('package.json "cmod"', hotkeys as Record<string, HotkeyInit>) }
|
|
260
264
|
}
|
|
261
265
|
|
|
262
|
-
export const coreVersion = '
|
|
266
|
+
export const coreVersion = '4.0.0'
|
|
263
267
|
|
|
264
|
-
export function
|
|
265
|
-
|
|
266
|
-
if (steps.permissions?.includes('tasks') === true) return '1.0.0'
|
|
267
|
-
if (steps.permissions !== undefined) return '0.2.0'
|
|
268
|
-
return Object.keys(steps.keys ?? {}).length > 0 ? '0.1.12' : '0.0.0'
|
|
268
|
+
export function hotkeyCommand(mod: string, hotkey: string): string {
|
|
269
|
+
return `${mod}--${hotkey}`
|
|
269
270
|
}
|
|
270
271
|
|
|
271
|
-
export function
|
|
272
|
-
return
|
|
272
|
+
export function hotkeyBindings(mod: string, hotkeys: Hotkeys | undefined): Record<string, string> {
|
|
273
|
+
return Object.fromEntries(Object.entries(hotkeys ?? {}).map(([name, { default: combo }]) => [combo, hotkeyCommand(mod, name)]))
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function boundCombos(keybindings: unknown): Map<string, string> {
|
|
277
|
+
const blocks = isObject(keybindings) && Array.isArray(keybindings['bindings']) ? keybindings['bindings'] : []
|
|
278
|
+
const combos = new Map<string, string>()
|
|
279
|
+
for (const block of blocks) {
|
|
280
|
+
if (!isObject(block) || block['context'] !== 'Chat' || !isObject(block['bindings'])) continue
|
|
281
|
+
for (const [combo, action] of Object.entries(block['bindings'])) if (typeof action === 'string' && action.startsWith('command:')) combos.set(action.slice('command:'.length), combo)
|
|
282
|
+
}
|
|
283
|
+
return combos
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function hotkeyWords(hotkeys: Hotkeys | undefined): string {
|
|
287
|
+
return listed(Object.values(hotkeys ?? {}).map(({ title, default: combo }) => `${combo} to ${title}`))
|
|
273
288
|
}
|
|
274
289
|
|
|
275
290
|
function readCommand(steps: Record<string, unknown>, key: 'install' | 'uninstall'): Steps {
|
|
@@ -282,6 +297,10 @@ function readCommand(steps: Record<string, unknown>, key: 'install' | 'uninstall
|
|
|
282
297
|
return { [key]: command }
|
|
283
298
|
}
|
|
284
299
|
|
|
300
|
+
export function bundleDefines(permissions: readonly string[]): Record<string, string> {
|
|
301
|
+
return permissions.includes('approve') ? {} : { 'globalThis.cmodWithoutApprove': 'true' }
|
|
302
|
+
}
|
|
303
|
+
|
|
285
304
|
export function scriptPaths(command: string): string[] {
|
|
286
305
|
const words = command
|
|
287
306
|
.split(/[\s;&|()<>]+/)
|
|
@@ -310,7 +329,7 @@ export async function scriptsSha256(steps: Steps, files: { read: ReadFile; list:
|
|
|
310
329
|
steps.install ?? '',
|
|
311
330
|
steps.uninstall ?? '',
|
|
312
331
|
steps.program ?? '',
|
|
313
|
-
...(steps.
|
|
332
|
+
...(steps.hotkeys === undefined ? [] : [JSON.stringify(Object.entries(steps.hotkeys).sort())]),
|
|
314
333
|
...(steps.permissions === undefined ? [] : [JSON.stringify([...steps.permissions].sort())]),
|
|
315
334
|
].join('\0')
|
|
316
335
|
for (const folder of [...folders].sort()) {
|
|
@@ -332,7 +351,7 @@ export function formatEvent(event: RunnerEvent): string {
|
|
|
332
351
|
case 'log':
|
|
333
352
|
return `log ${event.text}`
|
|
334
353
|
case 'needs-consent':
|
|
335
|
-
return `needs-consent ${[event.sha256, event.install, event.uninstall, event.
|
|
354
|
+
return `needs-consent ${[event.sha256, event.install, event.uninstall, event.hotkeys, ...event.permissions].join('\t')}`
|
|
336
355
|
case 'done':
|
|
337
356
|
return event.version === undefined ? `done ${event.name}` : `done ${event.name} ${event.version}`
|
|
338
357
|
case 'missing':
|
|
@@ -349,8 +368,8 @@ export function parseEvent(line: string): RunnerEvent {
|
|
|
349
368
|
if (progress !== null) return { type: 'progress', done: Number(progress[1]), total: Number(progress[2]), label: progress[3] ?? '' }
|
|
350
369
|
const consent = /^needs-consent (\S+)\t([^\t]*)\t([^\t]*)(?:\t(.*))?$/.exec(line)
|
|
351
370
|
if (consent !== null) {
|
|
352
|
-
const [
|
|
353
|
-
return { type: 'needs-consent', sha256: consent[1] as string, install: consent[2] as string, uninstall: consent[3] as string,
|
|
371
|
+
const [hotkeys = '', ...permissions] = (consent[4] ?? '').split('\t')
|
|
372
|
+
return { type: 'needs-consent', sha256: consent[1] as string, install: consent[2] as string, uninstall: consent[3] as string, hotkeys, permissions: permissions.filter((item) => item !== '') }
|
|
354
373
|
}
|
|
355
374
|
const done = /^done (\S+)(?: (\S+))?$/.exec(line)
|
|
356
375
|
if (done !== null) return { type: 'done', name: done[1] as string, ...(done[2] === undefined ? {} : { version: done[2] }) }
|
package/src/register.ts
CHANGED
|
@@ -21,7 +21,6 @@ async function startMod($: EngineInterface, eventInput: Frozen<Args<'session.sta
|
|
|
21
21
|
const claude: Claude = {
|
|
22
22
|
plugin: { name: $.plugin.name, root: $.plugin.root },
|
|
23
23
|
ui: {
|
|
24
|
-
status: (text) => $.ui.status(text),
|
|
25
24
|
log: (text, options) => $.ui.log(text, options),
|
|
26
25
|
notice: (toolUseId, text) => $.ui.notice(toolUseId, text),
|
|
27
26
|
invalidate: (event) => $.ui.invalidate(event),
|
|
@@ -112,7 +111,7 @@ function routeCallFailure(_$: EngineInterface, eventInput: Frozen<Args<'cmod.cal
|
|
|
112
111
|
|
|
113
112
|
export function registerMod<State extends object, Declared extends Options>(addHook: On, definition: ModDefinition<State, string, Declared>, options: PluginOptions): void {
|
|
114
113
|
permissionCheck = undefined
|
|
115
|
-
registered = definition
|
|
114
|
+
registered = definition
|
|
116
115
|
lifecycle = createLifecycle(definition, () => permissionCheck, options)
|
|
117
116
|
addHook('session.start', startMod)
|
|
118
117
|
addHook('classic.Setup', routeToMod).catch(routeFailure)
|
|
@@ -142,13 +141,13 @@ export function registerMod<State extends object, Declared extends Options>(addH
|
|
|
142
141
|
addHook('classic.PostCompact', routeToMod).catch(routeFailure)
|
|
143
142
|
addHook('classic.Stop', routeToMod).catch(routeFailure)
|
|
144
143
|
addHook('classic.StopFailure', routeToMod).catch(routeFailure)
|
|
145
|
-
addHook('classic.FileChanged', routeToMod).catch(routeFailure)
|
|
146
144
|
addHook('classic.WorktreeCreate', routeToMod).catch(routeFailure)
|
|
147
145
|
addHook('classic.WorktreeRemove', routeToMod).catch(routeFailure)
|
|
148
146
|
addHook('tool.call', routeToMod).catch(routeFailure)
|
|
149
147
|
addHook('prompt.submit', routeToMod).catch(routeFailure)
|
|
150
148
|
addHook('prompt.context', routeToMod).catch(routeFailure)
|
|
151
149
|
addHook('command.run', routeToMod).catch(routeFailure)
|
|
150
|
+
addHook('command.describe', routeToMod).catch(routeFailure)
|
|
152
151
|
addHook('session.measure', routeToMod).catch(routeFailure)
|
|
153
152
|
addHook('skill.prompt', routeToMod).catch(routeFailure)
|
|
154
153
|
addHook('ui.render', routeToMod).catch(routeFailure)
|