@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
@@ -1,5 +1,6 @@
1
1
  import type { FileMatch, Metadata } from '../mod.js';
2
2
  import type { Claude } from './claude.js';
3
+ import type { Failure } from './toaster.js';
3
4
  export type Places = {
4
5
  readonly home: string | undefined;
5
6
  readonly configRoot: string | undefined;
@@ -11,10 +12,11 @@ export type ModFiles = {
11
12
  update(path: string, change: (metadata: Record<string, string>) => void): Promise<void>;
12
13
  };
13
14
  export declare function resolvedPath(path: string, places: Places): string;
14
- export declare function createModFiles({ name, claude, places, checkWrite }: {
15
+ export declare function createModFiles({ name, claude, places, checkWrite, report, }: {
15
16
  readonly name: string;
16
17
  readonly claude: Claude;
17
18
  readonly places: Places;
18
19
  readonly checkWrite: (path: string) => Promise<void>;
20
+ readonly report: (failure: Failure) => void;
19
21
  }): ModFiles;
20
22
  //# sourceMappingURL=metadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/runtime/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAA;IACrE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxF,CAAA;AAUD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAAG,QAAQ,CAoHhN"}
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/runtime/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAKpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;CACnC,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAA;IACrE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxF,CAAA;AAUD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,MAAM,EACN,MAAM,EACN,UAAU,EACV,MAAM,GACP,EAAE;IACD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C,GAAG,QAAQ,CA4HX"}
@@ -1,4 +1,5 @@
1
1
  import { formatOf, MetadataError, readMetadata, sidecarSuffix, writeMetadata } from '../utils/metadata.js';
2
+ import { ifMissing, isMissing } from '../utils/paths.js';
2
3
  import { messageOf } from '../utils/text.js';
3
4
  import { picomatch, resolve } from '../vendor.js';
4
5
  const skippedFolders = ['.git', 'node_modules'];
@@ -14,7 +15,7 @@ export function resolvedPath(path, places) {
14
15
  }
15
16
  return path.startsWith('/') ? resolve(path) : resolve(places.projectRoot(), path);
16
17
  }
17
- export function createModFiles({ name, claude, places, checkWrite }) {
18
+ export function createModFiles({ name, claude, places, checkWrite, report, }) {
18
19
  const prefix = `${name}.`;
19
20
  const cache = new Map();
20
21
  const logged = new Set();
@@ -27,11 +28,20 @@ export function createModFiles({ name, claude, places, checkWrite }) {
27
28
  const extensionless = !realPath.slice(realPath.lastIndexOf('/') + 1).includes('.');
28
29
  const firstLine = extensionless ? (await claude.fs.read(realPath)).split('\n', 1)[0] : undefined;
29
30
  const format = formatOf(realPath, firstLine);
30
- const holder = format.kind === 'sidecar' ? `${realPath}${sidecarSuffix}` : realPath;
31
- const sidecar = format.kind === 'sidecar' ? await claude.fs.stat(holder).catch(() => undefined) : undefined;
31
+ const holder = format.type === 'sidecar' ? `${realPath}${sidecarSuffix}` : realPath;
32
+ const sidecar = format.type === 'sidecar' ? await claude.fs.stat(holder).catch(ifMissing(undefined)) : undefined;
32
33
  return { realPath, format, holder, signature: `${stat.mtimeMs}:${stat.size}:${sidecar?.kind === 'file' ? `${sidecar.mtimeMs}:${sidecar.size}` : ''}` };
33
34
  };
34
- const textOf = async (holder) => ((await claude.fs.stat(holder).catch(() => undefined))?.kind === 'file' ? claude.fs.read(holder) : '');
35
+ const textOf = async (holder) => ((await claude.fs.stat(holder).catch(ifMissing(undefined)))?.kind === 'file' ? claude.fs.read(holder) : '');
36
+ const skipped = (path, value) => (error) => {
37
+ if (isMissing(error))
38
+ return value;
39
+ const reason = `${name}: fs.find skipped ${path}: ${messageOf(error)}`;
40
+ if (!logged.has(reason))
41
+ claude.ui.log(reason, { to: 'debug' });
42
+ logged.add(reason);
43
+ return value;
44
+ };
35
45
  const readEntry = async (path) => {
36
46
  const located = await locate(path);
37
47
  const cached = cache.get(path);
@@ -48,7 +58,7 @@ export function createModFiles({ name, claude, places, checkWrite }) {
48
58
  const failure = `${located.holder} ${error.message}`;
49
59
  read = { metadata: {}, error: failure };
50
60
  if (!logged.has(failure))
51
- claude.ui.log(`${name}: ${failure}`);
61
+ report({ id: `metadata:${located.holder}`, message: `Skips the metadata of ${located.holder}`, description: failure });
52
62
  logged.add(failure);
53
63
  }
54
64
  cache.set(path, { signature: located.signature, read });
@@ -58,21 +68,21 @@ export function createModFiles({ name, claude, places, checkWrite }) {
58
68
  const absolute = resolvedPath(pattern, places);
59
69
  const scanned = picomatch.scan(absolute);
60
70
  if (!scanned.isGlob)
61
- return (await claude.fs.stat(absolute).catch(() => undefined))?.kind === 'file' ? [absolute] : [];
71
+ return (await claude.fs.stat(absolute).catch(skipped(absolute, undefined)))?.kind === 'file' ? [absolute] : [];
62
72
  const isMatch = picomatch(absolute, { dot: true });
63
73
  const entersSkipped = skippedFolders.filter((folder) => scanned.glob.split('/').includes(folder));
64
74
  const maxDepth = scanned.glob.includes('**') ? Number.POSITIVE_INFINITY : scanned.glob.split('/').length;
65
75
  const found = [];
66
76
  const visited = new Set();
67
77
  const walk = async (folder, depth) => {
68
- const real = (await claude.fs.stat(folder, { resolve: true }).catch(() => undefined))?.realPath ?? folder;
78
+ const real = (await claude.fs.stat(folder, { resolve: true }).catch(skipped(folder, undefined)))?.realPath ?? folder;
69
79
  if (visited.has(real))
70
80
  return;
71
81
  visited.add(real);
72
- const entries = await claude.fs.list(folder).catch(() => []);
82
+ const entries = await claude.fs.list(folder).catch(skipped(folder, []));
73
83
  await Promise.all(entries.map(async (entry) => {
74
84
  const path = `${folder}/${entry.name}`;
75
- const kind = entry.isLink === true ? (await claude.fs.stat(path).catch(() => undefined))?.kind : entry.kind;
85
+ const kind = entry.isLink === true ? (await claude.fs.stat(path).catch(skipped(path, undefined)))?.kind : entry.kind;
76
86
  if (kind === 'file' && isMatch(path))
77
87
  found.push(path);
78
88
  if (kind !== 'dir' || depth + 1 >= maxDepth)
@@ -83,7 +93,7 @@ export function createModFiles({ name, claude, places, checkWrite }) {
83
93
  }));
84
94
  };
85
95
  const base = scanned.base === '' ? '/' : scanned.base;
86
- if ((await claude.fs.stat(base).catch(() => undefined))?.kind === 'dir')
96
+ if ((await claude.fs.stat(base).catch(skipped(base, undefined)))?.kind === 'dir')
87
97
  await walk(base, 0);
88
98
  return found;
89
99
  };
@@ -93,7 +103,7 @@ export function createModFiles({ name, claude, places, checkWrite }) {
93
103
  const paths = [...new Set((await Promise.all(patterns.map(matchesOf))).flat())].sort();
94
104
  const kept = paths.filter((path) => !(path.endsWith(sidecarSuffix) && paths.includes(path.slice(0, -sidecarSuffix.length))));
95
105
  return Promise.all(kept.map(async (path) => {
96
- const read = await readEntry(path);
106
+ const read = await readEntry(path).catch((error) => ({ metadata: {}, error: `${path} ${messageOf(error)}` }));
97
107
  return { path, ...(read.name === undefined ? {} : { name: read.name }), metadata: own(read.metadata), ...(read.error === undefined ? {} : { error: read.error }) };
98
108
  }));
99
109
  },
@@ -1,6 +1,7 @@
1
1
  import type { PluginOptions } from 'claude-code';
2
2
  import { type Options } from '../options.js';
3
3
  import type { Claude } from './claude.js';
4
+ import type { Failure } from './toaster.js';
4
5
  export declare class MissingOptions extends Error {
5
6
  readonly keys: readonly string[];
6
7
  readonly titles: readonly string[];
@@ -11,11 +12,12 @@ export type ModOptions = {
11
12
  readonly missing: readonly string[];
12
13
  load(root: string): Promise<void>;
13
14
  };
14
- export declare function createOptions({ name, declared, fromClaude, claude, changed }: {
15
+ export declare function createOptions({ name, declared, fromClaude, claude, report, changed, }: {
15
16
  readonly name: string;
16
17
  readonly declared: Options;
17
18
  readonly fromClaude: PluginOptions;
18
19
  readonly claude: Claude;
20
+ readonly report: (failure: Failure) => void;
19
21
  readonly changed: () => void;
20
22
  }): ModOptions;
21
23
  //# sourceMappingURL=options.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/runtime/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAoC,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AAG9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAElC,YAAY,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAI7D;CACF;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC,CAAA;AAED,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,UAAU,CAmE3O"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/runtime/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAoC,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AAG9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAE3C,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAElC,YAAY,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAI7D;CACF;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC,CAAA;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,MAAM,EACN,MAAM,EACN,OAAO,GACR,EAAE;IACD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,GAAG,UAAU,CAmEb"}
@@ -10,7 +10,7 @@ export class MissingOptions extends Error {
10
10
  this.titles = titles;
11
11
  }
12
12
  }
13
- export function createOptions({ name, declared, fromClaude, claude, changed }) {
13
+ export function createOptions({ name, declared, fromClaude, claude, report, changed, }) {
14
14
  let values = merged(declared, fromClaude, {}, new Set());
15
15
  let locked;
16
16
  let loadedRoot;
@@ -31,7 +31,7 @@ export function createOptions({ name, declared, fromClaude, claude, changed }) {
31
31
  const path = `${project.folder}/options.json`;
32
32
  if (!(await claude.fs.exists(path)))
33
33
  return {};
34
- const ignore = (reason) => claude.ui.log(`${path} ${reason}`);
34
+ const ignore = (reason) => report({ id: `options:${path} ${reason}`, message: `Ignores part of ${path}`, description: `${path} ${reason}` });
35
35
  let file;
36
36
  try {
37
37
  file = JSON.parse(await claude.fs.read(path));
@@ -49,7 +49,7 @@ export function createOptions({ name, declared, fromClaude, claude, changed }) {
49
49
  const option = declared[key];
50
50
  if (option === undefined)
51
51
  ignore(`sets ${key}, which ${name} does not declare. ${Object.keys(declared).length === 0 ? 'Remove it.' : `Remove it, or use one of: ${Object.keys(declared).join(', ')}.`}`);
52
- else if (option.kind === 'secret')
52
+ else if (option.type === 'secret')
53
53
  ignore(`sets ${key}, a secret, and a project file is committed for everyone to read. Remove it, and set it in /config.`);
54
54
  else {
55
55
  const misfit = fitsOption(option, value);
@@ -96,5 +96,5 @@ function valueOf(key, option, own, project, locked) {
96
96
  return project[key];
97
97
  if (!isUnset(own) && fitsOption(option, own) === undefined)
98
98
  return Array.isArray(own) ? Object.freeze([...own]) : own;
99
- return option.default ?? (option.kind === 'list' ? Object.freeze([]) : option.kind === 'number' ? undefined : '');
99
+ return option.default ?? (option.type === 'list' ? Object.freeze([]) : option.type === 'number' ? undefined : '');
100
100
  }
@@ -1,7 +1,15 @@
1
+ import type { HookStream, ProcessSpawnChunk, ProcessSpawnResult } from 'claude-code';
1
2
  import type { Claude } from './claude.js';
2
3
  export type Programs = {
3
4
  readonly claude: Claude;
4
5
  refresh(): Promise<void>;
5
6
  };
7
+ export declare function lastLineOf(text: string): string | undefined;
8
+ export declare function cmodError(stderr: string, command: string | undefined): string;
9
+ export declare function readLines(stream: HookStream<ProcessSpawnChunk, ProcessSpawnResult>, onLine: (text: string) => void): Promise<{
10
+ code: number | null;
11
+ lastError: string;
12
+ }>;
13
+ export declare function runCmod(claude: Claude, args: readonly string[], session: string): Promise<string>;
6
14
  export declare function locatingPrograms(claude: Claude): Programs;
7
15
  //# sourceMappingURL=programs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/runtime/programs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAE5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAsBzD"}
1
+ {"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/runtime/programs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIpF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAE5E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI7E;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAgB9K;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvG;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAsBzD"}
@@ -1,4 +1,42 @@
1
1
  import { storeFolder } from '../records.js';
2
+ import { ifMissing } from '../utils/paths.js';
3
+ import { formatExit } from '../utils/text.js';
4
+ export function lastLineOf(text) {
5
+ const trimmed = text.trim();
6
+ return trimmed === '' ? undefined : trimmed.slice(trimmed.lastIndexOf('\n') + 1).trim();
7
+ }
8
+ export function cmodError(stderr, command) {
9
+ const line = lastLineOf(stderr) ?? '';
10
+ const prefix = `cmod ${command}: `;
11
+ return line.startsWith(prefix) ? line.slice(prefix.length) : line;
12
+ }
13
+ export async function readLines(stream, onLine) {
14
+ let pending = '';
15
+ let lastError = '';
16
+ let piece = await stream.next();
17
+ while (piece.done !== true) {
18
+ if (piece.value.stream === 'stderr') {
19
+ lastError = lastLineOf(piece.value.text) ?? lastError;
20
+ }
21
+ else {
22
+ const lines = `${pending}${piece.value.text}`.split('\n');
23
+ pending = lines.pop() ?? '';
24
+ for (const text of lines)
25
+ onLine(text);
26
+ }
27
+ piece = await stream.next();
28
+ }
29
+ if (pending !== '')
30
+ onLine(pending);
31
+ return { code: piece.value.code, lastError };
32
+ }
33
+ export async function runCmod(claude, args, session) {
34
+ const lines = [];
35
+ const { code, lastError } = await readLines(claude.process.spawn({ argv: ['cmod', ...args], env: { CLAUDE_CODE_SESSION_ID: session } }), (line) => void lines.push(line));
36
+ if (code !== 0)
37
+ throw new Error(cmodError(lastError, args[0]) || `cmod ${args.slice(0, 2).join(' ')} ${formatExit(code, '')}.`);
38
+ return lines.join('\n');
39
+ }
2
40
  export function locatingPrograms(claude) {
3
41
  let folder;
4
42
  let names = new Set();
@@ -17,7 +55,7 @@ export function locatingPrograms(claude) {
17
55
  async refresh() {
18
56
  const [home, dataHome] = await Promise.all([claude.env.home(), claude.env.dataHome()]);
19
57
  const programs = `${storeFolder({ HOME: home, XDG_DATA_HOME: dataHome })}/programs`;
20
- names = new Set((await claude.fs.list(programs).catch(() => [])).map((entry) => entry.name));
58
+ names = new Set((await claude.fs.list(programs).catch(ifMissing([]))).map((entry) => entry.name));
21
59
  folder = programs;
22
60
  },
23
61
  };
@@ -1,9 +1,10 @@
1
1
  import type { Args, EventResult, Frozen } from 'claude-code';
2
+ import type { Disposable } from '../mod.js';
2
3
  import type { RoutedEvent } from './hooks.js';
3
4
  export type RouterNext<N extends RoutedEvent> = (e: Frozen<Args<N>>) => Promise<EventResult<N>>;
4
5
  export type RoutedHook<N extends RoutedEvent> = (e: Frozen<Args<N>>, next: RouterNext<N>) => EventResult<N> | Promise<EventResult<N>>;
5
6
  export type Router = {
6
- add<N extends RoutedEvent>(event: N, hook: RoutedHook<N>): void;
7
+ add<N extends RoutedEvent>(event: N, hook: RoutedHook<N>): Disposable;
7
8
  has(event: RoutedEvent): boolean;
8
9
  dispatch<N extends RoutedEvent>(event: N, e: Frozen<Args<N>>, next: RouterNext<N>): Promise<EventResult<N>>;
9
10
  };
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/runtime/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAE/F,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAErI,MAAM,MAAM,MAAM,GAAG;IACnB,GAAG,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IAC/D,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAA;IAChC,QAAQ,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5G,CAAA;AAID,wBAAgB,YAAY,IAAI,MAAM,CAoBrC"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/runtime/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAE/F,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,WAAW,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;AAErI,MAAM,MAAM,MAAM,GAAG;IACnB,GAAG,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAA;IACrE,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAA;IAChC,QAAQ,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5G,CAAA;AAID,wBAAgB,YAAY,IAAI,MAAM,CA0BrC"}
package/runtime/router.js CHANGED
@@ -2,9 +2,17 @@ export function createRouter() {
2
2
  const hooks = new Map();
3
3
  return {
4
4
  add(event, hook) {
5
- const chain = hooks.get(event) ?? [];
6
- chain.push(hook);
7
- hooks.set(event, chain);
5
+ const erased = hook;
6
+ hooks.set(event, [...(hooks.get(event) ?? []), erased]);
7
+ return {
8
+ dispose() {
9
+ const chain = (hooks.get(event) ?? []).filter((each) => each !== erased);
10
+ if (chain.length === 0)
11
+ hooks.delete(event);
12
+ else
13
+ hooks.set(event, chain);
14
+ },
15
+ };
8
16
  },
9
17
  has: (event) => hooks.has(event),
10
18
  dispatch(event, e, next) {
@@ -0,0 +1,36 @@
1
+ import type { SessionChange, SessionData } from '../session/conversation.js';
2
+ import { type SessionEvent, type Sessions, type Usage } from '../session/session.js';
3
+ import { type SentMatcher } from '../utils/matcher.js';
4
+ import type { Claude } from './claude.js';
5
+ export type Subscription = {
6
+ readonly event: SessionEvent;
7
+ readonly matcher: SentMatcher | undefined;
8
+ };
9
+ export declare const conversationMethods: {
10
+ readonly sessions: 'cmod:sessions';
11
+ readonly search: 'cmod:search';
12
+ readonly messages: 'cmod:messages';
13
+ readonly turns: 'cmod:turns';
14
+ readonly contexts: 'cmod:contexts';
15
+ readonly tasks: 'cmod:tasks';
16
+ readonly goal: 'cmod:goal';
17
+ readonly updateGoal: 'cmod:updateGoal';
18
+ readonly subscribe: 'cmod:subscribe';
19
+ readonly send: 'cmod:send';
20
+ readonly changed: 'cmod:changed';
21
+ };
22
+ export type SessionsClient = {
23
+ readonly sessions: Sessions;
24
+ changed(session: string, change: SessionChange): void;
25
+ /** An empty title keeps the title the session already has. */
26
+ follow(current: Omit<SessionData, 'isRunning' | 'lastAt'>): void;
27
+ };
28
+ export declare function usageOf(claude: Pick<Claude, 'session'>): Promise<Usage>;
29
+ export declare function payloadOf(change: SessionChange): unknown;
30
+ export declare function isHeardBy(event: SessionEvent, matcher: unknown, change: SessionChange): boolean;
31
+ export declare function createSessionsClient({ name, claude, report }: {
32
+ readonly name: string;
33
+ readonly claude: Claude;
34
+ readonly report: (id: string, message: string, error: unknown) => void;
35
+ }): SessionsClient;
36
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/runtime/sessions.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAyB,aAAa,EAAE,WAAW,EAAsB,MAAM,4BAA4B,CAAA;AAGvH,OAAO,EAAW,KAAK,YAAY,EAAuC,KAAK,QAAQ,EAAsB,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAA;AACtJ,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,CAAA;AAEtG,eAAO,MAAM,mBAAmB;aAC9B,QAAQ,EAAE,eAAe;aACzB,MAAM,EAAE,aAAa;aACrB,QAAQ,EAAE,eAAe;aACzB,KAAK,EAAE,YAAY;aACnB,QAAQ,EAAE,eAAe;aACzB,KAAK,EAAE,YAAY;aACnB,IAAI,EAAE,WAAW;aACjB,UAAU,EAAE,iBAAiB;aAC7B,SAAS,EAAE,gBAAgB;aAC3B,IAAI,EAAE,WAAW;aACjB,OAAO,EAAE,cAAc;CACf,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;IACrD,8DAA8D;IAC9D,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAA;CACjE,CAAA;AAID,wBAAsB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAI7E;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAMxD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAG/F;AAED,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG,cAAc,CAuLzM"}
@@ -0,0 +1,235 @@
1
+ import { Message } from '../session/context/turn/message/message.js';
2
+ import { Note } from '../session/context/turn/message/note.js';
3
+ import { Person } from '../session/context/turn/message/sender.js';
4
+ import { Session } from '../session/session.js';
5
+ import { matches, sentMatcher } from '../utils/matcher.js';
6
+ import { cmodPluginName } from './dependencies.js';
7
+ export const conversationMethods = {
8
+ sessions: 'cmod:sessions',
9
+ search: 'cmod:search',
10
+ messages: 'cmod:messages',
11
+ turns: 'cmod:turns',
12
+ contexts: 'cmod:contexts',
13
+ tasks: 'cmod:tasks',
14
+ goal: 'cmod:goal',
15
+ updateGoal: 'cmod:updateGoal',
16
+ subscribe: 'cmod:subscribe',
17
+ send: 'cmod:send',
18
+ changed: 'cmod:changed',
19
+ };
20
+ export async function usageOf(claude) {
21
+ const [model, usage] = await Promise.all([claude.session.model(), claude.session.usage()]);
22
+ const { tokens, window, percent } = usage.context;
23
+ return { model, context: { ...(tokens === undefined ? {} : { tokens }), window, ...(percent === undefined ? {} : { percent }) }, ...(usage.cost === undefined ? {} : { cost: { usd: usage.cost.usd } }) };
24
+ }
25
+ export function payloadOf(change) {
26
+ if (change.type === Message)
27
+ return change.message;
28
+ if ('turn' in change)
29
+ return change.turn;
30
+ if ('context' in change)
31
+ return change.context;
32
+ if ('task' in change)
33
+ return change.task;
34
+ return change.goal;
35
+ }
36
+ export function isHeardBy(event, matcher, change) {
37
+ const named = change.type === Message ? (event === Message || event === change.message.type) : event === change.type;
38
+ return named && (matcher === undefined || matches(payloadOf(change), matcher));
39
+ }
40
+ export function createSessionsClient({ name, claude, report }) {
41
+ const call = (method, input) => claude.cmod.call({ to: cmodPluginName, method, input });
42
+ const listening = new Map();
43
+ const queued = new Set();
44
+ let current = { id: '', title: '', project: '', cwd: '', model: '', startedAt: '', lastAt: '', isRunning: true };
45
+ const subscribe = (session) => {
46
+ if (queued.has(session))
47
+ return;
48
+ queued.add(session);
49
+ void Promise.resolve().then(async () => {
50
+ queued.delete(session);
51
+ const subscriptions = [...(listening.get(session) ?? [])].map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : sentMatcher(matcher) }));
52
+ await call(conversationMethods.subscribe, { session, subscriptions }).catch((error) => report(`sessions:${session}`, `Hears nothing from session ${session}`, error));
53
+ });
54
+ };
55
+ const isCurrent = (id) => id === current.id;
56
+ const refuseElsewhere = (id, what) => {
57
+ throw new Error(`${name}: ${what} works on the session this mod runs in, and ${id} runs elsewhere or has ended.`);
58
+ };
59
+ const turnOf = (session, data) => ({ ...data, messages: () => call(conversationMethods.messages, { session, turn: data.prompt.id }) });
60
+ const contextOf = (session, data) => ({
61
+ ...data,
62
+ turns: async () => (await call(conversationMethods.turns, { session, context: data.number })).map((turn) => turnOf(session, turn)),
63
+ });
64
+ const taskOf = (session, data) => ({
65
+ ...data,
66
+ messages: () => call(conversationMethods.messages, { session, task: data.id }),
67
+ tasks: async () => (await call(conversationMethods.tasks, { session, task: data.id })).map((task) => taskOf(session, task)),
68
+ });
69
+ const goalOf = (session, data) => ({
70
+ ...data,
71
+ update: async ({ text }) => {
72
+ if (text.trim() === '')
73
+ throw new Error(`${name}: a goal needs text.`);
74
+ await call(conversationMethods.updateGoal, { session, text });
75
+ },
76
+ tasks: async () => (await call(conversationMethods.tasks, { session })).map((task) => taskOf(session, task)),
77
+ });
78
+ const payloadFor = (session, change) => {
79
+ if (change.type === Message)
80
+ return change.message;
81
+ if ('turn' in change)
82
+ return turnOf(session, change.turn);
83
+ if ('context' in change)
84
+ return contextOf(session, change.context);
85
+ if ('task' in change)
86
+ return taskOf(session, change.task);
87
+ return goalOf(session, change.goal);
88
+ };
89
+ const sessionOf = (read) => {
90
+ const id = () => read().id;
91
+ const session = {
92
+ get id() {
93
+ return read().id;
94
+ },
95
+ get title() {
96
+ return read().title;
97
+ },
98
+ get project() {
99
+ return read().project;
100
+ },
101
+ get cwd() {
102
+ return read().cwd;
103
+ },
104
+ get model() {
105
+ return read().model;
106
+ },
107
+ get startedAt() {
108
+ return read().startedAt;
109
+ },
110
+ get lastAt() {
111
+ return read().lastAt;
112
+ },
113
+ get forkedFrom() {
114
+ return read().forkedFrom;
115
+ },
116
+ get isRunning() {
117
+ return read().isRunning;
118
+ },
119
+ async usage() {
120
+ if (!isCurrent(id()))
121
+ refuseElsewhere(id(), 'usage()');
122
+ return usageOf(claude);
123
+ },
124
+ on(event, matcherOrListener, maybeListener) {
125
+ const listener = (maybeListener ?? matcherOrListener);
126
+ const matcher = maybeListener === undefined ? undefined : matcherOrListener;
127
+ const owner = id();
128
+ const heard = { event, matcher, listener };
129
+ const set = listening.get(owner) ?? new Set();
130
+ set.add(heard);
131
+ listening.set(owner, set);
132
+ subscribe(owner);
133
+ return {
134
+ dispose() {
135
+ if (!set.delete(heard))
136
+ return;
137
+ if (set.size === 0)
138
+ listening.delete(owner);
139
+ subscribe(owner);
140
+ },
141
+ };
142
+ },
143
+ async send({ type, text, as }) {
144
+ if (text.trim() === '')
145
+ throw new Error(`${name}: send needs text.`);
146
+ if (isCurrent(id())) {
147
+ if (as === Session)
148
+ throw new Error(`${name}: send with as: Session messages another session, and this one runs this mod.`);
149
+ if (type === Note) {
150
+ if (as !== undefined)
151
+ throw new Error(`${name}: a Note is always from this mod, so it takes no as.`);
152
+ const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
153
+ if (added.deny !== undefined)
154
+ throw new Error(`${name}: Claude Code refused the note: ${added.deny}`);
155
+ return;
156
+ }
157
+ const submitted = await claude.prompt.submit({ text, ...(as === Person ? { asUser: true } : {}) });
158
+ if ('drop' in submitted && typeof submitted.drop === 'string')
159
+ throw new Error(`${name}: Claude Code dropped the prompt: ${submitted.drop}`);
160
+ return;
161
+ }
162
+ if (!read().isRunning)
163
+ throw new Error(`${name}: session ${id()} has ended, so it takes no messages.`);
164
+ if (as === Session) {
165
+ if (type === Note)
166
+ throw new Error(`${name}: a message from this session reaches Claude as a Prompt, so send it with type: Prompt.`);
167
+ const sent = await claude.session.send({ to: { sessionId: id() }, text });
168
+ if (sent.isDelivered !== true)
169
+ throw new Error(`${name}: Claude Code could not deliver the message to session ${id()}.`);
170
+ return;
171
+ }
172
+ await call(conversationMethods.send, { session: id(), type, text, ...(as === undefined ? {} : { as }) });
173
+ },
174
+ async startTask(task) {
175
+ if (!isCurrent(id()))
176
+ refuseElsewhere(id(), 'startTask()');
177
+ const started = await claude.agent.spawn({
178
+ prompt: task.prompt,
179
+ ...(task.name === undefined ? {} : { description: task.name, name: task.name }),
180
+ ...(task.agentType === undefined ? {} : { subagentType: task.agentType }),
181
+ ...(task.model === undefined ? {} : { model: task.model }),
182
+ ...(task.cwd === undefined ? {} : { cwd: task.cwd }),
183
+ });
184
+ if (started.deny !== undefined)
185
+ throw new Error(`${name}: Claude Code refused the task: ${started.deny}`);
186
+ const data = { id: started.agentId ?? '', title: task.name ?? '', agentType: task.agentType ?? 'general-purpose', model: started.model, openedBy: '', openedAt: new Date().toISOString() };
187
+ return taskOf(id(), data);
188
+ },
189
+ messages: (options = {}) => call(conversationMethods.messages, { session: id(), ...options }),
190
+ turns: async (options = {}) => (await call(conversationMethods.turns, { session: id(), ...options })).map((turn) => turnOf(id(), turn)),
191
+ contexts: async () => (await call(conversationMethods.contexts, { session: id() })).map((context) => contextOf(id(), context)),
192
+ goal: async () => goalOf(id(), await call(conversationMethods.goal, { session: id() })),
193
+ };
194
+ return session;
195
+ };
196
+ const currentSession = sessionOf(() => current);
197
+ return {
198
+ sessions: {
199
+ get current() {
200
+ return currentSession;
201
+ },
202
+ list: async (scope = {}) => (await call(conversationMethods.sessions, scope)).map((data) => {
203
+ if (data.id !== current.id)
204
+ return sessionOf(() => data);
205
+ if (current.title === '')
206
+ current = { ...current, title: data.title };
207
+ return currentSession;
208
+ }),
209
+ search: (text, options = {}) => call(conversationMethods.search, { text, ...options, ...(options.before === undefined ? {} : { before: { session: options.before.session, id: options.before.id } }) }),
210
+ },
211
+ changed(session, change) {
212
+ for (const { event, matcher, listener } of listening.get(session) ?? []) {
213
+ if (!isHeardBy(event, matcher, change))
214
+ continue;
215
+ void Promise.resolve()
216
+ .then(() => listener(payloadFor(session, change)))
217
+ .catch((error) => report(`session:${event}`, `The ${event} listener failed`, error));
218
+ }
219
+ },
220
+ follow(next) {
221
+ const moved = next.id !== current.id && current.id !== '';
222
+ const previous = current.id;
223
+ current = { ...next, title: next.title || (next.id === current.id ? current.title : ''), isRunning: true, lastAt: new Date().toISOString() };
224
+ if (!moved)
225
+ return;
226
+ const kept = listening.get(previous);
227
+ if (kept === undefined)
228
+ return;
229
+ listening.delete(previous);
230
+ listening.set(current.id, kept);
231
+ subscribe(previous);
232
+ subscribe(current.id);
233
+ },
234
+ };
235
+ }
@@ -1,12 +1,15 @@
1
1
  import type { ClassicHookInputs } from 'claude-code';
2
2
  import type { Claude } from './claude.js';
3
+ import type { Failure } from './toaster.js';
3
4
  export type StateOptions = {
4
5
  readonly name: string;
5
6
  readonly initial: object;
6
7
  readonly session: string;
7
8
  readonly root: string;
8
9
  readonly claude: Claude;
10
+ readonly report: (failure: Failure) => void;
9
11
  readonly changed: () => void;
12
+ readonly forkParent: () => Promise<string | undefined>;
10
13
  };
11
14
  export type ModState<State extends object> = {
12
15
  readonly state: State;
@@ -14,8 +17,9 @@ export type ModState<State extends object> = {
14
17
  changed(): void;
15
18
  load(): Promise<void>;
16
19
  moveTo(root: string): Promise<void>;
17
- switchSession(session: string, source: ClassicHookInputs['SessionStart']['source']): Promise<void>;
20
+ switchSession(session: string, source: ClassicHookInputs['SessionStart']['source'], transcript: string): Promise<void>;
18
21
  };
19
22
  export declare function onStateChange(state: object, listener: () => void): void;
20
- export declare function createState<State extends object>({ name, initial, session, root, claude, changed: redraw }: StateOptions): ModState<State>;
23
+ export declare function sessionsWith(claude: Pick<Claude, 'store'>, name: string, key: string, value: unknown): Promise<string[]>;
24
+ export declare function createState<State extends object>({ name, initial, session, root, claude, report, changed: redraw, forkParent }: StateOptions): ModState<State>;
21
25
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/runtime/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAQzC,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,MAAM,IAAI;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,OAAO,IAAI,IAAI,CAAA;IACf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACnG,CAAA;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAIvE;AAED,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,CAoF1I"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/runtime/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAQ3C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,QAAQ,CAAC,KAAK,SAAS,MAAM,IAAI;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,OAAO,IAAI,IAAI,CAAA;IACf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvH,CAAA;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAIvE;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAO9H;AAED,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,CA6G9J"}