indusagi-coding-agent 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.
- package/CHANGELOG.md +2249 -0
- package/README.md +546 -0
- package/dist/cli/args.js +282 -0
- package/dist/cli/config-selector.js +30 -0
- package/dist/cli/file-processor.js +78 -0
- package/dist/cli/list-models.js +91 -0
- package/dist/cli/session-picker.js +31 -0
- package/dist/cli.js +10 -0
- package/dist/config.js +158 -0
- package/dist/core/agent-session.js +2097 -0
- package/dist/core/auth-storage.js +278 -0
- package/dist/core/bash-executor.js +211 -0
- package/dist/core/compaction/branch-summarization.js +241 -0
- package/dist/core/compaction/compaction.js +606 -0
- package/dist/core/compaction/index.js +6 -0
- package/dist/core/compaction/utils.js +137 -0
- package/dist/core/diagnostics.js +1 -0
- package/dist/core/event-bus.js +24 -0
- package/dist/core/exec.js +70 -0
- package/dist/core/export-html/ansi-to-html.js +248 -0
- package/dist/core/export-html/index.js +221 -0
- package/dist/core/export-html/template.css +905 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1549 -0
- package/dist/core/export-html/tool-renderer.js +56 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/extensions/index.js +8 -0
- package/dist/core/extensions/loader.js +395 -0
- package/dist/core/extensions/runner.js +499 -0
- package/dist/core/extensions/types.js +31 -0
- package/dist/core/extensions/wrapper.js +101 -0
- package/dist/core/footer-data-provider.js +133 -0
- package/dist/core/index.js +8 -0
- package/dist/core/keybindings.js +140 -0
- package/dist/core/messages.js +122 -0
- package/dist/core/model-registry.js +454 -0
- package/dist/core/model-resolver.js +309 -0
- package/dist/core/package-manager.js +1142 -0
- package/dist/core/prompt-templates.js +250 -0
- package/dist/core/resource-loader.js +569 -0
- package/dist/core/sdk.js +225 -0
- package/dist/core/session-manager.js +1078 -0
- package/dist/core/settings-manager.js +430 -0
- package/dist/core/skills.js +339 -0
- package/dist/core/system-prompt.js +136 -0
- package/dist/core/timings.js +24 -0
- package/dist/core/tools/bash.js +226 -0
- package/dist/core/tools/edit-diff.js +242 -0
- package/dist/core/tools/edit.js +145 -0
- package/dist/core/tools/find.js +205 -0
- package/dist/core/tools/grep.js +238 -0
- package/dist/core/tools/index.js +60 -0
- package/dist/core/tools/ls.js +117 -0
- package/dist/core/tools/path-utils.js +52 -0
- package/dist/core/tools/read.js +165 -0
- package/dist/core/tools/truncate.js +204 -0
- package/dist/core/tools/write.js +77 -0
- package/dist/index.js +41 -0
- package/dist/main.js +565 -0
- package/dist/migrations.js +260 -0
- package/dist/modes/index.js +7 -0
- package/dist/modes/interactive/components/armin.js +328 -0
- package/dist/modes/interactive/components/assistant-message.js +86 -0
- package/dist/modes/interactive/components/bash-execution.js +155 -0
- package/dist/modes/interactive/components/bordered-loader.js +47 -0
- package/dist/modes/interactive/components/branch-summary-message.js +41 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +42 -0
- package/dist/modes/interactive/components/config-selector.js +458 -0
- package/dist/modes/interactive/components/countdown-timer.js +27 -0
- package/dist/modes/interactive/components/custom-editor.js +61 -0
- package/dist/modes/interactive/components/custom-message.js +80 -0
- package/dist/modes/interactive/components/diff.js +132 -0
- package/dist/modes/interactive/components/dynamic-border.js +19 -0
- package/dist/modes/interactive/components/extension-editor.js +96 -0
- package/dist/modes/interactive/components/extension-input.js +54 -0
- package/dist/modes/interactive/components/extension-selector.js +70 -0
- package/dist/modes/interactive/components/footer.js +213 -0
- package/dist/modes/interactive/components/index.js +31 -0
- package/dist/modes/interactive/components/keybinding-hints.js +60 -0
- package/dist/modes/interactive/components/login-dialog.js +138 -0
- package/dist/modes/interactive/components/model-selector.js +253 -0
- package/dist/modes/interactive/components/oauth-selector.js +91 -0
- package/dist/modes/interactive/components/scoped-models-selector.js +262 -0
- package/dist/modes/interactive/components/session-selector-search.js +145 -0
- package/dist/modes/interactive/components/session-selector.js +698 -0
- package/dist/modes/interactive/components/settings-selector.js +250 -0
- package/dist/modes/interactive/components/show-images-selector.js +33 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +44 -0
- package/dist/modes/interactive/components/theme-selector.js +43 -0
- package/dist/modes/interactive/components/thinking-selector.js +45 -0
- package/dist/modes/interactive/components/tool-execution.js +608 -0
- package/dist/modes/interactive/components/tree-selector.js +892 -0
- package/dist/modes/interactive/components/user-message-selector.js +109 -0
- package/dist/modes/interactive/components/user-message.js +15 -0
- package/dist/modes/interactive/components/visual-truncate.js +32 -0
- package/dist/modes/interactive/interactive-mode.js +3576 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.js +938 -0
- package/dist/modes/print-mode.js +96 -0
- package/dist/modes/rpc/rpc-client.js +390 -0
- package/dist/modes/rpc/rpc-mode.js +448 -0
- package/dist/modes/rpc/rpc-types.js +7 -0
- package/dist/utils/changelog.js +86 -0
- package/dist/utils/clipboard-image.js +116 -0
- package/dist/utils/clipboard.js +58 -0
- package/dist/utils/frontmatter.js +25 -0
- package/dist/utils/git.js +5 -0
- package/dist/utils/image-convert.js +34 -0
- package/dist/utils/image-resize.js +180 -0
- package/dist/utils/mime.js +25 -0
- package/dist/utils/photon.js +120 -0
- package/dist/utils/shell.js +164 -0
- package/dist/utils/sleep.js +16 -0
- package/dist/utils/tools-manager.js +186 -0
- package/docs/compaction.md +390 -0
- package/docs/custom-provider.md +538 -0
- package/docs/development.md +69 -0
- package/docs/extensions.md +1733 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +79 -0
- package/docs/keybindings.md +162 -0
- package/docs/models.md +193 -0
- package/docs/packages.md +163 -0
- package/docs/prompt-templates.md +67 -0
- package/docs/providers.md +147 -0
- package/docs/rpc.md +1048 -0
- package/docs/sdk.md +957 -0
- package/docs/session.md +412 -0
- package/docs/settings.md +216 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +226 -0
- package/docs/terminal-setup.md +65 -0
- package/docs/themes.md +295 -0
- package/docs/tree.md +219 -0
- package/docs/tui.md +887 -0
- package/docs/windows.md +17 -0
- package/examples/README.md +25 -0
- package/examples/extensions/README.md +192 -0
- package/examples/extensions/antigravity-image-gen.ts +414 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +115 -0
- package/examples/extensions/custom-footer.ts +65 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +605 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +350 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +83 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +133 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +105 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/handoff.ts +151 -0
- package/examples/extensions/hello.ts +25 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +60 -0
- package/examples/extensions/modal-editor.ts +86 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +25 -0
- package/examples/extensions/overlay-qa-tests.ts +882 -0
- package/examples/extensions/overlay-test.ts +151 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +65 -0
- package/examples/extensions/plan-mode/index.ts +341 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +399 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/qna.ts +120 -0
- package/examples/extensions/question.ts +265 -0
- package/examples/extensions/questionnaire.ts +428 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/sandbox/index.ts +318 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +344 -0
- package/examples/extensions/space-invaders.ts +561 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +40 -0
- package/examples/extensions/subagent/README.md +172 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +127 -0
- package/examples/extensions/subagent/index.ts +964 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +196 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/todo.ts +300 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +147 -0
- package/examples/extensions/trigger-compact.ts +40 -0
- package/examples/extensions/truncated-tool.ts +193 -0
- package/examples/extensions/widget-placement.ts +17 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +50 -0
- package/examples/sdk/03-custom-prompt.ts +55 -0
- package/examples/sdk/04-skills.ts +46 -0
- package/examples/sdk/05-tools.ts +56 -0
- package/examples/sdk/06-extensions.ts +88 -0
- package/examples/sdk/07-context-files.ts +40 -0
- package/examples/sdk/08-prompt-templates.ts +47 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +82 -0
- package/examples/sdk/13-codex-oauth.ts +37 -0
- package/examples/sdk/README.md +144 -0
- package/package.json +85 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI component for managing package resources (enable/disable)
|
|
3
|
+
*/
|
|
4
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
5
|
+
import { Container, getEditorKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "indusagi/tui";
|
|
6
|
+
import { CONFIG_DIR_NAME } from "../../../config.js";
|
|
7
|
+
import { theme } from "../theme/theme.js";
|
|
8
|
+
import { DynamicBorder } from "./dynamic-border.js";
|
|
9
|
+
import { rawKeyHint } from "./keybinding-hints.js";
|
|
10
|
+
const RESOURCE_TYPE_LABELS = {
|
|
11
|
+
extensions: "Extensions",
|
|
12
|
+
skills: "Skills",
|
|
13
|
+
prompts: "Prompts",
|
|
14
|
+
themes: "Themes",
|
|
15
|
+
};
|
|
16
|
+
function getGroupLabel(metadata) {
|
|
17
|
+
if (metadata.origin === "package") {
|
|
18
|
+
return `${metadata.source} (${metadata.scope})`;
|
|
19
|
+
}
|
|
20
|
+
// Top-level resources
|
|
21
|
+
if (metadata.source === "auto") {
|
|
22
|
+
return metadata.scope === "user" ? "User (~/.indusagi/agent/)" : "Project (.indusagi/)";
|
|
23
|
+
}
|
|
24
|
+
return metadata.scope === "user" ? "User settings" : "Project settings";
|
|
25
|
+
}
|
|
26
|
+
function buildGroups(resolved) {
|
|
27
|
+
const groupMap = new Map();
|
|
28
|
+
const addToGroup = (resources, resourceType) => {
|
|
29
|
+
for (const res of resources) {
|
|
30
|
+
const { path, enabled, metadata } = res;
|
|
31
|
+
const groupKey = `${metadata.origin}:${metadata.scope}:${metadata.source}`;
|
|
32
|
+
if (!groupMap.has(groupKey)) {
|
|
33
|
+
groupMap.set(groupKey, {
|
|
34
|
+
key: groupKey,
|
|
35
|
+
label: getGroupLabel(metadata),
|
|
36
|
+
scope: metadata.scope,
|
|
37
|
+
origin: metadata.origin,
|
|
38
|
+
source: metadata.source,
|
|
39
|
+
subgroups: [],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const group = groupMap.get(groupKey);
|
|
43
|
+
const subgroupKey = `${groupKey}:${resourceType}`;
|
|
44
|
+
let subgroup = group.subgroups.find((sg) => sg.type === resourceType);
|
|
45
|
+
if (!subgroup) {
|
|
46
|
+
subgroup = {
|
|
47
|
+
type: resourceType,
|
|
48
|
+
label: RESOURCE_TYPE_LABELS[resourceType],
|
|
49
|
+
items: [],
|
|
50
|
+
};
|
|
51
|
+
group.subgroups.push(subgroup);
|
|
52
|
+
}
|
|
53
|
+
const displayName = resourceType === "skills" && basename(path) === "SKILL.md" ? basename(dirname(path)) : basename(path);
|
|
54
|
+
subgroup.items.push({
|
|
55
|
+
path,
|
|
56
|
+
enabled,
|
|
57
|
+
metadata,
|
|
58
|
+
resourceType,
|
|
59
|
+
displayName,
|
|
60
|
+
groupKey,
|
|
61
|
+
subgroupKey,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
addToGroup(resolved.extensions, "extensions");
|
|
66
|
+
addToGroup(resolved.skills, "skills");
|
|
67
|
+
addToGroup(resolved.prompts, "prompts");
|
|
68
|
+
addToGroup(resolved.themes, "themes");
|
|
69
|
+
// Sort groups: packages first, then top-level; user before project
|
|
70
|
+
const groups = Array.from(groupMap.values());
|
|
71
|
+
groups.sort((a, b) => {
|
|
72
|
+
if (a.origin !== b.origin) {
|
|
73
|
+
return a.origin === "package" ? -1 : 1;
|
|
74
|
+
}
|
|
75
|
+
if (a.scope !== b.scope) {
|
|
76
|
+
return a.scope === "user" ? -1 : 1;
|
|
77
|
+
}
|
|
78
|
+
return a.source.localeCompare(b.source);
|
|
79
|
+
});
|
|
80
|
+
// Sort subgroups within each group by type order, and items by name
|
|
81
|
+
const typeOrder = { extensions: 0, skills: 1, prompts: 2, themes: 3 };
|
|
82
|
+
for (const group of groups) {
|
|
83
|
+
group.subgroups.sort((a, b) => typeOrder[a.type] - typeOrder[b.type]);
|
|
84
|
+
for (const subgroup of group.subgroups) {
|
|
85
|
+
subgroup.items.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return groups;
|
|
89
|
+
}
|
|
90
|
+
class ConfigSelectorHeader {
|
|
91
|
+
invalidate() { }
|
|
92
|
+
render(width) {
|
|
93
|
+
const title = theme.bold("Resource Configuration");
|
|
94
|
+
const sep = theme.fg("muted", " · ");
|
|
95
|
+
const hint = rawKeyHint("space", "toggle") + sep + rawKeyHint("esc", "close");
|
|
96
|
+
const hintWidth = visibleWidth(hint);
|
|
97
|
+
const titleWidth = visibleWidth(title);
|
|
98
|
+
const spacing = Math.max(1, width - titleWidth - hintWidth);
|
|
99
|
+
return [
|
|
100
|
+
truncateToWidth(`${title}${" ".repeat(spacing)}${hint}`, width, ""),
|
|
101
|
+
theme.fg("muted", "Type to filter resources"),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
class ResourceList {
|
|
106
|
+
get focused() {
|
|
107
|
+
return this._focused;
|
|
108
|
+
}
|
|
109
|
+
set focused(value) {
|
|
110
|
+
this._focused = value;
|
|
111
|
+
this.searchInput.focused = value;
|
|
112
|
+
}
|
|
113
|
+
constructor(groups, settingsManager, cwd, agentDir) {
|
|
114
|
+
this.flatItems = [];
|
|
115
|
+
this.filteredItems = [];
|
|
116
|
+
this.selectedIndex = 0;
|
|
117
|
+
this.maxVisible = 15;
|
|
118
|
+
this._focused = false;
|
|
119
|
+
this.groups = groups;
|
|
120
|
+
this.settingsManager = settingsManager;
|
|
121
|
+
this.cwd = cwd;
|
|
122
|
+
this.agentDir = agentDir;
|
|
123
|
+
this.searchInput = new Input();
|
|
124
|
+
this.buildFlatList();
|
|
125
|
+
this.filteredItems = [...this.flatItems];
|
|
126
|
+
}
|
|
127
|
+
buildFlatList() {
|
|
128
|
+
this.flatItems = [];
|
|
129
|
+
for (const group of this.groups) {
|
|
130
|
+
this.flatItems.push({ type: "group", group });
|
|
131
|
+
for (const subgroup of group.subgroups) {
|
|
132
|
+
this.flatItems.push({ type: "subgroup", subgroup, group });
|
|
133
|
+
for (const item of subgroup.items) {
|
|
134
|
+
this.flatItems.push({ type: "item", item });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Start selection on first item (not header)
|
|
139
|
+
this.selectedIndex = this.flatItems.findIndex((e) => e.type === "item");
|
|
140
|
+
if (this.selectedIndex < 0)
|
|
141
|
+
this.selectedIndex = 0;
|
|
142
|
+
}
|
|
143
|
+
findNextItem(fromIndex, direction) {
|
|
144
|
+
let idx = fromIndex + direction;
|
|
145
|
+
while (idx >= 0 && idx < this.filteredItems.length) {
|
|
146
|
+
if (this.filteredItems[idx].type === "item") {
|
|
147
|
+
return idx;
|
|
148
|
+
}
|
|
149
|
+
idx += direction;
|
|
150
|
+
}
|
|
151
|
+
return fromIndex; // Stay at current if no item found
|
|
152
|
+
}
|
|
153
|
+
filterItems(query) {
|
|
154
|
+
if (!query.trim()) {
|
|
155
|
+
this.filteredItems = [...this.flatItems];
|
|
156
|
+
this.selectFirstItem();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const lowerQuery = query.toLowerCase();
|
|
160
|
+
const matchingItems = new Set();
|
|
161
|
+
const matchingSubgroups = new Set();
|
|
162
|
+
const matchingGroups = new Set();
|
|
163
|
+
for (const entry of this.flatItems) {
|
|
164
|
+
if (entry.type === "item") {
|
|
165
|
+
const item = entry.item;
|
|
166
|
+
if (item.displayName.toLowerCase().includes(lowerQuery) ||
|
|
167
|
+
item.resourceType.toLowerCase().includes(lowerQuery) ||
|
|
168
|
+
item.path.toLowerCase().includes(lowerQuery)) {
|
|
169
|
+
matchingItems.add(item);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Find which subgroups and groups contain matching items
|
|
174
|
+
for (const group of this.groups) {
|
|
175
|
+
for (const subgroup of group.subgroups) {
|
|
176
|
+
for (const item of subgroup.items) {
|
|
177
|
+
if (matchingItems.has(item)) {
|
|
178
|
+
matchingSubgroups.add(subgroup);
|
|
179
|
+
matchingGroups.add(group);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
this.filteredItems = [];
|
|
185
|
+
for (const entry of this.flatItems) {
|
|
186
|
+
if (entry.type === "group" && matchingGroups.has(entry.group)) {
|
|
187
|
+
this.filteredItems.push(entry);
|
|
188
|
+
}
|
|
189
|
+
else if (entry.type === "subgroup" && matchingSubgroups.has(entry.subgroup)) {
|
|
190
|
+
this.filteredItems.push(entry);
|
|
191
|
+
}
|
|
192
|
+
else if (entry.type === "item" && matchingItems.has(entry.item)) {
|
|
193
|
+
this.filteredItems.push(entry);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
this.selectFirstItem();
|
|
197
|
+
}
|
|
198
|
+
selectFirstItem() {
|
|
199
|
+
const firstItemIndex = this.filteredItems.findIndex((e) => e.type === "item");
|
|
200
|
+
this.selectedIndex = firstItemIndex >= 0 ? firstItemIndex : 0;
|
|
201
|
+
}
|
|
202
|
+
updateItem(item, enabled) {
|
|
203
|
+
item.enabled = enabled;
|
|
204
|
+
// Update in groups too
|
|
205
|
+
for (const group of this.groups) {
|
|
206
|
+
for (const subgroup of group.subgroups) {
|
|
207
|
+
const found = subgroup.items.find((i) => i.path === item.path && i.resourceType === item.resourceType);
|
|
208
|
+
if (found) {
|
|
209
|
+
found.enabled = enabled;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
invalidate() { }
|
|
216
|
+
render(width) {
|
|
217
|
+
const lines = [];
|
|
218
|
+
// Search input
|
|
219
|
+
lines.push(...this.searchInput.render(width));
|
|
220
|
+
lines.push("");
|
|
221
|
+
if (this.filteredItems.length === 0) {
|
|
222
|
+
lines.push(theme.fg("muted", " No resources found"));
|
|
223
|
+
return lines;
|
|
224
|
+
}
|
|
225
|
+
// Calculate visible range
|
|
226
|
+
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible));
|
|
227
|
+
const endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);
|
|
228
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
229
|
+
const entry = this.filteredItems[i];
|
|
230
|
+
const isSelected = i === this.selectedIndex;
|
|
231
|
+
if (entry.type === "group") {
|
|
232
|
+
// Main group header (no cursor)
|
|
233
|
+
const groupLine = theme.fg("accent", theme.bold(entry.group.label));
|
|
234
|
+
lines.push(truncateToWidth(` ${groupLine}`, width, ""));
|
|
235
|
+
}
|
|
236
|
+
else if (entry.type === "subgroup") {
|
|
237
|
+
// Subgroup header (indented, no cursor)
|
|
238
|
+
const subgroupLine = theme.fg("muted", entry.subgroup.label);
|
|
239
|
+
lines.push(truncateToWidth(` ${subgroupLine}`, width, ""));
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Resource item (cursor only on items)
|
|
243
|
+
const item = entry.item;
|
|
244
|
+
const cursor = isSelected ? "> " : " ";
|
|
245
|
+
const checkbox = item.enabled ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
|
|
246
|
+
const name = isSelected ? theme.bold(item.displayName) : item.displayName;
|
|
247
|
+
lines.push(truncateToWidth(`${cursor} ${checkbox} ${name}`, width, "..."));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Scroll indicator
|
|
251
|
+
if (startIndex > 0 || endIndex < this.filteredItems.length) {
|
|
252
|
+
lines.push(theme.fg("dim", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`));
|
|
253
|
+
}
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
handleInput(data) {
|
|
257
|
+
const kb = getEditorKeybindings();
|
|
258
|
+
if (kb.matches(data, "selectUp")) {
|
|
259
|
+
this.selectedIndex = this.findNextItem(this.selectedIndex, -1);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (kb.matches(data, "selectDown")) {
|
|
263
|
+
this.selectedIndex = this.findNextItem(this.selectedIndex, 1);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (kb.matches(data, "selectPageUp")) {
|
|
267
|
+
// Jump up by maxVisible, then find nearest item
|
|
268
|
+
let target = Math.max(0, this.selectedIndex - this.maxVisible);
|
|
269
|
+
while (target < this.filteredItems.length && this.filteredItems[target].type !== "item") {
|
|
270
|
+
target++;
|
|
271
|
+
}
|
|
272
|
+
if (target < this.filteredItems.length) {
|
|
273
|
+
this.selectedIndex = target;
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (kb.matches(data, "selectPageDown")) {
|
|
278
|
+
// Jump down by maxVisible, then find nearest item
|
|
279
|
+
let target = Math.min(this.filteredItems.length - 1, this.selectedIndex + this.maxVisible);
|
|
280
|
+
while (target >= 0 && this.filteredItems[target].type !== "item") {
|
|
281
|
+
target--;
|
|
282
|
+
}
|
|
283
|
+
if (target >= 0) {
|
|
284
|
+
this.selectedIndex = target;
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (kb.matches(data, "selectCancel")) {
|
|
289
|
+
this.onCancel?.();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
293
|
+
this.onExit?.();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (data === " " || kb.matches(data, "selectConfirm")) {
|
|
297
|
+
const entry = this.filteredItems[this.selectedIndex];
|
|
298
|
+
if (entry?.type === "item") {
|
|
299
|
+
const newEnabled = !entry.item.enabled;
|
|
300
|
+
this.toggleResource(entry.item, newEnabled);
|
|
301
|
+
this.updateItem(entry.item, newEnabled);
|
|
302
|
+
this.onToggle?.(entry.item, newEnabled);
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// Pass to search input
|
|
307
|
+
this.searchInput.handleInput(data);
|
|
308
|
+
this.filterItems(this.searchInput.getValue());
|
|
309
|
+
}
|
|
310
|
+
toggleResource(item, enabled) {
|
|
311
|
+
if (item.metadata.origin === "top-level") {
|
|
312
|
+
this.toggleTopLevelResource(item, enabled);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
this.togglePackageResource(item, enabled);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
toggleTopLevelResource(item, enabled) {
|
|
319
|
+
const scope = item.metadata.scope;
|
|
320
|
+
const settings = scope === "project" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();
|
|
321
|
+
const arrayKey = item.resourceType;
|
|
322
|
+
const current = (settings[arrayKey] ?? []);
|
|
323
|
+
// Generate pattern for this resource
|
|
324
|
+
const pattern = this.getResourcePattern(item);
|
|
325
|
+
const disablePattern = `-${pattern}`;
|
|
326
|
+
const enablePattern = `+${pattern}`;
|
|
327
|
+
// Filter out existing patterns for this resource
|
|
328
|
+
const updated = current.filter((p) => {
|
|
329
|
+
const stripped = p.startsWith("!") || p.startsWith("+") || p.startsWith("-") ? p.slice(1) : p;
|
|
330
|
+
return stripped !== pattern;
|
|
331
|
+
});
|
|
332
|
+
if (enabled) {
|
|
333
|
+
updated.push(enablePattern);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
updated.push(disablePattern);
|
|
337
|
+
}
|
|
338
|
+
if (scope === "project") {
|
|
339
|
+
if (arrayKey === "extensions") {
|
|
340
|
+
this.settingsManager.setProjectExtensionPaths(updated);
|
|
341
|
+
}
|
|
342
|
+
else if (arrayKey === "skills") {
|
|
343
|
+
this.settingsManager.setProjectSkillPaths(updated);
|
|
344
|
+
}
|
|
345
|
+
else if (arrayKey === "prompts") {
|
|
346
|
+
this.settingsManager.setProjectPromptTemplatePaths(updated);
|
|
347
|
+
}
|
|
348
|
+
else if (arrayKey === "themes") {
|
|
349
|
+
this.settingsManager.setProjectThemePaths(updated);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
if (arrayKey === "extensions") {
|
|
354
|
+
this.settingsManager.setExtensionPaths(updated);
|
|
355
|
+
}
|
|
356
|
+
else if (arrayKey === "skills") {
|
|
357
|
+
this.settingsManager.setSkillPaths(updated);
|
|
358
|
+
}
|
|
359
|
+
else if (arrayKey === "prompts") {
|
|
360
|
+
this.settingsManager.setPromptTemplatePaths(updated);
|
|
361
|
+
}
|
|
362
|
+
else if (arrayKey === "themes") {
|
|
363
|
+
this.settingsManager.setThemePaths(updated);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
togglePackageResource(item, enabled) {
|
|
368
|
+
const scope = item.metadata.scope;
|
|
369
|
+
const settings = scope === "project" ? this.settingsManager.getProjectSettings() : this.settingsManager.getGlobalSettings();
|
|
370
|
+
const packages = [...(settings.packages ?? [])];
|
|
371
|
+
const pkgIndex = packages.findIndex((pkg) => {
|
|
372
|
+
const source = typeof pkg === "string" ? pkg : pkg.source;
|
|
373
|
+
return source === item.metadata.source;
|
|
374
|
+
});
|
|
375
|
+
if (pkgIndex === -1)
|
|
376
|
+
return;
|
|
377
|
+
let pkg = packages[pkgIndex];
|
|
378
|
+
// Convert string to object form if needed
|
|
379
|
+
if (typeof pkg === "string") {
|
|
380
|
+
pkg = { source: pkg };
|
|
381
|
+
packages[pkgIndex] = pkg;
|
|
382
|
+
}
|
|
383
|
+
// Get the resource array for this type
|
|
384
|
+
const arrayKey = item.resourceType;
|
|
385
|
+
const current = (pkg[arrayKey] ?? []);
|
|
386
|
+
// Generate pattern relative to package root
|
|
387
|
+
const pattern = this.getPackageResourcePattern(item);
|
|
388
|
+
const disablePattern = `-${pattern}`;
|
|
389
|
+
const enablePattern = `+${pattern}`;
|
|
390
|
+
// Filter out existing patterns for this resource
|
|
391
|
+
const updated = current.filter((p) => {
|
|
392
|
+
const stripped = p.startsWith("!") || p.startsWith("+") || p.startsWith("-") ? p.slice(1) : p;
|
|
393
|
+
return stripped !== pattern;
|
|
394
|
+
});
|
|
395
|
+
if (enabled) {
|
|
396
|
+
updated.push(enablePattern);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
updated.push(disablePattern);
|
|
400
|
+
}
|
|
401
|
+
pkg[arrayKey] = updated.length > 0 ? updated : undefined;
|
|
402
|
+
// Clean up empty filter object
|
|
403
|
+
const hasFilters = ["extensions", "skills", "prompts", "themes"].some((k) => pkg[k] !== undefined);
|
|
404
|
+
if (!hasFilters) {
|
|
405
|
+
packages[pkgIndex] = pkg.source;
|
|
406
|
+
}
|
|
407
|
+
if (scope === "project") {
|
|
408
|
+
this.settingsManager.setProjectPackages(packages);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
this.settingsManager.setPackages(packages);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
getTopLevelBaseDir(scope) {
|
|
415
|
+
return scope === "project" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;
|
|
416
|
+
}
|
|
417
|
+
getResourcePattern(item) {
|
|
418
|
+
const scope = item.metadata.scope;
|
|
419
|
+
const baseDir = this.getTopLevelBaseDir(scope);
|
|
420
|
+
return relative(baseDir, item.path);
|
|
421
|
+
}
|
|
422
|
+
getPackageResourcePattern(item) {
|
|
423
|
+
const baseDir = item.metadata.baseDir ?? dirname(item.path);
|
|
424
|
+
return relative(baseDir, item.path);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
export class ConfigSelectorComponent extends Container {
|
|
428
|
+
get focused() {
|
|
429
|
+
return this._focused;
|
|
430
|
+
}
|
|
431
|
+
set focused(value) {
|
|
432
|
+
this._focused = value;
|
|
433
|
+
this.resourceList.focused = value;
|
|
434
|
+
}
|
|
435
|
+
constructor(resolvedPaths, settingsManager, cwd, agentDir, onClose, onExit, requestRender) {
|
|
436
|
+
super();
|
|
437
|
+
this._focused = false;
|
|
438
|
+
const groups = buildGroups(resolvedPaths);
|
|
439
|
+
// Add header
|
|
440
|
+
this.addChild(new Spacer(1));
|
|
441
|
+
this.addChild(new DynamicBorder());
|
|
442
|
+
this.addChild(new Spacer(1));
|
|
443
|
+
this.addChild(new ConfigSelectorHeader());
|
|
444
|
+
this.addChild(new Spacer(1));
|
|
445
|
+
// Resource list
|
|
446
|
+
this.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir);
|
|
447
|
+
this.resourceList.onCancel = onClose;
|
|
448
|
+
this.resourceList.onExit = onExit;
|
|
449
|
+
this.resourceList.onToggle = () => requestRender();
|
|
450
|
+
this.addChild(this.resourceList);
|
|
451
|
+
// Bottom border
|
|
452
|
+
this.addChild(new Spacer(1));
|
|
453
|
+
this.addChild(new DynamicBorder());
|
|
454
|
+
}
|
|
455
|
+
getResourceList() {
|
|
456
|
+
return this.resourceList;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable countdown timer for dialog components.
|
|
3
|
+
*/
|
|
4
|
+
export class CountdownTimer {
|
|
5
|
+
constructor(timeoutMs, tui, onTick, onExpire) {
|
|
6
|
+
this.tui = tui;
|
|
7
|
+
this.onTick = onTick;
|
|
8
|
+
this.onExpire = onExpire;
|
|
9
|
+
this.remainingSeconds = Math.ceil(timeoutMs / 1000);
|
|
10
|
+
this.onTick(this.remainingSeconds);
|
|
11
|
+
this.intervalId = setInterval(() => {
|
|
12
|
+
this.remainingSeconds--;
|
|
13
|
+
this.onTick(this.remainingSeconds);
|
|
14
|
+
this.tui?.requestRender();
|
|
15
|
+
if (this.remainingSeconds <= 0) {
|
|
16
|
+
this.dispose();
|
|
17
|
+
this.onExpire();
|
|
18
|
+
}
|
|
19
|
+
}, 1000);
|
|
20
|
+
}
|
|
21
|
+
dispose() {
|
|
22
|
+
if (this.intervalId) {
|
|
23
|
+
clearInterval(this.intervalId);
|
|
24
|
+
this.intervalId = undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Editor } from "indusagi/tui";
|
|
2
|
+
/**
|
|
3
|
+
* Custom editor that handles app-level keybindings for coding-agent.
|
|
4
|
+
*/
|
|
5
|
+
export class CustomEditor extends Editor {
|
|
6
|
+
constructor(tui, theme, keybindings, options) {
|
|
7
|
+
super(tui, theme, options);
|
|
8
|
+
this.actionHandlers = new Map();
|
|
9
|
+
this.keybindings = keybindings;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register a handler for an app action.
|
|
13
|
+
*/
|
|
14
|
+
onAction(action, handler) {
|
|
15
|
+
this.actionHandlers.set(action, handler);
|
|
16
|
+
}
|
|
17
|
+
handleInput(data) {
|
|
18
|
+
// Check extension-registered shortcuts first
|
|
19
|
+
if (this.onExtensionShortcut?.(data)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Check for paste image keybinding
|
|
23
|
+
if (this.keybindings.matches(data, "pasteImage")) {
|
|
24
|
+
this.onPasteImage?.();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Check app keybindings first
|
|
28
|
+
// Escape/interrupt - only if autocomplete is NOT active
|
|
29
|
+
if (this.keybindings.matches(data, "interrupt")) {
|
|
30
|
+
if (!this.isShowingAutocomplete()) {
|
|
31
|
+
// Use dynamic onEscape if set, otherwise registered handler
|
|
32
|
+
const handler = this.onEscape ?? this.actionHandlers.get("interrupt");
|
|
33
|
+
if (handler) {
|
|
34
|
+
handler();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Let parent handle escape for autocomplete cancellation
|
|
39
|
+
super.handleInput(data);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Exit (Ctrl+D) - only when editor is empty
|
|
43
|
+
if (this.keybindings.matches(data, "exit")) {
|
|
44
|
+
if (this.getText().length === 0) {
|
|
45
|
+
const handler = this.onCtrlD ?? this.actionHandlers.get("exit");
|
|
46
|
+
if (handler)
|
|
47
|
+
handler();
|
|
48
|
+
}
|
|
49
|
+
return; // Always consume
|
|
50
|
+
}
|
|
51
|
+
// Check all other app actions
|
|
52
|
+
for (const [action, handler] of this.actionHandlers) {
|
|
53
|
+
if (action !== "interrupt" && action !== "exit" && this.keybindings.matches(data, action)) {
|
|
54
|
+
handler();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Pass to parent for editor handling
|
|
59
|
+
super.handleInput(data);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Box, Container, Markdown, Spacer, Text } from "indusagi/tui";
|
|
2
|
+
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
|
+
/**
|
|
4
|
+
* Component that renders a custom message entry from extensions.
|
|
5
|
+
* Uses distinct styling to differentiate from user messages.
|
|
6
|
+
*/
|
|
7
|
+
export class CustomMessageComponent extends Container {
|
|
8
|
+
constructor(message, customRenderer, markdownTheme = getMarkdownTheme()) {
|
|
9
|
+
super();
|
|
10
|
+
this._expanded = false;
|
|
11
|
+
this.message = message;
|
|
12
|
+
this.customRenderer = customRenderer;
|
|
13
|
+
this.markdownTheme = markdownTheme;
|
|
14
|
+
this.addChild(new Spacer(1));
|
|
15
|
+
// Create box with purple background (used for default rendering)
|
|
16
|
+
this.box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
|
|
17
|
+
this.rebuild();
|
|
18
|
+
}
|
|
19
|
+
setExpanded(expanded) {
|
|
20
|
+
if (this._expanded !== expanded) {
|
|
21
|
+
this._expanded = expanded;
|
|
22
|
+
this.rebuild();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
invalidate() {
|
|
26
|
+
super.invalidate();
|
|
27
|
+
this.rebuild();
|
|
28
|
+
}
|
|
29
|
+
rebuild() {
|
|
30
|
+
// Remove previous content component
|
|
31
|
+
if (this.customComponent) {
|
|
32
|
+
this.removeChild(this.customComponent);
|
|
33
|
+
this.customComponent = undefined;
|
|
34
|
+
}
|
|
35
|
+
this.removeChild(this.box);
|
|
36
|
+
// Try custom renderer first - it handles its own styling
|
|
37
|
+
if (this.customRenderer) {
|
|
38
|
+
try {
|
|
39
|
+
const component = this.customRenderer(this.message, { expanded: this._expanded }, theme);
|
|
40
|
+
if (component) {
|
|
41
|
+
// Custom renderer provides its own styled component
|
|
42
|
+
this.customComponent = component;
|
|
43
|
+
this.addChild(component);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Fall through to default rendering
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Default rendering uses our box
|
|
52
|
+
this.addChild(this.box);
|
|
53
|
+
this.box.clear();
|
|
54
|
+
// Default rendering: label + content
|
|
55
|
+
const label = theme.fg("customMessageLabel", `\x1b[1m[${this.message.customType}]\x1b[22m`);
|
|
56
|
+
this.box.addChild(new Text(label, 0, 0));
|
|
57
|
+
this.box.addChild(new Spacer(1));
|
|
58
|
+
// Extract text content
|
|
59
|
+
let text;
|
|
60
|
+
if (typeof this.message.content === "string") {
|
|
61
|
+
text = this.message.content;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
text = this.message.content
|
|
65
|
+
.filter((c) => c.type === "text")
|
|
66
|
+
.map((c) => c.text)
|
|
67
|
+
.join("\n");
|
|
68
|
+
}
|
|
69
|
+
// Limit lines when collapsed
|
|
70
|
+
if (!this._expanded) {
|
|
71
|
+
const lines = text.split("\n");
|
|
72
|
+
if (lines.length > 5) {
|
|
73
|
+
text = `${lines.slice(0, 5).join("\n")}\n...`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.box.addChild(new Markdown(text, 0, 0, this.markdownTheme, {
|
|
77
|
+
color: (text) => theme.fg("customMessageText", text),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
}
|