@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.
Files changed (271) hide show
  1. package/docs/commands.md +50 -10
  2. package/docs/dependencies.md +12 -12
  3. package/docs/hooks.md +39 -27
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +9 -7
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +200 -174
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +248 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +65 -22
  13. package/docs/ui.md +108 -76
  14. package/jobs/check.d.ts.map +1 -1
  15. package/jobs/check.js +5 -2
  16. package/jobs/context.d.ts +7 -8
  17. package/jobs/context.d.ts.map +1 -1
  18. package/jobs/context.js +23 -73
  19. package/jobs/permissions/decide-permission.d.ts +5 -3
  20. package/jobs/permissions/decide-permission.d.ts.map +1 -1
  21. package/jobs/permissions/decide-permission.js +6 -4
  22. package/jobs/permissions/find-project-scope.d.ts +1 -1
  23. package/jobs/permissions/find-project-scope.d.ts.map +1 -1
  24. package/jobs/permissions/find-project-scope.js +8 -11
  25. package/jobs/permissions/match-target.d.ts +2 -2
  26. package/jobs/permissions/match-target.d.ts.map +1 -1
  27. package/jobs/permissions.d.ts +1 -1
  28. package/jobs/permissions.d.ts.map +1 -1
  29. package/jobs/permissions.js +6 -4
  30. package/jobs/program.d.ts.map +1 -1
  31. package/jobs/program.js +8 -3
  32. package/jobs/prompt.d.ts +4 -2
  33. package/jobs/prompt.d.ts.map +1 -1
  34. package/jobs/prompt.js +16 -15
  35. package/jobs/schedule.d.ts +2 -2
  36. package/jobs/schedule.d.ts.map +1 -1
  37. package/jobs/schedule.js +9 -9
  38. package/jobs/slash-command.d.ts +4 -4
  39. package/jobs/slash-command.d.ts.map +1 -1
  40. package/jobs/slash-command.js +14 -9
  41. package/jobs/status-line.d.ts +1 -11
  42. package/jobs/status-line.d.ts.map +1 -1
  43. package/jobs/status-line.js +8 -12
  44. package/jobs/tool.d.ts +2 -2
  45. package/jobs/tool.d.ts.map +1 -1
  46. package/jobs/tool.js +15 -9
  47. package/mod.d.ts +154 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +94 -3
  50. package/options.d.ts +2 -2
  51. package/options.js +19 -19
  52. package/package.json +3 -3
  53. package/records.d.ts +10 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +28 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +17 -5
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +66 -0
  61. package/runtime/bound.d.ts +4 -0
  62. package/runtime/bound.d.ts.map +1 -0
  63. package/runtime/bound.js +9 -0
  64. package/runtime/claude.d.ts +3 -2
  65. package/runtime/claude.d.ts.map +1 -1
  66. package/runtime/conversations.d.ts +16 -0
  67. package/runtime/conversations.d.ts.map +1 -0
  68. package/runtime/conversations.js +291 -0
  69. package/runtime/deadline.d.ts +0 -1
  70. package/runtime/deadline.d.ts.map +1 -1
  71. package/runtime/deadline.js +1 -1
  72. package/runtime/dependencies.d.ts +2 -0
  73. package/runtime/dependencies.d.ts.map +1 -1
  74. package/runtime/dependencies.js +9 -0
  75. package/runtime/engine.d.ts +17 -0
  76. package/runtime/engine.d.ts.map +1 -0
  77. package/runtime/engine.js +10 -0
  78. package/runtime/grants.d.ts +4 -0
  79. package/runtime/grants.d.ts.map +1 -1
  80. package/runtime/grants.js +63 -26
  81. package/runtime/hooks.d.ts +5 -4
  82. package/runtime/hooks.d.ts.map +1 -1
  83. package/runtime/hooks.js +10 -5
  84. package/runtime/installer.d.ts.map +1 -1
  85. package/runtime/installer.js +11 -9
  86. package/runtime/lifecycle.d.ts +3 -1
  87. package/runtime/lifecycle.d.ts.map +1 -1
  88. package/runtime/lifecycle.js +316 -185
  89. package/runtime/metadata.d.ts +3 -1
  90. package/runtime/metadata.d.ts.map +1 -1
  91. package/runtime/metadata.js +21 -11
  92. package/runtime/options.d.ts +3 -1
  93. package/runtime/options.d.ts.map +1 -1
  94. package/runtime/options.js +4 -4
  95. package/runtime/programs.d.ts +8 -0
  96. package/runtime/programs.d.ts.map +1 -1
  97. package/runtime/programs.js +39 -1
  98. package/runtime/router.d.ts +2 -1
  99. package/runtime/router.d.ts.map +1 -1
  100. package/runtime/router.js +11 -3
  101. package/runtime/sessions.d.ts +36 -0
  102. package/runtime/sessions.d.ts.map +1 -0
  103. package/runtime/sessions.js +235 -0
  104. package/runtime/state.d.ts +6 -2
  105. package/runtime/state.d.ts.map +1 -1
  106. package/runtime/state.js +46 -13
  107. package/runtime/toaster.d.ts +31 -0
  108. package/runtime/toaster.d.ts.map +1 -0
  109. package/runtime/toaster.js +176 -0
  110. package/runtime/ui.d.ts +6 -17
  111. package/runtime/ui.d.ts.map +1 -1
  112. package/runtime/ui.js +36 -112
  113. package/session/context/context.d.ts +10 -0
  114. package/session/context/context.d.ts.map +1 -0
  115. package/session/context/context.js +2 -0
  116. package/session/context/turn/message/document.d.ts +7 -0
  117. package/session/context/turn/message/document.d.ts.map +1 -0
  118. package/session/context/turn/message/document.js +1 -0
  119. package/session/context/turn/message/message.d.ts +54 -0
  120. package/session/context/turn/message/message.d.ts.map +1 -0
  121. package/session/context/turn/message/message.js +137 -0
  122. package/session/context/turn/message/note.d.ts +8 -0
  123. package/session/context/turn/message/note.d.ts.map +1 -0
  124. package/session/context/turn/message/note.js +1 -0
  125. package/session/context/turn/message/notice.d.ts +6 -0
  126. package/session/context/turn/message/notice.d.ts.map +1 -0
  127. package/session/context/turn/message/notice.js +1 -0
  128. package/session/context/turn/message/prompt.d.ts +8 -0
  129. package/session/context/turn/message/prompt.d.ts.map +1 -0
  130. package/session/context/turn/message/prompt.js +1 -0
  131. package/session/context/turn/message/reply.d.ts +6 -0
  132. package/session/context/turn/message/reply.d.ts.map +1 -0
  133. package/session/context/turn/message/reply.js +1 -0
  134. package/session/context/turn/message/sender.d.ts +14 -0
  135. package/session/context/turn/message/sender.d.ts.map +1 -0
  136. package/session/context/turn/message/sender.js +30 -0
  137. package/session/context/turn/message/skill.d.ts +7 -0
  138. package/session/context/turn/message/skill.d.ts.map +1 -0
  139. package/session/context/turn/message/skill.js +1 -0
  140. package/session/context/turn/message/tool-call.d.ts +9 -0
  141. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  142. package/session/context/turn/message/tool-call.js +1 -0
  143. package/session/context/turn/message/tool-result.d.ts +8 -0
  144. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  145. package/session/context/turn/message/tool-result.js +1 -0
  146. package/session/context/turn/turn.d.ts +12 -0
  147. package/session/context/turn/turn.d.ts.map +1 -0
  148. package/session/context/turn/turn.js +2 -0
  149. package/session/conversation.d.ts +64 -0
  150. package/session/conversation.d.ts.map +1 -0
  151. package/session/conversation.js +123 -0
  152. package/session/goal/goal.d.ts +10 -0
  153. package/session/goal/goal.d.ts.map +1 -0
  154. package/session/goal/goal.js +1 -0
  155. package/session/goal/task.d.ts +16 -0
  156. package/session/goal/task.d.ts.map +1 -0
  157. package/session/goal/task.js +3 -0
  158. package/session/session.d.ts +99 -0
  159. package/session/session.d.ts.map +1 -0
  160. package/session/session.js +1 -0
  161. package/src/jobs/check.ts +7 -4
  162. package/src/jobs/context.ts +24 -82
  163. package/src/jobs/permissions/decide-permission.ts +10 -8
  164. package/src/jobs/permissions/find-project-scope.ts +8 -14
  165. package/src/jobs/permissions/match-target.ts +3 -3
  166. package/src/jobs/permissions.ts +8 -6
  167. package/src/jobs/program.ts +7 -3
  168. package/src/jobs/prompt.ts +21 -24
  169. package/src/jobs/schedule.ts +14 -14
  170. package/src/jobs/slash-command.ts +20 -14
  171. package/src/jobs/status-line.ts +9 -21
  172. package/src/jobs/tool.ts +17 -11
  173. package/src/mod.ts +209 -97
  174. package/src/options.ts +22 -22
  175. package/src/records.ts +35 -23
  176. package/src/register.ts +17 -5
  177. package/src/runtime/announce.ts +74 -0
  178. package/src/runtime/bound.ts +12 -0
  179. package/src/runtime/claude.ts +4 -2
  180. package/src/runtime/conversations.ts +302 -0
  181. package/src/runtime/deadline.ts +2 -2
  182. package/src/runtime/dependencies.ts +12 -0
  183. package/src/runtime/engine.ts +22 -0
  184. package/src/runtime/grants.ts +66 -24
  185. package/src/runtime/hooks.ts +19 -10
  186. package/src/runtime/installer.ts +10 -9
  187. package/src/runtime/lifecycle.ts +312 -180
  188. package/src/runtime/metadata.ts +33 -11
  189. package/src/runtime/options.ts +19 -4
  190. package/src/runtime/programs.ts +40 -1
  191. package/src/runtime/router.ts +11 -4
  192. package/src/runtime/sessions.ts +242 -0
  193. package/src/runtime/state.ts +50 -14
  194. package/src/runtime/toaster.ts +218 -0
  195. package/src/runtime/ui.ts +42 -139
  196. package/src/session/context/context.ts +12 -0
  197. package/src/session/context/turn/message/document.ts +8 -0
  198. package/src/session/context/turn/message/message.ts +165 -0
  199. package/src/session/context/turn/message/note.ts +9 -0
  200. package/src/session/context/turn/message/notice.ts +7 -0
  201. package/src/session/context/turn/message/prompt.ts +9 -0
  202. package/src/session/context/turn/message/reply.ts +7 -0
  203. package/src/session/context/turn/message/sender.ts +38 -0
  204. package/src/session/context/turn/message/skill.ts +8 -0
  205. package/src/session/context/turn/message/tool-call.ts +10 -0
  206. package/src/session/context/turn/message/tool-result.ts +9 -0
  207. package/src/session/context/turn/turn.ts +14 -0
  208. package/src/session/conversation.ts +169 -0
  209. package/src/session/goal/goal.ts +9 -0
  210. package/src/session/goal/task.ts +19 -0
  211. package/src/session/session.ts +94 -0
  212. package/src/testing/fake-claude.ts +22 -16
  213. package/src/testing/fake-elements.ts +3 -2
  214. package/src/testing/fake-files.ts +1 -1
  215. package/src/testing/fake-sessions.ts +203 -0
  216. package/src/testing.ts +112 -33
  217. package/src/ui/components.ts +3 -3
  218. package/src/ui/elements.ts +22 -8
  219. package/src/ui/markdown.ts +22 -26
  220. package/src/ui/places.ts +49 -0
  221. package/src/utils/call-effects.ts +3 -3
  222. package/src/utils/changelog.ts +32 -0
  223. package/src/utils/matcher.ts +37 -0
  224. package/src/utils/metadata.ts +17 -15
  225. package/src/utils/parse-shell.ts +9 -8
  226. package/src/utils/paths.ts +15 -1
  227. package/src/utils/text.ts +4 -0
  228. package/testing/fake-claude.d.ts +4 -1
  229. package/testing/fake-claude.d.ts.map +1 -1
  230. package/testing/fake-claude.js +18 -11
  231. package/testing/fake-elements.js +4 -2
  232. package/testing/fake-files.js +1 -1
  233. package/testing/fake-sessions.d.ts +74 -0
  234. package/testing/fake-sessions.d.ts.map +1 -0
  235. package/testing/fake-sessions.js +170 -0
  236. package/testing.d.ts +33 -13
  237. package/testing.d.ts.map +1 -1
  238. package/testing.js +92 -21
  239. package/ui/components.d.ts +2 -3
  240. package/ui/components.d.ts.map +1 -1
  241. package/ui/components.js +3 -3
  242. package/ui/elements.d.ts.map +1 -1
  243. package/ui/elements.js +19 -9
  244. package/ui/markdown.d.ts +35 -33
  245. package/ui/markdown.d.ts.map +1 -1
  246. package/ui/markdown.js +14 -16
  247. package/ui/places.d.ts +43 -0
  248. package/ui/places.d.ts.map +1 -0
  249. package/ui/places.js +31 -0
  250. package/utils/call-effects.d.ts +1 -1
  251. package/utils/call-effects.d.ts.map +1 -1
  252. package/utils/call-effects.js +2 -2
  253. package/utils/changelog.d.ts +9 -0
  254. package/utils/changelog.d.ts.map +1 -0
  255. package/utils/changelog.js +29 -0
  256. package/utils/matcher.d.ts +13 -0
  257. package/utils/matcher.d.ts.map +1 -0
  258. package/utils/matcher.js +32 -0
  259. package/utils/metadata.d.ts +3 -3
  260. package/utils/metadata.js +18 -14
  261. package/utils/parse-shell.js +9 -7
  262. package/utils/paths.d.ts +2 -0
  263. package/utils/paths.d.ts.map +1 -1
  264. package/utils/paths.js +13 -1
  265. package/utils/text.d.ts +1 -0
  266. package/utils/text.d.ts.map +1 -1
  267. package/utils/text.js +3 -0
  268. package/src/ui/slots.ts +0 -36
  269. package/ui/slots.d.ts +0 -208
  270. package/ui/slots.d.ts.map +0 -1
  271. package/ui/slots.js +0 -19
