Compare commits

..

311 Commits

Author SHA1 Message Date
ezynda3 6750a556ad Deploying to gh-pages from @ mark3labs/kit@cea82ea2d0 🚀 2026-06-18 16:38:23 +00:00
ezynda3 ce26794cce Deploying to gh-pages from @ mark3labs/kit@dd7ae41a58 🚀 2026-06-18 15:19:30 +00:00
ezynda3 baff270e72 Deploying to gh-pages from @ mark3labs/kit@bd56f4a089 🚀 2026-06-18 11:46:36 +00:00
ezynda3 b7f6ec8caf Deploying to gh-pages from @ mark3labs/kit@888c6c7953 🚀 2026-06-18 09:42:40 +00:00
ezynda3 4b533d69ef Deploying to gh-pages from @ mark3labs/kit@a9d808eb9f 🚀 2026-06-18 09:38:10 +00:00
ezynda3 6f1a3309ee Deploying to gh-pages from @ mark3labs/kit@d7948a64f3 🚀 2026-06-18 09:34:17 +00:00
ezynda3 665bca2c5f Deploying to gh-pages from @ mark3labs/kit@d2e2e5e9b3 🚀 2026-06-17 14:18:18 +00:00
ezynda3 4ae165c21e Deploying to gh-pages from @ mark3labs/kit@2c05280150 🚀 2026-06-17 14:16:56 +00:00
ezynda3 1fda7ac9b8 Deploying to gh-pages from @ mark3labs/kit@6a1b061d06 🚀 2026-06-16 11:21:32 +00:00
ezynda3 834a861724 Deploying to gh-pages from @ mark3labs/kit@3a35bc5cec 🚀 2026-06-16 11:11:00 +00:00
ezynda3 a8af4369d2 Deploying to gh-pages from @ mark3labs/kit@08c3d0fe3a 🚀 2026-06-16 10:29:41 +00:00
ezynda3 87f6aee896 Deploying to gh-pages from @ mark3labs/kit@16662ca208 🚀 2026-06-15 21:29:38 +00:00
ezynda3 fda05e4949 Deploying to gh-pages from @ mark3labs/kit@7067c99c84 🚀 2026-06-15 20:47:04 +00:00
ezynda3 f33ad8cbc2 Deploying to gh-pages from @ mark3labs/kit@feaec4268e 🚀 2026-06-15 13:32:05 +00:00
ezynda3 0f5a696cfd Deploying to gh-pages from @ mark3labs/kit@7f366eab84 🚀 2026-06-12 13:23:42 +00:00
ezynda3 10d05df015 Deploying to gh-pages from @ mark3labs/kit@e8e99b19a8 🚀 2026-06-11 13:13:47 +00:00
ezynda3 b0103e3cbf Deploying to gh-pages from @ mark3labs/kit@ef072f6e59 🚀 2026-06-09 13:28:36 +00:00
ezynda3 c0827868b3 Deploying to gh-pages from @ mark3labs/kit@49f8b485be 🚀 2026-06-09 13:18:36 +00:00
ezynda3 e1badea340 Deploying to gh-pages from @ mark3labs/kit@febdc530e1 🚀 2026-06-07 21:21:47 +00:00
ezynda3 c2b70ea4b9 Deploying to gh-pages from @ mark3labs/kit@e610bdd2d0 🚀 2026-06-07 19:04:18 +00:00
ezynda3 02b652fe59 Deploying to gh-pages from @ mark3labs/kit@6100e8b3a8 🚀 2026-06-07 15:06:00 +00:00
ezynda3 91c37897e9 Deploying to gh-pages from @ mark3labs/kit@9f125f3400 🚀 2026-06-07 14:45:38 +00:00
ezynda3 1b946b1815 Deploying to gh-pages from @ mark3labs/kit@00eab47218 🚀 2026-06-07 14:11:05 +00:00
ezynda3 a391221424 Deploying to gh-pages from @ mark3labs/kit@06bf6d087a 🚀 2026-06-07 11:06:39 +00:00
ezynda3 62940d96d5 Deploying to gh-pages from @ mark3labs/kit@fd960921ca 🚀 2026-06-06 16:22:35 +00:00
ezynda3 09088878e2 Deploying to gh-pages from @ mark3labs/kit@0b651a8df9 🚀 2026-06-04 12:48:34 +00:00
ezynda3 4798ea314e Deploying to gh-pages from @ mark3labs/kit@7315c1dea7 🚀 2026-06-04 12:36:12 +00:00
ezynda3 97c8d174fd Deploying to gh-pages from @ mark3labs/kit@0313fa03ad 🚀 2026-06-04 12:31:12 +00:00
ezynda3 2d1ecc5cba Deploying to gh-pages from @ mark3labs/kit@d27022bcfb 🚀 2026-06-04 11:37:00 +00:00
ezynda3 5eae413b91 Deploying to gh-pages from @ mark3labs/kit@ae722d520f 🚀 2026-06-02 12:22:14 +00:00
ezynda3 a75764fcdc Deploying to gh-pages from @ mark3labs/kit@7a04bdfeba 🚀 2026-06-02 11:42:03 +00:00
ezynda3 fd1a4d6910 Deploying to gh-pages from @ mark3labs/kit@7e4708f511 🚀 2026-06-02 11:12:28 +00:00
ezynda3 a77f1c1d6f Deploying to gh-pages from @ mark3labs/kit@1e12102b92 🚀 2026-05-29 17:33:31 +00:00
ezynda3 245863b9f5 Deploying to gh-pages from @ mark3labs/kit@ab2a77c95e 🚀 2026-05-29 15:44:43 +00:00
ezynda3 505e54318d Deploying to gh-pages from @ mark3labs/kit@1e78153b50 🚀 2026-05-29 14:53:03 +00:00
ezynda3 097f2beeb7 Deploying to gh-pages from @ mark3labs/kit@a613361969 🚀 2026-05-29 14:45:42 +00:00
ezynda3 d1ba46b969 Deploying to gh-pages from @ mark3labs/kit@67722b0c24 🚀 2026-05-29 13:43:01 +00:00
ezynda3 c16f1938a2 Deploying to gh-pages from @ mark3labs/kit@1a2f6da40f 🚀 2026-05-29 12:09:53 +00:00
ezynda3 9a57895c36 Deploying to gh-pages from @ mark3labs/kit@747f5be099 🚀 2026-05-29 08:57:48 +00:00
ezynda3 f728f185a6 Deploying to gh-pages from @ mark3labs/kit@d7c4565999 🚀 2026-05-25 10:30:53 +00:00
ezynda3 bdbb3f7499 Deploying to gh-pages from @ mark3labs/kit@bd24f3315c 🚀 2026-05-20 07:38:13 +00:00
ezynda3 06db3f2ef9 Deploying to gh-pages from @ mark3labs/kit@592f8dc84f 🚀 2026-05-19 12:11:37 +00:00
ezynda3 b9f1d1e90b Deploying to gh-pages from @ mark3labs/kit@66c4a1eb15 🚀 2026-05-19 10:25:25 +00:00
ezynda3 e9e5400546 Deploying to gh-pages from @ mark3labs/kit@5104477631 🚀 2026-05-16 13:20:05 +00:00
ezynda3 82194f36bd Deploying to gh-pages from @ mark3labs/kit@394a4676a1 🚀 2026-05-16 11:08:28 +00:00
ezynda3 9ed9b70b48 Deploying to gh-pages from @ mark3labs/kit@30f2bc243d 🚀 2026-05-16 10:49:24 +00:00
ezynda3 fdc0e924bd Deploying to gh-pages from @ mark3labs/kit@922e246098 🚀 2026-05-15 11:32:19 +00:00
ezynda3 1e9a87f3f7 Deploying to gh-pages from @ mark3labs/kit@32b6376515 🚀 2026-05-15 11:19:41 +00:00
ezynda3 9d4cefa521 Deploying to gh-pages from @ mark3labs/kit@cf194ff89a 🚀 2026-05-15 11:09:17 +00:00
ezynda3 358cbe8b03 Deploying to gh-pages from @ mark3labs/kit@03006425fa 🚀 2026-05-15 10:56:03 +00:00
ezynda3 240c3f9c63 Deploying to gh-pages from @ mark3labs/kit@a322dfc59a 🚀 2026-05-15 10:31:28 +00:00
ezynda3 a241d1a6cd Deploying to gh-pages from @ mark3labs/kit@b1387d837e 🚀 2026-05-15 10:07:05 +00:00
ezynda3 c95a1c723f Deploying to gh-pages from @ mark3labs/kit@f561f4cfd9 🚀 2026-05-14 17:42:56 +00:00
ezynda3 ae36b8abd1 Deploying to gh-pages from @ mark3labs/kit@64caed57d4 🚀 2026-05-13 18:11:01 +00:00
ezynda3 9ed9713985 Deploying to gh-pages from @ mark3labs/kit@975c30a773 🚀 2026-05-13 17:13:01 +00:00
ezynda3 dccd43e049 Deploying to gh-pages from @ mark3labs/kit@35b9360d64 🚀 2026-05-13 12:35:45 +00:00
ezynda3 f40e424698 Deploying to gh-pages from @ mark3labs/kit@1b8373e133 🚀 2026-05-12 10:31:06 +00:00
ezynda3 66e3308b4c Deploying to gh-pages from @ mark3labs/kit@1a5e4ce7c5 🚀 2026-05-08 10:12:09 +00:00
ezynda3 5e3ac82c20 Deploying to gh-pages from @ mark3labs/kit@24e2ea111c 🚀 2026-05-08 09:17:03 +00:00
ezynda3 b861501219 Deploying to gh-pages from @ mark3labs/kit@99f2680c2e 🚀 2026-05-08 07:54:36 +00:00
ezynda3 16c3924175 Deploying to gh-pages from @ mark3labs/kit@c4a2b0f1a3 🚀 2026-05-07 14:47:24 +00:00
ezynda3 eef2eef7de Deploying to gh-pages from @ mark3labs/kit@7cf38b37ee 🚀 2026-05-07 08:14:06 +00:00
ezynda3 f60b497fcd Deploying to gh-pages from @ mark3labs/kit@d304805106 🚀 2026-05-04 16:30:44 +00:00
ezynda3 b180b923ea Deploying to gh-pages from @ mark3labs/kit@34d5abff9c 🚀 2026-05-04 13:23:41 +00:00
ezynda3 4c8abad1e7 Deploying to gh-pages from @ mark3labs/kit@fc0ddd5f4f 🚀 2026-05-04 12:51:40 +00:00
ezynda3 ece392ee04 Deploying to gh-pages from @ mark3labs/kit@7aa6160c75 🚀 2026-05-04 09:11:18 +00:00
ezynda3 131d4d2c04 Deploying to gh-pages from @ mark3labs/kit@e830bf87ca 🚀 2026-04-27 06:43:23 +00:00
ezynda3 c6cda4e679 Deploying to gh-pages from @ mark3labs/kit@3881d1c28f 🚀 2026-04-24 12:14:13 +00:00
ezynda3 1834da8186 Deploying to gh-pages from @ mark3labs/kit@53f6682bd0 🚀 2026-04-23 13:34:29 +00:00
ezynda3 4b9f95aa24 Deploying to gh-pages from @ mark3labs/kit@996b15c9b9 🚀 2026-04-23 10:14:00 +00:00
ezynda3 5a71f424d6 Deploying to gh-pages from @ mark3labs/kit@aeb704367c 🚀 2026-04-23 09:56:36 +00:00
ezynda3 855805c461 Deploying to gh-pages from @ mark3labs/kit@d2e23295b6 🚀 2026-04-23 09:04:20 +00:00
ezynda3 b418565a00 Deploying to gh-pages from @ mark3labs/kit@e5a13e2e12 🚀 2026-04-22 18:05:40 +00:00
ezynda3 b232c32a17 Deploying to gh-pages from @ mark3labs/kit@558fb5214f 🚀 2026-04-22 17:25:42 +00:00
ezynda3 920e8fa208 Deploying to gh-pages from @ mark3labs/kit@61408ed490 🚀 2026-04-22 13:58:39 +00:00
ezynda3 0028e58a9b Deploying to gh-pages from @ mark3labs/kit@3cfb6437f9 🚀 2026-04-22 13:48:55 +00:00
ezynda3 44c4527f84 Deploying to gh-pages from @ mark3labs/kit@d33ad4028b 🚀 2026-04-22 10:07:10 +00:00
ezynda3 b3eef6f8e6 Deploying to gh-pages from @ mark3labs/kit@307dcd1734 🚀 2026-04-22 08:57:05 +00:00
ezynda3 9e4813586e Deploying to gh-pages from @ mark3labs/kit@81240b075e 🚀 2026-04-22 08:56:20 +00:00
ezynda3 b650f95c75 Deploying to gh-pages from @ mark3labs/kit@9a662d440c 🚀 2026-04-22 08:41:46 +00:00
ezynda3 a464dd984e Deploying to gh-pages from @ mark3labs/kit@4ba9d6fab3 🚀 2026-04-21 20:28:48 +00:00
ezynda3 637884b6be Deploying to gh-pages from @ mark3labs/kit@aec0e7cc01 🚀 2026-04-21 19:44:54 +00:00
ezynda3 e8a9f94679 Deploying to gh-pages from @ mark3labs/kit@bac04636bf 🚀 2026-04-21 19:24:44 +00:00
ezynda3 979aa8969a Deploying to gh-pages from @ mark3labs/kit@5f851fd08e 🚀 2026-04-21 19:05:50 +00:00
ezynda3 9736164c02 Deploying to gh-pages from @ mark3labs/kit@f8371836d8 🚀 2026-04-21 18:20:24 +00:00
ezynda3 2f52a270b6 Deploying to gh-pages from @ mark3labs/kit@74f00244be 🚀 2026-04-21 17:43:35 +00:00
ezynda3 1043a04b48 Deploying to gh-pages from @ mark3labs/kit@b5d7fd4f3e 🚀 2026-04-21 17:34:04 +00:00
ezynda3 1f4ff9ccb5 Deploying to gh-pages from @ mark3labs/kit@5857d40978 🚀 2026-04-21 17:28:09 +00:00
ezynda3 e3f1258870 Deploying to gh-pages from @ mark3labs/kit@3ff701054a 🚀 2026-04-21 17:19:41 +00:00
ezynda3 d381d970c1 Deploying to gh-pages from @ mark3labs/kit@c1dee3ceba 🚀 2026-04-21 16:52:38 +00:00
ezynda3 401c4c621b Deploying to gh-pages from @ mark3labs/kit@2d9783a44d 🚀 2026-04-21 16:33:41 +00:00
ezynda3 1173c80a8f Deploying to gh-pages from @ mark3labs/kit@88dd216e15 🚀 2026-04-21 13:25:13 +00:00
ezynda3 a70dec6efa Deploying to gh-pages from @ mark3labs/kit@9e5806ade8 🚀 2026-04-21 08:29:00 +00:00
ezynda3 d5826315dc Deploying to gh-pages from @ mark3labs/kit@50f586ec8f 🚀 2026-04-21 07:38:57 +00:00
ezynda3 0dc2cab65e Deploying to gh-pages from @ mark3labs/kit@8a8e684dff 🚀 2026-04-17 12:30:42 +00:00
ezynda3 7c0361245f Deploying to gh-pages from @ mark3labs/kit@7ef99ac60f 🚀 2026-04-17 12:27:07 +00:00
ezynda3 0a8577f572 Deploying to gh-pages from @ mark3labs/kit@a67f514560 🚀 2026-04-17 09:20:00 +00:00
ezynda3 7dd23a57c2 Deploying to gh-pages from @ mark3labs/kit@b6bb35cb71 🚀 2026-04-17 09:16:16 +00:00
ezynda3 3fdfed8df0 Deploying to gh-pages from @ mark3labs/kit@3bb20f5283 🚀 2026-04-16 20:12:39 +00:00
ezynda3 a18bd5d828 Deploying to gh-pages from @ mark3labs/kit@633fa38b2b 🚀 2026-04-16 09:33:24 +00:00
ezynda3 f9b1a6715e Deploying to gh-pages from @ mark3labs/kit@f905cee48c 🚀 2026-04-16 09:28:30 +00:00
ezynda3 24a2dffe08 Deploying to gh-pages from @ mark3labs/kit@182c10ea1a 🚀 2026-04-16 09:22:10 +00:00
ezynda3 9de96a7d50 Deploying to gh-pages from @ mark3labs/kit@fcaa52bf1c 🚀 2026-04-16 09:11:44 +00:00
ezynda3 ff3c30d651 Deploying to gh-pages from @ mark3labs/kit@7e6455732c 🚀 2026-04-15 15:06:42 +00:00
ezynda3 9d6ce4eefb Deploying to gh-pages from @ mark3labs/kit@71301a9035 🚀 2026-04-15 14:33:34 +00:00
ezynda3 e514713cbe Deploying to gh-pages from @ mark3labs/kit@0974d37ab2 🚀 2026-04-15 13:29:46 +00:00
ezynda3 ccf0a1dda9 Deploying to gh-pages from @ mark3labs/kit@398e825df8 🚀 2026-04-15 13:03:19 +00:00
ezynda3 bbf349952d Deploying to gh-pages from @ mark3labs/kit@3c51c20be7 🚀 2026-04-15 12:23:40 +00:00
ezynda3 08ecef02c5 Deploying to gh-pages from @ mark3labs/kit@25410af440 🚀 2026-04-15 10:02:11 +00:00
ezynda3 efdc81e42f Deploying to gh-pages from @ mark3labs/kit@26c9f009f9 🚀 2026-04-15 09:25:22 +00:00
ezynda3 eef0467763 Deploying to gh-pages from @ mark3labs/kit@e068487ff7 🚀 2026-04-15 08:51:03 +00:00
ezynda3 8d277bd550 Deploying to gh-pages from @ mark3labs/kit@0ffb0ba788 🚀 2026-04-15 08:28:18 +00:00
ezynda3 0f373cd7c5 Deploying to gh-pages from @ mark3labs/kit@65c6e9f797 🚀 2026-04-14 14:17:33 +00:00
ezynda3 524ba2d971 Deploying to gh-pages from @ mark3labs/kit@68d798d2f4 🚀 2026-04-14 10:22:43 +00:00
ezynda3 0bc33fdf64 Deploying to gh-pages from @ mark3labs/kit@eefd5565f8 🚀 2026-04-14 09:46:52 +00:00
ezynda3 8dfbe49b98 Deploying to gh-pages from @ mark3labs/kit@9d1b8a102e 🚀 2026-04-14 09:40:03 +00:00
ezynda3 7f7b3c527b Deploying to gh-pages from @ mark3labs/kit@f57e045c69 🚀 2026-04-14 09:28:44 +00:00
ezynda3 e21567ecf5 Deploying to gh-pages from @ mark3labs/kit@eb5da28a15 🚀 2026-04-14 09:17:02 +00:00
ezynda3 7295ef0e90 Deploying to gh-pages from @ mark3labs/kit@cd8e2a7654 🚀 2026-04-14 08:57:16 +00:00
ezynda3 3115882e5e Deploying to gh-pages from @ mark3labs/kit@64da1caf41 🚀 2026-04-14 08:40:24 +00:00
ezynda3 77e90d335a Deploying to gh-pages from @ mark3labs/kit@7eaeafff8c 🚀 2026-04-11 12:25:17 +00:00
ezynda3 752d2797fa Deploying to gh-pages from @ mark3labs/kit@8ed8d23c73 🚀 2026-04-11 09:10:23 +00:00
ezynda3 f85a86a4a6 Deploying to gh-pages from @ mark3labs/kit@2de98d32be 🚀 2026-04-10 14:06:21 +00:00
ezynda3 e29c62333f Deploying to gh-pages from @ mark3labs/kit@83127467c5 🚀 2026-04-09 14:08:08 +00:00
ezynda3 9756682bcb Deploying to gh-pages from @ mark3labs/kit@e07c94f49d 🚀 2026-04-09 10:54:42 +00:00
ezynda3 ec2c146dd2 Deploying to gh-pages from @ mark3labs/kit@b87146a284 🚀 2026-04-09 10:28:09 +00:00
ezynda3 5b6c1367c8 Deploying to gh-pages from @ mark3labs/kit@186d9f7f44 🚀 2026-04-09 10:00:59 +00:00
ezynda3 8b0a83b7d5 Deploying to gh-pages from @ mark3labs/kit@3a8ffc2104 🚀 2026-04-09 09:35:32 +00:00
ezynda3 e3d413ceef Deploying to gh-pages from @ mark3labs/kit@e54570162e 🚀 2026-04-09 09:08:25 +00:00
ezynda3 880b9dff37 Deploying to gh-pages from @ mark3labs/kit@34bb97a40e 🚀 2026-04-08 17:52:30 +00:00
ezynda3 687c092987 Deploying to gh-pages from @ mark3labs/kit@f5c1a16f8a 🚀 2026-04-08 15:53:41 +00:00
ezynda3 9ef8959b8b Deploying to gh-pages from @ mark3labs/kit@b29d7d2166 🚀 2026-04-08 13:56:32 +00:00
ezynda3 145be66dab Deploying to gh-pages from @ mark3labs/kit@3ea0db69ea 🚀 2026-04-08 12:16:01 +00:00
ezynda3 8e9484ceb1 Deploying to gh-pages from @ mark3labs/kit@4304a5e899 🚀 2026-04-08 12:05:22 +00:00
ezynda3 702c230090 Deploying to gh-pages from @ mark3labs/kit@4019c1e4f7 🚀 2026-04-08 11:24:04 +00:00
ezynda3 8e6390d1c0 Deploying to gh-pages from @ mark3labs/kit@30ad7c1d0b 🚀 2026-04-08 11:15:38 +00:00
ezynda3 1e707653ff Deploying to gh-pages from @ mark3labs/kit@e33564c569 🚀 2026-04-08 10:35:04 +00:00
ezynda3 6e2fb58951 Deploying to gh-pages from @ mark3labs/kit@5ff28445fd 🚀 2026-04-08 10:24:55 +00:00
ezynda3 f73dc7c338 Deploying to gh-pages from @ mark3labs/kit@13d177e5d0 🚀 2026-04-07 21:39:49 +00:00
ezynda3 bbb651d12d Deploying to gh-pages from @ mark3labs/kit@3ffc995f27 🚀 2026-04-07 19:06:38 +00:00
ezynda3 bdf2ed7fa0 Deploying to gh-pages from @ mark3labs/kit@b2bd016135 🚀 2026-04-07 18:20:43 +00:00
ezynda3 e0f6f3d762 Deploying to gh-pages from @ mark3labs/kit@812dedaea2 🚀 2026-04-07 14:42:24 +00:00
ezynda3 8e068b14cd Deploying to gh-pages from @ mark3labs/kit@f65b6737f2 🚀 2026-04-07 14:11:32 +00:00
ezynda3 cffda6b80c Deploying to gh-pages from @ mark3labs/kit@5d45aa196b 🚀 2026-04-07 13:32:01 +00:00
ezynda3 2f3970efd8 Deploying to gh-pages from @ mark3labs/kit@debb39f56c 🚀 2026-04-07 13:29:42 +00:00
ezynda3 2369793637 Deploying to gh-pages from @ mark3labs/kit@7ce6f4fd9e 🚀 2026-04-07 12:01:49 +00:00
ezynda3 9a84fee5d7 Deploying to gh-pages from @ mark3labs/kit@c2f2bdb3d3 🚀 2026-04-07 11:10:32 +00:00
ezynda3 3c08f14a2f Deploying to gh-pages from @ mark3labs/kit@201d14804e 🚀 2026-04-07 10:52:59 +00:00
ezynda3 e6f98776f3 Deploying to gh-pages from @ mark3labs/kit@7e54710d4a 🚀 2026-04-07 10:36:45 +00:00
ezynda3 f85085a12e Deploying to gh-pages from @ mark3labs/kit@88870be4d2 🚀 2026-04-06 07:53:04 +00:00
ezynda3 adf9cfff76 Deploying to gh-pages from @ mark3labs/kit@46bf809715 🚀 2026-04-06 06:51:14 +00:00
ezynda3 32d4fc558f Deploying to gh-pages from @ mark3labs/kit@02f54d7b49 🚀 2026-04-06 06:40:50 +00:00
ezynda3 93fbcc40d5 Deploying to gh-pages from @ mark3labs/kit@e19e9642a2 🚀 2026-04-04 16:33:31 +00:00
ezynda3 76fe2851d1 Deploying to gh-pages from @ mark3labs/kit@32675b8b35 🚀 2026-04-04 15:12:27 +00:00
ezynda3 7816d63a0e Deploying to gh-pages from @ mark3labs/kit@aecce001ee 🚀 2026-04-04 14:42:29 +00:00
ezynda3 6d0109ce74 Deploying to gh-pages from @ mark3labs/kit@32d73171fd 🚀 2026-04-04 14:19:32 +00:00
ezynda3 764e32c4fd Deploying to gh-pages from @ mark3labs/kit@265fd2ec0c 🚀 2026-04-04 13:44:43 +00:00
ezynda3 7837be9d1f Deploying to gh-pages from @ mark3labs/kit@efebf2eba6 🚀 2026-04-04 13:33:39 +00:00
ezynda3 fc64612282 Deploying to gh-pages from @ mark3labs/kit@f7b655ae33 🚀 2026-04-04 12:01:37 +00:00
ezynda3 649e67e344 Deploying to gh-pages from @ mark3labs/kit@35982b41ad 🚀 2026-04-03 10:49:39 +00:00
ezynda3 fbaedfb71d Deploying to gh-pages from @ mark3labs/kit@788e3b71fd 🚀 2026-04-03 09:37:42 +00:00
ezynda3 016929df2e Deploying to gh-pages from @ mark3labs/kit@3496bc2684 🚀 2026-04-02 14:21:27 +00:00
ezynda3 dbead16e4e Deploying to gh-pages from @ mark3labs/kit@997c7d15ff 🚀 2026-04-02 14:20:05 +00:00
ezynda3 dfec8ef6ca Deploying to gh-pages from @ mark3labs/kit@83246e47d5 🚀 2026-04-02 14:18:51 +00:00
ezynda3 b3145e0f2f Deploying to gh-pages from @ mark3labs/kit@50e7b78c33 🚀 2026-04-02 13:50:23 +00:00
ezynda3 a4f2aaa91a Deploying to gh-pages from @ mark3labs/kit@b937af3056 🚀 2026-04-02 13:13:20 +00:00
ezynda3 3257994ff8 Deploying to gh-pages from @ mark3labs/kit@a5e995c750 🚀 2026-04-02 13:12:16 +00:00
ezynda3 d161c930cd Deploying to gh-pages from @ mark3labs/kit@e95e08a699 🚀 2026-04-02 13:10:28 +00:00
ezynda3 0c6b1c8307 Deploying to gh-pages from @ mark3labs/kit@bcaf92f62a 🚀 2026-04-02 13:09:46 +00:00
ezynda3 2cae02c9ba Deploying to gh-pages from @ mark3labs/kit@ead4afbfe6 🚀 2026-04-02 12:55:16 +00:00
ezynda3 d56c2627ff Deploying to gh-pages from @ mark3labs/kit@685aaf207f 🚀 2026-04-02 12:42:29 +00:00
ezynda3 aa20be72a4 Deploying to gh-pages from @ mark3labs/kit@76ff6c9639 🚀 2026-04-02 12:11:29 +00:00
ezynda3 ecb3b446f9 Deploying to gh-pages from @ mark3labs/kit@1cf24ee5de 🚀 2026-04-02 11:46:03 +00:00
ezynda3 9e166ad6db Deploying to gh-pages from @ mark3labs/kit@c9637090fa 🚀 2026-04-02 11:45:34 +00:00
ezynda3 c2fbf21192 Deploying to gh-pages from @ mark3labs/kit@0ff0ff42ab 🚀 2026-04-02 11:39:55 +00:00
ezynda3 1ee23a7bf6 Deploying to gh-pages from @ mark3labs/kit@7d2f078111 🚀 2026-04-02 11:19:13 +00:00
ezynda3 73dc7e5e20 Deploying to gh-pages from @ mark3labs/kit@b0b66941ab 🚀 2026-04-02 11:05:11 +00:00
ezynda3 1629e74b7d Deploying to gh-pages from @ mark3labs/kit@cbb7387a72 🚀 2026-04-01 18:24:45 +00:00
ezynda3 93def3b103 Deploying to gh-pages from @ mark3labs/kit@19430b0ecb 🚀 2026-04-01 18:11:29 +00:00
ezynda3 1ba4811536 Deploying to gh-pages from @ mark3labs/kit@8e3cfeede5 🚀 2026-04-01 18:02:44 +00:00
ezynda3 db695f9e38 Deploying to gh-pages from @ mark3labs/kit@4e7d823ee4 🚀 2026-04-01 13:11:28 +00:00
ezynda3 76d57f3978 Deploying to gh-pages from @ mark3labs/kit@7a16c76adc 🚀 2026-04-01 12:12:14 +00:00
ezynda3 edc406611e Deploying to gh-pages from @ mark3labs/kit@70a21ee73a 🚀 2026-04-01 11:59:58 +00:00
ezynda3 19ca9c60da Deploying to gh-pages from @ mark3labs/kit@28d2de8f39 🚀 2026-04-01 11:50:10 +00:00
ezynda3 e5413157fc Deploying to gh-pages from @ mark3labs/kit@7f192ae850 🚀 2026-04-01 10:35:49 +00:00
ezynda3 144f2e953c Deploying to gh-pages from @ mark3labs/kit@9f6746ded9 🚀 2026-04-01 10:30:07 +00:00
ezynda3 a9d85c7bc2 Deploying to gh-pages from @ mark3labs/kit@7514d3a0ff 🚀 2026-04-01 10:25:03 +00:00
ezynda3 e2f85c469c Deploying to gh-pages from @ mark3labs/kit@c83281a52b 🚀 2026-04-01 10:22:44 +00:00
ezynda3 ee47fe7b0b Deploying to gh-pages from @ mark3labs/kit@4515bb92c2 🚀 2026-04-01 10:21:51 +00:00
ezynda3 144c873ce5 Deploying to gh-pages from @ mark3labs/kit@e326b84204 🚀 2026-04-01 10:21:14 +00:00
ezynda3 6e349633d8 Deploying to gh-pages from @ mark3labs/kit@1b93049b8e 🚀 2026-04-01 10:12:14 +00:00
ezynda3 1e1105045f Deploying to gh-pages from @ mark3labs/kit@4912449dda 🚀 2026-04-01 10:09:52 +00:00
ezynda3 042fd08a86 Deploying to gh-pages from @ mark3labs/kit@b70cce4f34 🚀 2026-03-31 22:13:52 +00:00
ezynda3 b7269b8c7e Deploying to gh-pages from @ mark3labs/kit@4c566836b2 🚀 2026-03-31 21:40:03 +00:00
ezynda3 7d9689bc3a Deploying to gh-pages from @ mark3labs/kit@ef519ba517 🚀 2026-03-31 12:05:56 +00:00
ezynda3 d6e9a0bb37 Deploying to gh-pages from @ mark3labs/kit@d79eb1f0fa 🚀 2026-03-31 11:27:16 +00:00
ezynda3 3d25fefb18 Deploying to gh-pages from @ mark3labs/kit@ac8ee6525d 🚀 2026-03-31 10:44:34 +00:00
ezynda3 72a3d94066 Deploying to gh-pages from @ mark3labs/kit@e35e8382d6 🚀 2026-03-31 10:19:37 +00:00
ezynda3 0b25f0224e Deploying to gh-pages from @ mark3labs/kit@fbb3408a25 🚀 2026-03-31 10:04:39 +00:00
ezynda3 c251b3b1d3 Deploying to gh-pages from @ mark3labs/kit@44fed9a647 🚀 2026-03-31 10:03:41 +00:00
ezynda3 e08002c6d7 Deploying to gh-pages from @ mark3labs/kit@e7f11487b9 🚀 2026-03-31 10:02:05 +00:00
ezynda3 868c836ae8 Deploying to gh-pages from @ mark3labs/kit@054c417603 🚀 2026-03-31 07:34:41 +00:00
ezynda3 a5045ddbfd Deploying to gh-pages from @ mark3labs/kit@94d62a6ef0 🚀 2026-03-30 17:39:19 +00:00
ezynda3 0ee6a640ff Deploying to gh-pages from @ mark3labs/kit@91e6dfd2c8 🚀 2026-03-30 17:34:19 +00:00
ezynda3 d728e4397c Deploying to gh-pages from @ mark3labs/kit@b6a0c4b44c 🚀 2026-03-30 17:30:50 +00:00
ezynda3 11b396c3aa Deploying to gh-pages from @ mark3labs/kit@8eb0fa855a 🚀 2026-03-30 17:28:50 +00:00
ezynda3 d82de1d63a Deploying to gh-pages from @ mark3labs/kit@3bf696c546 🚀 2026-03-30 15:31:33 +00:00
ezynda3 70243c6136 Deploying to gh-pages from @ mark3labs/kit@3e461a0539 🚀 2026-03-30 15:30:51 +00:00
ezynda3 1c46ad6e43 Deploying to gh-pages from @ mark3labs/kit@a2ece01ecf 🚀 2026-03-30 15:24:27 +00:00
ezynda3 001975a0bc Deploying to gh-pages from @ mark3labs/kit@623c9fb5ad 🚀 2026-03-30 15:21:20 +00:00
ezynda3 9e926998b8 Deploying to gh-pages from @ mark3labs/kit@139506f336 🚀 2026-03-30 14:06:42 +00:00
ezynda3 78a3cf7a12 Deploying to gh-pages from @ mark3labs/kit@5a3d3fdd7d 🚀 2026-03-30 13:32:30 +00:00
ezynda3 0a7d23104e Deploying to gh-pages from @ mark3labs/kit@5a71cde5ff 🚀 2026-03-30 13:05:41 +00:00
ezynda3 44cb0fbc37 Deploying to gh-pages from @ mark3labs/kit@044d3eb206 🚀 2026-03-30 12:50:28 +00:00
ezynda3 e6dc292a60 Deploying to gh-pages from @ mark3labs/kit@4af75901b5 🚀 2026-03-30 11:45:28 +00:00
ezynda3 e6e949a559 Deploying to gh-pages from @ mark3labs/kit@274a775c26 🚀 2026-03-30 09:23:15 +00:00
ezynda3 5809583e36 Deploying to gh-pages from @ mark3labs/kit@4af75901b5 🚀 2026-03-29 12:13:21 +00:00
ezynda3 94206cfb3d Deploying to gh-pages from @ mark3labs/kit@49ff4c0678 🚀 2026-03-29 12:02:55 +00:00
ezynda3 75708f46c1 Deploying to gh-pages from @ mark3labs/kit@b0802a5c32 🚀 2026-03-29 11:48:38 +00:00
ezynda3 707adc5088 Deploying to gh-pages from @ mark3labs/kit@dfe65ca227 🚀 2026-03-29 11:44:20 +00:00
ezynda3 2fb2983c1d Deploying to gh-pages from @ mark3labs/kit@d4ec756ce5 🚀 2026-03-29 11:42:57 +00:00
ezynda3 e1e1fdf271 Deploying to gh-pages from @ mark3labs/kit@2971e73ee8 🚀 2026-03-29 11:41:10 +00:00
ezynda3 bd3ce2d3b0 Deploying to gh-pages from @ mark3labs/kit@5aa6c9e116 🚀 2026-03-29 11:36:31 +00:00
ezynda3 498ff88a4b Deploying to gh-pages from @ mark3labs/kit@bca08476de 🚀 2026-03-29 11:32:57 +00:00
ezynda3 4d53a6a8bf Deploying to gh-pages from @ mark3labs/kit@6a599d86af 🚀 2026-03-29 11:31:51 +00:00
ezynda3 aa79357217 Deploying to gh-pages from @ mark3labs/kit@fd6f200659 🚀 2026-03-29 11:29:22 +00:00
ezynda3 e1f7e585bd Deploying to gh-pages from @ mark3labs/kit@f0e4e2f757 🚀 2026-03-29 11:02:29 +00:00
ezynda3 55ff94eb3c Deploying to gh-pages from @ mark3labs/kit@d25249506a 🚀 2026-03-29 10:33:57 +00:00
ezynda3 e389b492ae Deploying to gh-pages from @ mark3labs/kit@971521f534 🚀 2026-03-29 10:25:47 +00:00
ezynda3 f1ebf2cc05 Deploying to gh-pages from @ mark3labs/kit@9c90563765 🚀 2026-03-28 22:18:55 +00:00
ezynda3 0fa8d44217 Deploying to gh-pages from @ mark3labs/kit@f36166bee5 🚀 2026-03-28 21:24:44 +00:00
ezynda3 d583fd72d4 Deploying to gh-pages from @ mark3labs/kit@879e81f9b5 🚀 2026-03-28 21:05:40 +00:00
ezynda3 3a9493871d Deploying to gh-pages from @ mark3labs/kit@727b42acfe 🚀 2026-03-28 21:01:04 +00:00
ezynda3 f1d3b6cd8d Deploying to gh-pages from @ mark3labs/kit@4830981570 🚀 2026-03-28 20:58:43 +00:00
ezynda3 83e0f8b9e3 Deploying to gh-pages from @ mark3labs/kit@dcfebafcc5 🚀 2026-03-28 14:50:01 +00:00
ezynda3 0096c12253 Deploying to gh-pages from @ mark3labs/kit@1f5c103667 🚀 2026-03-28 09:16:22 +00:00
ezynda3 8b6662263d Deploying to gh-pages from @ mark3labs/kit@4caa8ba3dc 🚀 2026-03-28 09:00:48 +00:00
ezynda3 f2fb3bc41d Deploying to gh-pages from @ mark3labs/kit@551f2710d9 🚀 2026-03-27 18:43:58 +00:00
ezynda3 b53d1919e2 Deploying to gh-pages from @ mark3labs/kit@b68b3dd0bf 🚀 2026-03-27 15:21:41 +00:00
ezynda3 877d60034a Deploying to gh-pages from @ mark3labs/kit@48521bf76d 🚀 2026-03-27 14:55:44 +00:00
ezynda3 de84440d03 Deploying to gh-pages from @ mark3labs/kit@d9326fcf21 🚀 2026-03-27 12:56:07 +00:00
ezynda3 678d6a3925 Deploying to gh-pages from @ mark3labs/kit@7a8a5b185f 🚀 2026-03-27 12:02:11 +00:00
ezynda3 a090f088c0 Deploying to gh-pages from @ mark3labs/kit@7c98ab921b 🚀 2026-03-27 11:02:38 +00:00
ezynda3 f04c6fe4ca Deploying to gh-pages from @ mark3labs/kit@8ae204f12f 🚀 2026-03-27 09:13:46 +00:00
ezynda3 d3a441faa9 Deploying to gh-pages from @ mark3labs/kit@8b1665a4ce 🚀 2026-03-27 07:36:57 +00:00
ezynda3 aefdaaf465 Deploying to gh-pages from @ mark3labs/kit@941f1daf0b 🚀 2026-03-26 13:47:23 +00:00
ezynda3 e9243b6eb3 Deploying to gh-pages from @ mark3labs/kit@ab7e2bda61 🚀 2026-03-26 13:03:44 +00:00
ezynda3 f7bd352266 Deploying to gh-pages from @ mark3labs/kit@741520927c 🚀 2026-03-26 12:57:11 +00:00
ezynda3 3a7e53f938 Deploying to gh-pages from @ mark3labs/kit@4c1bda9541 🚀 2026-03-26 12:48:38 +00:00
ezynda3 69881eff50 Deploying to gh-pages from @ mark3labs/kit@83a959a379 🚀 2026-03-26 12:23:02 +00:00
ezynda3 2afe4489a4 Deploying to gh-pages from @ mark3labs/kit@1cd074836f 🚀 2026-03-26 10:42:18 +00:00
ezynda3 43cf2100dd Deploying to gh-pages from @ mark3labs/kit@ab3ce260c8 🚀 2026-03-26 10:38:33 +00:00
ezynda3 254896e02d Deploying to gh-pages from @ mark3labs/kit@8e8cc3946d 🚀 2026-03-26 09:52:14 +00:00
ezynda3 13ec5721f5 Deploying to gh-pages from @ mark3labs/kit@e18e36625e 🚀 2026-03-26 09:44:46 +00:00
ezynda3 6e26fb7764 Deploying to gh-pages from @ mark3labs/kit@be55bc03f1 🚀 2026-03-26 09:10:48 +00:00
ezynda3 a6c4e70ac4 Deploying to gh-pages from @ mark3labs/kit@09919b6307 🚀 2026-03-25 15:18:21 +00:00
ezynda3 b4af7e892b Deploying to gh-pages from @ mark3labs/kit@7a2de4cc3c 🚀 2026-03-25 15:10:11 +00:00
ezynda3 e3b08effe5 Deploying to gh-pages from @ mark3labs/kit@acd7fd7f45 🚀 2026-03-25 15:03:20 +00:00
ezynda3 35dc729860 Deploying to gh-pages from @ mark3labs/kit@3446f38516 🚀 2026-03-25 14:49:09 +00:00
ezynda3 76dcc2ba7f Deploying to gh-pages from @ mark3labs/kit@db4bb19bac 🚀 2026-03-25 14:42:17 +00:00
ezynda3 475fc3a1c0 Deploying to gh-pages from @ mark3labs/kit@d1cffb85ef 🚀 2026-03-24 12:14:10 +00:00
ezynda3 c2f45393bf Deploying to gh-pages from @ mark3labs/kit@329cd4ea4a 🚀 2026-03-24 11:20:19 +00:00
ezynda3 8760c178c5 Deploying to gh-pages from @ mark3labs/kit@4e779d576f 🚀 2026-03-24 10:39:19 +00:00
ezynda3 f6cf5db412 Deploying to gh-pages from @ mark3labs/kit@fc054f50e8 🚀 2026-03-24 10:28:52 +00:00
ezynda3 67beb5446f Deploying to gh-pages from @ mark3labs/kit@d8f1b32885 🚀 2026-03-23 14:55:25 +00:00
ezynda3 11791d98c7 Deploying to gh-pages from @ mark3labs/kit@1e2a3e2589 🚀 2026-03-23 14:52:07 +00:00
ezynda3 0774e8cfba Deploying to gh-pages from @ mark3labs/kit@c7f43917b1 🚀 2026-03-23 14:28:29 +00:00
ezynda3 713c3ed7d1 Deploying to gh-pages from @ mark3labs/kit@6a8833a7b1 🚀 2026-03-23 13:09:25 +00:00
ezynda3 ed9155a0a8 Deploying to gh-pages from @ mark3labs/kit@ab09d5c9e4 🚀 2026-03-22 18:11:55 +00:00
ezynda3 61c6c8e67b Deploying to gh-pages from @ mark3labs/kit@2347e0e506 🚀 2026-03-22 17:50:41 +00:00
ezynda3 97f1bdf755 Deploying to gh-pages from @ mark3labs/kit@3e1c19442b 🚀 2026-03-22 17:42:42 +00:00
ezynda3 3105437279 Deploying to gh-pages from @ mark3labs/kit@f79601feb1 🚀 2026-03-22 16:58:06 +00:00
ezynda3 9b610cb1cd Deploying to gh-pages from @ mark3labs/kit@eb3219e7ca 🚀 2026-03-22 16:40:50 +00:00
ezynda3 119d7a58fd Deploying to gh-pages from @ mark3labs/kit@d24540693c 🚀 2026-03-22 12:00:37 +00:00
ezynda3 bce4e90f80 Deploying to gh-pages from @ mark3labs/kit@f7c8e7757b 🚀 2026-03-22 10:52:34 +00:00
ezynda3 0b7ec37fd4 Deploying to gh-pages from @ mark3labs/kit@0d5374b17b 🚀 2026-03-22 10:42:27 +00:00
ezynda3 ab6f7f7751 Deploying to gh-pages from @ mark3labs/kit@25f17a104d 🚀 2026-03-22 10:31:58 +00:00
ezynda3 350027abe7 Deploying to gh-pages from @ mark3labs/kit@20125f939b 🚀 2026-03-22 10:24:12 +00:00
ezynda3 d98bba4746 Deploying to gh-pages from @ mark3labs/kit@d3b67ffd14 🚀 2026-03-21 21:58:32 +00:00
ezynda3 4859b50eb0 Deploying to gh-pages from @ mark3labs/kit@915dc066dd 🚀 2026-03-21 18:16:01 +00:00
ezynda3 082756c086 Deploying to gh-pages from @ mark3labs/kit@3b14814740 🚀 2026-03-21 18:01:56 +00:00
ezynda3 7df4089d01 Deploying to gh-pages from @ mark3labs/kit@a1decf9cff 🚀 2026-03-21 17:49:09 +00:00
ezynda3 6e1abeb3bd Deploying to gh-pages from @ mark3labs/kit@ec4ac64343 🚀 2026-03-21 17:41:51 +00:00
ezynda3 840954a598 Deploying to gh-pages from @ mark3labs/kit@47f3dca219 🚀 2026-03-21 17:41:36 +00:00
ezynda3 5819801c96 Deploying to gh-pages from @ mark3labs/kit@a95117686e 🚀 2026-03-21 15:49:30 +00:00
ezynda3 4adede09cc Deploying to gh-pages from @ mark3labs/kit@c0880e1ef6 🚀 2026-03-21 15:33:03 +00:00
ezynda3 de6835b63b Deploying to gh-pages from @ mark3labs/kit@4e66c0b4f7 🚀 2026-03-20 15:09:40 +00:00
ezynda3 7d5118de1d Deploying to gh-pages from @ mark3labs/kit@131ce8f2cc 🚀 2026-03-20 14:18:44 +00:00
ezynda3 18ddedd928 Deploying to gh-pages from @ mark3labs/kit@3d0f3358cb 🚀 2026-03-20 11:01:51 +00:00
ezynda3 70bcdc1243 Deploying to gh-pages from @ mark3labs/kit@25da02fa65 🚀 2026-03-20 10:48:51 +00:00
ezynda3 f83d54144c Deploying to gh-pages from @ mark3labs/kit@4ae03aab7c 🚀 2026-03-20 10:40:47 +00:00
ezynda3 019eee1104 Deploying to gh-pages from @ mark3labs/kit@93895392e6 🚀 2026-03-20 10:28:30 +00:00
ezynda3 e9a963b8ae Deploying to gh-pages from @ mark3labs/kit@473070e78b 🚀 2026-03-20 10:25:20 +00:00
ezynda3 4fc24bf73d Deploying to gh-pages from @ mark3labs/kit@12268a777f 🚀 2026-03-20 10:21:54 +00:00
ezynda3 235f65582c Deploying to gh-pages from @ mark3labs/kit@351c10d814 🚀 2026-03-20 10:19:37 +00:00
ezynda3 b811934d7e Deploying to gh-pages from @ mark3labs/kit@9de3843605 🚀 2026-03-20 10:19:19 +00:00
ezynda3 28d9a2fbb1 Deploying to gh-pages from @ mark3labs/kit@1d5473e111 🚀 2026-03-20 10:17:54 +00:00
ezynda3 c0c9787987 Deploying to gh-pages from @ mark3labs/kit@b6adcf159e 🚀 2026-03-20 10:16:45 +00:00
ezynda3 473c9b634b Deploying to gh-pages from @ mark3labs/kit@b1da4a28e6 🚀 2026-03-20 10:15:51 +00:00
ezynda3 3e02930514 Deploying to gh-pages from @ mark3labs/kit@95abb6fa6e 🚀 2026-03-20 10:03:49 +00:00
ezynda3 a1038d8071 Deploying to gh-pages from @ mark3labs/kit@a9970cf346 🚀 2026-03-20 09:54:46 +00:00
ezynda3 d687d9ca7a Deploying to gh-pages from @ mark3labs/kit@13060a20f9 🚀 2026-03-19 15:05:26 +00:00
ezynda3 c254663081 Deploying to gh-pages from @ mark3labs/kit@adf603e944 🚀 2026-03-19 14:30:06 +00:00
ezynda3 12d19656d8 Deploying to gh-pages from @ mark3labs/kit@af486133a5 🚀 2026-03-19 14:26:26 +00:00
ezynda3 b6da6aed75 Deploying to gh-pages from @ mark3labs/kit@a97cd47ced 🚀 2026-03-19 14:11:45 +00:00
ezynda3 ce86806b0e Deploying to gh-pages from @ mark3labs/kit@68518a2bdb 🚀 2026-03-19 14:02:50 +00:00
ezynda3 c54b5b78ae Deploying to gh-pages from @ mark3labs/kit@fd61db3e12 🚀 2026-03-19 13:49:04 +00:00
ezynda3 8c81b53c76 Deploying to gh-pages from @ mark3labs/kit@e49066a119 🚀 2026-03-19 13:47:36 +00:00
ezynda3 91ee4d7ffa Deploying to gh-pages from @ mark3labs/kit@efaff7f44f 🚀 2026-03-19 13:42:34 +00:00
Ed Zynda f752e3d1be Create CNAME 2026-03-19 16:29:40 +03:00
Ed Zynda db274af1c6 init gh-pages 2026-03-19 16:28:40 +03:00
361 changed files with 21455 additions and 72470 deletions
-32
View File
@@ -1,32 +0,0 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- uses: golangci/golangci-lint-action@v7
with:
version: v2.10.1
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- run: go test -race ./...
-32
View File
@@ -1,32 +0,0 @@
name: Build and Deploy Docs to GitHub Pages
on:
push:
branches: [master]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install Dependencies
working-directory: ./www
run: bun install
- name: Build
working-directory: ./www
run: bun run build
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: www/out
branch: gh-pages
-105
View File
@@ -1,105 +0,0 @@
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: 'Tag to use for npm publish'
required: true
skip_goreleaser:
description: 'Skip goreleaser job (npm-only release)'
type: boolean
default: true
permissions:
contents: write
id-token: write # Required for npm trusted publishing (OIDC)
jobs:
goreleaser:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || !inputs.skip_goreleaser }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: "1.26"
- uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
npm-publish:
runs-on: ubuntu-latest
needs: goreleaser
if: ${{ always() && (needs.goreleaser.result == 'success' || needs.goreleaser.result == 'skipped') }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Set version from tag
working-directory: npm
run: |
TAG=${{ inputs.tag || github.ref_name }}
VERSION=${TAG#v}
echo "Setting npm version to $VERSION"
npm version $VERSION --no-git-tag-version
- name: Publish to npm
working-directory: npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
notify:
runs-on: ubuntu-latest
needs: [goreleaser, npm-publish]
if: ${{ always() && (needs.goreleaser.result == 'success' || needs.goreleaser.result == 'skipped') && (needs.npm-publish.result == 'success') }}
steps:
- name: Send Discord Notification
env:
DISCORD_WEBHOOK: ${{ secrets.RELEASES_WEBHOOK }}
TAG_NAME: ${{ inputs.tag || github.ref_name }}
RELEASE_URL: https://github.com/${{ github.repository }}/releases/tag/${{ inputs.tag || github.ref_name }}
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{
\"embeds\": [{
\"title\": \"New Release: $TAG_NAME\",
\"description\": \"A new version of kit has been released!\",
\"color\": 5814783,
\"fields\": [
{
\"name\": \"Version\",
\"value\": \"$TAG_NAME\",
\"inline\": true
},
{
\"name\": \"Repository\",
\"value\": \"[kit](https://github.com/${{ github.repository }})\",
\"inline\": true
}
],
\"footer\": {
\"text\": \"Released via GitHub Actions\"
},
\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",
\"url\": \"$RELEASE_URL\"
}]
}" \
$DISCORD_WEBHOOK
-17
View File
@@ -1,17 +0,0 @@
.aider*
.task/
.env
.kit/*
!.kit/extensions/
!.kit/prompts/
aidocs/
*.log
/kit
.idea
build/
dist/
contribute/output/
CONTEXT.md
output/
.agents/
skills-lock.json
-9
View File
@@ -1,9 +0,0 @@
version: "2"
linters:
enable:
- modernize
formatters:
enable:
- gofmt
-70
View File
@@ -1,70 +0,0 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
builds:
- main: ./cmd/kit
binary: kit
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
- -X main.date={{.Date}}
archives:
- formats: [tar.gz]
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
formats: [zip]
checksum:
name_template: checksums.txt
release:
header: |
## Installation
```bash
# Bun / npm
bun add -g @mark3labs/kit
# or: npm install -g @mark3labs/kit
# Go install (requires Go 1.26+)
go install github.com/mark3labs/kit/cmd/kit@latest
# Or download from assets below
```
changelog:
sort: asc
use: github
filters:
exclude:
- "^Merge"
- "^merge"
- "^wip"
- "^WIP"
groups:
- title: "Features"
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
order: 0
- title: "Bug Fixes"
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
order: 1
- title: "Documentation"
regexp: '^.*?docs(\([[:word:]]+\))??!?:.+$'
order: 2
- title: "Maintenance"
regexp: '^.*?(chore|refactor|style|ci|build)(\([[:word:]]+\))??!?:.+$'
order: 3
- title: "Other Changes"
order: 999
@@ -1 +0,0 @@
{"Created":"2026-02-25T21:53:09.725157606Z","name":"iteratr_events","subjects":["iteratr.\u003e"],"retention":"limits","max_consumers":-1,"max_msgs":-1,"max_bytes":-1,"max_age":2592000000000000,"max_msgs_per_subject":-1,"max_msg_size":-1,"discard":"old","storage":"file","num_replicas":1,"duplicate_window":120000000000,"compression":"none","allow_direct":false,"mirror_direct":false,"sealed":false,"deny_delete":false,"deny_purge":false,"allow_rollup_hdrs":false,"consumer_limits":{}}
@@ -1 +0,0 @@
0d94ed0d599988d1
@@ -1 +0,0 @@
{"Created":"0001-01-01T00:00:00Z","Name":"","name":"7UfQJgqo","deliver_policy":"all","ack_policy":"explicit","ack_wait":30000000000,"max_deliver":-1,"filter_subject":"iteratr.unified-bubbletea-architecture.\u003e","replay_policy":"instant","max_waiting":512,"max_ack_pending":1000,"inactive_threshold":5000000000,"num_replicas":0}
@@ -1 +0,0 @@
0a75862a3a998ab5
@@ -1 +0,0 @@
{"Created":"0001-01-01T00:00:00Z","Name":"","name":"EnXXTfbX","deliver_policy":"all","ack_policy":"explicit","ack_wait":30000000000,"max_deliver":-1,"filter_subject":"iteratr.unified-bubbletea-architecture.\u003e","replay_policy":"instant","max_waiting":512,"max_ack_pending":1000,"inactive_threshold":5000000000,"num_replicas":0}
@@ -1 +0,0 @@
3288f873d6615ff9
-1
View File
@@ -1 +0,0 @@
39825
-228
View File
@@ -1,228 +0,0 @@
//go:build ignore
package main
import (
"context"
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"strings"
"time"
"kit/ext"
)
const (
diagnosticsTimeout = 20 * time.Second
maxOutputBytes = 12_000
)
type toolPathInput struct {
Path string `json:"path"`
}
type lintResult struct {
Output string
Err error
}
func Init(api ext.API) {
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
ctx.Print("go-edit-lint extension loaded - will run gopls and golangci-lint on Go file edits")
})
api.OnToolResult(func(e ext.ToolResultEvent, ctx ext.Context) *ext.ToolResultResult {
if e.IsError || !isEditOrWrite(e.ToolName) {
return nil
}
absPath, ok := resolveGoFilePath(e.Input, ctx.CWD)
if !ok {
return nil
}
report := runGoDiagnostics(ctx.CWD, absPath)
// Check if there are issues and add explicit prompt for the LLM to react
goplsIssues, lintIssues := countIssues(report)
hasIssues := goplsIssues > 0 || lintIssues > 0
var enhanced string
if hasIssues {
enhanced = e.Content + "\n\n" + report + "\n\n⚠️ DIAGNOSTICS FOUND: Please review the issues above and fix them before proceeding."
} else {
enhanced = e.Content + "\n\n" + report
}
// Show TUI message block for diagnostics visibility (only if there are issues)
if hasIssues {
var msgLines []string
msgLines = append(msgLines, fmt.Sprintf("File: %s", filepath.Base(absPath)))
if goplsIssues > 0 {
msgLines = append(msgLines, fmt.Sprintf("gopls: %d issue(s)", goplsIssues))
}
if lintIssues > 0 {
msgLines = append(msgLines, fmt.Sprintf("golangci-lint: %d issue(s)", lintIssues))
}
msgLines = append(msgLines, "", "⚠️ Please fix these issues before proceeding.")
borderColor := "#f9e2af" // yellow
if goplsIssues > 0 && lintIssues > 0 {
borderColor = "#f38ba8" // red
}
ctx.PrintBlock(ext.PrintBlockOpts{
Text: strings.Join(msgLines, "\n"),
BorderColor: borderColor,
Subtitle: "go-edit-lint",
})
}
return &ext.ToolResultResult{Content: &enhanced}
})
}
func isEditOrWrite(toolName string) bool {
return strings.EqualFold(toolName, "edit") || strings.EqualFold(toolName, "write")
}
func resolveGoFilePath(inputJSON, cwd string) (string, bool) {
var args toolPathInput
if err := json.Unmarshal([]byte(inputJSON), &args); err != nil || args.Path == "" {
return "", false
}
absPath := args.Path
if !filepath.IsAbs(absPath) {
absPath = filepath.Join(cwd, absPath)
}
if strings.ToLower(filepath.Ext(absPath)) != ".go" {
return "", false
}
return absPath, true
}
func runGoDiagnostics(cwd, absPath string) string {
gopls := runGopls(cwd, absPath)
lint := runGolangCILint(cwd, "./...")
return fmt.Sprintf(
"<go_diagnostics file=%q>\n[gopls]\n%s\n\n[golangci-lint]\n%s\n</go_diagnostics>",
filepath.Base(absPath),
formatToolResult(gopls, "No diagnostics."),
formatToolResult(lint, "No lint issues."),
)
}
func runGopls(cwd, absPath string) lintResult {
ctx, cancel := context.WithTimeout(context.Background(), diagnosticsTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, "gopls", "check", absPath)
cmd.Dir = cwd
out, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return lintResult{Err: fmt.Errorf("timed out after %s", diagnosticsTimeout)}
}
if err != nil {
return lintResult{Output: truncate(string(out), maxOutputBytes), Err: fmt.Errorf("failed to run gopls check: %w", err)}
}
return lintResult{Output: truncate(string(out), maxOutputBytes)}
}
func runGolangCILint(cwd, target string) lintResult {
ctx, cancel := context.WithTimeout(context.Background(), diagnosticsTimeout)
defer cancel()
args := []string{
"run",
target,
"--show-stats=false",
"--output.text.path", "stdout",
"--output.text.colors=false",
"--output.text.print-issued-lines=false",
}
cmd := exec.CommandContext(ctx, "golangci-lint", args...)
cmd.Dir = cwd
out, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return lintResult{Err: fmt.Errorf("timed out after %s", diagnosticsTimeout)}
}
trimmed := truncate(string(out), maxOutputBytes)
if err == nil {
return lintResult{Output: trimmed}
}
exitErr, ok := err.(*exec.ExitError)
if ok && exitErr.ExitCode() == 1 {
return lintResult{Output: trimmed}
}
return lintResult{Output: trimmed, Err: fmt.Errorf("failed to run golangci-lint: %w", err)}
}
func formatToolResult(res lintResult, emptyFallback string) string {
var lines []string
if res.Err != nil {
lines = append(lines, "ERROR: "+res.Err.Error())
}
out := strings.TrimSpace(res.Output)
if out == "" {
if res.Err == nil {
lines = append(lines, emptyFallback)
}
} else {
lines = append(lines, out)
}
if len(lines) == 0 {
return emptyFallback
}
return strings.Join(lines, "\n")
}
func truncate(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max] + "\n... output truncated ..."
}
func countIssues(report string) (goplsCount, lintCount int) {
// Extract gopls section
goplsStart := strings.Index(report, "[gopls]")
lintStart := strings.Index(report, "[golangci-lint]")
endTag := strings.Index(report, "</go_diagnostics>")
if goplsStart != -1 && lintStart != -1 {
goplsSection := report[goplsStart:lintStart]
// Count non-empty lines excluding the header and "No diagnostics." message
for _, line := range strings.Split(goplsSection, "\n") {
line = strings.TrimSpace(line)
if line != "" && line != "[gopls]" && line != "No diagnostics." {
goplsCount++
}
}
}
if lintStart != -1 && endTag != -1 {
lintSection := report[lintStart:endTag]
// Count non-empty lines excluding the header and "No lint issues." message
for _, line := range strings.Split(lintSection, "\n") {
line = strings.TrimSpace(line)
if line != "" && line != "[golangci-lint]" && line != "No lint issues." {
lintCount++
}
}
}
return goplsCount, lintCount
}
-304
View File
@@ -1,304 +0,0 @@
//go:build ignore
// subagent-monitor — live horizontal widget strip for spawned subagents
//
// Subscribes to subagents spawned by the main Kit agent and displays a
// single widget just above the input box. Each subagent occupies one column
// in a side-by-side horizontal layout. Columns show scrolling real-time
// output as the subagent works. When a subagent finishes its column is
// removed automatically.
//
// Yaegi-safe design notes:
// - No sync.Mutex (Yaegi has reflection issues with sync primitives)
// - No channels in maps (Yaegi panics on range over map[string]chan)
// - All ctx.* calls guarded with nil checks
// - Simple data structures only
package main
import (
"fmt"
"strings"
"time"
"kit/ext"
)
// ---------------------------------------------------------------------------
// Per-subagent state
// ---------------------------------------------------------------------------
type submonEntry struct {
id int
callID string
task string
lines []string
started time.Time
elapsed time.Duration
}
const (
submonColWidth = 34 // visible character width per column
submonMaxLines = 5 // scrolling output lines per column
submonColGap = 2 // spaces between columns
)
// ---------------------------------------------------------------------------
// Package-level state - all simple types
// ---------------------------------------------------------------------------
var (
submonCtx ext.Context
submonHasCtx bool
submonEntries []*submonEntry
submonNextID int
)
func submonInit() {
submonEntries = nil
submonNextID = 1
}
// ---------------------------------------------------------------------------
// String helpers
// ---------------------------------------------------------------------------
func submonPad(s string, w int) string {
r := []rune(s)
if len(r) >= w {
return string(r[:w])
}
return s + strings.Repeat(" ", w-len(r))
}
func submonTrunc(s string, w int) string {
r := []rune(s)
if len(r) <= w {
return s
}
if w <= 1 {
return "…"
}
return string(r[:w-1]) + "…"
}
// ---------------------------------------------------------------------------
// Widget rendering
// ---------------------------------------------------------------------------
func submonRenderColumn(e *submonEntry) []string {
var rows []string
// Calculate elapsed time on-demand to avoid race conditions with ticker
elapsed := e.elapsed
if elapsed == 0 && !e.started.IsZero() {
elapsed = time.Since(e.started)
}
secs := int(elapsed.Seconds())
timeStr := fmt.Sprintf("%ds", secs)
taskMax := submonColWidth - len(timeStr) - 3
taskPart := submonTrunc(e.task, taskMax)
header := fmt.Sprintf("#%d %s %s", e.id, taskPart, timeStr)
rows = append(rows, submonPad(header, submonColWidth))
display := e.lines
if len(display) > submonMaxLines {
display = display[len(display)-submonMaxLines:]
}
for _, l := range display {
rows = append(rows, submonPad(" "+submonTrunc(l, submonColWidth-2), submonColWidth))
}
for len(rows) < submonMaxLines+1 {
if len(rows) == 1 && len(e.lines) == 0 {
rows = append(rows, submonPad(" waiting…", submonColWidth))
} else {
rows = append(rows, strings.Repeat(" ", submonColWidth))
}
}
return rows
}
func submonBuildWidget() string {
if len(submonEntries) == 0 {
return ""
}
numCols := len(submonEntries)
numRows := submonMaxLines + 1
cols := make([][]string, numCols)
for i, e := range submonEntries {
rows := submonRenderColumn(e)
col := make([]string, numRows)
for j := 0; j < numRows; j++ {
if j < len(rows) {
col[j] = rows[j]
} else {
col[j] = strings.Repeat(" ", submonColWidth)
}
}
cols[i] = col
}
gap := strings.Repeat(" ", submonColGap)
var sb strings.Builder
for row := 0; row < numRows; row++ {
for ci := range cols {
if ci > 0 {
sb.WriteString(gap)
}
sb.WriteString(cols[ci][row])
}
if row < numRows-1 {
sb.WriteString("\n")
}
}
return sb.String()
}
func submonPushWidget() {
if !submonHasCtx {
return
}
if submonCtx.SetWidget == nil {
return
}
text := submonBuildWidget()
if len(submonEntries) == 0 {
if submonCtx.RemoveWidget != nil {
submonCtx.RemoveWidget("submon")
}
return
}
submonCtx.SetWidget(ext.WidgetConfig{
ID: "submon",
Placement: ext.WidgetAbove,
Content: ext.WidgetContent{Text: text},
Style: ext.WidgetStyle{BorderColor: "#89b4fa"},
Priority: 0,
})
}
func submonAppendLine(e *submonEntry, line string) {
line = strings.TrimRight(line, "\r\n")
if strings.TrimSpace(line) == "" {
return
}
e.lines = append(e.lines, line)
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
func Init(api ext.API) {
submonInit()
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
submonCtx = ctx
submonHasCtx = true
submonInit()
if ctx.RemoveWidget != nil {
ctx.RemoveWidget("submon")
}
})
api.OnAgentEnd(func(_ ext.AgentEndEvent, ctx ext.Context) {
submonCtx = ctx
submonHasCtx = true
})
// ── SubagentStart ────────────────────────────────────────────────────────
api.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {
submonCtx = ctx
submonHasCtx = true
id := submonNextID
submonNextID++
entry := &submonEntry{
id: id,
callID: e.ToolCallID,
task: e.Task,
started: time.Now(),
}
submonEntries = append(submonEntries, entry)
submonPushWidget()
})
// ── SubagentChunk ────────────────────────────────────────────────────────
api.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {
submonCtx = ctx
submonHasCtx = true
var entry *submonEntry
for _, en := range submonEntries {
if en.callID == e.ToolCallID {
entry = en
break
}
}
if entry == nil {
return
}
switch e.ChunkType {
case "text":
for _, line := range strings.Split(e.Content, "\n") {
submonAppendLine(entry, line)
}
case "tool_call":
submonAppendLine(entry, "→ "+e.ToolName)
case "tool_execution_start":
submonAppendLine(entry, "⚙ "+e.ToolName)
case "tool_result":
if e.IsError {
submonAppendLine(entry, "✗ "+e.ToolName)
} else {
submonAppendLine(entry, "✓ "+e.ToolName)
}
}
submonPushWidget()
})
// ── SubagentEnd ──────────────────────────────────────────────────────────
api.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {
submonCtx = ctx
submonHasCtx = true
var entry *submonEntry
for _, en := range submonEntries {
if en.callID == e.ToolCallID {
entry = en
break
}
}
if entry != nil {
entry.elapsed = time.Since(entry.started)
if e.ErrorMsg != "" {
submonAppendLine(entry, "✗ "+submonTrunc(e.ErrorMsg, submonColWidth-2))
}
}
submonPushWidget()
// Remove the entry immediately (no goroutine to avoid races)
newEntries := submonEntries[:0]
for _, en := range submonEntries {
if en.callID != e.ToolCallID {
newEntries = append(newEntries, en)
}
}
submonEntries = newEntries
submonPushWidget()
})
// ── SessionShutdown ──────────────────────────────────────────────────────
api.OnSessionShutdown(func(_ ext.SessionShutdownEvent, ctx ext.Context) {
submonInit()
// Guard ctx access - may be nil during shutdown
if ctx.RemoveWidget != nil {
ctx.RemoveWidget("submon")
}
})
}
-70
View File
@@ -1,70 +0,0 @@
---
description: Semantic version tagging workflow - analyzes commits and tags releases
---
# Release Tagging Workflow
Tag a new version of this Go project following semantic versioning.
## Steps
1. **Fetch remote tags**: `git fetch --tags origin`
2. **Find latest version**: `git tag -l | sort -V | tail -5` to see recent tags
3. **Analyze changes since last tag**:
- `git log <latest-tag>..HEAD --oneline` - list commits
- `git diff <latest-tag>..HEAD --stat` - see file stats
- `git diff <latest-tag>..HEAD --name-only` - see changed files
4. **Determine version bump** (Semantic Versioning):
- **MAJOR (X.0.0)**: Breaking API changes, incompatible modifications
- **MINOR (0.X.0)**: New features, backward-compatible additions
- **PATCH (0.0.X)**: Bug fixes, backward-compatible fixes
Look for indicators:
- `feat:` or `feature:` commits → MINOR
- `fix:` or `bugfix:` commits → PATCH
- `breaking:` or `BREAKING CHANGE:` → MAJOR
- Breaking API changes in `pkg/` or public interfaces → MAJOR
- New commands, flags, or features → MINOR
- Documentation-only changes → PATCH (or skip)
5. **Calculate new version**: Increment appropriate segment, reset lower segments to 0
6. **Draft tag message**:
- Summarize key changes from commits
- Group by type (Features, Fixes, Breaking Changes)
- Keep concise but informative
7. **Create annotated tag**: `git tag -a vX.Y.Z -m "vX.Y.Z - <summary>\n\n<detailed list>"`
8. **Push tag**: `git push origin vX.Y.Z`
## Guidelines
- Always fetch remote tags first to avoid conflicts
- Use annotated tags (`-a`) with descriptive messages
- Follow semver strictly - when in doubt, prefer conservative bump (patch over minor)
- For Go projects, changes to `pkg/` or exported APIs warrant careful version consideration
- If no changes since last tag, suggest skipping the release
- Include commit summaries in the tag message body
## Example Tag Message Format
```
v0.30.1 - Bug fixes for model handling and UI improvements
Fixes:
- Properly handle think tags from Qwen/DeepSeek models
- Handle custom provider model persistence and bare model names
Improvements:
- UI style refactoring and cleanup
```
Wait for the user to confirm the version and message before executing tag commands.
---
$@
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"external_directory": {
"~/go/**": "deny"
}
}
}
-120
View File
@@ -1,120 +0,0 @@
# KIT Agent Guidelines
## Build/Test Commands
- **Build**: `go build -o output/kit ./cmd/kit`
- **Test all**: `go test -race ./...`
- **Test single**: `go test -race ./cmd -run TestScriptExecution`
- **Lint**: `go vet ./...`
- **Format**: `go fmt ./...`
## Code Style
- **Imports**: stdlib → third-party → local (blank lines between)
- **Naming**: camelCase (unexported), PascalCase (exported)
- **Errors**: Always check, wrap with `fmt.Errorf("context: %w", err)`
- **Logging**: Use `github.com/charmbracelet/log` structured logging
- **Types**: Prefer `any` over `interface{}`
- **JSON**: snake_case tags with `omitempty` where appropriate
- **Context**: First parameter for blocking operations
## Architecture
- Multi-provider LLM support via `llm.Provider` interface
- MCP client-server for tool integration
- Builtin servers: bash, fetch, todo, fs
- **Extension system** (`internal/extensions/`): Yaegi-interpreted Go, 13 lifecycle events, custom tools/commands/widgets/overlays/editor interceptors
- **TUI** (`internal/ui/`): Bubble Tea v2 parent-child model (`AppModel``InputComponent`, `StreamComponent`, etc.)
- **Decoupling pattern**: `cmd/root.go` has converter functions (e.g. `widgetProviderForUI()`) that bridge `internal/extensions/` types to `internal/ui/` types — the UI never imports extensions directly
- **Public SDK** (`pkg/kit/`): The public-facing Go SDK for embedding Kit as a library. See rules below.
## Public SDK (`pkg/kit/`) Rules
`pkg/kit/` is the **public API surface** consumed by external Go developers. All exported symbols, types, function names, and godoc comments in this package are part of the SDK contract.
### No Dependency Name Leakage
Internal dependency names (e.g. `charm.land/fantasy`, library-specific jargon) **must not** appear in:
- **Exported function/method names** — use generic terms (`LLM`, `Provider`, `Message`) instead of library names
- **Exported type names** — type aliases should use domain names (e.g. `LLMMessage`, not `FantasyMessage`)
- **Godoc comments** on exported symbols — these are visible in `go doc` output and pkg.go.dev
- **Struct field names and tags** on exported types
Using dependency types directly in **function bodies** (private implementation) is fine — that's invisible to SDK consumers.
### Naming Conventions for SDK Symbols
- Type aliases re-exporting dependency types: use `LLM*` prefix (e.g. `LLMMessage`, `LLMUsage`, `LLMResponse`)
- Conversion helpers: use `ConvertToLLM*` / `ConvertFromLLM*` (not the dependency name)
- Provider queries: use `GetLLMProviders` (not `GetFantasyProviders`)
- When wrapping internal methods, the `pkg/kit/` name should be dependency-agnostic even if the `internal/` method still uses the old name
### Deprecation Pattern
When renaming a public SDK symbol, keep the old name as a deprecated wrapper for one release cycle:
```go
// Deprecated: Use NewName instead.
func OldName() { return NewName() }
```
## Key Patterns
### Yaegi (Extension Interpreter) Gotchas
- **No interfaces across boundary**: All extension-facing API types must be concrete structs, never interfaces. Yaegi crashes on interface wrapper generation.
- **Function field bug**: Named function references assigned to struct fields return zero values across the interpreter boundary. Always use anonymous closure literals:
```go
// WRONG: ctx.SetEditor(ext.EditorConfig{HandleKey: myHandler})
// RIGHT: ctx.SetEditor(ext.EditorConfig{HandleKey: func(k, t string) ext.EditorKeyAction { return myHandler(k, t) }})
```
- **Symbol exports**: Every new type exposed to extensions must be added to `internal/extensions/symbols.go`
### BubbleTea Integration
- **No `prog.Send()` from inside `Update()`**: Calling `prog.Send()` synchronously within a BubbleTea `Update()` handler deadlocks the event loop. Use `go appInstance.NotifyWidgetUpdate()` (async goroutine) instead.
- **Height measurement**: `distributeHeight()` in `model.go` must measure using the same render path as `View()`. If an interceptor wraps rendering, measure with the wrapper too, or layout will mismatch.
- **Channel-based prompts**: Extension prompt calls (PromptSelect, etc.) block on a `chan PromptResponse`. Extension slash commands run in dedicated goroutines (not `tea.Cmd`) to avoid stalling BubbleTea's Cmd scheduler.
### Extension State Management
- **Thread-safe maps on Runner**: Widget/header/footer/editor state lives on the Runner with `sync.RWMutex`, queried by UI via callbacks
- **Context function fields**: The `Context` struct uses function fields (`Print func(string)`, `SetWidget func(WidgetConfig)`) wired by closures in `cmd/root.go`
- **Package-level vars in extensions**: Yaegi supports package-level variables captured in closures — this is how extensions maintain state across event callbacks
### Unicode in Widget Text
- Widget content renders through `lipgloss.Style.Render()` which preserves ANSI escape codes
- Use rune-based width calculations (`len([]rune(s))`) not byte length (`len(s)`) when aligning box-drawing characters or multi-byte symbols
## Testing
### Interactive TUI Testing with tmux
Use tmux to test Kit interactively without blocking the agent:
```bash
tmux new-session -d -s kittest -x 120 -y 40 "output/kit -e examples/extensions/my-ext.go --no-session 2>kit_stderr.log"
sleep 3
tmux capture-pane -t kittest -p # read screen
tmux send-keys -t kittest '/command' Enter # send input
tmux kill-session -t kittest # cleanup
```
### Non-Interactive Kit (Subprocess Spawning)
Extensions can spawn Kit as a subprocess for sub-agent patterns:
```bash
kit --quiet --no-session --no-extensions --system-prompt /path/to/prompt.txt --model provider/model "question"
```
Positional args are the prompt. `@file` args attach file content. Key flags: `--quiet` (stdout only, no TUI), `--no-session` (ephemeral), `--no-extensions` (prevent recursive loading), `--system-prompt` (string or file path).
## External Repo Research
- **ALWAYS use `btca`** to search external repos (e.g. iteratr, other reference codebases)
- Never guess or manually search the filesystem for external projects
- Example: `btca ask -r https://github.com/user/repo -q "How does X work?"`
- See `.agents/skills/btca-cli/SKILL.md` for full btca usage
## BTCA Configured Resources
The following external repositories are configured in `btca.config.jsonc` for research:
- bubbletea
- lipgloss
- bubbles
- glamour
- fantasy
- catwalk
- crush
- pi
- iteratr
- yaegi
- acp-go-sdk
- opencode
- herald
- herald-md
+1
View File
@@ -0,0 +1 @@
go-kit.dev
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Mark III Labs, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-792
View File
@@ -1,792 +0,0 @@
<p align="center">
<img src="logo.jpg" alt="KIT" width="400">
</p>
<p align="center">
<a href="https://github.com/mark3labs/kit/actions/workflows/ci.yml"><img src="https://github.com/mark3labs/kit/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://github.com/mark3labs/kit/releases/latest"><img src="https://img.shields.io/github/v/release/mark3labs/kit?style=flat&color=blue" alt="Release"></a>
<a href="https://www.npmjs.com/package/@mark3labs/kit"><img src="https://img.shields.io/npm/v/@mark3labs/kit?style=flat&color=cb3837" alt="npm"></a>
<a href="https://pkg.go.dev/github.com/mark3labs/kit"><img src="https://pkg.go.dev/badge/github.com/mark3labs/kit.svg" alt="Go Reference"></a>
<a href="https://github.com/mark3labs/kit/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mark3labs/kit?style=flat" alt="License"></a>
<a href="https://discord.gg/RqSS2NQVsY"><img src="https://img.shields.io/badge/Discord-community-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"></a>
</p>
# KIT (Knowledge Inference Tool)
A powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.
## Features
- **Multi-Provider LLM Support**: Anthropic, OpenAI, Google Gemini, Ollama, Azure OpenAI, AWS Bedrock, OpenRouter, and more
- **Built-in Core Tools**: bash, read, write, edit, grep, find, ls, subagent - no MCP overhead
- **MCP Integration**: Connect external MCP servers for expanded capabilities
- **Extension System**: Write custom tools, commands, widgets, and UI modifications in Go
- **Theming**: 22 built-in color themes (KITT, Catppuccin, Dracula, Nord, etc.) with runtime switching, persistence, and custom theme files
- **Model Persistence**: Model and thinking level selections are automatically saved and restored across sessions
- **Prompt Templates**: Create reusable prompt templates with shell-style argument substitution
- **Interactive TUI**: Rich terminal interface powered by Bubble Tea with streaming, syntax highlighting, and custom rendering
- **Session Management**: Tree-based conversation history with branching support
- **Non-Interactive Mode**: Script-friendly positional args with JSON output
- **ACP Server**: Run Kit as an [Agent Client Protocol](https://agentclientprotocol.com) agent over stdio
- **Go SDK**: Embed Kit in your own applications
## Installation
### Using npm / bun / pnpm
```bash
npm install -g @mark3labs/kit
# or
bun install -g @mark3labs/kit
# or
pnpm install -g @mark3labs/kit
```
### Using Go
```bash
go install github.com/mark3labs/kit/cmd/kit@latest
```
### Building from source
```bash
git clone https://github.com/mark3labs/kit.git
cd kit
go build -o kit ./cmd/kit
```
## Quick Start
### Basic Usage
```bash
# Start interactive session
kit
# Run a one-off prompt
kit "List files in src/"
# Attach files as context
kit @main.go @test.go "Review these files"
# Continue the most recent session
kit --continue
# Model and thinking level selections are automatically persisted
# across sessions and restored on next launch
# Use specific model
kit --model anthropic/claude-sonnet-latest
```
### Non-Interactive Mode
```bash
# Get JSON output for scripting
kit "Explain main.go" --json
# Quiet mode (final response only)
kit "Run tests" --quiet
# Ephemeral mode (no session file)
kit "Quick question" --no-session
```
### ACP Server Mode
Kit can run as an [ACP (Agent Client Protocol)](https://agentclientprotocol.com) agent server, enabling ACP-compatible clients (such as [OpenCode](https://github.com/sst/opencode)) to drive Kit as a remote coding agent over stdio.
```bash
# Start Kit as an ACP server (communicates via JSON-RPC 2.0 on stdin/stdout)
kit acp
# With debug logging to stderr
kit acp --debug
```
The ACP server exposes Kit's full capabilities — LLM execution, tool calls (bash, read, write, edit, grep, etc.), and session persistence — over the standard ACP protocol. Sessions are persisted to Kit's normal JSONL session files, so they can be resumed later.
## Configuration
Kit looks for configuration in the following locations (in order of priority):
1. CLI flags
2. Environment variables (with `KIT_` prefix)
3. `./.kit.yml` / `./.kit.yaml` / `./.kit.json` (project-local)
4. `~/.kit.yml` / `~/.kit.yaml` / `~/.kit.json` (global)
### Basic Configuration
Create `~/.kit.yml`:
```yaml
model: anthropic/claude-sonnet-latest
max-tokens: 4096
temperature: 0.7
stream: true
```
### Environment Variables
```bash
export ANTHROPIC_API_KEY="sk-..."
export OPENAI_API_KEY="sk-..."
export KIT_MODEL="openai/gpt-4o"
```
### MCP Server Configuration
Add external MCP servers to `.kit.yml`:
```yaml
mcpServers:
filesystem:
type: local
command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed"]
environment:
LOG_LEVEL: "info"
allowedTools: ["read_file", "write_file"]
search:
type: remote
url: "https://mcp.example.com/search"
```
## CLI Reference
### Global Flags
```bash
# Model and provider
--model, -m Model to use (provider/model format)
--provider-api-key API key for the provider
--provider-url Base URL for provider API
--tls-skip-verify Skip TLS certificate verification
# Session management
--session, -s Open specific JSONL session file
--continue, -c Resume most recent session for current directory
--resume, -r Interactive session picker
--no-session Ephemeral mode, no persistence
# Behavior (non-interactive: pass prompt as positional arg)
--quiet Suppress all output (non-interactive only)
--json Output response as JSON (non-interactive only)
--no-exit Enter interactive mode after prompt completes
--max-steps Maximum agent steps (0 for unlimited)
--stream Enable streaming output (default: true)
--compact Enable compact output mode
--auto-compact Auto-compact conversation near context limit
# Extensions
--extension, -e Load additional extension file(s) (repeatable)
--no-extensions Disable all extensions
--prompt-template Load a specific prompt template by name
--no-prompt-templates Disable prompt template loading
# Generation parameters
--max-tokens Maximum tokens in response (default: 4096)
--temperature Randomness 0.0-1.0 (default: 0.7)
--top-p Nucleus sampling 0.0-1.0 (default: 0.95)
--top-k Limit top K tokens (default: 40)
--stop-sequences Custom stop sequences (comma-separated)
--thinking-level Extended thinking level: off, minimal, low, medium, high (default: off)
# System
--config Config file path (default: ~/.kit.yml)
--system-prompt System prompt text or file path
--debug Enable debug logging
```
### Commands
```bash
# Authentication (for OAuth-enabled providers)
kit auth login [provider] # Start OAuth flow (e.g., anthropic)
kit auth logout [provider] # Remove credentials for provider
kit auth status # Check authentication status
# Model database
kit models [provider] # List available models (optionally filter by provider)
kit models --all # Show all providers (not just LLM-compatible)
kit update-models [source] # Update model database (from models.dev, URL, file, or 'embedded')
# Extension management
kit extensions list # List discovered extensions
kit extensions validate # Validate extension files
kit extensions init # Generate example extension template
kit install <git-url> # Install extensions from git repositories
kit install -l <git-url> # Install to project-local .kit/git/ directory
kit install -u <git-url> # Update an already-installed package
kit install --uninstall <pkg> # Remove an installed package
# Skills
kit skill # Install the Kit extensions skill via skills.sh
# ACP server
kit acp # Start as ACP agent (stdio JSON-RPC)
kit acp --debug # With debug logging to stderr
```
## Themes
Kit ships with 22 built-in color themes that control all UI elements. Switch at runtime:
```
/theme dracula
/theme catppuccin
/theme tokyonight
```
Theme selections are automatically saved and restored on next launch (stored in `~/.config/kit/preferences.yml`). This persistence also applies to **model** and **thinking level** selections — all are saved together and restored on startup.
### Custom themes
Drop a `.yml` file in `~/.config/kit/themes/` (user) or `.kit/themes/` (project):
```yaml
# ~/.config/kit/themes/my-theme.yml
primary:
light: "#8839ef"
dark: "#cba6f7"
success:
light: "#40a02b"
dark: "#a6e3a1"
```
Built-in themes: `kitt`, `catppuccin`, `dracula`, `tokyonight`, `nord`, `gruvbox`, `monokai`, `solarized`, `github`, `one-dark`, `rose-pine`, `ayu`, `material`, `everforest`, `kanagawa`, `amoled`, `synthwave`, `vesper`, `flexoki`, `matrix`, `vercel`, `zenburn`
## Extension System
Extensions are Go source files that run via Yaegi interpreter. They can add custom tools, slash commands, widgets, keyboard shortcuts, themes, and intercept lifecycle events.
### Minimal Extension
```go
//go:build ignore
package main
import "kit/ext"
func Init(api ext.API) {
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
ctx.SetFooter(ext.HeaderFooterConfig{
Content: ext.WidgetContent{Text: "Custom Footer"},
})
})
}
```
**Usage:**
```bash
kit -e examples/extensions/minimal.go
```
### Extension Capabilities
**Lifecycle Events**: OnSessionStart, OnSessionShutdown, OnBeforeAgentStart, OnAgentStart, OnAgentEnd, OnToolCall, OnToolExecutionStart, OnToolOutput, OnToolExecutionEnd, OnToolResult, OnInput, OnMessageStart, OnMessageUpdate, OnMessageEnd, OnModelChange, OnContextPrepare, OnBeforeFork, OnBeforeSessionSwitch, OnBeforeCompact, OnCustomEvent, OnSubagentStart, OnSubagentChunk, OnSubagentEnd
**Custom Components**:
- **Tools**: Add new tools the LLM can invoke
- **Commands**: Register slash commands (e.g., `/mycommand`)
- **Options**: Register configurable extension options
- **Widgets**: Persistent status displays above/below input
- **Headers/Footers**: Persistent content above/below the conversation
- **Status Bar**: Custom status bar entries
- **Shortcuts**: Global keyboard shortcuts
- **Overlays**: Modal dialogs with markdown content
- **Tool Renderers**: Customize how tool calls display
- **Message Renderers**: Custom rendering for assistant messages
- **Editor Interceptors**: Handle key events and wrap rendering
- **Interactive Prompts**: Select, confirm, input, and multi-select dialogs
- **Subagents**: Spawn in-process child Kit instances
- **LLM Completion**: Direct model calls via `Complete()`
- **Themes**: Register and switch color themes via `RegisterTheme`, `SetTheme`, `ListThemes`
- **Custom Events**: Inter-extension communication via `EmitCustomEvent`
**Bridged SDK APIs** (NEW): Extensions can now access internal SDK capabilities:
- **Tree Navigation**: Navigate conversation history (`GetTreeNode`, `GetCurrentBranch`, `NavigateTo`), summarize branches (`SummarizeBranch`), and implement fresh context loops (`CollapseBranch`)
- **Skill Loading**: Dynamically load and inject skills at runtime (`LoadSkill`, `DiscoverSkills`, `InjectSkillAsContext`)
- **Template Parsing**: Parse and render templates with `{{variables}}` (`ParseTemplate`, `RenderTemplate`), parse CLI-style arguments (`ParseArguments`, `SimpleParseArguments`), and evaluate model conditionals (`EvaluateModelConditional`, `RenderWithModelConditionals`)
- **Model Resolution**: Resolve model fallback chains (`ResolveModelChain`), query model capabilities (`GetModelCapabilities`, `CheckModelAvailable`), and extract provider/model ID (`GetCurrentProvider`, `GetCurrentModelID`)
### Extension Examples
See the `examples/extensions/` directory:
- `minimal.go` - Clean UI with custom footer
- `auto-commit.go` - Auto-commit on shutdown
- `bookmark.go` - Bookmark conversations
- `branded-output.go` - Branded output rendering
- `compact-notify.go` - Notification on compaction
- `confirm-destructive.go` - Confirm destructive operations
- `context-inject.go` - Inject context into conversations
- `conversation-manager.go` - **NEW** Tree navigation, branch summarization, and fresh context loops
- `custom-editor-demo.go` - Vim-like modal editor
- `dev-reload.go` - Development live-reload
- `header-footer-demo.go` - Custom headers and footers
- `inline-bash.go` - Inline bash execution
- `interactive-shell.go` - Interactive shell integration
- `kit-kit.go` - Kit-in-Kit (sub-agent spawning)
- `lsp-diagnostics.go` - LSP diagnostic integration
- `notify.go` - Desktop notifications
- `overlay-demo.go` - Modal dialogs
- `permission-gate.go` - Permission gating for tools
- `pirate.go` - Pirate-themed personality
- `plan-mode.go` - Read-only planning mode
- `project-rules.go` - Project-specific rules
- `prompt-demo.go` - Interactive prompts (select/confirm/input)
- `prompt-templates.go` - **NEW** Frontmatter-driven templates with model switching and skill injection
- `protected-paths.go` - Path protection for sensitive files
- `subagent-widget.go` - Multi-agent orchestration with status widget
- `subagent-test.go` - Subagent testing utilities
- `summarize.go` - Conversation summarization
- `tool-logger.go` - Log all tool calls
- `neon-theme.go` - Custom theme registration and switching
- `tool-renderer-demo.go` - Custom tool call rendering
- `widget-status.go` - Persistent status widgets
Also see `.kit/extensions/go-edit-lint.go` (in this repo) for a project-local extension example that runs gopls and golangci-lint on Go file edits.
### Loading Extensions
**Auto-discovery** (loads automatically):
- `~/.config/kit/extensions/*.go` (global single files)
- `~/.config/kit/extensions/*/main.go` (global subdirectory extensions)
- `.kit/extensions/*.go` (project-local single files)
- `.kit/extensions/*/main.go` (project-local subdirectory extensions)
- `~/.local/share/kit/git/` (global git-installed packages)
- `.kit/git/` (project-local git-installed packages)
**Explicit loading**:
```bash
kit -e path/to/extension.go
kit -e ext1.go -e ext2.go # Multiple extensions
```
**Disable auto-load**:
```bash
kit --no-extensions
```
### Testing Extensions
Kit provides a testing package to help you write unit tests for your extensions:
```go
package main
import (
"testing"
"github.com/mark3labs/kit/pkg/extensions/test"
"github.com/mark3labs/kit/internal/extensions"
)
func TestMyExtension(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Emit events and verify behavior
_, err := harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Verify the extension printed something
test.AssertPrinted(t, harness, "session started")
}
```
**Available assertions:**
- `AssertBlocked()`, `AssertNotBlocked()` — Verify tool blocking
- `AssertWidgetSet()`, `AssertWidgetText()` — Verify widget content
- `AssertPrinted()`, `AssertPrintedContains()` — Verify output
- `AssertToolRegistered()`, `AssertCommandRegistered()` — Verify registration
See `examples/extensions/tool-logger_test.go` for a complete example with 14 test cases covering tool calls, input handling, and session lifecycle.
### Prompt Templates
Create reusable prompt templates with shell-style argument substitution. Templates are loaded from `~/.kit/prompts/*.md` and `.kit/prompts/*.md`.
**Example template** (`~/.kit/prompts/review.md`):
```markdown
---
description: Review code for issues
---
Review the following code for bugs and security issues.
Focus on $1 specifically.
```
**Usage:**
```
/review error handling
```
**Argument placeholders:**
- `$1`, `$2`, etc. — Individual arguments
- `$@` or `$ARGUMENTS` — All arguments
- `${@:2}` — Arguments from position 2 onwards
- `${@:1:3}` — 3 arguments starting at position 1
Disable templates with `--no-prompt-templates` or load a specific template with `--prompt-template <name>`.
## Session Management
Kit uses a tree-based session model that supports branching and forking conversations.
### Session Locations
- Default: `~/.kit/sessions/<cwd-path>/<timestamp>_<id>.jsonl`
- Path separators in the working directory are replaced with `--` (e.g., `/home/user/project` becomes `home--user--project`)
- Each line is a session entry (messages, tool calls, extension data)
- Supports branching from any message to explore alternate paths
### Session Commands
```bash
# Resume most recent session for current directory
kit --continue
kit -c
# Interactive session picker
kit --resume
kit -r
# Open specific session file
kit --session path/to/session.jsonl
kit -s path/to/session.jsonl
# Ephemeral mode (no file persistence)
kit --no-session
```
### Interactive Session Commands
During an interactive session, use these slash commands:
| Command | Description |
|---------|-------------|
| `/name [name]` | Set or display the session's display name |
| `/session` | Show session info (path, ID, message count) |
| `/resume` | Open the session picker to switch sessions |
| `/export [path]` | Export session as JSONL (auto-generates path if omitted) |
| `/import <path>` | Import and switch to a session from a JSONL file |
| `/share` | Upload session to GitHub Gist and get a shareable viewer URL |
| `/tree` | Navigate the session tree |
| `/fork` | Branch from an earlier message |
| `/new` | Start a fresh session |
## Go SDK
Embed Kit in your Go applications:
```go
package main
import (
"context"
"log"
kit "github.com/mark3labs/kit/pkg/kit"
)
func main() {
ctx := context.Background()
// Create Kit instance with default configuration
host, err := kit.New(ctx, nil)
if err != nil {
log.Fatal(err)
}
defer func() { _ = host.Close() }()
// Send a prompt
response, err := host.Prompt(ctx, "What is 2+2?")
if err != nil {
log.Fatal(err)
}
println(response)
}
```
### With Options
```go
host, err := kit.New(ctx, &kit.Options{
Model: "ollama/llama3",
SystemPrompt: "You are a helpful bot",
ConfigFile: "/path/to/config.yml",
MaxSteps: 10,
Streaming: true,
Quiet: true,
// Session options
SessionPath: "./session.jsonl", // Open specific session
Continue: true, // Resume most recent session
NoSession: true, // Ephemeral mode
// Tool options
ExtraTools: []kit.Tool{...}, // Additional tools alongside defaults
// Compaction
AutoCompact: true, // Auto-compact near context limit
Debug: true, // Debug logging
})
```
### With Callbacks
```go
unsub := host.OnToolCall(func(e kit.ToolCallEvent) {
println("Calling tool:", e.ToolName)
})
defer unsub()
unsub2 := host.OnToolResult(func(e kit.ToolResultEvent) {
if e.IsError {
println("Tool failed:", e.ToolName)
}
})
defer unsub2()
unsub3 := host.OnStreaming(func(e kit.MessageUpdateEvent) {
print(e.Chunk)
})
defer unsub3()
response, err := host.Prompt(
ctx,
"List files in current directory",
)
```
### Session Management
```go
// Multi-turn conversations retain context automatically
host.Prompt(ctx, "My name is Alice")
response, _ := host.Prompt(ctx, "What's my name?")
// Sessions are persisted automatically to JSONL files.
// Access session info:
path := host.GetSessionPath()
id := host.GetSessionID()
// Clear conversation history
host.ClearSession()
```
Session persistence is configured via `Options`:
```go
host, _ := kit.New(ctx, &kit.Options{
SessionPath: "./my-session.jsonl", // Open specific session
Continue: true, // Resume most recent session
NoSession: true, // Ephemeral mode
})
```
## Advanced Usage
### Subagent Pattern
Spawn Kit as a subprocess for multi-agent orchestration:
```bash
kit "Analyze codebase" \
--json \
--no-session \
--no-extensions \
--quiet \
--model anthropic/claude-haiku-3-5-20241022
```
Parse the JSON output:
```json
{
"response": "Final assistant response text",
"model": "anthropic/claude-haiku-3-5-20241022",
"stop_reason": "end_turn",
"session_id": "a1b2c3d4e5f6",
"usage": {
"input_tokens": 1024,
"output_tokens": 512,
"total_tokens": 1536,
"cache_read_tokens": 0,
"cache_creation_tokens": 0
},
"messages": [
{
"role": "assistant",
"parts": [
{"type": "text", "data": "..."},
{"type": "tool_call", "data": {"name": "...", "args": "..."}},
{"type": "tool_result", "data": {"name": "...", "result": "..."}}
]
}
]
}
```
### Testing with tmux
Test the TUI non-interactively:
```bash
# Start Kit in detached tmux session
tmux new-session -d -s kittest -x 120 -y 40 \
"kit -e ext.go --no-session 2>kit.log"
# Wait for startup
sleep 3
# Capture screen
tmux capture-pane -t kittest -p
# Send input
tmux send-keys -t kittest '/command' Enter
# Cleanup
tmux kill-session -t kittest
```
## Development
### Build and Test
```bash
# Build
go build -o output/kit ./cmd/kit
# Run tests
go test -race ./...
# Run specific test
go test -race ./cmd -run TestScriptExecution
# Lint
go vet ./...
# Format
go fmt ./...
```
### Project Structure
```
cmd/kit/ - CLI entry point (main.go)
cmd/ - CLI command implementations (root, auth, models, etc.)
pkg/kit/ - Go SDK for embedding Kit
internal/app/ - Application orchestrator (agent loop, message store, queue)
internal/agent/ - Agent execution and tool dispatch
internal/auth/ - OAuth authentication and credential storage
internal/acpserver/ - ACP (Agent Client Protocol) server
internal/clipboard/ - Cross-platform clipboard operations
internal/compaction/ - Conversation compaction and summarization
internal/config/ - Configuration management
internal/core/ - Built-in tools (bash, read, write, edit, grep, find, ls)
internal/extensions/ - Yaegi extension system
internal/kitsetup/ - Initial setup wizard
internal/message/ - Message content types and structured content blocks
internal/models/ - Provider and model management
internal/session/ - Session persistence (tree-based JSONL)
internal/skills/ - Skill loading and system prompt composition
internal/tools/ - MCP tool integration
internal/ui/ - Bubble Tea TUI components
examples/extensions/ - Example extension files
npm/ - NPM package wrapper for distribution
```
## Supported Providers
- **Anthropic** - Claude models (native, prompt caching, OAuth)
- **OpenAI** - GPT models
- **Google** - Gemini models
- **Ollama** - Local models
- **Azure OpenAI** - Azure-hosted OpenAI
- **AWS Bedrock** - Bedrock models
- **Google Vertex** - Claude on Vertex AI
- **OpenRouter** - Multi-provider router
- **Vercel AI** - Vercel AI SDK models
- **Custom** - Any OpenAI-compatible endpoint via `--provider-url`
- **Auto-routed** - Any provider from models.dev database
### Custom Provider
Use `custom/custom` when pointing Kit at any OpenAI-compatible endpoint with `--provider-url`:
```bash
kit --provider-url "http://localhost:8080/v1" "Hello"
```
This automatically defaults to `custom/custom` without needing to specify a model. The custom provider routes through the `openaicompat` provider and supports:
- Zero cost tracking (input/output = 0)
- 262K context window, 65K output limit
- Reasoning and temperature support
- Optional `CUSTOM_API_KEY` environment variable or `--provider-api-key` flag
### Model String Format
```bash
provider/model # Standard format
anthropic/claude-sonnet-latest
openai/gpt-4o
ollama/llama3
google/gemini-2.0-flash-exp
```
### Model Aliases
```bash
# Anthropic Claude
claude-opus-latest → claude-opus-4-6
claude-sonnet-latest → claude-sonnet-4-6
claude-haiku-latest → claude-haiku-4-5
claude-4-opus-latest → claude-opus-4-6
claude-4-sonnet-latest → claude-sonnet-4-6
claude-4-haiku-latest → claude-haiku-4-5
claude-3-7-sonnet-latest → claude-3-7-sonnet-20250219
claude-3-5-sonnet-latest → claude-3-5-sonnet-20241022
claude-3-5-haiku-latest → claude-3-5-haiku-20241022
claude-3-opus-latest → claude-3-opus-20240229
# OpenAI GPT
o1-latest → o1
o3-latest → o3
o4-latest → o4-mini
gpt-5-latest → gpt-5.4
gpt-5-chat-latest → gpt-5.4
gpt-4-latest → gpt-4o
gpt-4 → gpt-4o
gpt-3.5-latest → gpt-3.5-turbo
gpt-3.5 → gpt-3.5-turbo
codex-latest → codex-mini-latest
# Google Gemini
gemini-pro-latest → gemini-2.5-pro
gemini-flash-latest → gemini-2.5-flash
gemini-flash → gemini-2.5-flash
gemini-pro → gemini-2.5-pro
```
## Contributing
Contributions are welcome! Please see the [contribution guide](contribute/contribute.md) for guidelines.
## License
[MIT](LICENSE)
## Community
- [Discord](https://discord.gg/RqSS2NQVsY)
- [GitHub Issues](https://github.com/mark3labs/kit/issues)
- [Documentation](https://github.com/mark3labs/kit/wiki)
-145
View File
@@ -1,145 +0,0 @@
version: "3"
vars:
BINARY: kit
OUTPUT_DIR: output
BUILD_FLAGS: -o {{.OUTPUT_DIR}}/{{.BINARY}}
LDFLAGS: -s -w -X main.version=dev
tasks:
default:
desc: Show available tasks
cmds:
- task --list-all
# -----------------------------------------------------------------------
# Build
# -----------------------------------------------------------------------
build:
desc: Build the kit binary
cmds:
- go build {{.BUILD_FLAGS}} -ldflags "{{.LDFLAGS}}" ./cmd/kit
sources:
- "**/*.go"
- go.mod
- go.sum
generates:
- "{{.OUTPUT_DIR}}/{{.BINARY}}"
build-all:
desc: Build for all platforms (linux, darwin, windows)
cmds:
- GOOS=linux GOARCH=amd64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-linux-amd64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=linux GOARCH=arm64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-linux-arm64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=darwin GOARCH=amd64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-darwin-amd64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=darwin GOARCH=arm64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-darwin-arm64 -ldflags "{{.LDFLAGS}}" ./cmd/kit
- GOOS=windows GOARCH=amd64 go build -o {{.OUTPUT_DIR}}/{{.BINARY}}-windows-amd64.exe -ldflags "{{.LDFLAGS}}" ./cmd/kit
install:
desc: Install kit to $GOPATH/bin
cmds:
- go install -ldflags "{{.LDFLAGS}}" ./cmd/kit
# -----------------------------------------------------------------------
# Test
# -----------------------------------------------------------------------
test:
desc: Run all tests with race detector
cmds:
- go test -race ./...
test-v:
desc: Run all tests (verbose)
cmds:
- go test -race -v ./...
test-short:
desc: Run tests in short mode (skip long-running tests)
cmds:
- go test -race -short ./...
test-pkg:
desc: "Run tests for a specific package (usage: task test-pkg -- ./internal/config)"
cmds:
- go test -race -v {{.CLI_ARGS}}
test-run:
desc: "Run a single test by name (usage: task test-run -- TestScriptExecution)"
cmds:
- go test -race -v ./... -run {{.CLI_ARGS}}
test-cover:
desc: Run tests with coverage report
cmds:
- mkdir -p {{.OUTPUT_DIR}}
- go test -race -coverprofile={{.OUTPUT_DIR}}/coverage.out ./...
- go tool cover -html={{.OUTPUT_DIR}}/coverage.out -o {{.OUTPUT_DIR}}/coverage.html
- echo "Coverage report written to {{.OUTPUT_DIR}}/coverage.html"
# -----------------------------------------------------------------------
# Code quality
# -----------------------------------------------------------------------
lint:
desc: Run golangci-lint and go vet
cmds:
- golangci-lint run ./...
- go vet ./...
fmt:
desc: Format all Go files
cmds:
- go fmt ./...
fmt-check:
desc: Check formatting (fails if files need formatting)
cmds:
- test -z "$(gofmt -l .)" || (echo "Files need formatting:" && gofmt -l . && exit 1)
tidy:
desc: Tidy go.mod and go.sum
cmds:
- go mod tidy
check:
desc: Run all quality checks (fmt, vet, test)
cmds:
- task: fmt-check
- task: lint
- task: test
# -----------------------------------------------------------------------
# Development
# -----------------------------------------------------------------------
dev:
desc: Build and run kit with optional args
cmds:
- task: build
- "{{.OUTPUT_DIR}}/{{.BINARY}} {{.CLI_ARGS}}"
watch:
desc: Watch for changes and rebuild (requires watchexec)
cmds:
- watchexec -e go -r -- task build
clean:
desc: Remove build artifacts
cmds:
- rm -rf {{.OUTPUT_DIR}}
# -----------------------------------------------------------------------
# Release
# -----------------------------------------------------------------------
release-snapshot:
desc: Build a release snapshot (no publish)
cmds:
- goreleaser release --snapshot --clean
release-check:
desc: Validate goreleaser config
cmds:
- goreleaser check
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"version":"1.5.2","languages":{"en":{"hash":"en_791e28a5e1","wasm":"en","page_count":21}},"include_characters":["_","‿","⁀","⁔","︳","︴","","","","_"]}
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
:root {
--pagefind-ui-scale: 0.8;
--pagefind-ui-primary: #034AD8;
--pagefind-ui-fade: #707070;
--pagefind-ui-text: #393939;
--pagefind-ui-background: #ffffff;
--pagefind-ui-border: #eeeeee;
--pagefind-ui-tag: #eeeeee;
--pagefind-ui-border-width: 2px;
--pagefind-ui-border-radius: 8px;
--pagefind-ui-image-border-radius: 8px;
--pagefind-ui-image-box-ratio: 3 / 2;
--pagefind-ui-font: system, -apple-system, ".SFNSText-Regular",
"San Francisco", "Roboto", "Segoe UI", "Helvetica Neue",
"Lucida Grande", sans-serif;
}
[data-pfmod-hidden] {
display: none !important;
}
[data-pfmod-suppressed] {
opacity: 0 !important;
pointer-events: none !important;
}
[data-pfmod-sr-hidden] {
-webkit-clip: rect(0 0 0 0) !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(100%) !important;
clip-path: inset(100%) !important;
height: 1px !important;
overflow: hidden !important;
overflow: clip !important;
position: absolute !important;
white-space: nowrap !important;
width: 1px !important;
}
[data-pfmod-loading] {
color: var(--pagefind-ui-text);
background-color: var(--pagefind-ui-text);
border-radius: var(--pagefind-ui-border-radius);
opacity: 0.1;
pointer-events: none;
}
/* Input */
.pagefind-modular-input-wrapper {
position: relative;
}
.pagefind-modular-input-wrapper::before {
background-color: var(--pagefind-ui-text);
width: calc(18px * var(--pagefind-ui-scale));
height: calc(18px * var(--pagefind-ui-scale));
top: calc(23px * var(--pagefind-ui-scale));
left: calc(20px * var(--pagefind-ui-scale));
content: "";
position: absolute;
display: block;
opacity: 0.7;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");
mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");
-webkit-mask-size: 100%;
mask-size: 100%;
z-index: 9;
pointer-events: none;
}
.pagefind-modular-input {
height: calc(64px * var(--pagefind-ui-scale));
padding: 0 calc(70px * var(--pagefind-ui-scale)) 0 calc(54px * var(--pagefind-ui-scale));
background-color: var(--pagefind-ui-background);
border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);
border-radius: var(--pagefind-ui-border-radius);
font-size: calc(21px * var(--pagefind-ui-scale));
position: relative;
appearance: none;
-webkit-appearance: none;
display: flex;
width: 100%;
box-sizing: border-box;
font-weight: 700;
}
.pagefind-modular-input::placeholder {
opacity: 0.2;
}
.pagefind-modular-input-clear {
position: absolute;
top: calc(2px * var(--pagefind-ui-scale));
right: calc(2px * var(--pagefind-ui-scale));
height: calc(60px * var(--pagefind-ui-scale));
border-radius: var(--pagefind-ui-border-radius);
padding: 0 calc(15px * var(--pagefind-ui-scale)) 0 calc(2px * var(--pagefind-ui-scale));
color: var(--pagefind-ui-text);
font-size: calc(14px * var(--pagefind-ui-scale));
cursor: pointer;
background-color: var(--pagefind-ui-background);
border: none;
appearance: none;
}
/* ResultList */
.pagefind-modular-list-result {
list-style-type: none;
display: flex;
align-items: flex-start;
gap: min(calc(40px * var(--pagefind-ui-scale)), 3%);
padding: calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale));
border-top: solid var(--pagefind-ui-border-width) var(--pagefind-ui-border);
}
.pagefind-modular-list-result:last-of-type {
border-bottom: solid var(--pagefind-ui-border-width) var(--pagefind-ui-border);
}
.pagefind-modular-list-thumb {
width: min(30%,
calc((30% - (100px * var(--pagefind-ui-scale))) * 100000));
max-width: calc(120px * var(--pagefind-ui-scale));
margin-top: calc(10px * var(--pagefind-ui-scale));
aspect-ratio: var(--pagefind-ui-image-box-ratio);
position: relative;
}
.pagefind-modular-list-image {
display: block;
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 0;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
border-radius: var(--pagefind-ui-image-border-radius);
}
.pagefind-modular-list-inner {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
margin-top: calc(10px * var(--pagefind-ui-scale));
}
.pagefind-modular-list-title {
display: inline-block;
font-weight: 700;
font-size: calc(21px * var(--pagefind-ui-scale));
margin-top: 0;
margin-bottom: 0;
}
.pagefind-modular-list-link {
color: var(--pagefind-ui-text);
text-decoration: none;
}
.pagefind-modular-list-link:hover {
text-decoration: underline;
}
.pagefind-modular-list-excerpt {
display: inline-block;
font-weight: 400;
font-size: calc(16px * var(--pagefind-ui-scale));
margin-top: calc(4px * var(--pagefind-ui-scale));
margin-bottom: 0;
min-width: calc(250px * var(--pagefind-ui-scale));
}
/* FilterPills */
.pagefind-modular-filter-pills-wrapper {
overflow-x: scroll;
padding: 15px 0;
}
.pagefind-modular-filter-pills {
display: flex;
gap: 6px;
}
.pagefind-modular-filter-pill {
display: flex;
justify-content: center;
align-items: center;
border: none;
appearance: none;
padding: 0 calc(24px * var(--pagefind-ui-scale));
background-color: var(--pagefind-ui-background);
color: var(--pagefind-ui-fade);
border: var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);
border-radius: calc(25px * var(--pagefind-ui-scale));
font-size: calc(18px * var(--pagefind-ui-scale));
height: calc(50px * var(--pagefind-ui-scale));
cursor: pointer;
white-space: nowrap;
}
.pagefind-modular-filter-pill:hover {
border-color: var(--pagefind-ui-primary);
}
.pagefind-modular-filter-pill[aria-pressed="true"] {
border-color: var(--pagefind-ui-primary);
color: var(--pagefind-ui-primary);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,18 @@
---
title: JSON Output
description: Machine-readable JSON output for scripting and automation.
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JSON Output | Kit</title>
<meta name="description" content="Machine-readable JSON output for scripting and automation.">
<link rel="canonical" href="/advanced/json-output">
<link rel="stylesheet" href="/assets/index-Di_r5hA0.css">
<script type="module" src="/assets/index-B6tF_bpk.js"></script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"JSON Output","description":"Machine-readable JSON output for scripting and automation.","url":"https://go-kit.dev/advanced/json-output","isPartOf":{"@type":"WebSite","name":"Kit","url":"https://go-kit.dev"}}</script>
</head>
<body>
<div id="tome-root"></div>
<div data-pagefind-body style="display:none"><h1>JSON Output</h1>
# JSON Output
Use the `--json` flag to get structured output for scripting and automation:
@@ -93,3 +103,23 @@ result, err := host.PromptResult(ctx, "Count files")
fmt.Println(result.Response)
fmt.Println(result.Usage.TotalTokens)
```
`PromptResult` blocks until end-of-turn regardless of streaming mode. When
streaming is enabled, every delta observed during the turn is also captured in
order in `result.Stream` (`[]kit.StreamEvent`), so you can assert streamed
ordering deterministically without wiring an `OnMessageUpdate` collector:
```go
for _, ev := range result.Stream {
switch ev.Kind {
case kit.StreamEventTextDelta:
fmt.Print(ev.Text)
case kit.StreamEventReasoningDelta:
fmt.Print(ev.Reasoning)
case kit.StreamEventToolCallChunk:
fmt.Printf("[%s %s]", ev.ToolName, ev.Args)
}
}
```</div>
</body>
</html>
@@ -1,8 +1,18 @@
---
title: Subagents
description: Multi-agent orchestration with Kit subagents.
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Subagents | Kit</title>
<meta name="description" content="Multi-agent orchestration with Kit subagents.">
<link rel="canonical" href="/advanced/subagents">
<link rel="stylesheet" href="/assets/index-Di_r5hA0.css">
<script type="module" src="/assets/index-B6tF_bpk.js"></script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Subagents","description":"Multi-agent orchestration with Kit subagents.","url":"https://go-kit.dev/advanced/subagents","isPartOf":{"@type":"WebSite","name":"Kit","url":"https://go-kit.dev"}}</script>
</head>
<body>
<div id="tome-root"></div>
<div data-pagefind-body style="display:none"><h1>Subagents</h1>
# Subagents
Kit supports multi-agent orchestration through both subprocess spawning and in-process subagents.
@@ -168,4 +178,6 @@ host.OnToolCall(func(e kit.ToolCallEvent) {
The listener receives the same event types as `Subscribe()` (`ToolCallEvent`, `MessageUpdateEvent`, `ReasoningDeltaEvent`, etc.) but scoped to the child agent's activity. Listeners are cleaned up automatically when the subagent completes.
If no listeners are registered for a tool call, no event dispatching overhead is incurred.
If no listeners are registered for a tool call, no event dispatching overhead is incurred.</div>
</body>
</html>
@@ -1,8 +1,18 @@
---
title: Testing with tmux
description: Test Kit's TUI non-interactively using tmux.
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Testing with tmux | Kit</title>
<meta name="description" content="Test Kit's TUI non-interactively using tmux.">
<link rel="canonical" href="/advanced/testing">
<link rel="stylesheet" href="/assets/index-Di_r5hA0.css">
<script type="module" src="/assets/index-B6tF_bpk.js"></script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Testing with tmux","description":"Test Kit's TUI non-interactively using tmux.","url":"https://go-kit.dev/advanced/testing","isPartOf":{"@type":"WebSite","name":"Kit","url":"https://go-kit.dev"}}</script>
</head>
<body>
<div id="tome-root"></div>
<div data-pagefind-body style="display:none"><h1>Testing with tmux</h1>
# Testing with tmux
Kit's interactive TUI can be tested non-interactively using tmux. This is useful for automated testing, CI pipelines, and extension development.
@@ -71,4 +81,6 @@ tmux kill-session -t kittest
- Redirect stderr to a log file (`2>kit.log`) for debugging
- Use `--no-session` to avoid creating session files during tests
- Add sufficient `sleep` between commands for the TUI to render
- Use `grep` on captured pane output to verify specific content
- Use `grep` on captured pane output to verify specific content</div>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+598
View File
@@ -0,0 +1,598 @@
const e={frontmatter:{title:"Commands",description:"Complete reference for all Kit CLI subcommands.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#commands"><span class="icon icon-link"></span></a>Commands</h1>
<h2 id="authentication"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#authentication"><span class="icon icon-link"></span></a>Authentication</h2>
<p>For OAuth-enabled providers like Anthropic.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Start OAuth flow (e.g., anthropic)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] --set-default </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Set provider's default model as system default</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> logout</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Remove credentials for provider</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Check authentication status</span></span></code></pre>
<h2 id="model-database"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-database"><span class="icon icon-link"></span></a>Model database</h2>
<p>Manage the local model database that maps provider names to API configurations.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [provider] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># List available models (optionally filter by provider)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --all</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Show all providers (not just LLM-compatible)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> update-models</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> [source] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Update model database</span></span></code></pre>
<p>The <code>update-models</code> command accepts an optional source argument:</p>
<ul>
<li><em>(none)</em> — update from <a href="https://models.dev">models.dev</a></li>
<li>A URL — fetch from a custom endpoint</li>
<li>A file path — load from a local file</li>
<li><code>embedded</code> — reset to the bundled database</li>
</ul>
<h2 id="extension-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-management"><span class="icon icon-link"></span></a>Extension management</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> list</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # List discovered extensions</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> validate</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Validate extension files</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> init</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Generate example extension template</span></span></code></pre>
<h3 id="installing-extensions-from-git"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installing-extensions-from-git"><span class="icon icon-link"></span></a>Installing extensions from git</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">git-ur</span><span style="color:#24292E;--shiki-dark:#E1E4E8">l</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install extensions from git repositories</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">git-ur</span><span style="color:#24292E;--shiki-dark:#E1E4E8">l</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install to project-local .kit/git/ directory</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">git-ur</span><span style="color:#24292E;--shiki-dark:#E1E4E8">l</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Update an already-installed package</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --uninstall</span><span style="color:#D73A49;--shiki-dark:#F97583"> &lt;</span><span style="color:#032F62;--shiki-dark:#9ECBFF">pk</span><span style="color:#24292E;--shiki-dark:#E1E4E8">g</span><span style="color:#D73A49;--shiki-dark:#F97583">&gt;</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Remove an installed package</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --all</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install all extensions without prompting</span></span></code></pre>
<h2 id="skills"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skills"><span class="icon icon-link"></span></a>Skills</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> skill</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Install the Kit extensions skill via skills.sh</span></span></code></pre>
<h3 id="skills-cli-flags"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skills-cli-flags"><span class="icon icon-link"></span></a>Skills CLI flags</h3>
<p>Control which skills are loaded at startup:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load a specific skill file</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/skill.md</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load multiple skill files or directories (flag is repeatable)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./skill1.md</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./skill2.md</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Scan a directory directly for skills (overrides auto-discovery)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skills-dir</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> /path/to/skills</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Hide a skill from the model catalog by name (still usable via /skill:)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --skill-disable</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> noisy-skill</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Disable all skill loading (auto-discovery and explicit)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-skills</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "prompt"</span></span></code></pre>
<p>Skills follow the <a href="https://agentskills.io/specification">agentskills.io</a> convention. They are auto-discovered from four canonical scopes:</p>
<table>
<thead>
<tr>
<th>Scope</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr>
<td>User-level (cross-client)</td>
<td><code>~/.agents/skills/</code></td>
</tr>
<tr>
<td>User-level (Kit)</td>
<td><code>~/.config/kit/skills/</code> (honors <code>$XDG_CONFIG_HOME</code>)</td>
</tr>
<tr>
<td>Project-local (cross-client)</td>
<td><code>&lt;project&gt;/.agents/skills/</code></td>
</tr>
<tr>
<td>Project-local (Kit)</td>
<td><code>&lt;project&gt;/.kit/skills/</code></td>
</tr>
</tbody>
</table>
<p>When two skills share the same <code>name</code>, the project-level one takes precedence over the user-level one. Use <code>--skills-dir</code> to scan one directory directly instead (it is <strong>not</strong> treated as a parent of <code>.agents</code>/<code>.kit</code> — the directory itself is scanned). <code>--skill</code> loads files explicitly (which disables auto-discovery), and <code>--no-skills</code> suppresses all skill loading regardless of other flags.</p>
<p>Disabled skills (<code>--skill-disable</code>, the <code>skill-disable</code> config key, or <code>disable-model-invocation: true</code> in a skill's frontmatter) are hidden from the model-facing <code>&lt;available_skills&gt;</code> catalog but remain available for explicit activation via the <code>/skill:&lt;name&gt;</code> command.</p>
<h3 id="skill-frontmatter"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skill-frontmatter"><span class="icon icon-link"></span></a>Skill frontmatter</h3>
<p>A skill is a markdown file (<code>SKILL.md</code> in a directory, or a standalone <code>.md</code>/<code>.txt</code> file) with optional YAML frontmatter. Kit reads the full <a href="https://agentskills.io/specification">agentskills.io</a> field set plus two Kit-specific extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">---</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">name</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">pdf-extractor</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # required</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">description</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">Use when extracting tables from PDFs</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # required (drives model discovery)</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">license</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">MIT</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # optional, SPDX identifier</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">compatibility</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">claude-code, cursor</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # optional, targeted environments</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">allowed-tools</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">read, bash</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # optional (experimental) tool restriction</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">disable-model-invocation</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # optional; true hides from the catalog</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">metadata</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#6A737D;--shiki-dark:#6A737D"># optional arbitrary key/value pairs</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> author</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">you</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">tags</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span><span style="color:#032F62;--shiki-dark:#9ECBFF">pdf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">data</span><span style="color:#24292E;--shiki-dark:#E1E4E8">] </span><span style="color:#6A737D;--shiki-dark:#6A737D"># Kit extension</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">when</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">on-demand</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Kit extension</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">---</span></span></code></pre>
<p><code>name</code> and <code>description</code> are required — a skill missing its description is skipped with a logged warning, since the description is the sole basis on which the model decides relevance. Descriptions are XML-escaped before they enter the catalog, so characters like <code>&lt;</code>, <code>&gt;</code>, and <code>&amp;</code> are safe. A skill directory may bundle <code>scripts/</code>, <code>references/</code>, and <code>assets/</code> subdirectories; when a skill is activated those files are enumerated in a <code>&lt;skill_resources&gt;</code> block so the model knows what it can read.</p>
<h3 id="project-trust-prompt"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#project-trust-prompt"><span class="icon icon-link"></span></a>Project trust prompt</h3>
<p>Because project-local skills are injected into the system prompt, entering a repository that ships <code>.agents/skills/</code> or <code>.kit/skills/</code> for the first time prompts you to trust it before any project skill loads — a safeguard against a freshly cloned, untrusted repo smuggling instructions into the agent:</p>
<pre><code>This project provides 2 skills under .agents/skills or .kit/skills:
/path/to/repo
Load them into the agent? [t]rust always / [o]nce / [s]kip (default skip):
</code></pre>
<p>Choosing <strong>trust always</strong> persists the directory to <code>~/.config/kit/trusted-projects.json</code> so you are not asked again. The prompt is skipped (skills load silently) in non-interactive runs — when a prompt is passed positionally, <code>--quiet</code> is set, or stdin is not a TTY.</p>
<h2 id="github-integration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#github-integration"><span class="icon icon-link"></span></a>GitHub integration</h2>
<p>Scaffold a GitHub Actions workflow that runs Kit as an automated collaborator/reviewer. The workflow triggers when someone comments <code>/kit ...</code> on an issue or pull request review, runs the agent non-interactively in the runner, and lets it respond.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Scaffold .github/workflows/kit.yml</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic/claude-sonnet-4-5-20250929</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Skip the model prompt</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --force</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Overwrite an existing workflow file</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-secret</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Skip the offer to set the provider secret via the gh CLI</span></span></code></pre>
<p>By default the command prompts for the model (pre-filled with a sensible default). If the <a href="https://cli.github.com/"><code>gh</code> CLI</a> is detected on your <code>PATH</code> and the provider API key is present in your environment, you'll be offered the option to store it as a repository secret automatically.</p>
<p>The generated workflow:</p>
<ul>
<li>Triggers only on <code>issue_comment</code> and <code>pull_request_review_comment</code> (<code>types: [created]</code>).</li>
<li>Runs only when the comment begins with the <code>/kit</code> command token.</li>
<li>Restricts triggers to repository owners, members, and collaborators (via <code>author_association</code>).</li>
<li>Uses least-privilege <code>permissions</code> and <code>persist-credentials: false</code>.</li>
<li>Authenticates git/PR operations with the built-in <code>secrets.GITHUB_TOKEN</code> and the provider via a repository secret (e.g. <code>ANTHROPIC_API_KEY</code>).</li>
</ul>
<p>After committing the workflow and setting the provider secret, comment <code>/kit &lt;your request&gt;</code> on any issue or pull request to trigger Kit.</p>
<p>The generated workflow uses the bundled <a href="https://github.com/mark3labs/kit/blob/master/action.yml"><code>mark3labs/kit</code></a> composite action, which installs the Kit binary and runs <code>kit github run</code>. That command reads the triggering event, enforces permissions, reacts with an emoji, runs the agent against the issue thread or PR, posts the response as a comment, and — if the agent changed files — pushes a <code>kit-agent[bot]</code> branch and opens a pull request.</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--model</code></td>
<td>Provider/model to write into the workflow</td>
</tr>
<tr>
<td><code>--force</code></td>
<td>Overwrite an existing workflow file</td>
</tr>
<tr>
<td><code>--no-secret</code></td>
<td>Skip the offer to set the provider secret via the <code>gh</code> CLI</td>
</tr>
</tbody>
</table>
<h2 id="interactive-slash-commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#interactive-slash-commands"><span class="icon icon-link"></span></a>Interactive slash commands</h2>
<p>These commands are available inside the Kit TUI during an interactive session:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/help</code></td>
<td>Show available commands</td>
</tr>
<tr>
<td><code>/tools</code></td>
<td>List available MCP tools</td>
</tr>
<tr>
<td><code>/servers</code></td>
<td>Show connected MCP servers</td>
</tr>
<tr>
<td><code>/model [name]</code></td>
<td>Switch model or open model selector</td>
</tr>
<tr>
<td><code>/theme [name]</code></td>
<td>Switch color theme or list available themes</td>
</tr>
<tr>
<td><code>/thinking [level]</code></td>
<td>Set thinking level (off, none, minimal, low, medium, high)</td>
</tr>
<tr>
<td><code>/compact [focus]</code></td>
<td>Summarize older messages to free context</td>
</tr>
<tr>
<td><code>/clear</code></td>
<td>Clear conversation</td>
</tr>
<tr>
<td><code>/clear-queue</code></td>
<td>Clear queued messages</td>
</tr>
<tr>
<td><code>/usage</code></td>
<td>Show token usage</td>
</tr>
<tr>
<td><code>/reset-usage</code></td>
<td>Reset usage statistics</td>
</tr>
<tr>
<td><code>/tree</code></td>
<td>Navigate session tree</td>
</tr>
<tr>
<td><code>/fork</code></td>
<td>Fork to new session from an earlier message</td>
</tr>
<tr>
<td><code>/new</code></td>
<td>Start a new session (creates new session file)</td>
</tr>
<tr>
<td><code>/name [name]</code></td>
<td>Set or show session display name</td>
</tr>
<tr>
<td><code>/resume</code></td>
<td>Open session picker to switch sessions (alias: <code>/r</code>)</td>
</tr>
<tr>
<td><code>/session</code></td>
<td>Show session info</td>
</tr>
<tr>
<td><code>/export [path]</code></td>
<td>Export session as JSONL (default: auto-generated path)</td>
</tr>
<tr>
<td><code>/import &lt;path&gt;</code></td>
<td>Import a session from a JSONL file</td>
</tr>
<tr>
<td><code>/share</code></td>
<td>Upload session to GitHub Gist and get a shareable viewer URL</td>
</tr>
<tr>
<td><code>/quit</code></td>
<td>Exit Kit</td>
</tr>
</tbody>
</table>
<h3 id="prompt-history"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#prompt-history"><span class="icon icon-link"></span></a>Prompt history</h3>
<p>Use <strong>↑</strong> and <strong>↓</strong> arrow keys to navigate through previously submitted prompts. Kit keeps the last 100 entries. Consecutive duplicates are skipped.</p>
<h3 id="cancelling-operations"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#cancelling-operations"><span class="icon icon-link"></span></a>Cancelling operations</h3>
<p>Press <strong>ESC twice</strong> to cancel the current operation:</p>
<ul>
<li>During a tool call: rolls back the entire turn to maintain API message pairing</li>
<li>During streaming: stops the response generation</li>
</ul>
<p>This ensures that <code>tool_use</code> and <code>tool_result</code> messages are always sent to the API as matched pairs, avoiding errors from orphaned tool calls.</p>
<h3 id="external-editor"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#external-editor"><span class="icon icon-link"></span></a>External editor</h3>
<p>Press <strong>Ctrl+X e</strong> to open your <code>$VISUAL</code> or <code>$EDITOR</code> in a temporary file pre-populated with the current input text. On save and quit, the edited content replaces the input textarea. On error exit (e.g., <code>:cq</code> in Vim), the original input is preserved.</p>
<h3 id="mid-turn-steering"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#mid-turn-steering"><span class="icon icon-link"></span></a>Mid-turn steering</h3>
<p>Press <strong>Ctrl+X s</strong> during streaming to inject a system-level instruction mid-turn. This allows you to steer the conversation direction without waiting for the model to finish:</p>
<ul>
<li>Works during streaming output</li>
<li>Sends a steering instruction as a system message</li>
<li>Model continues from the interruption point with the new guidance</li>
</ul>
<p>Example: While the model is writing code, press Ctrl+X s and type "Use async/await instead" to change the implementation approach.</p>
<h3 id="image-attachments"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#image-attachments"><span class="icon icon-link"></span></a>Image attachments</h3>
<p>Attach images to your next prompt straight from the clipboard:</p>
<ul>
<li>Copy an image (e.g. a screenshot) to the system clipboard, then press <strong>Ctrl+V</strong> in the input to attach it.</li>
<li>Press <strong>Ctrl+U</strong> to clear all pending image attachments.</li>
<li>Attachments are sent alongside your text when you submit, and cleared afterward.</li>
</ul>
<p>When a terminal supports color, Kit renders a small low-resolution <strong>thumbnail preview</strong> of each pending image directly in the input, below the <code>[N image(s) attached]</code> indicator, so you can confirm the right image was attached before sending.</p>
<p>The preview is drawn with Unicode half-block characters and ordinary terminal colors — not a graphics protocol — so it renders correctly inside terminal multiplexers like <strong>tmux</strong> and <strong>zellij</strong>. Thumbnails are capped to a small cell box for a glanceable, low-res look.</p>
<ul>
<li>Best fidelity needs a <strong>truecolor</strong> terminal (<code>COLORTERM=truecolor</code>); Kit degrades to 256-color where truecolor is unavailable.</li>
<li>On terminals with neither, the preview is skipped and the <code>[N image(s) attached]</code> text indicator is shown alone.</li>
</ul>
<p>You can also attach image files by referencing them with <code>@path/to/image.png</code> — binary files are auto-detected by MIME type. See <a href="/quick-start">Quick Start</a> for the <code>@</code> attachment syntax.</p>
<h2 id="prompt-templates"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#prompt-templates"><span class="icon icon-link"></span></a>Prompt templates</h2>
<h3 id="creating-templates"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#creating-templates"><span class="icon icon-link"></span></a>Creating templates</h3>
<p>Templates use YAML frontmatter for metadata and support argument placeholders:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">---</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">description</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">Review code for issues</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">---</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Review the following code for bugs and security issues.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">Focus on $1 specifically.</span></span></code></pre>
<p>Save to <code>~/.kit/prompts/review.md</code> or <code>.kit/prompts/review.md</code>.</p>
<h3 id="using-templates"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#using-templates"><span class="icon icon-link"></span></a>Using templates</h3>
<p>Templates appear as slash commands:</p>
<pre><code>/review error handling
</code></pre>
<h3 id="argument-placeholders"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#argument-placeholders"><span class="icon icon-link"></span></a>Argument placeholders</h3>
<table>
<thead>
<tr>
<th>Placeholder</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>$1</code>, <code>$2</code>, etc.</td>
<td>Individual arguments by position</td>
</tr>
<tr>
<td><code>$@</code>, <code>$ARGUMENTS</code></td>
<td>All arguments joined with spaces (zero or more)</td>
</tr>
<tr>
<td><code>$+</code></td>
<td>All arguments joined with spaces (one or more required)</td>
</tr>
<tr>
<td><code>\${@:N}</code></td>
<td>Arguments from position N onwards</td>
</tr>
<tr>
<td><code>\${@:N:L}</code></td>
<td>L arguments starting at position N</td>
</tr>
</tbody>
</table>
<p>Placeholders inside fenced code blocks (<code>\`\`\`</code>) and inline code spans are ignored, so documentation examples won't be substituted.</p>
<h3 id="cli-flags"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#cli-flags"><span class="icon icon-link"></span></a>CLI flags</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Load a specific template by name</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --prompt-template</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> review</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Disable template loading</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-prompt-templates</span></span></code></pre>
<h2 id="acp-server"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#acp-server"><span class="icon icon-link"></span></a>ACP server</h2>
<p>Run Kit as an <a href="https://agentclientprotocol.com">ACP (Agent Client Protocol)</a> agent server. ACP-compatible clients communicate with Kit over JSON-RPC 2.0 on stdin/stdout.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Start as ACP agent</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --debug</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # With debug logging to stderr</span></span></code></pre>`,headings:[{depth:2,text:"Authentication",id:"authentication"},{depth:2,text:"Model database",id:"model-database"},{depth:2,text:"Extension management",id:"extension-management"},{depth:3,text:"Installing extensions from git",id:"installing-extensions-from-git"},{depth:2,text:"Skills",id:"skills"},{depth:3,text:"Skills CLI flags",id:"skills-cli-flags"},{depth:3,text:"Skill frontmatter",id:"skill-frontmatter"},{depth:3,text:"Project trust prompt",id:"project-trust-prompt"},{depth:2,text:"GitHub integration",id:"github-integration"},{depth:2,text:"Interactive slash commands",id:"interactive-slash-commands"},{depth:3,text:"Prompt history",id:"prompt-history"},{depth:3,text:"Cancelling operations",id:"cancelling-operations"},{depth:3,text:"External editor",id:"external-editor"},{depth:3,text:"Mid-turn steering",id:"mid-turn-steering"},{depth:3,text:"Image attachments",id:"image-attachments"},{depth:2,text:"Prompt templates",id:"prompt-templates"},{depth:3,text:"Creating templates",id:"creating-templates"},{depth:3,text:"Using templates",id:"using-templates"},{depth:3,text:"Argument placeholders",id:"argument-placeholders"},{depth:3,text:"CLI flags",id:"cli-flags"},{depth:2,text:"ACP server",id:"acp-server"}],raw:`
# Commands
## Authentication
For OAuth-enabled providers like Anthropic.
\`\`\`bash
kit auth login [provider] # Start OAuth flow (e.g., anthropic)
kit auth login [provider] --set-default # Set provider's default model as system default
kit auth logout [provider] # Remove credentials for provider
kit auth status # Check authentication status
\`\`\`
## Model database
Manage the local model database that maps provider names to API configurations.
\`\`\`bash
kit models [provider] # List available models (optionally filter by provider)
kit models --all # Show all providers (not just LLM-compatible)
kit update-models [source] # Update model database
\`\`\`
The \`update-models\` command accepts an optional source argument:
- *(none)* — update from [models.dev](https://models.dev)
- A URL — fetch from a custom endpoint
- A file path — load from a local file
- \`embedded\` — reset to the bundled database
## Extension management
\`\`\`bash
kit extensions list # List discovered extensions
kit extensions validate # Validate extension files
kit extensions init # Generate example extension template
\`\`\`
### Installing extensions from git
\`\`\`bash
kit install <git-url> # Install extensions from git repositories
kit install -l <git-url> # Install to project-local .kit/git/ directory
kit install -u <git-url> # Update an already-installed package
kit install --uninstall <pkg> # Remove an installed package
kit install --all # Install all extensions without prompting
\`\`\`
## Skills
\`\`\`bash
kit skill # Install the Kit extensions skill via skills.sh
\`\`\`
### Skills CLI flags
Control which skills are loaded at startup:
\`\`\`bash
# Load a specific skill file
kit --skill path/to/skill.md "prompt"
# Load multiple skill files or directories (flag is repeatable)
kit --skill ./skill1.md --skill ./skill2.md "prompt"
# Scan a directory directly for skills (overrides auto-discovery)
kit --skills-dir /path/to/skills "prompt"
# Hide a skill from the model catalog by name (still usable via /skill:)
kit --skill-disable noisy-skill "prompt"
# Disable all skill loading (auto-discovery and explicit)
kit --no-skills "prompt"
\`\`\`
Skills follow the [agentskills.io](https://agentskills.io/specification) convention. They are auto-discovered from four canonical scopes:
| Scope | Location |
|-------|----------|
| User-level (cross-client) | \`~/.agents/skills/\` |
| User-level (Kit) | \`~/.config/kit/skills/\` (honors \`$XDG_CONFIG_HOME\`) |
| Project-local (cross-client) | \`<project>/.agents/skills/\` |
| Project-local (Kit) | \`<project>/.kit/skills/\` |
When two skills share the same \`name\`, the project-level one takes precedence over the user-level one. Use \`--skills-dir\` to scan one directory directly instead (it is **not** treated as a parent of \`.agents\`/\`.kit\` — the directory itself is scanned). \`--skill\` loads files explicitly (which disables auto-discovery), and \`--no-skills\` suppresses all skill loading regardless of other flags.
Disabled skills (\`--skill-disable\`, the \`skill-disable\` config key, or \`disable-model-invocation: true\` in a skill's frontmatter) are hidden from the model-facing \`<available_skills>\` catalog but remain available for explicit activation via the \`/skill:<name>\` command.
### Skill frontmatter
A skill is a markdown file (\`SKILL.md\` in a directory, or a standalone \`.md\`/\`.txt\` file) with optional YAML frontmatter. Kit reads the full [agentskills.io](https://agentskills.io/specification) field set plus two Kit-specific extensions:
\`\`\`yaml
---
name: pdf-extractor # required
description: Use when extracting tables from PDFs # required (drives model discovery)
license: MIT # optional, SPDX identifier
compatibility: claude-code, cursor # optional, targeted environments
allowed-tools: read, bash # optional (experimental) tool restriction
disable-model-invocation: false # optional; true hides from the catalog
metadata: # optional arbitrary key/value pairs
author: you
tags: [pdf, data] # Kit extension
when: on-demand # Kit extension
---
\`\`\`
\`name\` and \`description\` are required — a skill missing its description is skipped with a logged warning, since the description is the sole basis on which the model decides relevance. Descriptions are XML-escaped before they enter the catalog, so characters like \`<\`, \`>\`, and \`&\` are safe. A skill directory may bundle \`scripts/\`, \`references/\`, and \`assets/\` subdirectories; when a skill is activated those files are enumerated in a \`<skill_resources>\` block so the model knows what it can read.
### Project trust prompt
Because project-local skills are injected into the system prompt, entering a repository that ships \`.agents/skills/\` or \`.kit/skills/\` for the first time prompts you to trust it before any project skill loads — a safeguard against a freshly cloned, untrusted repo smuggling instructions into the agent:
\`\`\`
This project provides 2 skills under .agents/skills or .kit/skills:
/path/to/repo
Load them into the agent? [t]rust always / [o]nce / [s]kip (default skip):
\`\`\`
Choosing **trust always** persists the directory to \`~/.config/kit/trusted-projects.json\` so you are not asked again. The prompt is skipped (skills load silently) in non-interactive runs — when a prompt is passed positionally, \`--quiet\` is set, or stdin is not a TTY.
## GitHub integration
Scaffold a GitHub Actions workflow that runs Kit as an automated collaborator/reviewer. The workflow triggers when someone comments \`/kit ...\` on an issue or pull request review, runs the agent non-interactively in the runner, and lets it respond.
\`\`\`bash
kit github install # Scaffold .github/workflows/kit.yml
kit github install --model anthropic/claude-sonnet-4-5-20250929 # Skip the model prompt
kit github install --force # Overwrite an existing workflow file
kit github install --no-secret # Skip the offer to set the provider secret via the gh CLI
\`\`\`
By default the command prompts for the model (pre-filled with a sensible default). If the [\`gh\` CLI](https://cli.github.com/) is detected on your \`PATH\` and the provider API key is present in your environment, you'll be offered the option to store it as a repository secret automatically.
The generated workflow:
- Triggers only on \`issue_comment\` and \`pull_request_review_comment\` (\`types: [created]\`).
- Runs only when the comment begins with the \`/kit\` command token.
- Restricts triggers to repository owners, members, and collaborators (via \`author_association\`).
- Uses least-privilege \`permissions\` and \`persist-credentials: false\`.
- Authenticates git/PR operations with the built-in \`secrets.GITHUB_TOKEN\` and the provider via a repository secret (e.g. \`ANTHROPIC_API_KEY\`).
After committing the workflow and setting the provider secret, comment \`/kit <your request>\` on any issue or pull request to trigger Kit.
The generated workflow uses the bundled [\`mark3labs/kit\`](https://github.com/mark3labs/kit/blob/master/action.yml) composite action, which installs the Kit binary and runs \`kit github run\`. That command reads the triggering event, enforces permissions, reacts with an emoji, runs the agent against the issue thread or PR, posts the response as a comment, and — if the agent changed files — pushes a \`kit-agent[bot]\` branch and opens a pull request.
| Flag | Description |
|------|-------------|
| \`--model\` | Provider/model to write into the workflow |
| \`--force\` | Overwrite an existing workflow file |
| \`--no-secret\` | Skip the offer to set the provider secret via the \`gh\` CLI |
## Interactive slash commands
These commands are available inside the Kit TUI during an interactive session:
| Command | Description |
|---------|-------------|
| \`/help\` | Show available commands |
| \`/tools\` | List available MCP tools |
| \`/servers\` | Show connected MCP servers |
| \`/model [name]\` | Switch model or open model selector |
| \`/theme [name]\` | Switch color theme or list available themes |
| \`/thinking [level]\` | Set thinking level (off, none, minimal, low, medium, high) |
| \`/compact [focus]\` | Summarize older messages to free context |
| \`/clear\` | Clear conversation |
| \`/clear-queue\` | Clear queued messages |
| \`/usage\` | Show token usage |
| \`/reset-usage\` | Reset usage statistics |
| \`/tree\` | Navigate session tree |
| \`/fork\` | Fork to new session from an earlier message |
| \`/new\` | Start a new session (creates new session file) |
| \`/name [name]\` | Set or show session display name |
| \`/resume\` | Open session picker to switch sessions (alias: \`/r\`) |
| \`/session\` | Show session info |
| \`/export [path]\` | Export session as JSONL (default: auto-generated path) |
| \`/import <path>\` | Import a session from a JSONL file |
| \`/share\` | Upload session to GitHub Gist and get a shareable viewer URL |
| \`/quit\` | Exit Kit |
### Prompt history
Use **↑** and **↓** arrow keys to navigate through previously submitted prompts. Kit keeps the last 100 entries. Consecutive duplicates are skipped.
### Cancelling operations
Press **ESC twice** to cancel the current operation:
- During a tool call: rolls back the entire turn to maintain API message pairing
- During streaming: stops the response generation
This ensures that \`tool_use\` and \`tool_result\` messages are always sent to the API as matched pairs, avoiding errors from orphaned tool calls.
### External editor
Press **Ctrl+X e** to open your \`$VISUAL\` or \`$EDITOR\` in a temporary file pre-populated with the current input text. On save and quit, the edited content replaces the input textarea. On error exit (e.g., \`:cq\` in Vim), the original input is preserved.
### Mid-turn steering
Press **Ctrl+X s** during streaming to inject a system-level instruction mid-turn. This allows you to steer the conversation direction without waiting for the model to finish:
- Works during streaming output
- Sends a steering instruction as a system message
- Model continues from the interruption point with the new guidance
Example: While the model is writing code, press Ctrl+X s and type "Use async/await instead" to change the implementation approach.
### Image attachments
Attach images to your next prompt straight from the clipboard:
- Copy an image (e.g. a screenshot) to the system clipboard, then press **Ctrl+V** in the input to attach it.
- Press **Ctrl+U** to clear all pending image attachments.
- Attachments are sent alongside your text when you submit, and cleared afterward.
When a terminal supports color, Kit renders a small low-resolution **thumbnail preview** of each pending image directly in the input, below the \`[N image(s) attached]\` indicator, so you can confirm the right image was attached before sending.
The preview is drawn with Unicode half-block characters and ordinary terminal colors — not a graphics protocol — so it renders correctly inside terminal multiplexers like **tmux** and **zellij**. Thumbnails are capped to a small cell box for a glanceable, low-res look.
- Best fidelity needs a **truecolor** terminal (\`COLORTERM=truecolor\`); Kit degrades to 256-color where truecolor is unavailable.
- On terminals with neither, the preview is skipped and the \`[N image(s) attached]\` text indicator is shown alone.
You can also attach image files by referencing them with \`@path/to/image.png\` — binary files are auto-detected by MIME type. See [Quick Start](/quick-start) for the \`@\` attachment syntax.
## Prompt templates
### Creating templates
Templates use YAML frontmatter for metadata and support argument placeholders:
\`\`\`markdown
---
description: Review code for issues
---
Review the following code for bugs and security issues.
Focus on $1 specifically.
\`\`\`
Save to \`~/.kit/prompts/review.md\` or \`.kit/prompts/review.md\`.
### Using templates
Templates appear as slash commands:
\`\`\`
/review error handling
\`\`\`
### Argument placeholders
| Placeholder | Description |
|-------------|-------------|
| \`$1\`, \`$2\`, etc. | Individual arguments by position |
| \`$@\`, \`$ARGUMENTS\` | All arguments joined with spaces (zero or more) |
| \`$+\` | All arguments joined with spaces (one or more required) |
| \`\${@:N}\` | Arguments from position N onwards |
| \`\${@:N:L}\` | L arguments starting at position N |
Placeholders inside fenced code blocks (\`\` \`\`\` \`\`) and inline code spans are ignored, so documentation examples won't be substituted.
### CLI flags
\`\`\`bash
# Load a specific template by name
kit --prompt-template review
# Disable template loading
kit --no-prompt-templates
\`\`\`
## ACP server
Run Kit as an [ACP (Agent Client Protocol)](https://agentclientprotocol.com) agent server. ACP-compatible clients communicate with Kit over JSON-RPC 2.0 on stdin/stdout.
\`\`\`bash
kit acp # Start as ACP agent
kit acp --debug # With debug logging to stderr
\`\`\`
`};export{e as default};
File diff suppressed because one or more lines are too long
+138
View File
@@ -0,0 +1,138 @@
const e={frontmatter:{title:"Development",description:"Build, test, and contribute to Kit.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="development"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#development"><span class="icon icon-link"></span></a>Development</h1>
<h2 id="build-and-test"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#build-and-test"><span class="icon icon-link"></span></a>Build and test</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Build</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> output/kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd/kit</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Run all tests</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -race</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Run a specific test</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -race</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> TestScriptExecution</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Lint</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> vet</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./...</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Format</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> fmt</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./...</span></span></code></pre>
<h2 id="project-structure"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#project-structure"><span class="icon icon-link"></span></a>Project structure</h2>
<pre><code>cmd/kit/ - CLI entry point (main.go)
cmd/ - CLI command implementations (root, auth, models, etc.)
pkg/kit/ - Go SDK for embedding Kit
internal/app/ - Application orchestrator (agent loop, message store, queue)
internal/agent/ - Agent execution and tool dispatch
internal/auth/ - OAuth authentication and credential storage
internal/acpserver/ - ACP (Agent Client Protocol) server
internal/clipboard/ - Cross-platform clipboard operations
internal/compaction/ - Conversation compaction and summarization
internal/config/ - Configuration management
internal/core/ - Built-in tools (bash with sudo password prompt, read, write, edit, grep, find, ls)
internal/extensions/ - Yaegi extension system
internal/kitsetup/ - Initial setup wizard
internal/message/ - Message content types and structured content blocks
internal/models/ - Provider and model management
internal/session/ - Session persistence (tree-based JSONL)
internal/skills/ - Skill loading and system prompt composition
internal/tools/ - MCP tool integration
internal/ui/ - Bubble Tea TUI components
examples/extensions/ - Example extension files
npm/ - NPM package wrapper for distribution
</code></pre>
<h2 id="architecture-overview"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#architecture-overview"><span class="icon icon-link"></span></a>Architecture overview</h2>
<p>Kit is built around a few key architectural patterns:</p>
<h3 id="multi-provider-llm-support"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#multi-provider-llm-support"><span class="icon icon-link"></span></a>Multi-provider LLM support</h3>
<p>The <code>llm.Provider</code> interface abstracts different LLM providers. Each provider implements message formatting, tool calling, and streaming for its specific API.</p>
<h3 id="mcp-client-server-model"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#mcp-client-server-model"><span class="icon icon-link"></span></a>MCP client-server model</h3>
<p>External tools are integrated via the Model Context Protocol (MCP). Kit acts as an MCP client, connecting to MCP servers configured in <code>.kit.yml</code>.</p>
<h3 id="extension-system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-system"><span class="icon icon-link"></span></a>Extension system</h3>
<p>Extensions are Go source files interpreted at runtime by Yaegi. The <code>internal/extensions/</code> package manages loading, symbol export, and lifecycle dispatch. See the <a href="/extensions/overview">Extension System</a> docs for details.</p>
<h3 id="tui-architecture"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#tui-architecture"><span class="icon icon-link"></span></a>TUI architecture</h3>
<p>The interactive terminal UI is built with <a href="https://github.com/charmbracelet/bubbletea">Bubble Tea v2</a>, using a parent-child model where <code>AppModel</code> manages child components (<code>InputComponent</code>, <code>StreamComponent</code>, etc.).</p>
<h3 id="decoupling-pattern"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#decoupling-pattern"><span class="icon icon-link"></span></a>Decoupling pattern</h3>
<p><code>cmd/root.go</code> contains converter functions (e.g., <code>widgetProviderForUI()</code>) that bridge <code>internal/extensions/</code> types to <code>internal/ui/</code> types. The UI never imports the extensions package directly.</p>
<h2 id="contributing"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#contributing"><span class="icon icon-link"></span></a>Contributing</h2>
<p>Contributions are welcome! Please see the <a href="https://github.com/mark3labs/kit/blob/master/contribute/contribute.md">contribution guide</a> for guidelines.</p>
<h2 id="community"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#community"><span class="icon icon-link"></span></a>Community</h2>
<ul>
<li><a href="https://discord.gg/RqSS2NQVsY">Discord</a></li>
<li><a href="https://github.com/mark3labs/kit/issues">GitHub Issues</a></li>
</ul>`,headings:[{depth:2,text:"Build and test",id:"build-and-test"},{depth:2,text:"Project structure",id:"project-structure"},{depth:2,text:"Architecture overview",id:"architecture-overview"},{depth:3,text:"Multi-provider LLM support",id:"multi-provider-llm-support"},{depth:3,text:"MCP client-server model",id:"mcp-client-server-model"},{depth:3,text:"Extension system",id:"extension-system"},{depth:3,text:"TUI architecture",id:"tui-architecture"},{depth:3,text:"Decoupling pattern",id:"decoupling-pattern"},{depth:2,text:"Contributing",id:"contributing"},{depth:2,text:"Community",id:"community"}],raw:`
# Development
## Build and test
\`\`\`bash
# Build
go build -o output/kit ./cmd/kit
# Run all tests
go test -race ./...
# Run a specific test
go test -race ./cmd -run TestScriptExecution
# Lint
go vet ./...
# Format
go fmt ./...
\`\`\`
## Project structure
\`\`\`
cmd/kit/ - CLI entry point (main.go)
cmd/ - CLI command implementations (root, auth, models, etc.)
pkg/kit/ - Go SDK for embedding Kit
internal/app/ - Application orchestrator (agent loop, message store, queue)
internal/agent/ - Agent execution and tool dispatch
internal/auth/ - OAuth authentication and credential storage
internal/acpserver/ - ACP (Agent Client Protocol) server
internal/clipboard/ - Cross-platform clipboard operations
internal/compaction/ - Conversation compaction and summarization
internal/config/ - Configuration management
internal/core/ - Built-in tools (bash with sudo password prompt, read, write, edit, grep, find, ls)
internal/extensions/ - Yaegi extension system
internal/kitsetup/ - Initial setup wizard
internal/message/ - Message content types and structured content blocks
internal/models/ - Provider and model management
internal/session/ - Session persistence (tree-based JSONL)
internal/skills/ - Skill loading and system prompt composition
internal/tools/ - MCP tool integration
internal/ui/ - Bubble Tea TUI components
examples/extensions/ - Example extension files
npm/ - NPM package wrapper for distribution
\`\`\`
## Architecture overview
Kit is built around a few key architectural patterns:
### Multi-provider LLM support
The \`llm.Provider\` interface abstracts different LLM providers. Each provider implements message formatting, tool calling, and streaming for its specific API.
### MCP client-server model
External tools are integrated via the Model Context Protocol (MCP). Kit acts as an MCP client, connecting to MCP servers configured in \`.kit.yml\`.
### Extension system
Extensions are Go source files interpreted at runtime by Yaegi. The \`internal/extensions/\` package manages loading, symbol export, and lifecycle dispatch. See the [Extension System](/extensions/overview) docs for details.
### TUI architecture
The interactive terminal UI is built with [Bubble Tea v2](https://github.com/charmbracelet/bubbletea), using a parent-child model where \`AppModel\` manages child components (\`InputComponent\`, \`StreamComponent\`, etc.).
### Decoupling pattern
\`cmd/root.go\` contains converter functions (e.g., \`widgetProviderForUI()\`) that bridge \`internal/extensions/\` types to \`internal/ui/\` types. The UI never imports the extensions package directly.
## Contributing
Contributions are welcome! Please see the [contribution guide](https://github.com/mark3labs/kit/blob/master/contribute/contribute.md) for guidelines.
## Community
- [Discord](https://discord.gg/RqSS2NQVsY)
- [GitHub Issues](https://github.com/mark3labs/kit/issues)
`};export{e as default};
File diff suppressed because one or more lines are too long
+298
View File
@@ -0,0 +1,298 @@
const t={frontmatter:{title:"Global Flags",description:"Complete reference for all Kit CLI flags.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="global-flags"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#global-flags"><span class="icon icon-link"></span></a>Global Flags</h1>
<p>All flags can be passed to the root <code>kit</code> command.</p>
<h2 id="model-and-provider"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-and-provider"><span class="icon icon-link"></span></a>Model and provider</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--model</code></td>
<td><code>-m</code></td>
<td><code>anthropic/claude-sonnet-latest</code></td>
<td>Model to use (provider/model format)</td>
</tr>
<tr>
<td><code>--provider-api-key</code></td>
<td>—</td>
<td>—</td>
<td>API key for the provider</td>
</tr>
<tr>
<td><code>--provider-url</code></td>
<td>—</td>
<td>—</td>
<td>Base URL for provider API</td>
</tr>
<tr>
<td><code>--tls-skip-verify</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Skip TLS certificate verification</td>
</tr>
</tbody>
</table>
<h2 id="session-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-management"><span class="icon icon-link"></span></a>Session management</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--session</code></td>
<td><code>-s</code></td>
<td>—</td>
<td>Open specific JSONL session file</td>
</tr>
<tr>
<td><code>--continue</code></td>
<td><code>-c</code></td>
<td><code>false</code></td>
<td>Resume most recent session for current directory</td>
</tr>
<tr>
<td><code>--resume</code></td>
<td><code>-r</code></td>
<td><code>false</code></td>
<td>Interactive session picker</td>
</tr>
<tr>
<td><code>--no-session</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Ephemeral mode, no persistence</td>
</tr>
</tbody>
</table>
<h2 id="behavior"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#behavior"><span class="icon icon-link"></span></a>Behavior</h2>
<p>These flags control Kit's behavior. When a prompt is passed as a positional argument, Kit runs in non-interactive mode.</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--quiet</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Suppress all output (non-interactive only)</td>
</tr>
<tr>
<td><code>--json</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Output response as JSON (non-interactive only)</td>
</tr>
<tr>
<td><code>--no-exit</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Enter interactive mode after prompt completes</td>
</tr>
<tr>
<td><code>--max-steps</code></td>
<td>—</td>
<td><code>0</code></td>
<td>Maximum agent steps (0 for unlimited)</td>
</tr>
<tr>
<td><code>--stream</code></td>
<td>—</td>
<td><code>true</code></td>
<td>Enable streaming output</td>
</tr>
<tr>
<td><code>--compact</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Enable compact output mode</td>
</tr>
<tr>
<td><code>--auto-compact</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Auto-compact conversation near context limit</td>
</tr>
</tbody>
</table>
<h2 id="extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extensions"><span class="icon icon-link"></span></a>Extensions</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--extension</code></td>
<td><code>-e</code></td>
<td>—</td>
<td>Load additional extension file(s) (repeatable)</td>
</tr>
<tr>
<td><code>--no-extensions</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Disable all extensions</td>
</tr>
<tr>
<td><code>--prompt-template</code></td>
<td>—</td>
<td>—</td>
<td>Load a specific prompt template by name</td>
</tr>
<tr>
<td><code>--no-prompt-templates</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Disable prompt template loading</td>
</tr>
</tbody>
</table>
<h2 id="skills"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#skills"><span class="icon icon-link"></span></a>Skills</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--skill</code></td>
<td>—</td>
<td>—</td>
<td>Load skill file or directory (repeatable)</td>
</tr>
<tr>
<td><code>--skills-dir</code></td>
<td>—</td>
<td>—</td>
<td>Scan this directory directly for skills (overrides auto-discovery)</td>
</tr>
<tr>
<td><code>--skill-disable</code></td>
<td>—</td>
<td>—</td>
<td>Hide a skill from the model catalog by name (repeatable); still usable via <code>/skill:</code></td>
</tr>
<tr>
<td><code>--no-skills</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Disable skill loading (auto-discovery and explicit)</td>
</tr>
</tbody>
</table>
<h2 id="generation-parameters"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#generation-parameters"><span class="icon icon-link"></span></a>Generation parameters</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--max-tokens</code></td>
<td>—</td>
<td><code>8192</code></td>
<td>Base cap for output tokens. Auto-raised per-model up to 32768 when the model's catalog ceiling is higher and no explicit value is set.</td>
</tr>
<tr>
<td><code>--temperature</code></td>
<td>—</td>
<td><code>0.7</code></td>
<td>Randomness 0.01.0</td>
</tr>
<tr>
<td><code>--top-p</code></td>
<td>—</td>
<td><code>0.95</code></td>
<td>Nucleus sampling 0.01.0</td>
</tr>
<tr>
<td><code>--top-k</code></td>
<td>—</td>
<td><code>40</code></td>
<td>Limit top K tokens</td>
</tr>
<tr>
<td><code>--stop-sequences</code></td>
<td>—</td>
<td>—</td>
<td>Custom stop sequences (comma-separated)</td>
</tr>
<tr>
<td><code>--frequency-penalty</code></td>
<td>—</td>
<td><code>0.0</code></td>
<td>Penalize frequent tokens (0.02.0)</td>
</tr>
<tr>
<td><code>--presence-penalty</code></td>
<td>—</td>
<td><code>0.0</code></td>
<td>Penalize present tokens (0.02.0)</td>
</tr>
<tr>
<td><code>--thinking-level</code></td>
<td>—</td>
<td><code>off</code></td>
<td>Extended thinking level: off, none, minimal, low, medium, high</td>
</tr>
</tbody>
</table>
<h2 id="system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#system"><span class="icon icon-link"></span></a>System</h2>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Short</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--config</code></td>
<td>—</td>
<td><code>~/.kit.yml</code></td>
<td>Config file path</td>
</tr>
<tr>
<td><code>--system-prompt</code></td>
<td>—</td>
<td>—</td>
<td>System prompt text or file path</td>
</tr>
<tr>
<td><code>--debug</code></td>
<td>—</td>
<td><code>false</code></td>
<td>Enable debug logging</td>
</tr>
</tbody>
</table>`,headings:[{depth:2,text:"Model and provider",id:"model-and-provider"},{depth:2,text:"Session management",id:"session-management"},{depth:2,text:"Behavior",id:"behavior"},{depth:2,text:"Extensions",id:"extensions"},{depth:2,text:"Skills",id:"skills"},{depth:2,text:"Generation parameters",id:"generation-parameters"},{depth:2,text:"System",id:"system"}],raw:"\n# Global Flags\n\nAll flags can be passed to the root `kit` command.\n\n## Model and provider\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--model` | `-m` | `anthropic/claude-sonnet-latest` | Model to use (provider/model format) |\n| `--provider-api-key` | — | — | API key for the provider |\n| `--provider-url` | — | — | Base URL for provider API |\n| `--tls-skip-verify` | — | `false` | Skip TLS certificate verification |\n\n## Session management\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--session` | `-s` | — | Open specific JSONL session file |\n| `--continue` | `-c` | `false` | Resume most recent session for current directory |\n| `--resume` | `-r` | `false` | Interactive session picker |\n| `--no-session` | — | `false` | Ephemeral mode, no persistence |\n\n## Behavior\n\nThese flags control Kit's behavior. When a prompt is passed as a positional argument, Kit runs in non-interactive mode.\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--quiet` | — | `false` | Suppress all output (non-interactive only) |\n| `--json` | — | `false` | Output response as JSON (non-interactive only) |\n| `--no-exit` | — | `false` | Enter interactive mode after prompt completes |\n| `--max-steps` | — | `0` | Maximum agent steps (0 for unlimited) |\n| `--stream` | — | `true` | Enable streaming output |\n| `--compact` | — | `false` | Enable compact output mode |\n| `--auto-compact` | — | `false` | Auto-compact conversation near context limit |\n\n## Extensions\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--extension` | `-e` | — | Load additional extension file(s) (repeatable) |\n| `--no-extensions` | — | `false` | Disable all extensions |\n| `--prompt-template` | — | — | Load a specific prompt template by name |\n| `--no-prompt-templates` | — | `false` | Disable prompt template loading |\n\n## Skills\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--skill` | — | — | Load skill file or directory (repeatable) |\n| `--skills-dir` | — | — | Scan this directory directly for skills (overrides auto-discovery) |\n| `--skill-disable` | — | — | Hide a skill from the model catalog by name (repeatable); still usable via `/skill:` |\n| `--no-skills` | — | `false` | Disable skill loading (auto-discovery and explicit) |\n\n## Generation parameters\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--max-tokens` | — | `8192` | Base cap for output tokens. Auto-raised per-model up to 32768 when the model's catalog ceiling is higher and no explicit value is set. |\n| `--temperature` | — | `0.7` | Randomness 0.01.0 |\n| `--top-p` | — | `0.95` | Nucleus sampling 0.01.0 |\n| `--top-k` | — | `40` | Limit top K tokens |\n| `--stop-sequences` | — | — | Custom stop sequences (comma-separated) |\n| `--frequency-penalty` | — | `0.0` | Penalize frequent tokens (0.02.0) |\n| `--presence-penalty` | — | `0.0` | Penalize present tokens (0.02.0) |\n| `--thinking-level` | — | `off` | Extended thinking level: off, none, minimal, low, medium, high |\n\n## System\n\n| Flag | Short | Default | Description |\n|------|-------|---------|-------------|\n| `--config` | — | `~/.kit.yml` | Config file path |\n| `--system-prompt` | — | — | System prompt text or file path |\n| `--debug` | — | `false` | Enable debug logging |\n"};export{t as default};
File diff suppressed because one or more lines are too long
+79
View File
@@ -0,0 +1,79 @@
const t={frontmatter:{title:"Kit",description:"Kit is a powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.",hidden:!1,toc:!1,draft:!1},html:`<div style="text-align: center; margin: 2rem 0;">
<img src="/logo.jpg" alt="KIT" style="max-width: 400px; width: 100%; margin: 0 auto; display: block;">
</div>
<p>A powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.</p>
<h2 id="features"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#features"><span class="icon icon-link"></span></a>Features</h2>
<ul>
<li><strong>Multi-Provider LLM Support</strong> — Anthropic, OpenAI, Google Gemini, Ollama, Azure OpenAI, AWS Bedrock, OpenRouter, and more</li>
<li><strong>Built-in Core Tools</strong> — bash (with interactive sudo password prompt), read, write, edit, grep, find, ls, subagent with no MCP overhead</li>
<li><strong>Smart @ Attachments</strong> — Binary files auto-detected via MIME type, MCP resources via <code>@mcp:server:uri</code></li>
<li><strong>MCP Integration</strong> — Connect external MCP servers for expanded capabilities (tools, prompts, and resources)</li>
<li><strong>Extension System</strong> — Write custom tools, commands, widgets, and UI modifications in Go</li>
<li><strong>Interactive TUI</strong> — Rich terminal interface powered by Bubble Tea with streaming, syntax highlighting, and custom rendering</li>
<li><strong>Session Management</strong> — Tree-based conversation history with branching support</li>
<li><strong>Non-Interactive Mode</strong> — Script-friendly positional args with JSON output</li>
<li><strong>GitHub Integration</strong> — Scaffold a GitHub Actions workflow with <code>kit github install</code> to run Kit as a collaborator/reviewer on <code>/kit</code> comments</li>
<li><strong>ACP Server</strong> — Run Kit as an <a href="https://agentclientprotocol.com">Agent Client Protocol</a> agent over stdio</li>
<li><strong>Go SDK</strong> — Embed Kit in your own applications</li>
</ul>
<h2 id="quick-links"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#quick-links"><span class="icon icon-link"></span></a>Quick links</h2>
<table>
<thead>
<tr>
<th>Resource</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="/installation">Installation</a></td>
<td>Get Kit up and running</td>
</tr>
<tr>
<td><a href="/quick-start">Quick Start</a></td>
<td>Your first Kit session</td>
</tr>
<tr>
<td><a href="/configuration">Configuration</a></td>
<td>Customize Kit for your workflow</td>
</tr>
<tr>
<td><a href="/extensions/overview">Extensions</a></td>
<td>Build custom tools and UI components</td>
</tr>
<tr>
<td><a href="/sdk/overview">Go SDK</a></td>
<td>Embed Kit in your applications</td>
</tr>
</tbody>
</table>`,headings:[{depth:2,text:"Features",id:"features"},{depth:2,text:"Quick links",id:"quick-links"}],raw:`
<div style="text-align: center; margin: 2rem 0;">
<img src="/logo.jpg" alt="KIT" style="max-width: 400px; width: 100%; margin: 0 auto; display: block;" />
</div>
A powerful, extensible AI coding agent CLI with multi-provider support, built-in tools, and a rich extension system.
## Features
- **Multi-Provider LLM Support** — Anthropic, OpenAI, Google Gemini, Ollama, Azure OpenAI, AWS Bedrock, OpenRouter, and more
- **Built-in Core Tools** — bash (with interactive sudo password prompt), read, write, edit, grep, find, ls, subagent with no MCP overhead
- **Smart @ Attachments** — Binary files auto-detected via MIME type, MCP resources via \`@mcp:server:uri\`
- **MCP Integration** — Connect external MCP servers for expanded capabilities (tools, prompts, and resources)
- **Extension System** — Write custom tools, commands, widgets, and UI modifications in Go
- **Interactive TUI** — Rich terminal interface powered by Bubble Tea with streaming, syntax highlighting, and custom rendering
- **Session Management** — Tree-based conversation history with branching support
- **Non-Interactive Mode** — Script-friendly positional args with JSON output
- **GitHub Integration** — Scaffold a GitHub Actions workflow with \`kit github install\` to run Kit as a collaborator/reviewer on \`/kit\` comments
- **ACP Server** — Run Kit as an [Agent Client Protocol](https://agentclientprotocol.com) agent over stdio
- **Go SDK** — Embed Kit in your own applications
## Quick links
| Resource | Description |
|----------|-------------|
| [Installation](/installation) | Get Kit up and running |
| [Quick Start](/quick-start) | Your first Kit session |
| [Configuration](/configuration) | Customize Kit for your workflow |
| [Extensions](/extensions/overview) | Build custom tools and UI components |
| [Go SDK](/sdk/overview) | Embed Kit in your applications |
`};export{t as default};
+1
View File
@@ -0,0 +1 @@
:root,:root[data-theme=dark],:root[data-theme=light],html,html[data-theme=dark],#tome-root,#tome-root *,[data-theme],body,div{--bg: #08080a !important;--sf: #0e0e12 !important;--sfH: #141418 !important;--bd: #1a1a22 !important;--tx: #e8e0e0 !important;--tx2: #8a8090 !important;--txM: #6a6070 !important;--ac: #e03030 !important;--acD: rgba(224, 48, 48, .12) !important;--acT: #ff4444 !important;--cdBg: #0a0a0e !important;--cdTx: #c8a0a0 !important;--sbBg: #0a0a0d !important;--hdBg: rgba(8, 8, 10, .92) !important}h1,h2,h3,h4,h5,h6{color:#e8e0e0!important;font-style:normal!important}
+88
View File
@@ -0,0 +1,88 @@
const s={frontmatter:{title:"Installation",description:"Install Kit using npm, bun, pnpm, Go, or build from source.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installation"><span class="icon icon-link"></span></a>Installation</h1>
<h2 id="using-npm--bun--pnpm"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#using-npm--bun--pnpm"><span class="icon icon-link"></span></a>Using npm / bun / pnpm</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">npm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mark3labs/kit</span></span></code></pre>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">bun</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mark3labs/kit</span></span></code></pre>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">pnpm</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -g</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mark3labs/kit</span></span></code></pre>
<h2 id="using-go"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#using-go"><span class="icon icon-link"></span></a>Using Go</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> github.com/mark3labs/kit/cmd/kit@latest</span></span></code></pre>
<h2 id="building-from-source"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#building-from-source"><span class="icon icon-link"></span></a>Building from source</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">git</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> clone</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/mark3labs/kit.git</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kit</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd/kit</span></span></code></pre>
<h2 id="verifying-the-installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#verifying-the-installation"><span class="icon icon-link"></span></a>Verifying the installation</h2>
<p>After installing, verify Kit is available:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --help</span></span></code></pre>
<h2 id="setting-up-a-provider"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#setting-up-a-provider"><span class="icon icon-link"></span></a>Setting up a provider</h2>
<p>Kit needs at least one LLM provider configured. Set an API key for your preferred provider:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Anthropic (default provider)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ANTHROPIC_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># OpenAI</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> OPENAI_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Google Gemini</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> GOOGLE_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span></span></code></pre>
<p>For OAuth-enabled providers like Anthropic, you can also authenticate interactively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic</span></span></code></pre>
<p>See <a href="/providers">Providers</a> for the full list of supported providers and their configuration.</p>`,headings:[{depth:2,text:"Using npm / bun / pnpm",id:"using-npm--bun--pnpm"},{depth:2,text:"Using Go",id:"using-go"},{depth:2,text:"Building from source",id:"building-from-source"},{depth:2,text:"Verifying the installation",id:"verifying-the-installation"},{depth:2,text:"Setting up a provider",id:"setting-up-a-provider"}],raw:`
# Installation
## Using npm / bun / pnpm
\`\`\`bash
npm install -g @mark3labs/kit
\`\`\`
\`\`\`bash
bun install -g @mark3labs/kit
\`\`\`
\`\`\`bash
pnpm install -g @mark3labs/kit
\`\`\`
## Using Go
\`\`\`bash
go install github.com/mark3labs/kit/cmd/kit@latest
\`\`\`
## Building from source
\`\`\`bash
git clone https://github.com/mark3labs/kit.git
cd kit
go build -o kit ./cmd/kit
\`\`\`
## Verifying the installation
After installing, verify Kit is available:
\`\`\`bash
kit --help
\`\`\`
## Setting up a provider
Kit needs at least one LLM provider configured. Set an API key for your preferred provider:
\`\`\`bash
# Anthropic (default provider)
export ANTHROPIC_API_KEY="sk-..."
# OpenAI
export OPENAI_API_KEY="sk-..."
# Google Gemini
export GOOGLE_API_KEY="..."
\`\`\`
For OAuth-enabled providers like Anthropic, you can also authenticate interactively:
\`\`\`bash
kit auth login anthropic
\`\`\`
See [Providers](/providers) for the full list of supported providers and their configuration.
`};export{s as default};
+272
View File
@@ -0,0 +1,272 @@
const s={frontmatter:{title:"JSON Output",description:"Machine-readable JSON output for scripting and automation.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="json-output"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#json-output"><span class="icon icon-link"></span></a>JSON Output</h1>
<p>Use the <code>--json</code> flag to get structured output for scripting and automation:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Explain main.go"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<h2 id="response-format"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#response-format"><span class="icon icon-link"></span></a>Response format</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "response"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Final assistant response text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "model"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-haiku-latest"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "stop_reason"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"end_turn"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "session_id"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"a1b2c3d4e5f6"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "usage"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "input_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1024</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "output_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">512</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "total_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">1536</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "cache_read_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "cache_creation_tokens"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "messages"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "role"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"assistant"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> "parts"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: [</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tool_call"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"args"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"type"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"tool_result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"data"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: {</span><span style="color:#005CC5;--shiki-dark:#79B8FF">"name"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#005CC5;--shiki-dark:#79B8FF">"result"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}}</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ]</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="fields"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#fields"><span class="icon icon-link"></span></a>Fields</h2>
<h3 id="top-level"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#top-level"><span class="icon icon-link"></span></a>Top-level</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>response</code></td>
<td>string</td>
<td>The final assistant response text</td>
</tr>
<tr>
<td><code>model</code></td>
<td>string</td>
<td>The model that was used</td>
</tr>
<tr>
<td><code>stop_reason</code></td>
<td>string</td>
<td>Why the model stopped (e.g., <code>end_turn</code>)</td>
</tr>
<tr>
<td><code>session_id</code></td>
<td>string</td>
<td>Session identifier (omitted in <code>--no-session</code> mode)</td>
</tr>
<tr>
<td><code>usage</code></td>
<td>object</td>
<td>Token usage statistics</td>
</tr>
<tr>
<td><code>messages</code></td>
<td>array</td>
<td>Full conversation history</td>
</tr>
</tbody>
</table>
<h3 id="usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#usage"><span class="icon icon-link"></span></a>Usage</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>input_tokens</code></td>
<td>int</td>
<td>Tokens sent to the model</td>
</tr>
<tr>
<td><code>output_tokens</code></td>
<td>int</td>
<td>Tokens generated by the model</td>
</tr>
<tr>
<td><code>total_tokens</code></td>
<td>int</td>
<td>Sum of input and output tokens</td>
</tr>
<tr>
<td><code>cache_read_tokens</code></td>
<td>int</td>
<td>Tokens read from prompt cache</td>
</tr>
<tr>
<td><code>cache_creation_tokens</code></td>
<td>int</td>
<td>Tokens written to prompt cache</td>
</tr>
</tbody>
</table>
<h3 id="message-parts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#message-parts"><span class="icon icon-link"></span></a>Message parts</h3>
<p>Each message contains a <code>parts</code> array with typed entries:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>text</code></td>
<td>Assistant text content</td>
</tr>
<tr>
<td><code>tool_call</code></td>
<td>Tool invocation with name and args</td>
</tr>
<tr>
<td><code>tool_result</code></td>
<td>Tool execution result</td>
</tr>
<tr>
<td><code>reasoning</code></td>
<td>Extended thinking content</td>
</tr>
<tr>
<td><code>finish</code></td>
<td>End-of-turn marker</td>
</tr>
</tbody>
</table>
<h2 id="parsing-in-scripts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#parsing-in-scripts"><span class="icon icon-link"></span></a>Parsing in scripts</h2>
<h3 id="bash--jq"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#bash--jq"><span class="icon icon-link"></span></a>bash + jq</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Count files"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">response</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$result</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jq</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -r</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '.response'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">tokens</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$(</span><span style="color:#005CC5;--shiki-dark:#79B8FF">echo</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#24292E;--shiki-dark:#E1E4E8">$result</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> jq</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '.usage.total_tokens'</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h3 id="go-sdk"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#go-sdk"><span class="icon icon-link"></span></a>Go SDK</h3>
<p>For Go programs, use the SDK's <code>PromptResult</code> method instead of parsing JSON:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PromptResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Count files"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result.Response)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Println</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result.Usage.TotalTokens)</span></span></code></pre>
<p><code>PromptResult</code> blocks until end-of-turn regardless of streaming mode. When
streaming is enabled, every delta observed during the turn is also captured in
order in <code>result.Stream</code> (<code>[]kit.StreamEvent</code>), so you can assert streamed
ordering deterministically without wiring an <code>OnMessageUpdate</code> collector:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, ev </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#D73A49;--shiki-dark:#F97583"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> result.Stream {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> switch</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ev.Kind {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.StreamEventTextDelta:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ev.Text)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.StreamEventReasoningDelta:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ev.Reasoning)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.StreamEventToolCallChunk:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"[</span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> %s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">]"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ev.ToolName, ev.Args)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>`,headings:[{depth:2,text:"Response format",id:"response-format"},{depth:2,text:"Fields",id:"fields"},{depth:3,text:"Top-level",id:"top-level"},{depth:3,text:"Usage",id:"usage"},{depth:3,text:"Message parts",id:"message-parts"},{depth:2,text:"Parsing in scripts",id:"parsing-in-scripts"},{depth:3,text:"bash + jq",id:"bash--jq"},{depth:3,text:"Go SDK",id:"go-sdk"}],raw:`
# JSON Output
Use the \`--json\` flag to get structured output for scripting and automation:
\`\`\`bash
kit "Explain main.go" --json --quiet --no-session
\`\`\`
## Response format
\`\`\`json
{
"response": "Final assistant response text",
"model": "anthropic/claude-haiku-latest",
"stop_reason": "end_turn",
"session_id": "a1b2c3d4e5f6",
"usage": {
"input_tokens": 1024,
"output_tokens": 512,
"total_tokens": 1536,
"cache_read_tokens": 0,
"cache_creation_tokens": 0
},
"messages": [
{
"role": "assistant",
"parts": [
{"type": "text", "data": "..."},
{"type": "tool_call", "data": {"name": "...", "args": "..."}},
{"type": "tool_result", "data": {"name": "...", "result": "..."}}
]
}
]
}
\`\`\`
## Fields
### Top-level
| Field | Type | Description |
|-------|------|-------------|
| \`response\` | string | The final assistant response text |
| \`model\` | string | The model that was used |
| \`stop_reason\` | string | Why the model stopped (e.g., \`end_turn\`) |
| \`session_id\` | string | Session identifier (omitted in \`--no-session\` mode) |
| \`usage\` | object | Token usage statistics |
| \`messages\` | array | Full conversation history |
### Usage
| Field | Type | Description |
|-------|------|-------------|
| \`input_tokens\` | int | Tokens sent to the model |
| \`output_tokens\` | int | Tokens generated by the model |
| \`total_tokens\` | int | Sum of input and output tokens |
| \`cache_read_tokens\` | int | Tokens read from prompt cache |
| \`cache_creation_tokens\` | int | Tokens written to prompt cache |
### Message parts
Each message contains a \`parts\` array with typed entries:
| Type | Description |
|------|-------------|
| \`text\` | Assistant text content |
| \`tool_call\` | Tool invocation with name and args |
| \`tool_result\` | Tool execution result |
| \`reasoning\` | Extended thinking content |
| \`finish\` | End-of-turn marker |
## Parsing in scripts
### bash + jq
\`\`\`bash
result=$(kit "Count files" --json --quiet --no-session)
response=$(echo "$result" | jq -r '.response')
tokens=$(echo "$result" | jq '.usage.total_tokens')
\`\`\`
### Go SDK
For Go programs, use the SDK's \`PromptResult\` method instead of parsing JSON:
\`\`\`go
result, err := host.PromptResult(ctx, "Count files")
fmt.Println(result.Response)
fmt.Println(result.Usage.TotalTokens)
\`\`\`
\`PromptResult\` blocks until end-of-turn regardless of streaming mode. When
streaming is enabled, every delta observed during the turn is also captured in
order in \`result.Stream\` (\`[]kit.StreamEvent\`), so you can assert streamed
ordering deterministically without wiring an \`OnMessageUpdate\` collector:
\`\`\`go
for _, ev := range result.Stream {
switch ev.Kind {
case kit.StreamEventTextDelta:
fmt.Print(ev.Text)
case kit.StreamEventReasoningDelta:
fmt.Print(ev.Reasoning)
case kit.StreamEventToolCallChunk:
fmt.Printf("[%s %s]", ev.ToolName, ev.Args)
}
}
\`\`\`
`};export{s as default};
+269
View File
@@ -0,0 +1,269 @@
const s={frontmatter:{title:"Loading Extensions",description:"How Kit discovers and loads extensions.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="loading-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#loading-extensions"><span class="icon icon-link"></span></a>Loading Extensions</h1>
<h2 id="auto-discovery"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#auto-discovery"><span class="icon icon-link"></span></a>Auto-discovery</h2>
<p>Kit automatically discovers and loads extensions from these paths, in order:</p>
<table>
<thead>
<tr>
<th>Path</th>
<th>Scope</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>~/.config/kit/extensions/*.go</code></td>
<td>Global single files</td>
</tr>
<tr>
<td><code>~/.config/kit/extensions/*/main.go</code></td>
<td>Global subdirectory extensions</td>
</tr>
<tr>
<td><code>.kit/extensions/*.go</code></td>
<td>Project-local single files</td>
</tr>
<tr>
<td><code>.kit/extensions/*/main.go</code></td>
<td>Project-local subdirectory extensions</td>
</tr>
<tr>
<td><code>~/.local/share/kit/git/</code></td>
<td>Global git-installed packages</td>
</tr>
<tr>
<td><code>.kit/git/</code></td>
<td>Project-local git-installed packages</td>
</tr>
</tbody>
</table>
<h2 id="explicit-loading"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#explicit-loading"><span class="icon icon-link"></span></a>Explicit loading</h2>
<p>Load extensions by path using the <code>-e</code> flag:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/extension.go</span></span></code></pre>
<p>Load multiple extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ext1.go</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ext2.go</span></span></code></pre>
<h2 id="disabling-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#disabling-extensions"><span class="icon icon-link"></span></a>Disabling extensions</h2>
<p>Disable all auto-discovered extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-extensions</span></span></code></pre>
<p>You can combine <code>--no-extensions</code> with <code>-e</code> to load only specific extensions:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-extensions</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> my-extension.go</span></span></code></pre>
<h2 id="installing-from-git"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installing-from-git"><span class="icon icon-link"></span></a>Installing from git</h2>
<p>Install extensions from git repositories using <code>kit install</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Install globally (to ~/.local/share/kit/git/)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/user/my-kit-extension.git</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Install project-locally (to .kit/git/)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -l</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/user/my-kit-extension.git</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Update an installed package</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -u</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> https://github.com/user/my-kit-extension.git</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Remove</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> install</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --uninstall</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> my-kit-extension</span></span></code></pre>
<h2 id="extension-structure"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-structure"><span class="icon icon-link"></span></a>Extension structure</h2>
<h3 id="single-file-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#single-file-extensions"><span class="icon icon-link"></span></a>Single-file extensions</h3>
<p>A single <code>.go</code> file with an <code>Init</code> function:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">//go:build ignore</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // register handlers, tools, commands, etc.</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>The <code>//go:build ignore</code> directive prevents the Go toolchain from trying to compile the file as part of a normal build.</p>
<h3 id="subdirectory-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#subdirectory-extensions"><span class="icon icon-link"></span></a>Subdirectory extensions</h3>
<p>For more complex extensions, create a directory with a <code>main.go</code> entry point:</p>
<pre><code>.kit/extensions/my-extension/
├── main.go # Must contain Init(api ext.API)
├── helpers.go # Additional source files
└── config.go
</code></pre>
<h3 id="package-level-state"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#package-level-state"><span class="icon icon-link"></span></a>Package-level state</h3>
<p>Yaegi supports package-level variables captured in closures. This is the standard way to maintain state across event callbacks:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> callCount </span><span style="color:#D73A49;--shiki-dark:#F97583">int</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnToolCall</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> callCount</span><span style="color:#D73A49;--shiki-dark:#F97583">++</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetFooter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">HeaderFooterConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WidgetContent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Text: fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Sprintf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Tools called: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%d</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, callCount),</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> },</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="standard-library-access"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#standard-library-access"><span class="icon icon-link"></span></a>Standard library access</h3>
<p>Extensions can import the full Go standard library, plus <code>os/exec</code> for spawning
subprocesses. Environment variables are also readable: <code>os.Getenv</code>,
<code>os.LookupEnv</code>, and <code>os.Environ</code> return Kit's process environment, so extensions
can pick up CI-provided variables (for example <code>GITHUB_EVENT_PATH</code> or a provider
API key) and any vars the user exported before launching Kit.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">os</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSessionStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> eventPath </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> os.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Getenv</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"GITHUB_EVENT_PATH"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">); eventPath </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ""</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PrintInfo</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Running in GitHub Actions: "</span><span style="color:#D73A49;--shiki-dark:#F97583"> +</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> eventPath)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Environment access is read-only from the host's perspective: the environment is
snapshotted when the extension loads, and calls to <code>os.Setenv</code> mutate only the
extension's sandboxed copy — they never change Kit's process environment or the
host. This keeps extensions from leaking state into Kit or other extensions
while still letting them read the configuration they need.</p>`,headings:[{depth:2,text:"Auto-discovery",id:"auto-discovery"},{depth:2,text:"Explicit loading",id:"explicit-loading"},{depth:2,text:"Disabling extensions",id:"disabling-extensions"},{depth:2,text:"Installing from git",id:"installing-from-git"},{depth:2,text:"Extension structure",id:"extension-structure"},{depth:3,text:"Single-file extensions",id:"single-file-extensions"},{depth:3,text:"Subdirectory extensions",id:"subdirectory-extensions"},{depth:3,text:"Package-level state",id:"package-level-state"},{depth:3,text:"Standard library access",id:"standard-library-access"}],raw:`
# Loading Extensions
## Auto-discovery
Kit automatically discovers and loads extensions from these paths, in order:
| Path | Scope |
|------|-------|
| \`~/.config/kit/extensions/*.go\` | Global single files |
| \`~/.config/kit/extensions/*/main.go\` | Global subdirectory extensions |
| \`.kit/extensions/*.go\` | Project-local single files |
| \`.kit/extensions/*/main.go\` | Project-local subdirectory extensions |
| \`~/.local/share/kit/git/\` | Global git-installed packages |
| \`.kit/git/\` | Project-local git-installed packages |
## Explicit loading
Load extensions by path using the \`-e\` flag:
\`\`\`bash
kit -e path/to/extension.go
\`\`\`
Load multiple extensions:
\`\`\`bash
kit -e ext1.go -e ext2.go
\`\`\`
## Disabling extensions
Disable all auto-discovered extensions:
\`\`\`bash
kit --no-extensions
\`\`\`
You can combine \`--no-extensions\` with \`-e\` to load only specific extensions:
\`\`\`bash
kit --no-extensions -e my-extension.go
\`\`\`
## Installing from git
Install extensions from git repositories using \`kit install\`:
\`\`\`bash
# Install globally (to ~/.local/share/kit/git/)
kit install https://github.com/user/my-kit-extension.git
# Install project-locally (to .kit/git/)
kit install -l https://github.com/user/my-kit-extension.git
# Update an installed package
kit install -u https://github.com/user/my-kit-extension.git
# Remove
kit install --uninstall my-kit-extension
\`\`\`
## Extension structure
### Single-file extensions
A single \`.go\` file with an \`Init\` function:
\`\`\`go
//go:build ignore
package main
import "kit/ext"
func Init(api ext.API) {
// register handlers, tools, commands, etc.
}
\`\`\`
The \`//go:build ignore\` directive prevents the Go toolchain from trying to compile the file as part of a normal build.
### Subdirectory extensions
For more complex extensions, create a directory with a \`main.go\` entry point:
\`\`\`
.kit/extensions/my-extension/
├── main.go # Must contain Init(api ext.API)
├── helpers.go # Additional source files
└── config.go
\`\`\`
### Package-level state
Yaegi supports package-level variables captured in closures. This is the standard way to maintain state across event callbacks:
\`\`\`go
package main
import "kit/ext"
var callCount int
func Init(api ext.API) {
api.OnToolCall(func(_ ext.ToolCallEvent, ctx ext.Context) {
callCount++
ctx.SetFooter(ext.HeaderFooterConfig{
Content: ext.WidgetContent{
Text: fmt.Sprintf("Tools called: %d", callCount),
},
})
})
}
\`\`\`
### Standard library access
Extensions can import the full Go standard library, plus \`os/exec\` for spawning
subprocesses. Environment variables are also readable: \`os.Getenv\`,
\`os.LookupEnv\`, and \`os.Environ\` return Kit's process environment, so extensions
can pick up CI-provided variables (for example \`GITHUB_EVENT_PATH\` or a provider
API key) and any vars the user exported before launching Kit.
\`\`\`go
package main
import (
"os"
"kit/ext"
)
func Init(api ext.API) {
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
if eventPath := os.Getenv("GITHUB_EVENT_PATH"); eventPath != "" {
ctx.PrintInfo("Running in GitHub Actions: " + eventPath)
}
})
}
\`\`\`
Environment access is read-only from the host's perspective: the environment is
snapshotted when the extension loads, and calls to \`os.Setenv\` mutate only the
extension's sandboxed copy — they never change Kit's process environment or the
host. This keeps extensions from leaking state into Kit or other extensions
while still letting them read the configuration they need.
`};export{s as default};
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
const e={frontmatter:{title:"Extension System",description:"Overview of Kit's Go-based extension system.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="extension-system"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-system"><span class="icon icon-link"></span></a>Extension System</h1>
<p>Extensions are Go source files interpreted at runtime via <a href="https://github.com/traefik/yaegi">Yaegi</a>. They can add custom tools, slash commands, widgets, keyboard shortcuts, and intercept lifecycle events — all without recompiling Kit.</p>
<h2 id="minimal-extension"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#minimal-extension"><span class="icon icon-link"></span></a>Minimal extension</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">//go:build ignore</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit/ext</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSessionStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetFooter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">HeaderFooterConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">WidgetContent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Text: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Custom Footer"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>Run it with:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -e</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> examples/extensions/minimal.go</span></span></code></pre>
<h2 id="how-extensions-work"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#how-extensions-work"><span class="icon icon-link"></span></a>How extensions work</h2>
<ol>
<li>Kit discovers extension files from <a href="/extensions/loading">auto-discovery paths</a> or explicit <code>-e</code> flags</li>
<li>Each <code>.go</code> file is loaded into a Yaegi interpreter with access to the <code>kit/ext</code> package</li>
<li>Kit calls the <code>Init(api ext.API)</code> function in each extension</li>
<li>The extension registers callbacks, tools, commands, and UI components via the <code>api</code> and <code>ctx</code> objects</li>
</ol>
<h2 id="key-concepts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#key-concepts"><span class="icon icon-link"></span></a>Key concepts</h2>
<h3 id="the-api-object"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#the-api-object"><span class="icon icon-link"></span></a>The <code>API</code> object</h3>
<p>Passed to <code>Init()</code>, the <code>API</code> object is used to register lifecycle event handlers and static components:</p>
<ul>
<li><strong>Lifecycle handlers</strong> — <code>api.OnSessionStart(...)</code>, <code>api.OnToolCall(...)</code>, etc.</li>
<li><strong>Tools</strong> — <code>api.RegisterTool(ext.ToolDef{...})</code></li>
<li><strong>Commands</strong> — <code>api.RegisterCommand(ext.CommandDef{...})</code></li>
<li><strong>Shortcuts</strong> — <code>api.RegisterShortcut(ext.ShortcutDef{...}, handler)</code></li>
<li><strong>Tool renderers</strong> — <code>api.RegisterToolRenderer(ext.ToolRenderConfig{...})</code></li>
<li><strong>Message renderers</strong> — <code>api.RegisterMessageRenderer(ext.MessageRendererConfig{...})</code></li>
<li><strong>Options</strong> — <code>api.RegisterOption(ext.OptionDef{...})</code></li>
</ul>
<h3 id="the-context-object"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#the-context-object"><span class="icon icon-link"></span></a>The <code>Context</code> object</h3>
<p>Passed to event handlers, the <code>Context</code> object provides runtime access to Kit's state and UI:</p>
<ul>
<li><strong>Output</strong> — <code>ctx.Print(...)</code>, <code>ctx.PrintInfo(...)</code>, <code>ctx.PrintError(...)</code></li>
<li><strong>UI components</strong> — <code>ctx.SetWidget(...)</code>, <code>ctx.SetHeader(...)</code>, <code>ctx.SetFooter(...)</code>, <code>ctx.SetStatus(...)</code></li>
<li><strong>Editor</strong> — <code>ctx.SetEditor(...)</code>, <code>ctx.ResetEditor()</code></li>
<li><strong>Prompts</strong> — <code>ctx.PromptSelect(...)</code>, <code>ctx.PromptConfirm(...)</code>, <code>ctx.PromptInput(...)</code></li>
<li><strong>Overlays</strong> — <code>ctx.ShowOverlay(...)</code></li>
<li><strong>Messages</strong> — <code>ctx.SendMessage(...)</code>, <code>ctx.GetMessages()</code></li>
<li><strong>Model</strong> — <code>ctx.SetModel(...)</code>, <code>ctx.GetAvailableModels()</code></li>
<li><strong>Tools</strong> — <code>ctx.GetAllTools()</code>, <code>ctx.SetActiveTools(...)</code></li>
<li><strong>Context stats</strong> — <code>ctx.GetContextStats()</code></li>
<li><strong>Session data</strong> — <code>ctx.AppendEntry(...)</code>, <code>ctx.GetEntries(...)</code> (append-only, in conversation tree)</li>
<li><strong>Session state</strong> — <code>ctx.SetState(...)</code>, <code>ctx.GetState(...)</code>, <code>ctx.DeleteState(...)</code>, <code>ctx.ListState()</code> (last-write-wins, sidecar file)</li>
<li><strong>Subagents</strong> — <code>ctx.SpawnSubagent(...)</code></li>
<li><strong>LLM completion</strong> — <code>ctx.Complete(...)</code></li>
<li><strong>Custom events</strong> — <code>ctx.EmitCustomEvent(...)</code></li>
</ul>
<p>See <a href="/extensions/capabilities">Capabilities</a> for full details on each component type, and <a href="/extensions/testing">Testing</a> for writing tests for your extensions.</p>`,headings:[{depth:2,text:"Minimal extension",id:"minimal-extension"},{depth:2,text:"How extensions work",id:"how-extensions-work"},{depth:2,text:"Key concepts",id:"key-concepts"},{depth:3,text:"The API object",id:"the-api-object"},{depth:3,text:"The Context object",id:"the-context-object"}],raw:'\n# Extension System\n\nExtensions are Go source files interpreted at runtime via [Yaegi](https://github.com/traefik/yaegi). They can add custom tools, slash commands, widgets, keyboard shortcuts, and intercept lifecycle events — all without recompiling Kit.\n\n## Minimal extension\n\n```go\n//go:build ignore\n\npackage main\n\nimport "kit/ext"\n\nfunc Init(api ext.API) {\n api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {\n ctx.SetFooter(ext.HeaderFooterConfig{\n Content: ext.WidgetContent{Text: "Custom Footer"},\n })\n })\n}\n```\n\nRun it with:\n\n```bash\nkit -e examples/extensions/minimal.go\n```\n\n## How extensions work\n\n1. Kit discovers extension files from [auto-discovery paths](/extensions/loading) or explicit `-e` flags\n2. Each `.go` file is loaded into a Yaegi interpreter with access to the `kit/ext` package\n3. Kit calls the `Init(api ext.API)` function in each extension\n4. The extension registers callbacks, tools, commands, and UI components via the `api` and `ctx` objects\n\n## Key concepts\n\n### The `API` object\n\nPassed to `Init()`, the `API` object is used to register lifecycle event handlers and static components:\n\n- **Lifecycle handlers** — `api.OnSessionStart(...)`, `api.OnToolCall(...)`, etc.\n- **Tools** — `api.RegisterTool(ext.ToolDef{...})`\n- **Commands** — `api.RegisterCommand(ext.CommandDef{...})`\n- **Shortcuts** — `api.RegisterShortcut(ext.ShortcutDef{...}, handler)`\n- **Tool renderers** — `api.RegisterToolRenderer(ext.ToolRenderConfig{...})`\n- **Message renderers** — `api.RegisterMessageRenderer(ext.MessageRendererConfig{...})`\n- **Options** — `api.RegisterOption(ext.OptionDef{...})`\n\n### The `Context` object\n\nPassed to event handlers, the `Context` object provides runtime access to Kit\'s state and UI:\n\n- **Output** — `ctx.Print(...)`, `ctx.PrintInfo(...)`, `ctx.PrintError(...)`\n- **UI components** — `ctx.SetWidget(...)`, `ctx.SetHeader(...)`, `ctx.SetFooter(...)`, `ctx.SetStatus(...)`\n- **Editor** — `ctx.SetEditor(...)`, `ctx.ResetEditor()`\n- **Prompts** — `ctx.PromptSelect(...)`, `ctx.PromptConfirm(...)`, `ctx.PromptInput(...)`\n- **Overlays** — `ctx.ShowOverlay(...)`\n- **Messages** — `ctx.SendMessage(...)`, `ctx.GetMessages()`\n- **Model** — `ctx.SetModel(...)`, `ctx.GetAvailableModels()`\n- **Tools** — `ctx.GetAllTools()`, `ctx.SetActiveTools(...)`\n- **Context stats** — `ctx.GetContextStats()`\n- **Session data** — `ctx.AppendEntry(...)`, `ctx.GetEntries(...)` (append-only, in conversation tree)\n- **Session state** — `ctx.SetState(...)`, `ctx.GetState(...)`, `ctx.DeleteState(...)`, `ctx.ListState()` (last-write-wins, sidecar file)\n- **Subagents** — `ctx.SpawnSubagent(...)`\n- **LLM completion** — `ctx.Complete(...)`\n- **Custom events** — `ctx.EmitCustomEvent(...)`\n\nSee [Capabilities](/extensions/capabilities) for full details on each component type, and [Testing](/extensions/testing) for writing tests for your extensions.\n'};export{e as default};
+406
View File
@@ -0,0 +1,406 @@
const s={frontmatter:{title:"Providers",description:"Supported LLM providers and model configuration.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="providers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#providers"><span class="icon icon-link"></span></a>Providers</h1>
<p>Kit supports a wide range of LLM providers through a unified <code>provider/model</code> string format.</p>
<h2 id="supported-providers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#supported-providers"><span class="icon icon-link"></span></a>Supported providers</h2>
<table>
<thead>
<tr>
<th>Provider</th>
<th>Prefix</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Anthropic</strong></td>
<td><code>anthropic/</code></td>
<td>Claude models (native, prompt caching, OAuth)</td>
</tr>
<tr>
<td><strong>OpenAI</strong></td>
<td><code>openai/</code></td>
<td>GPT models</td>
</tr>
<tr>
<td><strong>GitHub Copilot</strong></td>
<td><code>copilot/</code></td>
<td>Copilot models through GitHub device login (experimental)</td>
</tr>
<tr>
<td><strong>Google</strong></td>
<td><code>google/</code> or <code>gemini/</code></td>
<td>Gemini models</td>
</tr>
<tr>
<td><strong>Ollama</strong></td>
<td><code>ollama/</code></td>
<td>Local models</td>
</tr>
<tr>
<td><strong>Azure OpenAI</strong></td>
<td><code>azure/</code></td>
<td>Azure-hosted OpenAI</td>
</tr>
<tr>
<td><strong>AWS Bedrock</strong></td>
<td><code>bedrock/</code></td>
<td>Bedrock models</td>
</tr>
<tr>
<td><strong>Google Vertex</strong></td>
<td><code>google-vertex-anthropic/</code></td>
<td>Claude on Vertex AI</td>
</tr>
<tr>
<td><strong>OpenRouter</strong></td>
<td><code>openrouter/</code></td>
<td>Multi-provider router</td>
</tr>
<tr>
<td><strong>Vercel AI</strong></td>
<td><code>vercel/</code></td>
<td>Vercel AI SDK models</td>
</tr>
<tr>
<td><strong>Custom</strong></td>
<td><code>custom/</code></td>
<td>Any OpenAI-compatible endpoint</td>
</tr>
<tr>
<td><strong>Auto-routed</strong></td>
<td>any</td>
<td>Any provider from the models.dev database</td>
</tr>
</tbody>
</table>
<h2 id="model-string-format"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-string-format"><span class="icon icon-link"></span></a>Model string format</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">provider/model</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Standard format</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">anthropic/claude-sonnet-latest</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">openai/gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">copilot/gpt-5.5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">ollama/llama3</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">google/gemini-2.5-flash</span></span></code></pre>
<h2 id="model-aliases"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-aliases"><span class="icon icon-link"></span></a>Model aliases</h2>
<p>Kit provides aliases for commonly used models:</p>
<h3 id="anthropic-claude"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#anthropic-claude"><span class="icon icon-link"></span></a>Anthropic Claude</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-opus-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-opus-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-sonnet-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-haiku-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-haiku-4-5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-4-opus-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-opus-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-4-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-sonnet-4-6</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-4-haiku-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-haiku-4-5</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-7-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-7-sonnet-20250219</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-5-sonnet-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-5-sonnet-20241022</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-5-haiku-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-5-haiku-20241022</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">claude-3-opus-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> claude-3-opus-20240229</span></span></code></pre>
<h3 id="openai-gpt"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#openai-gpt"><span class="icon icon-link"></span></a>OpenAI GPT</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">o1-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> o1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">o3-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> o3</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">o4-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> o4-mini</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-5-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-5.4</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-5-chat-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-5.4</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-4-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-4</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-3.5-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-3.5-turbo</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gpt-3.5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gpt-3.5-turbo</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">codex-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> codex-mini-latest</span></span></code></pre>
<h3 id="google-gemini"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#google-gemini"><span class="icon icon-link"></span></a>Google Gemini</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-pro-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-pro</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-flash-latest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-flash</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-flash</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-flash</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">gemini-pro</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> →</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> gemini-2.5-pro</span></span></code></pre>
<h2 id="specifying-a-model"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#specifying-a-model"><span class="icon icon-link"></span></a>Specifying a model</h2>
<p>Via CLI flag:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai/gpt-4o</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -m</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ollama/llama3</span></span></code></pre>
<p>Via config file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">model</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">anthropic/claude-sonnet-latest</span></span></code></pre>
<p>Via environment variable:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> KIT_MODEL</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"google/gemini-2.0-flash-exp"</span></span></code></pre>
<h2 id="authentication"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#authentication"><span class="icon icon-link"></span></a>Authentication</h2>
<h3 id="api-keys"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#api-keys"><span class="icon icon-link"></span></a>API keys</h3>
<p>Set the appropriate environment variable for your provider:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ANTHROPIC_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> OPENAI_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"sk-..."</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">export</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> GOOGLE_API_KEY</span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"..."</span></span></code></pre>
<p>Or pass it directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --provider-api-key</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "sk-..."</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai/gpt-4o</span></span></code></pre>
<h3 id="oauth"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#oauth"><span class="icon icon-link"></span></a>OAuth</h3>
<p>For providers that support OAuth:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Anthropic OAuth</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # ChatGPT/Codex OAuth</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> login</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> copilot</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # GitHub Copilot device login (experimental)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> status</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Check authentication status</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> auth</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> logout</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> copilot</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Remove credentials</span></span></code></pre>
<p>The experimental <code>copilot/</code> provider requires an active GitHub Copilot subscription
and uses GitHub device login; no OpenAI account or OpenAI API key is required.</p>
<h3 id="custom-provider-url"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-provider-url"><span class="icon icon-link"></span></a>Custom provider URL</h3>
<p>For self-hosted or proxy endpoints:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --provider-url</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "https://my-proxy.example.com/v1"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai/gpt-4o</span></span></code></pre>
<p>When <code>--provider-url</code> is set with an explicit <code>--model</code>, Kit routes through the
<code>custom</code> (OpenAI-compatible) wire and strips any provider prefix from the model
name. So <code>openai/gpt-4o</code>, <code>google/gemma-4-12b</code>, and bare <code>gpt-4o</code> all resolve
to the same endpoint — Kit treats <code>--provider-url</code> as authoritative about <em>where</em>
to send the request, and the model string as just the upstream model id.</p>
<p>This avoids name collisions when a local server (LM Studio, Ollama, vLLM, ...)
happens to expose a model whose name matches a known cloud provider.</p>
<p>When <code>--provider-url</code> is provided without <code>--model</code>, Kit automatically defaults to <code>custom/custom</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --provider-url</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "http://localhost:8080/v1"</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span></span></code></pre>
<p>The <code>custom/custom</code> model has zero cost, 262K context window, and supports reasoning. It routes through the <code>openaicompat</code> provider and accepts any OpenAI-compatible API endpoint.</p>
<p>Optionally set <code>CUSTOM_API_KEY</code> environment variable or use <code>--provider-api-key</code> for endpoints requiring authentication.</p>
<h2 id="auto-routed-providers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#auto-routed-providers"><span class="icon icon-link"></span></a>Auto-routed providers</h2>
<p>Any provider in the <a href="https://models.dev">models.dev</a> database can be used with the
standard <code>provider/model</code> format, even without a dedicated native integration. Kit
auto-routes the request through the matching <strong>wire protocol</strong> — the actual API
shape the provider speaks — rather than requiring a per-provider code path:</p>
<table>
<thead>
<tr>
<th>Wire protocol</th>
<th>npm package (models.dev)</th>
<th>Transport used</th>
</tr>
</thead>
<tbody>
<tr>
<td>OpenAI (Responses API)</td>
<td><code>@ai-sdk/openai</code></td>
<td>OpenAI</td>
</tr>
<tr>
<td>OpenAI (chat completions)</td>
<td><code>@ai-sdk/openai-compatible</code></td>
<td>OpenAI-compatible</td>
</tr>
<tr>
<td>Anthropic</td>
<td><code>@ai-sdk/anthropic</code></td>
<td>Anthropic</td>
</tr>
<tr>
<td>Google Gemini</td>
<td><code>@ai-sdk/google</code></td>
<td>Google</td>
</tr>
</tbody>
</table>
<p>The provider's <code>api</code> URL from the database is used as the base URL. A provider
whose npm package isn't recognized but that has an <code>api</code> URL falls back to the
OpenAI-compatible wire.</p>
<p>Because routing follows the wire protocol, aggregator/proxy providers work across
<strong>all</strong> of their models — including ones they re-flavor onto a different protocol
via a per-model override. For example, an aggregator that proxies Claude, GPT,
<em>and</em> Gemini routes them to the Anthropic, OpenAI, and Google transports
respectively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> opencode/claude-haiku-4-5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # → Anthropic wire</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> opencode/gpt-5</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # → OpenAI wire</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> opencode/gemini-3.5-flash</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Hello"</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # → Google wire</span></span></code></pre>
<p>Provide the provider's API key the same way as any other — via its environment
variable (e.g. <code>OPENCODE_API_KEY</code>) or <code>--provider-api-key</code>.</p>
<h2 id="model-database"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#model-database"><span class="icon icon-link"></span></a>Model database</h2>
<p>Kit ships with a local model database that maps provider names to API configurations. You can manage it with:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # List available models</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> openai</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Filter by provider</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> models</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --all</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Show all providers</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> update-models</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Update from models.dev</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> update-models</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> embedded</span><span style="color:#6A737D;--shiki-dark:#6A737D"> # Reset to bundled database</span></span></code></pre>`,headings:[{depth:2,text:"Supported providers",id:"supported-providers"},{depth:2,text:"Model string format",id:"model-string-format"},{depth:2,text:"Model aliases",id:"model-aliases"},{depth:3,text:"Anthropic Claude",id:"anthropic-claude"},{depth:3,text:"OpenAI GPT",id:"openai-gpt"},{depth:3,text:"Google Gemini",id:"google-gemini"},{depth:2,text:"Specifying a model",id:"specifying-a-model"},{depth:2,text:"Authentication",id:"authentication"},{depth:3,text:"API keys",id:"api-keys"},{depth:3,text:"OAuth",id:"oauth"},{depth:3,text:"Custom provider URL",id:"custom-provider-url"},{depth:2,text:"Auto-routed providers",id:"auto-routed-providers"},{depth:2,text:"Model database",id:"model-database"}],raw:`
# Providers
Kit supports a wide range of LLM providers through a unified \`provider/model\` string format.
## Supported providers
| Provider | Prefix | Description |
|----------|--------|-------------|
| **Anthropic** | \`anthropic/\` | Claude models (native, prompt caching, OAuth) |
| **OpenAI** | \`openai/\` | GPT models |
| **GitHub Copilot** | \`copilot/\` | Copilot models through GitHub device login (experimental) |
| **Google** | \`google/\` or \`gemini/\` | Gemini models |
| **Ollama** | \`ollama/\` | Local models |
| **Azure OpenAI** | \`azure/\` | Azure-hosted OpenAI |
| **AWS Bedrock** | \`bedrock/\` | Bedrock models |
| **Google Vertex** | \`google-vertex-anthropic/\` | Claude on Vertex AI |
| **OpenRouter** | \`openrouter/\` | Multi-provider router |
| **Vercel AI** | \`vercel/\` | Vercel AI SDK models |
| **Custom** | \`custom/\` | Any OpenAI-compatible endpoint |
| **Auto-routed** | any | Any provider from the models.dev database |
## Model string format
\`\`\`bash
provider/model # Standard format
anthropic/claude-sonnet-latest
openai/gpt-4o
copilot/gpt-5.5
ollama/llama3
google/gemini-2.5-flash
\`\`\`
## Model aliases
Kit provides aliases for commonly used models:
### Anthropic Claude
\`\`\`bash
claude-opus-latest → claude-opus-4-6
claude-sonnet-latest → claude-sonnet-4-6
claude-haiku-latest → claude-haiku-4-5
claude-4-opus-latest → claude-opus-4-6
claude-4-sonnet-latest → claude-sonnet-4-6
claude-4-haiku-latest → claude-haiku-4-5
claude-3-7-sonnet-latest → claude-3-7-sonnet-20250219
claude-3-5-sonnet-latest → claude-3-5-sonnet-20241022
claude-3-5-haiku-latest → claude-3-5-haiku-20241022
claude-3-opus-latest → claude-3-opus-20240229
\`\`\`
### OpenAI GPT
\`\`\`bash
o1-latest → o1
o3-latest → o3
o4-latest → o4-mini
gpt-5-latest → gpt-5.4
gpt-5-chat-latest → gpt-5.4
gpt-4-latest → gpt-4o
gpt-4 → gpt-4o
gpt-3.5-latest → gpt-3.5-turbo
gpt-3.5 → gpt-3.5-turbo
codex-latest → codex-mini-latest
\`\`\`
### Google Gemini
\`\`\`bash
gemini-pro-latest → gemini-2.5-pro
gemini-flash-latest → gemini-2.5-flash
gemini-flash → gemini-2.5-flash
gemini-pro → gemini-2.5-pro
\`\`\`
## Specifying a model
Via CLI flag:
\`\`\`bash
kit --model openai/gpt-4o
kit -m ollama/llama3
\`\`\`
Via config file:
\`\`\`yaml
model: anthropic/claude-sonnet-latest
\`\`\`
Via environment variable:
\`\`\`bash
export KIT_MODEL="google/gemini-2.0-flash-exp"
\`\`\`
## Authentication
### API keys
Set the appropriate environment variable for your provider:
\`\`\`bash
export ANTHROPIC_API_KEY="sk-..."
export OPENAI_API_KEY="sk-..."
export GOOGLE_API_KEY="..."
\`\`\`
Or pass it directly:
\`\`\`bash
kit --provider-api-key "sk-..." --model openai/gpt-4o
\`\`\`
### OAuth
For providers that support OAuth:
\`\`\`bash
kit auth login anthropic # Anthropic OAuth
kit auth login openai # ChatGPT/Codex OAuth
kit auth login copilot # GitHub Copilot device login (experimental)
kit auth status # Check authentication status
kit auth logout copilot # Remove credentials
\`\`\`
The experimental \`copilot/\` provider requires an active GitHub Copilot subscription
and uses GitHub device login; no OpenAI account or OpenAI API key is required.
### Custom provider URL
For self-hosted or proxy endpoints:
\`\`\`bash
kit --provider-url "https://my-proxy.example.com/v1" --model openai/gpt-4o
\`\`\`
When \`--provider-url\` is set with an explicit \`--model\`, Kit routes through the
\`custom\` (OpenAI-compatible) wire and strips any provider prefix from the model
name. So \`openai/gpt-4o\`, \`google/gemma-4-12b\`, and bare \`gpt-4o\` all resolve
to the same endpoint — Kit treats \`--provider-url\` as authoritative about *where*
to send the request, and the model string as just the upstream model id.
This avoids name collisions when a local server (LM Studio, Ollama, vLLM, ...)
happens to expose a model whose name matches a known cloud provider.
When \`--provider-url\` is provided without \`--model\`, Kit automatically defaults to \`custom/custom\`:
\`\`\`bash
kit --provider-url "http://localhost:8080/v1" "Hello"
\`\`\`
The \`custom/custom\` model has zero cost, 262K context window, and supports reasoning. It routes through the \`openaicompat\` provider and accepts any OpenAI-compatible API endpoint.
Optionally set \`CUSTOM_API_KEY\` environment variable or use \`--provider-api-key\` for endpoints requiring authentication.
## Auto-routed providers
Any provider in the [models.dev](https://models.dev) database can be used with the
standard \`provider/model\` format, even without a dedicated native integration. Kit
auto-routes the request through the matching **wire protocol** — the actual API
shape the provider speaks — rather than requiring a per-provider code path:
| Wire protocol | npm package (models.dev) | Transport used |
|---------------|--------------------------|----------------|
| OpenAI (Responses API) | \`@ai-sdk/openai\` | OpenAI |
| OpenAI (chat completions) | \`@ai-sdk/openai-compatible\` | OpenAI-compatible |
| Anthropic | \`@ai-sdk/anthropic\` | Anthropic |
| Google Gemini | \`@ai-sdk/google\` | Google |
The provider's \`api\` URL from the database is used as the base URL. A provider
whose npm package isn't recognized but that has an \`api\` URL falls back to the
OpenAI-compatible wire.
Because routing follows the wire protocol, aggregator/proxy providers work across
**all** of their models — including ones they re-flavor onto a different protocol
via a per-model override. For example, an aggregator that proxies Claude, GPT,
*and* Gemini routes them to the Anthropic, OpenAI, and Google transports
respectively:
\`\`\`bash
kit --model opencode/claude-haiku-4-5 "Hello" # → Anthropic wire
kit --model opencode/gpt-5 "Hello" # → OpenAI wire
kit --model opencode/gemini-3.5-flash "Hello" # → Google wire
\`\`\`
Provide the provider's API key the same way as any other — via its environment
variable (e.g. \`OPENCODE_API_KEY\`) or \`--provider-api-key\`.
## Model database
Kit ships with a local model database that maps provider names to API configurations. You can manage it with:
\`\`\`bash
kit models # List available models
kit models openai # Filter by provider
kit models --all # Show all providers
kit update-models # Update from models.dev
kit update-models embedded # Reset to bundled database
\`\`\`
`};export{s as default};
+125
View File
@@ -0,0 +1,125 @@
const s={frontmatter:{title:"Quick Start",description:"Get up and running with Kit in minutes.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="quick-start"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#quick-start"><span class="icon icon-link"></span></a>Quick Start</h1>
<h2 id="basic-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-usage"><span class="icon icon-link"></span></a>Basic usage</h2>
<p>Start an interactive session:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span></span></code></pre>
<p>Run a one-off prompt:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "List files in src/"</span></span></code></pre>
<p>Attach files as context using the <code>@</code> prefix:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @main.go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @test.go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Review these files"</span></span></code></pre>
<p>Binary files (images, audio, PDFs) are automatically detected via MIME type and sent as multimodal attachments. You can also reference MCP resources:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> @mcp:myserver:file:///data/report.csv</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Summarize this data"</span></span></code></pre>
<p>Use a specific model:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic/claude-sonnet-latest</span></span></code></pre>
<h2 id="non-interactive-mode"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#non-interactive-mode"><span class="icon icon-link"></span></a>Non-interactive mode</h2>
<p>Kit can run as a non-interactive tool for scripting and automation.</p>
<p>Get JSON output:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Explain main.go"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span></span></code></pre>
<p>Quiet mode (final response only, no TUI):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Run tests"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span></span></code></pre>
<p>Ephemeral mode (no session file created):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Quick question"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<h2 id="resuming-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#resuming-sessions"><span class="icon icon-link"></span></a>Resuming sessions</h2>
<p>Continue the most recent session for the current directory:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --continue</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># or</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -c</span></span></code></pre>
<p>Pick from previous sessions interactively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --resume</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># or</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -r</span></span></code></pre>
<h2 id="acp-server-mode"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#acp-server-mode"><span class="icon icon-link"></span></a>ACP server mode</h2>
<p>Kit can run as an <a href="https://agentclientprotocol.com">ACP (Agent Client Protocol)</a> agent server, enabling ACP-compatible clients (such as <a href="https://github.com/sst/opencode">OpenCode</a>) to drive Kit as a remote coding agent over stdio:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start Kit as an ACP server (JSON-RPC 2.0 on stdin/stdout)</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># With debug logging to stderr</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> acp</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --debug</span></span></code></pre>
<p>The ACP server exposes Kit's full capabilities — LLM execution, tool calls (bash, read, write, edit, grep, etc.), and session persistence — over the standard ACP protocol.</p>`,headings:[{depth:2,text:"Basic usage",id:"basic-usage"},{depth:2,text:"Non-interactive mode",id:"non-interactive-mode"},{depth:2,text:"Resuming sessions",id:"resuming-sessions"},{depth:2,text:"ACP server mode",id:"acp-server-mode"}],raw:`
# Quick Start
## Basic usage
Start an interactive session:
\`\`\`bash
kit
\`\`\`
Run a one-off prompt:
\`\`\`bash
kit "List files in src/"
\`\`\`
Attach files as context using the \`@\` prefix:
\`\`\`bash
kit @main.go @test.go "Review these files"
\`\`\`
Binary files (images, audio, PDFs) are automatically detected via MIME type and sent as multimodal attachments. You can also reference MCP resources:
\`\`\`bash
kit @mcp:myserver:file:///data/report.csv "Summarize this data"
\`\`\`
Use a specific model:
\`\`\`bash
kit --model anthropic/claude-sonnet-latest
\`\`\`
## Non-interactive mode
Kit can run as a non-interactive tool for scripting and automation.
Get JSON output:
\`\`\`bash
kit "Explain main.go" --json
\`\`\`
Quiet mode (final response only, no TUI):
\`\`\`bash
kit "Run tests" --quiet
\`\`\`
Ephemeral mode (no session file created):
\`\`\`bash
kit "Quick question" --no-session
\`\`\`
## Resuming sessions
Continue the most recent session for the current directory:
\`\`\`bash
kit --continue
# or
kit -c
\`\`\`
Pick from previous sessions interactively:
\`\`\`bash
kit --resume
# or
kit -r
\`\`\`
## ACP server mode
Kit can run as an [ACP (Agent Client Protocol)](https://agentclientprotocol.com) agent server, enabling ACP-compatible clients (such as [OpenCode](https://github.com/sst/opencode)) to drive Kit as a remote coding agent over stdio:
\`\`\`bash
# Start Kit as an ACP server (JSON-RPC 2.0 on stdin/stdout)
kit acp
# With debug logging to stderr
kit acp --debug
\`\`\`
The ACP server exposes Kit's full capabilities — LLM execution, tool calls (bash, read, write, edit, grep, etc.), and session persistence — over the standard ACP protocol.
`};export{s as default};
+223
View File
@@ -0,0 +1,223 @@
const e={frontmatter:{title:"Session Management",description:"How Kit persists and manages conversation sessions.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="session-management"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-management"><span class="icon icon-link"></span></a>Session Management</h1>
<p>Kit uses a tree-based session model that supports branching and forking conversations.</p>
<h2 id="session-storage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-storage"><span class="icon icon-link"></span></a>Session storage</h2>
<p>Sessions are stored as JSONL (JSON Lines) files:</p>
<pre><code>~/.kit/sessions/&lt;cwd-path&gt;/&lt;timestamp&gt;_&lt;id&gt;.jsonl
</code></pre>
<p>Path separators in the working directory are replaced with <code>--</code>. For example, <code>/home/user/project</code> becomes <code>home--user--project</code>.</p>
<p>Each line in the session file is a JSON entry representing a message, tool call, model change, or extension data. The tree structure allows branching from any message to explore alternate paths.</p>
<h2 id="compaction"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#compaction"><span class="icon icon-link"></span></a>Compaction</h2>
<p>When conversations grow long, Kit can compact them to free up context window space. The compaction system:</p>
<ul>
<li><strong>Non-destructive</strong>: Old messages remain on disk for history; only the LLM context is summarized</li>
<li><strong>File tracking</strong>: Tracks which files were read and modified across compactions</li>
<li><strong>Split-turn handling</strong>: Can summarize large single turns by splitting them</li>
<li><strong>Tool result truncation</strong>: Caps tool output during serialization to stay within token budgets</li>
</ul>
<p>Use <code>/compact [focus]</code> to manually compact, or enable <code>--auto-compact</code> to compact automatically near the context limit.</p>
<h2 id="auto-cleanup"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#auto-cleanup"><span class="icon icon-link"></span></a>Auto-cleanup</h2>
<p>Kit automatically cleans up empty sessions on shutdown and when using <code>/resume</code>. A session is considered empty if it has no messages beyond the initial system prompt. This prevents cluttering your sessions directory with unused files.</p>
<p>To start fresh without creating a session file at all, use ephemeral mode:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<h2 id="resuming-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#resuming-sessions"><span class="icon icon-link"></span></a>Resuming sessions</h2>
<h3 id="continue-most-recent"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#continue-most-recent"><span class="icon icon-link"></span></a>Continue most recent</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --continue</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -c</span></span></code></pre>
<h3 id="interactive-picker"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#interactive-picker"><span class="icon icon-link"></span></a>Interactive picker</h3>
<p>Choose from previous sessions interactively:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --resume</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -r</span></span></code></pre>
<p>The session picker supports search, scope/filter toggles (all sessions vs. current directory), and session deletion. You can also open it during a session with the <code>/resume</code> slash command.</p>
<h3 id="open-a-specific-session"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#open-a-specific-session"><span class="icon icon-link"></span></a>Open a specific session</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --session</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/session.jsonl</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> path/to/session.jsonl</span></span></code></pre>
<h2 id="session-commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#session-commands"><span class="icon icon-link"></span></a>Session commands</h2>
<p>These slash commands are available during an interactive session:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/name [name]</code></td>
<td>Set or display the session's display name</td>
</tr>
<tr>
<td><code>/session</code></td>
<td>Show session info (path, ID, message count)</td>
</tr>
<tr>
<td><code>/resume</code></td>
<td>Open the session picker to switch sessions</td>
</tr>
<tr>
<td><code>/export [path]</code></td>
<td>Export session as JSONL (auto-generates path if omitted)</td>
</tr>
<tr>
<td><code>/import &lt;path&gt;</code></td>
<td>Import and switch to a session from a JSONL file</td>
</tr>
<tr>
<td><code>/share</code></td>
<td>Upload session to GitHub Gist and get a shareable viewer URL</td>
</tr>
<tr>
<td><code>/tree</code></td>
<td>Navigate the session tree</td>
</tr>
<tr>
<td><code>/fork</code></td>
<td>Fork to new session from an earlier message (creates new session file)</td>
</tr>
<tr>
<td><code>/new</code></td>
<td>Start a new session (creates new session file)</td>
</tr>
</tbody>
</table>
<h2 id="ephemeral-mode"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#ephemeral-mode"><span class="icon icon-link"></span></a>Ephemeral mode</h2>
<p>Run without creating a session file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span></span></code></pre>
<p>This is useful for one-off prompts, scripting, and subagent patterns where persistence isn't needed.</p>
<h2 id="sharing-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#sharing-sessions"><span class="icon icon-link"></span></a>Sharing sessions</h2>
<p>The <code>/share</code> command uploads your session JSONL to GitHub Gist (via the <code>gh</code> CLI) and prints a shareable viewer URL:</p>
<pre><code>/share
</code></pre>
<p>The shared session includes:</p>
<ul>
<li>The <strong>system prompt</strong> that was active during the conversation</li>
<li>The <strong>model</strong> used (e.g., <code>anthropic/claude-sonnet-4-5</code>)</li>
</ul>
<p>The viewer displays this information in a collapsible "System Prompt" section at the top of the session, with the model shown as a badge in the header.</p>
<p>The viewer is available at <code>https://go-kit.dev/session/#GIST_ID</code> and supports all message types including text, reasoning blocks, tool calls, images, and model changes.</p>
<p>You can also load any JSONL session via URL parameter: <code>https://go-kit.dev/session/?url=https://example.com/session.jsonl</code></p>
<h2 id="preferences-persistence"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#preferences-persistence"><span class="icon icon-link"></span></a>Preferences persistence</h2>
<p>Kit automatically saves your preferences across sessions to <code>~/.config/kit/preferences.yml</code>:</p>
<ul>
<li><strong>Theme</strong> — Set via <code>/theme &lt;name&gt;</code></li>
<li><strong>Model</strong> — Set via <code>/model &lt;name&gt;</code> or the model selector</li>
<li><strong>Thinking level</strong> — Set via <code>/thinking &lt;level&gt;</code> or Shift+Tab cycling</li>
</ul>
<p>These preferences are restored on next launch. Precedence: CLI flag &gt; config file &gt; saved preference &gt; default.</p>`,headings:[{depth:2,text:"Session storage",id:"session-storage"},{depth:2,text:"Compaction",id:"compaction"},{depth:2,text:"Auto-cleanup",id:"auto-cleanup"},{depth:2,text:"Resuming sessions",id:"resuming-sessions"},{depth:3,text:"Continue most recent",id:"continue-most-recent"},{depth:3,text:"Interactive picker",id:"interactive-picker"},{depth:3,text:"Open a specific session",id:"open-a-specific-session"},{depth:2,text:"Session commands",id:"session-commands"},{depth:2,text:"Ephemeral mode",id:"ephemeral-mode"},{depth:2,text:"Sharing sessions",id:"sharing-sessions"},{depth:2,text:"Preferences persistence",id:"preferences-persistence"}],raw:`
# Session Management
Kit uses a tree-based session model that supports branching and forking conversations.
## Session storage
Sessions are stored as JSONL (JSON Lines) files:
\`\`\`
~/.kit/sessions/<cwd-path>/<timestamp>_<id>.jsonl
\`\`\`
Path separators in the working directory are replaced with \`--\`. For example, \`/home/user/project\` becomes \`home--user--project\`.
Each line in the session file is a JSON entry representing a message, tool call, model change, or extension data. The tree structure allows branching from any message to explore alternate paths.
## Compaction
When conversations grow long, Kit can compact them to free up context window space. The compaction system:
- **Non-destructive**: Old messages remain on disk for history; only the LLM context is summarized
- **File tracking**: Tracks which files were read and modified across compactions
- **Split-turn handling**: Can summarize large single turns by splitting them
- **Tool result truncation**: Caps tool output during serialization to stay within token budgets
Use \`/compact [focus]\` to manually compact, or enable \`--auto-compact\` to compact automatically near the context limit.
## Auto-cleanup
Kit automatically cleans up empty sessions on shutdown and when using \`/resume\`. A session is considered empty if it has no messages beyond the initial system prompt. This prevents cluttering your sessions directory with unused files.
To start fresh without creating a session file at all, use ephemeral mode:
\`\`\`bash
kit --no-session
\`\`\`
## Resuming sessions
### Continue most recent
\`\`\`bash
kit --continue
kit -c
\`\`\`
### Interactive picker
Choose from previous sessions interactively:
\`\`\`bash
kit --resume
kit -r
\`\`\`
The session picker supports search, scope/filter toggles (all sessions vs. current directory), and session deletion. You can also open it during a session with the \`/resume\` slash command.
### Open a specific session
\`\`\`bash
kit --session path/to/session.jsonl
kit -s path/to/session.jsonl
\`\`\`
## Session commands
These slash commands are available during an interactive session:
| Command | Description |
|---------|-------------|
| \`/name [name]\` | Set or display the session's display name |
| \`/session\` | Show session info (path, ID, message count) |
| \`/resume\` | Open the session picker to switch sessions |
| \`/export [path]\` | Export session as JSONL (auto-generates path if omitted) |
| \`/import <path>\` | Import and switch to a session from a JSONL file |
| \`/share\` | Upload session to GitHub Gist and get a shareable viewer URL |
| \`/tree\` | Navigate the session tree |
| \`/fork\` | Fork to new session from an earlier message (creates new session file) |
| \`/new\` | Start a new session (creates new session file) |
## Ephemeral mode
Run without creating a session file:
\`\`\`bash
kit --no-session
\`\`\`
This is useful for one-off prompts, scripting, and subagent patterns where persistence isn't needed.
## Sharing sessions
The \`/share\` command uploads your session JSONL to GitHub Gist (via the \`gh\` CLI) and prints a shareable viewer URL:
\`\`\`
/share
\`\`\`
The shared session includes:
- The **system prompt** that was active during the conversation
- The **model** used (e.g., \`anthropic/claude-sonnet-4-5\`)
The viewer displays this information in a collapsible "System Prompt" section at the top of the session, with the model shown as a badge in the header.
The viewer is available at \`https://go-kit.dev/session/#GIST_ID\` and supports all message types including text, reasoning blocks, tool calls, images, and model changes.
You can also load any JSONL session via URL parameter: \`https://go-kit.dev/session/?url=https://example.com/session.jsonl\`
## Preferences persistence
Kit automatically saves your preferences across sessions to \`~/.config/kit/preferences.yml\`:
- **Theme** — Set via \`/theme <name>\`
- **Model** — Set via \`/model <name>\` or the model selector
- **Thinking level** — Set via \`/thinking <level>\` or Shift+Tab cycling
These preferences are restored on next launch. Precedence: CLI flag > config file > saved preference > default.
`};export{e as default};
+174
View File
@@ -0,0 +1,174 @@
const s={frontmatter:{title:"SDK Sessions",description:"Session management in the Kit Go SDK.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="sdk-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#sdk-sessions"><span class="icon icon-link"></span></a>SDK Sessions</h1>
<h2 id="automatic-persistence"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#automatic-persistence"><span class="icon icon-link"></span></a>Automatic persistence</h2>
<p>By default, Kit automatically persists sessions to JSONL files. Multi-turn conversations retain context across calls:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"My name is Alice"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">response, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Prompt</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"What's my name?"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// response: "Your name is Alice"</span></span></code></pre>
<h2 id="accessing-session-info"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#accessing-session-info"><span class="icon icon-link"></span></a>Accessing session info</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get the current session file path</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">path </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetSessionPath</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get the session ID</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">id </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetSessionID</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get the current model string</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">model </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetModelString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h2 id="configuring-sessions-via-options"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#configuring-sessions-via-options"><span class="icon icon-link"></span></a>Configuring sessions via Options</h2>
<p>Session behavior is configured at initialization:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Open a specific session file</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SessionPath: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./my-session.jsonl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Resume the most recent session for the current directory</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Continue: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Ephemeral mode (no file persistence)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> NoSession: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Custom session directory</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SessionDir: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/custom/sessions/"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h2 id="clearing-history"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#clearing-history"><span class="icon icon-link"></span></a>Clearing history</h2>
<p>Clear the in-memory conversation history (does not delete the session file):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ClearSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h2 id="tree-based-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#tree-based-sessions"><span class="icon icon-link"></span></a>Tree-based sessions</h2>
<p>Kit's session model is tree-based, supporting branching. You can branch from any entry to explore alternate conversation paths:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Access the tree session manager</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ts </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetTreeSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Branch from a specific entry</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Branch</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"entry-id-123"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="listing-and-managing-sessions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#listing-and-managing-sessions"><span class="icon icon-link"></span></a>Listing and managing sessions</h2>
<p>Package-level functions for session discovery:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// List sessions for a specific directory</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">sessions </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListSessions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/home/user/project"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// List all sessions across all directories</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">all </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListAllSessions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Delete a session file</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">DeleteSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"/path/to/session.jsonl"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="custom-session-manager"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-session-manager"><span class="icon icon-link"></span></a>Custom session manager</h2>
<p>For advanced use cases (databases, cloud storage, multi-user apps), implement the <code>SessionManager</code> interface to replace the default JSONL file backend:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> kit.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;</span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Options</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SessionManager: myCustomSession,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<p>The interface requires methods for message storage, branching, compaction, branch summaries, extension data, and lifecycle management. See the <a href="https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#SessionManager"><code>SessionManager</code> interface definition</a> for the complete method set.</p>
<p>The <code>AppendBranchSummary(fromID, summary)</code> method backs <code>host.CollapseBranch</code>,
which collapses a branch range into a single summary entry. Custom managers
that don't track branch summaries can return <code>kit.ErrBranchSummaryNotSupported</code>
from that method; <code>host.CollapseBranch</code> then surfaces the same sentinel so
callers can detect it with <code>errors.Is</code>.</p>
<p>When using a custom <code>SessionManager</code>, the <code>SessionPath</code>, <code>Continue</code>, and <code>NoSession</code> options are ignored — your manager handles its own storage and session selection.</p>`,headings:[{depth:2,text:"Automatic persistence",id:"automatic-persistence"},{depth:2,text:"Accessing session info",id:"accessing-session-info"},{depth:2,text:"Configuring sessions via Options",id:"configuring-sessions-via-options"},{depth:2,text:"Clearing history",id:"clearing-history"},{depth:2,text:"Tree-based sessions",id:"tree-based-sessions"},{depth:2,text:"Listing and managing sessions",id:"listing-and-managing-sessions"},{depth:2,text:"Custom session manager",id:"custom-session-manager"}],raw:`
# SDK Sessions
## Automatic persistence
By default, Kit automatically persists sessions to JSONL files. Multi-turn conversations retain context across calls:
\`\`\`go
host.Prompt(ctx, "My name is Alice")
response, _ := host.Prompt(ctx, "What's my name?")
// response: "Your name is Alice"
\`\`\`
## Accessing session info
\`\`\`go
// Get the current session file path
path := host.GetSessionPath()
// Get the session ID
id := host.GetSessionID()
// Get the current model string
model := host.GetModelString()
\`\`\`
## Configuring sessions via Options
Session behavior is configured at initialization:
\`\`\`go
// Open a specific session file
host, _ := kit.New(ctx, &kit.Options{
SessionPath: "./my-session.jsonl",
})
// Resume the most recent session for the current directory
host, _ := kit.New(ctx, &kit.Options{
Continue: true,
})
// Ephemeral mode (no file persistence)
host, _ := kit.New(ctx, &kit.Options{
NoSession: true,
})
// Custom session directory
host, _ := kit.New(ctx, &kit.Options{
SessionDir: "/custom/sessions/",
})
\`\`\`
## Clearing history
Clear the in-memory conversation history (does not delete the session file):
\`\`\`go
host.ClearSession()
\`\`\`
## Tree-based sessions
Kit's session model is tree-based, supporting branching. You can branch from any entry to explore alternate conversation paths:
\`\`\`go
// Access the tree session manager
ts := host.GetTreeSession()
// Branch from a specific entry
err := host.Branch("entry-id-123")
\`\`\`
## Listing and managing sessions
Package-level functions for session discovery:
\`\`\`go
// List sessions for a specific directory
sessions := kit.ListSessions("/home/user/project")
// List all sessions across all directories
all := kit.ListAllSessions()
// Delete a session file
kit.DeleteSession("/path/to/session.jsonl")
\`\`\`
## Custom session manager
For advanced use cases (databases, cloud storage, multi-user apps), implement the \`SessionManager\` interface to replace the default JSONL file backend:
\`\`\`go
host, _ := kit.New(ctx, &kit.Options{
SessionManager: myCustomSession,
})
\`\`\`
The interface requires methods for message storage, branching, compaction, branch summaries, extension data, and lifecycle management. See the [\`SessionManager\` interface definition](https://pkg.go.dev/github.com/mark3labs/kit/pkg/kit#SessionManager) for the complete method set.
The \`AppendBranchSummary(fromID, summary)\` method backs \`host.CollapseBranch\`,
which collapses a branch range into a single summary entry. Custom managers
that don't track branch summaries can return \`kit.ErrBranchSummaryNotSupported\`
from that method; \`host.CollapseBranch\` then surfaces the same sentinel so
callers can detect it with \`errors.Is\`.
When using a custom \`SessionManager\`, the \`SessionPath\`, \`Continue\`, and \`NoSession\` options are ignored — your manager handles its own storage and session selection.
`};export{s as default};
+315
View File
@@ -0,0 +1,315 @@
const s={frontmatter:{title:"Subagents",description:"Multi-agent orchestration with Kit subagents.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#subagents"><span class="icon icon-link"></span></a>Subagents</h1>
<p>Kit supports multi-agent orchestration through both subprocess spawning and in-process subagents.</p>
<h2 id="subprocess-pattern"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#subprocess-pattern"><span class="icon icon-link"></span></a>Subprocess pattern</h2>
<p>Spawn Kit as a subprocess for isolated agent execution:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Analyze codebase"</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --json</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --no-extensions</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --quiet</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF"> --model</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> anthropic/claude-haiku-latest</span></span></code></pre>
<p>Key flags for subprocess usage:</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--quiet</code></td>
<td>Stdout only, no TUI</td>
</tr>
<tr>
<td><code>--no-session</code></td>
<td>Ephemeral, no persistence</td>
</tr>
<tr>
<td><code>--no-extensions</code></td>
<td>Prevent recursive extension loading</td>
</tr>
<tr>
<td><code>--json</code></td>
<td>Machine-readable output</td>
</tr>
<tr>
<td><code>--system-prompt</code></td>
<td>Custom system prompt (string or file path)</td>
</tr>
</tbody>
</table>
<p>Positional arguments are the prompt. <code>@file</code> arguments attach file content as context.</p>
<h2 id="built-in-subagent-tool"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#built-in-subagent-tool"><span class="icon icon-link"></span></a>Built-in subagent tool</h2>
<p>Kit includes a built-in <code>subagent</code> tool that the LLM can use to delegate tasks to independent child agents:</p>
<pre><code>subagent(
task: "Analyze the test files and summarize coverage",
model: "anthropic/claude-haiku-latest", // optional
system_prompt: "You are a test analysis expert.", // optional
timeout_seconds: 300 // optional, max 1800
)
</code></pre>
<p>Subagents run as separate in-process Kit instances with full tool access (except spawning further subagents, to prevent infinite recursion). They can run in parallel.</p>
<h2 id="extension-subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-subagents"><span class="icon icon-link"></span></a>Extension subagents</h2>
<p>Extensions can spawn subagents programmatically:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SpawnSubagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Review this code for security issues"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Model: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-sonnet-latest"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SystemPrompt: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"You are a security auditor."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="monitoring-subagents-from-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#monitoring-subagents-from-extensions"><span class="icon icon-link"></span></a>Monitoring subagents from extensions</h3>
<p>When the LLM (not the extension itself) spawns a subagent using the <code>subagent</code> tool, extensions can monitor its activity in real-time using three lifecycle event handlers:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Track active subagents and display their output</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">var</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets </span><span style="color:#D73A49;--shiki-dark:#F97583">map</span><span style="color:#24292E;--shiki-dark:#E1E4E8">[</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">]</span><span style="color:#D73A49;--shiki-dark:#F97583">*</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentWidget</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> Init</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">api</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">API</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Subagent started by the main agent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSubagentStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ToolCallID — unique ID for this subagent invocation</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.Task — the task/prompt sent to the subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> NewWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e.ToolCallID, e.Task)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets[e.ToolCallID] </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Config</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Real-time streaming from subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSubagentChunk</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentChunkEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ToolCallID — matches the start event</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ChunkType — "text", "tool_call", "tool_execution_start", "tool_result"</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.Content — text content</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ToolName — tool name (for tool chunks)</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.IsError — true if tool result failed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets[e.ToolCallID]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AddOutput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Config</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Subagent completed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSubagentEnd</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentEndEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.Response — final response from subagent</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // e.ErrorMsg — error message if subagent failed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> subagentWidgets[e.ToolCallID]</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MarkComplete</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e.Response, e.ErrorMsg)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(widget.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Config</span><span style="color:#24292E;--shiki-dark:#E1E4E8">())</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0"> delete</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(subagentWidgets, e.ToolCallID)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p><strong>Event structs:</strong></p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SubagentStartEvent</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolCallID </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Unique ID for this subagent invocation</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // The task/prompt sent to subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SubagentChunkEvent</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolCallID </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Matches SubagentStartEvent.ToolCallID</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Task description</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ChunkType </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // "text", "tool_call", "tool_execution_start", "tool_result"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Content </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // For text chunks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolName </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // For tool-related chunks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> IsError </span><span style="color:#D73A49;--shiki-dark:#F97583">bool</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // For tool_result chunks</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> SubagentEndEvent</span><span style="color:#D73A49;--shiki-dark:#F97583"> struct</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolCallID </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Matches start event</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Task description</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Response </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Final response from subagent</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ErrorMsg </span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#6A737D;--shiki-dark:#6A737D"> // Error message if failed</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<p>This enables building monitoring widgets that display real-time activity from all subagents spawned by the main agent.</p>
<h2 id="go-sdk-subagents"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#go-sdk-subagents"><span class="icon icon-link"></span></a>Go SDK subagents</h2>
<p>The SDK provides in-process subagent spawning:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Subagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ctx, </span><span style="color:#6F42C1;--shiki-dark:#B392F0">kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubagentConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Task: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Summarize the changes in this PR"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Model: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"anthropic/claude-haiku-latest"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> SystemPrompt: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"You are a code reviewer."</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Timeout: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">5</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> time.Minute,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="real-time-subagent-events"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#real-time-subagent-events"><span class="icon icon-link"></span></a>Real-time subagent events</h3>
<p>Use <code>SubscribeSubagent</code> to receive real-time events from LLM-initiated subagents (i.e., when the model uses the <code>subagent</code> tool). Register inside an <code>OnToolCall</code> handler using the tool call ID:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnToolCall</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">e</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> e.ToolName </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "subagent"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> host.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SubscribeSubagent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(e.ToolCallID, </span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">event</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Event</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> switch</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ev </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> event.(</span><span style="color:#D73A49;--shiki-dark:#F97583">type</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">MessageUpdateEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Print</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(ev.Chunk) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// streaming text from child</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Child calling: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s\\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ev.ToolName)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> case</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> kit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolResultEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> fmt.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Printf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Child result: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s\\n</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, ev.ToolName)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<p>The listener receives the same event types as <code>Subscribe()</code> (<code>ToolCallEvent</code>, <code>MessageUpdateEvent</code>, <code>ReasoningDeltaEvent</code>, etc.) but scoped to the child agent's activity. Listeners are cleaned up automatically when the subagent completes.</p>
<p>If no listeners are registered for a tool call, no event dispatching overhead is incurred.</p>`,headings:[{depth:2,text:"Subprocess pattern",id:"subprocess-pattern"},{depth:2,text:"Built-in subagent tool",id:"built-in-subagent-tool"},{depth:2,text:"Extension subagents",id:"extension-subagents"},{depth:3,text:"Monitoring subagents from extensions",id:"monitoring-subagents-from-extensions"},{depth:2,text:"Go SDK subagents",id:"go-sdk-subagents"},{depth:3,text:"Real-time subagent events",id:"real-time-subagent-events"}],raw:`
# Subagents
Kit supports multi-agent orchestration through both subprocess spawning and in-process subagents.
## Subprocess pattern
Spawn Kit as a subprocess for isolated agent execution:
\`\`\`bash
kit "Analyze codebase" \\
--json \\
--no-session \\
--no-extensions \\
--quiet \\
--model anthropic/claude-haiku-latest
\`\`\`
Key flags for subprocess usage:
| Flag | Purpose |
|------|---------|
| \`--quiet\` | Stdout only, no TUI |
| \`--no-session\` | Ephemeral, no persistence |
| \`--no-extensions\` | Prevent recursive extension loading |
| \`--json\` | Machine-readable output |
| \`--system-prompt\` | Custom system prompt (string or file path) |
Positional arguments are the prompt. \`@file\` arguments attach file content as context.
## Built-in subagent tool
Kit includes a built-in \`subagent\` tool that the LLM can use to delegate tasks to independent child agents:
\`\`\`
subagent(
task: "Analyze the test files and summarize coverage",
model: "anthropic/claude-haiku-latest", // optional
system_prompt: "You are a test analysis expert.", // optional
timeout_seconds: 300 // optional, max 1800
)
\`\`\`
Subagents run as separate in-process Kit instances with full tool access (except spawning further subagents, to prevent infinite recursion). They can run in parallel.
## Extension subagents
Extensions can spawn subagents programmatically:
\`\`\`go
result := ctx.SpawnSubagent(ext.SubagentConfig{
Task: "Review this code for security issues",
Model: "anthropic/claude-sonnet-latest",
SystemPrompt: "You are a security auditor.",
})
\`\`\`
### Monitoring subagents from extensions
When the LLM (not the extension itself) spawns a subagent using the \`subagent\` tool, extensions can monitor its activity in real-time using three lifecycle event handlers:
\`\`\`go
// Track active subagents and display their output
var subagentWidgets map[string]*SubagentWidget
func Init(api ext.API) {
// Subagent started by the main agent
api.OnSubagentStart(func(e ext.SubagentStartEvent, ctx ext.Context) {
// e.ToolCallID — unique ID for this subagent invocation
// e.Task — the task/prompt sent to the subagent
widget := NewWidget(e.ToolCallID, e.Task)
subagentWidgets[e.ToolCallID] = widget
ctx.SetWidget(widget.Config())
})
// Real-time streaming from subagent
api.OnSubagentChunk(func(e ext.SubagentChunkEvent, ctx ext.Context) {
// e.ToolCallID — matches the start event
// e.ChunkType — "text", "tool_call", "tool_execution_start", "tool_result"
// e.Content — text content
// e.ToolName — tool name (for tool chunks)
// e.IsError — true if tool result failed
widget := subagentWidgets[e.ToolCallID]
if widget != nil {
widget.AddOutput(e)
ctx.SetWidget(widget.Config())
}
})
// Subagent completed
api.OnSubagentEnd(func(e ext.SubagentEndEvent, ctx ext.Context) {
// e.Response — final response from subagent
// e.ErrorMsg — error message if subagent failed
widget := subagentWidgets[e.ToolCallID]
if widget != nil {
widget.MarkComplete(e.Response, e.ErrorMsg)
ctx.SetWidget(widget.Config())
delete(subagentWidgets, e.ToolCallID)
}
})
}
\`\`\`
**Event structs:**
\`\`\`go
type SubagentStartEvent struct {
ToolCallID string // Unique ID for this subagent invocation
Task string // The task/prompt sent to subagent
}
type SubagentChunkEvent struct {
ToolCallID string // Matches SubagentStartEvent.ToolCallID
Task string // Task description
ChunkType string // "text", "tool_call", "tool_execution_start", "tool_result"
Content string // For text chunks
ToolName string // For tool-related chunks
IsError bool // For tool_result chunks
}
type SubagentEndEvent struct {
ToolCallID string // Matches start event
Task string // Task description
Response string // Final response from subagent
ErrorMsg string // Error message if failed
}
\`\`\`
This enables building monitoring widgets that display real-time activity from all subagents spawned by the main agent.
## Go SDK subagents
The SDK provides in-process subagent spawning:
\`\`\`go
result, err := host.Subagent(ctx, kit.SubagentConfig{
Task: "Summarize the changes in this PR",
Model: "anthropic/claude-haiku-latest",
SystemPrompt: "You are a code reviewer.",
Timeout: 5 * time.Minute,
})
\`\`\`
### Real-time subagent events
Use \`SubscribeSubagent\` to receive real-time events from LLM-initiated subagents (i.e., when the model uses the \`subagent\` tool). Register inside an \`OnToolCall\` handler using the tool call ID:
\`\`\`go
host.OnToolCall(func(e kit.ToolCallEvent) {
if e.ToolName == "subagent" {
host.SubscribeSubagent(e.ToolCallID, func(event kit.Event) {
switch ev := event.(type) {
case kit.MessageUpdateEvent:
fmt.Print(ev.Chunk) // streaming text from child
case kit.ToolCallEvent:
fmt.Printf("Child calling: %s\\n", ev.ToolName)
case kit.ToolResultEvent:
fmt.Printf("Child result: %s\\n", ev.ToolName)
}
})
}
})
\`\`\`
The listener receives the same event types as \`Subscribe()\` (\`ToolCallEvent\`, \`MessageUpdateEvent\`, \`ReasoningDeltaEvent\`, etc.) but scoped to the child agent's activity. Listeners are cleaned up automatically when the subagent completes.
If no listeners are registered for a tool call, no event dispatching overhead is incurred.
`};export{s as default};
+130
View File
@@ -0,0 +1,130 @@
const s={frontmatter:{title:"Testing with tmux",description:"Test Kit's TUI non-interactively using tmux.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="testing-with-tmux"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-with-tmux"><span class="icon icon-link"></span></a>Testing with tmux</h1>
<p>Kit's interactive TUI can be tested non-interactively using tmux. This is useful for automated testing, CI pipelines, and extension development.</p>
<h2 id="basic-pattern"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-pattern"><span class="icon icon-link"></span></a>Basic pattern</h2>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start Kit in a detached tmux session</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> new-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 120</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -y</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 40</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "output/kit -e ext.go --no-session 2&gt;kit_stderr.log"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Wait for startup</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Capture the current screen</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Send input</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> send-keys</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/command'</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Enter</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Wait for response</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 2</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Capture updated screen</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Cleanup</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kill-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span></span></code></pre>
<h2 id="testing-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-extensions"><span class="icon icon-link"></span></a>Testing extensions</h2>
<p>When testing extensions, the pattern is:</p>
<ol>
<li>Build Kit with your changes</li>
<li>Start Kit in tmux with the extension loaded</li>
<li>Send slash commands or prompts</li>
<li>Capture and verify the screen output</li>
<li>Check stderr logs for errors</li>
</ol>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Build first</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> build</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -o</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> output/kit</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> ./cmd/kit</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Start with extension</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> new-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -d</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -x</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 120</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -y</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 40</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> \\</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "output/kit -e examples/extensions/widget-status.go --no-session 2&gt;kit_stderr.log"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 3</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Verify widget appears in screen</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span><span style="color:#D73A49;--shiki-dark:#F97583"> |</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> grep</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "Status"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Send a slash command</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> send-keys</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> '/stats'</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> Enter</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">sleep</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> capture-pane</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -p</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Cleanup</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">tmux</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kill-session</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -t</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> kittest</span></span></code></pre>
<h2 id="tips"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#tips"><span class="icon icon-link"></span></a>Tips</h2>
<ul>
<li>Use <code>-x</code> and <code>-y</code> to set consistent terminal dimensions</li>
<li>Redirect stderr to a log file (<code>2&gt;kit.log</code>) for debugging</li>
<li>Use <code>--no-session</code> to avoid creating session files during tests</li>
<li>Add sufficient <code>sleep</code> between commands for the TUI to render</li>
<li>Use <code>grep</code> on captured pane output to verify specific content</li>
</ul>`,headings:[{depth:2,text:"Basic pattern",id:"basic-pattern"},{depth:2,text:"Testing extensions",id:"testing-extensions"},{depth:2,text:"Tips",id:"tips"}],raw:`
# Testing with tmux
Kit's interactive TUI can be tested non-interactively using tmux. This is useful for automated testing, CI pipelines, and extension development.
## Basic pattern
\`\`\`bash
# Start Kit in a detached tmux session
tmux new-session -d -s kittest -x 120 -y 40 \\
"output/kit -e ext.go --no-session 2>kit_stderr.log"
# Wait for startup
sleep 3
# Capture the current screen
tmux capture-pane -t kittest -p
# Send input
tmux send-keys -t kittest '/command' Enter
# Wait for response
sleep 2
# Capture updated screen
tmux capture-pane -t kittest -p
# Cleanup
tmux kill-session -t kittest
\`\`\`
## Testing extensions
When testing extensions, the pattern is:
1. Build Kit with your changes
2. Start Kit in tmux with the extension loaded
3. Send slash commands or prompts
4. Capture and verify the screen output
5. Check stderr logs for errors
\`\`\`bash
# Build first
go build -o output/kit ./cmd/kit
# Start with extension
tmux new-session -d -s kittest -x 120 -y 40 \\
"output/kit -e examples/extensions/widget-status.go --no-session 2>kit_stderr.log"
sleep 3
# Verify widget appears in screen
tmux capture-pane -t kittest -p | grep "Status"
# Send a slash command
tmux send-keys -t kittest '/stats' Enter
sleep 1
tmux capture-pane -t kittest -p
# Cleanup
tmux kill-session -t kittest
\`\`\`
## Tips
- Use \`-x\` and \`-y\` to set consistent terminal dimensions
- Redirect stderr to a log file (\`2>kit.log\`) for debugging
- Use \`--no-session\` to avoid creating session files during tests
- Add sufficient \`sleep\` between commands for the TUI to render
- Use \`grep\` on captured pane output to verify specific content
`};export{s as default};
+915
View File
@@ -0,0 +1,915 @@
const s={frontmatter:{title:"Testing Extensions",description:"Write unit tests for your Kit extensions using the test package.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="testing-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-extensions"><span class="icon icon-link"></span></a>Testing Extensions</h1>
<p>Kit provides a testing package (<code>github.com/mark3labs/kit/pkg/extensions/test</code>) that enables you to write unit tests for your extensions. Tests run outside the Yaegi interpreter but load your extension code into an isolated interpreter instance, allowing you to verify behavior without running the full Kit TUI.</p>
<h2 id="overview"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#overview"><span class="icon icon-link"></span></a>Overview</h2>
<p>Extension tests allow you to:</p>
<ul>
<li>Test event handlers without running the interactive TUI</li>
<li>Verify tool/command registration</li>
<li>Assert that context methods (Print, SetWidget, etc.) are called correctly</li>
<li>Test blocking and non-blocking event handling</li>
<li>Simulate user input and tool calls</li>
<li>Verify widget, header, footer, and status bar updates</li>
</ul>
<h2 id="installation"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#installation"><span class="icon icon-link"></span></a>Installation</h2>
<p>The test package is part of the Kit codebase. Import it in your extension tests:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/pkg/extensions/test</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/internal/extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h2 id="basic-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#basic-usage"><span class="icon icon-link"></span></a>Basic Usage</h2>
<h3 id="testing-an-extension-file"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-an-extension-file"><span class="icon icon-link"></span></a>Testing an Extension File</h3>
<p>Create a test file alongside your extension (e.g., <code>my-ext_test.go</code>):</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">package</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">import</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> (</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/pkg/extensions/test</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> "</span><span style="color:#6F42C1;--shiki-dark:#B392F0">github.com/mark3labs/kit/internal/extensions</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestMyExtension</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Create a test harness</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Load your extension</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Emit events and check results</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolName: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my_tool"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Input: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">\`{"key": "value"}\`</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> err </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Fatalf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"unexpected error: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, err)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Use assertion helpers</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertNotBlocked</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, result)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrinted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"expected output"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-inline-extension-code"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-inline-extension-code"><span class="icon icon-link"></span></a>Testing Inline Extension Code</h3>
<p>For quick tests or edge cases, you can load extension source directly:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestToolBlocking</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> src </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> \`package main</span></span>
<span class="line"></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">import "kit/ext"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">func Init(api ext.API) {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> if tc.ToolName == "dangerous" {</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> return &amp;ext.ToolCallResult{Block: true, Reason: "not allowed"}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> }</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> return nil</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF"> })</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">}</span></span>
<span class="line"><span style="color:#032F62;--shiki-dark:#9ECBFF">\`</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadString</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(src, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Test the tool is blocked</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ToolName: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"dangerous"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Input: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"{}"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertBlocked</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, result, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"not allowed"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="common-testing-patterns"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#common-testing-patterns"><span class="icon icon-link"></span></a>Common Testing Patterns</h2>
<h3 id="testing-handler-registration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-handler-registration"><span class="icon icon-link"></span></a>Testing Handler Registration</h3>
<p>Verify your extension registers the expected handlers:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertHasHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, extensions.ToolCall)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertHasHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, extensions.SessionStart)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertNoHandlers</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, extensions.AgentEnd) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// Verify no unexpected handlers</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-tool-registration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-tool-registration"><span class="icon icon-link"></span></a>Testing Tool Registration</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestTools</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Verify a specific tool is registered</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertToolRegistered</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my_tool"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Or inspect all tools</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RegisteredTools</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, tool </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#D73A49;--shiki-dark:#F97583"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Tool: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> - </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, tool.Name, tool.Description)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-commands"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-commands"><span class="icon icon-link"></span></a>Testing Commands</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestCommands</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertCommandRegistered</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"mycommand"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-widgets"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-widgets"><span class="icon icon-link"></span></a>Testing Widgets</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestWidgets</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Trigger event that creates the widget</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Verify widget was set</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetText</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Expected Text"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetTextContains</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"partial"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Check widget properties directly</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget, ok </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ok {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Border color: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, widget.Style.BorderColor)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-input-handling"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-input-handling"><span class="icon icon-link"></span></a>Testing Input Handling</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestInput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">InputEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Text: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"!mycommand"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Source: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"cli"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertInputHandled</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, result, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"handled"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-headers-and-footers"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-headers-and-footers"><span class="icon icon-link"></span></a>Testing Headers and Footers</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestHeaderFooter</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertHeaderSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertFooterSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Inspect content</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> header </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetHeader</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> header </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Header text: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, header.Content.Text)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-status-bar"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-status-bar"><span class="icon icon-link"></span></a>Testing Status Bar</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestStatus</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AgentEndEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertStatusSet</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"myext:status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertStatusText</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"myext:status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Ready"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-print-output"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-print-output"><span class="icon icon-link"></span></a>Testing Print Output</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestOutput</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{ToolName: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Exact match</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrinted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"exact output"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Partial match</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrintedContains</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"partial"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Styled output</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrintInfo</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"info message"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertPrintError</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"error message"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-with-prompts"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-with-prompts"><span class="icon icon-link"></span></a>Testing with Prompts</h3>
<p>Configure mock prompt results for testing interactive behavior:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestWithPrompts</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Configure what prompts should return</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetPromptSelectResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PromptSelectResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Value: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"option1"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Index: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">0</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Cancelled: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">().</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetPromptConfirmResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">PromptConfirmResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Value: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">true</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Cancelled: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">false</span><span style="color:#24292E;--shiki-dark:#E1E4E8">,</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Now when your extension calls ctx.PromptSelect(), it gets this result</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="testing-complete-session-flow"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-complete-session-flow"><span class="icon icon-link"></span></a>Testing Complete Session Flow</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestFullSession</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Simulate a complete session</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">BeforeAgentStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AgentStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Multiple tool calls</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> []</span><span style="color:#D73A49;--shiki-dark:#F97583">string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Read"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Grep"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Bash"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583"> for</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, tool </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#D73A49;--shiki-dark:#F97583"> range</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tools {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{ToolName: tool})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolResultEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{ToolName: tool})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> }</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AgentEndEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> _, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionShutdownEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> </span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // Verify final state</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">AssertWidgetTextContains</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t, harness, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Complete"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h2 id="available-assertions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#available-assertions"><span class="icon icon-link"></span></a>Available Assertions</h2>
<p>The test package provides these assertion helpers:</p>
<h3 id="event-results"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#event-results"><span class="icon icon-link"></span></a>Event Results</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertNotBlocked(t, result)</code></td>
<td>Verify tool was not blocked</td>
</tr>
<tr>
<td><code>AssertBlocked(t, result, reason)</code></td>
<td>Verify tool was blocked with reason</td>
</tr>
<tr>
<td><code>AssertInputHandled(t, result, action)</code></td>
<td>Verify input was handled</td>
</tr>
<tr>
<td><code>AssertInputTransformed(t, result, text)</code></td>
<td>Verify input was transformed</td>
</tr>
</tbody>
</table>
<h3 id="context-interactions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#context-interactions"><span class="icon icon-link"></span></a>Context Interactions</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertPrinted(t, harness, text)</code></td>
<td>Verify exact print output</td>
</tr>
<tr>
<td><code>AssertPrintedContains(t, harness, substring)</code></td>
<td>Verify partial print output</td>
</tr>
<tr>
<td><code>AssertPrintInfo(t, harness, text)</code></td>
<td>Verify PrintInfo was called</td>
</tr>
<tr>
<td><code>AssertPrintError(t, harness, text)</code></td>
<td>Verify PrintError was called</td>
</tr>
<tr>
<td><code>AssertWidgetSet(t, harness, id)</code></td>
<td>Verify widget was set</td>
</tr>
<tr>
<td><code>AssertWidgetNotSet(t, harness, id)</code></td>
<td>Verify widget was not set</td>
</tr>
<tr>
<td><code>AssertWidgetText(t, harness, id, text)</code></td>
<td>Verify widget content</td>
</tr>
<tr>
<td><code>AssertWidgetTextContains(t, harness, id, substring)</code></td>
<td>Verify widget contains text</td>
</tr>
<tr>
<td><code>AssertHeaderSet(t, harness)</code></td>
<td>Verify header was set</td>
</tr>
<tr>
<td><code>AssertFooterSet(t, harness)</code></td>
<td>Verify footer was set</td>
</tr>
<tr>
<td><code>AssertStatusSet(t, harness, key)</code></td>
<td>Verify status was set</td>
</tr>
<tr>
<td><code>AssertStatusText(t, harness, key, text)</code></td>
<td>Verify status text</td>
</tr>
</tbody>
</table>
<h3 id="registration"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#registration"><span class="icon icon-link"></span></a>Registration</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertToolRegistered(t, harness, name)</code></td>
<td>Verify tool registration</td>
</tr>
<tr>
<td><code>AssertCommandRegistered(t, harness, name)</code></td>
<td>Verify command registration</td>
</tr>
<tr>
<td><code>AssertHasHandlers(t, harness, eventType)</code></td>
<td>Verify handlers exist</td>
</tr>
<tr>
<td><code>AssertNoHandlers(t, harness, eventType)</code></td>
<td>Verify no handlers</td>
</tr>
</tbody>
</table>
<h3 id="messaging"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#messaging"><span class="icon icon-link"></span></a>Messaging</h3>
<table>
<thead>
<tr>
<th>Function</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>AssertMessageSent(t, harness, text)</code></td>
<td>Verify SendMessage was called</td>
</tr>
<tr>
<td><code>AssertCancelAndSend(t, harness, text)</code></td>
<td>Verify CancelAndSend was called</td>
</tr>
</tbody>
</table>
<h2 id="helper-functions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#helper-functions"><span class="icon icon-link"></span></a>Helper Functions</h2>
<p>For custom assertions, extract result details:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">result, _ </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ToolCallEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span><span style="color:#D73A49;--shiki-dark:#F97583">...</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">tcr </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetToolCallResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> tcr </span><span style="color:#D73A49;--shiki-dark:#F97583">!=</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> nil</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Logf</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Block: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%v</span><span style="color:#032F62;--shiki-dark:#9ECBFF">, Reason: </span><span style="color:#005CC5;--shiki-dark:#79B8FF">%s</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, tcr.Block, tcr.Reason)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ir </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetInputResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">trr </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetToolResultResult</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(result)</span></span></code></pre>
<h2 id="advanced-usage"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#advanced-usage"><span class="icon icon-link"></span></a>Advanced Usage</h2>
<h3 id="accessing-the-mock-context"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#accessing-the-mock-context"><span class="icon icon-link"></span></a>Accessing the Mock Context</h3>
<p>For custom verification:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">ctx </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Get all recorded prints</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">prints </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetPrints</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Check options</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">value </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetOption</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-option"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Verify widget properties</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">widget, ok </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetWidget</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-widget"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">if</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ok </span><span style="color:#D73A49;--shiki-dark:#F97583">&amp;&amp;</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> widget.Style.BorderColor </span><span style="color:#D73A49;--shiki-dark:#F97583">==</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> "#ff0000"</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Log</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"Widget has red border"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Check status entries</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">status, ok </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">GetStatus</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"myext:status"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h3 id="testing-multiple-extensions"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-multiple-extensions"><span class="icon icon-link"></span></a>Testing Multiple Extensions</h3>
<p>Each harness is isolated:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness1 </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness1.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"ext1.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness2 </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">harness2.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"ext2.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D">// Events to one don't affect the other</span></span></code></pre>
<h3 id="testing-extensions-that-read-environment-variables"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#testing-extensions-that-read-environment-variables"><span class="icon icon-link"></span></a>Testing extensions that read environment variables</h3>
<p>The harness seeds the interpreter with the process environment, mirroring the
production loader, so an extension's <code>os.Getenv</code> / <code>os.LookupEnv</code> / <code>os.Environ</code>
calls work in tests. Set test-specific variables with <code>t.Setenv</code> <strong>before</strong>
loading the extension, since the environment is snapshotted at load time:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> TestReadsEnv</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">t</span><span style="color:#D73A49;--shiki-dark:#F97583"> *</span><span style="color:#6F42C1;--shiki-dark:#B392F0">testing</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">T</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> t.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Setenv</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"MY_API_KEY"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"test-value"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> test.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">New</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(t)</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">LoadFile</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"my-ext.go"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) </span><span style="color:#6A737D;--shiki-dark:#6A737D">// snapshots the env, incl. MY_API_KEY</span></span>
<span class="line"></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> harness.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Emit</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#6F42C1;--shiki-dark:#B392F0">extensions</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{SessionID: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"s1"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"> // assert on behavior that depends on MY_API_KEY</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">}</span></span></code></pre>
<h3 id="running-tests"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#running-tests"><span class="icon icon-link"></span></a>Running Tests</h3>
<p>Run all tests in your extension directory:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#005CC5;--shiki-dark:#79B8FF">cd</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> examples/extensions</span></span>
<span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span></span></code></pre>
<p>Run with race detector:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -race</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span></span></code></pre>
<p>Run a specific test:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6F42C1;--shiki-dark:#B392F0">go</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> test</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -v</span><span style="color:#005CC5;--shiki-dark:#79B8FF"> -run</span><span style="color:#032F62;--shiki-dark:#9ECBFF"> TestMyExtension</span></span></code></pre>
<h2 id="best-practices"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#best-practices"><span class="icon icon-link"></span></a>Best Practices</h2>
<ol>
<li><strong>Test one behavior per test</strong> — Keep tests focused and readable</li>
<li><strong>Use inline source for edge cases</strong> — <code>LoadString()</code> is great for testing specific scenarios</li>
<li><strong>Use <code>LoadFile()</code> for integration tests</strong> — Tests the actual extension file</li>
<li><strong>Assert on context calls</strong> — Verify your extension interacts with the context correctly</li>
<li><strong>Test both positive and negative cases</strong> — Verify tools are blocked AND allowed appropriately</li>
<li><strong>Test all event handlers</strong> — Make sure all registered handlers work correctly</li>
<li><strong>Use descriptive test names</strong> — <code>TestExtension_BlocksDangerousTools</code> is clearer than <code>Test1</code></li>
</ol>
<h2 id="limitations"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#limitations"><span class="icon icon-link"></span></a>Limitations</h2>
<p>The test harness has these intentional limitations:</p>
<ul>
<li><strong>No TUI rendering</strong> — Widgets are recorded but not rendered visually</li>
<li><strong>Prompts return configured values</strong> — Pre-configure prompt results in tests</li>
<li><strong>Subagents don't spawn real processes</strong> — <code>SpawnSubagent()</code> returns nil/empty results</li>
<li><strong>LLM completions are mocked</strong> — <code>Complete()</code> returns empty responses</li>
<li><strong>Some context methods are no-ops</strong> — <code>Exit()</code>, <code>SetActiveTools()</code>, etc. don't have side effects</li>
</ul>
<p>These limitations focus testing on extension logic rather than the full Kit runtime.</p>
<h2 id="complete-example"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#complete-example"><span class="icon icon-link"></span></a>Complete Example</h2>
<p>See <code>examples/extensions/tool-logger_test.go</code> for a complete example with 14 tests covering:</p>
<ul>
<li>Handler registration</li>
<li>Tool call and result handling</li>
<li>Session lifecycle events</li>
<li>Input commands (<code>!time</code>, <code>!status</code>)</li>
<li>Unknown command handling</li>
<li>Concurrent operations (race condition check)</li>
<li>Real file logging verification</li>
</ul>`,headings:[{depth:2,text:"Overview",id:"overview"},{depth:2,text:"Installation",id:"installation"},{depth:2,text:"Basic Usage",id:"basic-usage"},{depth:3,text:"Testing an Extension File",id:"testing-an-extension-file"},{depth:3,text:"Testing Inline Extension Code",id:"testing-inline-extension-code"},{depth:2,text:"Common Testing Patterns",id:"common-testing-patterns"},{depth:3,text:"Testing Handler Registration",id:"testing-handler-registration"},{depth:3,text:"Testing Tool Registration",id:"testing-tool-registration"},{depth:3,text:"Testing Commands",id:"testing-commands"},{depth:3,text:"Testing Widgets",id:"testing-widgets"},{depth:3,text:"Testing Input Handling",id:"testing-input-handling"},{depth:3,text:"Testing Headers and Footers",id:"testing-headers-and-footers"},{depth:3,text:"Testing Status Bar",id:"testing-status-bar"},{depth:3,text:"Testing Print Output",id:"testing-print-output"},{depth:3,text:"Testing with Prompts",id:"testing-with-prompts"},{depth:3,text:"Testing Complete Session Flow",id:"testing-complete-session-flow"},{depth:2,text:"Available Assertions",id:"available-assertions"},{depth:3,text:"Event Results",id:"event-results"},{depth:3,text:"Context Interactions",id:"context-interactions"},{depth:3,text:"Registration",id:"registration"},{depth:3,text:"Messaging",id:"messaging"},{depth:2,text:"Helper Functions",id:"helper-functions"},{depth:2,text:"Advanced Usage",id:"advanced-usage"},{depth:3,text:"Accessing the Mock Context",id:"accessing-the-mock-context"},{depth:3,text:"Testing Multiple Extensions",id:"testing-multiple-extensions"},{depth:3,text:"Testing extensions that read environment variables",id:"testing-extensions-that-read-environment-variables"},{depth:3,text:"Running Tests",id:"running-tests"},{depth:2,text:"Best Practices",id:"best-practices"},{depth:2,text:"Limitations",id:"limitations"},{depth:2,text:"Complete Example",id:"complete-example"}],raw:`
# Testing Extensions
Kit provides a testing package (\`github.com/mark3labs/kit/pkg/extensions/test\`) that enables you to write unit tests for your extensions. Tests run outside the Yaegi interpreter but load your extension code into an isolated interpreter instance, allowing you to verify behavior without running the full Kit TUI.
## Overview
Extension tests allow you to:
- Test event handlers without running the interactive TUI
- Verify tool/command registration
- Assert that context methods (Print, SetWidget, etc.) are called correctly
- Test blocking and non-blocking event handling
- Simulate user input and tool calls
- Verify widget, header, footer, and status bar updates
## Installation
The test package is part of the Kit codebase. Import it in your extension tests:
\`\`\`go
import (
"testing"
"github.com/mark3labs/kit/pkg/extensions/test"
"github.com/mark3labs/kit/internal/extensions"
)
\`\`\`
## Basic Usage
### Testing an Extension File
Create a test file alongside your extension (e.g., \`my-ext_test.go\`):
\`\`\`go
package main
import (
"testing"
"github.com/mark3labs/kit/pkg/extensions/test"
"github.com/mark3labs/kit/internal/extensions"
)
func TestMyExtension(t *testing.T) {
// Create a test harness
harness := test.New(t)
// Load your extension
harness.LoadFile("my-ext.go")
// Emit events and check results
result, err := harness.Emit(extensions.ToolCallEvent{
ToolName: "my_tool",
Input: \`{"key": "value"}\`,
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Use assertion helpers
test.AssertNotBlocked(t, result)
test.AssertPrinted(t, harness, "expected output")
}
\`\`\`
### Testing Inline Extension Code
For quick tests or edge cases, you can load extension source directly:
\`\`\`go
func TestToolBlocking(t *testing.T) {
src := \`package main
import "kit/ext"
func Init(api ext.API) {
api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {
if tc.ToolName == "dangerous" {
return &ext.ToolCallResult{Block: true, Reason: "not allowed"}
}
return nil
})
}
\`
harness := test.New(t)
harness.LoadString(src, "test-ext.go")
// Test the tool is blocked
result, _ := harness.Emit(extensions.ToolCallEvent{
ToolName: "dangerous",
Input: "{}",
})
test.AssertBlocked(t, result, "not allowed")
}
\`\`\`
## Common Testing Patterns
### Testing Handler Registration
Verify your extension registers the expected handlers:
\`\`\`go
func TestHandlers(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
test.AssertHasHandlers(t, harness, extensions.ToolCall)
test.AssertHasHandlers(t, harness, extensions.SessionStart)
test.AssertNoHandlers(t, harness, extensions.AgentEnd) // Verify no unexpected handlers
}
\`\`\`
### Testing Tool Registration
\`\`\`go
func TestTools(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Verify a specific tool is registered
test.AssertToolRegistered(t, harness, "my_tool")
// Or inspect all tools
tools := harness.RegisteredTools()
for _, tool := range tools {
t.Logf("Tool: %s - %s", tool.Name, tool.Description)
}
}
\`\`\`
### Testing Commands
\`\`\`go
func TestCommands(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
test.AssertCommandRegistered(t, harness, "mycommand")
}
\`\`\`
### Testing Widgets
\`\`\`go
func TestWidgets(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Trigger event that creates the widget
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
// Verify widget was set
test.AssertWidgetSet(t, harness, "my-widget")
test.AssertWidgetText(t, harness, "my-widget", "Expected Text")
test.AssertWidgetTextContains(t, harness, "my-widget", "partial")
// Check widget properties directly
widget, ok := harness.Context().GetWidget("my-widget")
if ok {
t.Logf("Border color: %s", widget.Style.BorderColor)
}
}
\`\`\`
### Testing Input Handling
\`\`\`go
func TestInput(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
result, _ := harness.Emit(extensions.InputEvent{
Text: "!mycommand",
Source: "cli",
})
test.AssertInputHandled(t, result, "handled")
}
\`\`\`
### Testing Headers and Footers
\`\`\`go
func TestHeaderFooter(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
test.AssertHeaderSet(t, harness)
test.AssertFooterSet(t, harness)
// Inspect content
header := harness.Context().GetHeader()
if header != nil {
t.Logf("Header text: %s", header.Content.Text)
}
}
\`\`\`
### Testing Status Bar
\`\`\`go
func TestStatus(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
_, _ = harness.Emit(extensions.AgentEndEvent{})
test.AssertStatusSet(t, harness, "myext:status")
test.AssertStatusText(t, harness, "myext:status", "Ready")
}
\`\`\`
### Testing Print Output
\`\`\`go
func TestOutput(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
_, _ = harness.Emit(extensions.ToolCallEvent{ToolName: "test"})
// Exact match
test.AssertPrinted(t, harness, "exact output")
// Partial match
test.AssertPrintedContains(t, harness, "partial")
// Styled output
test.AssertPrintInfo(t, harness, "info message")
test.AssertPrintError(t, harness, "error message")
}
\`\`\`
### Testing with Prompts
Configure mock prompt results for testing interactive behavior:
\`\`\`go
func TestWithPrompts(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Configure what prompts should return
harness.Context().SetPromptSelectResult(extensions.PromptSelectResult{
Value: "option1",
Index: 0,
Cancelled: false,
})
harness.Context().SetPromptConfirmResult(extensions.PromptConfirmResult{
Value: true,
Cancelled: false,
})
// Now when your extension calls ctx.PromptSelect(), it gets this result
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
}
\`\`\`
### Testing Complete Session Flow
\`\`\`go
func TestFullSession(t *testing.T) {
harness := test.New(t)
harness.LoadFile("my-ext.go")
// Simulate a complete session
_, _ = harness.Emit(extensions.SessionStartEvent{SessionID: "test"})
_, _ = harness.Emit(extensions.BeforeAgentStartEvent{})
_, _ = harness.Emit(extensions.AgentStartEvent{})
// Multiple tool calls
tools := []string{"Read", "Grep", "Bash"}
for _, tool := range tools {
_, _ = harness.Emit(extensions.ToolCallEvent{ToolName: tool})
_, _ = harness.Emit(extensions.ToolResultEvent{ToolName: tool})
}
_, _ = harness.Emit(extensions.AgentEndEvent{})
_, _ = harness.Emit(extensions.SessionShutdownEvent{})
// Verify final state
test.AssertWidgetTextContains(t, harness, "status", "Complete")
}
\`\`\`
## Available Assertions
The test package provides these assertion helpers:
### Event Results
| Function | Description |
|----------|-------------|
| \`AssertNotBlocked(t, result)\` | Verify tool was not blocked |
| \`AssertBlocked(t, result, reason)\` | Verify tool was blocked with reason |
| \`AssertInputHandled(t, result, action)\` | Verify input was handled |
| \`AssertInputTransformed(t, result, text)\` | Verify input was transformed |
### Context Interactions
| Function | Description |
|----------|-------------|
| \`AssertPrinted(t, harness, text)\` | Verify exact print output |
| \`AssertPrintedContains(t, harness, substring)\` | Verify partial print output |
| \`AssertPrintInfo(t, harness, text)\` | Verify PrintInfo was called |
| \`AssertPrintError(t, harness, text)\` | Verify PrintError was called |
| \`AssertWidgetSet(t, harness, id)\` | Verify widget was set |
| \`AssertWidgetNotSet(t, harness, id)\` | Verify widget was not set |
| \`AssertWidgetText(t, harness, id, text)\` | Verify widget content |
| \`AssertWidgetTextContains(t, harness, id, substring)\` | Verify widget contains text |
| \`AssertHeaderSet(t, harness)\` | Verify header was set |
| \`AssertFooterSet(t, harness)\` | Verify footer was set |
| \`AssertStatusSet(t, harness, key)\` | Verify status was set |
| \`AssertStatusText(t, harness, key, text)\` | Verify status text |
### Registration
| Function | Description |
|----------|-------------|
| \`AssertToolRegistered(t, harness, name)\` | Verify tool registration |
| \`AssertCommandRegistered(t, harness, name)\` | Verify command registration |
| \`AssertHasHandlers(t, harness, eventType)\` | Verify handlers exist |
| \`AssertNoHandlers(t, harness, eventType)\` | Verify no handlers |
### Messaging
| Function | Description |
|----------|-------------|
| \`AssertMessageSent(t, harness, text)\` | Verify SendMessage was called |
| \`AssertCancelAndSend(t, harness, text)\` | Verify CancelAndSend was called |
## Helper Functions
For custom assertions, extract result details:
\`\`\`go
result, _ := harness.Emit(extensions.ToolCallEvent{...})
tcr := test.GetToolCallResult(result)
if tcr != nil {
t.Logf("Block: %v, Reason: %s", tcr.Block, tcr.Reason)
}
ir := test.GetInputResult(result)
trr := test.GetToolResultResult(result)
\`\`\`
## Advanced Usage
### Accessing the Mock Context
For custom verification:
\`\`\`go
ctx := harness.Context()
// Get all recorded prints
prints := ctx.GetPrints()
// Check options
value := ctx.GetOption("my-option")
// Verify widget properties
widget, ok := ctx.GetWidget("my-widget")
if ok && widget.Style.BorderColor == "#ff0000" {
t.Log("Widget has red border")
}
// Check status entries
status, ok := ctx.GetStatus("myext:status")
\`\`\`
### Testing Multiple Extensions
Each harness is isolated:
\`\`\`go
harness1 := test.New(t)
harness1.LoadFile("ext1.go")
harness2 := test.New(t)
harness2.LoadFile("ext2.go")
// Events to one don't affect the other
\`\`\`
### Testing extensions that read environment variables
The harness seeds the interpreter with the process environment, mirroring the
production loader, so an extension's \`os.Getenv\` / \`os.LookupEnv\` / \`os.Environ\`
calls work in tests. Set test-specific variables with \`t.Setenv\` **before**
loading the extension, since the environment is snapshotted at load time:
\`\`\`go
func TestReadsEnv(t *testing.T) {
t.Setenv("MY_API_KEY", "test-value")
harness := test.New(t)
harness.LoadFile("my-ext.go") // snapshots the env, incl. MY_API_KEY
harness.Emit(extensions.SessionStartEvent{SessionID: "s1"})
// assert on behavior that depends on MY_API_KEY
}
\`\`\`
### Running Tests
Run all tests in your extension directory:
\`\`\`bash
cd examples/extensions
go test -v
\`\`\`
Run with race detector:
\`\`\`bash
go test -race -v
\`\`\`
Run a specific test:
\`\`\`bash
go test -v -run TestMyExtension
\`\`\`
## Best Practices
1. **Test one behavior per test** — Keep tests focused and readable
2. **Use inline source for edge cases** — \`LoadString()\` is great for testing specific scenarios
3. **Use \`LoadFile()\` for integration tests** — Tests the actual extension file
4. **Assert on context calls** — Verify your extension interacts with the context correctly
5. **Test both positive and negative cases** — Verify tools are blocked AND allowed appropriately
6. **Test all event handlers** — Make sure all registered handlers work correctly
7. **Use descriptive test names** — \`TestExtension_BlocksDangerousTools\` is clearer than \`Test1\`
## Limitations
The test harness has these intentional limitations:
- **No TUI rendering** — Widgets are recorded but not rendered visually
- **Prompts return configured values** — Pre-configure prompt results in tests
- **Subagents don't spawn real processes** — \`SpawnSubagent()\` returns nil/empty results
- **LLM completions are mocked** — \`Complete()\` returns empty responses
- **Some context methods are no-ops** — \`Exit()\`, \`SetActiveTools()\`, etc. don't have side effects
These limitations focus testing on extension logic rather than the full Kit runtime.
## Complete Example
See \`examples/extensions/tool-logger_test.go\` for a complete example with 14 tests covering:
- Handler registration
- Tool call and result handling
- Session lifecycle events
- Input commands (\`!time\`, \`!status\`)
- Unknown command handling
- Concurrent operations (race condition check)
- Real file logging verification
`};export{s as default};
+1
View File
@@ -0,0 +1 @@
const e={};export{e as default};
+685
View File
@@ -0,0 +1,685 @@
const s={frontmatter:{title:"Themes",description:"Customize Kit's appearance with built-in themes, custom theme files, and the extension theme API.",hidden:!1,toc:!0,draft:!1},html:`<h1 id="themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#themes"><span class="icon icon-link"></span></a>Themes</h1>
<p>Kit ships with 22 built-in color themes and supports custom themes via YAML/JSON files or the extension API. Themes control all UI colors: input borders, popups, system messages, markdown rendering, syntax highlighting, and diff displays.</p>
<h2 id="quick-start"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#quick-start"><span class="icon icon-link"></span></a>Quick start</h2>
<p>Switch themes at runtime with the <code>/theme</code> command:</p>
<pre><code>/theme dracula
/theme catppuccin
/theme kitt
</code></pre>
<p>Run <code>/theme</code> with no arguments to list all available themes.</p>
<p><strong>Theme selections are automatically saved</strong> to <code>~/.config/kit/preferences.yml</code> and restored on next launch. You don't need to add anything to your config file — just <code>/theme &lt;name&gt;</code> and it sticks. This persistence also applies to <strong>model</strong> and <strong>thinking level</strong> selections.</p>
<h2 id="built-in-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#built-in-themes"><span class="icon icon-link"></span></a>Built-in themes</h2>
<table>
<thead>
<tr>
<th>Theme</th>
<th>Style</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>kitt</code></td>
<td>KITT-inspired reds and ambers (default)</td>
</tr>
<tr>
<td><code>catppuccin</code></td>
<td>Soothing pastels (Mocha/Latte)</td>
</tr>
<tr>
<td><code>dracula</code></td>
<td>Purple and cyan dark theme</td>
</tr>
<tr>
<td><code>tokyonight</code></td>
<td>Cool blues with warm accents</td>
</tr>
<tr>
<td><code>nord</code></td>
<td>Arctic, north-bluish palette</td>
</tr>
<tr>
<td><code>gruvbox</code></td>
<td>Retro groove colors</td>
</tr>
<tr>
<td><code>monokai</code></td>
<td>Classic syntax theme</td>
</tr>
<tr>
<td><code>solarized</code></td>
<td>Precision colors for machines and people</td>
</tr>
<tr>
<td><code>github</code></td>
<td>GitHub's light and dark palettes</td>
</tr>
<tr>
<td><code>one-dark</code></td>
<td>Atom One Dark</td>
</tr>
<tr>
<td><code>rose-pine</code></td>
<td>Soho vibes with muted tones</td>
</tr>
<tr>
<td><code>ayu</code></td>
<td>Simple with bright colors</td>
</tr>
<tr>
<td><code>material</code></td>
<td>Material Design palette</td>
</tr>
<tr>
<td><code>everforest</code></td>
<td>Green-focused comfortable theme</td>
</tr>
<tr>
<td><code>kanagawa</code></td>
<td>Dark theme inspired by Katsushika Hokusai</td>
</tr>
<tr>
<td><code>amoled</code></td>
<td>Pure black background, vivid accents</td>
</tr>
<tr>
<td><code>synthwave</code></td>
<td>Retro neon glows</td>
</tr>
<tr>
<td><code>vesper</code></td>
<td>Warm minimalist dark theme</td>
</tr>
<tr>
<td><code>flexoki</code></td>
<td>Inky reading palette</td>
</tr>
<tr>
<td><code>matrix</code></td>
<td>Green-on-black terminal aesthetic</td>
</tr>
<tr>
<td><code>vercel</code></td>
<td>Clean monochrome with blue accents</td>
</tr>
<tr>
<td><code>zenburn</code></td>
<td>Low-contrast, warm dark theme</td>
</tr>
</tbody>
</table>
<p>All themes support both light and dark terminal modes via adaptive colors.</p>
<h2 id="custom-theme-files"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#custom-theme-files"><span class="icon icon-link"></span></a>Custom theme files</h2>
<p>Create a <code>.yml</code>, <code>.yaml</code>, or <code>.json</code> file with color definitions. Kit discovers themes from two directories:</p>
<table>
<thead>
<tr>
<th>Location</th>
<th>Scope</th>
<th>Precedence</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>~/.config/kit/themes/</code></td>
<td>User (global)</td>
<td>Overrides built-ins</td>
</tr>
<tr>
<td><code>.kit/themes/</code></td>
<td>Project-local</td>
<td>Overrides user and built-ins</td>
</tr>
</tbody>
</table>
<h3 id="theme-file-format"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#theme-file-format"><span class="icon icon-link"></span></a>Theme file format</h3>
<p>A theme file defines adaptive color pairs with <code>light</code> and <code>dark</code> hex values. Any field left empty inherits from the default KITT theme.</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># ~/.config/kit/themes/my-theme.yml</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Core semantic colors</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">primary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#8839ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cba6f7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">secondary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#04a5e5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89dceb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">success</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#40a02b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#a6e3a1"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">warning</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#df8e1d"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f9e2af"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#d20f39"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f38ba8"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">info</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e66f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89b4fa"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Text and chrome</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">text</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#4c4f69"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cdd6f4"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">muted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#6c6f85"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#a6adc8"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">very-muted</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#9ca0b0"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#6c7086"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">background</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#eff1f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e1e2e"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">border</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#acb0be"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#585b70"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">muted-border</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#ccd0da"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#313244"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Semantic roles</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">system</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#179299"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#94e2d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">tool</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fe640b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fab387"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">accent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#ea76cb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f5c2e7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">highlight</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#e6e9ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#181825"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Diff backgrounds</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-insert-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#d5f0d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a3a2a"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-delete-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#f5d5d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#3a1a2a"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-equal-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#eceef3"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#232336"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">diff-missing-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#e4e6eb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a1a2e"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Code block backgrounds</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">code-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#eceef3"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#232336"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">gutter-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#e4e6eb"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a1a2e"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">write-bg</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#d5f0d5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1a3a2a"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Markdown and syntax highlighting</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">markdown</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> heading</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e66f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89b4fa"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> link</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#1e66f5"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#89b4fa"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> keyword</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#8839ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cba6f7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> string</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#40a02b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#a6e3a1"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> number</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fe640b"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#fab387"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> comment</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#9ca0b0"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#6c7086"</span></span></code></pre>
<h3 id="partial-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#partial-themes"><span class="icon icon-link"></span></a>Partial themes</h3>
<p>You only need to define the colors you want to change. Unspecified fields fall back to the default theme:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#6A737D;--shiki-dark:#6A737D"># Just override the primary and accent colors</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">primary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF00FF"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">accent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FFFF"</span></span></code></pre>
<h3 id="distributing-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#distributing-themes"><span class="icon icon-link"></span></a>Distributing themes</h3>
<ul>
<li><strong>Personal</strong>: Drop a file in <code>~/.config/kit/themes/</code></li>
<li><strong>Team/project</strong>: Drop a file in <code>.kit/themes/</code> and commit it to version control</li>
<li><strong>Override built-in</strong>: Name your file the same as a built-in (e.g., <code>dracula.yml</code>) and it takes precedence</li>
</ul>
<h2 id="config-file-theme"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#config-file-theme"><span class="icon icon-link"></span></a>Config file theme</h2>
<p>You can also set theme colors directly in <code>.kit.yml</code>:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">theme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> primary</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> light</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#8839ef"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#cba6f7"</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> error</span><span style="color:#24292E;--shiki-dark:#E1E4E8">:</span></span>
<span class="line"><span style="color:#22863A;--shiki-dark:#85E89D"> dark</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF0000"</span></span></code></pre>
<p>Or reference an external theme file:</p>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#22863A;--shiki-dark:#85E89D">theme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"./themes/my-custom-theme.yml"</span></span></code></pre>
<h2 id="extension-theme-api"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#extension-theme-api"><span class="icon icon-link"></span></a>Extension theme API</h2>
<p>Extensions can register and switch themes programmatically at runtime.</p>
<h3 id="registering-a-theme"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#registering-a-theme"><span class="icon icon-link"></span></a>Registering a theme</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">api.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">OnSessionStart</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#D73A49;--shiki-dark:#F97583">func</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#E36209;--shiki-dark:#FFAB70">_</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SessionStartEvent</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#E36209;--shiki-dark:#FFAB70">ctx</span><span style="color:#6F42C1;--shiki-dark:#B392F0"> ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">Context</span><span style="color:#24292E;--shiki-dark:#E1E4E8">) {</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">RegisterTheme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"neon"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColorConfig</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Primary: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CC00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Secondary: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#0088CC"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FFFF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Success: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00CC44"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FF66"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Warning: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CCAA00"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FFFF00"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Error: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CC0033"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF0055"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Info: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#0088CC"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00CCFF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Text: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#111111"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#F0F0F0"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> Background: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#F0F0F0"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#0A0A14"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MdKeyword: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#CC00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#FF00FF"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MdString: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00CC44"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#00FF66"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> MdComment: </span><span style="color:#6F42C1;--shiki-dark:#B392F0">ext</span><span style="color:#24292E;--shiki-dark:#E1E4E8">.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ThemeColor</span><span style="color:#24292E;--shiki-dark:#E1E4E8">{Light: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#888888"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">, Dark: </span><span style="color:#032F62;--shiki-dark:#9ECBFF">"#555555"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">},</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8"> })</span></span>
<span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">})</span></span></code></pre>
<h3 id="switching-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#switching-themes"><span class="icon icon-link"></span></a>Switching themes</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">err </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">SetTheme</span><span style="color:#24292E;--shiki-dark:#E1E4E8">(</span><span style="color:#032F62;--shiki-dark:#9ECBFF">"dracula"</span><span style="color:#24292E;--shiki-dark:#E1E4E8">)</span></span></code></pre>
<h3 id="listing-available-themes"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#listing-available-themes"><span class="icon icon-link"></span></a>Listing available themes</h3>
<pre class="shiki shiki-themes github-light github-dark" style="background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#24292E;--shiki-dark:#E1E4E8">names </span><span style="color:#D73A49;--shiki-dark:#F97583">:=</span><span style="color:#24292E;--shiki-dark:#E1E4E8"> ctx.</span><span style="color:#6F42C1;--shiki-dark:#B392F0">ListThemes</span><span style="color:#24292E;--shiki-dark:#E1E4E8">()</span></span></code></pre>
<h3 id="themecolorconfig-fields"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#themecolorconfig-fields"><span class="icon icon-link"></span></a>ThemeColorConfig fields</h3>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Primary</code></td>
<td>Main brand/accent color</td>
</tr>
<tr>
<td><code>Secondary</code></td>
<td>Secondary accent</td>
</tr>
<tr>
<td><code>Success</code></td>
<td>Success states</td>
</tr>
<tr>
<td><code>Warning</code></td>
<td>Warning states</td>
</tr>
<tr>
<td><code>Error</code></td>
<td>Error/critical states</td>
</tr>
<tr>
<td><code>Info</code></td>
<td>Informational states</td>
</tr>
<tr>
<td><code>Text</code></td>
<td>Primary text</td>
</tr>
<tr>
<td><code>Muted</code></td>
<td>Dimmed text</td>
</tr>
<tr>
<td><code>VeryMuted</code></td>
<td>Very dimmed text</td>
</tr>
<tr>
<td><code>Background</code></td>
<td>Base background</td>
</tr>
<tr>
<td><code>Border</code></td>
<td>Panel borders</td>
</tr>
<tr>
<td><code>MutedBorder</code></td>
<td>Subtle dividers</td>
</tr>
<tr>
<td><code>System</code></td>
<td>System messages</td>
</tr>
<tr>
<td><code>Tool</code></td>
<td>Tool-related elements</td>
</tr>
<tr>
<td><code>Accent</code></td>
<td>Secondary highlight</td>
</tr>
<tr>
<td><code>Highlight</code></td>
<td>Highlighted regions</td>
</tr>
<tr>
<td><code>MdHeading</code></td>
<td>Markdown headings</td>
</tr>
<tr>
<td><code>MdLink</code></td>
<td>Markdown links</td>
</tr>
<tr>
<td><code>MdKeyword</code></td>
<td>Syntax: keywords</td>
</tr>
<tr>
<td><code>MdString</code></td>
<td>Syntax: strings</td>
</tr>
<tr>
<td><code>MdNumber</code></td>
<td>Syntax: numbers</td>
</tr>
<tr>
<td><code>MdComment</code></td>
<td>Syntax: comments</td>
</tr>
</tbody>
</table>
<p>Each field is an <code>ext.ThemeColor</code> with <code>Light</code> and <code>Dark</code> hex strings. Empty fields inherit from the default theme.</p>
<h2 id="precedence-order"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#precedence-order"><span class="icon icon-link"></span></a>Precedence order</h2>
<p>When multiple sources define the same theme name, later sources win:</p>
<ol>
<li>Built-in presets (lowest)</li>
<li>User themes (<code>~/.config/kit/themes/</code>)</li>
<li>Project-local themes (<code>.kit/themes/</code>)</li>
<li>Extension-registered themes (highest)</li>
</ol>
<h3 id="startup-theme-resolution"><a class="heading-anchor" aria-hidden="" tabindex="-1" href="#startup-theme-resolution"><span class="icon icon-link"></span></a>Startup theme resolution</h3>
<p>At startup, Kit determines which theme to apply:</p>
<ol>
<li><strong><code>.kit.yml</code> <code>theme:</code> key</strong> — explicit config always wins (highest priority)</li>
<li><strong><code>~/.config/kit/preferences.yml</code></strong> — persisted <code>/theme</code> selection</li>
<li><strong>Default <code>kitt</code> theme</strong> — fallback</li>
</ol>
<p>The preferences file is updated automatically whenever you use <code>/theme</code> or <code>ctx.SetTheme()</code>. It is separate from <code>.kit.yml</code> so it never clobbers your config comments or formatting.</p>
<p>Theme changes via <code>/theme</code> or <code>ctx.SetTheme()</code> take effect immediately on all UI elements, including previously rendered messages.</p>`,headings:[{depth:2,text:"Quick start",id:"quick-start"},{depth:2,text:"Built-in themes",id:"built-in-themes"},{depth:2,text:"Custom theme files",id:"custom-theme-files"},{depth:3,text:"Theme file format",id:"theme-file-format"},{depth:3,text:"Partial themes",id:"partial-themes"},{depth:3,text:"Distributing themes",id:"distributing-themes"},{depth:2,text:"Config file theme",id:"config-file-theme"},{depth:2,text:"Extension theme API",id:"extension-theme-api"},{depth:3,text:"Registering a theme",id:"registering-a-theme"},{depth:3,text:"Switching themes",id:"switching-themes"},{depth:3,text:"Listing available themes",id:"listing-available-themes"},{depth:3,text:"ThemeColorConfig fields",id:"themecolorconfig-fields"},{depth:2,text:"Precedence order",id:"precedence-order"},{depth:3,text:"Startup theme resolution",id:"startup-theme-resolution"}],raw:`
# Themes
Kit ships with 22 built-in color themes and supports custom themes via YAML/JSON files or the extension API. Themes control all UI colors: input borders, popups, system messages, markdown rendering, syntax highlighting, and diff displays.
## Quick start
Switch themes at runtime with the \`/theme\` command:
\`\`\`
/theme dracula
/theme catppuccin
/theme kitt
\`\`\`
Run \`/theme\` with no arguments to list all available themes.
**Theme selections are automatically saved** to \`~/.config/kit/preferences.yml\` and restored on next launch. You don't need to add anything to your config file — just \`/theme <name>\` and it sticks. This persistence also applies to **model** and **thinking level** selections.
## Built-in themes
| Theme | Style |
|-------|-------|
| \`kitt\` | KITT-inspired reds and ambers (default) |
| \`catppuccin\` | Soothing pastels (Mocha/Latte) |
| \`dracula\` | Purple and cyan dark theme |
| \`tokyonight\` | Cool blues with warm accents |
| \`nord\` | Arctic, north-bluish palette |
| \`gruvbox\` | Retro groove colors |
| \`monokai\` | Classic syntax theme |
| \`solarized\` | Precision colors for machines and people |
| \`github\` | GitHub's light and dark palettes |
| \`one-dark\` | Atom One Dark |
| \`rose-pine\` | Soho vibes with muted tones |
| \`ayu\` | Simple with bright colors |
| \`material\` | Material Design palette |
| \`everforest\` | Green-focused comfortable theme |
| \`kanagawa\` | Dark theme inspired by Katsushika Hokusai |
| \`amoled\` | Pure black background, vivid accents |
| \`synthwave\` | Retro neon glows |
| \`vesper\` | Warm minimalist dark theme |
| \`flexoki\` | Inky reading palette |
| \`matrix\` | Green-on-black terminal aesthetic |
| \`vercel\` | Clean monochrome with blue accents |
| \`zenburn\` | Low-contrast, warm dark theme |
All themes support both light and dark terminal modes via adaptive colors.
## Custom theme files
Create a \`.yml\`, \`.yaml\`, or \`.json\` file with color definitions. Kit discovers themes from two directories:
| Location | Scope | Precedence |
|----------|-------|------------|
| \`~/.config/kit/themes/\` | User (global) | Overrides built-ins |
| \`.kit/themes/\` | Project-local | Overrides user and built-ins |
### Theme file format
A theme file defines adaptive color pairs with \`light\` and \`dark\` hex values. Any field left empty inherits from the default KITT theme.
\`\`\`yaml
# ~/.config/kit/themes/my-theme.yml
# Core semantic colors
primary:
light: "#8839ef"
dark: "#cba6f7"
secondary:
light: "#04a5e5"
dark: "#89dceb"
success:
light: "#40a02b"
dark: "#a6e3a1"
warning:
light: "#df8e1d"
dark: "#f9e2af"
error:
light: "#d20f39"
dark: "#f38ba8"
info:
light: "#1e66f5"
dark: "#89b4fa"
# Text and chrome
text:
light: "#4c4f69"
dark: "#cdd6f4"
muted:
light: "#6c6f85"
dark: "#a6adc8"
very-muted:
light: "#9ca0b0"
dark: "#6c7086"
background:
light: "#eff1f5"
dark: "#1e1e2e"
border:
light: "#acb0be"
dark: "#585b70"
muted-border:
light: "#ccd0da"
dark: "#313244"
# Semantic roles
system:
light: "#179299"
dark: "#94e2d5"
tool:
light: "#fe640b"
dark: "#fab387"
accent:
light: "#ea76cb"
dark: "#f5c2e7"
highlight:
light: "#e6e9ef"
dark: "#181825"
# Diff backgrounds
diff-insert-bg:
light: "#d5f0d5"
dark: "#1a3a2a"
diff-delete-bg:
light: "#f5d5d5"
dark: "#3a1a2a"
diff-equal-bg:
light: "#eceef3"
dark: "#232336"
diff-missing-bg:
light: "#e4e6eb"
dark: "#1a1a2e"
# Code block backgrounds
code-bg:
light: "#eceef3"
dark: "#232336"
gutter-bg:
light: "#e4e6eb"
dark: "#1a1a2e"
write-bg:
light: "#d5f0d5"
dark: "#1a3a2a"
# Markdown and syntax highlighting
markdown:
heading:
light: "#1e66f5"
dark: "#89b4fa"
link:
light: "#1e66f5"
dark: "#89b4fa"
keyword:
light: "#8839ef"
dark: "#cba6f7"
string:
light: "#40a02b"
dark: "#a6e3a1"
number:
light: "#fe640b"
dark: "#fab387"
comment:
light: "#9ca0b0"
dark: "#6c7086"
\`\`\`
### Partial themes
You only need to define the colors you want to change. Unspecified fields fall back to the default theme:
\`\`\`yaml
# Just override the primary and accent colors
primary:
dark: "#FF00FF"
accent:
dark: "#00FFFF"
\`\`\`
### Distributing themes
- **Personal**: Drop a file in \`~/.config/kit/themes/\`
- **Team/project**: Drop a file in \`.kit/themes/\` and commit it to version control
- **Override built-in**: Name your file the same as a built-in (e.g., \`dracula.yml\`) and it takes precedence
## Config file theme
You can also set theme colors directly in \`.kit.yml\`:
\`\`\`yaml
theme:
primary:
light: "#8839ef"
dark: "#cba6f7"
error:
dark: "#FF0000"
\`\`\`
Or reference an external theme file:
\`\`\`yaml
theme: "./themes/my-custom-theme.yml"
\`\`\`
## Extension theme API
Extensions can register and switch themes programmatically at runtime.
### Registering a theme
\`\`\`go
api.OnSessionStart(func(_ ext.SessionStartEvent, ctx ext.Context) {
ctx.RegisterTheme("neon", ext.ThemeColorConfig{
Primary: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
Secondary: ext.ThemeColor{Light: "#0088CC", Dark: "#00FFFF"},
Success: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
Warning: ext.ThemeColor{Light: "#CCAA00", Dark: "#FFFF00"},
Error: ext.ThemeColor{Light: "#CC0033", Dark: "#FF0055"},
Info: ext.ThemeColor{Light: "#0088CC", Dark: "#00CCFF"},
Text: ext.ThemeColor{Light: "#111111", Dark: "#F0F0F0"},
Background: ext.ThemeColor{Light: "#F0F0F0", Dark: "#0A0A14"},
MdKeyword: ext.ThemeColor{Light: "#CC00FF", Dark: "#FF00FF"},
MdString: ext.ThemeColor{Light: "#00CC44", Dark: "#00FF66"},
MdComment: ext.ThemeColor{Light: "#888888", Dark: "#555555"},
})
})
\`\`\`
### Switching themes
\`\`\`go
err := ctx.SetTheme("dracula")
\`\`\`
### Listing available themes
\`\`\`go
names := ctx.ListThemes()
\`\`\`
### ThemeColorConfig fields
| Field | Description |
|-------|-------------|
| \`Primary\` | Main brand/accent color |
| \`Secondary\` | Secondary accent |
| \`Success\` | Success states |
| \`Warning\` | Warning states |
| \`Error\` | Error/critical states |
| \`Info\` | Informational states |
| \`Text\` | Primary text |
| \`Muted\` | Dimmed text |
| \`VeryMuted\` | Very dimmed text |
| \`Background\` | Base background |
| \`Border\` | Panel borders |
| \`MutedBorder\` | Subtle dividers |
| \`System\` | System messages |
| \`Tool\` | Tool-related elements |
| \`Accent\` | Secondary highlight |
| \`Highlight\` | Highlighted regions |
| \`MdHeading\` | Markdown headings |
| \`MdLink\` | Markdown links |
| \`MdKeyword\` | Syntax: keywords |
| \`MdString\` | Syntax: strings |
| \`MdNumber\` | Syntax: numbers |
| \`MdComment\` | Syntax: comments |
Each field is an \`ext.ThemeColor\` with \`Light\` and \`Dark\` hex strings. Empty fields inherit from the default theme.
## Precedence order
When multiple sources define the same theme name, later sources win:
1. Built-in presets (lowest)
2. User themes (\`~/.config/kit/themes/\`)
3. Project-local themes (\`.kit/themes/\`)
4. Extension-registered themes (highest)
### Startup theme resolution
At startup, Kit determines which theme to apply:
1. **\`.kit.yml\` \`theme:\` key** — explicit config always wins (highest priority)
2. **\`~/.config/kit/preferences.yml\`** — persisted \`/theme\` selection
3. **Default \`kitt\` theme** — fallback
The preferences file is updated automatically whenever you use \`/theme\` or \`ctx.SetTheme()\`. It is separate from \`.kit.yml\` so it never clobbers your config comments or formatting.
Theme changes via \`/theme\` or \`ctx.SetTheme()\` take effect immediately on all UI elements, including previously rendered messages.
`};export{s as default};
-95
View File
@@ -1,95 +0,0 @@
{
"$schema": "https://btca.dev/btca.schema.json",
"resources": [
{
"type": "git",
"name": "bubbletea",
"url": "https://github.com/charmbracelet/bubbletea",
"branch": "main"
},
{
"type": "git",
"name": "lipgloss",
"url": "https://github.com/charmbracelet/lipgloss",
"branch": "main"
},
{
"type": "git",
"name": "bubbles",
"url": "https://github.com/charmbracelet/bubbles",
"branch": "main"
},
{
"type": "git",
"name": "glamour",
"url": "https://github.com/charmbracelet/glamour",
"branch": "v2-exp"
},
{
"type": "git",
"name": "fantasy",
"url": "https://github.com/charmbracelet/fantasy",
"branch": "main"
},
{
"type": "git",
"name": "catwalk",
"url": "https://github.com/charmbracelet/catwalk",
"branch": "main"
},
{
"type": "git",
"name": "crush",
"url": "https://github.com/charmbracelet/crush",
"branch": "main"
},
{
"type": "git",
"name": "pi",
"url": "https://github.com/badlogic/pi-mono",
"branch": "main",
"searchPaths": [
"packages/coding-agent",
"packages/tui"
]
},
{
"type": "git",
"name": "iteratr",
"url": "https://github.com/mark3labs/iteratr",
"branch": "master"
},
{
"type": "git",
"name": "yaegi",
"url": "https://github.com/traefik/yaegi",
"branch": "master"
},
{
"type": "git",
"name": "acp-go-sdk",
"url": "https://github.com/coder/acp-go-sdk",
"branch": "main"
},
{
"type": "git",
"name": "opencode",
"url": "https://github.com/anomalyco/opencode",
"branch": "dev"
},
{
"type": "git",
"name": "herald",
"url": "https://github.com/indaco/herald",
"branch": "main"
},
{
"type": "git",
"name": "herald-md",
"url": "https://github.com/indaco/herald-md",
"branch": "main"
}
],
"model": "claude-haiku-4-5",
"provider": "opencode"
}
+293
View File
@@ -0,0 +1,293 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Commands | Kit</title>
<meta name="description" content="Complete reference for all Kit CLI subcommands.">
<link rel="canonical" href="/cli/commands">
<link rel="stylesheet" href="/assets/index-Di_r5hA0.css">
<script type="module" src="/assets/index-B6tF_bpk.js"></script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Commands","description":"Complete reference for all Kit CLI subcommands.","url":"https://go-kit.dev/cli/commands","isPartOf":{"@type":"WebSite","name":"Kit","url":"https://go-kit.dev"}}</script>
</head>
<body>
<div id="tome-root"></div>
<div data-pagefind-body style="display:none"><h1>Commands</h1>
# Commands
## Authentication
For OAuth-enabled providers like Anthropic.
```bash
kit auth login [provider] # Start OAuth flow (e.g., anthropic)
kit auth login [provider] --set-default # Set provider's default model as system default
kit auth logout [provider] # Remove credentials for provider
kit auth status # Check authentication status
```
## Model database
Manage the local model database that maps provider names to API configurations.
```bash
kit models [provider] # List available models (optionally filter by provider)
kit models --all # Show all providers (not just LLM-compatible)
kit update-models [source] # Update model database
```
The `update-models` command accepts an optional source argument:
- *(none)* — update from [models.dev](https://models.dev)
- A URL — fetch from a custom endpoint
- A file path — load from a local file
- `embedded` — reset to the bundled database
## Extension management
```bash
kit extensions list # List discovered extensions
kit extensions validate # Validate extension files
kit extensions init # Generate example extension template
```
### Installing extensions from git
```bash
kit install <git-url> # Install extensions from git repositories
kit install -l <git-url> # Install to project-local .kit/git/ directory
kit install -u <git-url> # Update an already-installed package
kit install --uninstall <pkg> # Remove an installed package
kit install --all # Install all extensions without prompting
```
## Skills
```bash
kit skill # Install the Kit extensions skill via skills.sh
```
### Skills CLI flags
Control which skills are loaded at startup:
```bash
# Load a specific skill file
kit --skill path/to/skill.md "prompt"
# Load multiple skill files or directories (flag is repeatable)
kit --skill ./skill1.md --skill ./skill2.md "prompt"
# Scan a directory directly for skills (overrides auto-discovery)
kit --skills-dir /path/to/skills "prompt"
# Hide a skill from the model catalog by name (still usable via /skill:)
kit --skill-disable noisy-skill "prompt"
# Disable all skill loading (auto-discovery and explicit)
kit --no-skills "prompt"
```
Skills follow the [agentskills.io](https://agentskills.io/specification) convention. They are auto-discovered from four canonical scopes:
| Scope | Location |
|-------|----------|
| User-level (cross-client) | `~/.agents/skills/` |
| User-level (Kit) | `~/.config/kit/skills/` (honors `$XDG_CONFIG_HOME`) |
| Project-local (cross-client) | `<project>/.agents/skills/` |
| Project-local (Kit) | `<project>/.kit/skills/` |
When two skills share the same `name`, the project-level one takes precedence over the user-level one. Use `--skills-dir` to scan one directory directly instead (it is **not** treated as a parent of `.agents`/`.kit` — the directory itself is scanned). `--skill` loads files explicitly (which disables auto-discovery), and `--no-skills` suppresses all skill loading regardless of other flags.
Disabled skills (`--skill-disable`, the `skill-disable` config key, or `disable-model-invocation: true` in a skill's frontmatter) are hidden from the model-facing `<available_skills>` catalog but remain available for explicit activation via the `/skill:<name>` command.
### Skill frontmatter
A skill is a markdown file (`SKILL.md` in a directory, or a standalone `.md`/`.txt` file) with optional YAML frontmatter. Kit reads the full [agentskills.io](https://agentskills.io/specification) field set plus two Kit-specific extensions:
```yaml
---
name: pdf-extractor # required
description: Use when extracting tables from PDFs # required (drives model discovery)
license: MIT # optional, SPDX identifier
compatibility: claude-code, cursor # optional, targeted environments
allowed-tools: read, bash # optional (experimental) tool restriction
disable-model-invocation: false # optional; true hides from the catalog
metadata: # optional arbitrary key/value pairs
author: you
tags: [pdf, data] # Kit extension
when: on-demand # Kit extension
---
```
`name` and `description` are required — a skill missing its description is skipped with a logged warning, since the description is the sole basis on which the model decides relevance. Descriptions are XML-escaped before they enter the catalog, so characters like `<`, `>`, and `&` are safe. A skill directory may bundle `scripts/`, `references/`, and `assets/` subdirectories; when a skill is activated those files are enumerated in a `<skill_resources>` block so the model knows what it can read.
### Project trust prompt
Because project-local skills are injected into the system prompt, entering a repository that ships `.agents/skills/` or `.kit/skills/` for the first time prompts you to trust it before any project skill loads — a safeguard against a freshly cloned, untrusted repo smuggling instructions into the agent:
```
This project provides 2 skills under .agents/skills or .kit/skills:
/path/to/repo
Load them into the agent? [t]rust always / [o]nce / [s]kip (default skip):
```
Choosing **trust always** persists the directory to `~/.config/kit/trusted-projects.json` so you are not asked again. The prompt is skipped (skills load silently) in non-interactive runs — when a prompt is passed positionally, `--quiet` is set, or stdin is not a TTY.
## GitHub integration
Scaffold a GitHub Actions workflow that runs Kit as an automated collaborator/reviewer. The workflow triggers when someone comments `/kit ...` on an issue or pull request review, runs the agent non-interactively in the runner, and lets it respond.
```bash
kit github install # Scaffold .github/workflows/kit.yml
kit github install --model anthropic/claude-sonnet-4-5-20250929 # Skip the model prompt
kit github install --force # Overwrite an existing workflow file
kit github install --no-secret # Skip the offer to set the provider secret via the gh CLI
```
By default the command prompts for the model (pre-filled with a sensible default). If the [`gh` CLI](https://cli.github.com/) is detected on your `PATH` and the provider API key is present in your environment, you'll be offered the option to store it as a repository secret automatically.
The generated workflow:
- Triggers only on `issue_comment` and `pull_request_review_comment` (`types: [created]`).
- Runs only when the comment begins with the `/kit` command token.
- Restricts triggers to repository owners, members, and collaborators (via `author_association`).
- Uses least-privilege `permissions` and `persist-credentials: false`.
- Authenticates git/PR operations with the built-in `secrets.GITHUB_TOKEN` and the provider via a repository secret (e.g. `ANTHROPIC_API_KEY`).
After committing the workflow and setting the provider secret, comment `/kit <your request>` on any issue or pull request to trigger Kit.
The generated workflow uses the bundled [`mark3labs/kit`](https://github.com/mark3labs/kit/blob/master/action.yml) composite action, which installs the Kit binary and runs `kit github run`. That command reads the triggering event, enforces permissions, reacts with an emoji, runs the agent against the issue thread or PR, posts the response as a comment, and — if the agent changed files — pushes a `kit-agent[bot]` branch and opens a pull request.
| Flag | Description |
|------|-------------|
| `--model` | Provider/model to write into the workflow |
| `--force` | Overwrite an existing workflow file |
| `--no-secret` | Skip the offer to set the provider secret via the `gh` CLI |
## Interactive slash commands
These commands are available inside the Kit TUI during an interactive session:
| Command | Description |
|---------|-------------|
| `/help` | Show available commands |
| `/tools` | List available MCP tools |
| `/servers` | Show connected MCP servers |
| `/model [name]` | Switch model or open model selector |
| `/theme [name]` | Switch color theme or list available themes |
| `/thinking [level]` | Set thinking level (off, none, minimal, low, medium, high) |
| `/compact [focus]` | Summarize older messages to free context |
| `/clear` | Clear conversation |
| `/clear-queue` | Clear queued messages |
| `/usage` | Show token usage |
| `/reset-usage` | Reset usage statistics |
| `/tree` | Navigate session tree |
| `/fork` | Fork to new session from an earlier message |
| `/new` | Start a new session (creates new session file) |
| `/name [name]` | Set or show session display name |
| `/resume` | Open session picker to switch sessions (alias: `/r`) |
| `/session` | Show session info |
| `/export [path]` | Export session as JSONL (default: auto-generated path) |
| `/import <path>` | Import a session from a JSONL file |
| `/share` | Upload session to GitHub Gist and get a shareable viewer URL |
| `/quit` | Exit Kit |
### Prompt history
Use **↑** and **↓** arrow keys to navigate through previously submitted prompts. Kit keeps the last 100 entries. Consecutive duplicates are skipped.
### Cancelling operations
Press **ESC twice** to cancel the current operation:
- During a tool call: rolls back the entire turn to maintain API message pairing
- During streaming: stops the response generation
This ensures that `tool_use` and `tool_result` messages are always sent to the API as matched pairs, avoiding errors from orphaned tool calls.
### External editor
Press **Ctrl+X e** to open your `$VISUAL` or `$EDITOR` in a temporary file pre-populated with the current input text. On save and quit, the edited content replaces the input textarea. On error exit (e.g., `:cq` in Vim), the original input is preserved.
### Mid-turn steering
Press **Ctrl+X s** during streaming to inject a system-level instruction mid-turn. This allows you to steer the conversation direction without waiting for the model to finish:
- Works during streaming output
- Sends a steering instruction as a system message
- Model continues from the interruption point with the new guidance
Example: While the model is writing code, press Ctrl+X s and type "Use async/await instead" to change the implementation approach.
### Image attachments
Attach images to your next prompt straight from the clipboard:
- Copy an image (e.g. a screenshot) to the system clipboard, then press **Ctrl+V** in the input to attach it.
- Press **Ctrl+U** to clear all pending image attachments.
- Attachments are sent alongside your text when you submit, and cleared afterward.
When a terminal supports color, Kit renders a small low-resolution **thumbnail preview** of each pending image directly in the input, below the `[N image(s) attached]` indicator, so you can confirm the right image was attached before sending.
The preview is drawn with Unicode half-block characters and ordinary terminal colors — not a graphics protocol — so it renders correctly inside terminal multiplexers like **tmux** and **zellij**. Thumbnails are capped to a small cell box for a glanceable, low-res look.
- Best fidelity needs a **truecolor** terminal (`COLORTERM=truecolor`); Kit degrades to 256-color where truecolor is unavailable.
- On terminals with neither, the preview is skipped and the `[N image(s) attached]` text indicator is shown alone.
You can also attach image files by referencing them with `@path/to/image.png` — binary files are auto-detected by MIME type. See [Quick Start](/quick-start) for the `@` attachment syntax.
## Prompt templates
### Creating templates
Templates use YAML frontmatter for metadata and support argument placeholders:
```markdown
---
description: Review code for issues
---
Review the following code for bugs and security issues.
Focus on $1 specifically.
```
Save to `~/.kit/prompts/review.md` or `.kit/prompts/review.md`.
### Using templates
Templates appear as slash commands:
```
/review error handling
```
### Argument placeholders
| Placeholder | Description |
|-------------|-------------|
| `$1`, `$2`, etc. | Individual arguments by position |
| `$@`, `$ARGUMENTS` | All arguments joined with spaces (zero or more) |
| `$+` | All arguments joined with spaces (one or more required) |
| `${@:N}` | Arguments from position N onwards |
| `${@:N:L}` | L arguments starting at position N |
Placeholders inside fenced code blocks (`` ``` ``) and inline code spans are ignored, so documentation examples won't be substituted.
### CLI flags
```bash
# Load a specific template by name
kit --prompt-template review
# Disable template loading
kit --no-prompt-templates
```
## ACP server
Run Kit as an [ACP (Agent Client Protocol)](https://agentclientprotocol.com) agent server. ACP-compatible clients communicate with Kit over JSON-RPC 2.0 on stdin/stdout.
```bash
kit acp # Start as ACP agent
kit acp --debug # With debug logging to stderr
```</div>
</body>
</html>
@@ -1,8 +1,18 @@
---
title: Global Flags
description: Complete reference for all Kit CLI flags.
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Global Flags | Kit</title>
<meta name="description" content="Complete reference for all Kit CLI flags.">
<link rel="canonical" href="/cli/flags">
<link rel="stylesheet" href="/assets/index-Di_r5hA0.css">
<script type="module" src="/assets/index-B6tF_bpk.js"></script>
<script type="application/ld+json">{"@context":"https://schema.org","@type":"TechArticle","headline":"Global Flags","description":"Complete reference for all Kit CLI flags.","url":"https://go-kit.dev/cli/flags","isPartOf":{"@type":"WebSite","name":"Kit","url":"https://go-kit.dev"}}</script>
</head>
<body>
<div id="tome-root"></div>
<div data-pagefind-body style="display:none"><h1>Global Flags</h1>
# Global Flags
All flags can be passed to the root `kit` command.
@@ -48,16 +58,27 @@ These flags control Kit's behavior. When a prompt is passed as a positional argu
| `--prompt-template` | — | — | Load a specific prompt template by name |
| `--no-prompt-templates` | — | `false` | Disable prompt template loading |
## Skills
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--skill` | — | — | Load skill file or directory (repeatable) |
| `--skills-dir` | — | — | Scan this directory directly for skills (overrides auto-discovery) |
| `--skill-disable` | — | — | Hide a skill from the model catalog by name (repeatable); still usable via `/skill:` |
| `--no-skills` | — | `false` | Disable skill loading (auto-discovery and explicit) |
## Generation parameters
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--max-tokens` | — | `4096` | Maximum tokens in response |
| `--max-tokens` | — | `8192` | Base cap for output tokens. Auto-raised per-model up to 32768 when the model's catalog ceiling is higher and no explicit value is set. |
| `--temperature` | — | `0.7` | Randomness 0.01.0 |
| `--top-p` | — | `0.95` | Nucleus sampling 0.01.0 |
| `--top-k` | — | `40` | Limit top K tokens |
| `--stop-sequences` | — | — | Custom stop sequences (comma-separated) |
| `--thinking-level` | — | `off` | Extended thinking level: off, minimal, low, medium, high |
| `--frequency-penalty` | — | `0.0` | Penalize frequent tokens (0.02.0) |
| `--presence-penalty` | — | `0.0` | Penalize present tokens (0.02.0) |
| `--thinking-level` | — | `off` | Extended thinking level: off, none, minimal, low, medium, high |
## System
@@ -65,4 +86,6 @@ These flags control Kit's behavior. When a prompt is passed as a positional argu
|------|-------|---------|-------------|
| `--config` | — | `~/.kit.yml` | Config file path |
| `--system-prompt` | — | — | System prompt text or file path |
| `--debug` | — | `false` | Enable debug logging |
| `--debug` | — | `false` | Enable debug logging |</div>
</body>
</html>
-162
View File
@@ -1,162 +0,0 @@
package cmd
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"os/signal"
"syscall"
"github.com/charmbracelet/log"
acp "github.com/coder/acp-go-sdk"
"github.com/mark3labs/kit/internal/acpserver"
"github.com/spf13/cobra"
)
var acpCmd = &cobra.Command{
Use: "acp",
Short: "Start Kit as an ACP agent server",
Long: `Start Kit as an ACP (Agent Client Protocol) agent server.
Communicates over stdio (stdin/stdout) using JSON-RPC 2.0 with
newline-delimited JSON, compatible with OpenCode and other ACP clients.
The server exposes Kit's LLM execution, tool system, and session
management via the Agent Client Protocol. Sessions are persisted
to Kit's standard JSONL session files.`,
RunE: runACP,
}
func init() {
rootCmd.AddCommand(acpCmd)
}
func runACP(cmd *cobra.Command, _ []string) error {
// Create the ACP agent implementation.
agent := acpserver.NewAgent()
defer agent.Close()
// Create the stdio connection. The SDK reads JSON-RPC from stdin and
// writes responses to stdout. We wrap stdin with a normalizer that
// fills in optional fields the SDK's generated validation requires
// (e.g. mcpServers) so clients that omit them still work.
conn := acp.NewAgentSideConnection(agent, os.Stdout, newACPNormalizer(os.Stdin))
// Wire the connection back to the agent so it can send session updates.
agent.SetAgentConnection(conn)
// Enable debug logging to stderr if requested.
if debugMode {
conn.SetLogger(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
})))
// Also set charmbracelet/log level for acpserver package logging
log.SetLevel(log.DebugLevel)
}
// Wait for either the client to disconnect or a signal.
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
select {
case <-conn.Done():
fmt.Fprintln(os.Stderr, "kit: ACP client disconnected")
case sig := <-sigCh:
fmt.Fprintf(os.Stderr, "kit: received %s, shutting down\n", sig)
}
return nil
}
// acpNormalizer wraps an io.Reader carrying newline-delimited JSON-RPC and
// patches incoming messages so that fields the SDK validates as required —
// but that some clients (e.g. Zed) omit — are defaulted. This avoids
// InvalidParams errors without forking the SDK.
type acpNormalizer struct {
scanner *bufio.Scanner
buf bytes.Buffer // leftover bytes from the last normalized line
}
func newACPNormalizer(r io.Reader) *acpNormalizer {
const maxMsg = 10 * 1024 * 1024 // 10 MB, matches SDK buffer
s := bufio.NewScanner(r)
s.Buffer(make([]byte, 0, 1024*1024), maxMsg)
return &acpNormalizer{scanner: s}
}
// Read satisfies io.Reader. It feeds one normalized JSON line (plus newline)
// per underlying scan, buffering across short caller reads.
func (n *acpNormalizer) Read(p []byte) (int, error) {
// Drain any leftover bytes from the previous line first.
if n.buf.Len() > 0 {
return n.buf.Read(p)
}
if !n.scanner.Scan() {
if err := n.scanner.Err(); err != nil {
return 0, err
}
return 0, io.EOF
}
line := n.scanner.Bytes()
normalized := normalizeACPLine(line)
n.buf.Write(normalized)
n.buf.WriteByte('\n')
return n.buf.Read(p)
}
// normalizeACPLine ensures session/new and session/load params contain an
// mcpServers array. Returns the original line unchanged for all other methods.
func normalizeACPLine(line []byte) []byte {
// Quick check: if it already contains mcpServers, nothing to do.
if bytes.Contains(line, []byte(`"mcpServers"`)) {
return line
}
// Only bother parsing if the method could be session/new or session/load.
if !bytes.Contains(line, []byte(`"session/new"`)) &&
!bytes.Contains(line, []byte(`"session/load"`)) {
return line
}
var msg struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
if err := json.Unmarshal(line, &msg); err != nil {
return line
}
if msg.Method != "session/new" && msg.Method != "session/load" {
return line
}
// Patch params to include mcpServers: [].
var params map[string]json.RawMessage
if err := json.Unmarshal(msg.Params, &params); err != nil {
return line
}
if _, ok := params["mcpServers"]; ok {
return line
}
params["mcpServers"] = json.RawMessage(`[]`)
patched, err := json.Marshal(params)
if err != nil {
return line
}
msg.Params = patched
out, err := json.Marshal(msg)
if err != nil {
return line
}
return out
}
-577
View File
@@ -1,577 +0,0 @@
package cmd
import (
"context"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"charm.land/huh/v2"
"github.com/mark3labs/kit/internal/auth"
kit "github.com/mark3labs/kit/pkg/kit"
"github.com/spf13/cobra"
)
// authCmd represents the auth command for managing AI provider authentication.
// This command provides subcommands for login, logout, and status checking
// of authentication credentials for various AI providers, with OAuth support
// for providers like Anthropic and OpenAI.
var authCmd = &cobra.Command{
Use: "auth",
Short: "Manage authentication credentials for AI providers",
Long: `Manage authentication credentials for AI providers.
This command allows you to securely authenticate and manage credentials for various AI providers
using OAuth flows. Stored credentials take precedence over environment variables.
Available providers:
- anthropic: Anthropic Claude API (OAuth)
- openai: OpenAI API (OAuth and API key)
Examples:
kit auth login anthropic
kit auth login openai
kit auth logout anthropic
kit auth status`,
}
// authLoginCmd represents the login subcommand for authenticating with AI providers.
// It handles OAuth flow for supported providers, opening a browser for authentication
// and securely storing the resulting credentials for future use.
var authLoginCmd = &cobra.Command{
Use: "login [provider]",
Short: "Authenticate with an AI provider using OAuth",
Long: `Authenticate with an AI provider using OAuth flow.
This will open your browser to complete the OAuth authentication process.
Your credentials will be securely stored and will take precedence over
environment variables when making API calls.
Available providers:
- anthropic: Anthropic Claude API (OAuth)
- openai: OpenAI ChatGPT Plus/Pro (Codex OAuth)
Example:
kit auth login anthropic
kit auth login openai`,
Args: cobra.ExactArgs(1),
RunE: runAuthLogin,
}
// authLogoutCmd represents the logout subcommand for removing stored authentication credentials.
// This command removes stored API keys or OAuth tokens for specified providers,
// requiring the user to authenticate again or use environment variables.
var authLogoutCmd = &cobra.Command{
Use: "logout [provider]",
Short: "Remove stored authentication credentials for a provider",
Long: `Remove stored authentication credentials for an AI provider.
This will delete the stored API key or OAuth credentials for the specified provider.
You will need to use environment variables or command-line flags for authentication after logout.
Available providers:
- anthropic: Anthropic Claude API
- openai: OpenAI API
Example:
kit auth logout anthropic
kit auth logout openai`,
Args: cobra.ExactArgs(1),
RunE: runAuthLogout,
}
// authStatusCmd represents the status subcommand for checking authentication status.
// It displays which providers have stored credentials, their types (OAuth vs API key),
// creation dates, and expiration status without revealing the actual credentials.
var authStatusCmd = &cobra.Command{
Use: "status",
Short: "Show authentication status for all providers",
Long: `Show the current authentication status for all supported AI providers.
This command displays which providers have stored credentials and when they were created.
It does not display the actual API keys for security reasons.
Example:
kit auth status`,
RunE: runAuthStatus,
}
func init() {
authCmd.AddCommand(authLoginCmd)
authCmd.AddCommand(authLogoutCmd)
authCmd.AddCommand(authStatusCmd)
}
func runAuthLogin(cmd *cobra.Command, args []string) error {
provider := strings.ToLower(args[0])
switch provider {
case "anthropic":
return loginAnthropic()
case "openai":
return loginOpenAI()
default:
return fmt.Errorf("unsupported provider: %s. Available providers: anthropic, openai", provider)
}
}
func runAuthLogout(cmd *cobra.Command, args []string) error {
provider := strings.ToLower(args[0])
switch provider {
case "anthropic":
return logoutAnthropic()
case "openai":
return logoutOpenAI()
default:
return fmt.Errorf("unsupported provider: %s. Available providers: anthropic, openai", provider)
}
}
func runAuthStatus(cmd *cobra.Command, args []string) error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
fmt.Println("Authentication Status")
fmt.Println("====================")
fmt.Printf("Credentials file: %s\n\n", cm.GetCredentialsPath())
// Check Anthropic credentials
fmt.Print("Anthropic Claude: ")
if hasAnthropicCreds, err := cm.HasAnthropicCredentials(); err != nil {
fmt.Printf("Error checking credentials: %v\n", err)
} else if hasAnthropicCreds {
if creds, err := cm.GetAnthropicCredentials(); err != nil {
fmt.Printf("Error reading credentials: %v\n", err)
} else {
authType := "API Key"
status := "✓ Authenticated"
if creds.Type == "oauth" {
authType = "OAuth"
if creds.IsExpired() {
status = "⚠️ Token expired (will refresh automatically)"
} else if creds.NeedsRefresh() {
status = "⚠️ Token expires soon (will refresh automatically)"
}
}
fmt.Printf("%s (%s, stored %s)\n", status, authType, creds.CreatedAt.Format("2006-01-02 15:04:05"))
}
} else {
fmt.Println("✗ Not authenticated")
// Check if environment variable is set
if os.Getenv("ANTHROPIC_API_KEY") != "" {
fmt.Println(" (ANTHROPIC_API_KEY environment variable is set)")
}
}
// Check OpenAI credentials
fmt.Print("\nOpenAI: ")
if hasOpenAICreds, err := cm.HasOpenAICredentials(); err != nil {
fmt.Printf("Error checking credentials: %v\n", err)
} else if hasOpenAICreds {
if creds, err := cm.GetOpenAICredentials(); err != nil {
fmt.Printf("Error reading credentials: %v\n", err)
} else {
authType := "API Key"
status := "✓ Authenticated"
if creds.Type == "oauth" {
authType = "OAuth (ChatGPT/Codex)"
if creds.IsExpired() {
status = "⚠️ Token expired (will refresh automatically)"
} else if creds.NeedsRefresh() {
status = "⚠️ Token expires soon (will refresh automatically)"
}
}
accountInfo := ""
if creds.Type == "oauth" && creds.AccountID != "" {
accountInfo = fmt.Sprintf(" [%s]", creds.AccountID)
}
fmt.Printf("%s (%s%s, stored %s)\n", status, authType, accountInfo, creds.CreatedAt.Format("2006-01-02 15:04:05"))
}
} else {
fmt.Println("✗ Not authenticated")
// Check if environment variable is set
if os.Getenv("OPENAI_API_KEY") != "" {
fmt.Println(" (OPENAI_API_KEY environment variable is set)")
}
}
fmt.Println("\nTo authenticate with a provider:")
fmt.Println(" kit auth login anthropic")
fmt.Println(" kit auth login openai")
return nil
}
func loginAnthropic() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if already authenticated
if hasAuth, err := cm.HasAnthropicCredentials(); err == nil && hasAuth {
var reauth bool
err := huh.NewConfirm().
Title("You are already authenticated with Anthropic").
Description("Do you want to re-authenticate?").
Affirmative("Yes").
Negative("No").
Value(&reauth).
Run()
if err != nil || !reauth {
fmt.Println("Authentication cancelled.")
return nil
}
}
// Create OAuth client
client := auth.NewOAuthClient()
// Generate authorization URL
fmt.Println("🔐 Starting OAuth authentication with Anthropic...")
authData, err := client.GetAuthorizationURL()
if err != nil {
return fmt.Errorf("failed to generate authorization URL: %w", err)
}
// Display URL and try to open browser
fmt.Println("\n📱 Opening your browser for authentication...")
fmt.Println("If the browser doesn't open automatically, please visit this URL:")
fmt.Printf("\n%s\n\n", authData.URL)
// Try to open browser
auth.TryOpenBrowser(authData.URL)
// Wait for user to complete OAuth flow
fmt.Println("After authorizing the application, you'll receive an authorization code.")
var code string
err = huh.NewInput().
Title("Authorization code").
Description("Paste the code from your browser").
Value(&code).
Run()
if err != nil {
return fmt.Errorf("failed to read authorization code: %w", err)
}
code = strings.TrimSpace(code)
if code == "" {
return fmt.Errorf("authorization code cannot be empty")
}
// Exchange code for tokens
fmt.Println("\n🔄 Exchanging authorization code for access token...")
creds, err := client.ExchangeCode(code, authData.Verifier)
if err != nil {
return fmt.Errorf("failed to exchange authorization code: %w", err)
}
// Store the credentials
if err := cm.SetOAuthCredentials(creds); err != nil {
return fmt.Errorf("failed to store credentials: %w", err)
}
fmt.Println("✅ Successfully authenticated with Anthropic!")
fmt.Printf("📁 Credentials stored in: %s\n", cm.GetCredentialsPath())
fmt.Println("\n🎉 Your OAuth credentials will now be used for Anthropic API calls.")
fmt.Println("💡 You can check your authentication status with: kit auth status")
return nil
}
func logoutAnthropic() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if authenticated
hasAuth, err := cm.HasAnthropicCredentials()
if err != nil {
return fmt.Errorf("failed to check authentication status: %w", err)
}
if !hasAuth {
fmt.Println("You are not currently authenticated with Anthropic.")
return nil
}
// Confirm logout
var confirm bool
err = huh.NewConfirm().
Title("Remove Anthropic credentials").
Description("Are you sure you want to remove your stored credentials?").
Affirmative("Yes").
Negative("No").
Value(&confirm).
Run()
if err != nil || !confirm {
fmt.Println("Logout cancelled.")
return nil
}
// Remove credentials
if err := cm.RemoveAnthropicCredentials(); err != nil {
return fmt.Errorf("failed to remove credentials: %w", err)
}
fmt.Println("✓ Successfully logged out from Anthropic!")
fmt.Println("You will need to use environment variables or command-line flags for authentication.")
return nil
}
func loginOpenAI() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if already authenticated
if hasAuth, err := cm.HasOpenAICredentials(); err == nil && hasAuth {
var reauth bool
err := huh.NewConfirm().
Title("You are already authenticated with OpenAI (ChatGPT/Codex)").
Description("Do you want to re-authenticate?").
Affirmative("Yes").
Negative("No").
Value(&reauth).
Run()
if err != nil || !reauth {
fmt.Println("Authentication cancelled.")
return nil
}
}
// Create OAuth client
client := auth.NewOpenAIOAuthClient()
// Generate authorization URL
fmt.Println("🔐 Starting OAuth authentication with OpenAI (ChatGPT/Codex)...")
fmt.Println("This will open your browser to authenticate with your ChatGPT account.")
fmt.Println()
authData, err := client.GetAuthorizationURL()
if err != nil {
return fmt.Errorf("failed to generate authorization URL: %w", err)
}
// Start local callback server
callbackServer, err := startOpenAICallbackServer(authData.State)
if err != nil {
fmt.Printf("⚠️ Could not start local callback server: %v\n", err)
fmt.Println("Falling back to manual code entry.")
}
if callbackServer != nil {
defer callbackServer.Close()
}
// Display URL and try to open browser
fmt.Println("📱 Opening your browser for authentication...")
fmt.Println("If the browser doesn't open automatically, please visit this URL:")
fmt.Printf("\n%s\n\n", authData.URL)
// Try to open browser
auth.TryOpenBrowser(authData.URL)
// Wait for callback or manual input
var code string
if callbackServer != nil {
fmt.Println("Waiting for browser authentication...")
select {
case callbackCode := <-callbackServer.CodeChan:
if callbackCode != "" {
code = callbackCode
fmt.Println("✓ Received authorization code from browser callback.")
}
case <-time.After(2 * time.Minute):
fmt.Println("\n⏱️ Timeout waiting for browser callback.")
callbackServer.Close()
}
}
// If no code from callback, prompt for manual entry
if code == "" {
fmt.Println("\nAfter authorizing, paste the callback URL or authorization code below.")
fmt.Println("(The callback URL will look like: http://localhost:1455/auth/callback?code=...&state=...)")
fmt.Println()
var input string
err = huh.NewInput().
Title("Callback URL or Code").
Description("Paste the full callback URL or just the authorization code").
Value(&input).
Run()
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
input = strings.TrimSpace(input)
if input == "" {
return fmt.Errorf("authorization code cannot be empty")
}
// Parse the input (could be full URL or just code)
parsedCode, parsedState := auth.ParseOpenAIAuthorizationInput(input)
if parsedCode == "" {
return fmt.Errorf("could not extract authorization code from input")
}
// Validate state if provided
if parsedState != "" && parsedState != authData.State {
return fmt.Errorf("state mismatch - possible security issue")
}
code = parsedCode
}
// Exchange code for tokens
fmt.Println("\n🔄 Exchanging authorization code for access token...")
creds, err := client.ExchangeCode(code, authData.Verifier)
if err != nil {
return fmt.Errorf("failed to exchange authorization code: %w", err)
}
// Store the credentials
if err := cm.SetOpenAIOAuthCredentials(creds); err != nil {
return fmt.Errorf("failed to store credentials: %w", err)
}
fmt.Println("✅ Successfully authenticated with OpenAI (ChatGPT/Codex)!")
fmt.Printf("📁 Credentials stored in: %s\n", cm.GetCredentialsPath())
fmt.Printf("👤 Account ID: %s\n", creds.AccountID)
fmt.Println("\n🎉 Your OAuth credentials will now be used for OpenAI API calls.")
fmt.Println("💡 You can check your authentication status with: kit auth status")
return nil
}
// callbackServer holds the HTTP server and channel for receiving the OAuth callback
type callbackServer struct {
Server *http.Server
CodeChan chan string
State string
}
// Close shuts down the callback server
func (cs *callbackServer) Close() {
if cs.Server != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = cs.Server.Shutdown(ctx)
}
}
// startOpenAICallbackServer starts a local HTTP server to receive the OAuth callback
func startOpenAICallbackServer(expectedState string) (*callbackServer, error) {
codeChan := make(chan string, 1)
mux := http.NewServeMux()
server := &http.Server{
Addr: "127.0.0.1:1455",
Handler: mux,
}
mux.HandleFunc("/auth/callback", func(w http.ResponseWriter, r *http.Request) {
// Check state
state := r.URL.Query().Get("state")
if state != expectedState {
http.Error(w, "State mismatch", http.StatusBadRequest)
return
}
code := r.URL.Query().Get("code")
if code == "" {
http.Error(w, "Missing authorization code", http.StatusBadRequest)
return
}
// Send code to channel
select {
case codeChan <- code:
default:
}
// Return success page
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head><title>Authentication Successful</title></head>
<body style="font-family: sans-serif; text-align: center; padding: 50px;">
<h1>✓ Authentication Successful</h1>
<p>You can close this window and return to the terminal.</p>
</body>
</html>`)
})
// Try to start server
listener, err := net.Listen("tcp", "127.0.0.1:1455")
if err != nil {
return nil, fmt.Errorf("port 1455 not available: %w", err)
}
_ = listener.Close()
go func() {
_ = server.ListenAndServe()
}()
return &callbackServer{
Server: server,
CodeChan: codeChan,
State: expectedState,
}, nil
}
func logoutOpenAI() error {
cm, err := kit.NewCredentialManager()
if err != nil {
return fmt.Errorf("failed to initialize credential manager: %w", err)
}
// Check if authenticated
hasAuth, err := cm.HasOpenAICredentials()
if err != nil {
return fmt.Errorf("failed to check authentication status: %w", err)
}
if !hasAuth {
fmt.Println("You are not currently authenticated with OpenAI.")
return nil
}
// Confirm logout
var confirm bool
err = huh.NewConfirm().
Title("Remove OpenAI credentials").
Description("Are you sure you want to remove your stored credentials?").
Affirmative("Yes").
Negative("No").
Value(&confirm).
Run()
if err != nil || !confirm {
fmt.Println("Logout cancelled.")
return nil
}
// Remove credentials
if err := cm.RemoveOpenAICredentials(); err != nil {
return fmt.Errorf("failed to remove credentials: %w", err)
}
fmt.Println("✓ Successfully logged out from OpenAI!")
fmt.Println("You will need to use environment variables or command-line flags for authentication.")
return nil
}
-244
View File
@@ -1,244 +0,0 @@
package cmd
import (
"fmt"
"os"
"text/tabwriter"
"github.com/mark3labs/kit/internal/extensions"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var extensionsCmd = &cobra.Command{
Use: "extensions",
Short: "Manage KIT extensions",
Long: "Commands for listing, validating, and scaffolding KIT extensions",
}
var extensionsListCmd = &cobra.Command{
Use: "list",
Short: "List discovered extensions and their handlers",
RunE: func(cmd *cobra.Command, args []string) error {
loaded, err := extensions.LoadExtensions(viper.GetStringSlice("extension"))
if err != nil {
return fmt.Errorf("loading extensions: %w", err)
}
if len(loaded) == 0 {
fmt.Println("No extensions found.")
fmt.Println()
fmt.Println("Extension search paths:")
fmt.Println(" ~/.config/kit/extensions/*.go (global)")
fmt.Println(" .kit/extensions/*.go (project)")
fmt.Println()
fmt.Println("Run 'kit extensions init' to create an example extension.")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintln(w, "EXTENSION\tEVENT\tHANDLERS\tTOOLS\tCOMMANDS")
for _, ext := range loaded {
totalHandlers := 0
for _, handlers := range ext.Handlers {
totalHandlers += len(handlers)
}
first := true
for event, handlers := range ext.Handlers {
if first {
_, _ = fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%d\n",
ext.Path, event, len(handlers), len(ext.Tools), len(ext.Commands))
first = false
} else {
_, _ = fmt.Fprintf(w, "\t%s\t%d\t\t\n",
event, len(handlers))
}
}
if first {
// Extension loaded but registered no handlers
_, _ = fmt.Fprintf(w, "%s\t(none)\t0\t%d\t%d\n",
ext.Path, len(ext.Tools), len(ext.Commands))
}
}
return w.Flush()
},
}
var extensionsValidateCmd = &cobra.Command{
Use: "validate",
Short: "Validate all extension files can be loaded",
RunE: func(cmd *cobra.Command, args []string) error {
loaded, err := extensions.LoadExtensions(viper.GetStringSlice("extension"))
if err != nil {
return fmt.Errorf("validation failed: %w", err)
}
fmt.Printf("Loaded %d extension(s) successfully\n", len(loaded))
for _, ext := range loaded {
total := 0
for _, h := range ext.Handlers {
total += len(h)
}
fmt.Printf(" %s (%d handlers, %d tools, %d commands)\n",
ext.Path, total, len(ext.Tools), len(ext.Commands))
}
return nil
},
}
var extensionsInitCmd = &cobra.Command{
Use: "init",
Short: "Generate an example extension file",
RunE: func(cmd *cobra.Command, args []string) error {
dir := ".kit/extensions"
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("creating extensions directory: %w", err)
}
example := `package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"time"
"kit/ext"
)
// Init is called when the extension is loaded. Register handlers here.
func Init(api ext.API) {
// ── Event handlers ────────────────────────────────────────────────
// Log every tool call to a file.
api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {
f, err := os.OpenFile("/tmp/kit-tool-log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
defer f.Close()
fmt.Fprintf(f, "[%s] tool=%s\n", time.Now().Format(time.RFC3339), tc.ToolName)
}
return nil // don't block
})
// Block dangerous bash commands.
api.OnToolCall(func(tc ext.ToolCallEvent, ctx ext.Context) *ext.ToolCallResult {
if tc.ToolName == "bash" && strings.Contains(tc.Input, "rm -rf /") {
return &ext.ToolCallResult{Block: true, Reason: "Blocked: dangerous command"}
}
return nil
})
// Handle custom ! commands via OnInput. Use ctx.Print* instead of
// fmt.Println — BubbleTea captures stdout in interactive mode.
api.OnInput(func(ie ext.InputEvent, ctx ext.Context) *ext.InputResult {
switch ie.Text {
case "!time":
ctx.PrintInfo("Current time: " + time.Now().Format(time.RFC3339))
return &ext.InputResult{Action: "handled"}
case "!status":
ctx.PrintBlock(ext.PrintBlockOpts{
Text: "Session active\nModel: " + ctx.Model + "\nCWD: " + ctx.CWD,
BorderColor: "#a6e3a1",
Subtitle: "my-extension",
})
return &ext.InputResult{Action: "handled"}
}
return nil
})
// ── Slash commands ────────────────────────────────────────────────
// Slash commands appear in /help and the autocomplete popup.
// They are invoked as /name <args> in the interactive TUI.
api.RegisterCommand(ext.CommandDef{
Name: "echo",
Description: "Echo back the provided text",
Execute: func(args string, ctx ext.Context) (string, error) {
if args == "" {
return "Usage: /echo <text>", nil
}
return args, nil
},
})
// ── Background work with SendMessage ─────────────────────────────
// ctx.SendMessage injects a message into the conversation and
// triggers a new agent turn. Safe to call from goroutines.
api.RegisterCommand(ext.CommandDef{
Name: "run",
Description: "Run a shell command in the background and feed the result to the agent",
Execute: func(args string, ctx ext.Context) (string, error) {
if args == "" {
return "Usage: /run <command>", nil
}
go func() {
out, err := exec.Command("sh", "-c", args).CombinedOutput()
if err != nil {
ctx.SendMessage(fmt.Sprintf("Background command %q failed: %v\n%s", args, err, out))
return
}
ctx.SendMessage(fmt.Sprintf("Background command %q finished:\n%s", args, out))
}()
return fmt.Sprintf("Running %q in background...", args), nil
},
})
// ── Custom tools ──────────────────────────────────────────────────
// Custom tools are added to the agent's tool list and can be
// called by the LLM. Parameters is a JSON Schema string.
api.RegisterTool(ext.ToolDef{
Name: "current_time",
Description: "Get the current date and time",
Parameters: ` + "`" + `{"type":"object","properties":{}}` + "`" + `,
Execute: func(input string) (string, error) {
return time.Now().Format(time.RFC3339), nil
},
})
api.RegisterTool(ext.ToolDef{
Name: "env_lookup",
Description: "Look up the value of an environment variable",
Parameters: ` + "`" + `{"type":"object","properties":{"name":{"type":"string","description":"Environment variable name"}},"required":["name"]}` + "`" + `,
Execute: func(input string) (string, error) {
var params struct {
Name string ` + "`" + `json:"name"` + "`" + `
}
if err := json.Unmarshal([]byte(input), &params); err != nil {
return "", fmt.Errorf("invalid parameters: %w", err)
}
val, ok := os.LookupEnv(params.Name)
if !ok {
return fmt.Sprintf("Environment variable %q is not set", params.Name), nil
}
return val, nil
},
})
}
`
path := dir + "/example.go"
if err := os.WriteFile(path, []byte(example), 0644); err != nil {
return fmt.Errorf("writing example: %w", err)
}
fmt.Printf("Created %s with example extension\n", path)
fmt.Println()
fmt.Println("The extension will be auto-loaded on the next kit run.")
fmt.Println("Use --no-extensions to disable all extensions.")
return nil
},
}
func init() {
rootCmd.AddCommand(extensionsCmd)
extensionsCmd.AddCommand(extensionsListCmd)
extensionsCmd.AddCommand(extensionsValidateCmd)
extensionsCmd.AddCommand(extensionsInitCmd)
}
-225
View File
@@ -1,225 +0,0 @@
package cmd
import (
"fmt"
"os/exec"
"github.com/charmbracelet/log"
"github.com/mark3labs/kit/internal/extensions"
"github.com/spf13/cobra"
)
var (
installLocalFlag bool
installUpdateFlag bool
installUninstallFlag bool
installAllFlag bool
)
var installCmd = &cobra.Command{
Use: "install <git-url>",
Short: "Install extensions from git repositories",
Long: `Install extensions from git repositories.
The install command downloads and installs Kit extensions from git repositories.
Extensions are stored in the global extensions directory by default, or in the
project's .kit/git/ directory when using the --local flag.
When a repo contains multiple extensions, an interactive multi-select is shown
so you can choose which to install. Use --all to skip selection and install everything.
Supported URL formats:
- github.com/user/repo (shorthand, defaults to HTTPS)
- git:github.com/user/repo
- https://github.com/user/repo
- ssh://git@github.com/user/repo
- git@github.com:user/repo
You can pin to a specific version, tag, or commit using @:
- github.com/user/repo@v1.0.0
- github.com/user/repo@main
- github.com/user/repo@abc1234
Examples:
kit install github.com/user/my-extension
kit install github.com/user/my-extension@v1.0.0
kit install github.com/user/my-extension --local
kit install github.com/user/collection --all`,
Args: cobra.ExactArgs(1),
RunE: runInstall,
}
func init() {
installCmd.Flags().BoolVarP(&installLocalFlag, "local", "l", false, "Install to project-local .kit/git/ directory")
installCmd.Flags().BoolVarP(&installUpdateFlag, "update", "u", false, "Update an already-installed package")
installCmd.Flags().BoolVar(&installUninstallFlag, "uninstall", false, "Remove an installed package")
installCmd.Flags().BoolVar(&installAllFlag, "all", false, "Install all extensions without prompting")
rootCmd.AddCommand(installCmd)
}
func runInstall(cmd *cobra.Command, args []string) error {
sourceStr := args[0]
// Check that git is available
if _, err := exec.LookPath("git"); err != nil {
return fmt.Errorf("git is not installed or not in PATH")
}
// Parse the source
source, err := extensions.ParseGitSource(sourceStr)
if err != nil {
return fmt.Errorf("invalid source: %w", err)
}
// Determine scope
scope := extensions.ScopeGlobal
if installLocalFlag {
scope = extensions.ScopeProject
}
installer := extensions.NewInstaller(".")
// Handle uninstall
if installUninstallFlag {
return runUninstall(installer, source, scope)
}
// Handle update
if installUpdateFlag {
return runUpdate(installer, source, scope)
}
// Handle install
return runInstallPackage(installer, source, scope)
}
func runInstallPackage(installer *extensions.Installer, source *extensions.GitSource, scope extensions.InstallScope) error {
// Check if already installed
existingScope, installed := installer.IsInstalled(source)
if installed {
return fmt.Errorf("extension already installed (scope: %s). Use --update to update or --uninstall to remove", existingScope)
}
// Preview extensions to decide if we need multi-select
previews, tempDir, err := installer.PreviewExtensions(source)
if err != nil {
return fmt.Errorf("previewing extensions: %w", err)
}
defer extensions.CleanupTempDir(tempDir)
if len(previews) == 0 {
return fmt.Errorf("no extensions found in %s", source.String())
}
scopeStr := "globally"
if scope == extensions.ScopeProject {
scopeStr = "locally in .kit/git/"
}
// Single extension or --all flag: install everything directly
if len(previews) == 1 || installAllFlag {
if err := installer.Install(source, scope); err != nil {
return fmt.Errorf("install failed: %w", err)
}
if source.Pinned {
fmt.Printf("Installed %s at %s %s\n", source.String(), source.Ref, scopeStr)
} else {
fmt.Printf("Installed %d extension(s) from %s %s\n", len(previews), source.String(), scopeStr)
}
log.Info("extension installed", "source", source.String(), "scope", scope)
return nil
}
// Multiple extensions: show interactive selection
includePaths, err := multiSelectForInstall(previews)
if err != nil {
if err.Error() == "selection cancelled" || err.Error() == "no extensions selected" {
fmt.Println("Install cancelled.")
return nil
}
return fmt.Errorf("selection failed: %w", err)
}
if err := installer.InstallWithInclude(source, scope, includePaths); err != nil {
return fmt.Errorf("install failed: %w", err)
}
fmt.Printf("Installed %d extension(s) from %s %s\n", len(includePaths), source.String(), scopeStr)
for _, path := range includePaths {
fmt.Printf(" - %s\n", path)
}
log.Info("extension installed", "source", source.String(), "scope", scope, "selected", len(includePaths))
return nil
}
func runUpdate(installer *extensions.Installer, source *extensions.GitSource, scope extensions.InstallScope) error {
// Find the installed package
existingScope, installed := installer.IsInstalled(source)
if !installed {
// Try to find with wildcard (no version)
entry, foundScope, err := extensions.FindInManifest(source.Identity())
if err != nil || entry == nil {
return fmt.Errorf("extension not installed: %s", source.Identity())
}
// Parse the found entry's source
foundSource, err := extensions.ParseGitSource(entry.Source)
if err != nil {
return fmt.Errorf("failed to parse installed source: %w", err)
}
existingScope = foundScope
source = foundSource
}
// Override scope if specified
if installLocalFlag && scope != existingScope {
return fmt.Errorf("extension installed in %s scope, cannot update with --local flag", existingScope)
}
scope = existingScope
// Check if pinned
if source.Pinned {
fmt.Printf("Skipping %s (pinned at %s)\n", source.Identity(), source.Ref)
return nil
}
// Update
if err := installer.Update(source, scope); err != nil {
return fmt.Errorf("update failed: %w", err)
}
fmt.Printf("Updated %s\n", source.Identity())
log.Info("extension updated", "source", source.Identity(), "scope", scope)
return nil
}
func runUninstall(installer *extensions.Installer, source *extensions.GitSource, scope extensions.InstallScope) error {
// Find where it's installed (ignore scope flag for uninstall - remove from wherever it exists)
existingScope, installed := installer.IsInstalled(source)
if !installed {
// Try to find in manifests
entry, foundScope, err := extensions.FindInManifest(source.Identity())
if err != nil || entry == nil {
return fmt.Errorf("extension not installed: %s", source.Identity())
}
existingScope = foundScope
// Parse the found entry's source
foundSource, err := extensions.ParseGitSource(entry.Source)
if err != nil {
return fmt.Errorf("failed to parse installed source: %w", err)
}
source = foundSource
}
// Uninstall from the scope where it's installed
if err := installer.Uninstall(source, existingScope); err != nil {
return fmt.Errorf("uninstall failed: %w", err)
}
fmt.Printf("Uninstalled %s from %s scope\n", source.Identity(), existingScope)
log.Info("extension uninstalled", "source", source.Identity(), "scope", existingScope)
return nil
}
-70
View File
@@ -1,70 +0,0 @@
package cmd
import (
"fmt"
"os"
"charm.land/huh/v2"
"github.com/charmbracelet/log"
"github.com/mark3labs/kit/internal/extensions"
)
// multiSelectForInstall runs a multi-select prompt for extension selection.
// Returns the selected extension paths, or an error if cancelled.
func multiSelectForInstall(previews []extensions.ExtensionPreview) ([]string, error) {
if len(previews) == 0 {
return nil, fmt.Errorf("no extensions to select")
}
// Non-interactive: select all
if !isInteractive() {
log.Info("Non-interactive mode, selecting all extensions")
paths := make([]string, len(previews))
for i, p := range previews {
paths[i] = p.Path
}
return paths, nil
}
// Single extension: just return it
if len(previews) == 1 {
return []string{previews[0].Path}, nil
}
// Build options for huh MultiSelect
options := make([]huh.Option[string], len(previews))
for i, p := range previews {
label := fmt.Sprintf("%s %s", p.Name, p.Path)
options[i] = huh.NewOption(label, p.Path).Selected(true)
}
var selected []string
form := huh.NewForm(
huh.NewGroup(
huh.NewMultiSelect[string]().
Title("Select extensions to install").
Options(options...).
Value(&selected),
),
)
if err := form.Run(); err != nil {
return nil, fmt.Errorf("selection cancelled")
}
if len(selected) == 0 {
return nil, fmt.Errorf("no extensions selected")
}
return selected, nil
}
// isInteractive checks if the terminal is interactive.
func isInteractive() bool {
fi, err := os.Stdout.Stat()
if err != nil {
return false
}
return (fi.Mode() & os.ModeCharDevice) != 0
}
-26
View File
@@ -1,26 +0,0 @@
package main
import (
"context"
"fmt"
"os"
"github.com/charmbracelet/fang"
"github.com/mark3labs/kit/cmd"
)
var version = "dev"
func main() {
for _, arg := range os.Args[1:] {
if arg == "--version" || arg == "-v" {
fmt.Printf("kit version %s\n", version)
os.Exit(0)
}
}
rootCmd := cmd.GetRootCommand(version)
if err := fang.Execute(context.Background(), rootCmd); err != nil {
os.Exit(1)
}
}

Some files were not shown because too many files have changed in this diff Show More