@cmodjs/core 0.3.0 → 1.5.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 (280) hide show
  1. package/docs/commands.md +61 -10
  2. package/docs/dependencies.md +16 -14
  3. package/docs/hooks.md +64 -29
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +13 -9
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +227 -175
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +311 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +68 -23
  13. package/docs/ui.md +116 -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 +14 -4
  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 +178 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +127 -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 +13 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +33 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +34 -5
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +68 -0
  61. package/runtime/before.d.ts +32 -0
  62. package/runtime/before.d.ts.map +1 -0
  63. package/runtime/before.js +214 -0
  64. package/runtime/bound.d.ts +4 -0
  65. package/runtime/bound.d.ts.map +1 -0
  66. package/runtime/bound.js +9 -0
  67. package/runtime/claude.d.ts +4 -2
  68. package/runtime/claude.d.ts.map +1 -1
  69. package/runtime/conversations.d.ts +17 -0
  70. package/runtime/conversations.d.ts.map +1 -0
  71. package/runtime/conversations.js +563 -0
  72. package/runtime/deadline.d.ts +0 -1
  73. package/runtime/deadline.d.ts.map +1 -1
  74. package/runtime/deadline.js +1 -1
  75. package/runtime/dependencies.d.ts +2 -0
  76. package/runtime/dependencies.d.ts.map +1 -1
  77. package/runtime/dependencies.js +10 -1
  78. package/runtime/engine.d.ts +13 -0
  79. package/runtime/engine.d.ts.map +1 -0
  80. package/runtime/engine.js +10 -0
  81. package/runtime/grants.d.ts +4 -0
  82. package/runtime/grants.d.ts.map +1 -1
  83. package/runtime/grants.js +68 -31
  84. package/runtime/hooks.d.ts +5 -4
  85. package/runtime/hooks.d.ts.map +1 -1
  86. package/runtime/hooks.js +29 -9
  87. package/runtime/installer.d.ts +1 -1
  88. package/runtime/installer.d.ts.map +1 -1
  89. package/runtime/installer.js +17 -14
  90. package/runtime/lifecycle.d.ts +3 -1
  91. package/runtime/lifecycle.d.ts.map +1 -1
  92. package/runtime/lifecycle.js +414 -212
  93. package/runtime/metadata.d.ts +3 -1
  94. package/runtime/metadata.d.ts.map +1 -1
  95. package/runtime/metadata.js +25 -14
  96. package/runtime/options.d.ts +3 -1
  97. package/runtime/options.d.ts.map +1 -1
  98. package/runtime/options.js +4 -4
  99. package/runtime/programs.d.ts +8 -0
  100. package/runtime/programs.d.ts.map +1 -1
  101. package/runtime/programs.js +39 -1
  102. package/runtime/router.d.ts +2 -1
  103. package/runtime/router.d.ts.map +1 -1
  104. package/runtime/router.js +11 -3
  105. package/runtime/sessions.d.ts +58 -0
  106. package/runtime/sessions.d.ts.map +1 -0
  107. package/runtime/sessions.js +433 -0
  108. package/runtime/state.d.ts +6 -2
  109. package/runtime/state.d.ts.map +1 -1
  110. package/runtime/state.js +46 -13
  111. package/runtime/toaster.d.ts +24 -0
  112. package/runtime/toaster.d.ts.map +1 -0
  113. package/runtime/toaster.js +286 -0
  114. package/runtime/ui.d.ts +6 -17
  115. package/runtime/ui.d.ts.map +1 -1
  116. package/runtime/ui.js +62 -118
  117. package/session/context/context.d.ts +12 -0
  118. package/session/context/context.d.ts.map +1 -0
  119. package/session/context/context.js +2 -0
  120. package/session/context/turn/message/document.d.ts +7 -0
  121. package/session/context/turn/message/document.d.ts.map +1 -0
  122. package/session/context/turn/message/document.js +1 -0
  123. package/session/context/turn/message/message.d.ts +55 -0
  124. package/session/context/turn/message/message.d.ts.map +1 -0
  125. package/session/context/turn/message/message.js +178 -0
  126. package/session/context/turn/message/note.d.ts +8 -0
  127. package/session/context/turn/message/note.d.ts.map +1 -0
  128. package/session/context/turn/message/note.js +1 -0
  129. package/session/context/turn/message/notice.d.ts +6 -0
  130. package/session/context/turn/message/notice.d.ts.map +1 -0
  131. package/session/context/turn/message/notice.js +1 -0
  132. package/session/context/turn/message/prompt.d.ts +8 -0
  133. package/session/context/turn/message/prompt.d.ts.map +1 -0
  134. package/session/context/turn/message/prompt.js +1 -0
  135. package/session/context/turn/message/reply.d.ts +6 -0
  136. package/session/context/turn/message/reply.d.ts.map +1 -0
  137. package/session/context/turn/message/reply.js +1 -0
  138. package/session/context/turn/message/sender.d.ts +14 -0
  139. package/session/context/turn/message/sender.d.ts.map +1 -0
  140. package/session/context/turn/message/sender.js +30 -0
  141. package/session/context/turn/message/skill.d.ts +7 -0
  142. package/session/context/turn/message/skill.d.ts.map +1 -0
  143. package/session/context/turn/message/skill.js +1 -0
  144. package/session/context/turn/message/tool-call.d.ts +9 -0
  145. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  146. package/session/context/turn/message/tool-call.js +1 -0
  147. package/session/context/turn/message/tool-result.d.ts +8 -0
  148. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  149. package/session/context/turn/message/tool-result.js +1 -0
  150. package/session/context/turn/turn.d.ts +13 -0
  151. package/session/context/turn/turn.d.ts.map +1 -0
  152. package/session/context/turn/turn.js +2 -0
  153. package/session/conversation.d.ts +73 -0
  154. package/session/conversation.d.ts.map +1 -0
  155. package/session/conversation.js +146 -0
  156. package/session/goal/goal.d.ts +11 -0
  157. package/session/goal/goal.d.ts.map +1 -0
  158. package/session/goal/goal.js +1 -0
  159. package/session/goal/task.d.ts +16 -0
  160. package/session/goal/task.d.ts.map +1 -0
  161. package/session/goal/task.js +3 -0
  162. package/session/session.d.ts +158 -0
  163. package/session/session.d.ts.map +1 -0
  164. package/session/session.js +2 -0
  165. package/src/jobs/check.ts +7 -4
  166. package/src/jobs/context.ts +24 -82
  167. package/src/jobs/permissions/decide-permission.ts +10 -8
  168. package/src/jobs/permissions/find-project-scope.ts +8 -14
  169. package/src/jobs/permissions/match-target.ts +3 -3
  170. package/src/jobs/permissions.ts +8 -6
  171. package/src/jobs/program.ts +13 -4
  172. package/src/jobs/prompt.ts +21 -24
  173. package/src/jobs/schedule.ts +14 -14
  174. package/src/jobs/slash-command.ts +20 -14
  175. package/src/jobs/status-line.ts +9 -21
  176. package/src/jobs/tool.ts +17 -11
  177. package/src/mod.ts +252 -97
  178. package/src/options.ts +22 -22
  179. package/src/records.ts +40 -23
  180. package/src/register.ts +34 -5
  181. package/src/runtime/announce.ts +76 -0
  182. package/src/runtime/before.ts +200 -0
  183. package/src/runtime/bound.ts +12 -0
  184. package/src/runtime/claude.ts +7 -2
  185. package/src/runtime/conversations.ts +579 -0
  186. package/src/runtime/deadline.ts +2 -2
  187. package/src/runtime/dependencies.ts +13 -1
  188. package/src/runtime/engine.ts +23 -0
  189. package/src/runtime/grants.ts +69 -29
  190. package/src/runtime/hooks.ts +39 -14
  191. package/src/runtime/installer.ts +16 -14
  192. package/src/runtime/lifecycle.ts +413 -201
  193. package/src/runtime/metadata.ts +37 -14
  194. package/src/runtime/options.ts +19 -4
  195. package/src/runtime/programs.ts +40 -1
  196. package/src/runtime/router.ts +11 -4
  197. package/src/runtime/sessions.ts +447 -0
  198. package/src/runtime/state.ts +50 -14
  199. package/src/runtime/toaster.ts +334 -0
  200. package/src/runtime/ui.ts +65 -143
  201. package/src/session/context/context.ts +14 -0
  202. package/src/session/context/turn/message/document.ts +8 -0
  203. package/src/session/context/turn/message/message.ts +203 -0
  204. package/src/session/context/turn/message/note.ts +9 -0
  205. package/src/session/context/turn/message/notice.ts +7 -0
  206. package/src/session/context/turn/message/prompt.ts +9 -0
  207. package/src/session/context/turn/message/reply.ts +7 -0
  208. package/src/session/context/turn/message/sender.ts +38 -0
  209. package/src/session/context/turn/message/skill.ts +8 -0
  210. package/src/session/context/turn/message/tool-call.ts +10 -0
  211. package/src/session/context/turn/message/tool-result.ts +9 -0
  212. package/src/session/context/turn/turn.ts +15 -0
  213. package/src/session/conversation.ts +194 -0
  214. package/src/session/goal/goal.ts +10 -0
  215. package/src/session/goal/task.ts +19 -0
  216. package/src/session/session.ts +141 -0
  217. package/src/testing/fake-claude.ts +25 -17
  218. package/src/testing/fake-elements.ts +3 -2
  219. package/src/testing/fake-files.ts +1 -1
  220. package/src/testing/fake-sessions.ts +277 -0
  221. package/src/testing.ts +138 -35
  222. package/src/ui/components.ts +3 -3
  223. package/src/ui/elements.ts +22 -8
  224. package/src/ui/markdown.ts +22 -26
  225. package/src/ui/places.ts +49 -0
  226. package/src/utils/call-effects.ts +5 -5
  227. package/src/utils/changelog.ts +32 -0
  228. package/src/utils/disposable.ts +3 -0
  229. package/src/utils/matcher.ts +37 -0
  230. package/src/utils/metadata.ts +17 -15
  231. package/src/utils/parse-shell.ts +9 -8
  232. package/src/utils/paths.ts +21 -4
  233. package/src/utils/text.ts +4 -0
  234. package/testing/fake-claude.d.ts +5 -1
  235. package/testing/fake-claude.d.ts.map +1 -1
  236. package/testing/fake-claude.js +21 -12
  237. package/testing/fake-elements.js +4 -2
  238. package/testing/fake-files.js +1 -1
  239. package/testing/fake-sessions.d.ts +81 -0
  240. package/testing/fake-sessions.d.ts.map +1 -0
  241. package/testing/fake-sessions.js +249 -0
  242. package/testing.d.ts +34 -13
  243. package/testing.d.ts.map +1 -1
  244. package/testing.js +115 -23
  245. package/ui/components.d.ts +2 -3
  246. package/ui/components.d.ts.map +1 -1
  247. package/ui/components.js +3 -3
  248. package/ui/elements.d.ts.map +1 -1
  249. package/ui/elements.js +19 -9
  250. package/ui/markdown.d.ts +35 -33
  251. package/ui/markdown.d.ts.map +1 -1
  252. package/ui/markdown.js +14 -16
  253. package/ui/places.d.ts +43 -0
  254. package/ui/places.d.ts.map +1 -0
  255. package/ui/places.js +31 -0
  256. package/utils/call-effects.d.ts +2 -1
  257. package/utils/call-effects.d.ts.map +1 -1
  258. package/utils/call-effects.js +3 -3
  259. package/utils/changelog.d.ts +9 -0
  260. package/utils/changelog.d.ts.map +1 -0
  261. package/utils/changelog.js +29 -0
  262. package/utils/disposable.d.ts +4 -0
  263. package/utils/disposable.d.ts.map +1 -0
  264. package/utils/disposable.js +1 -0
  265. package/utils/matcher.d.ts +13 -0
  266. package/utils/matcher.d.ts.map +1 -0
  267. package/utils/matcher.js +32 -0
  268. package/utils/metadata.d.ts +3 -3
  269. package/utils/metadata.js +18 -14
  270. package/utils/parse-shell.js +9 -7
  271. package/utils/paths.d.ts +4 -1
  272. package/utils/paths.d.ts.map +1 -1
  273. package/utils/paths.js +19 -4
  274. package/utils/text.d.ts +1 -0
  275. package/utils/text.d.ts.map +1 -1
  276. package/utils/text.js +3 -0
  277. package/src/ui/slots.ts +0 -36
  278. package/ui/slots.d.ts +0 -208
  279. package/ui/slots.d.ts.map +0 -1
  280. package/ui/slots.js +0 -19
