omnexx 0.11.3 → 0.13.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/README.md +17 -4
- package/dist/{chat-context-PTZRLJEV.js → chat-context-P4ZON7M4.js} +4 -3
- package/dist/{chat-context-PTZRLJEV.js.map → chat-context-P4ZON7M4.js.map} +1 -1
- package/dist/chat-history-SMI5AWQG.js +21 -0
- package/dist/{chat-server-HFGPZ7RU.js → chat-server-FMF26CBV.js} +55 -11
- package/dist/chat-server-FMF26CBV.js.map +1 -0
- package/dist/{chat-settings-X7R4JKI7.js → chat-settings-4XUKDH6H.js} +10 -9
- package/dist/{chat-settings-X7R4JKI7.js.map → chat-settings-4XUKDH6H.js.map} +1 -1
- package/dist/chunk-23MT7K6X.js +209 -0
- package/dist/chunk-23MT7K6X.js.map +1 -0
- package/dist/{chunk-3Y7NVLVI.js → chunk-3OILFLAA.js} +424 -50
- package/dist/chunk-3OILFLAA.js.map +1 -0
- package/dist/{chunk-4DX4BPIF.js → chunk-ET6OMKAS.js} +4 -2
- package/dist/chunk-ET6OMKAS.js.map +1 -0
- package/dist/chunk-FR5DUUJ4.js +56 -0
- package/dist/chunk-FR5DUUJ4.js.map +1 -0
- package/dist/chunk-FSA52KQB.js +569 -0
- package/dist/chunk-FSA52KQB.js.map +1 -0
- package/dist/{chunk-LRBHQHO6.js → chunk-GBFAEDOD.js} +47 -4
- package/dist/chunk-GBFAEDOD.js.map +1 -0
- package/dist/chunk-H4XHEBFQ.js +257 -0
- package/dist/chunk-H4XHEBFQ.js.map +1 -0
- package/dist/{chunk-6U3WB5AL.js → chunk-KJ5SPE6X.js} +718 -115
- package/dist/chunk-KJ5SPE6X.js.map +1 -0
- package/dist/{chunk-YAWRCULP.js → chunk-L2YBK4AY.js} +18 -6
- package/dist/chunk-L2YBK4AY.js.map +1 -0
- package/dist/{chunk-AXEJCZPA.js → chunk-MC4NBIIA.js} +7 -7
- package/dist/{chunk-FLIPPDUD.js → chunk-PRAOJIKH.js} +148 -219
- package/dist/chunk-PRAOJIKH.js.map +1 -0
- package/dist/{chunk-LZGGM2YZ.js → chunk-RSQGFSCA.js} +4 -4
- package/dist/{chunk-UAW5RG53.js → chunk-V5RPCSON.js} +43 -71
- package/dist/chunk-V5RPCSON.js.map +1 -0
- package/dist/chunk-WXMTUSCS.js +88 -0
- package/dist/chunk-WXMTUSCS.js.map +1 -0
- package/dist/{chunk-IGUI6IJP.js → chunk-XKO2GYOF.js} +1067 -817
- package/dist/chunk-XKO2GYOF.js.map +1 -0
- package/dist/chunk-XP4J622W.js +35 -0
- package/dist/chunk-XP4J622W.js.map +1 -0
- package/dist/{chunk-E6NF2XSG.js → chunk-YJ7BPHUB.js} +4 -4
- package/dist/cli.js +99 -61
- package/dist/cli.js.map +1 -1
- package/dist/{list-4L6TDLAZ.js → list-V6A7DKXF.js} +2 -2
- package/dist/{model-picker-JKB7LVQR.js → model-picker-IR7QMEWU.js} +7 -6
- package/dist/ollama-setup-EJY37RBS.js +31 -0
- package/dist/ollama-setup-EJY37RBS.js.map +1 -0
- package/dist/ollama-start-EBZJFMNR.js +72 -0
- package/dist/ollama-start-EBZJFMNR.js.map +1 -0
- package/dist/{print-AMD3ZAUQ.js → print-HXC5CFGD.js} +11 -7
- package/dist/{print-AMD3ZAUQ.js.map → print-HXC5CFGD.js.map} +1 -1
- package/dist/session-2YQYN6VU.js +41 -0
- package/dist/session-2YQYN6VU.js.map +1 -0
- package/dist/{start-LXX7MA37.js → start-KMBA2W6F.js} +30 -15
- package/dist/start-KMBA2W6F.js.map +1 -0
- package/package.json +2 -1
- package/dist/chat-server-HFGPZ7RU.js.map +0 -1
- package/dist/chunk-3Y7NVLVI.js.map +0 -1
- package/dist/chunk-4DX4BPIF.js.map +0 -1
- package/dist/chunk-6U3WB5AL.js.map +0 -1
- package/dist/chunk-FLIPPDUD.js.map +0 -1
- package/dist/chunk-IGUI6IJP.js.map +0 -1
- package/dist/chunk-LRBHQHO6.js.map +0 -1
- package/dist/chunk-UAW5RG53.js.map +0 -1
- package/dist/chunk-YAWRCULP.js.map +0 -1
- package/dist/ollama-G2MYWBEE.js +0 -71
- package/dist/ollama-G2MYWBEE.js.map +0 -1
- package/dist/session-OORM43WG.js +0 -36
- package/dist/start-LXX7MA37.js.map +0 -1
- /package/dist/{list-4L6TDLAZ.js.map → chat-history-SMI5AWQG.js.map} +0 -0
- /package/dist/{chunk-AXEJCZPA.js.map → chunk-MC4NBIIA.js.map} +0 -0
- /package/dist/{chunk-LZGGM2YZ.js.map → chunk-RSQGFSCA.js.map} +0 -0
- /package/dist/{chunk-E6NF2XSG.js.map → chunk-YJ7BPHUB.js.map} +0 -0
- /package/dist/{model-picker-JKB7LVQR.js.map → list-V6A7DKXF.js.map} +0 -0
- /package/dist/{session-OORM43WG.js.map → model-picker-IR7QMEWU.js.map} +0 -0
package/README.md
CHANGED
|
@@ -121,14 +121,27 @@ Optional: an advisory **fast judge** (Nimble on Ollama, usually on your Mac over
|
|
|
121
121
|
|
|
122
122
|
## Free and local with Ollama
|
|
123
123
|
|
|
124
|
+
If Ollama is running, `omnexx` (or `omnexx --ollama`, or the VS Code panel) just works. With no
|
|
125
|
+
provider set up, it:
|
|
126
|
+
|
|
127
|
+
- finds Ollama on `localhost:11434` (or `OLLAMA_HOST`) and lists the models you've pulled;
|
|
128
|
+
- keeps only models that can call tools (`/api/show` capabilities), skips `:cloud` models, and
|
|
129
|
+
ranks coding models first (qwen3-coder, devstral, gpt-oss, qwen3.x, qwen2.5-coder, ...), then
|
|
130
|
+
bigger, then longer context;
|
|
131
|
+
- makes a copy with a usable context window (`omnexx-<model>`, `num_ctx` 32k, or 16k under 16 GB
|
|
132
|
+
of RAM, never more than the model supports), since Ollama's default window is too small;
|
|
133
|
+
- saves it as the chat, planner, worker and cheap model, and says so in one line:
|
|
134
|
+
`using Ollama's qwen3-coder:30b (local, free, tools, 32k context) for chat, planning and work`.
|
|
135
|
+
|
|
136
|
+
No usable model yet? It suggests one for your machine (`ollama pull qwen3-coder`, or
|
|
137
|
+
`qwen2.5-coder:7b` under 32 GB) and offers to pull it: `y` in the terminal, **Pull it now** in
|
|
138
|
+
VS Code. Ollama not running? It says how to install (https://ollama.com/download) or start it.
|
|
139
|
+
|
|
124
140
|
```bash
|
|
125
|
-
|
|
126
|
-
npx omnexx --ollama # finds Ollama on localhost:11434, picks a coding model, opens the chat
|
|
141
|
+
npx omnexx --ollama # set up as above, then open the chat
|
|
127
142
|
npx omnexx --ollama llama3.3 # or name the model
|
|
128
143
|
```
|
|
129
144
|
|
|
130
|
-
No key and no config: with no provider set up, omnexx also connects a running Ollama by itself.
|
|
131
|
-
|
|
132
145
|
## From the plain terminal
|
|
133
146
|
|
|
134
147
|
```bash
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
listRunIds,
|
|
5
5
|
repoMemoryFile,
|
|
6
6
|
repoRoot
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-GBFAEDOD.js";
|
|
8
8
|
import {
|
|
9
9
|
SECTION_IDS,
|
|
10
10
|
SECTION_INFO,
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
loadConfig,
|
|
14
14
|
readSectionModes,
|
|
15
15
|
sectionsFile
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-V5RPCSON.js";
|
|
17
|
+
import "./chunk-FR5DUUJ4.js";
|
|
17
18
|
import {
|
|
18
19
|
resolvePaths
|
|
19
20
|
} from "./chunk-Q3MDYD6K.js";
|
|
@@ -117,4 +118,4 @@ export {
|
|
|
117
118
|
contextFiles,
|
|
118
119
|
setSectionMode
|
|
119
120
|
};
|
|
120
|
-
//# sourceMappingURL=chat-context-
|
|
121
|
+
//# sourceMappingURL=chat-context-P4ZON7M4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/chat-context.ts"],"sourcesContent":["import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport {\n SECTION_IDS,\n SECTION_INFO,\n SECTION_MODES,\n type SectionId,\n type SectionMode,\n} from '../agent/sections.js';\nimport { loadConfig } from '../config/load.js';\nimport { readSectionModes, sectionsFile } from '../config/sections-file.js';\nimport { resolvePaths, type OmnexxPaths } from '../core/paths.js';\nimport { repoMemoryFile } from '../core/repo-memory.js';\nimport { listRunIds, RunStore } from '../core/run-store.js';\nimport { estimateTokens } from '../core/tokens.js';\nimport { UsageError } from '../errors.js';\nimport { repoRoot } from '../git/repo.js';\nimport type { CliIO } from './io.js';\n\n/**\n * The Context explorer's data: every file that feeds the agent's context in this workspace, its\n * size in tokens, whether it can be edited, and (for sections) how it reaches the model. Section\n * modes are saved to context-sections.json and loaded as a config layer.\n */\n\nexport interface ContextFile {\n id: string;\n label: string;\n about: string;\n path: string;\n tokens: number;\n exists: boolean;\n /** Edits are read on the next cycle; generated files are views. */\n editable: boolean;\n /** For context sections: how it reaches the model. */\n section?: SectionId;\n mode?: SectionMode;\n}\n\nexport async function setSectionMode(io: CliIO, id: string, mode: string): Promise<void> {\n if (!(SECTION_IDS as readonly string[]).includes(id)) throw new UsageError(`no section \"${id}\"`);\n if (!(SECTION_MODES as readonly string[]).includes(mode))\n throw new UsageError(`mode is one of ${SECTION_MODES.join(', ')}`);\n const p = resolvePaths(io.env);\n const modes = { ...(await readSectionModes(p)), [id]: mode };\n await mkdir(dirname(sectionsFile(p)), { recursive: true });\n await writeFile(sectionsFile(p), `${JSON.stringify(modes, null, 2)}\\n`);\n}\n\nasync function sizeOf(path: string): Promise<{ exists: boolean; tokens: number }> {\n try {\n if (!(await stat(path)).isFile()) return { exists: false, tokens: 0 };\n return { exists: true, tokens: estimateTokens(await readFile(path, 'utf8')) };\n } catch {\n return { exists: false, tokens: 0 };\n }\n}\n\n/** The newest run started from this repository, if any. */\nasync function latestRun(paths: OmnexxPaths, root: string): Promise<RunStore | undefined> {\n for (const id of [...(await listRunIds(paths))].reverse()) {\n const store = new RunStore(paths, id);\n const s = await store.readState().catch(() => undefined);\n if (s?.repoRoot === root) return store;\n }\n return undefined;\n}\n\nexport async function contextFiles(\n io: CliIO,\n): Promise<{ runId: string | null; files: ContextFile[] }> {\n const paths = resolvePaths(io.env);\n const root = await repoRoot(io.cwd).catch(() => io.cwd);\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const modes = config.context.sections;\n const run = await latestRun(paths, root);\n const state = run ? await run.readState().catch(() => undefined) : undefined;\n const memoryDir = dirname(await repoMemoryFile({ home: paths.home }, root));\n\n const entries: Omit<ContextFile, 'tokens' | 'exists'>[] = [];\n const section = (id: SectionId, path: string, editable: boolean): void => {\n const info = SECTION_INFO[id];\n entries.push({\n id,\n label: info.label,\n about: info.about,\n path,\n editable,\n section: id,\n mode: modes[id],\n });\n };\n if (run) {\n if (state?.taskId) section('role', run.file(join('roles', `${state.taskId}.md`)), true);\n section('codemap', run.file('codemap.md'), false);\n section('intent', run.file('intent.md'), true);\n section('lessons', run.file('notes.md'), false);\n section('progress', run.file('progress.md'), false);\n entries.push({\n id: 'goal',\n label: 'Goal',\n about: 'what this run is for',\n path: run.file('goal.md'),\n editable: true,\n });\n }\n entries.push(\n {\n id: 'repo-role',\n label: 'Repo role note',\n about: 'added to every role card in this repo',\n path: join(root, '.omnexx', 'role.md'),\n editable: true,\n },\n {\n id: 'general-notes',\n label: 'General notes',\n about: 'what the agent keeps about this repo across runs',\n path: join(memoryDir, 'scopes', 'general.md'),\n editable: true,\n },\n {\n id: 'repo-notes',\n label: 'Repo notes',\n about: 'architecture and where things live, across runs',\n path: join(memoryDir, 'scopes', 'repo.md'),\n editable: true,\n },\n );\n const files = await Promise.all(entries.map(async (e) => ({ ...e, ...(await sizeOf(e.path)) })));\n return { runId: run?.runId ?? null, files };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/cli/chat-context.ts"],"sourcesContent":["import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport {\n SECTION_IDS,\n SECTION_INFO,\n SECTION_MODES,\n type SectionId,\n type SectionMode,\n} from '../agent/sections.js';\nimport { loadConfig } from '../config/load.js';\nimport { readSectionModes, sectionsFile } from '../config/sections-file.js';\nimport { resolvePaths, type OmnexxPaths } from '../core/paths.js';\nimport { repoMemoryFile } from '../core/repo-memory.js';\nimport { listRunIds, RunStore } from '../core/run-store.js';\nimport { estimateTokens } from '../core/tokens.js';\nimport { UsageError } from '../errors.js';\nimport { repoRoot } from '../git/repo.js';\nimport type { CliIO } from './io.js';\n\n/**\n * The Context explorer's data: every file that feeds the agent's context in this workspace, its\n * size in tokens, whether it can be edited, and (for sections) how it reaches the model. Section\n * modes are saved to context-sections.json and loaded as a config layer.\n */\n\nexport interface ContextFile {\n id: string;\n label: string;\n about: string;\n path: string;\n tokens: number;\n exists: boolean;\n /** Edits are read on the next cycle; generated files are views. */\n editable: boolean;\n /** For context sections: how it reaches the model. */\n section?: SectionId;\n mode?: SectionMode;\n}\n\nexport async function setSectionMode(io: CliIO, id: string, mode: string): Promise<void> {\n if (!(SECTION_IDS as readonly string[]).includes(id)) throw new UsageError(`no section \"${id}\"`);\n if (!(SECTION_MODES as readonly string[]).includes(mode))\n throw new UsageError(`mode is one of ${SECTION_MODES.join(', ')}`);\n const p = resolvePaths(io.env);\n const modes = { ...(await readSectionModes(p)), [id]: mode };\n await mkdir(dirname(sectionsFile(p)), { recursive: true });\n await writeFile(sectionsFile(p), `${JSON.stringify(modes, null, 2)}\\n`);\n}\n\nasync function sizeOf(path: string): Promise<{ exists: boolean; tokens: number }> {\n try {\n if (!(await stat(path)).isFile()) return { exists: false, tokens: 0 };\n return { exists: true, tokens: estimateTokens(await readFile(path, 'utf8')) };\n } catch {\n return { exists: false, tokens: 0 };\n }\n}\n\n/** The newest run started from this repository, if any. */\nasync function latestRun(paths: OmnexxPaths, root: string): Promise<RunStore | undefined> {\n for (const id of [...(await listRunIds(paths))].reverse()) {\n const store = new RunStore(paths, id);\n const s = await store.readState().catch(() => undefined);\n if (s?.repoRoot === root) return store;\n }\n return undefined;\n}\n\nexport async function contextFiles(\n io: CliIO,\n): Promise<{ runId: string | null; files: ContextFile[] }> {\n const paths = resolvePaths(io.env);\n const root = await repoRoot(io.cwd).catch(() => io.cwd);\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const modes = config.context.sections;\n const run = await latestRun(paths, root);\n const state = run ? await run.readState().catch(() => undefined) : undefined;\n const memoryDir = dirname(await repoMemoryFile({ home: paths.home }, root));\n\n const entries: Omit<ContextFile, 'tokens' | 'exists'>[] = [];\n const section = (id: SectionId, path: string, editable: boolean): void => {\n const info = SECTION_INFO[id];\n entries.push({\n id,\n label: info.label,\n about: info.about,\n path,\n editable,\n section: id,\n mode: modes[id],\n });\n };\n if (run) {\n if (state?.taskId) section('role', run.file(join('roles', `${state.taskId}.md`)), true);\n section('codemap', run.file('codemap.md'), false);\n section('intent', run.file('intent.md'), true);\n section('lessons', run.file('notes.md'), false);\n section('progress', run.file('progress.md'), false);\n entries.push({\n id: 'goal',\n label: 'Goal',\n about: 'what this run is for',\n path: run.file('goal.md'),\n editable: true,\n });\n }\n entries.push(\n {\n id: 'repo-role',\n label: 'Repo role note',\n about: 'added to every role card in this repo',\n path: join(root, '.omnexx', 'role.md'),\n editable: true,\n },\n {\n id: 'general-notes',\n label: 'General notes',\n about: 'what the agent keeps about this repo across runs',\n path: join(memoryDir, 'scopes', 'general.md'),\n editable: true,\n },\n {\n id: 'repo-notes',\n label: 'Repo notes',\n about: 'architecture and where things live, across runs',\n path: join(memoryDir, 'scopes', 'repo.md'),\n editable: true,\n },\n );\n const files = await Promise.all(entries.map(async (e) => ({ ...e, ...(await sizeOf(e.path)) })));\n return { runId: run?.runId ?? null, files };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CHAT_FILE,
|
|
4
|
+
chatTitle,
|
|
5
|
+
listChats,
|
|
6
|
+
readSavedChat,
|
|
7
|
+
transcriptEntries,
|
|
8
|
+
userMessageCount
|
|
9
|
+
} from "./chunk-WXMTUSCS.js";
|
|
10
|
+
import "./chunk-FR5DUUJ4.js";
|
|
11
|
+
import "./chunk-Q3MDYD6K.js";
|
|
12
|
+
import "./chunk-2H7UOFLK.js";
|
|
13
|
+
export {
|
|
14
|
+
CHAT_FILE,
|
|
15
|
+
chatTitle,
|
|
16
|
+
listChats,
|
|
17
|
+
readSavedChat,
|
|
18
|
+
transcriptEntries,
|
|
19
|
+
userMessageCount
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=chat-history-SMI5AWQG.js.map
|
|
@@ -33,8 +33,13 @@ function text(c, key) {
|
|
|
33
33
|
if (typeof v !== "string" || !v.trim()) throw new Error(`"${c.type}" needs a "${key}" string`);
|
|
34
34
|
return v;
|
|
35
35
|
}
|
|
36
|
+
function rawText(c, key) {
|
|
37
|
+
const v = c[key];
|
|
38
|
+
if (typeof v !== "string") throw new Error(`"${c.type}" needs a "${key}" string`);
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
36
41
|
async function serveChat(io, runCli, opts) {
|
|
37
|
-
const { Session } = await import("./session-
|
|
42
|
+
const { Session } = await import("./session-2YQYN6VU.js");
|
|
38
43
|
const session = new Session(io, runCli);
|
|
39
44
|
const write = (msg) => {
|
|
40
45
|
println(io.stdout, JSON.stringify(msg));
|
|
@@ -72,6 +77,7 @@ async function serveChat(io, runCli, opts) {
|
|
|
72
77
|
live: session.live,
|
|
73
78
|
todos: session.todos,
|
|
74
79
|
pending: session.pending?.question ?? null,
|
|
80
|
+
pendingLabels: session.pending?.labels ?? null,
|
|
75
81
|
mode: session.mode,
|
|
76
82
|
busy: session.busy !== void 0 || session.chatBusy,
|
|
77
83
|
paused: session.chatPaused,
|
|
@@ -89,6 +95,8 @@ async function serveChat(io, runCli, opts) {
|
|
|
89
95
|
} : null,
|
|
90
96
|
run: session.runId ? { id: session.runId, alive: session.runAlive, ...session.info } : null,
|
|
91
97
|
helpers: session.helpers,
|
|
98
|
+
waiting: session.waiting,
|
|
99
|
+
estimate: session.estimate,
|
|
92
100
|
browser: session.browserFrame ? {
|
|
93
101
|
seq: session.browserFrame.seq,
|
|
94
102
|
url: session.browserFrame.url,
|
|
@@ -104,7 +112,7 @@ async function serveChat(io, runCli, opts) {
|
|
|
104
112
|
};
|
|
105
113
|
session.onChange(schedule);
|
|
106
114
|
const sendModels = async () => {
|
|
107
|
-
const { loadModelChoices } = await import("./model-picker-
|
|
115
|
+
const { loadModelChoices } = await import("./model-picker-IR7QMEWU.js");
|
|
108
116
|
const { items, unreachable } = await loadModelChoices(io);
|
|
109
117
|
write({
|
|
110
118
|
type: "models",
|
|
@@ -114,17 +122,20 @@ async function serveChat(io, runCli, opts) {
|
|
|
114
122
|
});
|
|
115
123
|
};
|
|
116
124
|
const sendSettings = async () => {
|
|
117
|
-
const { providerSettings } = await import("./chat-settings-
|
|
125
|
+
const { providerSettings } = await import("./chat-settings-4XUKDH6H.js");
|
|
118
126
|
write({ type: "settings", ...await providerSettings(io) });
|
|
119
127
|
};
|
|
120
128
|
const sendBrowser = async () => {
|
|
121
|
-
const { browserSettings } = await import("./chat-settings-
|
|
129
|
+
const { browserSettings } = await import("./chat-settings-4XUKDH6H.js");
|
|
122
130
|
write({ type: "browser", ...await browserSettings(io) });
|
|
123
131
|
};
|
|
124
132
|
const sendContextFiles = async () => {
|
|
125
|
-
const { contextFiles } = await import("./chat-context-
|
|
133
|
+
const { contextFiles } = await import("./chat-context-P4ZON7M4.js");
|
|
126
134
|
write({ type: "contextFiles", ...await contextFiles(io) });
|
|
127
135
|
};
|
|
136
|
+
const sendContext = () => {
|
|
137
|
+
write({ type: "contextItems", items: session.contextItems(), busy: session.chatBusy });
|
|
138
|
+
};
|
|
128
139
|
const sendRoles = async () => {
|
|
129
140
|
const { readRoles } = await import("./roles-IHKWWYJL.js");
|
|
130
141
|
const { QuotaLedger } = await import("./quota-ledger-7F6QTZAM.js");
|
|
@@ -198,7 +209,7 @@ async function serveChat(io, runCli, opts) {
|
|
|
198
209
|
return typeof v === "string" ? v : void 0;
|
|
199
210
|
};
|
|
200
211
|
run(
|
|
201
|
-
import("./chat-settings-
|
|
212
|
+
import("./chat-settings-4XUKDH6H.js").then(async ({ addProvider }) => {
|
|
202
213
|
await providersChanged(
|
|
203
214
|
await addProvider(io, {
|
|
204
215
|
provider: str("provider"),
|
|
@@ -212,7 +223,7 @@ async function serveChat(io, runCli, opts) {
|
|
|
212
223
|
}
|
|
213
224
|
case "testProvider":
|
|
214
225
|
run(
|
|
215
|
-
import("./chat-settings-
|
|
226
|
+
import("./chat-settings-4XUKDH6H.js").then(async ({ testProvider }) => {
|
|
216
227
|
write({ type: "providerTest", ...await testProvider(io, text(c, "name")) });
|
|
217
228
|
})
|
|
218
229
|
);
|
|
@@ -222,18 +233,51 @@ async function serveChat(io, runCli, opts) {
|
|
|
222
233
|
return;
|
|
223
234
|
case "setSectionMode":
|
|
224
235
|
run(
|
|
225
|
-
import("./chat-context-
|
|
236
|
+
import("./chat-context-P4ZON7M4.js").then(async ({ setSectionMode }) => {
|
|
226
237
|
await setSectionMode(io, text(c, "id"), text(c, "mode"));
|
|
227
238
|
await sendContextFiles();
|
|
228
239
|
})
|
|
229
240
|
);
|
|
230
241
|
return;
|
|
242
|
+
case "chats":
|
|
243
|
+
run(
|
|
244
|
+
session.savedChats().then((chats) => {
|
|
245
|
+
write({ type: "chats", chats });
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
return;
|
|
249
|
+
case "resumeChat":
|
|
250
|
+
run(
|
|
251
|
+
(async () => {
|
|
252
|
+
clearedAt = session.entries.at(-1)?.id ?? clearedAt;
|
|
253
|
+
sentUpTo = clearedAt;
|
|
254
|
+
await session.resumeChat(text(c, "id"));
|
|
255
|
+
write({ type: "ready", version: opts.version });
|
|
256
|
+
emit();
|
|
257
|
+
})()
|
|
258
|
+
);
|
|
259
|
+
return;
|
|
260
|
+
case "contextList":
|
|
261
|
+
sendContext();
|
|
262
|
+
return;
|
|
263
|
+
case "contextUpdate":
|
|
264
|
+
session.changeContext({ op: "edit", id: text(c, "id"), text: rawText(c, "text") });
|
|
265
|
+
sendContext();
|
|
266
|
+
return;
|
|
267
|
+
case "contextDelete":
|
|
268
|
+
session.changeContext({ op: "delete", id: text(c, "id") });
|
|
269
|
+
sendContext();
|
|
270
|
+
return;
|
|
271
|
+
case "contextReset":
|
|
272
|
+
session.changeContext({ op: "reset" });
|
|
273
|
+
sendContext();
|
|
274
|
+
return;
|
|
231
275
|
case "browser":
|
|
232
276
|
run(sendBrowser());
|
|
233
277
|
return;
|
|
234
278
|
case "setBrowser":
|
|
235
279
|
run(
|
|
236
|
-
import("./chat-settings-
|
|
280
|
+
import("./chat-settings-4XUKDH6H.js").then(async ({ setBrowserSettings }) => {
|
|
237
281
|
await setBrowserSettings(io, c);
|
|
238
282
|
session.push("system", "browser settings saved; new runs use them");
|
|
239
283
|
await sendBrowser();
|
|
@@ -254,7 +298,7 @@ async function serveChat(io, runCli, opts) {
|
|
|
254
298
|
return;
|
|
255
299
|
case "removeProvider":
|
|
256
300
|
run(
|
|
257
|
-
import("./chat-settings-
|
|
301
|
+
import("./chat-settings-4XUKDH6H.js").then(async ({ removeProvider }) => {
|
|
258
302
|
await providersChanged(await removeProvider(io, text(c, "name")));
|
|
259
303
|
})
|
|
260
304
|
);
|
|
@@ -299,4 +343,4 @@ async function serveChat(io, runCli, opts) {
|
|
|
299
343
|
export {
|
|
300
344
|
serveChat
|
|
301
345
|
};
|
|
302
|
-
//# sourceMappingURL=chat-server-
|
|
346
|
+
//# sourceMappingURL=chat-server-FMF26CBV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/chat-server.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { resolvePaths } from '../core/paths.js';\nimport { readFile } from 'node:fs/promises';\nimport { createInterface } from 'node:readline';\nimport { describeError } from '../errors.js';\nimport type { Telemetry } from '../telemetry/aggregate.js';\nimport type { TodoItem } from '../tools/todo.js';\nimport type { Entry, Helper, Mode, RunCli } from '../tui/session.js';\nimport { println, type CliIO } from './io.js';\n\n/**\n * `omnexx chat-server`: the interactive session behind a JSON-lines protocol on stdin/stdout, for\n * editor panels (the VS Code chat). It drives the same Session as the terminal UI, so slash\n * commands, approvals, model switching and plan mode behave the same way.\n *\n * In (one JSON object per line): send {text} · answer {yes} · stop · pause · resume · mode {mode} ·\n * yolo {on} ·\n * model {ref} · models · connect {value, name?} · undo · clear · compact · sync (resend all) ·\n * settings · addProvider {provider?, key?, url?} · removeProvider {name} · testProvider {name} ·\n * roles · setRoles {roles} · contextFiles · setSectionMode {id, mode} · contextList ·\n * contextUpdate {id, text} · contextDelete {id} · contextReset · chats · resumeChat {id} · browser ·\n * setBrowser {engine, profile?, sessionName?, headless}.\n * Out: ready {version} · state (below; `entries` holds only lines not sent before) · models\n * {items, unreachable, current} · settings {catalog, connected} · providerTest {name, ok, models,\n * detail} · contextItems {items, busy} · chats {chats} · browser {engine, profile, sessionName, headless} ·\n * error {message}.\n */\n\nexport interface ChatUsage {\n /** When the chat's first event happened (epoch ms), for an uptime clock. */\n startedAt: number | null;\n tokens: Telemetry['tokens'];\n usd: number;\n turns: number;\n toolCalls: number;\n toolErrors: number;\n compactions: number;\n}\n\nexport interface ChatState {\n type: 'state';\n entries: Entry[];\n /** The reply as it streams in; empty once it lands in `entries`. */\n live: string;\n todos: TodoItem[];\n /** A question waiting for allow / deny, or null. */\n pending: string | null;\n /** Button names when the question is an offer (Pull it now / Not now) rather than a guard. */\n pendingLabels: { yes: string; no: string } | null;\n mode: Mode;\n busy: boolean;\n paused: boolean;\n autoApprove: boolean;\n model: string | null;\n /** Tokens in use, the compaction limit, and what they are made of. */\n context: {\n used: number;\n limit: number;\n parts?: { system: number; tools: number; conversation: number };\n } | null;\n usage: ChatUsage | null;\n run: { id: string; alive: boolean; phase?: string; done?: number; tasks?: number } | null;\n /** Helpers (subagents) for the current message. */\n helpers: Helper[];\n /** A model request in flight: when it went out and how long it usually takes (null: unknown). */\n waiting: { startedAt: number; estimateMs: number | null } | null;\n /** \"est. ~3 min, ~40k tokens, ~$0.02\" before work, \"~2 min left\" during it, or null. */\n estimate: string | null;\n /** The latest browser frame's number and page; the image comes as a `browserFrame` message. */\n browser: { seq: number; url: string; action: string; at: number } | null;\n}\n\n/** Frames larger than this are skipped rather than pushed through the pipe. */\nconst MAX_FRAME_BYTES = 4 * 1024 * 1024;\n\nconst MODES = new Set<Mode>(['chat', 'plan', 'run']);\n/** Coalesce bursts of changes (a streaming reply fires one per token) into one state line. */\nconst EMIT_EVERY_MS = 40;\n\ntype Command = { type: string } & Record<string, unknown>;\n\nfunction parseCommand(line: string): Command {\n let data: unknown;\n try {\n data = JSON.parse(line);\n } catch {\n throw new Error('expected one JSON object per line');\n }\n if (typeof data !== 'object' || data === null || typeof (data as Command).type !== 'string')\n throw new Error('expected an object with a \"type\"');\n return data as Command;\n}\n\nfunction text(c: Command, key: string): string {\n const v = c[key];\n if (typeof v !== 'string' || !v.trim()) throw new Error(`\"${c.type}\" needs a \"${key}\" string`);\n return v;\n}\n\n/** A string that may be empty (an edit can clear a block's text). */\nfunction rawText(c: Command, key: string): string {\n const v = c[key];\n if (typeof v !== 'string') throw new Error(`\"${c.type}\" needs a \"${key}\" string`);\n return v;\n}\n\nexport async function serveChat(\n io: CliIO,\n runCli: RunCli,\n opts: { version: string },\n): Promise<number> {\n const { Session } = await import('../tui/session.js');\n const session = new Session(io, runCli);\n const write = (msg: object): void => {\n println(io.stdout, JSON.stringify(msg));\n };\n const fail = (err: unknown): void => {\n write({ type: 'error', message: describeError(err) });\n };\n\n let sentUpTo = -1;\n let sentFrame = 0;\n /** Send the browser's latest screenshot once per new frame. */\n const sendFrame = async (): Promise<void> => {\n const f = session.browserFrame;\n if (!f || f.seq === sentFrame) return;\n sentFrame = f.seq;\n const data = await readFile(f.path).catch(() => undefined);\n if (!data || data.length > MAX_FRAME_BYTES) return;\n write({\n type: 'browserFrame',\n seq: f.seq,\n mime: f.mime,\n url: f.url,\n action: f.action,\n data: data.toString('base64'),\n });\n };\n /** The last entry before the latest new chat; a reloaded panel starts after it. */\n let clearedAt = -1;\n let timer: NodeJS.Timeout | undefined;\n const emit = (): void => {\n timer = undefined;\n const fresh = session.entries.filter((e) => e.id > sentUpTo);\n sentUpTo = session.entries.at(-1)?.id ?? sentUpTo;\n const t = session.chatTelemetry;\n const state: ChatState = {\n type: 'state',\n entries: fresh,\n live: session.live,\n todos: session.todos,\n pending: session.pending?.question ?? null,\n pendingLabels: session.pending?.labels ?? null,\n mode: session.mode,\n busy: session.busy !== undefined || session.chatBusy,\n paused: session.chatPaused,\n autoApprove: session.autoApprove,\n model: session.chatModelName ?? null,\n context: session.context ?? null,\n usage: t\n ? {\n startedAt: t.startedAt ?? null,\n tokens: t.tokens,\n usd: t.usd,\n turns: t.turns,\n toolCalls: t.toolCalls,\n toolErrors: t.toolErrors,\n compactions: t.compactions,\n }\n : null,\n run: session.runId ? { id: session.runId, alive: session.runAlive, ...session.info } : null,\n helpers: session.helpers,\n waiting: session.waiting,\n estimate: session.estimate,\n browser: session.browserFrame\n ? {\n seq: session.browserFrame.seq,\n url: session.browserFrame.url,\n action: session.browserFrame.action,\n at: session.browserFrame.at,\n }\n : null,\n };\n write(state);\n void sendFrame();\n };\n const schedule = (): void => {\n timer ??= setTimeout(emit, EMIT_EVERY_MS);\n };\n session.onChange(schedule);\n\n const sendModels = async (): Promise<void> => {\n const { loadModelChoices } = await import('../tui/model-picker.js');\n const { items, unreachable } = await loadModelChoices(io);\n write({\n type: 'models',\n items: items.filter((i) => i.kind === 'model'),\n unreachable,\n current: session.chatModelName ?? null,\n });\n };\n const sendSettings = async (): Promise<void> => {\n const { providerSettings } = await import('./chat-settings.js');\n write({ type: 'settings', ...(await providerSettings(io)) });\n };\n const sendBrowser = async (): Promise<void> => {\n const { browserSettings } = await import('./chat-settings.js');\n write({ type: 'browser', ...(await browserSettings(io)) });\n };\n const sendContextFiles = async (): Promise<void> => {\n const { contextFiles } = await import('./chat-context.js');\n write({ type: 'contextFiles', ...(await contextFiles(io)) });\n };\n /** The context inspector's items: what the next request sends, with token counts. */\n const sendContext = (): void => {\n write({ type: 'contextItems', items: session.contextItems(), busy: session.chatBusy });\n };\n /** Roles as saved, plus which models are out of quota right now (ref → when it ran out). */\n const sendRoles = async (): Promise<void> => {\n const { readRoles } = await import('../config/roles.js');\n const { QuotaLedger } = await import('../providers/quota-ledger.js');\n const paths = resolvePaths(io.env);\n write({\n type: 'roles',\n roles: await readRoles(paths),\n exhausted: new QuotaLedger(join(paths.configHome, 'quota.json')).all(),\n });\n };\n /** After a provider change: say what happened, then refresh the settings page and models. */\n const providersChanged = async (message: string): Promise<void> => {\n session.push('system', message);\n await sendSettings();\n await sendModels();\n };\n\n /** Commands still running, so a closed pipe lets them finish before the process exits. */\n const inflight = new Set<Promise<unknown>>();\n const run = (p: Promise<unknown>): void => {\n const tracked = p.catch(fail).finally(() => {\n inflight.delete(tracked);\n schedule();\n });\n inflight.add(tracked);\n };\n\n const handle = (c: Command): void => {\n switch (c.type) {\n case 'send':\n run(session.submit(text(c, 'text')));\n return;\n case 'answer':\n if (!session.pending) throw new Error('nothing is waiting for an answer');\n run(session.submit(c.yes === true ? 'y' : 'n'));\n return;\n case 'stop':\n case 'pause':\n case 'resume':\n case 'undo':\n case 'compact':\n run(session.command(c.type));\n return;\n case 'clear':\n run(\n session.command('clear').then(() => {\n // A new chat starts the panel over: nothing from before the clear is sent again.\n clearedAt = session.entries.at(-1)?.id ?? clearedAt;\n sentUpTo = clearedAt;\n write({ type: 'ready', version: opts.version });\n emit();\n }),\n );\n return;\n case 'mode': {\n const mode = c.mode as Mode;\n if (!MODES.has(mode)) throw new Error('mode is one of chat, plan, run');\n if (session.mode !== mode) session.setMode(mode);\n return;\n }\n case 'yolo':\n // /yolo toggles; only run it when the state differs from what was asked, and without\n // echoing it as a message.\n if ((c.on === true) !== session.autoApprove) run(session.command('yolo'));\n return;\n case 'model':\n run(session.switchModel(text(c, 'ref')));\n return;\n case 'models':\n run(sendModels());\n return;\n case 'settings':\n run(sendSettings());\n return;\n case 'addProvider': {\n const str = (k: string): string | undefined => {\n const v = c[k];\n return typeof v === 'string' ? v : undefined;\n };\n run(\n import('./chat-settings.js').then(async ({ addProvider }) => {\n await providersChanged(\n await addProvider(io, {\n provider: str('provider'),\n key: str('key'),\n url: str('url'),\n }),\n );\n }),\n );\n return;\n }\n case 'testProvider':\n run(\n import('./chat-settings.js').then(async ({ testProvider }) => {\n write({ type: 'providerTest', ...(await testProvider(io, text(c, 'name'))) });\n }),\n );\n return;\n case 'contextFiles':\n run(sendContextFiles());\n return;\n case 'setSectionMode':\n run(\n import('./chat-context.js').then(async ({ setSectionMode }) => {\n await setSectionMode(io, text(c, 'id'), text(c, 'mode'));\n await sendContextFiles();\n }),\n );\n return;\n case 'chats':\n run(\n session.savedChats().then((chats) => {\n write({ type: 'chats', chats });\n }),\n );\n return;\n case 'resumeChat':\n run(\n (async () => {\n // Mark the panel's start first, so a reload shows only the reopened chat.\n clearedAt = session.entries.at(-1)?.id ?? clearedAt;\n sentUpTo = clearedAt;\n await session.resumeChat(text(c, 'id'));\n write({ type: 'ready', version: opts.version });\n emit();\n })(),\n );\n return;\n case 'contextList':\n sendContext();\n return;\n case 'contextUpdate':\n session.changeContext({ op: 'edit', id: text(c, 'id'), text: rawText(c, 'text') });\n sendContext();\n return;\n case 'contextDelete':\n session.changeContext({ op: 'delete', id: text(c, 'id') });\n sendContext();\n return;\n case 'contextReset':\n session.changeContext({ op: 'reset' });\n sendContext();\n return;\n case 'browser':\n run(sendBrowser());\n return;\n case 'setBrowser':\n run(\n import('./chat-settings.js').then(async ({ setBrowserSettings }) => {\n await setBrowserSettings(io, c);\n session.push('system', 'browser settings saved; new runs use them');\n await sendBrowser();\n }),\n );\n return;\n case 'roles':\n run(sendRoles());\n return;\n case 'setRoles':\n run(\n import('../config/roles.js').then(async ({ parseRoles, writeRoles }) => {\n await writeRoles(resolvePaths(io.env), parseRoles(c.roles));\n session.push('system', 'model roles saved; new chats and runs use them');\n await sendRoles();\n }),\n );\n return;\n case 'removeProvider':\n run(\n import('./chat-settings.js').then(async ({ removeProvider }) => {\n await providersChanged(await removeProvider(io, text(c, 'name')));\n }),\n );\n return;\n case 'connect': {\n const name = typeof c.name === 'string' && c.name.trim() ? ` --name ${c.name.trim()}` : '';\n run(session.submit(`/model ${text(c, 'value')}${name}`));\n return;\n }\n case 'sync':\n // A reloaded panel: start it over with the conversation since the last new chat.\n sentUpTo = clearedAt;\n sentFrame = 0;\n write({ type: 'ready', version: opts.version });\n emit();\n return;\n default:\n throw new Error(`unknown command \"${c.type}\"`);\n }\n };\n\n await session.loadCommands();\n await session.continueChat().catch(fail);\n await session.greet(false).catch(fail);\n write({ type: 'ready', version: opts.version });\n emit();\n\n const lines = createInterface({ input: io.stdin, crlfDelay: Infinity });\n for await (const line of lines) {\n if (!line.trim()) continue;\n try {\n handle(parseCommand(line));\n } catch (err) {\n fail(err);\n }\n }\n // The editor closed the pipe: end any turn in progress, let running commands finish, then\n // flush the last state.\n if (session.chatBusy) await session.command('stop');\n await Promise.allSettled([...inflight]);\n if (timer) {\n clearTimeout(timer);\n emit();\n }\n return 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
setTable
|
|
4
|
+
} from "./chunk-QS6LKKWO.js";
|
|
2
5
|
import {
|
|
3
6
|
connect,
|
|
4
7
|
describeConnect
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RSQGFSCA.js";
|
|
6
9
|
import {
|
|
7
10
|
PROVIDER_TEMPLATES,
|
|
8
11
|
removeEndpointBlock
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-7PC4SSYS.js";
|
|
11
|
-
import {
|
|
12
|
-
setTable
|
|
13
|
-
} from "./chunk-QS6LKKWO.js";
|
|
12
|
+
} from "./chunk-MC4NBIIA.js";
|
|
14
13
|
import {
|
|
15
14
|
clearKey,
|
|
16
15
|
findProviderKey,
|
|
17
16
|
maskKey
|
|
18
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-L2YBK4AY.js";
|
|
19
18
|
import {
|
|
20
19
|
loadConfig
|
|
21
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-V5RPCSON.js";
|
|
21
|
+
import "./chunk-FR5DUUJ4.js";
|
|
22
|
+
import "./chunk-7PC4SSYS.js";
|
|
22
23
|
import {
|
|
23
24
|
resolvePaths,
|
|
24
25
|
userConfigFile
|
|
@@ -227,4 +228,4 @@ export {
|
|
|
227
228
|
setBrowserSettings,
|
|
228
229
|
testProvider
|
|
229
230
|
};
|
|
230
|
-
//# sourceMappingURL=chat-settings-
|
|
231
|
+
//# sourceMappingURL=chat-settings-4XUKDH6H.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/chat-settings.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { parse } from 'smol-toml';\nimport { clearKey, findProviderKey, maskKey } from '../auth/keys.js';\nimport { loadConfig } from '../config/load.js';\nimport { resolvePaths, userConfigFile } from '../core/paths.js';\nimport { UsageError } from '../errors.js';\nimport { setTable, type TomlValue } from '../integrations/toml-edit.js';\nimport { PROVIDER_TEMPLATES, removeEndpointBlock } from './commands/providers.js';\nimport { connect, describeConnect } from './connect.js';\nimport type { CliIO } from './io.js';\n\n/**\n * Provider settings for editor panels (`omnexx chat-server`): what can be added, what is set up,\n * and adding or removing one. Keys go to the 0600 credentials file and are only ever shown masked.\n */\n\nexport interface CatalogProvider {\n name: string;\n label: string;\n baseUrl: string;\n /** A local server the person runs themselves: no key. */\n local: boolean;\n /** Where to get a key. */\n signup?: string;\n /** How its keys start, for a placeholder. */\n keyPrefix?: string;\n}\n\nexport interface ConnectedProvider {\n name: string;\n label: string;\n baseUrl: string;\n local: boolean;\n free: boolean;\n /** The key, masked (`sk-…wxyz`), or null when none is set. */\n key: string | null;\n /** Where the key comes from: an environment variable or the credentials file. */\n keySource: string | null;\n}\n\nexport interface ProviderSettings {\n catalog: CatalogProvider[];\n connected: ConnectedProvider[];\n}\n\nconst ANTHROPIC: CatalogProvider = {\n name: 'anthropic',\n label: 'Anthropic (Claude)',\n baseUrl: 'https://api.anthropic.com',\n local: false,\n signup: 'https://console.anthropic.com/settings/keys',\n keyPrefix: 'sk-ant-',\n};\n\nconst NAME = /^[a-z][a-z0-9_-]{0,39}$/;\n\nexport function providerCatalog(): CatalogProvider[] {\n const listed = Object.entries(PROVIDER_TEMPLATES)\n .filter(([name]) => name !== 'custom')\n .map(([name, t]) => ({\n name,\n label: t.label ?? name,\n baseUrl: t.baseUrl,\n local: t.local === true,\n ...(t.signup ? { signup: t.signup } : {}),\n ...(t.keyPrefix ? { keyPrefix: t.keyPrefix } : {}),\n }));\n return [ANTHROPIC, ...listed];\n}\n\nexport async function providerSettings(io: CliIO): Promise<ProviderSettings> {\n const paths = resolvePaths(io.env);\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const catalog = providerCatalog();\n const labelOf = (name: string): string => catalog.find((c) => c.name === name)?.label ?? name;\n const connected: ConnectedProvider[] = [];\n const anthropic = await findProviderKey(paths, io.env, 'anthropic');\n if (anthropic)\n connected.push({\n name: 'anthropic',\n label: ANTHROPIC.label,\n baseUrl: ANTHROPIC.baseUrl,\n local: false,\n free: false,\n key: maskKey(anthropic.key),\n keySource: anthropic.source,\n });\n for (const [name, ep] of Object.entries(config.providers.endpoints)) {\n const found = await findProviderKey(paths, io.env, name, ep.api_key_env);\n connected.push({\n name,\n label: labelOf(name),\n baseUrl: ep.base_url,\n local:\n PROVIDER_TEMPLATES[name]?.local === true ||\n /\\/\\/(localhost|127\\.0\\.0\\.1)[:/]/.test(ep.base_url),\n free: ep.free,\n key: found ? maskKey(found.key) : null,\n keySource: found?.source ?? null,\n });\n }\n return { catalog, connected };\n}\n\nexport interface AddProvider {\n /** A catalog name, or a new name for a custom URL. */\n provider?: string | undefined;\n key?: string | undefined;\n /** For a custom or local OpenAI-compatible server. */\n url?: string | undefined;\n}\n\n/** Connect a provider from the settings form; returns what to tell the person. */\nexport async function addProvider(io: CliIO, a: AddProvider): Promise<string> {\n const url = a.url?.trim();\n const provider = a.provider?.trim().toLowerCase();\n const key = a.key?.trim() ? a.key.trim() : undefined;\n if (url && !/^https?:\\/\\/\\S+$/i.test(url))\n throw new UsageError('the URL must start with http:// or https://');\n if (provider && !NAME.test(provider))\n throw new UsageError(\n `\"${provider}\" is not a usable name`,\n 'lowercase letters, digits, _ and -',\n );\n if (!url && !provider && !key) throw new UsageError('pick a provider or enter a URL');\n // A catalog provider is connected by name; a URL gets the name it was given, if any.\n const target = url ?? provider ?? key ?? '';\n const r = await connect(io, target, url ? key : target === key ? undefined : key, {\n ...(url && provider ? { name: provider } : {}),\n });\n return describeConnect(r);\n}\n\n/** Remove a provider: its endpoint block and its stored key. Environment variables stay. */\nexport async function removeProvider(io: CliIO, name: string): Promise<string> {\n const lower = name.trim().toLowerCase();\n if (!NAME.test(lower)) throw new UsageError(`\"${name}\" is not a provider name`);\n const paths = resolvePaths(io.env);\n const removedBlock =\n lower === 'anthropic' ? false : await removeEndpointBlock(userConfigFile(paths), lower);\n const removedKey = await clearKey(paths, lower);\n if (!removedBlock && !removedKey)\n return `${lower} had nothing to remove (a key from an environment variable stays set)`;\n return `removed ${lower}${removedKey ? ' and its stored key' : ''}`;\n}\n\nexport interface ProviderTest {\n name: string;\n ok: boolean;\n /** How many models the provider listed, when it answered. */\n models: number;\n detail: string;\n}\n\nconst TEST_TIMEOUT_MS = 8_000;\n\n/**\n * Check a provider from the settings page: list its models with the stored key. Anthropic is\n * built in and has no list endpoint here, so it only reports whether a key is set.\n */\nexport async function testProvider(io: CliIO, name: string): Promise<ProviderTest> {\n const lower = name.trim().toLowerCase();\n if (!NAME.test(lower)) throw new UsageError(`\"${name}\" is not a provider name`);\n const paths = resolvePaths(io.env);\n if (lower === 'anthropic') {\n const found = await findProviderKey(paths, io.env, 'anthropic');\n return {\n name: lower,\n ok: found !== undefined,\n models: found ? 3 : 0,\n detail: found ? 'key set (opus, sonnet, haiku)' : 'no key set',\n };\n }\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const ep = config.providers.endpoints[lower];\n if (!ep) return { name: lower, ok: false, models: 0, detail: 'not set up' };\n const key = (await findProviderKey(paths, io.env, lower, ep.api_key_env))?.key;\n const { discoverModels } = await import('../providers/discovery.js');\n const models = await discoverModels(\n { baseUrl: ep.base_url, ...(key ? { apiKey: key } : {}) },\n { timeoutMs: TEST_TIMEOUT_MS, ...(io.fetch ? { fetch: io.fetch } : {}) },\n );\n if (models.length)\n return { name: lower, ok: true, models: models.length, detail: `${models.length} models` };\n return {\n name: lower,\n ok: false,\n models: 0,\n detail: key || ep.free ? \"couldn't reach it or it listed no models\" : 'no key set',\n };\n}\n\n/** The browser tool's engine, profile and session, as the settings page shows and saves them. */\nexport interface BrowserSettings {\n engine: 'chromium' | 'camoufox';\n /** A folder (or Chrome profile name) whose logins the agent reuses; null when unset. */\n profile: string | null;\n sessionName: string | null;\n headless: boolean;\n}\n\nconst ENGINES = ['chromium', 'camoufox'] as const;\n// Same rule as browser.session_name in src/config/sections/browser.ts.\nconst SESSION_NAME = /^[\\w.-]{1,64}$/;\n/** C0 and C1 control characters, newlines included. */\nconst hasControl = (text: string): boolean =>\n Array.from(text).some((ch) => {\n const code = ch.codePointAt(0) ?? 0;\n return code < 0x20 || (code >= 0x7f && code <= 0x9f);\n });\n\nexport async function browserSettings(io: CliIO): Promise<BrowserSettings> {\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const b = config.browser;\n return {\n engine: b.engine,\n profile: b.profile ?? null,\n sessionName: b.session_name ?? null,\n headless: b.headless,\n };\n}\n\n/** A trimmed optional text field: empty is unset; newlines and control characters are refused. */\nfunction optionalText(raw: unknown, label: string): string | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (typeof raw !== 'string') throw new UsageError(`${label} must be text`);\n const trimmed = raw.trim();\n if (hasControl(trimmed))\n throw new UsageError(`${label} can't contain newlines or control characters`);\n return trimmed || undefined;\n}\n\n/** Check what the settings form sent; throws a UsageError naming the bad field. */\nexport function parseBrowserSettings(raw: unknown): BrowserSettings {\n const obj = typeof raw === 'object' && raw !== null ? (raw as Record<string, unknown>) : {};\n const engine = obj.engine ?? 'chromium';\n if (typeof engine !== 'string' || !(ENGINES as readonly string[]).includes(engine))\n throw new UsageError('the browser engine must be chromium or camoufox');\n const profile = optionalText(obj.profile, 'the browser profile');\n const sessionName = optionalText(obj.sessionName, 'the session name');\n if (sessionName && !SESSION_NAME.test(sessionName))\n throw new UsageError(\n `\"${sessionName}\" is not a usable session name`,\n 'up to 64 letters, digits, _ . and -',\n );\n if (obj.headless !== undefined && typeof obj.headless !== 'boolean')\n throw new UsageError('headless must be on or off');\n return {\n engine: engine as BrowserSettings['engine'],\n profile: profile ?? null,\n sessionName: sessionName ?? null,\n headless: obj.headless !== false,\n };\n}\n\nasync function readText(file: string): Promise<string> {\n try {\n return await readFile(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return '';\n throw err;\n }\n}\n\n/** The [browser] keys already in the user config, kept when the form saves its four. */\nfunction existingBrowser(text: string): Record<string, TomlValue> {\n const table = (parse(text) as Record<string, unknown>).browser;\n return typeof table === 'object' && table !== null ? (table as Record<string, TomlValue>) : {};\n}\n\n/** Save the browser settings to the user's config.toml; other [browser] keys stay as they were. */\nexport async function setBrowserSettings(io: CliIO, raw: unknown): Promise<BrowserSettings> {\n const next = parseBrowserSettings(raw);\n const file = userConfigFile(resolvePaths(io.env));\n const text = await readText(file);\n const updated = setTable(text, ['browser'], {\n ...existingBrowser(text),\n engine: next.engine,\n profile: next.profile ?? undefined,\n session_name: next.sessionName ?? undefined,\n headless: next.headless,\n });\n await mkdir(dirname(file), { recursive: true });\n await writeFile(file, updated, 'utf8');\n return next;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/chat-settings.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { parse } from 'smol-toml';\nimport { clearKey, findProviderKey, maskKey } from '../auth/keys.js';\nimport { loadConfig } from '../config/load.js';\nimport { resolvePaths, userConfigFile } from '../core/paths.js';\nimport { UsageError } from '../errors.js';\nimport { setTable, type TomlValue } from '../integrations/toml-edit.js';\nimport { PROVIDER_TEMPLATES, removeEndpointBlock } from './commands/providers.js';\nimport { connect, describeConnect } from './connect.js';\nimport type { CliIO } from './io.js';\n\n/**\n * Provider settings for editor panels (`omnexx chat-server`): what can be added, what is set up,\n * and adding or removing one. Keys go to the 0600 credentials file and are only ever shown masked.\n */\n\nexport interface CatalogProvider {\n name: string;\n label: string;\n baseUrl: string;\n /** A local server the person runs themselves: no key. */\n local: boolean;\n /** Where to get a key. */\n signup?: string;\n /** How its keys start, for a placeholder. */\n keyPrefix?: string;\n}\n\nexport interface ConnectedProvider {\n name: string;\n label: string;\n baseUrl: string;\n local: boolean;\n free: boolean;\n /** The key, masked (`sk-…wxyz`), or null when none is set. */\n key: string | null;\n /** Where the key comes from: an environment variable or the credentials file. */\n keySource: string | null;\n}\n\nexport interface ProviderSettings {\n catalog: CatalogProvider[];\n connected: ConnectedProvider[];\n}\n\nconst ANTHROPIC: CatalogProvider = {\n name: 'anthropic',\n label: 'Anthropic (Claude)',\n baseUrl: 'https://api.anthropic.com',\n local: false,\n signup: 'https://console.anthropic.com/settings/keys',\n keyPrefix: 'sk-ant-',\n};\n\nconst NAME = /^[a-z][a-z0-9_-]{0,39}$/;\n\nexport function providerCatalog(): CatalogProvider[] {\n const listed = Object.entries(PROVIDER_TEMPLATES)\n .filter(([name]) => name !== 'custom')\n .map(([name, t]) => ({\n name,\n label: t.label ?? name,\n baseUrl: t.baseUrl,\n local: t.local === true,\n ...(t.signup ? { signup: t.signup } : {}),\n ...(t.keyPrefix ? { keyPrefix: t.keyPrefix } : {}),\n }));\n return [ANTHROPIC, ...listed];\n}\n\nexport async function providerSettings(io: CliIO): Promise<ProviderSettings> {\n const paths = resolvePaths(io.env);\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const catalog = providerCatalog();\n const labelOf = (name: string): string => catalog.find((c) => c.name === name)?.label ?? name;\n const connected: ConnectedProvider[] = [];\n const anthropic = await findProviderKey(paths, io.env, 'anthropic');\n if (anthropic)\n connected.push({\n name: 'anthropic',\n label: ANTHROPIC.label,\n baseUrl: ANTHROPIC.baseUrl,\n local: false,\n free: false,\n key: maskKey(anthropic.key),\n keySource: anthropic.source,\n });\n for (const [name, ep] of Object.entries(config.providers.endpoints)) {\n const found = await findProviderKey(paths, io.env, name, ep.api_key_env);\n connected.push({\n name,\n label: labelOf(name),\n baseUrl: ep.base_url,\n local:\n PROVIDER_TEMPLATES[name]?.local === true ||\n /\\/\\/(localhost|127\\.0\\.0\\.1)[:/]/.test(ep.base_url),\n free: ep.free,\n key: found ? maskKey(found.key) : null,\n keySource: found?.source ?? null,\n });\n }\n return { catalog, connected };\n}\n\nexport interface AddProvider {\n /** A catalog name, or a new name for a custom URL. */\n provider?: string | undefined;\n key?: string | undefined;\n /** For a custom or local OpenAI-compatible server. */\n url?: string | undefined;\n}\n\n/** Connect a provider from the settings form; returns what to tell the person. */\nexport async function addProvider(io: CliIO, a: AddProvider): Promise<string> {\n const url = a.url?.trim();\n const provider = a.provider?.trim().toLowerCase();\n const key = a.key?.trim() ? a.key.trim() : undefined;\n if (url && !/^https?:\\/\\/\\S+$/i.test(url))\n throw new UsageError('the URL must start with http:// or https://');\n if (provider && !NAME.test(provider))\n throw new UsageError(\n `\"${provider}\" is not a usable name`,\n 'lowercase letters, digits, _ and -',\n );\n if (!url && !provider && !key) throw new UsageError('pick a provider or enter a URL');\n // A catalog provider is connected by name; a URL gets the name it was given, if any.\n const target = url ?? provider ?? key ?? '';\n const r = await connect(io, target, url ? key : target === key ? undefined : key, {\n ...(url && provider ? { name: provider } : {}),\n });\n return describeConnect(r);\n}\n\n/** Remove a provider: its endpoint block and its stored key. Environment variables stay. */\nexport async function removeProvider(io: CliIO, name: string): Promise<string> {\n const lower = name.trim().toLowerCase();\n if (!NAME.test(lower)) throw new UsageError(`\"${name}\" is not a provider name`);\n const paths = resolvePaths(io.env);\n const removedBlock =\n lower === 'anthropic' ? false : await removeEndpointBlock(userConfigFile(paths), lower);\n const removedKey = await clearKey(paths, lower);\n if (!removedBlock && !removedKey)\n return `${lower} had nothing to remove (a key from an environment variable stays set)`;\n return `removed ${lower}${removedKey ? ' and its stored key' : ''}`;\n}\n\nexport interface ProviderTest {\n name: string;\n ok: boolean;\n /** How many models the provider listed, when it answered. */\n models: number;\n detail: string;\n}\n\nconst TEST_TIMEOUT_MS = 8_000;\n\n/**\n * Check a provider from the settings page: list its models with the stored key. Anthropic is\n * built in and has no list endpoint here, so it only reports whether a key is set.\n */\nexport async function testProvider(io: CliIO, name: string): Promise<ProviderTest> {\n const lower = name.trim().toLowerCase();\n if (!NAME.test(lower)) throw new UsageError(`\"${name}\" is not a provider name`);\n const paths = resolvePaths(io.env);\n if (lower === 'anthropic') {\n const found = await findProviderKey(paths, io.env, 'anthropic');\n return {\n name: lower,\n ok: found !== undefined,\n models: found ? 3 : 0,\n detail: found ? 'key set (opus, sonnet, haiku)' : 'no key set',\n };\n }\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const ep = config.providers.endpoints[lower];\n if (!ep) return { name: lower, ok: false, models: 0, detail: 'not set up' };\n const key = (await findProviderKey(paths, io.env, lower, ep.api_key_env))?.key;\n const { discoverModels } = await import('../providers/discovery.js');\n const models = await discoverModels(\n { baseUrl: ep.base_url, ...(key ? { apiKey: key } : {}) },\n { timeoutMs: TEST_TIMEOUT_MS, ...(io.fetch ? { fetch: io.fetch } : {}) },\n );\n if (models.length)\n return { name: lower, ok: true, models: models.length, detail: `${models.length} models` };\n return {\n name: lower,\n ok: false,\n models: 0,\n detail: key || ep.free ? \"couldn't reach it or it listed no models\" : 'no key set',\n };\n}\n\n/** The browser tool's engine, profile and session, as the settings page shows and saves them. */\nexport interface BrowserSettings {\n engine: 'chromium' | 'camoufox';\n /** A folder (or Chrome profile name) whose logins the agent reuses; null when unset. */\n profile: string | null;\n sessionName: string | null;\n headless: boolean;\n}\n\nconst ENGINES = ['chromium', 'camoufox'] as const;\n// Same rule as browser.session_name in src/config/sections/browser.ts.\nconst SESSION_NAME = /^[\\w.-]{1,64}$/;\n/** C0 and C1 control characters, newlines included. */\nconst hasControl = (text: string): boolean =>\n Array.from(text).some((ch) => {\n const code = ch.codePointAt(0) ?? 0;\n return code < 0x20 || (code >= 0x7f && code <= 0x9f);\n });\n\nexport async function browserSettings(io: CliIO): Promise<BrowserSettings> {\n const { config } = await loadConfig({ cwd: io.cwd, env: io.env });\n const b = config.browser;\n return {\n engine: b.engine,\n profile: b.profile ?? null,\n sessionName: b.session_name ?? null,\n headless: b.headless,\n };\n}\n\n/** A trimmed optional text field: empty is unset; newlines and control characters are refused. */\nfunction optionalText(raw: unknown, label: string): string | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (typeof raw !== 'string') throw new UsageError(`${label} must be text`);\n const trimmed = raw.trim();\n if (hasControl(trimmed))\n throw new UsageError(`${label} can't contain newlines or control characters`);\n return trimmed || undefined;\n}\n\n/** Check what the settings form sent; throws a UsageError naming the bad field. */\nexport function parseBrowserSettings(raw: unknown): BrowserSettings {\n const obj = typeof raw === 'object' && raw !== null ? (raw as Record<string, unknown>) : {};\n const engine = obj.engine ?? 'chromium';\n if (typeof engine !== 'string' || !(ENGINES as readonly string[]).includes(engine))\n throw new UsageError('the browser engine must be chromium or camoufox');\n const profile = optionalText(obj.profile, 'the browser profile');\n const sessionName = optionalText(obj.sessionName, 'the session name');\n if (sessionName && !SESSION_NAME.test(sessionName))\n throw new UsageError(\n `\"${sessionName}\" is not a usable session name`,\n 'up to 64 letters, digits, _ . and -',\n );\n if (obj.headless !== undefined && typeof obj.headless !== 'boolean')\n throw new UsageError('headless must be on or off');\n return {\n engine: engine as BrowserSettings['engine'],\n profile: profile ?? null,\n sessionName: sessionName ?? null,\n headless: obj.headless !== false,\n };\n}\n\nasync function readText(file: string): Promise<string> {\n try {\n return await readFile(file, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return '';\n throw err;\n }\n}\n\n/** The [browser] keys already in the user config, kept when the form saves its four. */\nfunction existingBrowser(text: string): Record<string, TomlValue> {\n const table = (parse(text) as Record<string, unknown>).browser;\n return typeof table === 'object' && table !== null ? (table as Record<string, TomlValue>) : {};\n}\n\n/** Save the browser settings to the user's config.toml; other [browser] keys stay as they were. */\nexport async function setBrowserSettings(io: CliIO, raw: unknown): Promise<BrowserSettings> {\n const next = parseBrowserSettings(raw);\n const file = userConfigFile(resolvePaths(io.env));\n const text = await readText(file);\n const updated = setTable(text, ['browser'], {\n ...existingBrowser(text),\n engine: next.engine,\n profile: next.profile ?? undefined,\n session_name: next.sessionName ?? undefined,\n headless: next.headless,\n });\n await mkdir(dirname(file), { recursive: true });\n await writeFile(file, updated, 'utf8');\n return next;\n}\n"],"mappings":"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","names":[]}
|