opencandle 0.7.0 → 0.8.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 +200 -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.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 +10 -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 +7 -3
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/planning.js +9 -6
- package/dist/routing/planning.js.map +1 -1
- package/dist/routing/router.js +7 -3
- package/dist/routing/router.js.map +1 -1
- package/gui/server/http-routes.ts +419 -0
- package/gui/server/server.ts +29 -292
- package/gui/web/dist/assets/CatalogOverlay-CCVKwBUB.js +1 -0
- package/gui/web/dist/assets/index-Dm4Aom2_.js +69 -0
- package/gui/web/dist/assets/{index-hwbx24a5.css → index-UzZUg3dx.css} +1 -1
- package/gui/web/dist/index.html +2 -2
- package/package.json +13 -9
- package/src/cli-main.ts +233 -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.ts +2 -1
- package/src/prompts/policy-cards.ts +12 -4
- package/src/prompts/workflow-prompts.ts +13 -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 +11 -6
- package/src/routing/planning.ts +15 -10
- package/src/routing/router.ts +11 -6
- package/gui/web/dist/assets/CatalogOverlay-CgeY5Pkp.js +0 -1
- package/gui/web/dist/assets/index-C6W_2eAn.js +0 -69
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AuthStorage, ModelRegistry, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getProvider } from "../onboarding/providers.js";
|
|
3
|
+
import { clearProviderOnboardingEntry, loadOnboardingState, saveOnboardingState, } from "../onboarding/state.js";
|
|
4
|
+
import { renderDoctorReport } from "./render.js";
|
|
5
|
+
import { buildDoctorReport } from "./report.js";
|
|
6
|
+
export async function handleDoctorCommand(args, cwd, agentDir) {
|
|
7
|
+
if (args[0] !== "doctor")
|
|
8
|
+
return false;
|
|
9
|
+
const json = args.includes("--json");
|
|
10
|
+
const enableFlag = args.findIndex((arg) => arg === "--enable" || arg === "--reenable");
|
|
11
|
+
if (enableFlag >= 0) {
|
|
12
|
+
const providerId = args[enableFlag + 1];
|
|
13
|
+
if (!providerId) {
|
|
14
|
+
console.error("Usage: opencandle doctor --enable <provider>");
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
getProvider(providerId);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
console.error(`Unknown provider: ${providerId}`);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
saveOnboardingState(clearProviderOnboardingEntry(loadOnboardingState(), providerId));
|
|
27
|
+
if (!json)
|
|
28
|
+
console.log(`Re-enabled ${providerId}.`);
|
|
29
|
+
}
|
|
30
|
+
const authStorage = AuthStorage.create();
|
|
31
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
32
|
+
const settingsManager = SettingsManager.create(cwd, agentDir);
|
|
33
|
+
const includeSessions = args.includes("--sessions");
|
|
34
|
+
if (includeSessions) {
|
|
35
|
+
console.error("Session checks may read browser cookies or trigger platform permission prompts for Reddit and X/Twitter.");
|
|
36
|
+
}
|
|
37
|
+
const report = await buildDoctorReport({
|
|
38
|
+
cwd,
|
|
39
|
+
agentDir,
|
|
40
|
+
includeSessions,
|
|
41
|
+
includeGui: args.includes("--full"),
|
|
42
|
+
modelSetup: buildCliModelSetupState(modelRegistry, settingsManager),
|
|
43
|
+
});
|
|
44
|
+
console.log(json ? JSON.stringify(report, null, 2) : renderDoctorReport(report));
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
function buildCliModelSetupState(modelRegistry, settingsManager) {
|
|
48
|
+
modelRegistry.refresh();
|
|
49
|
+
const provider = settingsManager.getDefaultProvider();
|
|
50
|
+
const modelId = settingsManager.getDefaultModel();
|
|
51
|
+
const activeModel = provider && modelId ? modelRegistry.find(provider, modelId) : undefined;
|
|
52
|
+
if (activeModel && modelRegistry.hasConfiguredAuth(activeModel)) {
|
|
53
|
+
return {
|
|
54
|
+
requirement: "ready",
|
|
55
|
+
currentModel: `${activeModel.provider}/${activeModel.id}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const availableModels = modelRegistry.getAvailable().map((model) => ({
|
|
59
|
+
provider: model.provider,
|
|
60
|
+
id: model.id,
|
|
61
|
+
label: `${model.provider}/${model.id}`,
|
|
62
|
+
}));
|
|
63
|
+
return {
|
|
64
|
+
requirement: availableModels.length > 0 ? "select_model" : "connect_auth",
|
|
65
|
+
currentModel: activeModel ? `${activeModel.provider}/${activeModel.id}` : undefined,
|
|
66
|
+
availableModels,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=cli-command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-command.js","sourceRoot":"","sources":["../../src/doctor/cli-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const STATUS_SYMBOLS = {
|
|
2
|
+
pass: "PASS",
|
|
3
|
+
warn: "WARN",
|
|
4
|
+
fail: "FAIL",
|
|
5
|
+
skip: "SKIP",
|
|
6
|
+
unknown: "INFO",
|
|
7
|
+
};
|
|
8
|
+
export function renderDoctorReport(report) {
|
|
9
|
+
const lines = [
|
|
10
|
+
`OpenCandle health: ${report.status.toUpperCase()}`,
|
|
11
|
+
report.summary,
|
|
12
|
+
`Generated: ${report.generatedAt}`,
|
|
13
|
+
`OC home: ${report.metadata.opencandleHome} (${report.metadata.opencandleHomeSource})`,
|
|
14
|
+
`CWD: ${report.metadata.cwd}`,
|
|
15
|
+
"",
|
|
16
|
+
];
|
|
17
|
+
for (const section of report.sections) {
|
|
18
|
+
lines.push(`${section.label} - ${section.status.toUpperCase()}`);
|
|
19
|
+
for (const check of section.checks) {
|
|
20
|
+
lines.push(` [${STATUS_SYMBOLS[check.status]}] ${check.label}: ${check.summary}`);
|
|
21
|
+
if (isActionable(check)) {
|
|
22
|
+
lines.push(` Fix: ${check.remediation}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
lines.push("");
|
|
26
|
+
}
|
|
27
|
+
return lines.join("\n").trimEnd();
|
|
28
|
+
}
|
|
29
|
+
function isActionable(check) {
|
|
30
|
+
return check.status !== "pass" && Boolean(check.remediation);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/doctor/render.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAsC;IACxD,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,MAAM;CAChB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,MAAoB;IACrD,MAAM,KAAK,GAAG;QACZ,sBAAsB,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE;QACnD,MAAM,CAAC,OAAO;QACd,cAAc,MAAM,CAAC,WAAW,EAAE;QAClC,YAAY,MAAM,CAAC,QAAQ,CAAC,cAAc,KAAK,MAAM,CAAC,QAAQ,CAAC,oBAAoB,GAAG;QACtF,QAAQ,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;QAC7B,EAAE;KACH,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,MAAM,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACjE,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,MAAM,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACnF,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxB,KAAK,CAAC,IAAI,CAAC,cAAc,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,YAAY,CAAC,KAAkB;IACtC,OAAO,KAAK,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type CommandRunner, type ProviderStatus } from "../onboarding/provider-status.js";
|
|
2
|
+
export declare const DOCTOR_REPORT_SCHEMA_VERSION = 1;
|
|
3
|
+
export type DoctorCheckStatus = "pass" | "warn" | "fail" | "skip" | "unknown";
|
|
4
|
+
export type DoctorCapabilityImpact = "core" | "optional";
|
|
5
|
+
export type DoctorOverallStatus = "ready" | "degraded" | "blocked";
|
|
6
|
+
export interface DoctorCheck {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly status: DoctorCheckStatus;
|
|
10
|
+
readonly capability: DoctorCapabilityImpact;
|
|
11
|
+
readonly summary: string;
|
|
12
|
+
readonly remediation?: string;
|
|
13
|
+
readonly metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface DoctorSection {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly status: DoctorOverallStatus;
|
|
19
|
+
readonly checks: DoctorCheck[];
|
|
20
|
+
}
|
|
21
|
+
export interface DoctorModelSetupState {
|
|
22
|
+
readonly requirement: "ready" | "select_model" | "connect_auth" | "unknown";
|
|
23
|
+
readonly currentModel?: string;
|
|
24
|
+
readonly availableModels?: readonly {
|
|
25
|
+
provider: string;
|
|
26
|
+
id: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
}[];
|
|
29
|
+
}
|
|
30
|
+
export interface DoctorGuiStatusInput {
|
|
31
|
+
readonly host: string;
|
|
32
|
+
readonly port: number;
|
|
33
|
+
readonly role?: string;
|
|
34
|
+
readonly reachable?: boolean;
|
|
35
|
+
readonly healthEndpoint?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface BuildDoctorReportOptions {
|
|
38
|
+
readonly cwd?: string;
|
|
39
|
+
readonly agentDir?: string;
|
|
40
|
+
readonly now?: Date;
|
|
41
|
+
readonly includeSessions?: boolean;
|
|
42
|
+
readonly includeGui?: boolean;
|
|
43
|
+
readonly gui?: DoctorGuiStatusInput;
|
|
44
|
+
readonly modelSetup?: DoctorModelSetupState;
|
|
45
|
+
readonly commandRunner?: CommandRunner;
|
|
46
|
+
readonly fetchImpl?: typeof fetch;
|
|
47
|
+
readonly providerStatuses?: readonly ProviderStatus[];
|
|
48
|
+
}
|
|
49
|
+
export interface DoctorReport {
|
|
50
|
+
readonly schemaVersion: number;
|
|
51
|
+
readonly generatedAt: string;
|
|
52
|
+
readonly status: DoctorOverallStatus;
|
|
53
|
+
readonly summary: string;
|
|
54
|
+
readonly sections: DoctorSection[];
|
|
55
|
+
readonly metadata: {
|
|
56
|
+
readonly cwd: string;
|
|
57
|
+
readonly agentDir?: string;
|
|
58
|
+
readonly opencandleHome: string;
|
|
59
|
+
readonly opencandleHomeSource: "default" | "env";
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export declare function buildDoctorReport(options?: BuildDoctorReportOptions): Promise<DoctorReport>;
|
|
63
|
+
export declare function deriveDoctorStatus(checks: readonly DoctorCheck[]): DoctorOverallStatus;
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { getUnsupportedNodeVersionMessage } from "../infra/node-version.js";
|
|
6
|
+
import { getConfigPath, getOnboardingPath, getOpenCandleHomeDir, getStateDbPath, resolveOpenCandlePath, } from "../infra/opencandle-paths.js";
|
|
7
|
+
import { probeAllProviderStatuses, probeProviderStatus, } from "../onboarding/provider-status.js";
|
|
8
|
+
import { getProvider, isExternalToolProvider, listAllProviders, } from "../onboarding/providers.js";
|
|
9
|
+
export const DOCTOR_REPORT_SCHEMA_VERSION = 1;
|
|
10
|
+
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
export async function buildDoctorReport(options = {}) {
|
|
13
|
+
const now = options.now ?? new Date();
|
|
14
|
+
const cwd = options.cwd ?? process.cwd();
|
|
15
|
+
const home = getOpenCandleHomeDir();
|
|
16
|
+
const runtimeChecks = buildRuntimeChecks(cwd);
|
|
17
|
+
const stateChecks = buildStateChecks(home, options.agentDir);
|
|
18
|
+
const providerChecks = stateChecks.some((check) => check.id === "state.config" && check.status === "fail")
|
|
19
|
+
? [providerChecksSkippedForInvalidConfig()]
|
|
20
|
+
: await buildProviderChecks(options);
|
|
21
|
+
const sections = [
|
|
22
|
+
section("runtime", "Runtime", runtimeChecks),
|
|
23
|
+
section("state", "Local state", stateChecks),
|
|
24
|
+
section("model", "Model", [buildModelCheck(options.modelSetup)]),
|
|
25
|
+
section("providers", "Providers", providerChecks),
|
|
26
|
+
];
|
|
27
|
+
if (options.includeGui || options.gui) {
|
|
28
|
+
sections.push(section("gui", "GUI", await buildGuiChecks(options)));
|
|
29
|
+
}
|
|
30
|
+
const allChecks = sections.flatMap((candidate) => candidate.checks);
|
|
31
|
+
const status = deriveDoctorStatus(allChecks);
|
|
32
|
+
return {
|
|
33
|
+
schemaVersion: DOCTOR_REPORT_SCHEMA_VERSION,
|
|
34
|
+
generatedAt: now.toISOString(),
|
|
35
|
+
status,
|
|
36
|
+
summary: buildSummary(status, allChecks),
|
|
37
|
+
sections,
|
|
38
|
+
metadata: {
|
|
39
|
+
cwd,
|
|
40
|
+
agentDir: options.agentDir,
|
|
41
|
+
opencandleHome: home,
|
|
42
|
+
opencandleHomeSource: process.env.OPENCANDLE_HOME ? "env" : "default",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function deriveDoctorStatus(checks) {
|
|
47
|
+
if (checks.some((candidate) => candidate.capability === "core" && candidate.status === "fail")) {
|
|
48
|
+
return "blocked";
|
|
49
|
+
}
|
|
50
|
+
if (checks.some((candidate) => candidate.status === "warn" ||
|
|
51
|
+
candidate.status === "unknown" ||
|
|
52
|
+
(candidate.capability === "optional" && candidate.status === "fail"))) {
|
|
53
|
+
return "degraded";
|
|
54
|
+
}
|
|
55
|
+
return "ready";
|
|
56
|
+
}
|
|
57
|
+
function section(id, label, checks) {
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
label,
|
|
61
|
+
status: deriveDoctorStatus(checks),
|
|
62
|
+
checks,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildRuntimeChecks(cwd) {
|
|
66
|
+
const packageJson = readPackageJson();
|
|
67
|
+
const nodeMessage = getUnsupportedNodeVersionMessage();
|
|
68
|
+
const sqliteLoads = canLoadBetterSqlite();
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
id: "runtime.opencandle_version",
|
|
72
|
+
label: "OpenCandle version",
|
|
73
|
+
status: "pass",
|
|
74
|
+
capability: "core",
|
|
75
|
+
summary: packageJson.version ? `OpenCandle ${packageJson.version}` : "Version unavailable",
|
|
76
|
+
metadata: { version: packageJson.version ?? null },
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "runtime.node",
|
|
80
|
+
label: "Node.js",
|
|
81
|
+
status: nodeMessage ? "fail" : "pass",
|
|
82
|
+
capability: "core",
|
|
83
|
+
summary: nodeMessage
|
|
84
|
+
? `Unsupported Node ${process.versions.node}`
|
|
85
|
+
: `Node ${process.version}`,
|
|
86
|
+
remediation: nodeMessage ?? undefined,
|
|
87
|
+
metadata: { version: process.versions.node, supportedRange: packageJson.engines?.node },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "runtime.better_sqlite3",
|
|
91
|
+
label: "better-sqlite3",
|
|
92
|
+
status: sqliteLoads ? "pass" : "fail",
|
|
93
|
+
capability: "core",
|
|
94
|
+
summary: sqliteLoads ? "Native SQLite binding loads" : "Native SQLite binding failed to load",
|
|
95
|
+
remediation: sqliteLoads
|
|
96
|
+
? undefined
|
|
97
|
+
: "Run `npm rebuild better-sqlite3` or reinstall dependencies under the active Node with `npm install`.",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "runtime.cwd",
|
|
101
|
+
label: "Working directory",
|
|
102
|
+
status: "pass",
|
|
103
|
+
capability: "core",
|
|
104
|
+
summary: cwd,
|
|
105
|
+
metadata: { cwd },
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
function buildStateChecks(home, agentDir) {
|
|
110
|
+
return [
|
|
111
|
+
{
|
|
112
|
+
id: "state.opencandle_home",
|
|
113
|
+
label: "OpenCandle home",
|
|
114
|
+
status: directoryUsable(home) ? "pass" : existsSync(home) ? "warn" : "skip",
|
|
115
|
+
capability: "core",
|
|
116
|
+
summary: `${home} (${process.env.OPENCANDLE_HOME ? "environment override" : "default"})`,
|
|
117
|
+
remediation: existsSync(home)
|
|
118
|
+
? "Ensure the OpenCandle home directory is readable and writable."
|
|
119
|
+
: "OpenCandle will create this directory when state is first written.",
|
|
120
|
+
metadata: { path: home, source: process.env.OPENCANDLE_HOME ? "env" : "default" },
|
|
121
|
+
},
|
|
122
|
+
piAgentDirCheck(agentDir),
|
|
123
|
+
configCheck(),
|
|
124
|
+
fileStateCheck("state.onboarding", "Onboarding state", getOnboardingPath()),
|
|
125
|
+
fileStateCheck("state.sqlite", "State database", getStateDbPath()),
|
|
126
|
+
fileStateCheck("state.sentiment", "Sentiment history store", resolveOpenCandlePath("sentinel.db")),
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
function configCheck() {
|
|
130
|
+
const path = getConfigPath();
|
|
131
|
+
if (!existsSync(path)) {
|
|
132
|
+
return {
|
|
133
|
+
id: "state.config",
|
|
134
|
+
label: "Config file",
|
|
135
|
+
status: "skip",
|
|
136
|
+
capability: "core",
|
|
137
|
+
summary: "No config file yet",
|
|
138
|
+
remediation: "No action required. OpenCandle will create config.json when settings are saved.",
|
|
139
|
+
metadata: { path },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
JSON.parse(readFileSync(path, "utf-8"));
|
|
144
|
+
return {
|
|
145
|
+
id: "state.config",
|
|
146
|
+
label: "Config file",
|
|
147
|
+
status: "pass",
|
|
148
|
+
capability: "core",
|
|
149
|
+
summary: "config.json parses",
|
|
150
|
+
metadata: { path },
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return {
|
|
155
|
+
id: "state.config",
|
|
156
|
+
label: "Config file",
|
|
157
|
+
status: "fail",
|
|
158
|
+
capability: "core",
|
|
159
|
+
summary: "config.json is invalid JSON",
|
|
160
|
+
remediation: `Repair or remove ${path}. ${error instanceof Error ? error.message : String(error)}`,
|
|
161
|
+
metadata: { path },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function piAgentDirCheck(agentDir) {
|
|
166
|
+
if (!agentDir) {
|
|
167
|
+
return {
|
|
168
|
+
id: "state.pi_agent_dir",
|
|
169
|
+
label: "Pi agent directory",
|
|
170
|
+
status: "unknown",
|
|
171
|
+
capability: "core",
|
|
172
|
+
summary: "Pi agent directory was not checked",
|
|
173
|
+
remediation: "Run doctor from an initialized OpenCandle CLI or GUI session.",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
id: "state.pi_agent_dir",
|
|
178
|
+
label: "Pi agent directory",
|
|
179
|
+
status: directoryUsable(agentDir) ? "pass" : existsSync(agentDir) ? "warn" : "skip",
|
|
180
|
+
capability: "core",
|
|
181
|
+
summary: agentDir,
|
|
182
|
+
remediation: existsSync(agentDir)
|
|
183
|
+
? "Ensure the Pi agent directory is readable and writable."
|
|
184
|
+
: "Pi will create this directory when model/auth state is first written.",
|
|
185
|
+
metadata: { path: agentDir },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function fileStateCheck(id, label, path) {
|
|
189
|
+
if (!existsSync(path)) {
|
|
190
|
+
return {
|
|
191
|
+
id,
|
|
192
|
+
label,
|
|
193
|
+
status: "skip",
|
|
194
|
+
capability: "core",
|
|
195
|
+
summary: "Not created yet",
|
|
196
|
+
remediation: "No action required for a new install.",
|
|
197
|
+
metadata: { path },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
label,
|
|
203
|
+
status: canAccess(path) ? "pass" : "warn",
|
|
204
|
+
capability: "core",
|
|
205
|
+
summary: canAccess(path) ? "Readable and writable" : "Access may be limited",
|
|
206
|
+
remediation: canAccess(path) ? undefined : `Ensure ${path} is readable and writable.`,
|
|
207
|
+
metadata: { path },
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function buildModelCheck(modelSetup) {
|
|
211
|
+
const state = modelSetup ?? { requirement: "unknown" };
|
|
212
|
+
if (state.requirement === "ready") {
|
|
213
|
+
return {
|
|
214
|
+
id: "model.readiness",
|
|
215
|
+
label: "Model readiness",
|
|
216
|
+
status: "pass",
|
|
217
|
+
capability: "core",
|
|
218
|
+
summary: state.currentModel ? `Ready with ${state.currentModel}` : "Model is ready",
|
|
219
|
+
metadata: { currentModel: state.currentModel ?? null },
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (state.requirement === "select_model") {
|
|
223
|
+
return {
|
|
224
|
+
id: "model.readiness",
|
|
225
|
+
label: "Model readiness",
|
|
226
|
+
status: "fail",
|
|
227
|
+
capability: "core",
|
|
228
|
+
summary: "Model credentials exist but no active model is selected",
|
|
229
|
+
remediation: "Run `/setup` in the CLI or choose an available model in the GUI model setup panel.",
|
|
230
|
+
metadata: { requirement: state.requirement, availableModels: state.availableModels ?? [] },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (state.requirement === "connect_auth") {
|
|
234
|
+
return {
|
|
235
|
+
id: "model.readiness",
|
|
236
|
+
label: "Model readiness",
|
|
237
|
+
status: "fail",
|
|
238
|
+
capability: "core",
|
|
239
|
+
summary: "No usable model credentials are configured",
|
|
240
|
+
remediation: "Run `/setup` in the CLI or paste a model API key in the GUI model setup panel.",
|
|
241
|
+
metadata: { requirement: state.requirement },
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
id: "model.readiness",
|
|
246
|
+
label: "Model readiness",
|
|
247
|
+
status: "unknown",
|
|
248
|
+
capability: "core",
|
|
249
|
+
summary: "Model readiness was not checked",
|
|
250
|
+
remediation: "Run the doctor command from an initialized OpenCandle session for model setup state.",
|
|
251
|
+
metadata: { requirement: "unknown" },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async function buildProviderChecks(options) {
|
|
255
|
+
const providers = listAllProviders();
|
|
256
|
+
const statuses = options.providerStatuses ??
|
|
257
|
+
(await probeAllProviderStatuses({
|
|
258
|
+
commandRunner: options.commandRunner,
|
|
259
|
+
fetchImpl: options.fetchImpl,
|
|
260
|
+
force: true,
|
|
261
|
+
respectSkipped: true,
|
|
262
|
+
}));
|
|
263
|
+
const checks = statuses.map((status) => providerStatusCheck(status));
|
|
264
|
+
for (const provider of providers.filter(isExternalToolProvider)) {
|
|
265
|
+
const installStatus = statuses.find((status) => status.providerId === provider.id && status.kind === "external-tool");
|
|
266
|
+
if (installStatus?.state === "skipped")
|
|
267
|
+
continue;
|
|
268
|
+
if (installStatus?.state !== "installed")
|
|
269
|
+
continue;
|
|
270
|
+
if (options.includeSessions) {
|
|
271
|
+
const status = await probeProviderStatus(provider.id, {
|
|
272
|
+
mode: "session",
|
|
273
|
+
force: true,
|
|
274
|
+
respectSkipped: true,
|
|
275
|
+
commandRunner: options.commandRunner,
|
|
276
|
+
});
|
|
277
|
+
checks.push(providerStatusCheck(status, "session"));
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
checks.push(sessionNotCheckedCheck(provider));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return checks;
|
|
284
|
+
}
|
|
285
|
+
function providerChecksSkippedForInvalidConfig() {
|
|
286
|
+
return {
|
|
287
|
+
id: "providers.config_blocked",
|
|
288
|
+
label: "Provider checks",
|
|
289
|
+
status: "skip",
|
|
290
|
+
capability: "optional",
|
|
291
|
+
summary: "Skipped because config.json is invalid",
|
|
292
|
+
remediation: "Repair the config file, then rerun doctor to check provider readiness.",
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function providerStatusCheck(status, modeOverride) {
|
|
296
|
+
const provider = getProvider(status.providerId);
|
|
297
|
+
const capability = provider.tier === "hard" ? "core" : "optional";
|
|
298
|
+
const metadata = {
|
|
299
|
+
providerId: status.providerId,
|
|
300
|
+
category: provider.category,
|
|
301
|
+
tier: provider.tier,
|
|
302
|
+
kind: status.kind,
|
|
303
|
+
state: status.state,
|
|
304
|
+
};
|
|
305
|
+
if (status.kind === "api-key") {
|
|
306
|
+
return {
|
|
307
|
+
id: `provider.${status.providerId}.credential`,
|
|
308
|
+
label: provider.displayName,
|
|
309
|
+
status: status.state === "configured" ? "pass" : "warn",
|
|
310
|
+
capability,
|
|
311
|
+
summary: status.state === "configured"
|
|
312
|
+
? `Configured via ${status.credentialSource}`
|
|
313
|
+
: "Credential is missing",
|
|
314
|
+
remediation: status.state === "configured"
|
|
315
|
+
? undefined
|
|
316
|
+
: `${provider.instructionsHint}. Run /connect ${provider.id}.`,
|
|
317
|
+
metadata: { ...metadata, credentialSource: status.credentialSource },
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (status.kind === "public-http") {
|
|
321
|
+
const ok = status.state === "reachable";
|
|
322
|
+
return {
|
|
323
|
+
id: `provider.${status.providerId}.reachability`,
|
|
324
|
+
label: provider.displayName,
|
|
325
|
+
status: ok ? "pass" : "fail",
|
|
326
|
+
capability,
|
|
327
|
+
summary: ok ? "Reachable" : `Not reachable (${status.state})`,
|
|
328
|
+
remediation: ok
|
|
329
|
+
? undefined
|
|
330
|
+
: "Check network connectivity and retry. Core quote and market-data workflows depend on Yahoo Finance reachability.",
|
|
331
|
+
metadata: { ...metadata, statusCode: status.statusCode ?? null },
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (modeOverride === "session" || status.mode === "session") {
|
|
335
|
+
const ok = status.state === "session_ok";
|
|
336
|
+
return {
|
|
337
|
+
id: `provider.${status.providerId}.session`,
|
|
338
|
+
label: `${provider.displayName} browser session`,
|
|
339
|
+
status: ok ? "pass" : "warn",
|
|
340
|
+
capability: "optional",
|
|
341
|
+
summary: ok ? "Browser session is usable" : (status.message ?? `Session ${status.state}`),
|
|
342
|
+
remediation: ok ? undefined : sessionRemediation(status.providerId, status.installCmd),
|
|
343
|
+
metadata,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const installed = status.state === "installed";
|
|
347
|
+
return {
|
|
348
|
+
id: `provider.${status.providerId}.binary`,
|
|
349
|
+
label: `${provider.displayName} CLI`,
|
|
350
|
+
status: installed
|
|
351
|
+
? "pass"
|
|
352
|
+
: status.state === "skipped"
|
|
353
|
+
? "skip"
|
|
354
|
+
: status.state === "missing"
|
|
355
|
+
? "warn"
|
|
356
|
+
: "unknown",
|
|
357
|
+
capability: "optional",
|
|
358
|
+
summary: installed ? "Installed" : (status.message ?? `CLI ${status.state}`),
|
|
359
|
+
remediation: installed
|
|
360
|
+
? undefined
|
|
361
|
+
: status.state === "skipped"
|
|
362
|
+
? `Run \`opencandle doctor --enable ${status.providerId}\` to re-enable this provider.`
|
|
363
|
+
: status.installCmd,
|
|
364
|
+
metadata,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function sessionNotCheckedCheck(provider) {
|
|
368
|
+
return {
|
|
369
|
+
id: `provider.${provider.id}.session`,
|
|
370
|
+
label: `${provider.displayName} browser session`,
|
|
371
|
+
status: "unknown",
|
|
372
|
+
capability: "optional",
|
|
373
|
+
summary: "Not checked",
|
|
374
|
+
remediation: "Run `opencandle doctor --sessions` to check browser-cookie session readiness.",
|
|
375
|
+
metadata: { providerId: provider.id, category: provider.category, tier: provider.tier },
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async function buildGuiChecks(options) {
|
|
379
|
+
if (options.gui?.reachable !== undefined || options.gui?.role) {
|
|
380
|
+
const role = options.gui.role ?? "unknown";
|
|
381
|
+
const reachable = options.gui.reachable ?? true;
|
|
382
|
+
return [
|
|
383
|
+
{
|
|
384
|
+
id: "gui.server",
|
|
385
|
+
label: "GUI server",
|
|
386
|
+
status: reachable ? (role === "follower" ? "warn" : "pass") : "skip",
|
|
387
|
+
capability: "optional",
|
|
388
|
+
summary: reachable ? `GUI server is ${role}` : "GUI server is not running",
|
|
389
|
+
remediation: role === "follower"
|
|
390
|
+
? "Open the writer GUI process to perform setup mutations."
|
|
391
|
+
: reachable
|
|
392
|
+
? undefined
|
|
393
|
+
: "Run `opencandle gui` to start the browser workbench.",
|
|
394
|
+
metadata: { ...options.gui },
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
const host = options.gui?.host ?? process.env.OPENCANDLE_GUI_HOST ?? "127.0.0.1";
|
|
399
|
+
const port = options.gui?.port ?? Number(process.env.OPENCANDLE_GUI_PORT ?? 14567);
|
|
400
|
+
const endpoint = options.gui?.healthEndpoint ?? `http://${host}:${port}/health`;
|
|
401
|
+
try {
|
|
402
|
+
const response = await (options.fetchImpl ?? fetch)(endpoint, {
|
|
403
|
+
signal: AbortSignal.timeout(1_500),
|
|
404
|
+
});
|
|
405
|
+
const body = await response.json().catch(() => null);
|
|
406
|
+
const role = isRecord(body) && typeof body.role === "string" ? body.role : "unknown";
|
|
407
|
+
const verified = response.ok &&
|
|
408
|
+
isRecord(body) &&
|
|
409
|
+
body.ok === true &&
|
|
410
|
+
(role === "writer" || role === "follower");
|
|
411
|
+
return [
|
|
412
|
+
{
|
|
413
|
+
id: "gui.server",
|
|
414
|
+
label: "GUI server",
|
|
415
|
+
status: verified ? (role === "follower" ? "warn" : "pass") : "warn",
|
|
416
|
+
capability: "optional",
|
|
417
|
+
summary: verified
|
|
418
|
+
? `GUI server is ${role}`
|
|
419
|
+
: response.ok
|
|
420
|
+
? "OpenCandle GUI health payload was not verified"
|
|
421
|
+
: `GUI health returned HTTP ${response.status}`,
|
|
422
|
+
remediation: verified && role === "follower"
|
|
423
|
+
? "Open the writer GUI process to perform setup mutations."
|
|
424
|
+
: undefined,
|
|
425
|
+
metadata: { host, port, role, endpoint, statusCode: response.status },
|
|
426
|
+
},
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return [
|
|
431
|
+
{
|
|
432
|
+
id: "gui.server",
|
|
433
|
+
label: "GUI server",
|
|
434
|
+
status: "skip",
|
|
435
|
+
capability: "optional",
|
|
436
|
+
summary: "GUI server is not running",
|
|
437
|
+
remediation: "Run `opencandle gui` to start the browser workbench.",
|
|
438
|
+
metadata: { host, port, endpoint },
|
|
439
|
+
},
|
|
440
|
+
];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function sessionRemediation(providerId, installCmd) {
|
|
444
|
+
if (providerId === "reddit")
|
|
445
|
+
return "Run `rdt login` and retry `opencandle doctor --sessions`.";
|
|
446
|
+
if (providerId === "twitter") {
|
|
447
|
+
return "Stay logged into x.com in a supported browser and retry `opencandle doctor --sessions`.";
|
|
448
|
+
}
|
|
449
|
+
return installCmd ?? "Install and authenticate the external tool.";
|
|
450
|
+
}
|
|
451
|
+
function buildSummary(status, checks) {
|
|
452
|
+
if (status === "ready")
|
|
453
|
+
return "OpenCandle core health is ready.";
|
|
454
|
+
const firstBlocking = checks.find((candidate) => candidate.capability === "core" && candidate.status === "fail");
|
|
455
|
+
if (firstBlocking)
|
|
456
|
+
return `OpenCandle is blocked: ${firstBlocking.summary}.`;
|
|
457
|
+
const issueCount = checks.filter((candidate) => ["warn", "fail", "unknown"].includes(candidate.status)).length;
|
|
458
|
+
return `OpenCandle is usable with ${issueCount} degraded or unchecked ${issueCount === 1 ? "capability" : "capabilities"}.`;
|
|
459
|
+
}
|
|
460
|
+
function readPackageJson() {
|
|
461
|
+
try {
|
|
462
|
+
return JSON.parse(readFileSync(resolve(PACKAGE_ROOT, "package.json"), "utf-8"));
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function isRecord(value) {
|
|
469
|
+
return typeof value === "object" && value !== null;
|
|
470
|
+
}
|
|
471
|
+
function canLoadBetterSqlite() {
|
|
472
|
+
try {
|
|
473
|
+
const Database = require("better-sqlite3");
|
|
474
|
+
const db = new Database(":memory:");
|
|
475
|
+
db.close();
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function directoryUsable(path) {
|
|
483
|
+
try {
|
|
484
|
+
return (statSync(path).isDirectory() &&
|
|
485
|
+
canAccess(path, constants.R_OK | constants.W_OK | constants.X_OK));
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function canAccess(path, mode = constants.R_OK | constants.W_OK) {
|
|
492
|
+
try {
|
|
493
|
+
accessSync(path, mode);
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
//# sourceMappingURL=report.js.map
|