@@ -0,0 +1,249 @@
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 { GoalChange } from '../session/goal/goal.js';
10
+ import { Task } from '../session/goal/task.js';
11
+ import { Skill } from '../session/context/turn/message/skill.js';
12
+ import { ToolCall } from '../session/context/turn/message/tool-call.js';
13
+ import { ToolResult } from '../session/context/turn/message/tool-result.js';
14
+ import { createConversation, emptyConversation, searchLimit } from '../session/conversation.js';
15
+ import { conversationMethods, everySession, isHeardBy, resultOf } from '../runtime/sessions.js';
16
+ import { receivedMatcher } from '../utils/matcher.js';
17
+ import { firstLine, messageOf } from '../utils/text.js';
18
+ export function fakeSessions({ project, given, push, callTool }) {
19
+ const kept = new Map();
20
+ const heard = new Map();
21
+ const watches = new Map();
22
+ const goals = new Map();
23
+ const sent = [];
24
+ let current = '';
25
+ let clock = Date.parse('2026-01-01T00:00:00.000Z');
26
+ let ids = 0;
27
+ const now = () => new Date((clock += 1000)).toISOString();
28
+ const keep = (data) => {
29
+ const session = { data, conversation: createConversation(emptyConversation(data.id)), messages: [], turns: new Map(), contexts: new Map(), tasks: new Map() };
30
+ kept.set(data.id, session);
31
+ return session;
32
+ };
33
+ const record = (session, changes) => {
34
+ for (const change of changes) {
35
+ if (change.type === Message)
36
+ session.messages.push(change.message);
37
+ if ('turn' in change)
38
+ session.turns.set(change.turn.prompt.id, change.turn);
39
+ if ('context' in change)
40
+ session.contexts.set(change.context.number, change.context);
41
+ if ('task' in change)
42
+ session.tasks.set(change.task.id, change.task);
43
+ 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) } : {}) };
44
+ for (const [mod, sessions] of heard) {
45
+ if ([...(sessions.get(session.data.id) ?? []), ...(sessions.get(everySession) ?? [])].some(({ event, matcher }) => isHeardBy(event, matcher, change)))
46
+ push(mod, session.data.id, change);
47
+ }
48
+ }
49
+ };
50
+ const add = (session, draft, task) => {
51
+ const before = session.messages.length;
52
+ record(session, session.conversation.add(rowOf(draft, `message-${(ids += 1)}`, now()), task));
53
+ return session.messages.slice(before);
54
+ };
55
+ for (const each of given) {
56
+ const session = keep({ title: '', project: project(), cwd: project(), model: 'test-model', startedAt: now(), lastAt: '', isRunning: false, ...each });
57
+ for (const draft of each.messages ?? [])
58
+ add(session, draft);
59
+ if (each.goal !== undefined)
60
+ record(session, session.conversation.changeGoal(each.goal));
61
+ }
62
+ const sessionOf = (id) => {
63
+ const session = kept.get(id);
64
+ if (session === undefined)
65
+ throw new Error(`No Claude Code transcript is named ${id}.jsonl. Give it to testMod as { sessions: [{ id: '${id}' }] }.`);
66
+ return session;
67
+ };
68
+ const currentSession = () => sessionOf(current);
69
+ const place = () => {
70
+ const { session, context, contextOpenedAt, turn, tasks, taskTurns } = currentSession().conversation.state;
71
+ return { session, context, contextOpenedAt, ...(turn === undefined ? {} : { turn }), tasks, taskTurns };
72
+ };
73
+ const taskOf = (session, id) => {
74
+ const task = session.tasks.get(id);
75
+ if (task === undefined)
76
+ throw new Error(`Session ${session.data.id} has no task ${id}. Start it with tested.startTask.`);
77
+ return task;
78
+ };
79
+ const turnsOf = (session, task) => [...session.turns.values()].filter((turn) => turn.prompt.task === (typeof task === 'string' ? task : undefined));
80
+ const isUnder = (session, task, ancestor) => {
81
+ for (let at = task, steps = 0; at !== undefined && steps < 64; at = session?.tasks.get(at)?.parent, steps += 1)
82
+ if (at === ancestor)
83
+ return true;
84
+ return false;
85
+ };
86
+ const lastOf = (items, last) => (typeof last === 'number' ? items.slice(-last) : [...items]);
87
+ 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);
88
+ const methods = {
89
+ [conversationMethods.sessions]: (request) => [...kept.values()]
90
+ .filter((session) => isInScope(session, request))
91
+ .map((session) => session.data)
92
+ .toSorted((first, second) => second.lastAt.localeCompare(first.lastAt)),
93
+ [conversationMethods.search](request) {
94
+ const text = String(request['text']);
95
+ if ([...text].length < 3)
96
+ throw new Error('cmod history search takes at least 3 characters.');
97
+ const inRecord = (message) => {
98
+ const session = kept.get(message.session);
99
+ const task = request['task'];
100
+ if (request['session'] !== undefined && message.session !== request['session'])
101
+ return false;
102
+ if (typeof task === 'string' && (message.task === undefined || !isUnder(session, message.task, task) || (request['isGoal'] === true && message.task === task)))
103
+ return false;
104
+ if (task === undefined && request['isGoal'] === true && message.task === undefined)
105
+ return false;
106
+ return (request['turn'] === undefined || message.turn === request['turn']) && (request['context'] === undefined || message.context === request['context']);
107
+ };
108
+ const found = [...kept.values()]
109
+ .filter((session) => request['session'] !== undefined || isInScope(session, request))
110
+ .flatMap((session) => session.messages)
111
+ .filter((message) => inRecord(message) && searchText(message).toLowerCase().includes(text.toLowerCase()))
112
+ .toSorted((first, second) => second.at.localeCompare(first.at));
113
+ const before = isObject(request['before']) ? request['before'] : undefined;
114
+ const start = before === undefined ? 0 : found.findIndex((message) => message.session === before['session'] && message.id === before['id']) + 1;
115
+ return found.slice(start, start + (typeof request['limit'] === 'number' ? request['limit'] : searchLimit));
116
+ },
117
+ [conversationMethods.messages](request) {
118
+ const session = sessionOf(String(request['session']));
119
+ const fits = session.messages.filter((message) => message.task === request['task'] && (request['turn'] === undefined || message.turn === request['turn']));
120
+ const after = typeof request['after'] === 'string' ? fits.findIndex((message) => message.id === request['after']) + 1 : 0;
121
+ return lastOf(fits.slice(after), request['last']);
122
+ },
123
+ [conversationMethods.turns]: (request) => lastOf(turnsOf(sessionOf(String(request['session'])), request['task']).filter((turn) => request['context'] === undefined || turn.context === request['context']), request['last']),
124
+ [conversationMethods.contexts](request) {
125
+ const session = sessionOf(String(request['session']));
126
+ if (typeof request['task'] !== 'string')
127
+ return [...session.contexts.values()];
128
+ const task = taskOf(session, request['task']);
129
+ return [{ number: session.conversation.state.tasks[task.id]?.context ?? 1, openedAt: task.openedAt, ...(task.closedAt === undefined ? {} : { closedAt: task.closedAt }) }];
130
+ },
131
+ [conversationMethods.tasks]: (request) => [...sessionOf(String(request['session'])).tasks.values()].filter((task) => request['all'] === true || task.parent === request['task']),
132
+ [conversationMethods.goal](request) {
133
+ const session = sessionOf(String(request['session']));
134
+ const task = request['task'];
135
+ if (typeof task !== 'string')
136
+ return { text: session.conversation.state.goal ?? '' };
137
+ return { text: goals.get(task) ?? turnsOf(session, task)[0]?.prompt.text ?? '' };
138
+ },
139
+ [conversationMethods.updateGoal](request) {
140
+ const session = sessionOf(String(request['session']));
141
+ const task = typeof request['task'] === 'string' ? request['task'] : undefined;
142
+ const text = String(request['text']);
143
+ if (task === undefined)
144
+ record(session, session.conversation.changeGoal(text));
145
+ else {
146
+ goals.set(task, text);
147
+ record(session, [{ type: GoalChange, goal: { text, task } }]);
148
+ }
149
+ return null;
150
+ },
151
+ [conversationMethods.watchBefore](request, caller) {
152
+ const targets = watches.get(caller) ?? new Map();
153
+ targets.set(String(request['session']), (Array.isArray(request['subscriptions']) ? request['subscriptions'] : []).map(({ event }) => event));
154
+ watches.set(caller, targets);
155
+ return null;
156
+ },
157
+ async [conversationMethods.act](request) {
158
+ const session = sessionOf(String(request['session']));
159
+ const action = String(request['action']);
160
+ const input = isObject(request['input']) ? request['input'] : {};
161
+ const task = typeof request['task'] === 'string' ? request['task'] : undefined;
162
+ const isLive = task === undefined ? session.data.isRunning : session.data.isRunning && taskOf(session, task).closedAt === undefined;
163
+ if (action === 'usage')
164
+ return { model: task === undefined ? session.data.model : (taskOf(session, task).model ?? session.data.model), context: {} };
165
+ if (action === 'clients')
166
+ return isLive ? ['terminal'] : [];
167
+ if (!isLive)
168
+ throw new Error(`session ${task ?? session.data.id} has ended`);
169
+ if (action === 'send') {
170
+ sent.push({ session: session.data.id, ...(task === undefined ? {} : { task }), type: String(input['type']), text: String(input['text']), ...(typeof input['as'] === 'string' ? { as: input['as'] } : {}) });
171
+ return null;
172
+ }
173
+ if (action === 'startTask') {
174
+ const id = `task-${(ids += 1)}`;
175
+ const data = { id, title: typeof input['name'] === 'string' ? input['name'] : '', agentType: typeof input['agentType'] === 'string' ? input['agentType'] : 'general-purpose', openedBy: '', openedAt: now(), ...(typeof input['model'] === 'string' ? { model: input['model'] } : {}) };
176
+ record(session, session.conversation.startTask(data));
177
+ return data;
178
+ }
179
+ if (session.data.id === current)
180
+ return resultOf(place(), await callTool({ ...input['input'], tool: String(input['name']) }));
181
+ return { id: '', type: ToolResult, at: now(), session: session.data.id, context: session.conversation.state.context, callId: '', text: '', isError: false };
182
+ },
183
+ [conversationMethods.subscribe](request, caller) {
184
+ const list = Array.isArray(request['subscriptions']) ? request['subscriptions'] : [];
185
+ const sessions = heard.get(caller) ?? new Map();
186
+ sessions.set(String(request['session']), list.map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : receivedMatcher(matcher) })));
187
+ heard.set(caller, sessions);
188
+ return null;
189
+ },
190
+ };
191
+ return {
192
+ answer(caller, method, input) {
193
+ const run = methods[method];
194
+ if (run === undefined)
195
+ return undefined;
196
+ try {
197
+ return { value: run(isObject(input) ? input : {}, caller) };
198
+ }
199
+ catch (error) {
200
+ return { deny: messageOf(error) };
201
+ }
202
+ },
203
+ add: (draft, task) => add(currentSession(), draft, task),
204
+ place,
205
+ wants: (mod, event) => [current, everySession].some((target) => (watches.get(mod)?.get(target) ?? []).includes(event)),
206
+ endTurn() {
207
+ const session = currentSession();
208
+ record(session, session.conversation.add({ id: `turn-end-${(ids += 1)}`, at: now(), type: 'system', name: 'turn_duration', isMeta: false, isSummary: false, content: [] }));
209
+ },
210
+ compact() {
211
+ const session = currentSession();
212
+ record(session, session.conversation.add({ id: `compact-${(ids += 1)}`, at: now(), type: 'system', name: 'compact_boundary', isMeta: false, isSummary: false, content: [] }));
213
+ },
214
+ startTask({ title, agentType = 'general-purpose', model, openedBy = '', parent }) {
215
+ const session = currentSession();
216
+ const id = `task-${(ids += 1)}`;
217
+ record(session, session.conversation.startTask({ id, title, agentType, openedBy, openedAt: now(), ...(model === undefined ? {} : { model }), ...(parent === undefined ? {} : { parent }) }));
218
+ return id;
219
+ },
220
+ start(id) {
221
+ current = id;
222
+ const session = kept.get(id) ?? keep({ id, title: '', project: project(), cwd: project(), model: 'test-model', startedAt: now(), lastAt: now(), isRunning: true });
223
+ session.data = { ...session.data, isRunning: true };
224
+ if (session.conversation.state.context === 0)
225
+ record(session, session.conversation.startContext(now()));
226
+ },
227
+ sent,
228
+ };
229
+ }
230
+ function rowOf(draft, id, at) {
231
+ const base = { id, at, isMeta: false, isSummary: false };
232
+ const text = (value) => [{ type: 'text', text: value }];
233
+ 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 }) });
234
+ if (draft.type === Prompt)
235
+ return { ...base, type: 'user', content: text(draft.text), origin: origin(draft.from) };
236
+ if (draft.type === Note)
237
+ return { ...base, type: 'user', isMeta: true, content: text(draft.text), origin: origin(draft.from ?? { type: Mod, name: 'test' }) };
238
+ if (draft.type === Reply)
239
+ return { ...base, type: 'assistant', content: text(draft.text) };
240
+ if (draft.type === ToolCall)
241
+ return { ...base, type: 'assistant', content: [{ type: 'tool_use', id: draft.callId ?? `toolu_${id}`, name: draft.name, input: draft.input ?? {} }], cwd: '/test/project' };
242
+ if (draft.type === ToolResult)
243
+ return { ...base, type: 'user', content: [{ type: 'tool_result', tool_use_id: draft.callId, content: draft.text, ...(draft.isError === true ? { is_error: true } : {}) }] };
244
+ if (draft.type === Skill)
245
+ return { ...base, type: 'user', isMeta: true, content: text(`${skillPrefix} /skills/${draft.name}\n\n${draft.text}`) };
246
+ if (draft.type === Document)
247
+ return { ...base, type: 'attachment', name: 'nested_memory', content: text(draft.text), path: draft.path };
248
+ return { ...base, type: 'system', name: 'informational', content: text(draft.text) };
249
+ }
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,33 @@ 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
+ readonly parent?: string;
53
+ }): Promise<string>;
31
54
  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>;
