@meowlynxsea/koi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +34 -0
  2. package/NOTICE +35 -0
  3. package/README.md +15 -0
  4. package/bin/koi +12 -0
  5. package/dist/highlights-eq9cgrbb.scm +604 -0
  6. package/dist/highlights-ghv9g403.scm +205 -0
  7. package/dist/highlights-hk7bwhj4.scm +284 -0
  8. package/dist/highlights-r812a2qc.scm +150 -0
  9. package/dist/highlights-x6tmsnaa.scm +115 -0
  10. package/dist/injections-73j83es3.scm +27 -0
  11. package/dist/main.js +489918 -0
  12. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  13. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  14. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  15. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  16. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  17. package/package.json +51 -0
  18. package/src/agent/check-permissions.ts +239 -0
  19. package/src/agent/hooks/message-utils.ts +305 -0
  20. package/src/agent/hooks/types.ts +32 -0
  21. package/src/agent/hooks.ts +1560 -0
  22. package/src/agent/mode.ts +163 -0
  23. package/src/agent/monitor-registry.ts +308 -0
  24. package/src/agent/permission-ui.ts +71 -0
  25. package/src/agent/plan-ui.ts +74 -0
  26. package/src/agent/question-ui.ts +58 -0
  27. package/src/agent/session-fork.ts +299 -0
  28. package/src/agent/session-snapshots.ts +216 -0
  29. package/src/agent/session-store.ts +649 -0
  30. package/src/agent/session-tasks.ts +305 -0
  31. package/src/agent/session.ts +27 -0
  32. package/src/agent/subagent-registry.ts +176 -0
  33. package/src/agent/subagent.ts +194 -0
  34. package/src/agent/tool-orchestration.ts +55 -0
  35. package/src/agent/tools.ts +8 -0
  36. package/src/cli/args.ts +6 -0
  37. package/src/cli/commands.ts +5 -0
  38. package/src/commands/skills/index.ts +23 -0
  39. package/src/config/models.ts +6 -0
  40. package/src/config/settings.ts +392 -0
  41. package/src/main.tsx +64 -0
  42. package/src/services/mcp/client.ts +194 -0
  43. package/src/services/mcp/config.ts +232 -0
  44. package/src/services/mcp/connection-manager.ts +258 -0
  45. package/src/services/mcp/index.ts +80 -0
  46. package/src/services/mcp/mcp-commands.ts +114 -0
  47. package/src/services/mcp/stdio-transport.ts +246 -0
  48. package/src/services/mcp/types.ts +155 -0
  49. package/src/skills/SkillsMenu.tsx +370 -0
  50. package/src/skills/bundled/batch.ts +106 -0
  51. package/src/skills/bundled/debug.ts +86 -0
  52. package/src/skills/bundled/loremIpsum.ts +101 -0
  53. package/src/skills/bundled/remember.ts +97 -0
  54. package/src/skills/bundled/simplify.ts +100 -0
  55. package/src/skills/bundled/skillify.ts +123 -0
  56. package/src/skills/bundled/stuck.ts +101 -0
  57. package/src/skills/bundled/updateConfig.ts +228 -0
  58. package/src/skills/bundled.ts +46 -0
  59. package/src/skills/frontmatter.ts +179 -0
  60. package/src/skills/index.ts +87 -0
  61. package/src/skills/invoke.ts +231 -0
  62. package/src/skills/loader.ts +710 -0
  63. package/src/skills/substitution.ts +169 -0
  64. package/src/skills/types.ts +201 -0
  65. package/src/tools/agent.ts +143 -0
  66. package/src/tools/ask-user-question.ts +46 -0
  67. package/src/tools/bash.ts +148 -0
  68. package/src/tools/edit.ts +164 -0
  69. package/src/tools/glob.ts +102 -0
  70. package/src/tools/grep.ts +248 -0
  71. package/src/tools/index.ts +73 -0
  72. package/src/tools/list-mcp-resources.ts +74 -0
  73. package/src/tools/ls.ts +85 -0
  74. package/src/tools/mcp.ts +76 -0
  75. package/src/tools/monitor.ts +159 -0
  76. package/src/tools/plan-mode.ts +134 -0
  77. package/src/tools/read-mcp-resource.ts +79 -0
  78. package/src/tools/read.ts +137 -0
  79. package/src/tools/skill.ts +176 -0
  80. package/src/tools/task.ts +349 -0
  81. package/src/tools/types.ts +52 -0
  82. package/src/tools/webfetch-domains.ts +239 -0
  83. package/src/tools/webfetch.ts +533 -0
  84. package/src/tools/write.ts +101 -0
  85. package/src/tui/app.tsx +1178 -0
  86. package/src/tui/components/chat-panel.tsx +1071 -0
  87. package/src/tui/components/command-panel.tsx +261 -0
  88. package/src/tui/components/confirm-modal.tsx +135 -0
  89. package/src/tui/components/connect-modal.tsx +435 -0
  90. package/src/tui/components/connecting-modal.tsx +167 -0
  91. package/src/tui/components/edit-pending-modal.tsx +103 -0
  92. package/src/tui/components/exit-modal.tsx +131 -0
  93. package/src/tui/components/fork-modal.tsx +377 -0
  94. package/src/tui/components/image-preview-modal.tsx +141 -0
  95. package/src/tui/components/image-utils.ts +128 -0
  96. package/src/tui/components/info-bar.tsx +103 -0
  97. package/src/tui/components/input-box.tsx +352 -0
  98. package/src/tui/components/mcp/MCPSettings.tsx +386 -0
  99. package/src/tui/components/mcp/index.ts +7 -0
  100. package/src/tui/components/model-modal.tsx +310 -0
  101. package/src/tui/components/pending-area.tsx +88 -0
  102. package/src/tui/components/rename-modal.tsx +119 -0
  103. package/src/tui/components/session-modal.tsx +233 -0
  104. package/src/tui/components/side-bar.tsx +349 -0
  105. package/src/tui/components/tool-output.ts +6 -0
  106. package/src/tui/hooks/user-prompt-history.ts +114 -0
  107. package/src/tui/theme.ts +63 -0
  108. package/src/types/commands.ts +80 -0
  109. package/src/types/cross-spawn.d.ts +24 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * SkillsMenu Component
