@pcircle/footprint 1.3.0 → 1.6.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 (349) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +215 -137
  3. package/SKILL.md +77 -33
  4. package/bin/footprint.js +16 -0
  5. package/dist/src/adapters/claude.d.ts +2 -0
  6. package/dist/src/adapters/claude.d.ts.map +1 -0
  7. package/dist/src/adapters/claude.js +7 -0
  8. package/dist/src/adapters/claude.js.map +1 -0
  9. package/dist/src/adapters/codex.d.ts +2 -0
  10. package/dist/src/adapters/codex.d.ts.map +1 -0
  11. package/dist/src/adapters/codex.js +7 -0
  12. package/dist/src/adapters/codex.js.map +1 -0
  13. package/dist/src/adapters/gemini.d.ts +2 -0
  14. package/dist/src/adapters/gemini.d.ts.map +1 -0
  15. package/dist/src/adapters/gemini.js +7 -0
  16. package/dist/src/adapters/gemini.js.map +1 -0
  17. package/dist/src/adapters/index.d.ts +5 -0
  18. package/dist/src/adapters/index.d.ts.map +1 -0
  19. package/dist/src/adapters/index.js +12 -0
  20. package/dist/src/adapters/index.js.map +1 -0
  21. package/dist/src/adapters/structured-prefix.d.ts +10 -0
  22. package/dist/src/adapters/structured-prefix.d.ts.map +1 -0
  23. package/dist/src/adapters/structured-prefix.js +59 -0
  24. package/dist/src/adapters/structured-prefix.js.map +1 -0
  25. package/dist/src/adapters/types.d.ts +32 -0
  26. package/dist/src/adapters/types.d.ts.map +1 -0
  27. package/dist/src/adapters/types.js +2 -0
  28. package/dist/src/adapters/types.js.map +1 -0
  29. package/dist/src/analyzers/content-analyzer.d.ts.map +1 -1
  30. package/dist/src/analyzers/content-analyzer.js +20 -4
  31. package/dist/src/analyzers/content-analyzer.js.map +1 -1
  32. package/dist/src/cli/context-flow.d.ts +92 -0
  33. package/dist/src/cli/context-flow.d.ts.map +1 -0
  34. package/dist/src/cli/context-flow.js +724 -0
  35. package/dist/src/cli/context-flow.js.map +1 -0
  36. package/dist/src/cli/history-display.d.ts +27 -0
  37. package/dist/src/cli/history-display.d.ts.map +1 -0
  38. package/dist/src/cli/history-display.js +167 -0
  39. package/dist/src/cli/history-display.js.map +1 -0
  40. package/dist/src/cli/index.js +924 -0
  41. package/dist/src/cli/index.js.map +1 -1
  42. package/dist/src/cli/launch-spec.d.ts +31 -0
  43. package/dist/src/cli/launch-spec.d.ts.map +1 -0
  44. package/dist/src/cli/launch-spec.js +182 -0
  45. package/dist/src/cli/launch-spec.js.map +1 -0
  46. package/dist/src/cli/live-demo.d.ts +34 -0
  47. package/dist/src/cli/live-demo.d.ts.map +1 -0
  48. package/dist/src/cli/live-demo.js +254 -0
  49. package/dist/src/cli/live-demo.js.map +1 -0
  50. package/dist/src/cli/pty-transcript.d.ts +34 -0
  51. package/dist/src/cli/pty-transcript.d.ts.map +1 -0
  52. package/dist/src/cli/pty-transcript.js +174 -0
  53. package/dist/src/cli/pty-transcript.js.map +1 -0
  54. package/dist/src/cli/session-display.d.ts +74 -0
  55. package/dist/src/cli/session-display.d.ts.map +1 -0
  56. package/dist/src/cli/session-display.js +922 -0
  57. package/dist/src/cli/session-display.js.map +1 -0
  58. package/dist/src/cli/session-execution.d.ts +55 -0
  59. package/dist/src/cli/session-execution.d.ts.map +1 -0
  60. package/dist/src/cli/session-execution.js +817 -0
  61. package/dist/src/cli/session-execution.js.map +1 -0
  62. package/dist/src/cli/session-runtime.d.ts +5 -0
  63. package/dist/src/cli/session-runtime.d.ts.map +1 -0
  64. package/dist/src/cli/session-runtime.js +11 -0
  65. package/dist/src/cli/session-runtime.js.map +1 -0
  66. package/dist/src/cli/setup.d.ts.map +1 -1
  67. package/dist/src/cli/setup.js +36 -12
  68. package/dist/src/cli/setup.js.map +1 -1
  69. package/dist/src/cli/utils/env.d.ts +7 -2
  70. package/dist/src/cli/utils/env.d.ts.map +1 -1
  71. package/dist/src/cli/utils/env.js +37 -6
  72. package/dist/src/cli/utils/env.js.map +1 -1
  73. package/dist/src/index.d.ts +4 -1
  74. package/dist/src/index.d.ts.map +1 -1
  75. package/dist/src/index.js +187 -33
  76. package/dist/src/index.js.map +1 -1
  77. package/dist/src/ingestion/deterministic.d.ts +3 -0
  78. package/dist/src/ingestion/deterministic.d.ts.map +1 -0
  79. package/dist/src/ingestion/deterministic.js +862 -0
  80. package/dist/src/ingestion/deterministic.js.map +1 -0
  81. package/dist/src/ingestion/index.d.ts +5 -0
  82. package/dist/src/ingestion/index.d.ts.map +1 -0
  83. package/dist/src/ingestion/index.js +27 -0
  84. package/dist/src/ingestion/index.js.map +1 -0
  85. package/dist/src/ingestion/semantic.d.ts +6 -0
  86. package/dist/src/ingestion/semantic.d.ts.map +1 -0
  87. package/dist/src/ingestion/semantic.js +627 -0
  88. package/dist/src/ingestion/semantic.js.map +1 -0
  89. package/dist/src/ingestion/types.d.ts +10 -0
  90. package/dist/src/ingestion/types.d.ts.map +1 -0
  91. package/dist/src/ingestion/types.js +2 -0
  92. package/dist/src/ingestion/types.js.map +1 -0
  93. package/dist/src/lib/context-memory.d.ts +140 -0
  94. package/dist/src/lib/context-memory.d.ts.map +1 -0
  95. package/dist/src/lib/context-memory.js +974 -0
  96. package/dist/src/lib/context-memory.js.map +1 -0
  97. package/dist/src/lib/crypto/decrypt.d.ts.map +1 -1
  98. package/dist/src/lib/crypto/decrypt.js +12 -8
  99. package/dist/src/lib/crypto/decrypt.js.map +1 -1
  100. package/dist/src/lib/crypto/encrypt.d.ts.map +1 -1
  101. package/dist/src/lib/crypto/encrypt.js +6 -3
  102. package/dist/src/lib/crypto/encrypt.js.map +1 -1
  103. package/dist/src/lib/crypto/key-derivation.d.ts +1 -1
  104. package/dist/src/lib/crypto/key-derivation.d.ts.map +1 -1
  105. package/dist/src/lib/crypto/key-derivation.js +11 -11
  106. package/dist/src/lib/crypto/key-derivation.js.map +1 -1
  107. package/dist/src/lib/history-handoff.d.ts +43 -0
  108. package/dist/src/lib/history-handoff.d.ts.map +1 -0
  109. package/dist/src/lib/history-handoff.js +179 -0
  110. package/dist/src/lib/history-handoff.js.map +1 -0
  111. package/dist/src/lib/observability.d.ts +3 -0
  112. package/dist/src/lib/observability.d.ts.map +1 -0
  113. package/dist/src/lib/observability.js +63 -0
  114. package/dist/src/lib/observability.js.map +1 -0
  115. package/dist/src/lib/session-artifacts.d.ts +51 -0
  116. package/dist/src/lib/session-artifacts.d.ts.map +1 -0
  117. package/dist/src/lib/session-artifacts.js +132 -0
  118. package/dist/src/lib/session-artifacts.js.map +1 -0
  119. package/dist/src/lib/session-filters.d.ts +11 -0
  120. package/dist/src/lib/session-filters.d.ts.map +1 -0
  121. package/dist/src/lib/session-filters.js +16 -0
  122. package/dist/src/lib/session-filters.js.map +1 -0
  123. package/dist/src/lib/session-history.d.ts +50 -0
  124. package/dist/src/lib/session-history.d.ts.map +1 -0
  125. package/dist/src/lib/session-history.js +73 -0
  126. package/dist/src/lib/session-history.js.map +1 -0
  127. package/dist/src/lib/session-trends.d.ts +129 -0
  128. package/dist/src/lib/session-trends.d.ts.map +1 -0
  129. package/dist/src/lib/session-trends.js +361 -0
  130. package/dist/src/lib/session-trends.js.map +1 -0
  131. package/dist/src/lib/storage/database.d.ts +257 -3
  132. package/dist/src/lib/storage/database.d.ts.map +1 -1
  133. package/dist/src/lib/storage/database.js +1836 -161
  134. package/dist/src/lib/storage/database.js.map +1 -1
  135. package/dist/src/lib/storage/export-sessions.d.ts +33 -0
  136. package/dist/src/lib/storage/export-sessions.d.ts.map +1 -0
  137. package/dist/src/lib/storage/export-sessions.js +525 -0
  138. package/dist/src/lib/storage/export-sessions.js.map +1 -0
  139. package/dist/src/lib/storage/export.d.ts +1 -2
  140. package/dist/src/lib/storage/export.d.ts.map +1 -1
  141. package/dist/src/lib/storage/export.js +46 -33
  142. package/dist/src/lib/storage/export.js.map +1 -1
  143. package/dist/src/lib/storage/index.d.ts +7 -6
  144. package/dist/src/lib/storage/index.d.ts.map +1 -1
  145. package/dist/src/lib/storage/index.js +6 -5
  146. package/dist/src/lib/storage/index.js.map +1 -1
  147. package/dist/src/lib/storage/salt-storage.d.ts +1 -1
  148. package/dist/src/lib/storage/salt-storage.d.ts.map +1 -1
  149. package/dist/src/lib/storage/salt-storage.js +26 -18
  150. package/dist/src/lib/storage/salt-storage.js.map +1 -1
  151. package/dist/src/lib/storage/schema.d.ts +7 -2
  152. package/dist/src/lib/storage/schema.d.ts.map +1 -1
  153. package/dist/src/lib/storage/schema.js +357 -40
  154. package/dist/src/lib/storage/schema.js.map +1 -1
  155. package/dist/src/lib/storage/types.d.ts +122 -0
  156. package/dist/src/lib/storage/types.d.ts.map +1 -1
  157. package/dist/src/lib/tool-wrapper.d.ts.map +1 -1
  158. package/dist/src/lib/tool-wrapper.js +2 -2
  159. package/dist/src/lib/tool-wrapper.js.map +1 -1
  160. package/dist/src/prompts/skill-prompt.d.ts +6 -0
  161. package/dist/src/prompts/skill-prompt.d.ts.map +1 -0
  162. package/dist/src/prompts/skill-prompt.js +138 -0
  163. package/dist/src/prompts/skill-prompt.js.map +1 -0
  164. package/dist/src/tools/capture-footprint.d.ts +2 -2
  165. package/dist/src/tools/capture-footprint.d.ts.map +1 -1
  166. package/dist/src/tools/capture-footprint.js +52 -11
  167. package/dist/src/tools/capture-footprint.js.map +1 -1
  168. package/dist/src/tools/confirm-context-link.d.ts +62 -0
  169. package/dist/src/tools/confirm-context-link.d.ts.map +1 -0
  170. package/dist/src/tools/confirm-context-link.js +36 -0
  171. package/dist/src/tools/confirm-context-link.js.map +1 -0
  172. package/dist/src/tools/context-schemas.d.ts +694 -0
  173. package/dist/src/tools/context-schemas.d.ts.map +1 -0
  174. package/dist/src/tools/context-schemas.js +171 -0
  175. package/dist/src/tools/context-schemas.js.map +1 -0
  176. package/dist/src/tools/delete-footprints.d.ts +18 -1
  177. package/dist/src/tools/delete-footprints.d.ts.map +1 -1
  178. package/dist/src/tools/delete-footprints.js +53 -5
  179. package/dist/src/tools/delete-footprints.js.map +1 -1
  180. package/dist/src/tools/export-footprints.d.ts +11 -3
  181. package/dist/src/tools/export-footprints.d.ts.map +1 -1
  182. package/dist/src/tools/export-footprints.js +48 -9
  183. package/dist/src/tools/export-footprints.js.map +1 -1
  184. package/dist/src/tools/export-sessions.d.ts +111 -0
  185. package/dist/src/tools/export-sessions.d.ts.map +1 -0
  186. package/dist/src/tools/export-sessions.js +136 -0
  187. package/dist/src/tools/export-sessions.js.map +1 -0
  188. package/dist/src/tools/get-context.d.ts +208 -0
  189. package/dist/src/tools/get-context.d.ts.map +1 -0
  190. package/dist/src/tools/get-context.js +27 -0
  191. package/dist/src/tools/get-context.js.map +1 -0
  192. package/dist/src/tools/get-footprint.d.ts +1 -7
  193. package/dist/src/tools/get-footprint.d.ts.map +1 -1
  194. package/dist/src/tools/get-footprint.js +7 -3
  195. package/dist/src/tools/get-footprint.js.map +1 -1
  196. package/dist/src/tools/get-history-handoff.d.ts +109 -0
  197. package/dist/src/tools/get-history-handoff.d.ts.map +1 -0
  198. package/dist/src/tools/get-history-handoff.js +85 -0
  199. package/dist/src/tools/get-history-handoff.js.map +1 -0
  200. package/dist/src/tools/get-history-trends.d.ts +155 -0
  201. package/dist/src/tools/get-history-trends.d.ts.map +1 -0
  202. package/dist/src/tools/get-history-trends.js +123 -0
  203. package/dist/src/tools/get-history-trends.js.map +1 -0
  204. package/dist/src/tools/get-session-artifacts.d.ts +151 -0
  205. package/dist/src/tools/get-session-artifacts.d.ts.map +1 -0
  206. package/dist/src/tools/get-session-artifacts.js +184 -0
  207. package/dist/src/tools/get-session-artifacts.js.map +1 -0
  208. package/dist/src/tools/get-session-decisions.d.ts +69 -0
  209. package/dist/src/tools/get-session-decisions.d.ts.map +1 -0
  210. package/dist/src/tools/get-session-decisions.js +99 -0
  211. package/dist/src/tools/get-session-decisions.js.map +1 -0
  212. package/dist/src/tools/get-session-messages.d.ts +55 -0
  213. package/dist/src/tools/get-session-messages.d.ts.map +1 -0
  214. package/dist/src/tools/get-session-messages.js +89 -0
  215. package/dist/src/tools/get-session-messages.js.map +1 -0
  216. package/dist/src/tools/get-session-narrative.d.ts +72 -0
  217. package/dist/src/tools/get-session-narrative.d.ts.map +1 -0
  218. package/dist/src/tools/get-session-narrative.js +106 -0
  219. package/dist/src/tools/get-session-narrative.js.map +1 -0
  220. package/dist/src/tools/get-session-timeline.d.ts +55 -0
  221. package/dist/src/tools/get-session-timeline.d.ts.map +1 -0
  222. package/dist/src/tools/get-session-timeline.js +93 -0
  223. package/dist/src/tools/get-session-timeline.js.map +1 -0
  224. package/dist/src/tools/get-session-trends.d.ts +108 -0
  225. package/dist/src/tools/get-session-trends.d.ts.map +1 -0
  226. package/dist/src/tools/get-session-trends.js +130 -0
  227. package/dist/src/tools/get-session-trends.js.map +1 -0
  228. package/dist/src/tools/get-session.d.ts +251 -0
  229. package/dist/src/tools/get-session.d.ts.map +1 -0
  230. package/dist/src/tools/get-session.js +290 -0
  231. package/dist/src/tools/get-session.js.map +1 -0
  232. package/dist/src/tools/index.d.ts +23 -3
  233. package/dist/src/tools/index.d.ts.map +1 -1
  234. package/dist/src/tools/index.js +23 -3
  235. package/dist/src/tools/index.js.map +1 -1
  236. package/dist/src/tools/list-contexts.d.ts +50 -0
  237. package/dist/src/tools/list-contexts.d.ts.map +1 -0
  238. package/dist/src/tools/list-contexts.js +28 -0
  239. package/dist/src/tools/list-contexts.js.map +1 -0
  240. package/dist/src/tools/list-footprints.d.ts +1 -15
  241. package/dist/src/tools/list-footprints.d.ts.map +1 -1
  242. package/dist/src/tools/list-footprints.js +17 -6
  243. package/dist/src/tools/list-footprints.js.map +1 -1
  244. package/dist/src/tools/list-sessions.d.ts +86 -0
  245. package/dist/src/tools/list-sessions.d.ts.map +1 -0
  246. package/dist/src/tools/list-sessions.js +97 -0
  247. package/dist/src/tools/list-sessions.js.map +1 -0
  248. package/dist/src/tools/manage-tags.d.ts +47 -0
  249. package/dist/src/tools/manage-tags.d.ts.map +1 -0
  250. package/dist/src/tools/manage-tags.js +109 -0
  251. package/dist/src/tools/manage-tags.js.map +1 -0
  252. package/dist/src/tools/merge-contexts.d.ts +58 -0
  253. package/dist/src/tools/merge-contexts.d.ts.map +1 -0
  254. package/dist/src/tools/merge-contexts.js +27 -0
  255. package/dist/src/tools/merge-contexts.js.map +1 -0
  256. package/dist/src/tools/move-session-context.d.ts +62 -0
  257. package/dist/src/tools/move-session-context.d.ts.map +1 -0
  258. package/dist/src/tools/move-session-context.js +33 -0
  259. package/dist/src/tools/move-session-context.js.map +1 -0
  260. package/dist/src/tools/reingest-session.d.ts +31 -0
  261. package/dist/src/tools/reingest-session.d.ts.map +1 -0
  262. package/dist/src/tools/reingest-session.js +43 -0
  263. package/dist/src/tools/reingest-session.js.map +1 -0
  264. package/dist/src/tools/reject-context-link.d.ts +58 -0
  265. package/dist/src/tools/reject-context-link.d.ts.map +1 -0
  266. package/dist/src/tools/reject-context-link.js +26 -0
  267. package/dist/src/tools/reject-context-link.js.map +1 -0
  268. package/dist/src/tools/resolve-context.d.ts +287 -0
  269. package/dist/src/tools/resolve-context.d.ts.map +1 -0
  270. package/dist/src/tools/resolve-context.js +35 -0
  271. package/dist/src/tools/resolve-context.js.map +1 -0
  272. package/dist/src/tools/search-footprints.d.ts +2 -16
  273. package/dist/src/tools/search-footprints.d.ts.map +1 -1
  274. package/dist/src/tools/search-footprints.js +23 -7
  275. package/dist/src/tools/search-footprints.js.map +1 -1
  276. package/dist/src/tools/search-history.d.ts +86 -0
  277. package/dist/src/tools/search-history.d.ts.map +1 -0
  278. package/dist/src/tools/search-history.js +103 -0
  279. package/dist/src/tools/search-history.js.map +1 -0
  280. package/dist/src/tools/session-ui-metadata.d.ts +15 -0
  281. package/dist/src/tools/session-ui-metadata.d.ts.map +1 -0
  282. package/dist/src/tools/session-ui-metadata.js +15 -0
  283. package/dist/src/tools/session-ui-metadata.js.map +1 -0
  284. package/dist/src/tools/set-active-context.d.ts +58 -0
  285. package/dist/src/tools/set-active-context.d.ts.map +1 -0
  286. package/dist/src/tools/set-active-context.js +26 -0
  287. package/dist/src/tools/set-active-context.js.map +1 -0
  288. package/dist/src/tools/split-context.d.ts +62 -0
  289. package/dist/src/tools/split-context.d.ts.map +1 -0
  290. package/dist/src/tools/split-context.js +36 -0
  291. package/dist/src/tools/split-context.js.map +1 -0
  292. package/dist/src/tools/suggest-capture.d.ts +1 -1
  293. package/dist/src/tools/suggest-capture.d.ts.map +1 -1
  294. package/dist/src/tools/suggest-capture.js +6 -2
  295. package/dist/src/tools/suggest-capture.js.map +1 -1
  296. package/dist/src/tools/verify-footprint.d.ts +7 -54
  297. package/dist/src/tools/verify-footprint.d.ts.map +1 -1
  298. package/dist/src/tools/verify-footprint.js +11 -8
  299. package/dist/src/tools/verify-footprint.js.map +1 -1
  300. package/dist/src/types.d.ts +6 -4
  301. package/dist/src/types.d.ts.map +1 -1
  302. package/dist/src/ui/register.d.ts +6 -1
  303. package/dist/src/ui/register.d.ts.map +1 -1
  304. package/dist/src/ui/register.js +60 -16
  305. package/dist/src/ui/register.js.map +1 -1
  306. package/dist/ui/dashboard.html +259 -875
  307. package/dist/ui/detail.html +124 -252
  308. package/dist/ui/export.html +133 -303
  309. package/dist/ui/session-dashboard-live.html +264 -0
  310. package/dist/ui/session-dashboard.html +329 -0
  311. package/dist/ui/session-detail-live.html +336 -0
  312. package/dist/ui/session-detail.html +355 -0
  313. package/package.json +61 -16
  314. package/dist/src/lib/errors/base-error.d.ts +0 -15
  315. package/dist/src/lib/errors/base-error.d.ts.map +0 -1
  316. package/dist/src/lib/errors/base-error.js +0 -34
  317. package/dist/src/lib/errors/base-error.js.map +0 -1
  318. package/dist/src/lib/errors/crypto-error.d.ts +0 -29
  319. package/dist/src/lib/errors/crypto-error.d.ts.map +0 -1
  320. package/dist/src/lib/errors/crypto-error.js +0 -43
  321. package/dist/src/lib/errors/crypto-error.js.map +0 -1
  322. package/dist/src/lib/errors/index.d.ts +0 -26
  323. package/dist/src/lib/errors/index.d.ts.map +0 -1
  324. package/dist/src/lib/errors/index.js +0 -26
  325. package/dist/src/lib/errors/index.js.map +0 -1
  326. package/dist/src/lib/errors/storage-error.d.ts +0 -25
  327. package/dist/src/lib/errors/storage-error.d.ts.map +0 -1
  328. package/dist/src/lib/errors/storage-error.js +0 -38
  329. package/dist/src/lib/errors/storage-error.js.map +0 -1
  330. package/dist/src/lib/errors/validation-error.d.ts +0 -21
  331. package/dist/src/lib/errors/validation-error.d.ts.map +0 -1
  332. package/dist/src/lib/errors/validation-error.js +0 -29
  333. package/dist/src/lib/errors/validation-error.js.map +0 -1
  334. package/dist/src/test-helpers.d.ts +0 -33
  335. package/dist/src/test-helpers.d.ts.map +0 -1
  336. package/dist/src/test-helpers.js +0 -108
  337. package/dist/src/test-helpers.js.map +0 -1
  338. package/dist/src/tools/get-tag-stats.d.ts +0 -30
  339. package/dist/src/tools/get-tag-stats.d.ts.map +0 -1
  340. package/dist/src/tools/get-tag-stats.js +0 -33
  341. package/dist/src/tools/get-tag-stats.js.map +0 -1
  342. package/dist/src/tools/remove-tag.d.ts +0 -22
  343. package/dist/src/tools/remove-tag.d.ts.map +0 -1
  344. package/dist/src/tools/remove-tag.js +0 -30
  345. package/dist/src/tools/remove-tag.js.map +0 -1
  346. package/dist/src/tools/rename-tag.d.ts +0 -24
  347. package/dist/src/tools/rename-tag.d.ts.map +0 -1
  348. package/dist/src/tools/rename-tag.js +0 -34
  349. package/dist/src/tools/rename-tag.js.map +0 -1