55
+ lines<P extends ComponentPlace>(place: P, props: Omit<PlaceProps<P>, 'Default'>, requestId?: string): Promise<string[]>;
56
+ type(line: string): Promise<CommandReply>;
36
57
  callTool(name: string, input: Record<string, unknown>): Promise<EventResult<'tool.call'>>;
37
58
  press(paneId: string, key: string, link?: string): Promise<void>;
38
- input(paneId: string, key: string, text: string, kind?: UiInputArgument['kind']): Promise<void>;
59
+ input(paneId: string, key: string, text: string, type?: UiInputArgument['kind']): Promise<void>;
39
60
  select(paneId: string, key: string, value: string): Promise<void>;
40
61
  moveTo(projectRoot: string, cwd?: string): Promise<void>;
41
62
  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":"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"}
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,20 @@
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
+ import { foldAnswers, installBefore } from './runtime/before.js';
4
+ import { createRouter } from './runtime/router.js';
3
5
  import { toolInputOf } from './runtime/tool-calls.js';
4
6
  import { createLifecycle } from './runtime/lifecycle.js';
5
7
  import { MissingOptions } from './runtime/options.js';
6
- import { fakeClaude } from './testing/fake-claude.js';
8
+ import { conversationMethods } from './runtime/sessions.js';
9
+ import { Note } from './session/context/turn/message/note.js';
10
+ import { Prompt } from './session/context/turn/message/prompt.js';
11
+ import { Mod, Person } from './session/context/turn/message/sender.js';
12
+ import { calledAnswer, fakeClaude } from './testing/fake-claude.js';
7
13
  import { elements, findElement, rowsOf } from './testing/fake-elements.js';
