@leing2021/super-pi 0.22.1 → 0.23.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 (110) hide show
  1. package/README.md +253 -211
  2. package/extensions/ce-core/index.ts +190 -7
  3. package/extensions/ce-core/tools/parallel-subagent.ts +3 -3
  4. package/extensions/ce-core/tools/subagent.ts +2 -2
  5. package/package.json +4 -20
  6. package/rules/common/naming.md +154 -0
  7. package/skills/02-plan/SKILL.md +3 -2
  8. package/skills/03-work/SKILL.md +9 -6
  9. package/skills/04-review/SKILL.md +3 -2
  10. package/skills/06-next/SKILL.md +38 -10
  11. package/skills/06-next/references/recommendation-logic.md +2 -2
  12. package/skills/07-worktree/SKILL.md +9 -8
  13. package/skills/{09-help → 08-help}/SKILL.md +3 -3
  14. package/skills/{09-help → 08-help}/references/workflow-sequence.md +2 -1
  15. package/skills/references/language-detection.md +44 -0
  16. package/LICENSE +0 -21
  17. package/THIRD-PARTY-NOTICES.md +0 -35
  18. package/extensions/subagent/__tests__/async-job-tracker.test.ts +0 -21
  19. package/extensions/subagent/__tests__/execution-activity.test.ts +0 -15
  20. package/extensions/subagent/__tests__/parallel-render-stress.test.ts +0 -82
  21. package/extensions/subagent/__tests__/render-dedup.test.ts +0 -98
  22. package/extensions/subagent/__tests__/render-widget.test.ts +0 -28
  23. package/extensions/subagent/__tests__/throttle.test.ts +0 -90
  24. package/extensions/subagent/agent-management.ts +0 -596
  25. package/extensions/subagent/agent-manager-chain-detail.ts +0 -163
  26. package/extensions/subagent/agent-manager-detail.ts +0 -232
  27. package/extensions/subagent/agent-manager-edit.ts +0 -391
  28. package/extensions/subagent/agent-manager-list.ts +0 -279
  29. package/extensions/subagent/agent-manager-parallel.ts +0 -305
  30. package/extensions/subagent/agent-manager.ts +0 -706
  31. package/extensions/subagent/agent-scope.ts +0 -9
  32. package/extensions/subagent/agent-selection.ts +0 -26
  33. package/extensions/subagent/agent-serializer.ts +0 -124
  34. package/extensions/subagent/agent-templates.ts +0 -63
  35. package/extensions/subagent/agents/context-builder.md +0 -37
  36. package/extensions/subagent/agents/delegate.md +0 -9
  37. package/extensions/subagent/agents/oracle.md +0 -73
  38. package/extensions/subagent/agents/planner.md +0 -52
  39. package/extensions/subagent/agents/researcher.md +0 -50
  40. package/extensions/subagent/agents/reviewer.md +0 -38
  41. package/extensions/subagent/agents/scout.md +0 -48
  42. package/extensions/subagent/agents/worker.md +0 -52
  43. package/extensions/subagent/agents.ts +0 -762
  44. package/extensions/subagent/artifacts.ts +0 -101
  45. package/extensions/subagent/async-execution.ts +0 -521
  46. package/extensions/subagent/async-job-tracker.ts +0 -240
  47. package/extensions/subagent/async-status.ts +0 -242
  48. package/extensions/subagent/chain-clarify.ts +0 -1365
  49. package/extensions/subagent/chain-execution.ts +0 -854
  50. package/extensions/subagent/chain-serializer.ts +0 -127
  51. package/extensions/subagent/completion-dedupe.ts +0 -66
  52. package/extensions/subagent/doctor.ts +0 -201
  53. package/extensions/subagent/execution.ts +0 -748
  54. package/extensions/subagent/file-coalescer.ts +0 -43
  55. package/extensions/subagent/fork-context.ts +0 -64
  56. package/extensions/subagent/formatters.ts +0 -123
  57. package/extensions/subagent/frontmatter.ts +0 -32
  58. package/extensions/subagent/index.ts +0 -586
  59. package/extensions/subagent/intercom-bridge.ts +0 -241
  60. package/extensions/subagent/jsonl-writer.ts +0 -84
  61. package/extensions/subagent/model-fallback.ts +0 -109
  62. package/extensions/subagent/notify.ts +0 -111
  63. package/extensions/subagent/parallel-utils.ts +0 -109
  64. package/extensions/subagent/pi-args.ts +0 -139
  65. package/extensions/subagent/pi-spawn.ts +0 -101
  66. package/extensions/subagent/post-exit-stdio-guard.ts +0 -88
  67. package/extensions/subagent/prompt-template-bridge.ts +0 -400
  68. package/extensions/subagent/prompts/gather-context-and-clarify.md +0 -13
  69. package/extensions/subagent/prompts/parallel-cleanup.md +0 -42
  70. package/extensions/subagent/prompts/parallel-research.md +0 -50
  71. package/extensions/subagent/prompts/parallel-review.md +0 -40
  72. package/extensions/subagent/render-helpers.ts +0 -83
  73. package/extensions/subagent/render.ts +0 -908
  74. package/extensions/subagent/result-intercom.ts +0 -238
  75. package/extensions/subagent/result-watcher.ts +0 -172
  76. package/extensions/subagent/run-history.ts +0 -58
  77. package/extensions/subagent/run-status.ts +0 -137
  78. package/extensions/subagent/schemas.ts +0 -165
  79. package/extensions/subagent/session-tokens.ts +0 -51
  80. package/extensions/subagent/settings.ts +0 -368
  81. package/extensions/subagent/single-output.ts +0 -98
  82. package/extensions/subagent/skills.ts +0 -627
  83. package/extensions/subagent/slash-bridge.ts +0 -177
  84. package/extensions/subagent/slash-commands.ts +0 -304
  85. package/extensions/subagent/slash-live-state.ts +0 -295
  86. package/extensions/subagent/subagent-control.ts +0 -151
  87. package/extensions/subagent/subagent-executor.ts +0 -1915
  88. package/extensions/subagent/subagent-prompt-runtime.ts +0 -76
  89. package/extensions/subagent/subagent-runner.ts +0 -1471
  90. package/extensions/subagent/subagents-status.ts +0 -473
  91. package/extensions/subagent/text-editor.ts +0 -273
  92. package/extensions/subagent/throttle.ts +0 -77
  93. package/extensions/subagent/top-level-async.ts +0 -16
  94. package/extensions/subagent/types.ts +0 -624
  95. package/extensions/subagent/utils.ts +0 -457
  96. package/extensions/subagent/worktree.ts +0 -580
  97. package/extensions/super-pi-extension/agents/ce-oracle.md +0 -109
  98. package/extensions/super-pi-extension/agents/ce-planner.md +0 -116
  99. package/extensions/super-pi-extension/agents/ce-reviewer.md +0 -99
  100. package/extensions/super-pi-extension/agents/ce-scout.md +0 -80
  101. package/extensions/super-pi-extension/agents/ce-worker.md +0 -78
  102. package/extensions/super-pi-extension/chains/ce-parallel-review.chain.md +0 -34
  103. package/extensions/super-pi-extension/chains/ce-review-only.chain.md +0 -12
  104. package/extensions/super-pi-extension/chains/ce-standard.chain.md +0 -28
  105. package/extensions/super-pi-extension/index.ts +0 -129
  106. package/extensions/super-pi-extension/model-sync.ts +0 -204
  107. package/skills/08-status/SKILL.md +0 -41
  108. package/skills/08-status/references/artifact-locations.md +0 -10
  109. package/skills/10-rules/SKILL.md +0 -76
  110. package/skills/pi-subagents/SKILL.md +0 -566
