ei-tui 0.1.3
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/LICENSE +21 -0
- package/README.md +170 -0
- package/package.json +63 -0
- package/src/README.md +96 -0
- package/src/cli/README.md +47 -0
- package/src/cli/commands/facts.ts +25 -0
- package/src/cli/commands/people.ts +25 -0
- package/src/cli/commands/quotes.ts +19 -0
- package/src/cli/commands/topics.ts +25 -0
- package/src/cli/commands/traits.ts +25 -0
- package/src/cli/retrieval.ts +269 -0
- package/src/cli.ts +176 -0
- package/src/core/AGENTS.md +104 -0
- package/src/core/embedding-service.ts +241 -0
- package/src/core/handlers/index.ts +1057 -0
- package/src/core/index.ts +4 -0
- package/src/core/llm-client.ts +265 -0
- package/src/core/model-context-windows.ts +49 -0
- package/src/core/orchestrators/ceremony.ts +500 -0
- package/src/core/orchestrators/extraction-chunker.ts +138 -0
- package/src/core/orchestrators/human-extraction.ts +457 -0
- package/src/core/orchestrators/index.ts +28 -0
- package/src/core/orchestrators/persona-generation.ts +76 -0
- package/src/core/orchestrators/persona-topics.ts +117 -0
- package/src/core/personas/index.ts +5 -0
- package/src/core/personas/opencode-agent.ts +81 -0
- package/src/core/processor.ts +1413 -0
- package/src/core/queue-processor.ts +197 -0
- package/src/core/state/checkpoints.ts +68 -0
- package/src/core/state/human.ts +176 -0
- package/src/core/state/index.ts +5 -0
- package/src/core/state/personas.ts +217 -0
- package/src/core/state/queue.ts +144 -0
- package/src/core/state-manager.ts +347 -0
- package/src/core/types.ts +421 -0
- package/src/core/utils/decay.ts +33 -0
- package/src/index.ts +1 -0
- package/src/integrations/opencode/importer.ts +896 -0
- package/src/integrations/opencode/index.ts +16 -0
- package/src/integrations/opencode/json-reader.ts +304 -0
- package/src/integrations/opencode/reader-factory.ts +35 -0
- package/src/integrations/opencode/sqlite-reader.ts +189 -0
- package/src/integrations/opencode/types.ts +244 -0
- package/src/prompts/AGENTS.md +62 -0
- package/src/prompts/ceremony/description-check.ts +47 -0
- package/src/prompts/ceremony/expire.ts +30 -0
- package/src/prompts/ceremony/explore.ts +60 -0
- package/src/prompts/ceremony/index.ts +11 -0
- package/src/prompts/ceremony/types.ts +42 -0
- package/src/prompts/generation/descriptions.ts +91 -0
- package/src/prompts/generation/index.ts +15 -0
- package/src/prompts/generation/persona.ts +155 -0
- package/src/prompts/generation/seeds.ts +31 -0
- package/src/prompts/generation/types.ts +47 -0
- package/src/prompts/heartbeat/check.ts +179 -0
- package/src/prompts/heartbeat/ei.ts +208 -0
- package/src/prompts/heartbeat/index.ts +15 -0
- package/src/prompts/heartbeat/types.ts +70 -0
- package/src/prompts/human/fact-scan.ts +152 -0
- package/src/prompts/human/index.ts +32 -0
- package/src/prompts/human/item-match.ts +74 -0
- package/src/prompts/human/item-update.ts +322 -0
- package/src/prompts/human/person-scan.ts +115 -0
- package/src/prompts/human/topic-scan.ts +135 -0
- package/src/prompts/human/trait-scan.ts +115 -0
- package/src/prompts/human/types.ts +127 -0
- package/src/prompts/index.ts +90 -0
- package/src/prompts/message-utils.ts +39 -0
- package/src/prompts/persona/index.ts +16 -0
- package/src/prompts/persona/topics-match.ts +69 -0
- package/src/prompts/persona/topics-scan.ts +98 -0
- package/src/prompts/persona/topics-update.ts +157 -0
- package/src/prompts/persona/traits.ts +117 -0
- package/src/prompts/persona/types.ts +74 -0
- package/src/prompts/response/index.ts +147 -0
- package/src/prompts/response/sections.ts +355 -0
- package/src/prompts/response/types.ts +38 -0
- package/src/prompts/validation/ei.ts +93 -0
- package/src/prompts/validation/index.ts +6 -0
- package/src/prompts/validation/types.ts +22 -0
- package/src/storage/crypto.ts +96 -0
- package/src/storage/index.ts +5 -0
- package/src/storage/interface.ts +9 -0
- package/src/storage/local.ts +79 -0
- package/src/storage/merge.ts +69 -0
- package/src/storage/remote.ts +145 -0
- package/src/templates/welcome.ts +91 -0
- package/tui/README.md +62 -0
- package/tui/bunfig.toml +4 -0
- package/tui/src/app.tsx +55 -0
- package/tui/src/commands/archive.tsx +93 -0
- package/tui/src/commands/context.tsx +124 -0
- package/tui/src/commands/delete.tsx +71 -0
- package/tui/src/commands/details.tsx +41 -0
- package/tui/src/commands/editor.tsx +46 -0
- package/tui/src/commands/help.tsx +12 -0
- package/tui/src/commands/me.tsx +145 -0
- package/tui/src/commands/model.ts +47 -0
- package/tui/src/commands/new.ts +31 -0
- package/tui/src/commands/pause.ts +46 -0
- package/tui/src/commands/persona.tsx +58 -0
- package/tui/src/commands/provider.tsx +124 -0
- package/tui/src/commands/quit.ts +22 -0
- package/tui/src/commands/quotes.tsx +172 -0
- package/tui/src/commands/registry.test.ts +137 -0
- package/tui/src/commands/registry.ts +130 -0
- package/tui/src/commands/resume.ts +39 -0
- package/tui/src/commands/setsync.tsx +43 -0
- package/tui/src/commands/settings.tsx +83 -0
- package/tui/src/components/ConfirmOverlay.tsx +51 -0
- package/tui/src/components/ConflictOverlay.tsx +78 -0
- package/tui/src/components/HelpOverlay.tsx +69 -0
- package/tui/src/components/Layout.tsx +24 -0
- package/tui/src/components/MessageList.tsx +174 -0
- package/tui/src/components/PersonaListOverlay.tsx +186 -0
- package/tui/src/components/PromptInput.tsx +145 -0
- package/tui/src/components/ProviderListOverlay.tsx +208 -0
- package/tui/src/components/QuotesOverlay.tsx +157 -0
- package/tui/src/components/Sidebar.tsx +95 -0
- package/tui/src/components/StatusBar.tsx +77 -0
- package/tui/src/components/WelcomeOverlay.tsx +73 -0
- package/tui/src/context/ei.tsx +623 -0
- package/tui/src/context/keyboard.tsx +164 -0
- package/tui/src/context/overlay.tsx +53 -0
- package/tui/src/index.tsx +8 -0
- package/tui/src/storage/file.ts +185 -0
- package/tui/src/util/duration.ts +32 -0
- package/tui/src/util/editor.ts +188 -0
- package/tui/src/util/logger.ts +109 -0
- package/tui/src/util/persona-editor.tsx +181 -0
- package/tui/src/util/provider-editor.tsx +168 -0
- package/tui/src/util/syntax.ts +35 -0
- package/tui/src/util/yaml-serializers.ts +755 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** File-based logger for TUI debugging. Usage: tail -f $EI_DATA_PATH/tui.log */
|
|
2
|
+
|
|
3
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
function getDataPath(): string {
|
|
7
|
+
if (Bun.env.EI_DATA_PATH) return Bun.env.EI_DATA_PATH;
|
|
8
|
+
const xdgData = Bun.env.XDG_DATA_HOME || join(Bun.env.HOME || "~", ".local", "share");
|
|
9
|
+
return join(xdgData, "ei");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getLogPath(): string {
|
|
13
|
+
return join(getDataPath(), "tui.log");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
17
|
+
|
|
18
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
19
|
+
debug: 0,
|
|
20
|
+
info: 1,
|
|
21
|
+
warn: 2,
|
|
22
|
+
error: 3,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const currentLevel: LogLevel = (Bun.env.EI_LOG_LEVEL as LogLevel) || "debug";
|
|
26
|
+
|
|
27
|
+
function shouldLog(level: LogLevel): boolean {
|
|
28
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatMessage(level: LogLevel, message: string, data?: unknown): string {
|
|
32
|
+
const timestamp = new Date().toISOString();
|
|
33
|
+
const levelStr = level.toUpperCase().padEnd(5);
|
|
34
|
+
let line = `[${timestamp}] ${levelStr} ${message}`;
|
|
35
|
+
|
|
36
|
+
if (data !== undefined) {
|
|
37
|
+
try {
|
|
38
|
+
line += ` ${JSON.stringify(data)}`;
|
|
39
|
+
} catch {
|
|
40
|
+
line += ` [unstringifiable: ${typeof data}]`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return line + "\n";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeLogSync(level: LogLevel, message: string, data?: unknown): void {
|
|
48
|
+
if (!shouldLog(level)) return;
|
|
49
|
+
|
|
50
|
+
const line = formatMessage(level, message, data);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
appendFileSync(getLogPath(), line);
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const logger = {
|
|
58
|
+
debug: (message: string, data?: unknown) => writeLogSync("debug", message, data),
|
|
59
|
+
info: (message: string, data?: unknown) => writeLogSync("info", message, data),
|
|
60
|
+
warn: (message: string, data?: unknown) => writeLogSync("warn", message, data),
|
|
61
|
+
error: (message: string, data?: unknown) => writeLogSync("error", message, data),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function clearLog(): void {
|
|
65
|
+
try {
|
|
66
|
+
const logPath = getLogPath();
|
|
67
|
+
const dataDir = logPath.substring(0, logPath.lastIndexOf("/"));
|
|
68
|
+
mkdirSync(dataDir, { recursive: true });
|
|
69
|
+
const header = `--- TUI Started at ${new Date().toISOString()} ---\n`;
|
|
70
|
+
Bun.write(logPath, header);
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function interceptConsole(): void {
|
|
75
|
+
const originalLog = console.log.bind(console);
|
|
76
|
+
const originalWarn = console.warn.bind(console);
|
|
77
|
+
const originalError = console.error.bind(console);
|
|
78
|
+
const originalDebug = console.debug.bind(console);
|
|
79
|
+
const originalInfo = console.info.bind(console);
|
|
80
|
+
|
|
81
|
+
const formatArgs = (args: unknown[]): string => {
|
|
82
|
+
return args.map(arg =>
|
|
83
|
+
typeof arg === "string" ? arg : JSON.stringify(arg)
|
|
84
|
+
).join(" ");
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
console.log = (...args: unknown[]) => {
|
|
88
|
+
writeLogSync("info", `[console.log] ${formatArgs(args)}`);
|
|
89
|
+
originalLog(...args);
|
|
90
|
+
};
|
|
91
|
+
console.warn = (...args: unknown[]) => {
|
|
92
|
+
writeLogSync("warn", `[console.warn] ${formatArgs(args)}`);
|
|
93
|
+
originalWarn(...args);
|
|
94
|
+
};
|
|
95
|
+
console.error = (...args: unknown[]) => {
|
|
96
|
+
writeLogSync("error", `[console.error] ${formatArgs(args)}`);
|
|
97
|
+
originalError(...args);
|
|
98
|
+
};
|
|
99
|
+
console.debug = (...args: unknown[]) => {
|
|
100
|
+
writeLogSync("debug", `[console.debug] ${formatArgs(args)}`);
|
|
101
|
+
originalDebug(...args);
|
|
102
|
+
};
|
|
103
|
+
console.info = (...args: unknown[]) => {
|
|
104
|
+
writeLogSync("info", `[console.info] ${formatArgs(args)}`);
|
|
105
|
+
originalInfo(...args);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default logger;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { spawnEditor } from "./editor.js";
|
|
2
|
+
import { personaToYAML, personaFromYAML, newPersonaToYAML, newPersonaFromYAML, validateModelProvider } from "./yaml-serializers.js";
|
|
3
|
+
import type { CommandContext } from "../commands/registry.js";
|
|
4
|
+
import type { PersonaEntity } from "../../../src/core/types.js";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
import { ConfirmOverlay } from "../components/ConfirmOverlay.js";
|
|
7
|
+
|
|
8
|
+
export interface PersonaEditorOptions {
|
|
9
|
+
personaId: string;
|
|
10
|
+
persona: PersonaEntity;
|
|
11
|
+
ctx: CommandContext;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PersonaEditorResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
cancelled: boolean;
|
|
17
|
+
personaWasModified: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NewPersonaEditorOptions {
|
|
21
|
+
personaName: string;
|
|
22
|
+
ctx: CommandContext;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface NewPersonaEditorResult {
|
|
26
|
+
created: boolean;
|
|
27
|
+
cancelled: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function createPersonaViaEditor(options: NewPersonaEditorOptions): Promise<NewPersonaEditorResult> {
|
|
31
|
+
const { personaName, ctx } = options;
|
|
32
|
+
|
|
33
|
+
let yamlContent = newPersonaToYAML(personaName);
|
|
34
|
+
|
|
35
|
+
while (true) {
|
|
36
|
+
const result = await spawnEditor({
|
|
37
|
+
initialContent: yamlContent,
|
|
38
|
+
filename: `${personaName}-new.yaml`,
|
|
39
|
+
renderer: ctx.renderer,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (result.aborted) {
|
|
43
|
+
ctx.showNotification("Creation cancelled", "info");
|
|
44
|
+
return { created: false, cancelled: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!result.success) {
|
|
48
|
+
ctx.showNotification("Editor failed to open", "error");
|
|
49
|
+
return { created: false, cancelled: false };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (result.content === null) {
|
|
53
|
+
ctx.showNotification("No content - persona not created", "info");
|
|
54
|
+
return { created: false, cancelled: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const parsed = newPersonaFromYAML(result.content);
|
|
59
|
+
// Validate provider name in model (case-insensitive match + auto-correct)
|
|
60
|
+
const human = await ctx.ei.getHuman();
|
|
61
|
+
const llmAccounts = human.settings?.accounts?.filter(a => a.type === "llm") ?? [];
|
|
62
|
+
parsed.model = validateModelProvider(parsed.model, llmAccounts);
|
|
63
|
+
|
|
64
|
+
const personaId = await ctx.ei.createPersona({
|
|
65
|
+
name: personaName,
|
|
66
|
+
...parsed,
|
|
67
|
+
});
|
|
68
|
+
// Ensure store has the new persona before selecting
|
|
69
|
+
// (onPersonaAdded fires refreshPersonas but doesn't await it)
|
|
70
|
+
await ctx.ei.refreshPersonas();
|
|
71
|
+
ctx.ei.selectPersona(personaId);
|
|
72
|
+
|
|
73
|
+
ctx.showNotification(`Created ${personaName}`, "info");
|
|
74
|
+
return { created: true, cancelled: false };
|
|
75
|
+
|
|
76
|
+
} catch (parseError) {
|
|
77
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
78
|
+
logger.debug("[persona-editor] YAML parse error in new persona", { error: errorMsg });
|
|
79
|
+
|
|
80
|
+
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
81
|
+
ctx.showOverlay((hideOverlay) => (
|
|
82
|
+
<ConfirmOverlay
|
|
83
|
+
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
84
|
+
onConfirm={() => {
|
|
85
|
+
logger.debug("[persona-editor] user confirmed re-edit (new)");
|
|
86
|
+
hideOverlay();
|
|
87
|
+
resolve(true);
|
|
88
|
+
}}
|
|
89
|
+
onCancel={() => {
|
|
90
|
+
logger.debug("[persona-editor] user cancelled re-edit (new)");
|
|
91
|
+
hideOverlay();
|
|
92
|
+
resolve(false);
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (shouldReEdit) {
|
|
99
|
+
yamlContent = result.content;
|
|
100
|
+
await new Promise(r => setTimeout(r, 50));
|
|
101
|
+
continue;
|
|
102
|
+
} else {
|
|
103
|
+
ctx.showNotification("Creation cancelled", "info");
|
|
104
|
+
return { created: false, cancelled: true };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function openPersonaEditor(options: PersonaEditorOptions): Promise<PersonaEditorResult> {
|
|
111
|
+
const { personaId, persona, ctx } = options;
|
|
112
|
+
const allGroups = await ctx.ei.getGroupList();
|
|
113
|
+
let yamlContent = personaToYAML(persona, allGroups);
|
|
114
|
+
|
|
115
|
+
while (true) {
|
|
116
|
+
const result = await spawnEditor({
|
|
117
|
+
initialContent: yamlContent,
|
|
118
|
+
filename: `${personaId}-details.yaml`,
|
|
119
|
+
renderer: ctx.renderer,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (result.aborted) {
|
|
123
|
+
ctx.showNotification("Editor cancelled", "info");
|
|
124
|
+
return { success: false, cancelled: true, personaWasModified: false };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!result.success) {
|
|
128
|
+
ctx.showNotification("Editor failed to open", "error");
|
|
129
|
+
return { success: false, cancelled: false, personaWasModified: false };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (result.content === null) {
|
|
133
|
+
ctx.showNotification("No changes made", "info");
|
|
134
|
+
return { success: true, cancelled: false, personaWasModified: false };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const parsed = personaFromYAML(result.content, persona);
|
|
139
|
+
// Validate provider name in model (case-insensitive match + auto-correct)
|
|
140
|
+
const human = await ctx.ei.getHuman();
|
|
141
|
+
const llmAccounts = human.settings?.accounts?.filter(a => a.type === "llm") ?? [];
|
|
142
|
+
parsed.updates.model = validateModelProvider(parsed.updates.model, llmAccounts);
|
|
143
|
+
|
|
144
|
+
await ctx.ei.updatePersona(personaId, parsed.updates);
|
|
145
|
+
|
|
146
|
+
ctx.showNotification(`Updated ${persona.display_name}`, "info");
|
|
147
|
+
return { success: true, cancelled: false, personaWasModified: true };
|
|
148
|
+
|
|
149
|
+
} catch (parseError) {
|
|
150
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
151
|
+
logger.debug("[persona-editor] YAML parse error", { error: errorMsg });
|
|
152
|
+
|
|
153
|
+
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
154
|
+
ctx.showOverlay((hideOverlay) => (
|
|
155
|
+
<ConfirmOverlay
|
|
156
|
+
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
157
|
+
onConfirm={() => {
|
|
158
|
+
logger.debug("[persona-editor] user confirmed re-edit");
|
|
159
|
+
hideOverlay();
|
|
160
|
+
resolve(true);
|
|
161
|
+
}}
|
|
162
|
+
onCancel={() => {
|
|
163
|
+
logger.debug("[persona-editor] user cancelled re-edit");
|
|
164
|
+
hideOverlay();
|
|
165
|
+
resolve(false);
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (shouldReEdit) {
|
|
172
|
+
yamlContent = result.content;
|
|
173
|
+
await new Promise(r => setTimeout(r, 50));
|
|
174
|
+
continue;
|
|
175
|
+
} else {
|
|
176
|
+
ctx.showNotification("Changes discarded", "info");
|
|
177
|
+
return { success: false, cancelled: true, personaWasModified: false };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { spawnEditor } from "./editor.js";
|
|
2
|
+
import { newProviderToYAML, newProviderFromYAML, providerToYAML, providerFromYAML } from "./yaml-serializers.js";
|
|
3
|
+
import type { CommandContext } from "../commands/registry.js";
|
|
4
|
+
import type { ProviderAccount, HumanSettings } from "../../../src/core/types.js";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
import { ConfirmOverlay } from "../components/ConfirmOverlay.js";
|
|
7
|
+
|
|
8
|
+
export interface NewProviderEditorResult {
|
|
9
|
+
created: boolean;
|
|
10
|
+
account: ProviderAccount | null;
|
|
11
|
+
cancelled: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EditProviderEditorResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
account: ProviderAccount | null;
|
|
17
|
+
cancelled: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createProviderViaEditor(ctx: CommandContext): Promise<NewProviderEditorResult> {
|
|
21
|
+
let yamlContent = newProviderToYAML();
|
|
22
|
+
|
|
23
|
+
while (true) {
|
|
24
|
+
const result = await spawnEditor({
|
|
25
|
+
initialContent: yamlContent,
|
|
26
|
+
filename: "new-provider.yaml",
|
|
27
|
+
renderer: ctx.renderer,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (result.aborted) {
|
|
31
|
+
ctx.showNotification("Creation cancelled", "info");
|
|
32
|
+
return { created: false, account: null, cancelled: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
ctx.showNotification("Editor failed to open", "error");
|
|
37
|
+
return { created: false, account: null, cancelled: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (result.content === null) {
|
|
41
|
+
ctx.showNotification("No content - provider not created", "info");
|
|
42
|
+
return { created: false, account: null, cancelled: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const account = newProviderFromYAML(result.content);
|
|
47
|
+
|
|
48
|
+
// Save to settings
|
|
49
|
+
const human = await ctx.ei.getHuman();
|
|
50
|
+
const accounts = [...(human.settings?.accounts ?? []), account];
|
|
51
|
+
const updates: Partial<HumanSettings> = { accounts };
|
|
52
|
+
|
|
53
|
+
// If no system default_model, auto-set to this new provider
|
|
54
|
+
if (!human.settings?.default_model) {
|
|
55
|
+
updates.default_model = account.default_model
|
|
56
|
+
? `${account.name}:${account.default_model}`
|
|
57
|
+
: account.name;
|
|
58
|
+
}
|
|
59
|
+
await ctx.ei.updateSettings(updates);
|
|
60
|
+
|
|
61
|
+
ctx.showNotification(`Created provider "${account.name}"`, "info");
|
|
62
|
+
return { created: true, account, cancelled: false };
|
|
63
|
+
|
|
64
|
+
} catch (parseError) {
|
|
65
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
66
|
+
logger.debug("[provider-editor] YAML parse error in new provider", { error: errorMsg });
|
|
67
|
+
|
|
68
|
+
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
69
|
+
ctx.showOverlay((hideOverlay) => (
|
|
70
|
+
<ConfirmOverlay
|
|
71
|
+
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
72
|
+
onConfirm={() => {
|
|
73
|
+
hideOverlay();
|
|
74
|
+
resolve(true);
|
|
75
|
+
}}
|
|
76
|
+
onCancel={() => {
|
|
77
|
+
hideOverlay();
|
|
78
|
+
resolve(false);
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (shouldReEdit) {
|
|
85
|
+
yamlContent = result.content;
|
|
86
|
+
await new Promise(r => setTimeout(r, 50));
|
|
87
|
+
continue;
|
|
88
|
+
} else {
|
|
89
|
+
ctx.showNotification("Creation cancelled", "info");
|
|
90
|
+
return { created: false, account: null, cancelled: true };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function openProviderEditor(account: ProviderAccount, ctx: CommandContext): Promise<EditProviderEditorResult> {
|
|
97
|
+
let yamlContent = providerToYAML(account);
|
|
98
|
+
|
|
99
|
+
while (true) {
|
|
100
|
+
const result = await spawnEditor({
|
|
101
|
+
initialContent: yamlContent,
|
|
102
|
+
filename: `${account.name}-provider.yaml`,
|
|
103
|
+
renderer: ctx.renderer,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (result.aborted) {
|
|
107
|
+
ctx.showNotification("Editor cancelled", "info");
|
|
108
|
+
return { success: false, account: null, cancelled: true };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
ctx.showNotification("Editor failed to open", "error");
|
|
113
|
+
return { success: false, account: null, cancelled: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (result.content === null) {
|
|
117
|
+
ctx.showNotification("No changes made", "info");
|
|
118
|
+
return { success: true, account, cancelled: false };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const updated = providerFromYAML(result.content, account);
|
|
123
|
+
|
|
124
|
+
// Update in settings
|
|
125
|
+
const human = await ctx.ei.getHuman();
|
|
126
|
+
const accounts = [...(human.settings?.accounts ?? [])];
|
|
127
|
+
const idx = accounts.findIndex(a => a.id === account.id);
|
|
128
|
+
if (idx >= 0) {
|
|
129
|
+
accounts[idx] = updated;
|
|
130
|
+
} else {
|
|
131
|
+
accounts.push(updated);
|
|
132
|
+
}
|
|
133
|
+
await ctx.ei.updateSettings({ accounts });
|
|
134
|
+
|
|
135
|
+
ctx.showNotification(`Updated provider "${updated.name}"`, "info");
|
|
136
|
+
return { success: true, account: updated, cancelled: false };
|
|
137
|
+
|
|
138
|
+
} catch (parseError) {
|
|
139
|
+
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
140
|
+
logger.debug("[provider-editor] YAML parse error", { error: errorMsg });
|
|
141
|
+
|
|
142
|
+
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
143
|
+
ctx.showOverlay((hideOverlay) => (
|
|
144
|
+
<ConfirmOverlay
|
|
145
|
+
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
146
|
+
onConfirm={() => {
|
|
147
|
+
hideOverlay();
|
|
148
|
+
resolve(true);
|
|
149
|
+
}}
|
|
150
|
+
onCancel={() => {
|
|
151
|
+
hideOverlay();
|
|
152
|
+
resolve(false);
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (shouldReEdit) {
|
|
159
|
+
yamlContent = result.content;
|
|
160
|
+
await new Promise(r => setTimeout(r, 50));
|
|
161
|
+
continue;
|
|
162
|
+
} else {
|
|
163
|
+
ctx.showNotification("Changes discarded", "info");
|
|
164
|
+
return { success: false, account: null, cancelled: true };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SyntaxStyle, parseColor } from "@opentui/core";
|
|
2
|
+
|
|
3
|
+
export function createSolarizedDarkSyntax(): SyntaxStyle {
|
|
4
|
+
return SyntaxStyle.fromStyles({
|
|
5
|
+
default: { fg: parseColor("#839496") },
|
|
6
|
+
|
|
7
|
+
keyword: { fg: parseColor("#859900"), bold: true },
|
|
8
|
+
string: { fg: parseColor("#2aa198") },
|
|
9
|
+
comment: { fg: parseColor("#586e75"), italic: true },
|
|
10
|
+
number: { fg: parseColor("#d33682") },
|
|
11
|
+
function: { fg: parseColor("#268bd2") },
|
|
12
|
+
type: { fg: parseColor("#b58900") },
|
|
13
|
+
operator: { fg: parseColor("#859900") },
|
|
14
|
+
variable: { fg: parseColor("#839496") },
|
|
15
|
+
|
|
16
|
+
"markup.heading": { fg: parseColor("#268bd2"), bold: true },
|
|
17
|
+
"markup.heading.1": { fg: parseColor("#cb4b16"), bold: true, underline: true },
|
|
18
|
+
"markup.heading.2": { fg: parseColor("#268bd2"), bold: true },
|
|
19
|
+
"markup.heading.3": { fg: parseColor("#2aa198") },
|
|
20
|
+
"markup.bold": { bold: true },
|
|
21
|
+
"markup.strong": { bold: true },
|
|
22
|
+
"markup.italic": { italic: true },
|
|
23
|
+
"markup.list": { fg: parseColor("#268bd2") },
|
|
24
|
+
"markup.quote": { fg: parseColor("#586e75"), italic: true },
|
|
25
|
+
"markup.raw": { fg: parseColor("#2aa198"), bg: parseColor("#073642") },
|
|
26
|
+
"markup.link": { fg: parseColor("#268bd2"), underline: true },
|
|
27
|
+
"markup.link.label": { fg: parseColor("#2aa198"), underline: true },
|
|
28
|
+
"markup.link.url": { fg: parseColor("#268bd2"), underline: true },
|
|
29
|
+
|
|
30
|
+
conceal: { fg: parseColor("#586e75") },
|
|
31
|
+
"punctuation.special": { fg: parseColor("#586e75") },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const solarizedDarkSyntax = createSolarizedDarkSyntax();
|