8
14
  import { fakeFiles } from './testing/fake-files.js';
9
- import { listed } from './utils/text.js';
15
+ import { fakeSessions } from './testing/fake-sessions.js';
16
+ import { listed, messageOf } from './utils/text.js';
17
+ import { componentOf } from './ui/places.js';
10
18
  const defaultColumns = 80;
11
19
  const defaultRows = 24;
12
20
  const presentation = { isFullscreen: false, columns: defaultColumns };
@@ -26,7 +34,32 @@ export function testMod(definition, options = {}) {
26
34
  fake.claude.session.id = async () => sessionId;
27
35
  fake.claude.session.root = async () => projectRoot;
28
36
  fake.claude.session.cwd = async () => cwd;
29
- fake.fakes.cmod.call = fakeDependencies(options.dependencies ?? {});
37
+ const conversations = fakeSessions({
38
+ callTool: (args) => fake.claude.tool.call(args),
39
+ project: () => projectRoot,
40
+ given: options.sessions ?? [],
41
+ push: (mod, session, change) => {
42
+ if (mod !== name)
43
+ return;
44
+ const pushed = { to: mod, method: conversationMethods.changed, input: { session, change }, caller: cmodPluginName };
45
+ void lifecycle.route('cmod.call', pushed, async () => ({ deny: notInstalled(mod) }));
46
+ },
47
+ });
48
+ conversations.start(sessionId);
49
+ fake.fakes.cmod.call = fakeDependencies(name, options.dependencies ?? {}, conversations);
50
+ const appendRow = fake.claude.session.append;
51
+ fake.claude.session.append = async (args) => {
52
+ const added = await appendRow(args);
53
+ if (added.deny === undefined)
54
+ conversations.add({ type: Note, text: args.message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''), from: { type: Mod, name } });
55
+ return added;
56
+ };
57
+ const submitRow = fake.claude.prompt.submit;
58
+ fake.claude.prompt.submit = async (args) => {
59
+ const submitted = await submitRow(args);
60
+ conversations.add({ type: Prompt, text: args.text, from: { type: Mod, name, ...(args.asUser === true ? { as: Person } : {}) } });
61
+ return submitted;
62
+ };
30
63
  const firedAgents = new Map();