3
+ *
4
+ * A modal dialog that displays available skills grouped by source,
5
+ * with search filtering and detailed skill information.
6
+ */
7
+
8
+ import { useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { useKeyboard, useTerminalDimensions } from "@opentui/react";
10
+ import { createTextAttributes } from "@opentui/core";
11
+ import type { TextareaRenderable } from "@opentui/core";
12
+ import type { SkillCommand, SkillSource } from "./types.js";
13
+
14
+ interface SkillsMenuProps {
15
+ isActive: boolean;
16
+ onClose: () => void;
17
+ skills: SkillCommand[];
18
+ onInvokeSkill?: (skill: SkillCommand, args: string) => void;
19
+ }
20
+
21
+ interface SkillGroup {
22
+ source: SkillSource;
23
+ label: string;
24
+ skills: SkillCommand[];
25
+ }
26
+
27
+ const SOURCE_LABELS: Record<SkillSource, string> = {
28
+ userSettings: "User Skills",
29
+ projectSettings: "Project Skills",
30
+ policySettings: "Policy Skills",
31
+ plugin: "Plugin Skills",
32
+ bundled: "Built-in Skills",
33
+ mcp: "MCP Skills",
34
+ };
35
+
36
+ /**
37
+ * SkillsMenu component
38
+ */
39
+ export function SkillsMenu({ isActive, onClose, skills, onInvokeSkill }: SkillsMenuProps) {
40
+ const { width, height } = useTerminalDimensions();
41
+ const inputRef = useRef<TextareaRenderable>(null);
42
+ const [filterText, setFilterText] = useState("");
43
+ const [selectedIndex, setSelectedIndex] = useState(0);
44
+ const [showDetails, setShowDetails] = useState(false);
45
+ const scrollOffsetRef = useRef(0);
46
+
47
+ const panelWidth = Math.min(80, Math.max(50, Math.floor(width * 0.8)));
48
+ const panelHeight = Math.min(height - 4, 25);
49
+ const inputHeight = 1;
50
+ const separatorHeight = 1;
51
+ const detailsHeight = showDetails ? 8 : 0;
52
+ const listHeight = panelHeight - inputHeight - separatorHeight - detailsHeight - 2;
53
+
54
+ // Group skills by source
55
+ const skillGroups = useMemo<SkillGroup[]>(() => {
56
+ const groups: Map<SkillSource, SkillCommand[]> = new Map();
57
+
58
+ for (const skill of skills) {
59
+ const existing = groups.get(skill.source) ?? [];
60
+ existing.push(skill);
61
+ groups.set(skill.source, existing);
62
+ }
63
+
64
+ return Array.from(groups.entries())
65
+ .filter(([_, groupSkills]) => groupSkills.length > 0)
66
+ .map(([source, groupSkills]) => ({
67
+ source,
68
+ label: SOURCE_LABELS[source] ?? source,
69
+ skills: groupSkills,
70
+ }));
71
+ }, [skills]);
72
+
73
+ // Flatten skills with filtering
74
+ const { flatItems, totalSkills } = useMemo(() => {
75
+ const query = filterText.toLowerCase().trim();
76
+
77
+ const filteredGroups = skillGroups
78
+ .map((group) => ({
79
+ ...group,
80
+ skills: group.skills.filter(
81
+ (skill) =>
82
+ !query ||
83
+ skill.name.toLowerCase().includes(query) ||
84
+ skill.description.toLowerCase().includes(query) ||
85
+ skill.whenToUse?.toLowerCase().includes(query)
86
+ ),
87
+ }))
88
+ .filter((group) => group.skills.length > 0);
89
+
90
+ const items: Array<{ type: "header" | "skill"; group?: SkillGroup; skill?: SkillCommand }> = [];
91
+ for (const group of filteredGroups) {
92
+ items.push({ type: "header", group });
93
+ for (const skill of group.skills) {
94
+ items.push({ type: "skill", group, skill });
95
+ }
96
+ }
97
+
98
+ const total = skills.length;
99
+ return { flatItems: items, totalSkills: total };
100
+ }, [skillGroups, filterText, skills.length]);
101
+
102
+ // Reset when opened
103
+ useLayoutEffect(() => {
104
+ if (isActive) {
105
+ setFilterText("");
106
+ setSelectedIndex(0);
107
+ setShowDetails(false);
108
+ scrollOffsetRef.current = 0;
109
+ const ta = inputRef.current;
110
+ if (ta) {
111
+ ta.editBuffer.replaceText("");
112
+ ta.focus();
113
+ }
114
+ }
115
+ }, [isActive]);
116
+
117
+ // Get selected skill
118
+ const selectedItem = flatItems[selectedIndex];
119
+ const selectedSkill = selectedItem?.type === "skill" ? selectedItem.skill : null;
120
+
121
+ // Keyboard handling
122
+ useKeyboard((key) => {
123
+ if (!isActive) return;
124
+
125
+ if (key.ctrl && key.name === "p") {
126
+ key.preventDefault();
127
+ key.stopPropagation();
128
+ onClose();
129
+ return;
130
+ }
131
+
132
+ if (key.name === "escape") {
133
+ key.preventDefault();
134
+ key.stopPropagation();
135
+ onClose();
136
+ return;
137
+ }
138
+
139
+ if (key.name === "up" || key.name === "down") {
140
+ key.preventDefault();
141
+ key.stopPropagation();
142
+
143
+ const newIndex = key.name === "up"
144
+ ? Math.max(0, selectedIndex - 1)
145
+ : Math.min(flatItems.length - 1, selectedIndex + 1);
146
+
147
+ const newItem = flatItems[newIndex];
148
+ // Skip headers when navigating
149
+ if (newItem?.type === "header") {
150
+ setSelectedIndex(key.name === "up" ? Math.max(0, newIndex - 1) : Math.min(flatItems.length - 1, newIndex + 1));
151
+ } else {
152
+ setSelectedIndex(newIndex);
153
+ }
154
+
155
+ // Scroll handling
156
+ const currentScroll = scrollOffsetRef.current;
157
+ const newFlatIndex = flatItems.findIndex((item, idx) => idx === newIndex && item.type === "skill");
158
+ if (newFlatIndex !== -1) {
159
+ if (newFlatIndex < currentScroll) {
160
+ scrollOffsetRef.current = newFlatIndex;
161
+ } else if (newFlatIndex > currentScroll + listHeight - 1) {
162
+ scrollOffsetRef.current = newFlatIndex - listHeight + 1;
163
+ }
164
+ }
165
+ return;
166
+ }
167
+
168
+ if (key.name === "return" || key.name === "enter") {
169
+ key.preventDefault();
170
+ key.stopPropagation();
171
+
172
+ if (selectedSkill) {
173
+ if (onInvokeSkill && selectedSkill.argumentHint) {
174
+ // Show details and allow typing arguments
175
+ setShowDetails(true);
176
+ } else if (onInvokeSkill) {
177
+ onInvokeSkill(selectedSkill, "");
178
+ onClose();
179
+ } else {
180
+ // Just close and show the skill was selected
181
+ onClose();
182
+ }
183
+ }
184
+ return;
185
+ }
186
+
187
+ if (key.name === "tab") {
188
+ key.preventDefault();
189
+ key.stopPropagation();
190
+ setShowDetails((prev) => !prev);
191
+ return;
192
+ }
193
+
194
+ if (key.name === "right") {
195
+ key.preventDefault();
196
+ key.stopPropagation();
197
+ if (selectedSkill) {
198
+ setShowDetails(true);
199
+ }
200
+ return;
201
+ }
202
+ });
203
+
204
+ const handleContentChange = () => {
205
+ const text = inputRef.current?.editBuffer.getText() ?? "";
206
+ setFilterText(text);
207
+ setSelectedIndex(0);
208
+ scrollOffsetRef.current = 0;
209
+ };
210
+
211
+ if (!isActive) return null;
212
+
213
+ const effectiveScrollOffset = scrollOffsetRef.current;
214
+ const visibleItems = flatItems.slice(effectiveScrollOffset, effectiveScrollOffset + listHeight);
215
+
216
+ // Render details panel
217
+ const renderDetails = () => {
218
+ if (!showDetails || !selectedSkill) return null;
219
+
220
+ const details = [
221
+ selectedSkill.description && `Description: ${selectedSkill.description}`,
222
+ selectedSkill.whenToUse && `When to use: ${selectedSkill.whenToUse}`,
223
+ selectedSkill.argumentHint && `Arguments: ${selectedSkill.argumentHint}`,
224
+ selectedSkill.allowedTools.length > 0 && `Allowed tools: ${selectedSkill.allowedTools.join(", ")}`,
225
+ selectedSkill.context && `Context: ${selectedSkill.context}`,
226
+ ].filter(Boolean);
227
+
228
+ return (
229
+ <box height={detailsHeight} flexDirection="column" marginTop={1}>
230
+ <box height={1}>
231
+ <text fg="#6272a4">
232
+ {"─".repeat(panelWidth - 4)}
233
+ </text>
234
+ </box>
235
+ {details.map((line, idx) => (
236
+ <box key={idx} height={1}>
237
+ <text fg="#8be9fd" wrapMode="none">{line}</text>
238
+ </box>
239
+ ))}
240
+ </box>
241
+ );
242
+ };
243
+
244
+ return (
245
+ <box
246
+ position="absolute"
247
+ top={0}
248
+ left={0}
249
+ width="100%"
250
+ height="100%"
251
+ backgroundColor="#00000080"
252
+ alignItems="center"
253
+ justifyContent="center"
254
+ >
255
+ <box
256
+ width={panelWidth}
257
+ height={panelHeight}
258
+ flexDirection="column"
259
+ borderStyle="rounded"
260
+ borderColor="#6272a4"
261
+ backgroundColor="#282a36"
262
+ paddingX={1}
263
+ paddingY={1}
264
+ >
265
+ {/* Header */}
266
+ <box height={1} flexDirection="row" justifyContent="space-between">
267
+ <text fg="#ff79c6" attributes={createTextAttributes({ bold: true })}>
268
+ Skills ({totalSkills})
269
+ </text>
270
+ <text fg="#6272a4">
271
+ ↑↓ navigate · Enter select · Tab details · Esc close
272
+ </text>
273
+ </box>
274
+
275
+ {/* Filter input */}
276
+ <box height={inputHeight} marginTop={1}>
277
+ <text fg="#6272a4">Filter: </text>
278
+ <textarea
279
+ ref={inputRef}
280
+ initialValue=""
281
+ focused={isActive}
282
+ showCursor
283
+ height={1}
284
+ wrapMode="none"
285
+ width={panelWidth - 10}
286
+ textColor="#f8f8f2"
287
+ backgroundColor="#44475a"
288
+ onContentChange={handleContentChange}
289
+ />
290
+ </box>
291
+
292
+ {/* Separator */}
293
+ <box height={separatorHeight} marginTop={1}>
294
+ <text fg="#6272a4">
295
+ {"─".repeat(panelWidth - 2)}
296
+ </text>
297
+ </box>
298
+
299
+ {/* Skill list */}
300
+ <box height={listHeight} flexDirection="column" overflow="hidden">
301
+ {visibleItems.map((item, idx) => {
302
+ const flatIndex = effectiveScrollOffset + idx;
303
+
304
+ if (item.type === "header") {
305
+ return (
306
+ <box key={`h-${item.group?.source}-${flatIndex}`} height={1}>
307
+ <text
308
+ fg="#bd93f9"
309
+ attributes={createTextAttributes({ bold: true })}
310
+ >
311
+ {item.group?.label}
312
+ </text>
313
+ </box>
314
+ );
315
+ }
316
+
317
+ if (item.type === "skill" && item.skill) {
318
+ const isSelected = flatIndex === selectedIndex;
319
+ const safeDescription = (item.skill.description ?? "").slice(0, 50);
320
+ return (
321
+ <box
322
+ key={`s-${item.skill.name}-${flatIndex}`}
323
+ height={1}
324
+ backgroundColor={isSelected ? "#44475a" : undefined}
325
+ paddingLeft={2}
326
+ flexDirection="row"
327
+ >
328
+ <text fg="#ffb86c">/{item.skill.name}</text>
329
+ <text fg="#6272a4"> {safeDescription}{item.skill.description && item.skill.description.length > 50 ? "..." : ""}</text>
330
+ </box>
331
+ );
332
+ }
333
+
334
+ return null;
335
+ })}
336
+ {flatItems.length === 0 && (
337
+ <box height={listHeight} alignItems="center" justifyContent="center">
338
+ <text fg="#6272a4">No skills found</text>
339
+ </box>
340
+ )}
341
+ </box>
342
+
343
+ {/* Details panel */}
344
+ {renderDetails()}
345
+ </box>
346
+ </box>
347
+ );
348
+ }
349
+
350
+ /**
351
+ * SkillsMenuStandalone - A standalone modal version that doesn't need isActive prop
352
+ */
353
+ export function SkillsMenuStandalone({
354
+ skills,
355
+ onClose,
356
+ onInvokeSkill,
357
+ }: {
358
+ skills: SkillCommand[];
359
+ onClose: () => void;
360
+ onInvokeSkill?: (skill: SkillCommand, args: string) => void;
361
+ }) {
362
+ return (
363
+ <SkillsMenu
364
+ isActive={true}
365
+ onClose={onClose}
366
+ skills={skills}
367
+ onInvokeSkill={onInvokeSkill}
368
+ />
369
+ );
370
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Batch Skill
3
+ *
4
+ * Orchestrate large-scale changes across multiple parallel agents.
5
+ * Adapted from Claude Code's batch skill.
6
+ */
7
+
8
+ import { registerBundledSkill } from "../bundled.js";
9
+
10
+ const MIN_AGENTS = 3;
11
+ const MAX_AGENTS = 20;
12
+
13
+ const WORKER_INSTRUCTIONS = `After implementing the change:
14
+ 1. **Review** — Use /simplify to review and clean up changes
15
+ 2. **Test** — Run the project's test suite
16
+ 3. **Commit** — Commit changes with a clear message
17
+ 4. **Report** — End with a summary of what was done`;
18
+
19
+ const NOT_A_GIT_REPO_MESSAGE = `This is not a git repository. The \`/batch\` command requires a git repo. Initialize a repo first, or run this from inside an existing one.`;
20
+
21
+ const MISSING_INSTRUCTION_MESSAGE = `Provide an instruction describing the batch change you want to make.
22
+
23
+ Examples:
24
+ /batch add TypeScript to all JavaScript files
25
+ /batch replace lodash with native equivalents
26
+ /batch add error handling to all API routes`;
27
+
28
+ export function registerBatchSkill(): void {
29
+ registerBundledSkill({
30
+ name: "batch",
31
+ description:
32
+ "Plan and execute a large-scale change across multiple parallel agents.",
33
+ whenToUse:
34
+ "Use when making sweeping changes across many files that can be decomposed into independent parallel units.",
35
+ argumentHint: "<instruction>",
36
+ userInvocable: true,
37
+ disableModelInvocation: true,
38
+ async getPromptForCommand(args) {
39
+ const instruction = args.trim();
40
+ if (!instruction) {
41
+ return [{ type: "text", text: MISSING_INSTRUCTION_MESSAGE }];
42
+ }
43
+
44
+ const prompt = `# Batch: Parallel Work Orchestration
45
+
46
+ You are orchestrating a large, parallelizable change across this codebase.
47
+
48
+ ## User Instruction
49
+
50
+ ${instruction}
51
+
52
+ ## Phase 1: Research and Plan
53
+
54
+ 1. **Understand the scope.** Research what files, patterns, and locations need to change.
55
+ 2. **Decompose into units.** Break the work into ${MIN_AGENTS}–${MAX_AGENTS} self-contained units. Each unit must:
56
+ - Be independently implementable
57
+ - Be mergeable without depending on another unit
58
+ - Be roughly uniform in size
59
+
60
+ Scale the count to the actual work: few files → closer to ${MIN_AGENTS}; many files → closer to ${MAX_AGENTS}.
61
+
62
+ 3. **Determine verification.** Figure out how to verify each change works:
63
+ - Run tests
64
+ - Check for linting errors
65
+ - Verify the change visually if applicable
66
+
67
+ 4. **Write the plan.** Include:
68
+ - Summary of scope
69
+ - Numbered list of work units with file lists
70
+ - Verification approach for each unit
71
+
72
+ ## Phase 2: Execute in Parallel
73
+
74
+ Spawn one agent per work unit. Each agent should:
75
+ - Have the full context of what to do
76
+ - Know the files to modify
77
+ - Understand the verification approach
78
+ - Follow these instructions:
79
+
80
+ \`\`\`
81
+ ${WORKER_INSTRUCTIONS}
82
+ \`\`\`
83
+
84
+ ## Phase 3: Track Progress
85
+
86
+ Track progress with a status table:
87
+
88
+ | # | Unit | Status |
89
+ |---|------|--------|
90
+ | 1 | <title> | running |
91
+ | 2 | <title> | done |
92
+
93
+ As agents complete, update the status and summarize results.
94
+
95
+ ## Final Summary
96
+
97
+ When all agents finish, provide a summary of:
98
+ - How many units completed successfully
99
+ - Any failures or issues encountered
100
+ - Overall impact of the changes
101
+ `;
102
+
103
+ return [{ type: "text", text: prompt }];
104
+ },
105
+ });
106
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Debug Skill
3
+ *
4
+ * Enable debug logging and help diagnose issues in Koi.
5
+ * Adapted from Claude Code's debug skill.
6
+ */
7
+
8
+ import { registerBundledSkill } from "../bundled.js";
9
+
10
+ const DEFAULT_DEBUG_LINES_READ = 30;
11
+
12
+ export function registerDebugSkill(): void {
13
+ registerBundledSkill({
14
+ name: "debug",
15
+ description:
16
+ "Enable debug logging for this session and help diagnose issues. Read debug logs and provide diagnostic information.",
17
+ allowedTools: ["Read", "Grep", "Glob"],
18
+ argumentHint: "[issue description]",
19
+ disableModelInvocation: true,
20
+ userInvocable: true,
21
+ async getPromptForCommand(args) {
22
+ const prompt = `# Debug Skill
23
+
24
+ Help the user debug an issue they're encountering in this Koi session.
25
+
26
+ ## Session Context
27
+
28
+ Working directory: \`{{cwd}}\`
29
+ Current time: {{timestamp}}
30
+
31
+ ## Issue Description
32
+
33
+ ${args || "The user did not describe a specific issue. Ask clarifying questions to understand what problem they are experiencing."}
34
+
35
+ ## Diagnostic Steps
36
+
37
+ ### 1. Gather Information
38
+ Ask the user to describe:
39
+ - What were you trying to do?
40
+ - What happened instead?
41
+ - When did this start happening?
42
+ - What changed recently?
43
+
44
+ ### 2. Check Common Issues
45
+
46
+ **Connection Issues:**
47
+ - Is the API provider configured correctly?
48
+ - Is the API key valid?
49
+ - Is there a network connectivity issue?
50
+
51
+ **Model Issues:**
52
+ - Is the model ID correct?
53
+ - Is the model available for this provider?
54
+ - Are there rate limit errors?
55
+
56
+ **Tool Issues:**
57
+ - Are the required tools available?
58
+ - Is there a permission issue?
59
+ - Are there file system errors?
60
+
61
+ ### 3. Suggest Next Steps
62
+
63
+ Based on the issue, suggest:
64
+ - Configuration fixes
65
+ - Alternative approaches
66
+ - Commands to run for more diagnostics
67
+ - Settings to check
68
+
69
+ ## Settings Files to Check
70
+
71
+ - Koi Settings: \`~/.config/koi/settings.json\`
72
+ - Pi Settings: \`~/.config/koi/pi/settings.json\`
73
+
74
+ ## Instructions
75
+
76
+ 1. Ask clarifying questions if the issue is unclear
77
+ 2. Read relevant configuration files
78
+ 3. Identify the root cause
79
+ 4. Provide specific, actionable fixes
80
+ 5. Explain what went wrong in plain language
81
+ `;
82
+
83
+ return [{ type: "text", text: prompt }];
84
+ },
85
+ });
86
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Lorem Ipsum Skill
3
+ *
4
+ * Generate filler text for testing long context handling.
5
+ */
6
+
7
+ import { registerBundledSkill } from "../bundled.js";
8
+
9
+ // Verified 1-token words (common English words)
10
+ const ONE_TOKEN_WORDS = [
11
+ "the", "a", "an", "I", "you", "he", "she", "it", "we", "they",
12
+ "me", "him", "her", "us", "them", "my", "your", "his", "its", "our",
13
+ "this", "that", "what", "who", "is", "are", "was", "were", "be",
14
+ "been", "have", "has", "had", "do", "does", "did", "will", "would",
15
+ "can", "could", "may", "might", "must", "shall", "should", "make",
16
+ "made", "get", "got", "go", "went", "come", "came", "see", "saw",
17
+ "know", "take", "think", "look", "want", "use", "find", "give",
18
+ "tell", "work", "call", "try", "ask", "need", "feel", "seem",
19
+ "leave", "put", "time", "year", "day", "way", "man", "thing",
20
+ "life", "hand", "part", "place", "case", "point", "fact", "good",
21
+ "new", "first", "last", "long", "great", "little", "own", "other",
22
+ "old", "right", "big", "high", "small", "large", "next", "early",
23
+ "young", "few", "public", "bad", "same", "able", "in", "on", "at",
24
+ "to", "for", "of", "with", "from", "by", "about", "like", "through",
25
+ "over", "before", "between", "under", "since", "without", "and", "or",
26
+ "but", "if", "than", "because", "as", "until", "while", "so",
27
+ "though", "both", "each", "when", "where", "why", "how", "not",
28
+ "now", "just", "more", "also", "here", "there", "then", "only",
29
+ "very", "well", "back", "still", "even", "much", "too", "such",
30
+ "never", "again", "most", "once", "off", "away", "down", "out",
31
+ "up", "test", "code", "data", "file", "line", "text", "word",
32
+ "number", "system", "program", "set", "run", "value", "name",
33
+ "type", "state", "end", "start"
34
+ ];
35
+
36
+ function generateLoremIpsum(targetTokens: number): string {
37
+ let tokens = 0;
38
+ let result = "";
39
+
40
+ while (tokens < targetTokens) {
41
+ const sentenceLength = 10 + Math.floor(Math.random() * 11);
42
+ let wordsInSentence = 0;
43
+
44
+ for (let i = 0; i < sentenceLength && tokens < targetTokens; i++) {
45
+ const word = ONE_TOKEN_WORDS[Math.floor(Math.random() * ONE_TOKEN_WORDS.length)];
46
+ result += word;
47
+ tokens++;
48
+ wordsInSentence++;
49
+
50
+ if (i === sentenceLength - 1 || tokens >= targetTokens) {
51
+ result += ". ";
52
+ } else {
53
+ result += " ";
54
+ }
55
+ }
56
+
57
+ // Paragraph break every 5-8 sentences
58
+ if (wordsInSentence > 0 && Math.random() < 0.2 && tokens < targetTokens) {
59
+ result += "\n\n";
60
+ }
61
+ }
62
+
63
+ return result.trim();
64
+ }
65
+
66
+ export function registerLoremIpsumSkill(): void {
67
+ registerBundledSkill({
68
+ name: "lorem-ipsum",
69
+ description:
70
+ "Generate filler text for long context testing. Specify token count as argument (e.g., /lorem-ipsum 50000).",
71
+ argumentHint: "[token_count]",
72
+ userInvocable: true,
73
+ async getPromptForCommand(args) {
74
+ const parsed = parseInt(args);
75
+
76
+ if (args && (isNaN(parsed) || parsed <= 0)) {
77
+ return [
78
+ {
79
+ type: "text",
80
+ text: "Invalid token count. Please provide a positive number (e.g., /lorem-ipsum 10000).",
81
+ },
82
+ ];
83
+ }
84
+
85
+ const targetTokens = parsed || 10000;
86
+ const cappedTokens = Math.min(targetTokens, 500_000);
87
+
88
+ if (cappedTokens < targetTokens) {
89
+ return [
90
+ {
91
+ type: "text",
92
+ text: `Requested ${targetTokens} tokens, but capped at 500,000 for safety.\n\n${generateLoremIpsum(cappedTokens)}`,
93
+ },
94
+ ];
95
+ }
96
+
97
+ const loremText = generateLoremIpsum(cappedTokens);
98
+ return [{ type: "text", text: loremText }];
99
+ },
100
+ });
101
+ }