@cmodjs/core 4.0.3 → 4.1.1
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 +44 -9
- package/docs/index.md +3 -3
- package/docs/install-steps.md +8 -8
- package/docs/jobs.md +2 -1
- package/docs/mod.md +1 -1
- package/docs/options.md +1 -1
- package/docs/permissions.md +1 -1
- package/docs/session.md +46 -13
- package/docs/testing.md +15 -11
- package/docs/ui.md +43 -11
- package/installer.d.ts +3 -2
- package/installer.d.ts.map +1 -1
- package/jobs/program.d.ts +1 -0
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +39 -23
- package/mod.d.ts +3 -2
- package/mod.d.ts.map +1 -1
- package/mod.js +1 -0
- package/package.json +1 -1
- package/records.d.ts +15 -1
- package/records.d.ts.map +1 -1
- package/records.js +39 -1
- package/register.d.ts.map +1 -1
- package/register.js +8 -5
- package/runtime/before.d.ts +6 -2
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +56 -18
- package/runtime/buttons.d.ts +10 -0
- package/runtime/buttons.d.ts.map +1 -0
- package/runtime/buttons.js +12 -0
- package/runtime/conversations.d.ts +3 -0
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +164 -27
- package/runtime/engine.d.ts +2 -0
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.d.ts +1 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +5 -2
- package/runtime/hotkeys.d.ts.map +1 -1
- package/runtime/hotkeys.js +7 -4
- package/runtime/installer.d.ts +10 -6
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +106 -48
- package/runtime/lifecycle.d.ts +2 -2
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +144 -42
- package/runtime/pages.d.ts +3 -1
- package/runtime/pages.d.ts.map +1 -1
- package/runtime/pages.js +48 -14
- package/runtime/parts.d.ts +1 -0
- package/runtime/parts.d.ts.map +1 -1
- package/runtime/parts.js +6 -3
- package/runtime/programs.d.ts +1 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +4 -1
- package/runtime/sessions.d.ts +10 -3
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +104 -59
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +23 -9
- package/runtime/ui.d.ts +22 -4
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +161 -59
- package/runtime/watches.d.ts.map +1 -1
- package/runtime/watches.js +18 -4
- package/runtime/widgets.d.ts +7 -5
- package/runtime/widgets.d.ts.map +1 -1
- package/runtime/widgets.js +31 -21
- package/session/context/turn/message/attached-file.d.ts +13 -0
- package/session/context/turn/message/attached-file.d.ts.map +1 -0
- package/session/context/turn/message/attached-file.js +21 -0
- package/session/context/turn/message/message.d.ts +3 -2
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +20 -6
- package/session/conversation.d.ts +15 -3
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +23 -4
- package/session/goal/task.d.ts +5 -0
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +36 -7
- package/session/session.d.ts.map +1 -1
- package/session/session.js +2 -1
- package/src/installer.ts +3 -2
- package/src/jobs/program.ts +41 -21
- package/src/mod.ts +3 -2
- package/src/records.ts +49 -1
- package/src/register.ts +11 -8
- package/src/runtime/before.ts +61 -22
- package/src/runtime/buttons.ts +22 -0
- package/src/runtime/conversations.ts +170 -34
- package/src/runtime/engine.ts +2 -0
- package/src/runtime/grants.ts +6 -2
- package/src/runtime/hotkeys.ts +7 -4
- package/src/runtime/installer.ts +147 -83
- package/src/runtime/lifecycle.ts +149 -48
- package/src/runtime/pages.ts +56 -12
- package/src/runtime/parts.ts +8 -4
- package/src/runtime/programs.ts +5 -1
- package/src/runtime/sessions.ts +106 -59
- package/src/runtime/toaster.ts +27 -9
- package/src/runtime/ui.ts +166 -62
- package/src/runtime/watches.ts +20 -5
- package/src/runtime/widgets.ts +35 -23
- package/src/session/context/turn/message/attached-file.ts +36 -0
- package/src/session/context/turn/message/message.ts +20 -7
- package/src/session/conversation.ts +31 -6
- package/src/session/goal/task.ts +6 -0
- package/src/session/session.ts +18 -7
- package/src/testing/fake-claude.ts +21 -6
- package/src/testing/fake-elements.ts +57 -8
- package/src/testing/fake-sessions.ts +72 -24
- package/src/testing.ts +114 -33
- package/src/ui/areas.ts +1 -0
- package/src/ui/components.ts +14 -8
- package/src/ui/elements.ts +6 -2
- package/src/ui/markdown.ts +3 -3
- package/src/ui/panes.ts +2 -2
- package/src/ui/styles.ts +6 -2
- package/testing/fake-claude.d.ts +3 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +18 -5
- package/testing/fake-elements.d.ts.map +1 -1
- package/testing/fake-elements.js +52 -7
- package/testing/fake-sessions.d.ts +24 -10
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +66 -20
- package/testing.d.ts +8 -12
- package/testing.d.ts.map +1 -1
- package/testing.js +113 -27
- package/ui/areas.d.ts +4 -0
- package/ui/areas.d.ts.map +1 -1
- package/ui/components.d.ts +1 -0
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +11 -7
- package/ui/elements.d.ts +3 -0
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +6 -1
- package/ui/markdown.d.ts +2 -2
- package/ui/markdown.d.ts.map +1 -1
- package/ui/panes.d.ts +2 -1
- package/ui/panes.d.ts.map +1 -1
- package/ui/styles.d.ts +11 -2
- package/ui/styles.d.ts.map +1 -1
package/src/runtime/installer.ts
CHANGED
|
@@ -1,100 +1,161 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RenderElement } from 'claude-code'
|
|
2
2
|
import type { Step } from '../installer.js'
|
|
3
3
|
import type { Mod } from '../mod.js'
|
|
4
4
|
import type { Option } from '../options.js'
|
|
5
|
-
import { permissionWords, secretFix } from '../records.js'
|
|
6
|
-
import { Box,
|
|
5
|
+
import { isObject, permissionWords, secretFix } from '../records.js'
|
|
6
|
+
import { Box, elementData, Input, Text, type Handler } from '../ui/elements.js'
|
|
7
|
+
import { filledButton } from './buttons.js'
|
|
8
|
+
import type { PageProps } from '../ui/panes.js'
|
|
7
9
|
import { messageOf } from '../utils/text.js'
|
|
8
10
|
import type { Claude } from './claude.js'
|
|
11
|
+
import { cmodPluginName, withBareRefusals } from './dependencies.js'
|
|
12
|
+
import { merged, partMethods, stalePress, type DrawnPart, type PartPress, type PartsUpdate, type ShownPages } from './ui.js'
|
|
9
13
|
|
|
10
|
-
export const
|
|
14
|
+
export const installPage = 'cmod:install'
|
|
11
15
|
|
|
12
16
|
export type Consent = {
|
|
13
17
|
readonly install: string
|
|
14
18
|
readonly uninstall: string
|
|
19
|
+
readonly program: string
|
|
15
20
|
readonly hotkeys: string
|
|
16
21
|
readonly permissions: readonly string[]
|
|
17
22
|
readonly updates?: string | undefined
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
type
|
|
21
|
-
draw(props:
|
|
25
|
+
type Question = {
|
|
26
|
+
draw(props: PageProps<undefined>): RenderElement
|
|
22
27
|
cancel(): void
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export type Installer = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
closed(id: string): void
|
|
31
|
+
answer(method: string, input: unknown): Promise<{ readonly value: unknown }> | undefined
|
|
32
|
+
hear(shown: ShownPages): void
|
|
29
33
|
consent(consent: Consent): Promise<boolean>
|
|
30
34
|
options(missing: readonly (readonly [string, Option])[], save: (key: string, value: string) => Promise<string | undefined>): Promise<boolean>
|
|
31
35
|
step<State extends object>(mod: Mod<State>, step: Step<State>, index: number, total: number): Promise<boolean>
|
|
32
36
|
close(): Promise<void>
|
|
37
|
+
shown(): Promise<void>
|
|
38
|
+
collect(run: () => Promise<unknown>): Promise<PartsUpdate>
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const keptDraws = 8
|
|
42
|
+
|
|
43
|
+
export function createInstaller(name: string, claude: () => Claude, waiting: { show(open: () => Promise<void>): void; hide(): void }, nextDraw: () => number): Installer {
|
|
44
|
+
let question: Question | undefined
|
|
38
45
|
let isOpen = false
|
|
46
|
+
let isSeen = false
|
|
47
|
+
let waitingForShow: (() => void)[] = []
|
|
48
|
+
let collected: PartsUpdate[] | undefined
|
|
49
|
+
const draws = new Map<number, readonly Handler[]>()
|
|
39
50
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
await claude().
|
|
51
|
+
const send = async (update: PartsUpdate) => {
|
|
52
|
+
if (collected !== undefined) return void collected.push(update)
|
|
53
|
+
await withBareRefusals(claude()).cmod.call({ to: cmodPluginName, method: partMethods.changed, input: update })
|
|
43
54
|
}
|
|
44
55
|
|
|
45
|
-
const
|
|
46
|
-
|
|
56
|
+
const log = (what: string) => (error: unknown) => claude().ui.log(`${name}: ${what}: ${messageOf(error)}`, { to: 'debug' })
|
|
57
|
+
|
|
58
|
+
const open = (isAsked: boolean) => send({ requests: [{ open: installPage, focus: true }], ...(isAsked ? { isAsked: true } : {}) })
|
|
59
|
+
|
|
60
|
+
const show = async <Answer>(build: (answer: (value: Answer) => void) => Omit<Question, 'cancel'>, cancelled: Answer): Promise<Answer> => {
|
|
61
|
+
question?.cancel()
|
|
47
62
|
const answer = new Promise<Answer>((resolve) => {
|
|
63
|
+
let isAnswered = false
|
|
48
64
|
const done = (value: Answer) => {
|
|
49
|
-
if (
|
|
65
|
+
if (isAnswered) return
|
|
66
|
+
isAnswered = true
|
|
50
67
|
waiting.hide()
|
|
51
|
-
resolve(value)
|
|
68
|
+
claude().clock.after(0, () => resolve(value))
|
|
52
69
|
}
|
|
53
|
-
|
|
54
|
-
page = shown
|
|
55
|
-
})
|
|
56
|
-
waiting.show(open)
|
|
57
|
-
if (!isOpen) await open().catch((error: unknown) => {
|
|
58
|
-
waiting.hide()
|
|
59
|
-
throw error
|
|
70
|
+
question = { ...build(done), cancel: () => done(cancelled) }
|
|
60
71
|
})
|
|
61
|
-
|
|
72
|
+
waiting.show(() => open(true))
|
|
73
|
+
if (isOpen) {
|
|
74
|
+
await send({ redraw: true }).catch(log('the installer did not redraw'))
|
|
75
|
+
} else {
|
|
76
|
+
isOpen = true
|
|
77
|
+
await open(false).catch((error: unknown) => {
|
|
78
|
+
isOpen = false
|
|
79
|
+
waiting.hide()
|
|
80
|
+
throw error
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
for (const resolve of waitingForShow) resolve()
|
|
84
|
+
waitingForShow = []
|
|
62
85
|
return answer
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
const
|
|
66
|
-
|
|
88
|
+
const redraw = () => void send({ redraw: true }).catch(log('the installer did not redraw'))
|
|
89
|
+
|
|
90
|
+
const actions = (...buttons: readonly RenderElement[]) => Box({ gap: 2, flexShrink: 0, children: buttons })
|
|
91
|
+
|
|
92
|
+
const dialog = (title: RenderElement, ...children: readonly RenderElement[]) => Box({ flexDirection: 'column', gap: 1, children: [title, ...children] })
|
|
93
|
+
|
|
94
|
+
const bullet = (text: string) => Box({ gap: 1, children: [Text({ dimColor: true, children: '•' }), Text({ wrap: 'wrap', children: text })] })
|
|
67
95
|
|
|
68
|
-
const
|
|
96
|
+
const group = (heading: string, items: readonly string[]) => Box({ flexDirection: 'column', children: [Text({ bold: true, children: heading }), ...items.map(bullet)] })
|
|
97
|
+
|
|
98
|
+
const draw = (columns: number, isFocused: boolean): DrawnPart => {
|
|
99
|
+
if (question === undefined && !isOpen) claude().clock.after(0, () => void send({ requests: [{ close: installPage }] }).catch(log('a leftover install page stayed open')))
|
|
100
|
+
const handlers: Handler[] = []
|
|
101
|
+
const tree = elementData(question?.draw({ params: undefined, isFocused, columns }) ?? Text({ dimColor: true, children: `${name} needs nothing from you now.` }), handlers)
|
|
102
|
+
const drawn = nextDraw()
|
|
103
|
+
draws.set(drawn, handlers)
|
|
104
|
+
for (const kept of draws.keys()) if (draws.size > keptDraws) draws.delete(kept)
|
|
105
|
+
return { draw: drawn, tree }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const collect = async (run: () => unknown): Promise<PartsUpdate> => {
|
|
109
|
+
const outer = collected
|
|
110
|
+
const mine: PartsUpdate[] = []
|
|
111
|
+
collected = mine
|
|
112
|
+
try {
|
|
113
|
+
await run()
|
|
114
|
+
} finally {
|
|
115
|
+
collected = outer
|
|
116
|
+
}
|
|
117
|
+
return merged(mine)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const press = async ({ draw: drawn, handler, args }: PartPress): Promise<PartsUpdate> => {
|
|
121
|
+
const run = draws.get(drawn)?.[handler]
|
|
122
|
+
if (run === undefined) throw new Error(`${name}: ${stalePress}`)
|
|
123
|
+
return collect(() => run(...args))
|
|
124
|
+
}
|
|
69
125
|
|
|
70
126
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
127
|
+
answer(method, input) {
|
|
128
|
+
if (method === partMethods.draw && isObject(input) && input['page'] === installPage) return Promise.resolve({ value: draw(Number(input['columns']), input['isFocused'] === true) })
|
|
129
|
+
if (method === partMethods.press && isObject(input) && typeof input['draw'] === 'number' && draws.has(input['draw'])) return press(input as PartPress).then((value) => ({ value }))
|
|
130
|
+
return undefined
|
|
74
131
|
},
|
|
75
|
-
|
|
76
|
-
if (id
|
|
132
|
+
hear(shown) {
|
|
133
|
+
if (shown.open.some((each) => each.id === installPage)) {
|
|
134
|
+
isSeen = isOpen
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
if (!isSeen) return
|
|
77
138
|
isOpen = false
|
|
78
|
-
|
|
139
|
+
isSeen = false
|
|
140
|
+
question?.cancel()
|
|
79
141
|
},
|
|
80
|
-
consent({ install, uninstall, hotkeys, permissions, updates }) {
|
|
142
|
+
consent({ install, uninstall, program, hotkeys, permissions, updates }) {
|
|
81
143
|
const changes = [
|
|
82
144
|
...(install === '' ? [] : [`Run ${install} now${uninstall === '' ? '' : `, and ${uninstall} when you remove it`}`]),
|
|
83
145
|
...(install === '' && uninstall !== '' ? [`Run ${uninstall} when you remove it`] : []),
|
|
146
|
+
...(program === '' ? [] : [`Install ${program}, which Claude runs by its name`]),
|
|
84
147
|
...(hotkeys === '' ? [] : [`Bind ${hotkeys}`]),
|
|
85
148
|
...(updates === undefined ? [] : [`Update ${name} automatically from the ${updates} marketplace`]),
|
|
86
149
|
]
|
|
87
150
|
return show<boolean>(
|
|
88
151
|
(answer) => ({
|
|
89
152
|
draw: () =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
[
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
],
|
|
97
|
-
[Button({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) }), Button({ key: 'accept', hotkey: 'a', label: 'Accept', onPress: () => answer(true) })],
|
|
153
|
+
dialog(
|
|
154
|
+
Text({ bold: true, children: `Install ${name}?` }),
|
|
155
|
+
...(permissions.length === 0 ? [] : [group(`${name} wants to:`, permissions.map(permissionWords))]),
|
|
156
|
+
...(changes.length === 0 ? [] : [group(permissions.length === 0 ? `${name} wants to change your computer:` : 'and change your computer:', changes)]),
|
|
157
|
+
Text({ dimColor: true, wrap: 'wrap', children: 'You can turn each permission off later in Settings.' }),
|
|
158
|
+
actions(filledButton({ key: 'accept', hotkey: 'a', variant: 'primary', label: 'Accept', onPress: () => answer(true) }), filledButton({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) })),
|
|
98
159
|
),
|
|
99
160
|
}),
|
|
100
161
|
false,
|
|
@@ -106,38 +167,36 @@ export function createInstaller(name: string, claude: () => Claude, waiting: { s
|
|
|
106
167
|
return show<boolean>(
|
|
107
168
|
(answer) => ({
|
|
108
169
|
draw: () =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
missing.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
flexDirection: 'column',
|
|
116
|
-
|
|
170
|
+
dialog(
|
|
171
|
+
Text({ bold: true, children: `Set up ${name}` }),
|
|
172
|
+
Text({ children: `${name} needs ${missing.length === 1 ? 'one setting' : `${missing.length} settings`}:` }),
|
|
173
|
+
...missing.map(([key, option]) => {
|
|
174
|
+
const label = Box({ flexDirection: 'column', children: [Text({ bold: true, children: option.title }), ...(option.description === '' ? [] : [Text({ dimColor: true, wrap: 'wrap', children: option.description })])] })
|
|
175
|
+
if (option.type === 'secret') {
|
|
176
|
+
return Box({ flexDirection: 'column', children: [label, Text({ dimColor: true, wrap: 'wrap', children: `${secretFix} Then press Next.` }), filledButton({ key: `secret:${key}`, label: 'Open /config', onPress: () => void claude().command.run({ command: 'config' }) })] })
|
|
177
|
+
}
|
|
178
|
+
const failure = failures.get(key)
|
|
179
|
+
const field = Input({
|
|
180
|
+
key: `option:${key}`,
|
|
181
|
+
label: 'Value',
|
|
182
|
+
placeholder: option.type === 'choice' ? (option.choices ?? []).join(', ') : option.type === 'toggle' ? 'true or false' : option.type === 'list' ? 'comma-separated' : '',
|
|
183
|
+
value: saved.get(key) ?? '',
|
|
184
|
+
submitLabel: 'save',
|
|
185
|
+
onSubmit: (value: string) =>
|
|
186
|
+
void save(key, value)
|
|
187
|
+
.then((refused) => {
|
|
188
|
+
if (refused === undefined) {
|
|
189
|
+
saved.set(key, value)
|
|
190
|
+
failures.delete(key)
|
|
191
|
+
} else failures.set(key, refused)
|
|
192
|
+
})
|
|
193
|
+
.catch((error: unknown) => failures.set(key, messageOf(error)))
|
|
194
|
+
.finally(redraw),
|
|
117
195
|
})
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const field = Input({
|
|
121
|
-
key: `option:${key}`,
|
|
122
|
-
label: 'Value',
|
|
123
|
-
placeholder: option.type === 'choice' ? (option.choices ?? []).join(', ') : option.type === 'toggle' ? 'true or false' : option.type === 'list' ? 'comma-separated' : '',
|
|
124
|
-
value: saved.get(key) ?? '',
|
|
125
|
-
submitLabel: 'save',
|
|
126
|
-
onSubmit: (value: string) =>
|
|
127
|
-
void save(key, value)
|
|
128
|
-
.then((refused) => {
|
|
129
|
-
if (refused === undefined) {
|
|
130
|
-
saved.set(key, value)
|
|
131
|
-
failures.delete(key)
|
|
132
|
-
} else failures.set(key, refused)
|
|
133
|
-
})
|
|
134
|
-
.catch((error: unknown) => failures.set(key, messageOf(error)))
|
|
135
|
-
.finally(() => claude().ui.invalidate('ui.render')),
|
|
136
|
-
})
|
|
137
|
-
const status = failure !== undefined ? Text({ color: 'error', children: failure }) : saved.has(key) ? Text({ color: 'success', children: '✔ saved' }) : Text({ children: '' })
|
|
138
|
-
return Box({ flexDirection: 'column', children: [label, field, status] })
|
|
196
|
+
const status = failure !== undefined ? [Text({ color: 'error', wrap: 'wrap', children: failure })] : saved.has(key) ? [Text({ color: 'success', children: '✓ saved' })] : []
|
|
197
|
+
return Box({ flexDirection: 'column', children: [label, field, ...status] })
|
|
139
198
|
}),
|
|
140
|
-
|
|
199
|
+
actions(filledButton({ key: 'next', hotkey: 'x', variant: 'primary', label: 'Next', onPress: () => answer(true) }), filledButton({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) })),
|
|
141
200
|
),
|
|
142
201
|
}),
|
|
143
202
|
false,
|
|
@@ -152,7 +211,7 @@ export function createInstaller(name: string, claude: () => Claude, waiting: { s
|
|
|
152
211
|
try {
|
|
153
212
|
body = step.render(mod, props)
|
|
154
213
|
} catch (error) {
|
|
155
|
-
body = Text({ color: 'error', children: `The ${step.title} step could not draw: ${messageOf(error)}` })
|
|
214
|
+
body = Text({ color: 'error', wrap: 'wrap', children: `The ${step.title} step could not draw: ${messageOf(error)}` })
|
|
156
215
|
}
|
|
157
216
|
const next = async () => {
|
|
158
217
|
try {
|
|
@@ -161,12 +220,13 @@ export function createInstaller(name: string, claude: () => Claude, waiting: { s
|
|
|
161
220
|
} catch (error) {
|
|
162
221
|
notice = { color: 'error', text: `The ${step.title} step's isDone failed: ${messageOf(error)}` }
|
|
163
222
|
}
|
|
164
|
-
|
|
223
|
+
await send({ redraw: true })
|
|
165
224
|
}
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
225
|
+
return dialog(
|
|
226
|
+
Text({ children: [Text({ bold: true, children: step.title }), Text({ dimColor: true, children: ` (${index} of ${total})` })] }),
|
|
227
|
+
body,
|
|
228
|
+
...(notice === undefined ? [] : [Text({ color: notice.color, wrap: 'wrap', children: notice.text })]),
|
|
229
|
+
actions(filledButton({ key: 'next', hotkey: 'x', variant: 'primary', label: index === total ? 'Finish' : 'Next', onPress: () => void next() }), filledButton({ key: 'not-now', hotkey: 'n', label: 'Not now', onPress: () => answer(false) })),
|
|
170
230
|
)
|
|
171
231
|
},
|
|
172
232
|
}),
|
|
@@ -174,10 +234,14 @@ export function createInstaller(name: string, claude: () => Claude, waiting: { s
|
|
|
174
234
|
)
|
|
175
235
|
},
|
|
176
236
|
async close() {
|
|
177
|
-
|
|
237
|
+
question?.cancel()
|
|
238
|
+
question = undefined
|
|
178
239
|
if (!isOpen) return
|
|
179
240
|
isOpen = false
|
|
180
|
-
|
|
241
|
+
isSeen = false
|
|
242
|
+
await send({ requests: [{ close: installPage }] }).catch(log('the install page stayed open'))
|
|
181
243
|
},
|
|
244
|
+
shown: () => new Promise<void>((resolve) => void waitingForShow.push(resolve)),
|
|
245
|
+
collect,
|
|
182
246
|
}
|
|
183
247
|
}
|