@chankov/agent-skills 0.1.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 (320) hide show
  1. package/.claude/commands/build.md +18 -0
  2. package/.claude/commands/code-simplify.md +22 -0
  3. package/.claude/commands/design-agent.md +14 -0
  4. package/.claude/commands/doctor.md +13 -0
  5. package/.claude/commands/plan.md +16 -0
  6. package/.claude/commands/prime.md +22 -0
  7. package/.claude/commands/review.md +16 -0
  8. package/.claude/commands/setup.md +19 -0
  9. package/.claude/commands/ship.md +17 -0
  10. package/.claude/commands/spec.md +15 -0
  11. package/.claude/commands/test.md +19 -0
  12. package/.opencode/commands/as-build.md +17 -0
  13. package/.opencode/commands/as-code-simplify.md +16 -0
  14. package/.opencode/commands/as-design-agent.md +15 -0
  15. package/.opencode/commands/as-doctor.md +11 -0
  16. package/.opencode/commands/as-plan.md +16 -0
  17. package/.opencode/commands/as-prime.md +22 -0
  18. package/.opencode/commands/as-review.md +15 -0
  19. package/.opencode/commands/as-setup.md +11 -0
  20. package/.opencode/commands/as-ship.md +16 -0
  21. package/.opencode/commands/as-spec.md +16 -0
  22. package/.opencode/commands/as-test.md +21 -0
  23. package/.pi/agents/agent-chain.yaml +49 -0
  24. package/.pi/agents/bowser.md +19 -0
  25. package/.pi/agents/pi-pi/agent-expert.md +98 -0
  26. package/.pi/agents/pi-pi/cli-expert.md +41 -0
  27. package/.pi/agents/pi-pi/config-expert.md +63 -0
  28. package/.pi/agents/pi-pi/ext-expert.md +43 -0
  29. package/.pi/agents/pi-pi/keybinding-expert.md +134 -0
  30. package/.pi/agents/pi-pi/pi-orchestrator.md +57 -0
  31. package/.pi/agents/pi-pi/prompt-expert.md +70 -0
  32. package/.pi/agents/pi-pi/skill-expert.md +42 -0
  33. package/.pi/agents/pi-pi/theme-expert.md +40 -0
  34. package/.pi/agents/pi-pi/tui-expert.md +85 -0
  35. package/.pi/agents/teams.yaml +31 -0
  36. package/.pi/damage-control-rules.yaml +278 -0
  37. package/.pi/extensions/chrome-devtools-mcp/README.md +39 -0
  38. package/.pi/extensions/chrome-devtools-mcp/index.ts +61 -0
  39. package/.pi/extensions/chrome-devtools-mcp/package.json +6 -0
  40. package/.pi/extensions/compact-and-continue/README.md +42 -0
  41. package/.pi/extensions/compact-and-continue/index.ts +120 -0
  42. package/.pi/extensions/compact-and-continue/package.json +6 -0
  43. package/.pi/extensions/mcp-bridge/README.md +46 -0
  44. package/.pi/extensions/mcp-bridge/index.ts +206 -0
  45. package/.pi/extensions/mcp-bridge/package.json +6 -0
  46. package/.pi/extensions/package-lock.json +1143 -0
  47. package/.pi/extensions/package.json +9 -0
  48. package/.pi/harnesses/agent-chain/README.md +37 -0
  49. package/.pi/harnesses/agent-chain/index.ts +795 -0
  50. package/.pi/harnesses/agent-chain/package.json +6 -0
  51. package/.pi/harnesses/agent-team/README.md +38 -0
  52. package/.pi/harnesses/agent-team/index.ts +732 -0
  53. package/.pi/harnesses/agent-team/package.json +6 -0
  54. package/.pi/harnesses/coms/README.md +36 -0
  55. package/.pi/harnesses/coms/index.ts +1595 -0
  56. package/.pi/harnesses/coms/package.json +6 -0
  57. package/.pi/harnesses/coms-net/README.md +46 -0
  58. package/.pi/harnesses/coms-net/index.ts +1637 -0
  59. package/.pi/harnesses/coms-net/package.json +6 -0
  60. package/.pi/harnesses/damage-control/README.md +38 -0
  61. package/.pi/harnesses/damage-control/index.ts +207 -0
  62. package/.pi/harnesses/damage-control/package.json +6 -0
  63. package/.pi/harnesses/damage-control-continue/README.md +37 -0
  64. package/.pi/harnesses/damage-control-continue/index.ts +234 -0
  65. package/.pi/harnesses/damage-control-continue/package.json +6 -0
  66. package/.pi/harnesses/minimal/README.md +27 -0
  67. package/.pi/harnesses/minimal/index.ts +32 -0
  68. package/.pi/harnesses/minimal/package.json +6 -0
  69. package/.pi/harnesses/package-lock.json +35 -0
  70. package/.pi/harnesses/package.json +9 -0
  71. package/.pi/harnesses/pi-pi/README.md +39 -0
  72. package/.pi/harnesses/pi-pi/index.ts +631 -0
  73. package/.pi/harnesses/pi-pi/package.json +6 -0
  74. package/.pi/harnesses/purpose-gate/README.md +27 -0
  75. package/.pi/harnesses/purpose-gate/index.ts +82 -0
  76. package/.pi/harnesses/purpose-gate/package.json +6 -0
  77. package/.pi/harnesses/session-replay/README.md +28 -0
  78. package/.pi/harnesses/session-replay/index.ts +214 -0
  79. package/.pi/harnesses/session-replay/package.json +6 -0
  80. package/.pi/harnesses/subagent-widget/README.md +36 -0
  81. package/.pi/harnesses/subagent-widget/index.ts +479 -0
  82. package/.pi/harnesses/subagent-widget/package.json +6 -0
  83. package/.pi/harnesses/system-select/README.md +39 -0
  84. package/.pi/harnesses/system-select/index.ts +165 -0
  85. package/.pi/harnesses/system-select/package.json +6 -0
  86. package/.pi/harnesses/tilldone/README.md +35 -0
  87. package/.pi/harnesses/tilldone/index.ts +724 -0
  88. package/.pi/harnesses/tilldone/package.json +6 -0
  89. package/.pi/harnesses/tool-counter/README.md +31 -0
  90. package/.pi/harnesses/tool-counter/index.ts +100 -0
  91. package/.pi/harnesses/tool-counter/package.json +6 -0
  92. package/.pi/harnesses/tool-counter-widget/README.md +27 -0
  93. package/.pi/harnesses/tool-counter-widget/index.ts +66 -0
  94. package/.pi/harnesses/tool-counter-widget/package.json +6 -0
  95. package/.pi/prompts/build.md +24 -0
  96. package/.pi/prompts/code-simplify.md +22 -0
  97. package/.pi/prompts/doctor.md +13 -0
  98. package/.pi/prompts/plan.md +16 -0
  99. package/.pi/prompts/review.md +16 -0
  100. package/.pi/prompts/setup.md +19 -0
  101. package/.pi/prompts/ship.md +17 -0
  102. package/.pi/prompts/spec.md +15 -0
  103. package/.pi/prompts/test.md +19 -0
  104. package/.pi/skills/bowser/SKILL.md +114 -0
  105. package/.versions/0.1.0/.claude/commands/build.md +18 -0
  106. package/.versions/0.1.0/.claude/commands/code-simplify.md +22 -0
  107. package/.versions/0.1.0/.claude/commands/design-agent.md +14 -0
  108. package/.versions/0.1.0/.claude/commands/doctor.md +13 -0
  109. package/.versions/0.1.0/.claude/commands/plan.md +16 -0
  110. package/.versions/0.1.0/.claude/commands/prime.md +22 -0
  111. package/.versions/0.1.0/.claude/commands/review.md +16 -0
  112. package/.versions/0.1.0/.claude/commands/setup.md +19 -0
  113. package/.versions/0.1.0/.claude/commands/ship.md +17 -0
  114. package/.versions/0.1.0/.claude/commands/spec.md +15 -0
  115. package/.versions/0.1.0/.claude/commands/test.md +19 -0
  116. package/.versions/0.1.0/.opencode/commands/as-build.md +17 -0
  117. package/.versions/0.1.0/.opencode/commands/as-code-simplify.md +16 -0
  118. package/.versions/0.1.0/.opencode/commands/as-design-agent.md +15 -0
  119. package/.versions/0.1.0/.opencode/commands/as-doctor.md +11 -0
  120. package/.versions/0.1.0/.opencode/commands/as-plan.md +16 -0
  121. package/.versions/0.1.0/.opencode/commands/as-prime.md +22 -0
  122. package/.versions/0.1.0/.opencode/commands/as-review.md +15 -0
  123. package/.versions/0.1.0/.opencode/commands/as-setup.md +11 -0
  124. package/.versions/0.1.0/.opencode/commands/as-ship.md +16 -0
  125. package/.versions/0.1.0/.opencode/commands/as-spec.md +16 -0
  126. package/.versions/0.1.0/.opencode/commands/as-test.md +21 -0
  127. package/.versions/0.1.0/.pi/agents/agent-chain.yaml +49 -0
  128. package/.versions/0.1.0/.pi/agents/bowser.md +19 -0
  129. package/.versions/0.1.0/.pi/agents/pi-pi/agent-expert.md +98 -0
  130. package/.versions/0.1.0/.pi/agents/pi-pi/cli-expert.md +41 -0
  131. package/.versions/0.1.0/.pi/agents/pi-pi/config-expert.md +63 -0
  132. package/.versions/0.1.0/.pi/agents/pi-pi/ext-expert.md +43 -0
  133. package/.versions/0.1.0/.pi/agents/pi-pi/keybinding-expert.md +134 -0
  134. package/.versions/0.1.0/.pi/agents/pi-pi/pi-orchestrator.md +57 -0
  135. package/.versions/0.1.0/.pi/agents/pi-pi/prompt-expert.md +70 -0
  136. package/.versions/0.1.0/.pi/agents/pi-pi/skill-expert.md +42 -0
  137. package/.versions/0.1.0/.pi/agents/pi-pi/theme-expert.md +40 -0
  138. package/.versions/0.1.0/.pi/agents/pi-pi/tui-expert.md +85 -0
  139. package/.versions/0.1.0/.pi/agents/teams.yaml +31 -0
  140. package/.versions/0.1.0/.pi/damage-control-rules.yaml +278 -0
  141. package/.versions/0.1.0/.pi/extensions/chrome-devtools-mcp/README.md +39 -0
  142. package/.versions/0.1.0/.pi/extensions/chrome-devtools-mcp/index.ts +61 -0
  143. package/.versions/0.1.0/.pi/extensions/chrome-devtools-mcp/package.json +6 -0
  144. package/.versions/0.1.0/.pi/extensions/compact-and-continue/README.md +42 -0
  145. package/.versions/0.1.0/.pi/extensions/compact-and-continue/index.ts +120 -0
  146. package/.versions/0.1.0/.pi/extensions/compact-and-continue/package.json +6 -0
  147. package/.versions/0.1.0/.pi/extensions/mcp-bridge/README.md +46 -0
  148. package/.versions/0.1.0/.pi/extensions/mcp-bridge/index.ts +206 -0
  149. package/.versions/0.1.0/.pi/extensions/mcp-bridge/package.json +6 -0
  150. package/.versions/0.1.0/.pi/extensions/package-lock.json +1143 -0
  151. package/.versions/0.1.0/.pi/extensions/package.json +9 -0
  152. package/.versions/0.1.0/.pi/harnesses/agent-chain/README.md +37 -0
  153. package/.versions/0.1.0/.pi/harnesses/agent-chain/index.ts +795 -0
  154. package/.versions/0.1.0/.pi/harnesses/agent-chain/package.json +6 -0
  155. package/.versions/0.1.0/.pi/harnesses/agent-team/README.md +38 -0
  156. package/.versions/0.1.0/.pi/harnesses/agent-team/index.ts +732 -0
  157. package/.versions/0.1.0/.pi/harnesses/agent-team/package.json +6 -0
  158. package/.versions/0.1.0/.pi/harnesses/coms/README.md +36 -0
  159. package/.versions/0.1.0/.pi/harnesses/coms/index.ts +1595 -0
  160. package/.versions/0.1.0/.pi/harnesses/coms/package.json +6 -0
  161. package/.versions/0.1.0/.pi/harnesses/coms-net/README.md +46 -0
  162. package/.versions/0.1.0/.pi/harnesses/coms-net/index.ts +1637 -0
  163. package/.versions/0.1.0/.pi/harnesses/coms-net/package.json +6 -0
  164. package/.versions/0.1.0/.pi/harnesses/damage-control/README.md +38 -0
  165. package/.versions/0.1.0/.pi/harnesses/damage-control/index.ts +207 -0
  166. package/.versions/0.1.0/.pi/harnesses/damage-control/package.json +6 -0
  167. package/.versions/0.1.0/.pi/harnesses/damage-control-continue/README.md +37 -0
  168. package/.versions/0.1.0/.pi/harnesses/damage-control-continue/index.ts +234 -0
  169. package/.versions/0.1.0/.pi/harnesses/damage-control-continue/package.json +6 -0
  170. package/.versions/0.1.0/.pi/harnesses/minimal/README.md +27 -0
  171. package/.versions/0.1.0/.pi/harnesses/minimal/index.ts +32 -0
  172. package/.versions/0.1.0/.pi/harnesses/minimal/package.json +6 -0
  173. package/.versions/0.1.0/.pi/harnesses/package-lock.json +35 -0
  174. package/.versions/0.1.0/.pi/harnesses/package.json +9 -0
  175. package/.versions/0.1.0/.pi/harnesses/pi-pi/README.md +39 -0
  176. package/.versions/0.1.0/.pi/harnesses/pi-pi/index.ts +631 -0
  177. package/.versions/0.1.0/.pi/harnesses/pi-pi/package.json +6 -0
  178. package/.versions/0.1.0/.pi/harnesses/purpose-gate/README.md +27 -0
  179. package/.versions/0.1.0/.pi/harnesses/purpose-gate/index.ts +82 -0
  180. package/.versions/0.1.0/.pi/harnesses/purpose-gate/package.json +6 -0
  181. package/.versions/0.1.0/.pi/harnesses/session-replay/README.md +28 -0
  182. package/.versions/0.1.0/.pi/harnesses/session-replay/index.ts +214 -0
  183. package/.versions/0.1.0/.pi/harnesses/session-replay/package.json +6 -0
  184. package/.versions/0.1.0/.pi/harnesses/subagent-widget/README.md +36 -0
  185. package/.versions/0.1.0/.pi/harnesses/subagent-widget/index.ts +479 -0
  186. package/.versions/0.1.0/.pi/harnesses/subagent-widget/package.json +6 -0
  187. package/.versions/0.1.0/.pi/harnesses/system-select/README.md +39 -0
  188. package/.versions/0.1.0/.pi/harnesses/system-select/index.ts +165 -0
  189. package/.versions/0.1.0/.pi/harnesses/system-select/package.json +6 -0
  190. package/.versions/0.1.0/.pi/harnesses/tilldone/README.md +35 -0
  191. package/.versions/0.1.0/.pi/harnesses/tilldone/index.ts +724 -0
  192. package/.versions/0.1.0/.pi/harnesses/tilldone/package.json +6 -0
  193. package/.versions/0.1.0/.pi/harnesses/tool-counter/README.md +31 -0
  194. package/.versions/0.1.0/.pi/harnesses/tool-counter/index.ts +100 -0
  195. package/.versions/0.1.0/.pi/harnesses/tool-counter/package.json +6 -0
  196. package/.versions/0.1.0/.pi/harnesses/tool-counter-widget/README.md +27 -0
  197. package/.versions/0.1.0/.pi/harnesses/tool-counter-widget/index.ts +66 -0
  198. package/.versions/0.1.0/.pi/harnesses/tool-counter-widget/package.json +6 -0
  199. package/.versions/0.1.0/.pi/prompts/build.md +24 -0
  200. package/.versions/0.1.0/.pi/prompts/code-simplify.md +22 -0
  201. package/.versions/0.1.0/.pi/prompts/doctor.md +13 -0
  202. package/.versions/0.1.0/.pi/prompts/plan.md +16 -0
  203. package/.versions/0.1.0/.pi/prompts/review.md +16 -0
  204. package/.versions/0.1.0/.pi/prompts/setup.md +19 -0
  205. package/.versions/0.1.0/.pi/prompts/ship.md +17 -0
  206. package/.versions/0.1.0/.pi/prompts/spec.md +15 -0
  207. package/.versions/0.1.0/.pi/prompts/test.md +19 -0
  208. package/.versions/0.1.0/.pi/skills/bowser/SKILL.md +114 -0
  209. package/.versions/0.1.0/.version +1 -0
  210. package/.versions/0.1.0/agents/builder.md +6 -0
  211. package/.versions/0.1.0/agents/code-reviewer.md +93 -0
  212. package/.versions/0.1.0/agents/documenter.md +6 -0
  213. package/.versions/0.1.0/agents/plan-reviewer.md +22 -0
  214. package/.versions/0.1.0/agents/planner.md +6 -0
  215. package/.versions/0.1.0/agents/scout.md +6 -0
  216. package/.versions/0.1.0/agents/security-auditor.md +97 -0
  217. package/.versions/0.1.0/agents/test-engineer.md +89 -0
  218. package/.versions/0.1.0/hooks/SIMPLIFY-IGNORE.md +90 -0
  219. package/.versions/0.1.0/hooks/hooks.json +14 -0
  220. package/.versions/0.1.0/hooks/session-start.sh +20 -0
  221. package/.versions/0.1.0/hooks/simplify-ignore-test.sh +247 -0
  222. package/.versions/0.1.0/hooks/simplify-ignore.sh +302 -0
  223. package/.versions/0.1.0/references/accessibility-checklist.md +159 -0
  224. package/.versions/0.1.0/references/performance-checklist.md +121 -0
  225. package/.versions/0.1.0/references/prompting-patterns.md +380 -0
  226. package/.versions/0.1.0/references/security-checklist.md +134 -0
  227. package/.versions/0.1.0/references/testing-patterns.md +236 -0
  228. package/.versions/0.1.0/skills/api-and-interface-design/SKILL.md +294 -0
  229. package/.versions/0.1.0/skills/browser-testing-with-devtools/SKILL.md +335 -0
  230. package/.versions/0.1.0/skills/ci-cd-and-automation/SKILL.md +390 -0
  231. package/.versions/0.1.0/skills/code-review-and-quality/SKILL.md +347 -0
  232. package/.versions/0.1.0/skills/code-simplification/SKILL.md +331 -0
  233. package/.versions/0.1.0/skills/context-engineering/SKILL.md +291 -0
  234. package/.versions/0.1.0/skills/debugging-and-error-recovery/SKILL.md +300 -0
  235. package/.versions/0.1.0/skills/deprecation-and-migration/SKILL.md +206 -0
  236. package/.versions/0.1.0/skills/designing-agents/SKILL.md +394 -0
  237. package/.versions/0.1.0/skills/designing-agents/pi-harness-authoring.md +213 -0
  238. package/.versions/0.1.0/skills/documentation-and-adrs/SKILL.md +278 -0
  239. package/.versions/0.1.0/skills/frontend-ui-engineering/SKILL.md +322 -0
  240. package/.versions/0.1.0/skills/git-workflow-and-versioning/SKILL.md +316 -0
  241. package/.versions/0.1.0/skills/guided-workspace-setup/SKILL.md +293 -0
  242. package/.versions/0.1.0/skills/idea-refine/SKILL.md +178 -0
  243. package/.versions/0.1.0/skills/idea-refine/examples.md +238 -0
  244. package/.versions/0.1.0/skills/idea-refine/frameworks.md +99 -0
  245. package/.versions/0.1.0/skills/idea-refine/refinement-criteria.md +113 -0
  246. package/.versions/0.1.0/skills/idea-refine/scripts/idea-refine.sh +15 -0
  247. package/.versions/0.1.0/skills/incremental-implementation/SKILL.md +279 -0
  248. package/.versions/0.1.0/skills/performance-optimization/SKILL.md +350 -0
  249. package/.versions/0.1.0/skills/planning-and-task-breakdown/SKILL.md +237 -0
  250. package/.versions/0.1.0/skills/security-and-hardening/SKILL.md +349 -0
  251. package/.versions/0.1.0/skills/shipping-and-launch/SKILL.md +309 -0
  252. package/.versions/0.1.0/skills/source-driven-development/SKILL.md +194 -0
  253. package/.versions/0.1.0/skills/spec-driven-development/SKILL.md +237 -0
  254. package/.versions/0.1.0/skills/test-driven-development/SKILL.md +379 -0
  255. package/.versions/0.1.0/skills/using-agent-skills/SKILL.md +176 -0
  256. package/CHANGELOG.md +14 -0
  257. package/LICENSE +21 -0
  258. package/README.md +359 -0
  259. package/agents/builder.md +6 -0
  260. package/agents/code-reviewer.md +93 -0
  261. package/agents/documenter.md +6 -0
  262. package/agents/plan-reviewer.md +22 -0
  263. package/agents/planner.md +6 -0
  264. package/agents/scout.md +6 -0
  265. package/agents/security-auditor.md +97 -0
  266. package/agents/test-engineer.md +89 -0
  267. package/bin/cli.js +375 -0
  268. package/bin/lib/detect-agent.js +37 -0
  269. package/bin/lib/doctor.js +209 -0
  270. package/bin/snapshot-version.js +71 -0
  271. package/docs/agent-skills-setup.md +187 -0
  272. package/docs/copilot-setup.md +94 -0
  273. package/docs/cursor-setup.md +67 -0
  274. package/docs/gemini-cli-setup.md +113 -0
  275. package/docs/getting-started.md +162 -0
  276. package/docs/npm-install.md +169 -0
  277. package/docs/opencode-setup.md +241 -0
  278. package/docs/pi-extensions.md +163 -0
  279. package/docs/pi-setup.md +416 -0
  280. package/docs/skill-anatomy.md +129 -0
  281. package/docs/windsurf-setup.md +48 -0
  282. package/hooks/SIMPLIFY-IGNORE.md +90 -0
  283. package/hooks/hooks.json +14 -0
  284. package/hooks/session-start.sh +20 -0
  285. package/hooks/simplify-ignore-test.sh +247 -0
  286. package/hooks/simplify-ignore.sh +302 -0
  287. package/package.json +86 -0
  288. package/references/accessibility-checklist.md +159 -0
  289. package/references/performance-checklist.md +121 -0
  290. package/references/prompting-patterns.md +380 -0
  291. package/references/security-checklist.md +134 -0
  292. package/references/testing-patterns.md +236 -0
  293. package/skills/api-and-interface-design/SKILL.md +294 -0
  294. package/skills/browser-testing-with-devtools/SKILL.md +335 -0
  295. package/skills/ci-cd-and-automation/SKILL.md +390 -0
  296. package/skills/code-review-and-quality/SKILL.md +347 -0
  297. package/skills/code-simplification/SKILL.md +331 -0
  298. package/skills/context-engineering/SKILL.md +291 -0
  299. package/skills/debugging-and-error-recovery/SKILL.md +300 -0
  300. package/skills/deprecation-and-migration/SKILL.md +206 -0
  301. package/skills/designing-agents/SKILL.md +394 -0
  302. package/skills/designing-agents/pi-harness-authoring.md +213 -0
  303. package/skills/documentation-and-adrs/SKILL.md +278 -0
  304. package/skills/frontend-ui-engineering/SKILL.md +322 -0
  305. package/skills/git-workflow-and-versioning/SKILL.md +316 -0
  306. package/skills/guided-workspace-setup/SKILL.md +293 -0
  307. package/skills/idea-refine/SKILL.md +178 -0
  308. package/skills/idea-refine/examples.md +238 -0
  309. package/skills/idea-refine/frameworks.md +99 -0
  310. package/skills/idea-refine/refinement-criteria.md +113 -0
  311. package/skills/idea-refine/scripts/idea-refine.sh +15 -0
  312. package/skills/incremental-implementation/SKILL.md +279 -0
  313. package/skills/performance-optimization/SKILL.md +350 -0
  314. package/skills/planning-and-task-breakdown/SKILL.md +237 -0
  315. package/skills/security-and-hardening/SKILL.md +349 -0
  316. package/skills/shipping-and-launch/SKILL.md +309 -0
  317. package/skills/source-driven-development/SKILL.md +194 -0
  318. package/skills/spec-driven-development/SKILL.md +237 -0
  319. package/skills/test-driven-development/SKILL.md +379 -0
  320. package/skills/using-agent-skills/SKILL.md +176 -0