@@ -1,14 +1,938 @@
1
1
  #!/usr/bin/env node
2
2
  /* global console, process */
3
3
  import { runSetup } from "./setup.js";
4
+ import { runLiveDemoCli } from "./live-demo.js";
5
+ import { confirmContextLinkCli, exportSessionsCli, ingestSessionCli, listSessionsCli, listContextsCli, mergeContextsCli, moveSessionContextCli, prepareContextCli, rejectContextLinkCli, resolveContextCli, runClaudeSession, runCodexSession, runGeminiSession, searchHistoryCli, setActiveContextCli, showContextCli, showHistoryHandoffCli, showSessionArtifactsCli, showSessionDecisionsCli, showHistoryTrendsCli, showSessionMessagesCli, showSessionTrendsCli, showSessionNarrativesCli, showSessionCli, showSessionTimelineCli, splitContextCli, } from "./session-runtime.js";
4
6
  const args = process.argv.slice(2);
5
7
  const command = args[0];
8
+ const sessionHosts = new Set(["claude", "gemini", "codex"]);
9
+ const sessionStatuses = new Set([
10
+ "running",
11
+ "completed",
12
+ "failed",
13
+ "interrupted",
14
+ ]);
15
+ const artifactTypes = new Set([
16
+ "file-change",
17
+ "command-output",
18
+ "test-result",
19
+ "git-commit",
20
+ ]);
21
+ const narrativeKinds = new Set([
22
+ "journal",
23
+ "project-summary",
24
+ "handoff",
25
+ ]);
26
+ const exportOutputModes = new Set([
27
+ "file",
28
+ "base64",
29
+ "both",
30
+ ]);
31
+ const historyTrendGroups = new Set(["issue", "family"]);
32
+ function consumeFlag(values, flag) {
33
+ const rest = [];
34
+ let present = false;
35
+ for (const value of values) {
36
+ if (value === flag) {
37
+ present = true;
38
+ continue;
39
+ }
40
+ rest.push(value);
41
+ }
42
+ return { present, rest };
43
+ }
44
+ function consumeOption(values, flag) {
45
+ const rest = [];
46
+ let value;
47
+ for (let index = 0; index < values.length; index += 1) {
48
+ const current = values[index];
49
+ if (current === flag) {
50
+ const next = values[index + 1];
51
+ if (!next || next.startsWith("--")) {
52
+ throw new Error(`Missing value for ${flag}`);
53
+ }
54
+ value = next;
55
+ index += 1;
56
+ continue;
57
+ }
58
+ rest.push(current);
59
+ }
60
+ return { value, rest };
61
+ }
62
+ function parseJsonOption(values) {
63
+ const { present, rest } = consumeFlag(values, "--json");
64
+ return { json: present, rest };
65
+ }
66
+ function assertNoExtraArgs(values, usage) {
67
+ if (values.length > 0) {
68
+ throw new Error(usage);
69
+ }
70
+ }
71
+ function parseEnumValue(value, allowed, flag) {
72
+ if (value === undefined) {
73
+ return undefined;
74
+ }
75
+ if (!allowed.has(value)) {
76
+ throw new Error(`Invalid value for ${flag}: ${value}`);
77
+ }
78
+ return value;
79
+ }
80
+ function parseIntegerOption(value, flag) {
81
+ if (value === undefined) {
82
+ return undefined;
83
+ }
84
+ if (!/^-?\d+$/.test(value)) {
85
+ throw new Error(`Invalid value for ${flag}: ${value}`);
86
+ }
87
+ return Number.parseInt(value, 10);
88
+ }
6
89
  async function main() {
7
90
  switch (command) {
8
91
  case "setup": {
9
92
  await runSetup();
10
93
  break;
11
94
  }
95
+ case "demo": {
96
+ const openParse = consumeFlag(args.slice(1), "--open");
97
+ const hostParse = consumeOption(openParse.rest, "--host");
98
+ const portParse = consumeOption(hostParse.rest, "--port");
99
+ assertNoExtraArgs(portParse.rest, 'Usage: "footprint demo [--host <address>] [--port <number>] [--open]"');
100
+ await runLiveDemoCli({
101
+ host: hostParse.value,
102
+ port: parseIntegerOption(portParse.value, "--port"),
103
+ open: openParse.present,
104
+ });
105
+ break;
106
+ }
107
+ case "run": {
108
+ const host = args[1];
109
+ const separatorIndex = args.indexOf("--");
110
+ const runOptionArgs = separatorIndex >= 0 ? args.slice(2, separatorIndex) : args.slice(2);
111
+ let commandArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
112
+ const prepareParse = consumeFlag(runOptionArgs, "--prepare-context");
113
+ const skipPrepareParse = consumeFlag(prepareParse.rest, "--no-context-prepare");
114
+ const interactiveParse = consumeFlag(skipPrepareParse.rest, "--interactive-context");
115
+ const contextTitleParse = consumeOption(interactiveParse.rest, "--context-title");
116
+ if (separatorIndex >= 0) {
117
+ assertNoExtraArgs(contextTitleParse.rest, 'Usage: "footprint run <claude|gemini|codex> [--prepare-context] [--no-context-prepare] [--interactive-context] [--context-title <text>] -- <args...>"');
118
+ }
119
+ else {
120
+ commandArgs = contextTitleParse.rest;
121
+ }
122
+ const prepareContext = skipPrepareParse.present
123
+ ? false
124
+ : prepareParse.present ||
125
+ (Boolean(process.stdin.isTTY) && Boolean(process.stderr.isTTY));
126
+ const interactiveContext = interactiveParse.present ||
127
+ (prepareContext &&
128
+ Boolean(process.stdin.isTTY) &&
129
+ Boolean(process.stderr.isTTY));
130
+ switch (host) {
131
+ case "claude":
132
+ process.exitCode = await runClaudeSession(commandArgs, {
133
+ prepareContext,
134
+ interactiveContext,
135
+ contextTitle: contextTitleParse.value,
136
+ });
137
+ break;
138
+ case "gemini":
139
+ process.exitCode = await runGeminiSession(commandArgs, {
140
+ prepareContext,
141
+ interactiveContext,
142
+ contextTitle: contextTitleParse.value,
143
+ });
144
+ break;
145
+ case "codex":
146
+ process.exitCode = await runCodexSession(commandArgs, {
147
+ prepareContext,
148
+ interactiveContext,
149
+ contextTitle: contextTitleParse.value,
150
+ });
151
+ break;
152
+ default:
153
+ throw new Error(`Unsupported host "${host}". Supported hosts: claude, gemini, codex.`);
154
+ }
155
+ break;
156
+ }
157
+ case "sessions": {
158
+ if (args[1] !== "list") {
159
+ throw new Error('Supported command: "footprint sessions list"');
160
+ }
161
+ const jsonParse = parseJsonOption(args.slice(2));
162
+ const queryParse = consumeOption(jsonParse.rest, "--query");
163
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
164
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
165
+ const statusParse = consumeOption(hostParse.rest, "--status");
166
+ assertNoExtraArgs(statusParse.rest, 'Usage: "footprint sessions list [--query <text>] [--issue-key <issue-key>] [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--json]"');
167
+ listSessionsCli({
168
+ json: jsonParse.json,
169
+ query: queryParse.value,
170
+ issueKey: issueKeyParse.value,
171
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
172
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
173
+ });
174
+ break;
175
+ }
176
+ case "contexts": {
177
+ if (args[1] !== "list") {
178
+ throw new Error('Supported command: "footprint contexts list"');
179
+ }
180
+ const jsonParse = parseJsonOption(args.slice(2));
181
+ assertNoExtraArgs(jsonParse.rest, 'Usage: "footprint contexts list [--json]"');
182
+ listContextsCli({ json: jsonParse.json });
183
+ break;
184
+ }
185
+ case "session": {
186
+ const subcommand = args[1];
187
+ const sessionId = args[2];
188
+ if (!sessionId) {
189
+ throw new Error('Usage: "footprint session <show|ingest|export|messages|trends|timeline|artifacts|narratives|decisions> <id> [options]".');
190
+ }
191
+ if (subcommand === "show") {
192
+ const jsonParse = parseJsonOption(args.slice(3));
193
+ const messageLimitParse = consumeOption(jsonParse.rest, "--message-limit");
194
+ const messageOffsetParse = consumeOption(messageLimitParse.rest, "--message-offset");
195
+ const trendLimitParse = consumeOption(messageOffsetParse.rest, "--trend-limit");
196
+ const trendOffsetParse = consumeOption(trendLimitParse.rest, "--trend-offset");
197
+ const timelineLimitParse = consumeOption(trendOffsetParse.rest, "--timeline-limit");
198
+ const timelineOffsetParse = consumeOption(timelineLimitParse.rest, "--timeline-offset");
199
+ const artifactLimitParse = consumeOption(timelineOffsetParse.rest, "--artifact-limit");
200
+ const artifactOffsetParse = consumeOption(artifactLimitParse.rest, "--artifact-offset");
201
+ const narrativeLimitParse = consumeOption(artifactOffsetParse.rest, "--narrative-limit");
202
+ const narrativeOffsetParse = consumeOption(narrativeLimitParse.rest, "--narrative-offset");
203
+ const decisionLimitParse = consumeOption(narrativeOffsetParse.rest, "--decision-limit");
204
+ const decisionOffsetParse = consumeOption(decisionLimitParse.rest, "--decision-offset");
205
+ assertNoExtraArgs(decisionOffsetParse.rest, 'Usage: "footprint session show <id> [--message-limit <n>] [--message-offset <n>] [--trend-limit <n>] [--trend-offset <n>] [--timeline-limit <n>] [--timeline-offset <n>] [--artifact-limit <n>] [--artifact-offset <n>] [--narrative-limit <n>] [--narrative-offset <n>] [--decision-limit <n>] [--decision-offset <n>] [--json]"');
206
+ showSessionCli(sessionId, {
207
+ json: jsonParse.json,
208
+ messageLimit: parseIntegerOption(messageLimitParse.value, "--message-limit"),
209
+ messageOffset: parseIntegerOption(messageOffsetParse.value, "--message-offset"),
210
+ trendLimit: parseIntegerOption(trendLimitParse.value, "--trend-limit"),
211
+ trendOffset: parseIntegerOption(trendOffsetParse.value, "--trend-offset"),
212
+ timelineLimit: parseIntegerOption(timelineLimitParse.value, "--timeline-limit"),
213
+ timelineOffset: parseIntegerOption(timelineOffsetParse.value, "--timeline-offset"),
214
+ artifactLimit: parseIntegerOption(artifactLimitParse.value, "--artifact-limit"),
215
+ artifactOffset: parseIntegerOption(artifactOffsetParse.value, "--artifact-offset"),
216
+ narrativeLimit: parseIntegerOption(narrativeLimitParse.value, "--narrative-limit"),
217
+ narrativeOffset: parseIntegerOption(narrativeOffsetParse.value, "--narrative-offset"),
218
+ decisionLimit: parseIntegerOption(decisionLimitParse.value, "--decision-limit"),
219
+ decisionOffset: parseIntegerOption(decisionOffsetParse.value, "--decision-offset"),
220
+ });
221
+ break;
222
+ }
223
+ if (subcommand === "ingest") {
224
+ const { json, rest } = parseJsonOption(args.slice(3));
225
+ assertNoExtraArgs(rest, 'Usage: "footprint session ingest <id> [--json]"');
226
+ ingestSessionCli(sessionId, { json });
227
+ break;
228
+ }
229
+ if (subcommand === "export") {
230
+ const jsonParse = parseJsonOption(args.slice(3));
231
+ const groupByParse = consumeOption(jsonParse.rest, "--group-by");
232
+ const outputModeParse = consumeOption(groupByParse.rest, "--output-mode");
233
+ assertNoExtraArgs(outputModeParse.rest, 'Usage: "footprint session export <id> [--group-by <issue|family>] [--output-mode <file|base64|both>] [--json]"');
234
+ await exportSessionsCli([sessionId], {
235
+ json: jsonParse.json,
236
+ groupBy: parseEnumValue(groupByParse.value, historyTrendGroups, "--group-by"),
237
+ outputMode: parseEnumValue(outputModeParse.value, exportOutputModes, "--output-mode"),
238
+ });
239
+ break;
240
+ }
241
+ if (subcommand === "messages") {
242
+ const jsonParse = parseJsonOption(args.slice(3));
243
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
244
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
245
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint session messages <id> [--limit <n>] [--offset <n>] [--json]"');
246
+ showSessionMessagesCli(sessionId, {
247
+ json: jsonParse.json,
248
+ limit: parseIntegerOption(limitParse.value, "--limit"),
249
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
250
+ });
251
+ break;
252
+ }
253
+ if (subcommand === "timeline") {
254
+ const jsonParse = parseJsonOption(args.slice(3));
255
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
256
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
257
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint session timeline <id> [--limit <n>] [--offset <n>] [--json]"');
258
+ showSessionTimelineCli(sessionId, {
259
+ json: jsonParse.json,
260
+ limit: parseIntegerOption(limitParse.value, "--limit"),
261
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
262
+ });
263
+ break;
264
+ }
265
+ if (subcommand === "trends") {
266
+ const jsonParse = parseJsonOption(args.slice(3));
267
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
268
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
269
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint session trends <id> [--limit <n>] [--offset <n>] [--json]"');
270
+ showSessionTrendsCli(sessionId, {
271
+ json: jsonParse.json,
272
+ limit: parseIntegerOption(limitParse.value, "--limit"),
273
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
274
+ });
275
+ break;
276
+ }
277
+ if (subcommand === "artifacts") {
278
+ const jsonParse = parseJsonOption(args.slice(3));
279
+ const typeParse = consumeOption(jsonParse.rest, "--type");
280
+ const limitParse = consumeOption(typeParse.rest, "--limit");
281
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
282
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint session artifacts <id> [--type <file-change|command-output|test-result|git-commit>] [--limit <n>] [--offset <n>] [--json]"');
283
+ showSessionArtifactsCli(sessionId, {
284
+ json: jsonParse.json,
285
+ artifactType: parseEnumValue(typeParse.value, artifactTypes, "--type"),
286
+ limit: parseIntegerOption(limitParse.value, "--limit"),
287
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
288
+ });
289
+ break;
290
+ }
291
+ if (subcommand === "narratives") {
292
+ const jsonParse = parseJsonOption(args.slice(3));
293
+ const kindParse = consumeOption(jsonParse.rest, "--kind");
294
+ const limitParse = consumeOption(kindParse.rest, "--limit");
295
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
296
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint session narratives <id> [--kind <journal|project-summary|handoff>] [--limit <n>] [--offset <n>] [--json]"');
297
+ showSessionNarrativesCli(sessionId, {
298
+ json: jsonParse.json,
299
+ kind: parseEnumValue(kindParse.value, narrativeKinds, "--kind"),
300
+ limit: parseIntegerOption(limitParse.value, "--limit"),
301
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
302
+ });
303
+ break;
304
+ }
305
+ if (subcommand === "decisions") {
306
+ const jsonParse = parseJsonOption(args.slice(3));
307
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
308
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
309
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint session decisions <id> [--limit <n>] [--offset <n>] [--json]"');
310
+ showSessionDecisionsCli(sessionId, {
311
+ json: jsonParse.json,
312
+ limit: parseIntegerOption(limitParse.value, "--limit"),
313
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
314
+ });
315
+ break;
316
+ }
317
+ throw new Error('Usage: "footprint session <show|ingest|export|messages|trends|timeline|artifacts|narratives|decisions> <id> [options]".');
318
+ }
319
+ case "context": {
320
+ const subcommand = args[1];
321
+ if (subcommand === "show") {
322
+ const contextId = args[2];
323
+ if (!contextId) {
324
+ throw new Error('Usage: "footprint context show <context-id> [--json]"');
325
+ }
326
+ const jsonParse = parseJsonOption(args.slice(3));
327
+ assertNoExtraArgs(jsonParse.rest, 'Usage: "footprint context show <context-id> [--json]"');
328
+ showContextCli(contextId, { json: jsonParse.json });
329
+ break;
330
+ }
331
+ if (subcommand === "resolve") {
332
+ const jsonParse = parseJsonOption(args.slice(2));
333
+ const sessionParse = consumeOption(jsonParse.rest, "--session");
334
+ const cwdParse = consumeOption(sessionParse.rest, "--cwd");
335
+ const titleParse = consumeOption(cwdParse.rest, "--title");
336
+ const hostParse = consumeOption(titleParse.rest, "--host");
337
+ assertNoExtraArgs(hostParse.rest, 'Usage: "footprint context resolve [--session <id>] [--cwd <path>] [--title <text>] [--host <claude|gemini|codex>] [--json]"');
338
+ resolveContextCli({
339
+ json: jsonParse.json,
340
+ sessionId: sessionParse.value,
341
+ cwd: cwdParse.value,
342
+ title: titleParse.value,
343
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
344
+ });
345
+ break;
346
+ }
347
+ if (subcommand === "prepare") {
348
+ const jsonParse = parseJsonOption(args.slice(2));
349
+ const interactiveParse = consumeFlag(jsonParse.rest, "--interactive");
350
+ const sessionParse = consumeOption(interactiveParse.rest, "--session");
351
+ const cwdParse = consumeOption(sessionParse.rest, "--cwd");
352
+ const titleParse = consumeOption(cwdParse.rest, "--title");
353
+ const hostParse = consumeOption(titleParse.rest, "--host");
354
+ assertNoExtraArgs(hostParse.rest, 'Usage: "footprint context prepare [--session <id>] [--cwd <path>] [--title <text>] [--host <claude|gemini|codex>] [--interactive] [--json]"');
355
+ await prepareContextCli({
356
+ json: jsonParse.json,
357
+ interactive: interactiveParse.present,
358
+ sessionId: sessionParse.value,
359
+ cwd: cwdParse.value,
360
+ title: titleParse.value,
361
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
362
+ });
363
+ break;
364
+ }
365
+ if (subcommand === "confirm") {
366
+ const jsonParse = parseJsonOption(args.slice(2));
367
+ const contextParse = consumeOption(jsonParse.rest, "--context");
368
+ const labelParse = consumeOption(contextParse.rest, "--label");
369
+ const preferredParse = consumeFlag(labelParse.rest, "--set-preferred");
370
+ if (preferredParse.rest.length === 0) {
371
+ throw new Error('Usage: "footprint context confirm <session-id> [<session-id> ...] [--context <context-id>] [--label <label>] [--set-preferred] [--json]"');
372
+ }
373
+ confirmContextLinkCli({
374
+ json: jsonParse.json,
375
+ sessionIds: preferredParse.rest,
376
+ contextId: contextParse.value,
377
+ label: labelParse.value,
378
+ setPreferred: preferredParse.present,
379
+ });
380
+ break;
381
+ }
382
+ if (subcommand === "reject") {
383
+ const sessionId = args[2];
384
+ if (!sessionId) {
385
+ throw new Error('Usage: "footprint context reject <session-id> --context <context-id> [--json]"');
386
+ }
387
+ const jsonParse = parseJsonOption(args.slice(3));
388
+ const contextParse = consumeOption(jsonParse.rest, "--context");
389
+ if (!contextParse.value) {
390
+ throw new Error('Usage: "footprint context reject <session-id> --context <context-id> [--json]"');
391
+ }
392
+ assertNoExtraArgs(contextParse.rest, 'Usage: "footprint context reject <session-id> --context <context-id> [--json]"');
393
+ rejectContextLinkCli({
394
+ json: jsonParse.json,
395
+ sessionId,
396
+ contextId: contextParse.value,
397
+ });
398
+ break;
399
+ }
400
+ if (subcommand === "move") {
401
+ const sessionId = args[2];
402
+ if (!sessionId) {
403
+ throw new Error('Usage: "footprint context move <session-id> [--context <context-id>] [--label <label>] [--set-preferred] [--json]"');
404
+ }
405
+ const jsonParse = parseJsonOption(args.slice(3));
406
+ const contextParse = consumeOption(jsonParse.rest, "--context");
407
+ const labelParse = consumeOption(contextParse.rest, "--label");
408
+ const preferredParse = consumeFlag(labelParse.rest, "--set-preferred");
409
+ assertNoExtraArgs(preferredParse.rest, 'Usage: "footprint context move <session-id> [--context <context-id>] [--label <label>] [--set-preferred] [--json]"');
410
+ moveSessionContextCli({
411
+ json: jsonParse.json,
412
+ sessionId,
413
+ contextId: contextParse.value,
414
+ label: labelParse.value,
415
+ setPreferred: preferredParse.present,
416
+ });
417
+ break;
418
+ }
419
+ if (subcommand === "merge") {
420
+ const sourceContextId = args[2];
421
+ const targetContextId = args[3];
422
+ if (!sourceContextId || !targetContextId) {
423
+ throw new Error('Usage: "footprint context merge <source-context-id> <target-context-id> [--json]"');
424
+ }
425
+ const jsonParse = parseJsonOption(args.slice(4));
426
+ assertNoExtraArgs(jsonParse.rest, 'Usage: "footprint context merge <source-context-id> <target-context-id> [--json]"');
427
+ mergeContextsCli({
428
+ json: jsonParse.json,
429
+ sourceContextId,
430
+ targetContextId,
431
+ });
432
+ break;
433
+ }
434
+ if (subcommand === "split") {
435
+ const contextId = args[2];
436
+ if (!contextId) {
437
+ throw new Error('Usage: "footprint context split <context-id> --sessions <id,id,...> [--label <label>] [--set-preferred] [--json]"');
438
+ }
439
+ const jsonParse = parseJsonOption(args.slice(3));
440
+ const sessionsParse = consumeOption(jsonParse.rest, "--sessions");
441
+ const labelParse = consumeOption(sessionsParse.rest, "--label");
442
+ const preferredParse = consumeFlag(labelParse.rest, "--set-preferred");
443
+ if (!sessionsParse.value) {
444
+ throw new Error('Usage: "footprint context split <context-id> --sessions <id,id,...> [--label <label>] [--set-preferred] [--json]"');
445
+ }
446
+ assertNoExtraArgs(preferredParse.rest, 'Usage: "footprint context split <context-id> --sessions <id,id,...> [--label <label>] [--set-preferred] [--json]"');
447
+ splitContextCli({
448
+ json: jsonParse.json,
449
+ contextId,
450
+ sessionIds: sessionsParse.value
451
+ .split(",")
452
+ .map((value) => value.trim())
453
+ .filter(Boolean),
454
+ label: labelParse.value,
455
+ setPreferred: preferredParse.present,
456
+ });
457
+ break;
458
+ }
459
+ if (subcommand === "activate") {
460
+ const contextId = args[2];
461
+ if (!contextId) {
462
+ throw new Error('Usage: "footprint context activate <context-id> [--cwd <path>] [--json]"');
463
+ }
464
+ const jsonParse = parseJsonOption(args.slice(3));
465
+ const cwdParse = consumeOption(jsonParse.rest, "--cwd");
466
+ assertNoExtraArgs(cwdParse.rest, 'Usage: "footprint context activate <context-id> [--cwd <path>] [--json]"');
467
+ setActiveContextCli({
468
+ json: jsonParse.json,
469
+ contextId,
470
+ cwd: cwdParse.value,
471
+ });
472
+ break;
473
+ }
474
+ throw new Error('Usage: "footprint context <show|resolve|prepare|confirm|reject|move|merge|split|activate> ..."');
475
+ }
476
+ case "history": {
477
+ if (args[1] === "search" && !args[2]) {
478
+ throw new Error('Usage: "footprint history search <query> [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--limit <n>] [--offset <n>] [--json]"');
479
+ }
480
+ if (args[1] === "search" && args[2]) {
481
+ const jsonParse = parseJsonOption(args.slice(3));
482
+ const hostParse = consumeOption(jsonParse.rest, "--host");
483
+ const statusParse = consumeOption(hostParse.rest, "--status");
484
+ const limitParse = consumeOption(statusParse.rest, "--limit");
485
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
486
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint history search <query> [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--limit <n>] [--offset <n>] [--json]"');
487
+ searchHistoryCli(args[2], {
488
+ json: jsonParse.json,
489
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
490
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
491
+ limit: limitParse.value
492
+ ? Number.parseInt(limitParse.value, 10)
493
+ : undefined,
494
+ offset: offsetParse.value
495
+ ? Number.parseInt(offsetParse.value, 10)
496
+ : undefined,
497
+ });
498
+ break;
499
+ }
500
+ if (args[1] === "handoff") {
501
+ const jsonParse = parseJsonOption(args.slice(2));
502
+ const queryParse = consumeOption(jsonParse.rest, "--query");
503
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
504
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
505
+ const statusParse = consumeOption(hostParse.rest, "--status");
506
+ const groupByParse = consumeOption(statusParse.rest, "--group-by");
507
+ assertNoExtraArgs(groupByParse.rest, 'Usage: "footprint history handoff [--query <text>] [--issue-key <issue-key>] [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--group-by <issue|family>] [--json]"');
508
+ showHistoryHandoffCli({
509
+ json: jsonParse.json,
510
+ query: queryParse.value,
511
+ issueKey: issueKeyParse.value,
512
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
513
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
514
+ groupBy: parseEnumValue(groupByParse.value, historyTrendGroups, "--group-by"),
515
+ });
516
+ break;
517
+ }
518
+ if (args[1] !== "trends") {
519
+ throw new Error('Usage: "footprint history <search|trends|handoff> ..."');
520
+ }
521
+ const jsonParse = parseJsonOption(args.slice(2));
522
+ const queryParse = consumeOption(jsonParse.rest, "--query");
523
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
524
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
525
+ const statusParse = consumeOption(hostParse.rest, "--status");
526
+ const groupByParse = consumeOption(statusParse.rest, "--group-by");
527
+ const limitParse = consumeOption(groupByParse.rest, "--limit");
528
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
529
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint history trends [--query <text>] [--issue-key <issue-key>] [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--group-by <issue|family>] [--limit <n>] [--offset <n>] [--json]"');
530
+ showHistoryTrendsCli({
531
+ json: jsonParse.json,
532
+ query: queryParse.value,
533
+ issueKey: issueKeyParse.value,
534
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
535
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
536
+ groupBy: parseEnumValue(groupByParse.value, historyTrendGroups, "--group-by"),
537
+ limit: limitParse.value
538
+ ? Number.parseInt(limitParse.value, 10)
539
+ : undefined,
540
+ offset: offsetParse.value
541
+ ? Number.parseInt(offsetParse.value, 10)
542
+ : undefined,
543
+ });
544
+ break;
545
+ }
546
+ case "list-sessions": {
547
+ const jsonParse = parseJsonOption(args.slice(1));
548
+ const queryParse = consumeOption(jsonParse.rest, "--query");
549
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
550
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
551
+ const statusParse = consumeOption(hostParse.rest, "--status");
552
+ assertNoExtraArgs(statusParse.rest, 'Usage: "footprint list-sessions [--query <text>] [--issue-key <issue-key>] [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--json]"');
553
+ listSessionsCli({
554
+ json: jsonParse.json,
555
+ query: queryParse.value,
556
+ issueKey: issueKeyParse.value,
557
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
558
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
559
+ });
560
+ break;
561
+ }
562
+ case "list-contexts": {
563
+ const jsonParse = parseJsonOption(args.slice(1));
564
+ assertNoExtraArgs(jsonParse.rest, 'Usage: "footprint list-contexts [--json]"');
565
+ listContextsCli({ json: jsonParse.json });
566
+ break;
567
+ }
568
+ case "get-context": {
569
+ const contextId = args[1];
570
+ if (!contextId) {
571
+ throw new Error('Usage: "footprint get-context <context-id> [--json]"');
572
+ }
573
+ const jsonParse = parseJsonOption(args.slice(2));
574
+ assertNoExtraArgs(jsonParse.rest, 'Usage: "footprint get-context <context-id> [--json]"');
575
+ showContextCli(contextId, { json: jsonParse.json });
576
+ break;
577
+ }
578
+ case "resolve-context": {
579
+ const jsonParse = parseJsonOption(args.slice(1));
580
+ const sessionParse = consumeOption(jsonParse.rest, "--session");
581
+ const cwdParse = consumeOption(sessionParse.rest, "--cwd");
582
+ const titleParse = consumeOption(cwdParse.rest, "--title");
583
+ const hostParse = consumeOption(titleParse.rest, "--host");
584
+ assertNoExtraArgs(hostParse.rest, 'Usage: "footprint resolve-context [--session <id>] [--cwd <path>] [--title <text>] [--host <claude|gemini|codex>] [--json]"');
585
+ resolveContextCli({
586
+ json: jsonParse.json,
587
+ sessionId: sessionParse.value,
588
+ cwd: cwdParse.value,
589
+ title: titleParse.value,
590
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
591
+ });
592
+ break;
593
+ }
594
+ case "prepare-context": {
595
+ const jsonParse = parseJsonOption(args.slice(1));
596
+ const interactiveParse = consumeFlag(jsonParse.rest, "--interactive");
597
+ const sessionParse = consumeOption(interactiveParse.rest, "--session");
598
+ const cwdParse = consumeOption(sessionParse.rest, "--cwd");
599
+ const titleParse = consumeOption(cwdParse.rest, "--title");
600
+ const hostParse = consumeOption(titleParse.rest, "--host");
601
+ assertNoExtraArgs(hostParse.rest, 'Usage: "footprint prepare-context [--session <id>] [--cwd <path>] [--title <text>] [--host <claude|gemini|codex>] [--interactive] [--json]"');
602
+ await prepareContextCli({
603
+ json: jsonParse.json,
604
+ interactive: interactiveParse.present,
605
+ sessionId: sessionParse.value,
606
+ cwd: cwdParse.value,
607
+ title: titleParse.value,
608
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
609
+ });
610
+ break;
611
+ }
612
+ case "confirm-context-link": {
613
+ const jsonParse = parseJsonOption(args.slice(1));
614
+ const contextParse = consumeOption(jsonParse.rest, "--context");
615
+ const labelParse = consumeOption(contextParse.rest, "--label");
616
+ const preferredParse = consumeFlag(labelParse.rest, "--set-preferred");
617
+ if (preferredParse.rest.length === 0) {
618
+ throw new Error('Usage: "footprint confirm-context-link <session-id> [<session-id> ...] [--context <context-id>] [--label <label>] [--set-preferred] [--json]"');
619
+ }
620
+ confirmContextLinkCli({
621
+ json: jsonParse.json,
622
+ sessionIds: preferredParse.rest,
623
+ contextId: contextParse.value,
624
+ label: labelParse.value,
625
+ setPreferred: preferredParse.present,
626
+ });
627
+ break;
628
+ }
629
+ case "reject-context-link": {
630
+ const sessionId = args[1];
631
+ if (!sessionId) {
632
+ throw new Error('Usage: "footprint reject-context-link <session-id> --context <context-id> [--json]"');
633
+ }
634
+ const jsonParse = parseJsonOption(args.slice(2));
635
+ const contextParse = consumeOption(jsonParse.rest, "--context");
636
+ if (!contextParse.value) {
637
+ throw new Error('Usage: "footprint reject-context-link <session-id> --context <context-id> [--json]"');
638
+ }
639
+ assertNoExtraArgs(contextParse.rest, 'Usage: "footprint reject-context-link <session-id> --context <context-id> [--json]"');
640
+ rejectContextLinkCli({
641
+ json: jsonParse.json,
642
+ sessionId,
643
+ contextId: contextParse.value,
644
+ });
645
+ break;
646
+ }
647
+ case "move-session-context": {
648
+ const sessionId = args[1];
649
+ if (!sessionId) {
650
+ throw new Error('Usage: "footprint move-session-context <session-id> [--context <context-id>] [--label <label>] [--set-preferred] [--json]"');
651
+ }
652
+ const jsonParse = parseJsonOption(args.slice(2));
653
+ const contextParse = consumeOption(jsonParse.rest, "--context");
654
+ const labelParse = consumeOption(contextParse.rest, "--label");
655
+ const preferredParse = consumeFlag(labelParse.rest, "--set-preferred");
656
+ assertNoExtraArgs(preferredParse.rest, 'Usage: "footprint move-session-context <session-id> [--context <context-id>] [--label <label>] [--set-preferred] [--json]"');
657
+ moveSessionContextCli({
658
+ json: jsonParse.json,
659
+ sessionId,
660
+ contextId: contextParse.value,
661
+ label: labelParse.value,
662
+ setPreferred: preferredParse.present,
663
+ });
664
+ break;
665
+ }
666
+ case "merge-contexts": {
667
+ const sourceContextId = args[1];
668
+ const targetContextId = args[2];
669
+ if (!sourceContextId || !targetContextId) {
670
+ throw new Error('Usage: "footprint merge-contexts <source-context-id> <target-context-id> [--json]"');
671
+ }
672
+ const jsonParse = parseJsonOption(args.slice(3));
673
+ assertNoExtraArgs(jsonParse.rest, 'Usage: "footprint merge-contexts <source-context-id> <target-context-id> [--json]"');
674
+ mergeContextsCli({
675
+ json: jsonParse.json,
676
+ sourceContextId,
677
+ targetContextId,
678
+ });
679
+ break;
680
+ }
681
+ case "split-context": {
682
+ const contextId = args[1];
683
+ if (!contextId) {
684
+ throw new Error('Usage: "footprint split-context <context-id> --sessions <id,id,...> [--label <label>] [--set-preferred] [--json]"');
685
+ }
686
+ const jsonParse = parseJsonOption(args.slice(2));
687
+ const sessionsParse = consumeOption(jsonParse.rest, "--sessions");
688
+ const labelParse = consumeOption(sessionsParse.rest, "--label");
689
+ const preferredParse = consumeFlag(labelParse.rest, "--set-preferred");
690
+ if (!sessionsParse.value) {
691
+ throw new Error('Usage: "footprint split-context <context-id> --sessions <id,id,...> [--label <label>] [--set-preferred] [--json]"');
692
+ }
693
+ assertNoExtraArgs(preferredParse.rest, 'Usage: "footprint split-context <context-id> --sessions <id,id,...> [--label <label>] [--set-preferred] [--json]"');
694
+ splitContextCli({
695
+ json: jsonParse.json,
696
+ contextId,
697
+ sessionIds: sessionsParse.value
698
+ .split(",")
699
+ .map((value) => value.trim())
700
+ .filter(Boolean),
701
+ label: labelParse.value,
702
+ setPreferred: preferredParse.present,
703
+ });
704
+ break;
705
+ }
706
+ case "set-active-context": {
707
+ const contextId = args[1];
708
+ if (!contextId) {
709
+ throw new Error('Usage: "footprint set-active-context <context-id> [--cwd <path>] [--json]"');
710
+ }
711
+ const jsonParse = parseJsonOption(args.slice(2));
712
+ const cwdParse = consumeOption(jsonParse.rest, "--cwd");
713
+ assertNoExtraArgs(cwdParse.rest, 'Usage: "footprint set-active-context <context-id> [--cwd <path>] [--json]"');
714
+ setActiveContextCli({
715
+ json: jsonParse.json,
716
+ contextId,
717
+ cwd: cwdParse.value,
718
+ });
719
+ break;
720
+ }
721
+ case "get-session": {
722
+ if (!args[1]) {
723
+ throw new Error('Usage: "footprint get-session <id>"');
724
+ }
725
+ const jsonParse = parseJsonOption(args.slice(2));
726
+ const messageLimitParse = consumeOption(jsonParse.rest, "--message-limit");
727
+ const messageOffsetParse = consumeOption(messageLimitParse.rest, "--message-offset");
728
+ const trendLimitParse = consumeOption(messageOffsetParse.rest, "--trend-limit");
729
+ const trendOffsetParse = consumeOption(trendLimitParse.rest, "--trend-offset");
730
+ const timelineLimitParse = consumeOption(trendOffsetParse.rest, "--timeline-limit");
731
+ const timelineOffsetParse = consumeOption(timelineLimitParse.rest, "--timeline-offset");
732
+ const artifactLimitParse = consumeOption(timelineOffsetParse.rest, "--artifact-limit");
733
+ const artifactOffsetParse = consumeOption(artifactLimitParse.rest, "--artifact-offset");
734
+ const narrativeLimitParse = consumeOption(artifactOffsetParse.rest, "--narrative-limit");
735
+ const narrativeOffsetParse = consumeOption(narrativeLimitParse.rest, "--narrative-offset");
736
+ const decisionLimitParse = consumeOption(narrativeOffsetParse.rest, "--decision-limit");
737
+ const decisionOffsetParse = consumeOption(decisionLimitParse.rest, "--decision-offset");
738
+ assertNoExtraArgs(decisionOffsetParse.rest, 'Usage: "footprint get-session <id> [--message-limit <n>] [--message-offset <n>] [--trend-limit <n>] [--trend-offset <n>] [--timeline-limit <n>] [--timeline-offset <n>] [--artifact-limit <n>] [--artifact-offset <n>] [--narrative-limit <n>] [--narrative-offset <n>] [--decision-limit <n>] [--decision-offset <n>] [--json]"');
739
+ showSessionCli(args[1], {
740
+ json: jsonParse.json,
741
+ messageLimit: parseIntegerOption(messageLimitParse.value, "--message-limit"),
742
+ messageOffset: parseIntegerOption(messageOffsetParse.value, "--message-offset"),
743
+ trendLimit: parseIntegerOption(trendLimitParse.value, "--trend-limit"),
744
+ trendOffset: parseIntegerOption(trendOffsetParse.value, "--trend-offset"),
745
+ timelineLimit: parseIntegerOption(timelineLimitParse.value, "--timeline-limit"),
746
+ timelineOffset: parseIntegerOption(timelineOffsetParse.value, "--timeline-offset"),
747
+ artifactLimit: parseIntegerOption(artifactLimitParse.value, "--artifact-limit"),
748
+ artifactOffset: parseIntegerOption(artifactOffsetParse.value, "--artifact-offset"),
749
+ narrativeLimit: parseIntegerOption(narrativeLimitParse.value, "--narrative-limit"),
750
+ narrativeOffset: parseIntegerOption(narrativeOffsetParse.value, "--narrative-offset"),
751
+ decisionLimit: parseIntegerOption(decisionLimitParse.value, "--decision-limit"),
752
+ decisionOffset: parseIntegerOption(decisionOffsetParse.value, "--decision-offset"),
753
+ });
754
+ break;
755
+ }
756
+ case "export-sessions": {
757
+ const jsonParse = parseJsonOption(args.slice(1));
758
+ const queryParse = consumeOption(jsonParse.rest, "--query");
759
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
760
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
761
+ const statusParse = consumeOption(hostParse.rest, "--status");
762
+ const groupByParse = consumeOption(statusParse.rest, "--group-by");
763
+ const outputModeParse = consumeOption(groupByParse.rest, "--output-mode");
764
+ await exportSessionsCli(outputModeParse.rest, {
765
+ json: jsonParse.json,
766
+ query: queryParse.value,
767
+ issueKey: issueKeyParse.value,
768
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
769
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
770
+ groupBy: parseEnumValue(groupByParse.value, historyTrendGroups, "--group-by"),
771
+ outputMode: parseEnumValue(outputModeParse.value, exportOutputModes, "--output-mode"),
772
+ });
773
+ break;
774
+ }
775
+ case "get-session-messages": {
776
+ if (!args[1]) {
777
+ throw new Error('Usage: "footprint get-session-messages <id> [--limit <n>] [--offset <n>] [--json]"');
778
+ }
779
+ const jsonParse = parseJsonOption(args.slice(2));
780
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
781
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
782
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-session-messages <id> [--limit <n>] [--offset <n>] [--json]"');
783
+ showSessionMessagesCli(args[1], {
784
+ json: jsonParse.json,
785
+ limit: parseIntegerOption(limitParse.value, "--limit"),
786
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
787
+ });
788
+ break;
789
+ }
790
+ case "get-session-timeline": {
791
+ if (!args[1]) {
792
+ throw new Error('Usage: "footprint get-session-timeline <id> [--limit <n>] [--offset <n>] [--json]"');
793
+ }
794
+ const jsonParse = parseJsonOption(args.slice(2));
795
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
796
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
797
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-session-timeline <id> [--limit <n>] [--offset <n>] [--json]"');
798
+ showSessionTimelineCli(args[1], {
799
+ json: jsonParse.json,
800
+ limit: parseIntegerOption(limitParse.value, "--limit"),
801
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
802
+ });
803
+ break;
804
+ }
805
+ case "get-session-trends": {
806
+ if (!args[1]) {
807
+ throw new Error('Usage: "footprint get-session-trends <id> [--limit <n>] [--offset <n>] [--json]"');
808
+ }
809
+ const jsonParse = parseJsonOption(args.slice(2));
810
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
811
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
812
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-session-trends <id> [--limit <n>] [--offset <n>] [--json]"');
813
+ showSessionTrendsCli(args[1], {
814
+ json: jsonParse.json,
815
+ limit: parseIntegerOption(limitParse.value, "--limit"),
816
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
817
+ });
818
+ break;
819
+ }
820
+ case "get-session-artifacts": {
821
+ if (!args[1]) {
822
+ throw new Error('Usage: "footprint get-session-artifacts <id> [--type <file-change|command-output|test-result|git-commit>] [--limit <n>] [--offset <n>] [--json]"');
823
+ }
824
+ const jsonParse = parseJsonOption(args.slice(2));
825
+ const typeParse = consumeOption(jsonParse.rest, "--type");
826
+ const limitParse = consumeOption(typeParse.rest, "--limit");
827
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
828
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-session-artifacts <id> [--type <file-change|command-output|test-result|git-commit>] [--limit <n>] [--offset <n>] [--json]"');
829
+ showSessionArtifactsCli(args[1], {
830
+ json: jsonParse.json,
831
+ artifactType: parseEnumValue(typeParse.value, artifactTypes, "--type"),
832
+ limit: parseIntegerOption(limitParse.value, "--limit"),
833
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
834
+ });
835
+ break;
836
+ }
837
+ case "get-session-narrative": {
838
+ if (!args[1]) {
839
+ throw new Error('Usage: "footprint get-session-narrative <id> [--kind <journal|project-summary|handoff>] [--limit <n>] [--offset <n>] [--json]"');
840
+ }
841
+ const jsonParse = parseJsonOption(args.slice(2));
842
+ const kindParse = consumeOption(jsonParse.rest, "--kind");
843
+ const limitParse = consumeOption(kindParse.rest, "--limit");
844
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
845
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-session-narrative <id> [--kind <journal|project-summary|handoff>] [--limit <n>] [--offset <n>] [--json]"');
846
+ showSessionNarrativesCli(args[1], {
847
+ json: jsonParse.json,
848
+ kind: parseEnumValue(kindParse.value, narrativeKinds, "--kind"),
849
+ limit: parseIntegerOption(limitParse.value, "--limit"),
850
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
851
+ });
852
+ break;
853
+ }
854
+ case "get-session-decisions": {
855
+ if (!args[1]) {
856
+ throw new Error('Usage: "footprint get-session-decisions <id> [--limit <n>] [--offset <n>] [--json]"');
857
+ }
858
+ const jsonParse = parseJsonOption(args.slice(2));
859
+ const limitParse = consumeOption(jsonParse.rest, "--limit");
860
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
861
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-session-decisions <id> [--limit <n>] [--offset <n>] [--json]"');
862
+ showSessionDecisionsCli(args[1], {
863
+ json: jsonParse.json,
864
+ limit: parseIntegerOption(limitParse.value, "--limit"),
865
+ offset: parseIntegerOption(offsetParse.value, "--offset"),
866
+ });
867
+ break;
868
+ }
869
+ case "search-history": {
870
+ if (!args[1]) {
871
+ throw new Error('Usage: "footprint search-history <query> [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--limit <n>] [--offset <n>] [--json]"');
872
+ }
873
+ const jsonParse = parseJsonOption(args.slice(2));
874
+ const hostParse = consumeOption(jsonParse.rest, "--host");
875
+ const statusParse = consumeOption(hostParse.rest, "--status");
876
+ const limitParse = consumeOption(statusParse.rest, "--limit");
877
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
878
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint search-history <query> [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--limit <n>] [--offset <n>] [--json]"');
879
+ searchHistoryCli(args[1], {
880
+ json: jsonParse.json,
881
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
882
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
883
+ limit: limitParse.value
884
+ ? Number.parseInt(limitParse.value, 10)
885
+ : undefined,
886
+ offset: offsetParse.value
887
+ ? Number.parseInt(offsetParse.value, 10)
888
+ : undefined,
889
+ });
890
+ break;
891
+ }
892
+ case "get-history-trends": {
893
+ const jsonParse = parseJsonOption(args.slice(1));
894
+ const queryParse = consumeOption(jsonParse.rest, "--query");
895
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
896
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
897
+ const statusParse = consumeOption(hostParse.rest, "--status");
898
+ const groupByParse = consumeOption(statusParse.rest, "--group-by");
899
+ const limitParse = consumeOption(groupByParse.rest, "--limit");
900
+ const offsetParse = consumeOption(limitParse.rest, "--offset");
901
+ assertNoExtraArgs(offsetParse.rest, 'Usage: "footprint get-history-trends [--query <text>] [--issue-key <issue-key>] [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--group-by <issue|family>] [--limit <n>] [--offset <n>] [--json]"');
902
+ showHistoryTrendsCli({
903
+ json: jsonParse.json,
904
+ query: queryParse.value,
905
+ issueKey: issueKeyParse.value,
906
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
907
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
908
+ groupBy: parseEnumValue(groupByParse.value, historyTrendGroups, "--group-by"),
909
+ limit: limitParse.value
910
+ ? Number.parseInt(limitParse.value, 10)
911
+ : undefined,
912
+ offset: offsetParse.value
913
+ ? Number.parseInt(offsetParse.value, 10)
914
+ : undefined,
915
+ });
916
+ break;
917
+ }
918
+ case "get-history-handoff": {
919
+ const jsonParse = parseJsonOption(args.slice(1));
920
+ const queryParse = consumeOption(jsonParse.rest, "--query");
921
+ const issueKeyParse = consumeOption(queryParse.rest, "--issue-key");
922
+ const hostParse = consumeOption(issueKeyParse.rest, "--host");
923
+ const statusParse = consumeOption(hostParse.rest, "--status");
924
+ const groupByParse = consumeOption(statusParse.rest, "--group-by");
925
+ assertNoExtraArgs(groupByParse.rest, 'Usage: "footprint get-history-handoff [--query <text>] [--issue-key <issue-key>] [--host <claude|gemini|codex>] [--status <running|completed|failed|interrupted>] [--group-by <issue|family>] [--json]"');
926
+ showHistoryHandoffCli({
927
+ json: jsonParse.json,
928
+ query: queryParse.value,
929
+ issueKey: issueKeyParse.value,
930
+ host: parseEnumValue(hostParse.value, sessionHosts, "--host"),
931
+ status: parseEnumValue(statusParse.value, sessionStatuses, "--status"),
932
+ groupBy: parseEnumValue(groupByParse.value, historyTrendGroups, "--group-by"),
933
+ });
934
+ break;
935
+ }
12
936
  default: {
13
937
  // No command or unknown command - start MCP server
14
938
  // Import and run server