opencandle 0.7.0 → 0.9.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 +34 -21
- package/dist/cli-main.d.ts +1 -0
- package/dist/cli-main.js +224 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +13 -229
- package/dist/cli.js.map +1 -1
- package/dist/doctor/cli-command.d.ts +1 -0
- package/dist/doctor/cli-command.js +69 -0
- package/dist/doctor/cli-command.js.map +1 -0
- package/dist/doctor/render.d.ts +2 -0
- package/dist/doctor/render.js +32 -0
- package/dist/doctor/render.js.map +1 -0
- package/dist/doctor/report.d.ts +63 -0
- package/dist/doctor/report.js +500 -0
- package/dist/doctor/report.js.map +1 -0
- package/dist/infra/native-dependencies.d.ts +8 -0
- package/dist/infra/native-dependencies.js +49 -0
- package/dist/infra/native-dependencies.js.map +1 -1
- package/dist/infra/node-version.js +2 -5
- package/dist/infra/node-version.js.map +1 -1
- package/dist/monitor.d.ts +1 -1
- package/dist/monitor.js +2 -1
- package/dist/monitor.js.map +1 -1
- package/dist/onboarding/provider-status.d.ts +3 -5
- package/dist/onboarding/provider-status.js +5 -34
- package/dist/onboarding/provider-status.js.map +1 -1
- package/dist/pi/session-writer-lock.d.ts +30 -0
- package/dist/pi/session-writer-lock.js +111 -0
- package/dist/pi/session-writer-lock.js.map +1 -0
- package/dist/pi/session.d.ts +0 -1
- package/dist/pi/session.js +2 -1
- package/dist/pi/session.js.map +1 -1
- package/dist/prompts/policy-cards.js +8 -4
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/prompts/workflow-prompts.js +25 -6
- package/dist/prompts/workflow-prompts.js.map +1 -1
- package/dist/providers/external-tool-command.d.ts +17 -0
- package/dist/providers/external-tool-command.js +114 -0
- package/dist/providers/external-tool-command.js.map +1 -0
- package/dist/providers/reddit-cli.d.ts +2 -6
- package/dist/providers/reddit-cli.js +4 -33
- package/dist/providers/reddit-cli.js.map +1 -1
- package/dist/providers/twitter-cli.d.ts +2 -6
- package/dist/providers/twitter-cli.js +4 -33
- package/dist/providers/twitter-cli.js.map +1 -1
- package/dist/routing/classify-intent.js +15 -12
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/entity-extractor.js +1 -1
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/planning.js +9 -6
- package/dist/routing/planning.js.map +1 -1
- package/dist/routing/router-llm-client.d.ts +1 -1
- package/dist/routing/router-llm-client.js +1 -1
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router.js +44 -3
- package/dist/routing/router.js.map +1 -1
- package/dist/workflows/compare-assets.js +4 -1
- package/dist/workflows/compare-assets.js.map +1 -1
- package/gui/server/ask-user-bridge.ts +26 -19
- package/gui/server/chat-event-adapter.ts +49 -3
- package/gui/server/http-routes.ts +577 -0
- package/gui/server/invoke-tool.ts +102 -7
- package/gui/server/live-chat-event-adapter.ts +16 -4
- package/gui/server/server.ts +70 -296
- package/gui/server/session-actions.ts +7 -1
- package/gui/server/session-entry-wait.ts +79 -0
- package/gui/server/writer-lock.ts +1 -122
- package/gui/server/ws-hub.ts +25 -0
- package/gui/shared/chat-events.ts +42 -19
- package/gui/shared/event-reducer.ts +41 -26
- package/gui/web/dist/assets/CatalogOverlay-DcTXLc7u.js +1 -0
- package/gui/web/dist/assets/index-DdMnIvZ7.css +2 -0
- package/gui/web/dist/assets/index-YlMaahAS.js +65 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +23 -19
- package/src/cli-main.ts +270 -0
- package/src/cli.ts +14 -267
- package/src/doctor/cli-command.ts +83 -0
- package/src/doctor/render.ts +37 -0
- package/src/doctor/report.ts +638 -0
- package/src/infra/native-dependencies.ts +67 -0
- package/src/infra/node-version.ts +2 -5
- package/src/monitor.ts +3 -1
- package/src/onboarding/provider-status.ts +10 -38
- package/src/pi/session-writer-lock.ts +156 -0
- package/src/pi/session.ts +2 -1
- package/src/prompts/policy-cards.ts +12 -4
- package/src/prompts/workflow-prompts.ts +28 -6
- package/src/providers/external-tool-command.ts +164 -0
- package/src/providers/reddit-cli.ts +10 -41
- package/src/providers/twitter-cli.ts +10 -41
- package/src/routing/classify-intent.ts +22 -17
- package/src/routing/entity-extractor.ts +1 -1
- package/src/routing/planning.ts +15 -10
- package/src/routing/router-llm-client.ts +2 -1
- package/src/routing/router.ts +57 -6
- package/src/workflows/compare-assets.ts +4 -2
- package/gui/web/dist/assets/CatalogOverlay-CgeY5Pkp.js +0 -1
- package/gui/web/dist/assets/index-C6W_2eAn.js +0 -69
- package/gui/web/dist/assets/index-hwbx24a5.css +0 -1
package/gui/web/dist/index.html
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-YlMaahAS.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DdMnIvZ7.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencandle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Financial trading & investing agent",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"homepage": "https://
|
|
6
|
+
"homepage": "https://opencandle.app",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/Kahtaf/OpenCandle.git"
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"gui:web:build": "npm --workspace @opencandle/gui-web run build",
|
|
89
89
|
"docs:site:build": "node website/build.mjs",
|
|
90
90
|
"docs:site:serve": "node website/serve.mjs",
|
|
91
|
+
"docs:links:check": "node scripts/check-public-doc-links.mjs",
|
|
91
92
|
"gui": "tsx gui/server/server.ts",
|
|
92
93
|
"gui:dev": "tsx gui/server/server.ts",
|
|
93
94
|
"lint": "biome check .",
|
|
@@ -108,45 +109,48 @@
|
|
|
108
109
|
"test:evals:usually": "EVAL_TIER=usually vitest run --config vitest.config.evals.ts",
|
|
109
110
|
"test:evals:product": "tsx tests/scripts/run-product-evals.ts",
|
|
110
111
|
"test:evals:competitive": "tsx tests/scripts/run-competitive-finance-eval.ts",
|
|
112
|
+
"test:packed-install": "node scripts/packed-install-smoke.mjs",
|
|
111
113
|
"eval:competitive:analyze": "tsx tests/scripts/analyze-competitive-finance-report.ts",
|
|
112
114
|
"review:pr": ".agents/skills/autoreview/scripts/autoreview --mode branch --prompt-file .agents/skills/autoreview/references/opencandle-review.md --parallel-tests \"npx tsc --noEmit && npx vitest run\"",
|
|
115
|
+
"package:contents:check": "node scripts/check-package-contents.mjs",
|
|
113
116
|
"version:patch": "npm version patch --no-git-tag-version",
|
|
114
117
|
"version:minor": "npm version minor --no-git-tag-version",
|
|
115
118
|
"version:major": "npm version major --no-git-tag-version",
|
|
116
119
|
"version:set": "npm version",
|
|
117
|
-
"prepublishOnly": "npm
|
|
118
|
-
"publish:dry": "npm publish --access public --dry-run",
|
|
120
|
+
"prepublishOnly": "npm run release:check",
|
|
121
|
+
"publish:dry": "npm run release:check && npm publish --access public --dry-run",
|
|
122
|
+
"release:check": "npx tsc --noEmit && npx biome ci . && npm test && npm run docs:site:build && npm run package:contents:check && npm run test:packed-install && npm run docs:links:check",
|
|
119
123
|
"release:patch": "node scripts/release.mjs patch",
|
|
120
124
|
"release:minor": "node scripts/release.mjs minor",
|
|
121
125
|
"release:major": "node scripts/release.mjs major"
|
|
122
126
|
},
|
|
123
127
|
"engines": {
|
|
124
|
-
"node": "
|
|
128
|
+
"node": ">=22.19.0 <27"
|
|
125
129
|
},
|
|
126
130
|
"dependencies": {
|
|
127
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
128
|
-
"@earendil-works/pi-ai": "^0.
|
|
129
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
130
|
-
"better-sqlite3": "^12.
|
|
131
|
+
"@earendil-works/pi-agent-core": "^0.80.2",
|
|
132
|
+
"@earendil-works/pi-ai": "^0.80.2",
|
|
133
|
+
"@earendil-works/pi-coding-agent": "^0.80.2",
|
|
134
|
+
"better-sqlite3": "^12.11.1",
|
|
131
135
|
"duck-duck-scrape": "^2.2.7",
|
|
132
|
-
"tsx": "^4.22.
|
|
136
|
+
"tsx": "^4.22.4",
|
|
133
137
|
"yahoo-finance2": "^3.15.3"
|
|
134
138
|
},
|
|
135
139
|
"peerDependencies": {
|
|
136
140
|
"@sinclair/typebox": "*"
|
|
137
141
|
},
|
|
138
142
|
"devDependencies": {
|
|
139
|
-
"@agentclientprotocol/claude-agent-acp": "^0.
|
|
140
|
-
"@
|
|
143
|
+
"@agentclientprotocol/claude-agent-acp": "^0.52.0",
|
|
144
|
+
"@agentclientprotocol/codex-acp": "^1.0.0",
|
|
145
|
+
"@biomejs/biome": "^2.5.1",
|
|
141
146
|
"@sinclair/typebox": "^0.34.0",
|
|
142
147
|
"@types/better-sqlite3": "^7.6.13",
|
|
143
|
-
"@types/node": "^
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"playwright-core": "^1.61.0",
|
|
148
|
+
"@types/node": "^26.0.1",
|
|
149
|
+
"acpx": "^0.11.2",
|
|
150
|
+
"playwright-core": "^1.61.1",
|
|
147
151
|
"typescript": "^6.0.3",
|
|
148
|
-
"vite": "^8.0
|
|
149
|
-
"vitest": "^4.1.
|
|
150
|
-
"vitest-evals": "^0.
|
|
152
|
+
"vite": "^8.1.0",
|
|
153
|
+
"vitest": "^4.1.9",
|
|
154
|
+
"vitest-evals": "^0.14.0"
|
|
151
155
|
}
|
|
152
156
|
}
|
package/src/cli-main.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
AuthStorage,
|
|
7
|
+
createAgentSessionRuntime,
|
|
8
|
+
createAgentSessionServices,
|
|
9
|
+
DefaultPackageManager,
|
|
10
|
+
getAgentDir,
|
|
11
|
+
InteractiveMode,
|
|
12
|
+
initTheme,
|
|
13
|
+
ModelRegistry,
|
|
14
|
+
SettingsManager,
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { loadEnv } from "./config.js";
|
|
17
|
+
import { handleDoctorCommand } from "./doctor/cli-command.js";
|
|
18
|
+
import { createOpenCandleSession } from "./pi/session.js";
|
|
19
|
+
import { continueOpenCandleSession } from "./pi/session-storage.js";
|
|
20
|
+
import {
|
|
21
|
+
acquireSessionWriterLock,
|
|
22
|
+
refreshSessionWriterLock,
|
|
23
|
+
releaseSessionWriterLock,
|
|
24
|
+
writerLockScopeForSession,
|
|
25
|
+
} from "./pi/session-writer-lock.js";
|
|
26
|
+
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
29
|
+
|
|
30
|
+
async function handlePackageCommand(
|
|
31
|
+
args: string[],
|
|
32
|
+
cwd: string,
|
|
33
|
+
agentDir: string,
|
|
34
|
+
): Promise<boolean> {
|
|
35
|
+
const [command, ...rest] = args;
|
|
36
|
+
if (!command || !["install", "remove", "uninstall", "list", "update"].includes(command)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
41
|
+
const packageManager = new DefaultPackageManager({
|
|
42
|
+
cwd,
|
|
43
|
+
agentDir,
|
|
44
|
+
settingsManager,
|
|
45
|
+
});
|
|
46
|
+
packageManager.setProgressCallback((event) => {
|
|
47
|
+
if (event.type === "start" || event.type === "progress") {
|
|
48
|
+
process.stdout.write(`${event.message}\n`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const source = rest.find((a) => !a.startsWith("-"));
|
|
53
|
+
const local = rest.includes("-l") || rest.includes("--local");
|
|
54
|
+
|
|
55
|
+
switch (command === "uninstall" ? "remove" : command) {
|
|
56
|
+
case "install": {
|
|
57
|
+
if (!source) {
|
|
58
|
+
console.error("Usage: opencandle install <source> [-l]");
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
await packageManager.install(source, { local });
|
|
63
|
+
packageManager.addSourceToSettings(source, { local });
|
|
64
|
+
console.log(`Installed ${source}`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
case "remove": {
|
|
68
|
+
if (!source) {
|
|
69
|
+
console.error("Usage: opencandle remove <source> [-l]");
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
await packageManager.remove(source, { local });
|
|
74
|
+
const removed = packageManager.removeSourceFromSettings(source, {
|
|
75
|
+
local,
|
|
76
|
+
});
|
|
77
|
+
if (!removed) {
|
|
78
|
+
console.error(`No matching package found for ${source}`);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`Removed ${source}`);
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
case "list": {
|
|
86
|
+
const globalPkgs = settingsManager.getGlobalSettings().packages ?? [];
|
|
87
|
+
const projectPkgs = settingsManager.getProjectSettings().packages ?? [];
|
|
88
|
+
if (globalPkgs.length === 0 && projectPkgs.length === 0) {
|
|
89
|
+
console.log("No packages installed.");
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (globalPkgs.length > 0) {
|
|
93
|
+
console.log("User packages:");
|
|
94
|
+
for (const pkg of globalPkgs) {
|
|
95
|
+
const s = typeof pkg === "string" ? pkg : pkg.source;
|
|
96
|
+
const path = packageManager.getInstalledPath(s, "user");
|
|
97
|
+
console.log(` ${s}${path ? `\n ${path}` : ""}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (projectPkgs.length > 0) {
|
|
101
|
+
console.log("Project packages:");
|
|
102
|
+
for (const pkg of projectPkgs) {
|
|
103
|
+
const s = typeof pkg === "string" ? pkg : pkg.source;
|
|
104
|
+
const path = packageManager.getInstalledPath(s, "project");
|
|
105
|
+
console.log(` ${s}${path ? `\n ${path}` : ""}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
case "update": {
|
|
111
|
+
await packageManager.update(source);
|
|
112
|
+
console.log(source ? `Updated ${source}` : "All packages updated.");
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function handleGuiCommand(args: string[], cwd: string): Promise<boolean> {
|
|
120
|
+
if (args[0] !== "gui") return false;
|
|
121
|
+
|
|
122
|
+
const tsxCli = require.resolve("tsx/cli");
|
|
123
|
+
const serverPath = resolve(packageRoot, "gui/server/server.ts");
|
|
124
|
+
const child = spawn(process.execPath, [tsxCli, serverPath, ...args.slice(1)], {
|
|
125
|
+
cwd,
|
|
126
|
+
env: process.env,
|
|
127
|
+
stdio: "inherit",
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const exitCode = await new Promise<number>((resolveExit) => {
|
|
131
|
+
child.on("close", (code, signal) => {
|
|
132
|
+
if (signal) {
|
|
133
|
+
resolveExit(1);
|
|
134
|
+
} else {
|
|
135
|
+
resolveExit(code ?? 0);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
process.exitCode = exitCode;
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function handleMonitorCommand(args: string[], cwd: string): Promise<boolean> {
|
|
144
|
+
if (args[0] !== "monitor") return false;
|
|
145
|
+
|
|
146
|
+
const tsxCli = require.resolve("tsx/cli");
|
|
147
|
+
const monitorPath = resolve(packageRoot, "src/monitor.ts");
|
|
148
|
+
const child = spawn(process.execPath, [tsxCli, monitorPath, ...args.slice(1)], {
|
|
149
|
+
cwd,
|
|
150
|
+
env: process.env,
|
|
151
|
+
stdio: "inherit",
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const exitCode = await new Promise<number>((resolveExit) => {
|
|
155
|
+
child.on("close", (code, signal) => {
|
|
156
|
+
if (signal) {
|
|
157
|
+
resolveExit(1);
|
|
158
|
+
} else {
|
|
159
|
+
resolveExit(code ?? 0);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
process.exitCode = exitCode;
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function main(): Promise<void> {
|
|
168
|
+
const rawArgs = process.argv.slice(2);
|
|
169
|
+
const cwd = process.cwd();
|
|
170
|
+
const agentDir = getAgentDir();
|
|
171
|
+
|
|
172
|
+
loadEnv();
|
|
173
|
+
|
|
174
|
+
if (await handleGuiCommand(rawArgs, cwd)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (await handleMonitorCommand(rawArgs, cwd)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (await handleDoctorCommand(rawArgs, cwd, agentDir)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (await handlePackageCommand(rawArgs, cwd, agentDir)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Default: start the OpenCandle interactive agent
|
|
191
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
192
|
+
const authStorage = AuthStorage.create();
|
|
193
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
194
|
+
const shouldSuppressFallbackMessage = modelRegistry.getAvailable().length === 0;
|
|
195
|
+
|
|
196
|
+
initTheme(settingsManager.getTheme(), true);
|
|
197
|
+
|
|
198
|
+
const sessionManager = continueOpenCandleSession(cwd);
|
|
199
|
+
const sessionWriterLockScope = writerLockScopeForSession(sessionManager);
|
|
200
|
+
const sessionWriterLock = await acquireSessionWriterLock(sessionWriterLockScope, "tui");
|
|
201
|
+
if (sessionWriterLock.role !== "writer") {
|
|
202
|
+
console.error(
|
|
203
|
+
`Session is currently being written by ${sessionWriterLock.lock.processKind} (pid ${sessionWriterLock.lock.pid}).`,
|
|
204
|
+
);
|
|
205
|
+
process.exitCode = 1;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
let activeSessionWriterLockScope = sessionWriterLockScope;
|
|
209
|
+
const writerLockHeartbeat = setInterval(
|
|
210
|
+
() => refreshSessionWriterLock(activeSessionWriterLockScope),
|
|
211
|
+
5000,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
let runtime: Awaited<ReturnType<typeof createAgentSessionRuntime>> | undefined;
|
|
215
|
+
try {
|
|
216
|
+
runtime = await createAgentSessionRuntime(
|
|
217
|
+
async (opts) => {
|
|
218
|
+
const services = await createAgentSessionServices({
|
|
219
|
+
cwd: opts.cwd,
|
|
220
|
+
agentDir: opts.agentDir,
|
|
221
|
+
authStorage,
|
|
222
|
+
settingsManager,
|
|
223
|
+
modelRegistry,
|
|
224
|
+
});
|
|
225
|
+
const result = await createOpenCandleSession({
|
|
226
|
+
cwd: opts.cwd,
|
|
227
|
+
agentDir: opts.agentDir,
|
|
228
|
+
settingsManager,
|
|
229
|
+
authStorage,
|
|
230
|
+
modelRegistry,
|
|
231
|
+
sessionManager: opts.sessionManager,
|
|
232
|
+
bindExtensions: false,
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
...result,
|
|
236
|
+
services,
|
|
237
|
+
diagnostics: services.diagnostics,
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
{ cwd, agentDir, sessionManager },
|
|
241
|
+
);
|
|
242
|
+
runtime.setRebindSession(async (nextSession) => {
|
|
243
|
+
const nextSessionWriterLockScope = writerLockScopeForSession(nextSession.sessionManager);
|
|
244
|
+
if (nextSessionWriterLockScope === activeSessionWriterLockScope) return;
|
|
245
|
+
const nextSessionWriterLock = await acquireSessionWriterLock(
|
|
246
|
+
nextSessionWriterLockScope,
|
|
247
|
+
"tui",
|
|
248
|
+
);
|
|
249
|
+
if (nextSessionWriterLock.role !== "writer") {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Session is currently being written by ${nextSessionWriterLock.lock.processKind} (pid ${nextSessionWriterLock.lock.pid}).`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
releaseSessionWriterLock(activeSessionWriterLockScope);
|
|
255
|
+
activeSessionWriterLockScope = nextSessionWriterLockScope;
|
|
256
|
+
});
|
|
257
|
+
const interactiveMode = new InteractiveMode(runtime, {
|
|
258
|
+
modelFallbackMessage: shouldSuppressFallbackMessage
|
|
259
|
+
? undefined
|
|
260
|
+
: runtime.modelFallbackMessage,
|
|
261
|
+
});
|
|
262
|
+
await interactiveMode.run();
|
|
263
|
+
} finally {
|
|
264
|
+
clearInterval(writerLockHeartbeat);
|
|
265
|
+
releaseSessionWriterLock(activeSessionWriterLockScope);
|
|
266
|
+
await runtime?.dispose();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
await main();
|
package/src/cli.ts
CHANGED
|
@@ -1,270 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./infra/
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
DefaultPackageManager,
|
|
12
|
-
getAgentDir,
|
|
13
|
-
InteractiveMode,
|
|
14
|
-
initTheme,
|
|
15
|
-
ModelRegistry,
|
|
16
|
-
SettingsManager,
|
|
17
|
-
} from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import { loadEnv } from "./config.js";
|
|
19
|
-
import { formatProviderStatus, probeAllProviderStatuses } from "./onboarding/provider-status.js";
|
|
20
|
-
import { getProvider, type ProviderId } from "./onboarding/providers.js";
|
|
21
|
-
import {
|
|
22
|
-
clearProviderOnboardingEntry,
|
|
23
|
-
loadOnboardingState,
|
|
24
|
-
saveOnboardingState,
|
|
25
|
-
} from "./onboarding/state.js";
|
|
26
|
-
import { createOpenCandleSession } from "./pi/session.js";
|
|
27
|
-
import { continueOpenCandleSession } from "./pi/session-storage.js";
|
|
28
|
-
|
|
29
|
-
const require = createRequire(import.meta.url);
|
|
30
|
-
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
31
|
-
|
|
32
|
-
async function handlePackageCommand(
|
|
33
|
-
args: string[],
|
|
34
|
-
cwd: string,
|
|
35
|
-
agentDir: string,
|
|
36
|
-
): Promise<boolean> {
|
|
37
|
-
const [command, ...rest] = args;
|
|
38
|
-
if (!command || !["install", "remove", "uninstall", "list", "update"].includes(command)) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
43
|
-
const packageManager = new DefaultPackageManager({
|
|
44
|
-
cwd,
|
|
45
|
-
agentDir,
|
|
46
|
-
settingsManager,
|
|
47
|
-
});
|
|
48
|
-
packageManager.setProgressCallback((event) => {
|
|
49
|
-
if (event.type === "start" || event.type === "progress") {
|
|
50
|
-
process.stdout.write(`${event.message}\n`);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const source = rest.find((a) => !a.startsWith("-"));
|
|
55
|
-
const local = rest.includes("-l") || rest.includes("--local");
|
|
56
|
-
|
|
57
|
-
switch (command === "uninstall" ? "remove" : command) {
|
|
58
|
-
case "install": {
|
|
59
|
-
if (!source) {
|
|
60
|
-
console.error("Usage: opencandle install <source> [-l]");
|
|
61
|
-
process.exitCode = 1;
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
await packageManager.install(source, { local });
|
|
65
|
-
packageManager.addSourceToSettings(source, { local });
|
|
66
|
-
console.log(`Installed ${source}`);
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
case "remove": {
|
|
70
|
-
if (!source) {
|
|
71
|
-
console.error("Usage: opencandle remove <source> [-l]");
|
|
72
|
-
process.exitCode = 1;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
await packageManager.remove(source, { local });
|
|
76
|
-
const removed = packageManager.removeSourceFromSettings(source, {
|
|
77
|
-
local,
|
|
78
|
-
});
|
|
79
|
-
if (!removed) {
|
|
80
|
-
console.error(`No matching package found for ${source}`);
|
|
81
|
-
process.exitCode = 1;
|
|
82
|
-
} else {
|
|
83
|
-
console.log(`Removed ${source}`);
|
|
84
|
-
}
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
case "list": {
|
|
88
|
-
const globalPkgs = settingsManager.getGlobalSettings().packages ?? [];
|
|
89
|
-
const projectPkgs = settingsManager.getProjectSettings().packages ?? [];
|
|
90
|
-
if (globalPkgs.length === 0 && projectPkgs.length === 0) {
|
|
91
|
-
console.log("No packages installed.");
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
if (globalPkgs.length > 0) {
|
|
95
|
-
console.log("User packages:");
|
|
96
|
-
for (const pkg of globalPkgs) {
|
|
97
|
-
const s = typeof pkg === "string" ? pkg : pkg.source;
|
|
98
|
-
const path = packageManager.getInstalledPath(s, "user");
|
|
99
|
-
console.log(` ${s}${path ? `\n ${path}` : ""}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (projectPkgs.length > 0) {
|
|
103
|
-
console.log("Project packages:");
|
|
104
|
-
for (const pkg of projectPkgs) {
|
|
105
|
-
const s = typeof pkg === "string" ? pkg : pkg.source;
|
|
106
|
-
const path = packageManager.getInstalledPath(s, "project");
|
|
107
|
-
console.log(` ${s}${path ? `\n ${path}` : ""}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
case "update": {
|
|
113
|
-
await packageManager.update(source);
|
|
114
|
-
console.log(source ? `Updated ${source}` : "All packages updated.");
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function handleGuiCommand(args: string[], cwd: string): Promise<boolean> {
|
|
122
|
-
if (args[0] !== "gui") return false;
|
|
123
|
-
|
|
124
|
-
const tsxCli = require.resolve("tsx/cli");
|
|
125
|
-
const serverPath = resolve(packageRoot, "gui/server/server.ts");
|
|
126
|
-
const child = spawn(process.execPath, [tsxCli, serverPath, ...args.slice(1)], {
|
|
127
|
-
cwd,
|
|
128
|
-
env: process.env,
|
|
129
|
-
stdio: "inherit",
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const exitCode = await new Promise<number>((resolveExit) => {
|
|
133
|
-
child.on("close", (code, signal) => {
|
|
134
|
-
if (signal) {
|
|
135
|
-
resolveExit(1);
|
|
136
|
-
} else {
|
|
137
|
-
resolveExit(code ?? 0);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
process.exitCode = exitCode;
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function handleMonitorCommand(args: string[], cwd: string): Promise<boolean> {
|
|
146
|
-
if (args[0] !== "monitor") return false;
|
|
147
|
-
|
|
148
|
-
const tsxCli = require.resolve("tsx/cli");
|
|
149
|
-
const monitorPath = resolve(packageRoot, "src/monitor.ts");
|
|
150
|
-
const child = spawn(process.execPath, [tsxCli, monitorPath, ...args.slice(1)], {
|
|
151
|
-
cwd,
|
|
152
|
-
env: process.env,
|
|
153
|
-
stdio: "inherit",
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const exitCode = await new Promise<number>((resolveExit) => {
|
|
157
|
-
child.on("close", (code, signal) => {
|
|
158
|
-
if (signal) {
|
|
159
|
-
resolveExit(1);
|
|
160
|
-
} else {
|
|
161
|
-
resolveExit(code ?? 0);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
process.exitCode = exitCode;
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function handleDoctorCommand(args: string[]): Promise<boolean> {
|
|
170
|
-
if (args[0] !== "doctor") return false;
|
|
171
|
-
|
|
172
|
-
loadEnv();
|
|
173
|
-
const enableFlag = args.findIndex((arg) => arg === "--enable" || arg === "--reenable");
|
|
174
|
-
if (enableFlag >= 0) {
|
|
175
|
-
const providerId = args[enableFlag + 1] as ProviderId | undefined;
|
|
176
|
-
if (!providerId) {
|
|
177
|
-
console.error("Usage: opencandle doctor --enable <provider>");
|
|
178
|
-
process.exitCode = 1;
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
getProvider(providerId);
|
|
183
|
-
} catch {
|
|
184
|
-
console.error(`Unknown provider: ${providerId}`);
|
|
185
|
-
process.exitCode = 1;
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
saveOnboardingState(clearProviderOnboardingEntry(loadOnboardingState(), providerId));
|
|
189
|
-
console.log(`Re-enabled ${providerId}.`);
|
|
190
|
-
}
|
|
191
|
-
const statuses = await probeAllProviderStatuses({ force: args.includes("--no-cache") });
|
|
192
|
-
console.log("OpenCandle provider status");
|
|
193
|
-
for (const status of statuses) {
|
|
194
|
-
console.log(` ${formatProviderStatus(status)}`);
|
|
195
|
-
}
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async function main(): Promise<void> {
|
|
200
|
-
const rawArgs = process.argv.slice(2);
|
|
201
|
-
const cwd = process.cwd();
|
|
202
|
-
const agentDir = getAgentDir();
|
|
203
|
-
|
|
204
|
-
if (await handleGuiCommand(rawArgs, cwd)) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (await handleMonitorCommand(rawArgs, cwd)) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (await handleDoctorCommand(rawArgs)) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (await handlePackageCommand(rawArgs, cwd, agentDir)) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Default: start the OpenCandle interactive agent
|
|
2
|
+
import { ensureOpenCandleNativeDependencies } from "./infra/native-dependencies.js";
|
|
3
|
+
import { assertSupportedNodeVersion } from "./infra/node-version.js";
|
|
4
|
+
|
|
5
|
+
if (process.argv[2] === "doctor") {
|
|
6
|
+
const [{ loadEnv }, { handleDoctorCommand }, { getAgentDir }] = await Promise.all([
|
|
7
|
+
import("./config.js"),
|
|
8
|
+
import("./doctor/cli-command.js"),
|
|
9
|
+
import("@earendil-works/pi-coding-agent"),
|
|
10
|
+
]);
|
|
221
11
|
loadEnv();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
initTheme(settingsManager.getTheme(), true);
|
|
228
|
-
|
|
229
|
-
const sessionManager = continueOpenCandleSession(cwd);
|
|
230
|
-
|
|
231
|
-
const runtime = await createAgentSessionRuntime(
|
|
232
|
-
async (opts) => {
|
|
233
|
-
const services = await createAgentSessionServices({
|
|
234
|
-
cwd: opts.cwd,
|
|
235
|
-
agentDir: opts.agentDir,
|
|
236
|
-
authStorage,
|
|
237
|
-
settingsManager,
|
|
238
|
-
modelRegistry,
|
|
239
|
-
});
|
|
240
|
-
const result = await createOpenCandleSession({
|
|
241
|
-
cwd: opts.cwd,
|
|
242
|
-
agentDir: opts.agentDir,
|
|
243
|
-
settingsManager,
|
|
244
|
-
authStorage,
|
|
245
|
-
modelRegistry,
|
|
246
|
-
sessionManager: opts.sessionManager,
|
|
247
|
-
bindExtensions: false,
|
|
248
|
-
});
|
|
249
|
-
return {
|
|
250
|
-
...result,
|
|
251
|
-
services,
|
|
252
|
-
diagnostics: services.diagnostics,
|
|
253
|
-
};
|
|
254
|
-
},
|
|
255
|
-
{ cwd, agentDir, sessionManager },
|
|
256
|
-
);
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
const interactiveMode = new InteractiveMode(runtime, {
|
|
260
|
-
modelFallbackMessage: shouldSuppressFallbackMessage
|
|
261
|
-
? undefined
|
|
262
|
-
: runtime.modelFallbackMessage,
|
|
263
|
-
});
|
|
264
|
-
await interactiveMode.run();
|
|
265
|
-
} finally {
|
|
266
|
-
await runtime.dispose();
|
|
267
|
-
}
|
|
12
|
+
await handleDoctorCommand(process.argv.slice(2), process.cwd(), getAgentDir());
|
|
13
|
+
} else {
|
|
14
|
+
assertSupportedNodeVersion();
|
|
15
|
+
await ensureOpenCandleNativeDependencies();
|
|
16
|
+
await import("./cli-main.js");
|
|
268
17
|
}
|
|
269
|
-
|
|
270
|
-
await main();
|