31
64
  const listAgents = fake.claude.agent.list;
32
65
  fake.claude.agent.list = async () => {
@@ -34,7 +67,8 @@ export function testMod(definition, options = {}) {
34
67
  return listAgents();
35
68
  return [...firedAgents].map(([id, type]) => ({ id, type, description: '', status: 'running' }));
36
69
  };
37
- Object.assign(fake.fakes.fs, fakeFiles(options.files ?? {}, options.links));
70
+ const files = fakeFiles(options.files ?? {}, options.links);
71
+ Object.assign(fake.fakes.fs, files);
38
72
  const starting = options.state;
39
73
  const declared = Object.entries(definition.state ?? {});
40
74
  const state = Object.fromEntries(declared.map(([lifetime, values]) => [lifetime, { ...values, ...starting?.[lifetime] }]));
@@ -55,6 +89,7 @@ export function testMod(definition, options = {}) {
55
89
  };
56
90
  });
57
91
  await started;
92
+ await fake.settle();
58
93
  const { failure } = lifecycle;
59
94
  if (failure instanceof MissingOptions) {
60
95
  const given = failure.keys.map((key) => `${key}: …`).join(', ');
@@ -63,9 +98,23 @@ export function testMod(definition, options = {}) {
63
98
  if (lifecycle.phase !== 'active')
64
99
  throw failure ?? new Error(`${name} did not start: the lifecycle is ${lifecycle.phase}.`);
65
100
  };
101
+ const cmodPlugin = createRouter();
102
+ const askMod = async (event, change) => {
103
+ const asked = { to: name, method: conversationMethods.before, input: { session: sessionId, event, change }, caller: cmodPluginName };
104
+ return calledAnswer(cmodPluginName, await lifecycle.route('cmod.call', asked, async () => ({ deny: notInstalled(name) })));
105
+ };
106
+ installBefore({
107
+ claude: fake.claude,
108
+ router: cmodPlugin,
109
+ hooks: {
110
+ place: async () => conversations.place(),
111
+ wants: async (event) => conversations.wants(name, event),
112
+ decide: (event, change) => foldAnswers(event, change, [(latest) => askMod(event, latest)], (error) => fake.claude.ui.log(`cmod: passed the ${event} on without ${name}'s answer: ${messageOf(error)}`, { to: 'debug' })),
113
+ },
114
+ });
66
115
  const route = async (event, input, below) => {
67
116
  await start();
68
- return lifecycle.route(event, input, async (e) => (below ?? claudeCodeAnswer(event, e)));
117
+ return cmodPlugin.dispatch(event, input, (passed) => lifecycle.route(event, passed, async (e) => (below ?? claudeCodeAnswer(event, e))));
69
118
  };
70
119
  const drawPane = async (paneId) => {
71
120
  await start();
@@ -96,14 +145,19 @@ export function testMod(definition, options = {}) {
96
145
  const fire = ((event, input, below) => {
97
146
  if (event.includes('.'))
98
147
  return route(event, input, below);
99
- if (event === 'SessionStart')
148
+ if (event === 'SessionStart') {
100
149
  sessionId = input.session_id ?? sessionId;
150
+ conversations.start(sessionId);
151
+ }
101
152
  if (event === 'UserPromptSubmit') {
102
153
  cwd = cwdAfterPrompt ?? cwd;
103
154
  cwdAfterPrompt = undefined;
104
155
  }
105
156
  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 };
157
+ const transcript = `/test/transcripts/${sessionId}.jsonl`;
158
+ const filled = { session_id: sessionId, transcript_path: transcript, cwd, hook_event_name: event, ...toolUse, ...input };
159
+ if (event === 'SessionStart' && !('transcript_path' in input))
160
+ return files.write(transcript, '').then(() => route('classic.SessionStart', filled, below));
107
161
  if (event !== 'PreToolUse')
108
162
  return route(`classic.${event}`, filled, below);
109
163
  return firePreToolUse(filled, below);
@@ -119,7 +173,7 @@ export function testMod(definition, options = {}) {
119
173
  const answer = await lifecycle.route('tool.call', envelope, async (e) => {
120
174
  reached = e;
121
175
  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: '' });
176
+ return check.decision === 'deny' ? { deny: check.reason ?? '' } : { result: '' };
123
177
  });
124
178
  const updatedInput = toolInputOf(reached);
125
179
  const decided = check === asks ? {} : check.decision === 'allow' ? { allow: true } : check.decision === 'ask' ? { ask: check.reason ?? '' } : {};
@@ -129,14 +183,37 @@ export function testMod(definition, options = {}) {
129
183
  ...(answer.context === undefined || answer.context.length === 0 ? {} : { additionalContext: [...answer.context] }),
130
184
  };
131
185
  };
186
+ const settled = async (value) => {
187
+ await fake.settle();
188
+ return value;
189
+ };
132
190
  return {
133
191
  start,
134
192
  fire,
135
193
  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);
194
+ add: async (message, added) => {
195
+ await start();
196
+ return settled(conversations.add(message, added?.task));
197
+ },
198
+ endTurn: async () => {
199
+ await start();
200
+ await settled(conversations.endTurn());
201
+ },
202
+ compact: async () => {
203
+ await start();
204
+ await settled(conversations.compact());
205
+ },
206
+ startTask: async (task) => {
207
+ await start();
208
+ return settled(conversations.startTask(task));
209
+ },
210
+ lines: async (target, props, requestId) => {
211
+ const component = componentOf(target);
212
+ if (props !== undefined && component === undefined)
213
+ 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.`);
214
+ const { drawing, columns } = props === undefined || component === undefined ? await drawPane(target) : await drawSlot(component, props, requestId);
138
215
  return rowsOf(drawing, columns).map((row) => row.trimEnd());
139
- }),
216
+ },
140
217
  async type(line) {
141
218
  const typed = /^\/(\S+)\s*(.*)$/s.exec(line);
142
219
  if (typed === null)
@@ -175,10 +252,10 @@ export function testMod(definition, options = {}) {
175
252
  return { element: e.element };
176
253
  });
177
254
  },
178
- async input(paneId, key, text, kind = 'submit') {
255
+ async input(paneId, key, text, type = 'submit') {
179
256
  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);
257
+ const typed = { ...eventIn(paneId, key), kind: type, value: text };
258
+ await props[type === 'submit' ? 'onSubmit' : 'onInput']?.(text, typed);
182
259
  },
183
260
  async select(paneId, key, value) {
184
261
  const props = await drawnElement(paneId, 'Select', key);
@@ -211,20 +288,31 @@ export function testMod(definition, options = {}) {
211
288
  },
212
289
  calls: fake.calls,
213
290
  fakes: fake.fakes,
214
- shown: fake.shown,
291
+ shown: {
292
+ ...fake.shown,
293
+ get toasts() {
294
+ return lifecycle.toasts;
295
+ },
296
+ sent: conversations.sent,
297
+ },
215
298
  };
216
299
  }
217
- function fakeDependencies(dependencies) {
218
- const apis = dependencies;
300
+ function fakeDependencies(caller, dependencies, conversations) {
301
+ const cmod = { forkParent: () => null, updateSettings: () => null };
302
+ const apis = { [cmodPluginName]: cmod, ...dependencies };
219
303
  return async (call) => {
304
+ const served = call.to === cmodPluginName ? conversations.answer(caller, call.method, call.input) : undefined;
305
+ if (served !== undefined)
306
+ return calledAnswer(caller, served);
220
307
  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;
308
+ return calledAnswer(caller, api === undefined ? { deny: notInstalled(call.to) } : await answerCall(call.to, api, call));
225
309
  };
226
310
  }
227
311
  function claudeCodeAnswer(event, e) {
312
+ if (event === 'agent.spawn') {
313
+ const { model, parentModel } = e;
314
+ return { agentId: 'agent-1', model: model ?? parentModel };
315
+ }
228
316
  if (event !== 'prompt.submit')
229
317
  return {};
230
318
  const { text, context, origin } = e;
@@ -239,7 +327,11 @@ function replyOf({ text, context }) {
239
327
  }
240
328
  async function modSteps() {
241
329
  const manifest = '../../../package.json';
242
- const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, () => undefined);
330
+ const loaded = await import(manifest, { with: { type: 'json' } }).then((module) => module.default, (error) => {
331
+ if (error.code === 'ERR_MODULE_NOT_FOUND')
332
+ return undefined;
333
+ throw error;
334
+ });
243
335
  return readSteps(loaded) ?? {};
244
336
  }
245
337
  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"}