@@ -0,0 +1,170 @@
1
+ import { isObject } from '../records.js';
2
+ import { Document } from '../session/context/turn/message/document.js';
3
+ import { Message, searchText, skillPrefix } from '../session/context/turn/message/message.js';
4
+ import { Note } from '../session/context/turn/message/note.js';
5
+ import { Notice } from '../session/context/turn/message/notice.js';
6
+ import { Prompt } from '../session/context/turn/message/prompt.js';
7
+ import { Reply } from '../session/context/turn/message/reply.js';
8
+ import { Mod, Person, Schedule } from '../session/context/turn/message/sender.js';
9
+ import { Task } from '../session/goal/task.js';
10
+ import { Skill } from '../session/context/turn/message/skill.js';
11
+ import { ToolCall } from '../session/context/turn/message/tool-call.js';
12
+ import { ToolResult } from '../session/context/turn/message/tool-result.js';
13
+ import { createConversation, emptyConversation, searchLimit } from '../session/conversation.js';
14
+ import { conversationMethods, isHeardBy } from '../runtime/sessions.js';
15
+ import { receivedMatcher } from '../utils/matcher.js';
16
+ import { firstLine, messageOf } from '../utils/text.js';
17
+ export function fakeSessions({ project, given, push }) {
18
+ const kept = new Map();
19
+ const heard = new Map();
20
+ const sent = [];
21
+ let current = '';
22
+ let clock = Date.parse('2026-01-01T00:00:00.000Z');
23
+ let ids = 0;
24
+ const now = () => new Date((clock += 1000)).toISOString();
25
+ const keep = (data) => {
26
+ const session = { data, conversation: createConversation(emptyConversation(data.id)), messages: [], turns: new Map(), contexts: new Map(), tasks: new Map() };
27
+ kept.set(data.id, session);
28
+ return session;
29
+ };
30
+ const record = (session, changes) => {
31
+ for (const change of changes) {
32
+ if (change.type === Message)
33
+ session.messages.push(change.message);
34
+ if ('turn' in change)
35
+ session.turns.set(change.turn.prompt.id, change.turn);
36
+ if ('context' in change)
37
+ session.contexts.set(change.context.number, change.context);
38
+ if ('task' in change)
39
+ session.tasks.set(change.task.id, change.task);
40
+ session.data = { ...session.data, lastAt: change.type === Message ? change.message.at : session.data.lastAt, ...(session.data.title === '' && change.type === Message && change.message.type === Prompt && change.message.from.type === Person ? { title: firstLine(change.message.text) } : {}) };
41
+ for (const [mod, sessions] of heard) {
42
+ if ((sessions.get(session.data.id) ?? []).some(({ event, matcher }) => isHeardBy(event, matcher, change)))
43
+ push(mod, session.data.id, change);
44
+ }
45
+ }
46
+ };
47
+ const add = (session, draft, task) => {
48
+ const before = session.messages.length;
49
+ record(session, session.conversation.add(rowOf(draft, `message-${(ids += 1)}`, now()), task));
50
+ return session.messages.slice(before);
51
+ };
52
+ for (const each of given) {
53
+ const session = keep({ title: '', project: project(), cwd: project(), model: 'test-model', startedAt: now(), lastAt: '', isRunning: false, ...each });
54
+ for (const draft of each.messages ?? [])
55
+ add(session, draft);
56
+ if (each.goal !== undefined)
57
+ record(session, session.conversation.changeGoal(each.goal));
58
+ }
59
+ const sessionOf = (id) => {
60
+ const session = kept.get(id);
61
+ if (session === undefined)
62
+ throw new Error(`No Claude Code transcript is named ${id}.jsonl. Give it to testMod as { sessions: [{ id: '${id}' }] }.`);
63
+ return session;
64
+ };
65
+ const currentSession = () => sessionOf(current);
66
+ const lastOf = (items, last) => (typeof last === 'number' ? items.slice(-last) : [...items]);
67
+ const isInScope = (session, request) => (request['everyProject'] === true || session.data.project === project()) && session.data.lastAt >= (typeof request['since'] === 'string' ? request['since'] : '') && (request['running'] !== true || session.data.isRunning);
68
+ const methods = {
69
+ [conversationMethods.sessions]: (request) => [...kept.values()]
70
+ .filter((session) => isInScope(session, request))
71
+ .map((session) => session.data)
72
+ .toSorted((first, second) => second.lastAt.localeCompare(first.lastAt)),
73
+ [conversationMethods.search](request) {
74
+ const text = String(request['text']);
75
+ if ([...text].length < 3)
76
+ throw new Error('cmod history search takes at least 3 characters.');
77
+ const found = [...kept.values()]
78
+ .filter((session) => isInScope(session, request))
79
+ .flatMap((session) => session.messages)
80
+ .filter((message) => searchText(message).toLowerCase().includes(text.toLowerCase()))
81
+ .toSorted((first, second) => second.at.localeCompare(first.at));
82
+ const before = isObject(request['before']) ? request['before'] : undefined;
83
+ const start = before === undefined ? 0 : found.findIndex((message) => message.session === before['session'] && message.id === before['id']) + 1;
84
+ return found.slice(start, start + (typeof request['limit'] === 'number' ? request['limit'] : searchLimit));
85
+ },
86
+ [conversationMethods.messages](request) {
87
+ const session = sessionOf(String(request['session']));
88
+ const fits = session.messages.filter((message) => message.task === request['task'] && (request['turn'] === undefined || message.turn === request['turn']));
89
+ const after = typeof request['after'] === 'string' ? fits.findIndex((message) => message.id === request['after']) + 1 : 0;
90
+ return lastOf(fits.slice(after), request['last']);
91
+ },
92
+ [conversationMethods.turns]: (request) => lastOf([...sessionOf(String(request['session'])).turns.values()].filter((turn) => request['context'] === undefined || turn.context === request['context']), request['last']),
93
+ [conversationMethods.contexts]: (request) => [...sessionOf(String(request['session'])).contexts.values()],
94
+ [conversationMethods.tasks]: (request) => [...sessionOf(String(request['session'])).tasks.values()].filter((task) => task.parent === request['task']),
95
+ [conversationMethods.goal]: (request) => ({ text: sessionOf(String(request['session'])).conversation.state.goal ?? '' }),
96
+ [conversationMethods.updateGoal](request) {
97
+ const session = sessionOf(String(request['session']));
98
+ record(session, session.conversation.changeGoal(String(request['text'])));
99
+ return null;
100
+ },
101
+ [conversationMethods.subscribe](request, caller) {
102
+ const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
103
+ const sessions = heard.get(caller) ?? new Map();
104
+ sessions.set(String(request['session']), list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
105
+ heard.set(caller, sessions);
106
+ return null;
107
+ },
108
+ [conversationMethods.send](request) {
109
+ sent.push({ session: String(request['session']), type: String(request['type']), text: String(request['text']), ...(typeof request['as'] === 'string' ? { as: request['as'] } : {}) });
110
+ return null;
111
+ },
112
+ };
113
+ return {
114
+ answer(caller, method, input) {
115
+ const run = methods[method];
116
+ if (run === undefined)
117
+ return undefined;
118
+ try {
119
+ return { value: run(isObject(input) ? input : {}, caller) };
120
+ }
121
+ catch (error) {
122
+ return { deny: messageOf(error) };
123
+ }
124
+ },
125
+ add: (draft, task) => add(currentSession(), draft, task),
126
+ endTurn() {
127
+ const session = currentSession();
128
+ record(session, session.conversation.add({ id: `turn-end-${(ids += 1)}`, at: now(), type: 'system', name: 'turn_duration', isMeta: false, isSummary: false, content: [] }));
129
+ },
130
+ compact() {
131
+ const session = currentSession();
132
+ const at = now();
133
+ record(session, [...session.conversation.endContext(at), ...session.conversation.startContext(at)]);
134
+ },
135
+ startTask({ title, agentType = 'general-purpose', model, openedBy = '' }) {
136
+ const session = currentSession();
137
+ const id = `task-${(ids += 1)}`;
138
+ record(session, session.conversation.startTask({ id, title, agentType, openedBy, openedAt: now(), ...(model === undefined ? {} : { model }) }));
139
+ return id;
140
+ },
141
+ start(id) {
142
+ current = id;
143
+ const session = kept.get(id) ?? keep({ id, title: '', project: project(), cwd: project(), model: 'test-model', startedAt: now(), lastAt: now(), isRunning: true });
144
+ session.data = { ...session.data, isRunning: true };
145
+ if (session.conversation.state.context === 0)
146
+ record(session, session.conversation.startContext(now()));
147
+ },
148
+ sent,
149
+ };
150
+ }
151
+ function rowOf(draft, id, at) {
152
+ const base = { id, at, isMeta: false, isSummary: false };
153
+ const text = (value) => [{ type: 'text', text: value }];
154
+ const origin = (from) => (from === undefined || from.type === Person ? { kind: 'human' } : from.type === Mod ? { kind: 'plugin', name: from.name, ...(from.as === Person ? { asUser: true } : {}) } : from.type === 'session' ? { kind: 'peer' } : from.type === Task ? { kind: 'task-notification' } : { kind: from.type === Schedule ? 'scheduled-trigger' : from.type, ...(from.name === undefined ? {} : { server: from.name }) });
155
+ if (draft.type === Prompt)
156
+ return { ...base, type: 'user', content: text(draft.text), origin: origin(draft.from) };
157
+ if (draft.type === Note)
158
+ return { ...base, type: 'user', isMeta: true, content: text(draft.text), origin: origin(draft.from ?? { type: Mod, name: 'test' }) };
159
+ if (draft.type === Reply)
160
+ return { ...base, type: 'assistant', content: text(draft.text) };
161
+ if (draft.type === ToolCall)
162
+ return { ...base, type: 'assistant', content: [{ type: 'tool_use', id: draft.callId ?? `toolu_${id}`, name: draft.name, input: draft.input ?? {} }], cwd: '/test/project' };
163
+ if (draft.type === ToolResult)
164
+ return { ...base, type: 'user', content: [{ type: 'tool_result', tool_use_id: draft.callId, content: draft.text, ...(draft.isError === true ? { is_error: true } : {}) }] };
165
+ if (draft.type === Skill)
166
+ return { ...base, type: 'user', isMeta: true, content: text(`${skillPrefix} /skills/${draft.name}\n\n${draft.text}`) };
167
+ if (draft.type === Document)
168
+ return { ...base, type: 'attachment', name: 'nested_memory', content: text(draft.text), path: draft.path };
169
+ return { ...base, type: 'system', name: 'informational', content: text(draft.text) };
170
+ }
package/testing.d.ts CHANGED
@@ -1,11 +1,21 @@
1
- import type { Args, ClassicHookInputs, CmodDependencies, EventResult, RenderComponent, UiInputArgument } from 'claude-code';
2
- import type { Reply } from './jobs/slash-command.js';
3
- import type { ModDefinition, ModEvent } from './mod.js';
1
+ import type { Args, ClassicHookInputs, CmodDependencies, EventResult, UiInputArgument } from 'claude-code';
2
+ import type { CommandReply } from './jobs/slash-command.js';
3
+ import type { Hook, ModDefinition } from './mod.js';
4
4
  import type { Options, OptionValues } from './options.js';
5
5
  import type { RoutedEvent } from './runtime/hooks.js';
6
- import { type Fakes, type Shown, type TestCall } from './testing/fake-claude.js';
7
- import type { Slot, SlotProps } from './ui/slots.js';
8
- export type { Fakes, Shown, TestCall } from './testing/fake-claude.js';
6
+ import type { ShownToast } from './runtime/toaster.js';
7
+ import type { Message } from './session/context/turn/message/message.js';
8
+ import { type Fakes, type Shown as ShownByClaude, type TestCall } from './testing/fake-claude.js';
9
+ import { type FakeSession, type FakeSessions, type MessageDraft } from './testing/fake-sessions.js';
10
+ import { type ComponentPlace, type PlaceProps } from './ui/places.js';
11
+ export type { Fakes, TestCall } from './testing/fake-claude.js';
12
+ export type { FakeSession, MessageDraft } from './testing/fake-sessions.js';
13
+ export type { RoutedEvent } from './runtime/hooks.js';
14
+ export type { ShownToast } from './runtime/toaster.js';
15
+ export type Shown = ShownByClaude & {
16
+ readonly toasts: readonly ShownToast[];
17
+ readonly sent: FakeSessions['sent'];
18
+ };
9
19
  export type TestOptions<State extends object, Declared extends Options = Options> = {
10
20
  readonly state?: {
11
21
  readonly [Lifetime in keyof State]?: Partial<State[Lifetime]>;
@@ -20,22 +30,32 @@ export type TestOptions<State extends object, Declared extends Options = Options
20
30
  };
21
31
  readonly files?: Readonly<Record<string, string>>;
22
32
  readonly links?: Readonly<Record<string, string>>;
33
+ readonly sessions?: readonly FakeSession[];
23
34
  };
24
35
  type FilledField = 'session_id' | 'transcript_path' | 'cwd' | 'hook_event_name' | 'tool_use_id';
25
- export type TestInput<E extends ModEvent> = Omit<ClassicHookInputs[E], FilledField> & Partial<Pick<ClassicHookInputs[E], FilledField & keyof ClassicHookInputs[E]>>;
36
+ export type TestInput<H extends Hook> = Omit<ClassicHookInputs[H], FilledField> & Partial<Pick<ClassicHookInputs[H], FilledField & keyof ClassicHookInputs[H]>>;
26
37
  export type TestedMod<State extends object> = {
27
38
  start(): Promise<void>;
28
- fire<E extends ModEvent>(event: E, input: TestInput<E>, below?: EventResult<`classic.${E}`>): Promise<EventResult<`classic.${E}`>>;
39
+ fire<H extends Hook>(hook: H, input: TestInput<H>, below?: EventResult<`classic.${H}`>): Promise<EventResult<`classic.${H}`>>;
29
40
  fire<N extends RoutedEvent>(event: N, input: Args<N>, below?: EventResult<N>): Promise<EventResult<N>>;
30
41
  settle(): Promise<void>;
42
+ add(message: MessageDraft, options?: {
43
+ readonly task?: string;
44
+ }): Promise<Message[]>;
45
+ endTurn(): Promise<void>;
46
+ compact(): Promise<void>;
47
+ startTask(task: {
48
+ readonly title: string;
49
+ readonly agentType?: string;
50
+ readonly model?: string;
51
+ readonly openedBy?: string;
52
+ }): Promise<string>;
31
53
  lines(paneId: string): Promise<string[]>;
32
- lines<S extends Slot<object, object, {
33
- readonly component: RenderComponent;
34
- }>>(slot: S, props: Omit<SlotProps<S>, 'Default'>, requestId?: string): Promise<string[]>;
35
- type(line: string): Promise<Reply>;
54
+ lines<P extends ComponentPlace>(place: P, props: Omit<PlaceProps<P>, 'Default'>, requestId?: string): Promise<string[]>;
55
+ type(line: string): Promise<CommandReply>;
36
56
  callTool(name: string, input: Record<string, unknown>): Promise<EventResult<'tool.call'>>;
37
57
  press(paneId: string, key: string, link?: string): Promise<void>;
38
- input(paneId: string, key: string, text: string, kind?: UiInputArgument['kind']): Promise<void>;
58
+ input(paneId: string, key: string, text: string, type?: UiInputArgument['kind']): Promise<void>;
39
59
  select(paneId: string, key: string, value: string): Promise<void>;
40
60
  moveTo(projectRoot: string, cwd?: string): Promise<void>;
41
61
  readonly state: Readonly<State>;
package/testing.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,iBAAiB,EACjB,gBAAgB,EAGhB,WAAW,EAMX,eAAe,EAGf,eAAe,EAGhB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAIzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAIrD,OAAO,EAAc,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAI5F,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEpD,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEtE,MAAM,MAAM,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,SAAS,OAAO,GAAG,OAAO,IAAI;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,QAAQ,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;KAAE,CAAA;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAE,QAAQ,EAAE,IAAI,IAAI,MAAM,gBAAgB,CAAC,CAAC,EAAE,gBAAgB,CAAC,IAAI,CAAC;KAAE,CAAA;IAC9F,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAClD,CAAA;AAED,KAAK,WAAW,GAAG,YAAY,GAAG,iBAAiB,GAAG,KAAK,GAAG,iBAAiB,GAAG,aAAa,CAAA;AAE/F,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,QAAQ,IAAI,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAEnK,MAAM,MAAM,SAAS,CAAC,KAAK,SAAS,MAAM,IAAI;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,CAAC,CAAC,SAAS,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAA;IAClI,IAAI,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACtG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACvB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACxC,KAAK,CAAC,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;KAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACpK,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAA;IACzF,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/F,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;IACnC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;CACtB,CAAA;AAUD,wBAAgB,OAAO,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,SAAS,OAAO,GAAG,OAAO,EAAE,UAAU,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAE,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAiMlM"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
package/testing.js CHANGED
@@ -1,12 +1,18 @@
1
1
  import { readSteps, storeFolder } from './records.js';
2
- import { answerCall, notInstalled } from './runtime/dependencies.js';
2
+ import { answerCall, cmodPluginName, notInstalled } from './runtime/dependencies.js';
3
3
  import { toolInputOf } from './runtime/tool-calls.js';
4
4
  import { createLifecycle } from './runtime/lifecycle.js';
5
5
  import { MissingOptions } from './runtime/options.js';
6
- import { fakeClaude } from './testing/fake-claude.js';
6
+ import { conversationMethods } from './runtime/sessions.js';
7
+ import { Note } from './session/context/turn/message/note.js';
8
+ import { Prompt } from './session/context/turn/message/prompt.js';
9
+ import { Mod, Person } from './session/context/turn/message/sender.js';
10
+ import { calledAnswer, fakeClaude } from './testing/fake-claude.js';
7
11
  import { elements, findElement, rowsOf } from './testing/fake-elements.js';
8
12
  import { fakeFiles } from './testing/fake-files.js';
13
+ import { fakeSessions } from './testing/fake-sessions.js';
9
14
  import { listed } from './utils/text.js';
15
+ import { componentOf } from './ui/places.js';
10
16
  const defaultColumns = 80;
11
17
  const defaultRows = 24;
12
18
  const presentation = { isFullscreen: false, columns: defaultColumns };
@@ -26,7 +32,31 @@ export function testMod(definition, options = {}) {
26
32
  fake.claude.session.id = async () => sessionId;
27
33
  fake.claude.session.root = async () => projectRoot;
28
34
  fake.claude.session.cwd = async () => cwd;
29
- fake.fakes.cmod.call = fakeDependencies(options.dependencies ?? {});
35
+ const conversations = fakeSessions({
36
+ project: () => projectRoot,
37
+ given: options.sessions ?? [],
38
+ push: (mod, session, change) => {
39
+ if (mod !== name)
40
+ return;
41
+ const pushed = { to: mod, method: conversationMethods.changed, input: { session, change }, caller: cmodPluginName };
42
+ void lifecycle.route('cmod.call', pushed, async () => ({ deny: notInstalled(mod) }));
43
+ },
44
+ });
45
+ conversations.start(sessionId);
46
+ fake.fakes.cmod.call = fakeDependencies(name, options.dependencies ?? {}, conversations);
47
+ const appendRow = fake.claude.session.append;
48
+ fake.claude.session.append = async (args) => {
49
+ const added = await appendRow(args);
50
+ if (added.deny === undefined)
51
+ conversations.add({ type: Note, text: args.message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''), from: { type: Mod, name } });
52
+ return added;
53
+ };
54
+ const submitRow = fake.claude.prompt.submit;
55
+ fake.claude.prompt.submit = async (args) => {
56
+ const submitted = await submitRow(args);
57
+ conversations.add({ type: Prompt, text: args.text, from: { type: Mod, name, ...(args.asUser === true ? { as: Person } : {}) } });
58
+ return submitted;
59
+ };
30
60
  const firedAgents = new Map();
31
61
  const listAgents = fake.claude.agent.list;
32
62
  fake.claude.agent.list = async () => {
@@ -34,7 +64,8 @@ export function testMod(definition, options = {}) {
34
64
  return listAgents();
35
65
  return [...firedAgents].map(([id, type]) => ({ id, type, description: '', status: 'running' }));
36
66
  };
37
- Object.assign(fake.fakes.fs, fakeFiles(options.files ?? {}, options.links));
67
+ const files = fakeFiles(options.files ?? {}, options.links);
68
+ Object.assign(fake.fakes.fs, files);
38
69
  const starting = options.state;
39
70
  const declared = Object.entries(definition.state ?? {});
40
71
  const state = Object.fromEntries(declared.map(([lifetime, values]) => [lifetime, { ...values, ...starting?.[lifetime] }]));
@@ -55,6 +86,7 @@ export function testMod(definition, options = {}) {
55
86
  };
56
87
  });
57
88
  await started;
89
+ await fake.settle();
58
90
  const { failure } = lifecycle;
59
91
  if (failure instanceof MissingOptions) {
60
92
  const given = failure.keys.map((key) => `${key}: …`).join(', ');
@@ -96,14 +128,19 @@ export function testMod(definition, options = {}) {
96
128
  const fire = ((event, input, below) => {
97
129
  if (event.includes('.'))
98
130
  return route(event, input, below);
99
- if (event === 'SessionStart')
131
+ if (event === 'SessionStart') {
100
132
  sessionId = input.session_id ?? sessionId;
133
+ conversations.start(sessionId);
134
+ }
101
135
  if (event === 'UserPromptSubmit') {
102
136
  cwd = cwdAfterPrompt ?? cwd;
103
137
  cwdAfterPrompt = undefined;
104
138
  }
105
139
  const toolUse = toolUseEvents.includes(event) ? { tool_use_id: `toolu_${(toolUses += 1)}` } : {};
106
- const filled = { session_id: sessionId, transcript_path: '/test/transcript.jsonl', cwd, hook_event_name: event, ...toolUse, ...input };
140
+ const transcript = `/test/transcripts/${sessionId}.jsonl`;
141
+ const filled = { session_id: sessionId, transcript_path: transcript, cwd, hook_event_name: event, ...toolUse, ...input };
142
+ if (event === 'SessionStart' && !('transcript_path' in input))
143
+ return files.write(transcript, '').then(() => route('classic.SessionStart', filled, below));
107
144
  if (event !== 'PreToolUse')
108
145
  return route(`classic.${event}`, filled, below);
109
146
  return firePreToolUse(filled, below);
@@ -119,7 +156,7 @@ export function testMod(definition, options = {}) {
119
156
  const answer = await lifecycle.route('tool.call', envelope, async (e) => {
120
157
  reached = e;
121
158
  check = await lifecycle.route('tool.check', { tool: e.tool, input: toolInputOf(e), tool_use_id: e.tool_use_id, ...(e.agentId === undefined ? {} : { agentId: e.agentId }) }, async () => asks);
122
- return (check.decision === 'deny' ? { deny: check.reason ?? '' } : { result: '' });
159
+ return check.decision === 'deny' ? { deny: check.reason ?? '' } : { result: '' };
123
160
  });
124
161
  const updatedInput = toolInputOf(reached);
125
162
  const decided = check === asks ? {} : check.decision === 'allow' ? { allow: true } : check.decision === 'ask' ? { ask: check.reason ?? '' } : {};
@@ -129,14 +166,37 @@ export function testMod(definition, options = {}) {
129
166
  ...(answer.context === undefined || answer.context.length === 0 ? {} : { additionalContext: [...answer.context] }),
130
167
  };
131
168
  };
169
+ const settled = async (value) => {
170
+ await fake.settle();
171
+ return value;
172
+ };
132
173
  return {
133
174
  start,
134
175
  fire,
135
176
  settle: fake.settle,
136
- lines: (async (target, props = {}, requestId) => {
137
- const { drawing, columns } = typeof target === 'string' ? await drawPane(target) : await drawSlot(target.component, props, requestId);
177
+ add: async (message, added) => {
178
+ await start();
179
+ return settled(conversations.add(message, added?.task));
180
+ },
181
+ endTurn: async () => {
182
+ await start();
183
+ await settled(conversations.endTurn());
184
+ },
185
+ compact: async () => {
186
+ await start();
187
+ await settled(conversations.compact());
188
+ },
189
+ startTask: async (task) => {
190
+ await start();
191
+ return settled(conversations.startTask(task));
192
+ },
193
+ lines: async (target, props, requestId) => {
194
+ const component = componentOf(target);
195
+ if (props !== undefined && component === undefined)
196
+ throw new Error(`lines takes a pane id, or a place render draws and its props, and ${target} is neither a place such as ToolCall nor called without props.`);
197
+ const { drawing, columns } = props === undefined || component === undefined ? await drawPane(target) : await drawSlot(component, props, requestId);
138
198
  return rowsOf(drawing, columns).map((row) => row.trimEnd());
139
- }),
199
+ },
140
200
  async type(line) {
141
201
  const typed = /^\/(\S+)\s*(.*)$/s.exec(line);
142
202
  if (typed === null)
@@ -175,10 +235,10 @@ export function testMod(definition, options = {}) {
175
235
  return { element: e.element };
176
236
  });
177
237
  },
178
- async input(paneId, key, text, kind = 'submit') {
238
+ async input(paneId, key, text, type = 'submit') {
179
239
  const props = await drawnElement(paneId, 'Input', key);
180
- const typed = { ...eventIn(paneId, key), kind, value: text };
181
- await props[kind === 'submit' ? 'onSubmit' : 'onInput']?.(text, typed);
240
+ const typed = { ...eventIn(paneId, key), kind: type, value: text };
241
+ await props[type === 'submit' ? 'onSubmit' : 'onInput']?.(text, typed);
182
242
  },
183
243
  async select(paneId, key, value) {
184
244
  const props = await drawnElement(paneId, 'Select', key);
@@ -211,17 +271,24 @@ export function testMod(definition, options = {}) {
211
271
  },
212
272
  calls: fake.calls,
213
273
  fakes: fake.fakes,
214
- shown: fake.shown,
274
+ shown: {
275
+ ...fake.shown,
276
+ get toasts() {
277
+ return lifecycle.toasts;
278
+ },
279
+ sent: conversations.sent,
280
+ },
215
281
  };
216
282
  }
217
- function fakeDependencies(dependencies) {
218
- const apis = dependencies;
283
+ function fakeDependencies(caller, dependencies, conversations) {
284
+ const cmod = { forkParent: () => null, updateSettings: () => null };
285
+ const apis = { [cmodPluginName]: cmod, ...dependencies };
219
286
  return async (call) => {
287
+ const served = call.to === cmodPluginName ? conversations.answer(caller, call.method, call.input) : undefined;
288
+ if (served !== undefined)
289
+ return calledAnswer(caller, served);
220
290
  const api = apis[call.to];
221
- const answer = api === undefined ? { deny: notInstalled(call.to) } : await answerCall(call.to, api, call);
222
- if (answer.deny !== undefined)
223
- throw new Error(answer.deny);
224
- return answer.value;
291
+ return calledAnswer(caller, api === undefined ? { deny: notInstalled(call.to) } : await answerCall(call.to, api, call));
225
292
  };
226
293
  }
227
294
  function claudeCodeAnswer(event, e) {
@@ -239,7 +306,11 @@ function replyOf({ text, context }) {
239
306
  }
240
307
  async function modSteps() {
241
308
  const manifest = '../../../package.json';
242
- const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, () => undefined);
309
+ const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, (error) => {
310
+ if (error.code === 'ERR_MODULE_NOT_FOUND')
311
+ return undefined;
312
+ throw error;
313
+ });
243
314
  return readSteps(loaded) ?? {};
244
315
  }
245
316
  function installJsx() {
@@ -40,9 +40,8 @@ export declare function Pagination({ page, pages, onPage }: {
40
40
  readonly pages: number;
41
41
  readonly onPage: (page: number) => void;
42
42
  }): RenderElement;
43
- export declare function ProgressBar({ done, total, width }: {
44
- readonly done: number;
45
- readonly total: number;
43
+ export declare function Progress({ value, width }: {
44
+ readonly value: number;
46
45
  readonly width?: number;
47
46
  }): RenderElement;
48
47
  //# sourceMappingURL=components.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/ui/components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhE,MAAM,MAAM,GAAG,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAQtN;AAED,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GAAG,aAAa,CAGlG;AAED,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAExK;AAED,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAEzK;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAEpK;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAS7J;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAGlJ"}
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/ui/components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhE,MAAM,MAAM,GAAG,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAQtN;AAED,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAA;CAAE,GAAG,aAAa,CAGlG;AAED,wBAAgB,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAExK;AAED,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAExH;AAED,wBAAgB,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAEzK;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAAG,aAAa,CAEpK;AAED,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAAG,aAAa,CAS7J;AAED,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAGlH"}
package/ui/components.js CHANGED
@@ -37,9 +37,9 @@ export function Pagination({ page, pages, onPage }) {
37
37
  ],
38
38
  });
39
39
  }
40
- export function ProgressBar({ done, total, width = 24 }) {
41
- const filled = total > 0 ? Math.round(Math.min(done / total, 1) * width) : 0;
42
- return Text({ children: [Text({ color: 'claude', children: '█'.repeat(filled) }), Text({ color: 'subtle', children: '░'.repeat(width - filled) }), ' ', Text({ dimColor: true, children: `${done}/${total}` })] });
40
+ export function Progress({ value, width = 24 }) {
41
+ const filled = Math.round((Math.min(Math.max(value, 0), 100) / 100) * width);
42
+ return Text({ children: [Text({ color: 'claude', children: '█'.repeat(filled) }), Text({ color: 'subtle', children: '░'.repeat(width - filled) })] });
43
43
  }
44
44
  const cardColumns = 36;
45
45
  function card(text, isShown) {
@@ -1 +1 @@
1
- {"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/ui/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,YAAY,EAAY,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAUpG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,GAAG,KAAK,CAQjI;AAWD,eAAO,MAAM,GAAG,0EAAiB,CAAA;AACjC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,MAAM,6EAAoB,CAAA;AACvC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,QAAQ,yDAAsB,CAAA;AAC3C,eAAO,MAAM,KAAK,4EAAmB,CAAA;AACrC,eAAO,MAAM,MAAM,6EAAoB,CAAA;AACvC,eAAO,MAAM,KAAK,4EAAmB,CAAA"}
1
+ {"version":3,"file":"elements.d.ts","sourceRoot":"","sources":["../src/ui/elements.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,YAAY,EAAY,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAcpG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,GAAG,KAAK,CASjI;AAoBD,eAAO,MAAM,GAAG,0EAAiB,CAAA;AACjC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,MAAM,6EAAoB,CAAA;AACvC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,IAAI,2EAAkB,CAAA;AACnC,eAAO,MAAM,QAAQ,yDAAsB,CAAA;AAC3C,eAAO,MAAM,KAAK,4EAAmB,CAAA;AACrC,eAAO,MAAM,MAAM,6EAAoB,CAAA;AACvC,eAAO,MAAM,KAAK,4EAAmB,CAAA"}
package/ui/elements.js CHANGED
@@ -1,24 +1,34 @@
1
+ const unbuilt = Symbol('an element built outside a render');
1
2
  let drawing;
2
3
  export function drawWith(table, draw, markdown) {
3
4
  const outer = drawing;
4
- drawing = { table: table, markdown };
5
+ const current = { table: table, markdown };
6
+ drawing = current;
5
7
  try {
6
- return draw();
8
+ return built(current, draw());
7
9
  }
8
10
  finally {
9
11
  drawing = outer;
10
12
  }
11
13
  }
12
14
  function element(name) {
13
- const construct = (props) => {
14
- if (drawing === undefined)
15
- throw new Error(`${name} was called outside a render. Use it inside a pane's render or a slot's component.`);
16
- if (name === 'Markdown' && drawing.markdown !== undefined)
17
- return drawing.markdown(props);
18
- return drawing.table[name](props);
19
- };
15
+ const construct = (props) => (drawing === undefined ? { [unbuilt]: { name, props } } : build(drawing, name, props));
20
16
  return construct;
21
17
  }
18
+ function build(current, name, props) {
19
+ const ready = 'children' in props ? { ...props, children: built(current, props.children) } : props;
20
+ if (name === 'Markdown' && current.markdown !== undefined)
21
+ return current.markdown(ready);
22
+ return current.table[name](ready);
23
+ }
24
+ function built(current, value) {
25
+ if (Array.isArray(value))
26
+ return value.map((child) => built(current, child));
27
+ if (typeof value !== 'object' || value === null || !(unbuilt in value))
28
+ return value;
29
+ const { name, props } = value[unbuilt];
30
+ return build(current, name, props);
31
+ }
22
32
  export const Box = element('Box');
23
33
  export const Text = element('Text');
24
34
  export const Button = element('Button');
package/ui/markdown.d.ts CHANGED
@@ -1,49 +1,51 @@
1
- import type { Slot, SlotProps } from './slots.js';
1
+ import type { PlaceProps } from './places.js';
2
2
  type Block = {
3
3
  readonly text: string;
4
4
  readonly source: string;
5
5
  };
6
- type MarkdownSlot<Props extends object = object> = Slot<Props, {
7
- readonly source?: string;
8
- }, {
9
- readonly markdown: MarkdownKind;
10
- }>;
11
- export type MarkdownPiece<Render> = {
12
- readonly text: string;
13
- } | {
14
- readonly render: Render;
15
- readonly props: Block;
16
- };
17
- export type MarkdownReader = <Render>(text: string, renderOf: (type: string) => Render | undefined, replyId?: string) => readonly MarkdownPiece<Render>[];
18
- export type MarkdownKind = {
19
- readonly name: string;
20
- readonly type: string;
21
- readonly createReader: () => MarkdownReader;
22
- };
23
- export declare const markdownSlots: {
24
- Heading: MarkdownSlot<Block & {
6
+ type BlockFields = {
7
+ heading: {
25
8
  readonly depth: 1 | 2 | 3 | 4 | 5 | 6;
26
- }>;
27
- Paragraph: MarkdownSlot<Block>;
28
- CodeBlock: MarkdownSlot<Block & {
9
+ };
10
+ code: {
29
11
  readonly lang: string | undefined;
30
12
  readonly meta: string | undefined;
31
13
  readonly value: string;
32
- }>;
33
- BlockQuote: MarkdownSlot<Block>;
34
- List: MarkdownSlot<Block & {
14
+ };
15
+ list: {
35
16
  readonly ordered: boolean;
36
17
  readonly start: number | undefined;
37
18
  readonly spread: boolean;
38
- }>;
39
- Table: MarkdownSlot<Block & {
19
+ };
20
+ table: {
40
21
  readonly align: readonly ('left' | 'right' | 'center' | null)[];
41
- }>;
42
- ThematicBreak: MarkdownSlot<Block>;
43
- HtmlBlock: MarkdownSlot<Block & {
22
+ };
23
+ html: {
44
24
  readonly value: string;
45
- }>;
25
+ };
26
+ };
27
+ export type BlockProps<Type extends string> = Type extends keyof BlockFields ? Block & BlockFields[Type] : Block;
28
+ export type MarkdownPiece<Render> = {
29
+ readonly text: string;
30
+ } | {
31
+ readonly render: Render;
32
+ readonly props: Block;
33
+ };
34
+ export type MarkdownReader = <Render>(text: string, renderOf: (type: string) => Render | undefined, replyId?: string) => readonly MarkdownPiece<Render>[];
35
+ export type MarkdownPlace<Type extends string = string> = {
36
+ readonly name: string;
37
+ readonly markdown: Type;
38
+ createReader(): MarkdownReader;
46
39
  };
47
- export declare function markdownBlocks<S extends MarkdownSlot>(text: string, slot: S): Omit<SlotProps<S>, 'Default'>[];
40
+ export declare const Heading: MarkdownPlace<"heading">;
41
+ export declare const Paragraph: MarkdownPlace<"paragraph">;
42
+ export declare const CodeBlock: MarkdownPlace<"code">;
43
+ export declare const BlockQuote: MarkdownPlace<"blockquote">;
44
+ export declare const List: MarkdownPlace<"list">;
45
+ export declare const Table: MarkdownPlace<"table">;
46
+ export declare const ThematicBreak: MarkdownPlace<"thematicBreak">;
47
+ export declare const HtmlBlock: MarkdownPlace<"html">;
48
+ export declare function createReader(): MarkdownReader;
49
+ export declare function markdownBlocks<P extends MarkdownPlace>(text: string, place: P): Omit<PlaceProps<P>, 'Default'>[];
48
50
  export {};
49
51
  //# sourceMappingURL=markdown.d.ts.map