@@ -1,391 +0,0 @@
1
- // SPDX-FileCopyrightText: 2025 Nico Bailon
2
- // SPDX-License-Identifier: MIT
3
- // Source: https://github.com/nicobailon/pi-subagents
4
- import type { Theme } from "@mariozechner/pi-coding-agent";
5
- import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
6
- import { defaultSystemPromptMode, type AgentConfig, type BuiltinAgentOverrideBase } from "./agents.ts";
7
- import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
8
- import type { TextEditorState } from "./text-editor.ts";
9
- import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.ts";
10
-
11
- export interface ModelInfo { provider: string; id: string; fullId: string; }
12
- export interface SkillInfo { name: string; source: string; description?: string; }
13
- export type EditScreen = "edit" | "edit-field" | "edit-prompt";
14
- export type EditField = typeof FIELD_ORDER[number];
15
-
16
- export interface EditState {
17
- draft: AgentConfig; isNew: boolean; fieldIndex: number; fieldMode: "text" | "model" | "thinking" | "skills" | null;
18
- fieldEditor: TextEditorState; promptEditor: TextEditorState; modelSearchQuery: string; modelCursor: number; filteredModels: ModelInfo[];
19
- thinkingCursor: number; skillSearchQuery: string; skillCursor: number; filteredSkills: SkillInfo[]; skillSelected: Set<string>; error?: string;
20
- fields: EditField[];
21
- title?: string;
22
- overrideBase?: BuiltinAgentOverrideBase;
23
- }
24
- export interface EditInputResult { action?: "save" | "discard" | "delete"; nextScreen?: EditScreen; }
25
- export interface CreateEditStateOptions {
26
- fields?: EditField[];
27
- title?: string;
28
- overrideBase?: BuiltinAgentOverrideBase;
29
- }
30
-
31
- const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
32
- const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
33
- type ThinkingLevel = typeof THINKING_LEVELS[number];
34
- const PROMPT_VIEWPORT_HEIGHT = 16;
35
- const MODEL_SELECTOR_HEIGHT = 10;
36
- const SKILL_SELECTOR_HEIGHT = 10;
37
-
38
- function formatTools(draft: Pick<AgentConfig, "tools" | "mcpDirectTools">): string { const tools = [...(draft.tools ?? []), ...(draft.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; return tools.length > 0 ? tools.join(", ") : ""; }
39
- function toolList(draft: Pick<AgentConfig, "tools" | "mcpDirectTools">): string[] | undefined { const tools = [...(draft.tools ?? []), ...(draft.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; return tools.length > 0 ? tools : undefined; }
40
- function parseTools(value: string): { tools?: string[]; mcp?: string[] } { const items = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0); const tools: string[] = []; const mcp: string[] = []; for (const item of items) { if (item.startsWith("mcp:")) { const name = item.slice(4).trim(); if (name) mcp.push(name); } else { tools.push(item); } } return { tools: tools.length > 0 ? tools : undefined, mcp: mcp.length > 0 ? mcp : undefined }; }
41
- function parseCommaList(value: string): string[] | undefined { const items = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0); return items.length > 0 ? items : undefined; }
42
- function arraysEqual(a: string[] | undefined, b: string[] | undefined): boolean { if (!a && !b) return true; if (!a || !b || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; }
43
-
44
- function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
45
- const base = state.overrideBase;
46
- if (!base) return false;
47
- switch (field) {
48
- case "model": return state.draft.model === base.model;
49
- case "fallbackModels": return arraysEqual(state.draft.fallbackModels, base.fallbackModels);
50
- case "thinking": return state.draft.thinking === base.thinking;
51
- case "systemPromptMode": return state.draft.systemPromptMode === base.systemPromptMode;
52
- case "inheritProjectContext": return state.draft.inheritProjectContext === base.inheritProjectContext;
53
- case "inheritSkills": return state.draft.inheritSkills === base.inheritSkills;
54
- case "disabled": return state.draft.disabled === base.disabled;
55
- case "tools": return arraysEqual(toolList(state.draft), toolList(base));
56
- case "skills": return arraysEqual(state.draft.skills, base.skills);
57
- case "prompt": return state.draft.systemPrompt === base.systemPrompt;
58
- default: return false;
59
- }
60
- }
61
-
62
- function resetFieldToBase(field: EditField, state: EditState): void {
63
- const base = state.overrideBase;
64
- if (!base) return;
65
- switch (field) {
66
- case "model": state.draft.model = base.model; break;
67
- case "fallbackModels": state.draft.fallbackModels = base.fallbackModels ? [...base.fallbackModels] : undefined; break;
68
- case "thinking": state.draft.thinking = base.thinking; break;
69
- case "systemPromptMode": state.draft.systemPromptMode = base.systemPromptMode; break;
70
- case "inheritProjectContext": state.draft.inheritProjectContext = base.inheritProjectContext; break;
71
- case "inheritSkills": state.draft.inheritSkills = base.inheritSkills; break;
72
- case "disabled": state.draft.disabled = base.disabled; break;
73
- case "tools": state.draft.tools = base.tools ? [...base.tools] : undefined; state.draft.mcpDirectTools = base.mcpDirectTools ? [...base.mcpDirectTools] : undefined; break;
74
- case "skills": state.draft.skills = base.skills ? [...base.skills] : undefined; break;
75
- case "prompt": state.draft.systemPrompt = base.systemPrompt; state.promptEditor = createEditorState(base.systemPrompt); break;
76
- default: break;
77
- }
78
- }
79
-
80
- export function createEditState(draft: AgentConfig, isNew: boolean, models: ModelInfo[], skills: SkillInfo[], options: CreateEditStateOptions = {}): EditState {
81
- return {
82
- draft: { ...draft, tools: draft.tools ? [...draft.tools] : undefined, mcpDirectTools: draft.mcpDirectTools ? [...draft.mcpDirectTools] : undefined, skills: draft.skills ? [...draft.skills] : undefined, fallbackModels: draft.fallbackModels ? [...draft.fallbackModels] : undefined, extensions: draft.extensions ? [...draft.extensions] : draft.extensions, defaultReads: draft.defaultReads ? [...draft.defaultReads] : undefined, extraFields: draft.extraFields ? { ...draft.extraFields } : undefined },
83
- isNew, fieldIndex: 0, fieldMode: null, fieldEditor: createEditorState(), promptEditor: createEditorState(draft.systemPrompt ?? ""),
84
- modelSearchQuery: "", modelCursor: 0, filteredModels: [...models], thinkingCursor: 0, skillSearchQuery: "", skillCursor: 0, filteredSkills: [...skills], skillSelected: new Set(draft.skills ?? []),
85
- fields: options.fields ?? [...FIELD_ORDER], title: options.title, overrideBase: options.overrideBase,
86
- };
87
- }
88
-
89
- function renderFieldValue(field: EditField, state: EditState): string {
90
- const draft = state.draft;
91
- switch (field) {
92
- case "name": return draft.name;
93
- case "description": return draft.description;
94
- case "model": return draft.model ?? "default";
95
- case "fallbackModels": return draft.fallbackModels && draft.fallbackModels.length > 0 ? draft.fallbackModels.join(", ") : "";
96
- case "thinking": return draft.thinking ?? "off";
97
- case "systemPromptMode": return draft.systemPromptMode ?? defaultSystemPromptMode(draft.name);
98
- case "inheritProjectContext": return draft.inheritProjectContext ? "on" : "off";
99
- case "inheritSkills": return draft.inheritSkills ? "on" : "off";
100
- case "disabled": return draft.disabled ? "on" : "off";
101
- case "tools": return formatTools(draft);
102
- case "extensions": return draft.extensions !== undefined ? (draft.extensions.length > 0 ? draft.extensions.join(", ") : "") : "(all)";
103
- case "skills": return draft.skills && draft.skills.length > 0 ? draft.skills.join(", ") : "";
104
- case "output": return draft.output ?? "";
105
- case "reads": return draft.defaultReads && draft.defaultReads.length > 0 ? draft.defaultReads.join(", ") : "";
106
- case "progress": return draft.defaultProgress ? "on" : "off";
107
- case "interactive": return draft.interactive ? "on" : "off";
108
- default: return "";
109
- }
110
- }
111
-
112
- function applyFieldValue(field: EditField, state: EditState, value: string): void {
113
- const draft = state.draft;
114
- switch (field) {
115
- case "name": draft.name = value.trim(); break;
116
- case "description": draft.description = value.trim(); break;
117
- case "model": draft.model = value.trim() || undefined; break;
118
- case "fallbackModels": draft.fallbackModels = parseCommaList(value); break;
119
- case "systemPromptMode": {
120
- const trimmed = value.trim();
121
- if (trimmed === "") {
122
- draft.systemPromptMode = defaultSystemPromptMode(draft.name);
123
- break;
124
- }
125
- if (trimmed === "append" || trimmed === "replace") {
126
- draft.systemPromptMode = trimmed;
127
- }
128
- break;
129
- }
130
- case "tools": { const parsed = parseTools(value); draft.tools = parsed.tools; draft.mcpDirectTools = parsed.mcp; break; }
131
- case "extensions": { const trimmed = value.trim(); draft.extensions = trimmed === "(all)" ? undefined : parseCommaList(trimmed) ?? []; break; }
132
- case "skills": draft.skills = parseCommaList(value); break;
133
- case "output": { const trimmed = value.trim(); draft.output = trimmed.length > 0 ? trimmed : undefined; break; }
134
- case "reads": draft.defaultReads = parseCommaList(value); break;
135
- case "inheritProjectContext":
136
- case "inheritSkills":
137
- case "disabled":
138
- case "progress":
139
- case "interactive":
140
- case "prompt":
141
- break;
142
- }
143
- }
144
-
145
- function openModelPicker(state: EditState, models: ModelInfo[]): void {
146
- state.fieldIndex = state.fields.indexOf("model"); state.fieldMode = "model"; state.modelSearchQuery = ""; state.filteredModels = [...models];
147
- const idx = state.filteredModels.findIndex((m) => m.fullId === state.draft.model || m.id === state.draft.model); state.modelCursor = idx >= 0 ? idx : 0;
148
- }
149
- function openThinkingPicker(state: EditState): void {
150
- state.fieldIndex = state.fields.indexOf("thinking"); state.fieldMode = "thinking";
151
- const idx = THINKING_LEVELS.indexOf((state.draft.thinking ?? "off") as ThinkingLevel); state.thinkingCursor = idx >= 0 ? idx : 0;
152
- }
153
- function openSkillPicker(state: EditState, skills: SkillInfo[]): void {
154
- state.fieldIndex = state.fields.indexOf("skills"); state.fieldMode = "skills"; state.skillSearchQuery = ""; state.filteredSkills = [...skills]; state.skillSelected = new Set(state.draft.skills ?? []); state.skillCursor = 0;
155
- }
156
-
157
- function renderModelPicker(state: EditState, width: number, theme: Theme): string[] {
158
- const lines: string[] = [];
159
- lines.push(renderHeader(" Select Model ", width, theme));
160
- lines.push(row("", width, theme));
161
- const cursor = "\x1b[7m \x1b[27m";
162
- lines.push(row(` ${theme.fg("dim", "Search: ")}${state.modelSearchQuery}${cursor}`, width, theme));
163
- lines.push(row("", width, theme));
164
- const currentModel = state.draft.model ?? "default";
165
- lines.push(row(` ${theme.fg("dim", "Current: ")}${theme.fg("warning", currentModel)}`, width, theme));
166
- lines.push(row("", width, theme));
167
- const list = state.filteredModels;
168
- if (list.length === 0) {
169
- lines.push(row(` ${theme.fg("dim", "No matching models")}`, width, theme));
170
- } else {
171
- const maxVisible = MODEL_SELECTOR_HEIGHT; let startIdx = 0;
172
- if (list.length > maxVisible) { startIdx = Math.max(0, state.modelCursor - Math.floor(maxVisible / 2)); startIdx = Math.min(startIdx, list.length - maxVisible); }
173
- const endIdx = Math.min(startIdx + maxVisible, list.length);
174
- if (startIdx > 0) lines.push(row(` ${theme.fg("dim", ` ↑ ${startIdx} more`)}`, width, theme));
175
- for (let i = startIdx; i < endIdx; i++) { const model = list[i]!; const isSelected = i === state.modelCursor; const prefix = isSelected ? theme.fg("accent", "→ ") : " "; const modelText = isSelected ? theme.fg("accent", model.id) : model.id; const provider = theme.fg("dim", ` [${model.provider}]`); lines.push(row(` ${prefix}${modelText}${provider}`, width, theme)); }
176
- const remaining = list.length - endIdx; if (remaining > 0) lines.push(row(` ${theme.fg("dim", ` ↓ ${remaining} more`)}`, width, theme));
177
- }
178
- while (lines.length < 19) lines.push(row("", width, theme));
179
- lines.push(renderFooter(" [enter] select [esc] cancel type to search ", width, theme));
180
- return lines;
181
- }
182
-
183
- function renderThinkingPicker(state: EditState, width: number, theme: Theme): string[] {
184
- const lines: string[] = [];
185
- lines.push(renderHeader(" Select Thinking Level ", width, theme));
186
- lines.push(row("", width, theme));
187
- const currentModel = state.draft.model ?? "default";
188
- const current = truncateToWidth(currentModel, width - 13);
189
- lines.push(row(` ${theme.fg("dim", "Model: ")}${theme.fg("warning", current)}`, width, theme));
190
- lines.push(row("", width, theme));
191
- const descriptions: Record<ThinkingLevel, string> = {
192
- off: "No extended thinking",
193
- minimal: "Brief reasoning",
194
- low: "Light reasoning",
195
- medium: "Moderate reasoning",
196
- high: "Deep reasoning",
197
- xhigh: "Maximum reasoning (ultrathink)",
198
- };
199
- for (let i = 0; i < THINKING_LEVELS.length; i++) {
200
- const level = THINKING_LEVELS[i]!;
201
- const isSelected = i === state.thinkingCursor;
202
- const prefix = isSelected ? theme.fg("accent", "→ ") : " ";
203
- const levelText = isSelected ? theme.fg("accent", level) : level;
204
- const desc = theme.fg("dim", ` - ${descriptions[level]}`);
205
- lines.push(row(` ${prefix}${levelText}${desc}`, width, theme));
206
- }
207
- while (lines.length < 19) lines.push(row("", width, theme));
208
- lines.push(renderFooter(" [enter] select [esc] cancel [↑↓] navigate ", width, theme));
209
- return lines;
210
- }
211
-
212
- function renderSkillPicker(state: EditState, width: number, theme: Theme): string[] {
213
- const lines: string[] = [];
214
- lines.push(renderHeader(" Select Skills ", width, theme));
215
- lines.push(row("", width, theme));
216
- const cursor = "\x1b[7m \x1b[27m";
217
- lines.push(row(` ${theme.fg("dim", "Search: ")}${state.skillSearchQuery}${cursor}`, width, theme));
218
- lines.push(row("", width, theme));
219
- const selected = [...state.skillSelected].join(", ") || theme.fg("dim", "(none)");
220
- lines.push(row(` ${theme.fg("dim", "Selected: ")}${truncateToWidth(selected, width - 14)}`, width, theme));
221
- lines.push(row("", width, theme));
222
- const list = state.filteredSkills;
223
- if (list.length === 0) {
224
- lines.push(row(` ${theme.fg("dim", "No matching skills")}`, width, theme));
225
- } else {
226
- let startIdx = 0;
227
- if (list.length > SKILL_SELECTOR_HEIGHT) { startIdx = Math.max(0, state.skillCursor - Math.floor(SKILL_SELECTOR_HEIGHT / 2)); startIdx = Math.min(startIdx, list.length - SKILL_SELECTOR_HEIGHT); }
228
- const endIdx = Math.min(startIdx + SKILL_SELECTOR_HEIGHT, list.length);
229
- if (startIdx > 0) lines.push(row(` ${theme.fg("dim", ` ↑ ${startIdx} more`)}`, width, theme));
230
- for (let i = startIdx; i < endIdx; i++) { const skill = list[i]!; const isCursor = i === state.skillCursor; const isSelected = state.skillSelected.has(skill.name); const prefix = isCursor ? theme.fg("accent", "→ ") : " "; const checkbox = isSelected ? theme.fg("success", "[x]") : "[ ]"; const nameText = isCursor ? theme.fg("accent", skill.name) : skill.name; const sourceBadge = theme.fg("dim", ` [${skill.source}]`); const desc = skill.description ? theme.fg("dim", ` - ${truncateToWidth(skill.description, 25)}`) : ""; lines.push(row(` ${prefix}${checkbox} ${nameText}${sourceBadge}${desc}`, width, theme)); }
231
- const remaining = list.length - endIdx; if (remaining > 0) lines.push(row(` ${theme.fg("dim", ` ↓ ${remaining} more`)}`, width, theme));
232
- }
233
- while (lines.length < 19) lines.push(row("", width, theme));
234
- lines.push(renderFooter(" [enter] confirm [space] toggle [esc] cancel ", width, theme));
235
- return lines;
236
- }
237
-
238
- function renderPromptEditor(state: EditState, width: number, theme: Theme): string[] {
239
- const lines: string[] = [];
240
- lines.push(renderHeader(" Editing System Prompt ", width, theme));
241
- lines.push(row("", width, theme));
242
- const textWidth = Math.max(10, width - 4);
243
- const wrapped = wrapText(state.promptEditor.buffer, textWidth);
244
- const cursorPos = getCursorDisplayPos(state.promptEditor.cursor, wrapped.starts);
245
- state.promptEditor.viewportOffset = ensureCursorVisible(cursorPos.line, PROMPT_VIEWPORT_HEIGHT, state.promptEditor.viewportOffset);
246
- const editorLines = renderEditor(state.promptEditor, textWidth, PROMPT_VIEWPORT_HEIGHT);
247
- for (const line of editorLines) lines.push(row(` ${line}`, width, theme));
248
- const scrollInfo = formatScrollInfo(state.promptEditor.viewportOffset, Math.max(0, wrapped.lines.length - state.promptEditor.viewportOffset - PROMPT_VIEWPORT_HEIGHT));
249
- lines.push(row(scrollInfo ? ` ${theme.fg("dim", scrollInfo)}` : "", width, theme));
250
- lines.push(renderFooter(" [esc] done ", width, theme));
251
- return lines;
252
- }
253
-
254
- export function handleEditInput(screen: EditScreen, state: EditState, data: string, width: number, models: ModelInfo[], skills: SkillInfo[]): EditInputResult | undefined {
255
- if (screen === "edit") {
256
- if (matchesKey(data, "ctrl+s")) return { action: "save" };
257
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) return { action: "discard" };
258
- if (data === "D" && state.overrideBase) return { action: "delete" };
259
- if (matchesKey(data, "up")) { state.fieldIndex = Math.max(0, state.fieldIndex - 1); return; }
260
- if (matchesKey(data, "down")) { state.fieldIndex = Math.min(state.fields.length - 1, state.fieldIndex + 1); return; }
261
- const field = state.fields[state.fieldIndex]!;
262
- if (data === "r" && state.overrideBase) { resetFieldToBase(field, state); return; }
263
- if (data === "m") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
264
- if (data === "t") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
265
- if (data === "s") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
266
- if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "disabled" || field === "progress" || field === "interactive")) {
267
- if (field === "inheritProjectContext") state.draft.inheritProjectContext = !state.draft.inheritProjectContext;
268
- if (field === "inheritSkills") state.draft.inheritSkills = !state.draft.inheritSkills;
269
- if (field === "disabled") state.draft.disabled = !state.draft.disabled;
270
- if (field === "progress") state.draft.defaultProgress = !state.draft.defaultProgress;
271
- if (field === "interactive") state.draft.interactive = !state.draft.interactive;
272
- return;
273
- }
274
- if (matchesKey(data, "return")) {
275
- if (field === "model") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
276
- if (field === "thinking") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
277
- if (field === "skills") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
278
- if (field === "prompt") { state.promptEditor = createEditorState(state.draft.systemPrompt ?? ""); return { nextScreen: "edit-prompt" }; }
279
- if (field === "inheritProjectContext" || field === "inheritSkills" || field === "disabled" || field === "progress" || field === "interactive") return;
280
- state.fieldMode = "text"; state.fieldEditor = createEditorState(renderFieldValue(field, state)); return { nextScreen: "edit-field" };
281
- }
282
- return;
283
- }
284
- if (screen === "edit-field") {
285
- if (state.fieldMode === "model") {
286
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
287
- if (matchesKey(data, "return")) { const selected = state.filteredModels[state.modelCursor]; if (selected) state.draft.model = selected.fullId; state.fieldMode = null; return { nextScreen: "edit" }; }
288
- if (matchesKey(data, "up")) { if (state.filteredModels.length > 0) state.modelCursor = state.modelCursor === 0 ? state.filteredModels.length - 1 : state.modelCursor - 1; return; }
289
- if (matchesKey(data, "down")) { if (state.filteredModels.length > 0) state.modelCursor = state.modelCursor === state.filteredModels.length - 1 ? 0 : state.modelCursor + 1; return; }
290
- if (matchesKey(data, "backspace")) { if (state.modelSearchQuery.length > 0) state.modelSearchQuery = state.modelSearchQuery.slice(0, -1); }
291
- else if (data.length === 1 && data.charCodeAt(0) >= 32) state.modelSearchQuery += data;
292
- const query = state.modelSearchQuery.toLowerCase();
293
- state.filteredModels = query ? models.filter((m) => m.fullId.toLowerCase().includes(query) || m.id.toLowerCase().includes(query) || m.provider.toLowerCase().includes(query)) : [...models];
294
- state.modelCursor = Math.min(state.modelCursor, Math.max(0, state.filteredModels.length - 1));
295
- return;
296
- }
297
- if (state.fieldMode === "thinking") {
298
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
299
- if (matchesKey(data, "return")) { const selected = THINKING_LEVELS[state.thinkingCursor]; state.draft.thinking = selected === "off" ? undefined : selected; state.fieldMode = null; return { nextScreen: "edit" }; }
300
- if (matchesKey(data, "up")) { state.thinkingCursor = state.thinkingCursor === 0 ? THINKING_LEVELS.length - 1 : state.thinkingCursor - 1; return; }
301
- if (matchesKey(data, "down")) { state.thinkingCursor = state.thinkingCursor === THINKING_LEVELS.length - 1 ? 0 : state.thinkingCursor + 1; return; }
302
- return;
303
- }
304
- if (state.fieldMode === "skills") {
305
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
306
- if (matchesKey(data, "return")) { const selected = [...state.skillSelected]; state.draft.skills = selected.length > 0 ? selected : undefined; state.fieldMode = null; return { nextScreen: "edit" }; }
307
- if (data === " ") { const skill = state.filteredSkills[state.skillCursor]; if (skill) { if (state.skillSelected.has(skill.name)) state.skillSelected.delete(skill.name); else state.skillSelected.add(skill.name); } return; }
308
- if (matchesKey(data, "up")) { if (state.filteredSkills.length > 0) state.skillCursor = state.skillCursor === 0 ? state.filteredSkills.length - 1 : state.skillCursor - 1; return; }
309
- if (matchesKey(data, "down")) { if (state.filteredSkills.length > 0) state.skillCursor = state.skillCursor === state.filteredSkills.length - 1 ? 0 : state.skillCursor + 1; return; }
310
- if (matchesKey(data, "backspace")) { if (state.skillSearchQuery.length > 0) state.skillSearchQuery = state.skillSearchQuery.slice(0, -1); }
311
- else if (data.length === 1 && data.charCodeAt(0) >= 32) state.skillSearchQuery += data;
312
- const query = state.skillSearchQuery.toLowerCase();
313
- state.filteredSkills = query ? skills.filter((s) => s.name.toLowerCase().includes(query) || (s.description?.toLowerCase().includes(query) ?? false)) : [...skills];
314
- state.skillCursor = Math.min(state.skillCursor, Math.max(0, state.filteredSkills.length - 1));
315
- return;
316
- }
317
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.fieldMode = null; return { nextScreen: "edit" }; }
318
- if (matchesKey(data, "return")) { const field = state.fields[state.fieldIndex]!; applyFieldValue(field, state, state.fieldEditor.buffer); state.fieldMode = null; return { nextScreen: "edit" }; }
319
- if (matchesKey(data, "tab")) return;
320
- const innerW = width - 2; const labelWidth = 12; const textWidth = Math.max(10, innerW - labelWidth - 6);
321
- const nextState = handleEditorInput(state.fieldEditor, data, textWidth); if (nextState) state.fieldEditor = nextState; return;
322
- }
323
- if (screen === "edit-prompt") {
324
- if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) { state.draft.systemPrompt = state.promptEditor.buffer; return { nextScreen: "edit" }; }
325
- const textWidth = Math.max(10, width - 4);
326
- if (matchesKey(data, "pageup") || matchesKey(data, "shift+up")) { const wrapped = wrapText(state.promptEditor.buffer, textWidth); const cursorPos = getCursorDisplayPos(state.promptEditor.cursor, wrapped.starts); const targetLine = Math.max(0, cursorPos.line - PROMPT_VIEWPORT_HEIGHT); const targetCol = Math.min(cursorPos.col, wrapped.lines[targetLine]?.length ?? 0); state.promptEditor = { ...state.promptEditor, cursor: wrapped.starts[targetLine] + targetCol }; return; }
327
- if (matchesKey(data, "pagedown") || matchesKey(data, "shift+down")) { const wrapped = wrapText(state.promptEditor.buffer, textWidth); const cursorPos = getCursorDisplayPos(state.promptEditor.cursor, wrapped.starts); const targetLine = Math.min(wrapped.lines.length - 1, cursorPos.line + PROMPT_VIEWPORT_HEIGHT); const targetCol = Math.min(cursorPos.col, wrapped.lines[targetLine]?.length ?? 0); state.promptEditor = { ...state.promptEditor, cursor: wrapped.starts[targetLine] + targetCol }; return; }
328
- const nextState = handleEditorInput(state.promptEditor, data, textWidth, { multiLine: true }); if (nextState) state.promptEditor = nextState; return;
329
- }
330
- return;
331
- }
332
-
333
- export function renderEdit(screen: EditScreen, state: EditState, width: number, theme: Theme): string[] {
334
- if (screen === "edit-field" && state.fieldMode === "model") return renderModelPicker(state, width, theme);
335
- if (screen === "edit-field" && state.fieldMode === "thinking") return renderThinkingPicker(state, width, theme);
336
- if (screen === "edit-field" && state.fieldMode === "skills") return renderSkillPicker(state, width, theme);
337
- if (screen === "edit-prompt") return renderPromptEditor(state, width, theme);
338
- const lines: string[] = [];
339
- const scopeBadge = state.draft.source === "user" ? "[user]" : "[proj]"; const label = state.isNew ? " [new]" : "";
340
- lines.push(renderHeader(` ${state.title ?? `Editing: ${state.draft.name} ${scopeBadge}${label}`} `, width, theme));
341
- lines.push(row("", width, theme));
342
- const innerW = width - 2; const labelWidth = 12; const valueWidth = Math.max(10, innerW - labelWidth - 6);
343
- for (let i = 0; i < state.fields.length; i++) {
344
- const field = state.fields[i]!; if (field === "prompt") break;
345
- const isFocused = i === state.fieldIndex; const prefix = isFocused ? theme.fg("accent", "▸ ") : " ";
346
- const fieldLabel = field === "systemPromptMode"
347
- ? "Prompt Mode"
348
- : field === "inheritProjectContext"
349
- ? "Project Ctx"
350
- : field === "inheritSkills"
351
- ? "Skills Ctx"
352
- : field === "disabled"
353
- ? "Disabled"
354
- : `${field[0]!.toUpperCase()}${field.slice(1)}`;
355
- const rawLabel = pad(`${fieldLabel}:`, labelWidth);
356
- const labelText = state.overrideBase && !fieldValueMatchesBase(field, state) ? theme.fg("accent", rawLabel) : rawLabel; let valueText = renderFieldValue(field, state);
357
- if (field === "inheritProjectContext") { const toggle = state.draft.inheritProjectContext ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.inheritProjectContext ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
358
- if (field === "inheritSkills") { const toggle = state.draft.inheritSkills ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.inheritSkills ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
359
- if (field === "disabled") { const toggle = state.draft.disabled ? theme.fg("warning", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.disabled ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
360
- if (field === "progress") { const toggle = state.draft.defaultProgress ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.defaultProgress ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
361
- if (field === "interactive") { const toggle = state.draft.interactive ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.interactive ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
362
- let displayValue = truncateToWidth(valueText, valueWidth);
363
- if (screen === "edit-field" && state.fieldMode === "text" && isFocused) {
364
- const { starts } = wrapText(state.fieldEditor.buffer, valueWidth);
365
- const pos = getCursorDisplayPos(state.fieldEditor.cursor, starts);
366
- state.fieldEditor.viewportOffset = ensureCursorVisible(pos.line, 1, state.fieldEditor.viewportOffset);
367
- const editorLine = renderEditor(state.fieldEditor, valueWidth, 1)[0] ?? "";
368
- displayValue = pad(editorLine, valueWidth);
369
- }
370
- lines.push(row(` ${prefix}${labelText} [${displayValue}]`, width, theme));
371
- }
372
- if (state.fields.includes("prompt")) {
373
- lines.push(row("", width, theme));
374
- const promptIndex = state.fields.indexOf("prompt");
375
- const promptFocused = state.fieldIndex === promptIndex;
376
- const promptPrefix = promptFocused ? theme.fg("accent", "▸ ") : " ";
377
- const promptTitle = state.overrideBase && !fieldValueMatchesBase("prompt", state)
378
- ? theme.fg("accent", "── System Prompt ──")
379
- : theme.fg("dim", "── System Prompt ──");
380
- lines.push(row(` ${promptPrefix}${promptTitle}`, width, theme));
381
- const previewWidth = innerW - 2; const wrapped = wrapText(state.draft.systemPrompt ?? "", previewWidth); const previewLines = wrapped.lines.slice(0, 4);
382
- for (const line of previewLines) lines.push(row(` ${line}`, width, theme));
383
- for (let i = previewLines.length; i < 4; i++) lines.push(row("", width, theme));
384
- }
385
- if (state.error) lines.push(row(` ${theme.fg("error", state.error)}`, width, theme)); else lines.push(row("", width, theme));
386
- const footer = state.overrideBase
387
- ? " [ctrl+s] save [r] reset field [D] remove override [esc] back "
388
- : " [ctrl+s] save [esc] back ";
389
- lines.push(renderFooter(footer, width, theme));
390
- return lines;
391
- }