@@ -0,0 +1,724 @@
1
+ /**
2
+ * TillDone Extension — Work Till It's Done
3
+ *
4
+ * A task-driven discipline extension. The agent MUST define what it's going
5
+ * to do (via `tilldone add`) before it can use any other tools. On agent
6
+ * completion, if tasks remain incomplete, the agent gets nudged to continue
7
+ * or mark them done. Play on words: "todo" → "tilldone" (work till done).
8
+ *
9
+ * Three-state lifecycle: idle → inprogress → done
10
+ *
11
+ * Each list has a title and description that give the tasks a theme.
12
+ * Use `new-list` to start a fresh list. `clear` wipes tasks with user confirm.
13
+ *
14
+ * UI surfaces:
15
+ * - Footer: persistent task list with live progress + list title
16
+ * - Widget: prominent "current task" display (the inprogress task)
17
+ * - Status: compact summary in the status line
18
+ * - /tilldone: interactive overlay with full task details
19
+ *
20
+ * Usage: pi -e extensions/tilldone.ts
21
+ */
22
+
23
+ import { StringEnum } from "@mariozechner/pi-ai";
24
+ import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
25
+ import { DynamicBorder } from "@mariozechner/pi-coding-agent";
26
+ import { Container, matchesKey, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
27
+ import { Type } from "@sinclair/typebox";
28
+
29
+ // ── Types ──────────────────────────────────────────────────────────────
30
+
31
+ type TaskStatus = "idle" | "inprogress" | "done";
32
+
33
+ interface Task {
34
+ id: number;
35
+ text: string;
36
+ status: TaskStatus;
37
+ }
38
+
39
+ interface TillDoneDetails {
40
+ action: string;
41
+ tasks: Task[];
42
+ nextId: number;
43
+ listTitle?: string;
44
+ listDescription?: string;
45
+ error?: string;
46
+ }
47
+
48
+ const TillDoneParams = Type.Object({
49
+ action: StringEnum(["new-list", "add", "toggle", "remove", "update", "list", "clear"] as const),
50
+ text: Type.Optional(Type.String({ description: "Task text (for add/update), or list title (for new-list)" })),
51
+ texts: Type.Optional(Type.Array(Type.String(), { description: "Multiple task texts (for add). Use this to batch-add several tasks at once." })),
52
+ description: Type.Optional(Type.String({ description: "List description (for new-list)" })),
53
+ id: Type.Optional(Type.Number({ description: "Task ID (for toggle/remove/update)" })),
54
+ });
55
+
56
+ // ── Status helpers ─────────────────────────────────────────────────────
57
+
58
+ const STATUS_ICON: Record<TaskStatus, string> = { idle: "○", inprogress: "●", done: "✓" };
59
+ const NEXT_STATUS: Record<TaskStatus, TaskStatus> = { idle: "inprogress", inprogress: "done", done: "idle" };
60
+ const STATUS_LABEL: Record<TaskStatus, string> = { idle: "idle", inprogress: "in progress", done: "done" };
61
+
62
+ // ── /tilldone overlay component ────────────────────────────────────────
63
+
64
+ class TillDoneListComponent {
65
+ private tasks: Task[];
66
+ private title: string | undefined;
67
+ private desc: string | undefined;
68
+ private theme: Theme;
69
+ private onClose: () => void;
70
+ private cachedWidth?: number;
71
+ private cachedLines?: string[];
72
+
73
+ constructor(tasks: Task[], title: string | undefined, desc: string | undefined, theme: Theme, onClose: () => void) {
74
+ this.tasks = tasks;
75
+ this.title = title;
76
+ this.desc = desc;
77
+ this.theme = theme;
78
+ this.onClose = onClose;
79
+ }
80
+
81
+ handleInput(data: string): void {
82
+ if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
83
+ this.onClose();
84
+ }
85
+ }
86
+
87
+ render(width: number): string[] {
88
+ if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
89
+
90
+ const lines: string[] = [];
91
+ const th = this.theme;
92
+
93
+ lines.push("");
94
+ const heading = this.title
95
+ ? th.fg("accent", ` ${this.title} `)
96
+ : th.fg("accent", " TillDone ");
97
+ const headingLen = this.title ? this.title.length + 2 : 10;
98
+ lines.push(truncateToWidth(
99
+ th.fg("borderMuted", "─".repeat(3)) + heading +
100
+ th.fg("borderMuted", "─".repeat(Math.max(0, width - 3 - headingLen))),
101
+ width,
102
+ ));
103
+
104
+ if (this.desc) {
105
+ lines.push(truncateToWidth(` ${th.fg("muted", this.desc)}`, width));
106
+ }
107
+ lines.push("");
108
+
109
+ if (this.tasks.length === 0) {
110
+ lines.push(truncateToWidth(` ${th.fg("dim", "No tasks yet. Ask the agent to add some!")}`, width));
111
+ } else {
112
+ const done = this.tasks.filter((t) => t.status === "done").length;
113
+ const active = this.tasks.filter((t) => t.status === "inprogress").length;
114
+ const idle = this.tasks.filter((t) => t.status === "idle").length;
115
+
116
+ lines.push(truncateToWidth(
117
+ " " +
118
+ th.fg("success", `${done} done`) + th.fg("dim", " ") +
119
+ th.fg("accent", `${active} active`) + th.fg("dim", " ") +
120
+ th.fg("muted", `${idle} idle`),
121
+ width,
122
+ ));
123
+ lines.push("");
124
+
125
+ for (const task of this.tasks) {
126
+ const icon = task.status === "done"
127
+ ? th.fg("success", STATUS_ICON.done)
128
+ : task.status === "inprogress"
129
+ ? th.fg("accent", STATUS_ICON.inprogress)
130
+ : th.fg("dim", STATUS_ICON.idle);
131
+ const id = th.fg("accent", `#${task.id}`);
132
+ const text = task.status === "done"
133
+ ? th.fg("dim", task.text)
134
+ : task.status === "inprogress"
135
+ ? th.fg("success", task.text)
136
+ : th.fg("muted", task.text);
137
+ lines.push(truncateToWidth(` ${icon} ${id} ${text}`, width));
138
+ }
139
+ }
140
+
141
+ lines.push("");
142
+ lines.push(truncateToWidth(` ${th.fg("dim", "Press Escape to close")}`, width));
143
+ lines.push("");
144
+
145
+ this.cachedWidth = width;
146
+ this.cachedLines = lines;
147
+ return lines;
148
+ }
149
+
150
+ invalidate(): void {
151
+ this.cachedWidth = undefined;
152
+ this.cachedLines = undefined;
153
+ }
154
+ }
155
+
156
+ // ── Extension entry point ──────────────────────────────────────────────
157
+
158
+ export default function (pi: ExtensionAPI) {
159
+ let tasks: Task[] = [];
160
+ let nextId = 1;
161
+ let listTitle: string | undefined;
162
+ let listDescription: string | undefined;
163
+ let nudgedThisCycle = false;
164
+
165
+ // ── Snapshot for details ───────────────────────────────────────────
166
+
167
+ const makeDetails = (action: string, error?: string): TillDoneDetails => ({
168
+ action,
169
+ tasks: [...tasks],
170
+ nextId,
171
+ listTitle,
172
+ listDescription,
173
+ ...(error ? { error } : {}),
174
+ });
175
+
176
+ // ── UI refresh ─────────────────────────────────────────────────────
177
+
178
+ const refreshWidget = (ctx: ExtensionContext) => {
179
+ const current = tasks.find((t) => t.status === "inprogress");
180
+
181
+ if (!current) {
182
+ ctx.ui.setWidget("tilldone-current", undefined);
183
+ return;
184
+ }
185
+
186
+ ctx.ui.setWidget("tilldone-current", (_tui, theme) => {
187
+ const container = new Container();
188
+ const borderFn = (s: string) => theme.fg("dim", s);
189
+
190
+ container.addChild(new Text("", 0, 0));
191
+ container.addChild(new DynamicBorder(borderFn));
192
+ const content = new Text("", 1, 0);
193
+ container.addChild(content);
194
+ container.addChild(new DynamicBorder(borderFn));
195
+
196
+ return {
197
+ render(width: number): string[] {
198
+ const cur = tasks.find((t) => t.status === "inprogress");
199
+ if (!cur) return [];
200
+
201
+ const line =
202
+ theme.fg("accent", "● ") +
203
+ theme.fg("dim", "WORKING ON ") +
204
+ theme.fg("accent", `#${cur.id}`) +
205
+ theme.fg("dim", " ") +
206
+ theme.fg("success", cur.text);
207
+
208
+ content.setText(truncateToWidth(line, width - 4));
209
+ return container.render(width);
210
+ },
211
+ invalidate() { container.invalidate(); },
212
+ };
213
+ }, { placement: "belowEditor" });
214
+ };
215
+
216
+ const refreshFooter = (ctx: ExtensionContext) => {
217
+ ctx.ui.setFooter((tui, theme, footerData) => {
218
+ const unsub = footerData.onBranchChange(() => tui.requestRender());
219
+
220
+ return {
221
+ dispose: unsub,
222
+ invalidate() {},
223
+ render(width: number): string[] {
224
+ const done = tasks.filter((t) => t.status === "done").length;
225
+ const active = tasks.filter((t) => t.status === "inprogress").length;
226
+ const idle = tasks.filter((t) => t.status === "idle").length;
227
+ const total = tasks.length;
228
+
229
+ // ── Line 1: list title + progress (left), counts (right) ──
230
+ const titleDisplay = listTitle
231
+ ? theme.fg("accent", ` ${listTitle} `)
232
+ : theme.fg("dim", " TillDone ");
233
+
234
+ const l1Left = total === 0
235
+ ? titleDisplay + theme.fg("muted", "no tasks")
236
+ : titleDisplay +
237
+ theme.fg("warning", "[") +
238
+ theme.fg("success", `${done}`) +
239
+ theme.fg("dim", "/") +
240
+ theme.fg("success", `${total}`) +
241
+ theme.fg("warning", "]");
242
+
243
+ const l1Right = total === 0
244
+ ? ""
245
+ : theme.fg("dim", STATUS_ICON.idle + " ") + theme.fg("muted", `${idle}`) +
246
+ theme.fg("dim", " ") +
247
+ theme.fg("accent", STATUS_ICON.inprogress + " ") + theme.fg("accent", `${active}`) +
248
+ theme.fg("dim", " ") +
249
+ theme.fg("success", STATUS_ICON.done + " ") + theme.fg("success", `${done}`) +
250
+ theme.fg("dim", " ");
251
+
252
+ const pad1 = " ".repeat(Math.max(1, width - visibleWidth(l1Left) - visibleWidth(l1Right)));
253
+ const line1 = truncateToWidth(l1Left + pad1 + l1Right, width, "");
254
+
255
+ if (total === 0) return [line1];
256
+
257
+ // ── Rows: inprogress first, then most recent done, max 5 ──
258
+ const activeTasks = tasks.filter((t) => t.status === "inprogress");
259
+ const doneTasks = tasks.filter((t) => t.status === "done").reverse();
260
+ const visible = [...activeTasks, ...doneTasks].slice(0, 5);
261
+ const remaining = total - visible.length;
262
+
263
+ const rows = visible.map((t) => {
264
+ const icon = t.status === "done"
265
+ ? theme.fg("success", STATUS_ICON.done)
266
+ : theme.fg("accent", STATUS_ICON.inprogress);
267
+ const text = t.status === "done"
268
+ ? theme.fg("dim", t.text)
269
+ : theme.fg("success", t.text);
270
+ return truncateToWidth(` ${icon} ${text}`, width, "");
271
+ });
272
+
273
+ if (remaining > 0) {
274
+ rows.push(truncateToWidth(
275
+ ` ${theme.fg("dim", ` +${remaining} more`)}`,
276
+ width, "",
277
+ ));
278
+ }
279
+
280
+ return [line1, ...rows];
281
+ },
282
+ };
283
+ });
284
+ };
285
+
286
+ const refreshUI = (ctx: ExtensionContext) => {
287
+ if (tasks.length === 0) {
288
+ ctx.ui.setStatus("📋 TillDone: no tasks", "tilldone");
289
+ } else {
290
+ const remaining = tasks.filter((t) => t.status !== "done").length;
291
+ const label = listTitle ? `📋 ${listTitle}` : "📋 TillDone";
292
+ ctx.ui.setStatus(`${label}: ${tasks.length} tasks (${remaining} remaining)`, "tilldone");
293
+ }
294
+
295
+ refreshWidget(ctx);
296
+ refreshFooter(ctx);
297
+ };
298
+
299
+ // ── State reconstruction from session ──────────────────────────────
300
+
301
+ const reconstructState = (ctx: ExtensionContext) => {
302
+ tasks = [];
303
+ nextId = 1;
304
+ listTitle = undefined;
305
+ listDescription = undefined;
306
+
307
+ for (const entry of ctx.sessionManager.getBranch()) {
308
+ if (entry.type !== "message") continue;
309
+ const msg = entry.message;
310
+ if (msg.role !== "toolResult" || msg.toolName !== "tilldone") continue;
311
+
312
+ const details = msg.details as TillDoneDetails | undefined;
313
+ if (details) {
314
+ tasks = details.tasks;
315
+ nextId = details.nextId;
316
+ listTitle = details.listTitle;
317
+ listDescription = details.listDescription;
318
+ }
319
+ }
320
+
321
+ refreshUI(ctx);
322
+ };
323
+
324
+ pi.on("session_start", async (_event, ctx) => {
325
+ reconstructState(ctx);
326
+ });
327
+ pi.on("session_switch", async (_event, ctx) => reconstructState(ctx));
328
+ pi.on("session_fork", async (_event, ctx) => reconstructState(ctx));
329
+ pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
330
+
331
+ // ── Blocking gate ──────────────────────────────────────────────────
332
+
333
+ pi.on("tool_call", async (event, _ctx) => {
334
+ if (event.toolName === "tilldone") return { block: false };
335
+
336
+ const pending = tasks.filter((t) => t.status !== "done");
337
+ const active = tasks.filter((t) => t.status === "inprogress");
338
+
339
+ if (tasks.length === 0) {
340
+ return {
341
+ block: true,
342
+ reason: "🚫 No TillDone tasks defined. You MUST use `tilldone new-list` or `tilldone add` to define your tasks before using any other tools. Plan your work first!",
343
+ };
344
+ }
345
+ if (pending.length === 0) {
346
+ return {
347
+ block: true,
348
+ reason: "🚫 All TillDone tasks are done. You MUST use `tilldone add` for new tasks or `tilldone new-list` to start a fresh list before using any other tools.",
349
+ };
350
+ }
351
+ if (active.length === 0) {
352
+ return {
353
+ block: true,
354
+ reason: "🚫 No task is in progress. You MUST use `tilldone toggle` to mark a task as inprogress before doing any work.",
355
+ };
356
+ }
357
+
358
+ return { block: false };
359
+ });
360
+
361
+ // ── Auto-nudge on agent_end ────────────────────────────────────────
362
+
363
+ pi.on("agent_end", async (_event, _ctx) => {
364
+ const incomplete = tasks.filter((t) => t.status !== "done");
365
+ if (incomplete.length === 0 || nudgedThisCycle) return;
366
+
367
+ nudgedThisCycle = true;
368
+
369
+ const taskList = incomplete
370
+ .map((t) => ` ${STATUS_ICON[t.status]} #${t.id} [${STATUS_LABEL[t.status]}]: ${t.text}`)
371
+ .join("\n");
372
+
373
+ pi.sendMessage(
374
+ {
375
+ customType: "tilldone-nudge",
376
+ content: `⚠️ You still have ${incomplete.length} incomplete task(s):\n\n${taskList}\n\nEither continue working on them or mark them done with \`tilldone toggle\`. Don't stop until it's done!`,
377
+ display: true,
378
+ },
379
+ { triggerTurn: true },
380
+ );
381
+ });
382
+
383
+ pi.on("input", async () => {
384
+ nudgedThisCycle = false;
385
+ return { action: "continue" as const };
386
+ });
387
+
388
+ // ── Register tilldone tool ─────────────────────────────────────────
389
+
390
+ pi.registerTool({
391
+ name: "tilldone",
392
+ label: "TillDone",
393
+ description:
394
+ "Manage your task list. You MUST add tasks before using any other tools. " +
395
+ "Actions: new-list (text=title, description), add (text or texts[] for batch), toggle (id) — cycles idle→inprogress→done, remove (id), update (id + text), list, clear. " +
396
+ "Always toggle a task to inprogress before starting work on it, and to done when finished. " +
397
+ "Use new-list to start a themed list with a title and description. " +
398
+ "IMPORTANT: If the user's new request does not fit the current list's theme, use clear to wipe the slate and new-list to start fresh.",
399
+ parameters: TillDoneParams,
400
+
401
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
402
+ switch (params.action) {
403
+ case "new-list": {
404
+ if (!params.text) {
405
+ return {
406
+ content: [{ type: "text" as const, text: "Error: text (title) required for new-list" }],
407
+ details: makeDetails("new-list", "text required"),
408
+ };
409
+ }
410
+
411
+ // If a list already exists, confirm before replacing
412
+ if (tasks.length > 0 || listTitle) {
413
+ const confirmed = await ctx.ui.confirm(
414
+ "Start a new list?",
415
+ `This will replace${listTitle ? ` "${listTitle}"` : " the current list"} (${tasks.length} task(s)). Continue?`,
416
+ { timeout: 30000 },
417
+ );
418
+ if (!confirmed) {
419
+ return {
420
+ content: [{ type: "text" as const, text: "New list cancelled by user." }],
421
+ details: makeDetails("new-list", "cancelled"),
422
+ };
423
+ }
424
+ }
425
+
426
+ tasks = [];
427
+ nextId = 1;
428
+ listTitle = params.text;
429
+ listDescription = params.description || undefined;
430
+
431
+ const result = {
432
+ content: [{
433
+ type: "text" as const,
434
+ text: `New list: "${listTitle}"${listDescription ? ` — ${listDescription}` : ""}`,
435
+ }],
436
+ details: makeDetails("new-list"),
437
+ };
438
+ refreshUI(ctx);
439
+ return result;
440
+ }
441
+
442
+ case "list": {
443
+ const header = listTitle ? `${listTitle}:` : "";
444
+ const result = {
445
+ content: [{
446
+ type: "text" as const,
447
+ text: tasks.length
448
+ ? (header ? header + "\n" : "") +
449
+ tasks.map((t) => `[${STATUS_ICON[t.status]}] #${t.id} (${t.status}): ${t.text}`).join("\n")
450
+ : "No tasks defined yet.",
451
+ }],
452
+ details: makeDetails("list"),
453
+ };
454
+ refreshUI(ctx);
455
+ return result;
456
+ }
457
+
458
+ case "add": {
459
+ const items = params.texts?.length ? params.texts : params.text ? [params.text] : [];
460
+ if (items.length === 0) {
461
+ return {
462
+ content: [{ type: "text" as const, text: "Error: text or texts required for add" }],
463
+ details: makeDetails("add", "text required"),
464
+ };
465
+ }
466
+ const added: Task[] = [];
467
+ for (const item of items) {
468
+ const t: Task = { id: nextId++, text: item, status: "idle" };
469
+ tasks.push(t);
470
+ added.push(t);
471
+ }
472
+ const msg = added.length === 1
473
+ ? `Added task #${added[0].id}: ${added[0].text}`
474
+ : `Added ${added.length} tasks: ${added.map((t) => `#${t.id}`).join(", ")}`;
475
+ const result = {
476
+ content: [{ type: "text" as const, text: msg }],
477
+ details: makeDetails("add"),
478
+ };
479
+ refreshUI(ctx);
480
+ return result;
481
+ }
482
+
483
+ case "toggle": {
484
+ if (params.id === undefined) {
485
+ return {
486
+ content: [{ type: "text" as const, text: "Error: id required for toggle" }],
487
+ details: makeDetails("toggle", "id required"),
488
+ };
489
+ }
490
+ const task = tasks.find((t) => t.id === params.id);
491
+ if (!task) {
492
+ return {
493
+ content: [{ type: "text" as const, text: `Task #${params.id} not found` }],
494
+ details: makeDetails("toggle", `#${params.id} not found`),
495
+ };
496
+ }
497
+ const prev = task.status;
498
+ task.status = NEXT_STATUS[task.status];
499
+
500
+ // Enforce single inprogress — demote any other active task
501
+ const demoted: Task[] = [];
502
+ if (task.status === "inprogress") {
503
+ for (const t of tasks) {
504
+ if (t.id !== task.id && t.status === "inprogress") {
505
+ t.status = "idle";
506
+ demoted.push(t);
507
+ }
508
+ }
509
+ }
510
+
511
+ let msg = `Task #${task.id}: ${prev} → ${task.status}`;
512
+ if (demoted.length > 0) {
513
+ msg += `\n(Auto-paused ${demoted.map((t) => `#${t.id}`).join(", ")} → idle. Only one task can be in progress at a time.)`;
514
+ }
515
+
516
+ const result = {
517
+ content: [{
518
+ type: "text" as const,
519
+ text: msg,
520
+ }],
521
+ details: makeDetails("toggle"),
522
+ };
523
+ refreshUI(ctx);
524
+ return result;
525
+ }
526
+
527
+ case "remove": {
528
+ if (params.id === undefined) {
529
+ return {
530
+ content: [{ type: "text" as const, text: "Error: id required for remove" }],
531
+ details: makeDetails("remove", "id required"),
532
+ };
533
+ }
534
+ const idx = tasks.findIndex((t) => t.id === params.id);
535
+ if (idx === -1) {
536
+ return {
537
+ content: [{ type: "text" as const, text: `Task #${params.id} not found` }],
538
+ details: makeDetails("remove", `#${params.id} not found`),
539
+ };
540
+ }
541
+ const removed = tasks.splice(idx, 1)[0];
542
+ const result = {
543
+ content: [{ type: "text" as const, text: `Removed task #${removed.id}: ${removed.text}` }],
544
+ details: makeDetails("remove"),
545
+ };
546
+ refreshUI(ctx);
547
+ return result;
548
+ }
549
+
550
+ case "update": {
551
+ if (params.id === undefined) {
552
+ return {
553
+ content: [{ type: "text" as const, text: "Error: id required for update" }],
554
+ details: makeDetails("update", "id required"),
555
+ };
556
+ }
557
+ if (!params.text) {
558
+ return {
559
+ content: [{ type: "text" as const, text: "Error: text required for update" }],
560
+ details: makeDetails("update", "text required"),
561
+ };
562
+ }
563
+ const toUpdate = tasks.find((t) => t.id === params.id);
564
+ if (!toUpdate) {
565
+ return {
566
+ content: [{ type: "text" as const, text: `Task #${params.id} not found` }],
567
+ details: makeDetails("update", `#${params.id} not found`),
568
+ };
569
+ }
570
+ const oldText = toUpdate.text;
571
+ toUpdate.text = params.text;
572
+ const result = {
573
+ content: [{ type: "text" as const, text: `Updated #${toUpdate.id}: "${oldText}" → "${toUpdate.text}"` }],
574
+ details: makeDetails("update"),
575
+ };
576
+ refreshUI(ctx);
577
+ return result;
578
+ }
579
+
580
+ case "clear": {
581
+ if (tasks.length > 0) {
582
+ const confirmed = await ctx.ui.confirm(
583
+ "Clear TillDone list?",
584
+ `This will remove all ${tasks.length} task(s)${listTitle ? ` from "${listTitle}"` : ""}. Continue?`,
585
+ { timeout: 30000 },
586
+ );
587
+ if (!confirmed) {
588
+ return {
589
+ content: [{ type: "text" as const, text: "Clear cancelled by user." }],
590
+ details: makeDetails("clear", "cancelled"),
591
+ };
592
+ }
593
+ }
594
+
595
+ const count = tasks.length;
596
+ tasks = [];
597
+ nextId = 1;
598
+ listTitle = undefined;
599
+ listDescription = undefined;
600
+
601
+ const result = {
602
+ content: [{ type: "text" as const, text: `Cleared ${count} task(s)` }],
603
+ details: makeDetails("clear"),
604
+ };
605
+ refreshUI(ctx);
606
+ return result;
607
+ }
608
+
609
+ default:
610
+ return {
611
+ content: [{ type: "text" as const, text: `Unknown action: ${params.action}` }],
612
+ details: makeDetails("list", `unknown action: ${params.action}`),
613
+ };
614
+ }
615
+ },
616
+
617
+ renderCall(args, theme) {
618
+ let text = theme.fg("toolTitle", theme.bold("tilldone ")) + theme.fg("muted", args.action);
619
+ if (args.texts?.length) text += ` ${theme.fg("dim", `${args.texts.length} tasks`)}`;
620
+ else if (args.text) text += ` ${theme.fg("dim", `"${args.text}"`)}`;
621
+ if (args.description) text += ` ${theme.fg("dim", `— ${args.description}`)}`;
622
+ if (args.id !== undefined) text += ` ${theme.fg("accent", `#${args.id}`)}`;
623
+ return new Text(text, 0, 0);
624
+ },
625
+
626
+ renderResult(result, { expanded }, theme) {
627
+ const details = result.details as TillDoneDetails | undefined;
628
+ if (!details) {
629
+ const text = result.content[0];
630
+ return new Text(text?.type === "text" ? text.text : "", 0, 0);
631
+ }
632
+
633
+ if (details.error) {
634
+ return new Text(theme.fg("error", `Error: ${details.error}`), 0, 0);
635
+ }
636
+
637
+ const taskList = details.tasks;
638
+
639
+ switch (details.action) {
640
+ case "new-list": {
641
+ let msg = theme.fg("success", "✓ New list ") + theme.fg("accent", `"${details.listTitle}"`);
642
+ if (details.listDescription) {
643
+ msg += theme.fg("dim", ` — ${details.listDescription}`);
644
+ }
645
+ return new Text(msg, 0, 0);
646
+ }
647
+
648
+ case "list": {
649
+ if (taskList.length === 0) return new Text(theme.fg("dim", "No tasks"), 0, 0);
650
+
651
+ let listText = "";
652
+ if (details.listTitle) {
653
+ listText += theme.fg("accent", details.listTitle) + theme.fg("dim", " ");
654
+ }
655
+ listText += theme.fg("muted", `${taskList.length} task(s):`);
656
+ const display = expanded ? taskList : taskList.slice(0, 5);
657
+ for (const t of display) {
658
+ const icon = t.status === "done"
659
+ ? theme.fg("success", STATUS_ICON.done)
660
+ : t.status === "inprogress"
661
+ ? theme.fg("accent", STATUS_ICON.inprogress)
662
+ : theme.fg("dim", STATUS_ICON.idle);
663
+ const itemText = t.status === "done"
664
+ ? theme.fg("dim", t.text)
665
+ : t.status === "inprogress"
666
+ ? theme.fg("success", t.text)
667
+ : theme.fg("muted", t.text);
668
+ listText += `\n${icon} ${theme.fg("accent", `#${t.id}`)} ${itemText}`;
669
+ }
670
+ if (!expanded && taskList.length > 5) {
671
+ listText += `\n${theme.fg("dim", `... ${taskList.length - 5} more`)}`;
672
+ }
673
+ return new Text(listText, 0, 0);
674
+ }
675
+
676
+ case "add": {
677
+ const text = result.content[0];
678
+ const msg = text?.type === "text" ? text.text : "";
679
+ return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0);
680
+ }
681
+
682
+ case "toggle": {
683
+ const text = result.content[0];
684
+ const msg = text?.type === "text" ? text.text : "";
685
+ return new Text(theme.fg("accent", "⟳ ") + theme.fg("muted", msg), 0, 0);
686
+ }
687
+
688
+ case "remove": {
689
+ const text = result.content[0];
690
+ const msg = text?.type === "text" ? text.text : "";
691
+ return new Text(theme.fg("warning", "✕ ") + theme.fg("muted", msg), 0, 0);
692
+ }
693
+
694
+ case "update": {
695
+ const text = result.content[0];
696
+ const msg = text?.type === "text" ? text.text : "";
697
+ return new Text(theme.fg("success", "✓ ") + theme.fg("muted", msg), 0, 0);
698
+ }
699
+
700
+ case "clear":
701
+ return new Text(theme.fg("success", "✓ ") + theme.fg("muted", "Cleared all tasks"), 0, 0);
702
+
703
+ default:
704
+ return new Text(theme.fg("dim", "done"), 0, 0);
705
+ }
706
+ },
707
+ });
708
+
709
+ // ── /tilldone command ──────────────────────────────────────────────
710
+
711
+ pi.registerCommand("tilldone", {
712
+ description: "Show all TillDone tasks on the current branch",
713
+ handler: async (_args, ctx) => {
714
+ if (!ctx.hasUI) {
715
+ ctx.ui.notify("/tilldone requires interactive mode", "error");
716
+ return;
717
+ }
718
+
719
+ await ctx.ui.custom<void>((_tui, theme, _kb, done) => {
720
+ return new TillDoneListComponent(tasks, listTitle, listDescription, theme, () => done());
721
+ });
722
+ },
723
+ });
724
+ }