@gmickel/gno 0.3.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 +256 -0
- package/assets/skill/SKILL.md +112 -0
- package/assets/skill/cli-reference.md +327 -0
- package/assets/skill/examples.md +234 -0
- package/assets/skill/mcp-reference.md +159 -0
- package/package.json +90 -0
- package/src/app/constants.ts +313 -0
- package/src/cli/colors.ts +65 -0
- package/src/cli/commands/ask.ts +545 -0
- package/src/cli/commands/cleanup.ts +105 -0
- package/src/cli/commands/collection/add.ts +120 -0
- package/src/cli/commands/collection/index.ts +10 -0
- package/src/cli/commands/collection/list.ts +108 -0
- package/src/cli/commands/collection/remove.ts +64 -0
- package/src/cli/commands/collection/rename.ts +95 -0
- package/src/cli/commands/context/add.ts +67 -0
- package/src/cli/commands/context/check.ts +153 -0
- package/src/cli/commands/context/index.ts +10 -0
- package/src/cli/commands/context/list.ts +109 -0
- package/src/cli/commands/context/rm.ts +52 -0
- package/src/cli/commands/doctor.ts +393 -0
- package/src/cli/commands/embed.ts +462 -0
- package/src/cli/commands/get.ts +356 -0
- package/src/cli/commands/index-cmd.ts +119 -0
- package/src/cli/commands/index.ts +102 -0
- package/src/cli/commands/init.ts +328 -0
- package/src/cli/commands/ls.ts +217 -0
- package/src/cli/commands/mcp/config.ts +300 -0
- package/src/cli/commands/mcp/index.ts +24 -0
- package/src/cli/commands/mcp/install.ts +203 -0
- package/src/cli/commands/mcp/paths.ts +470 -0
- package/src/cli/commands/mcp/status.ts +222 -0
- package/src/cli/commands/mcp/uninstall.ts +158 -0
- package/src/cli/commands/mcp.ts +20 -0
- package/src/cli/commands/models/clear.ts +103 -0
- package/src/cli/commands/models/index.ts +32 -0
- package/src/cli/commands/models/list.ts +214 -0
- package/src/cli/commands/models/path.ts +51 -0
- package/src/cli/commands/models/pull.ts +199 -0
- package/src/cli/commands/models/use.ts +85 -0
- package/src/cli/commands/multi-get.ts +400 -0
- package/src/cli/commands/query.ts +220 -0
- package/src/cli/commands/ref-parser.ts +108 -0
- package/src/cli/commands/reset.ts +191 -0
- package/src/cli/commands/search.ts +136 -0
- package/src/cli/commands/shared.ts +156 -0
- package/src/cli/commands/skill/index.ts +19 -0
- package/src/cli/commands/skill/install.ts +197 -0
- package/src/cli/commands/skill/paths-cmd.ts +81 -0
- package/src/cli/commands/skill/paths.ts +191 -0
- package/src/cli/commands/skill/show.ts +73 -0
- package/src/cli/commands/skill/uninstall.ts +141 -0
- package/src/cli/commands/status.ts +205 -0
- package/src/cli/commands/update.ts +68 -0
- package/src/cli/commands/vsearch.ts +188 -0
- package/src/cli/context.ts +64 -0
- package/src/cli/errors.ts +64 -0
- package/src/cli/format/search-results.ts +211 -0
- package/src/cli/options.ts +183 -0
- package/src/cli/program.ts +1330 -0
- package/src/cli/run.ts +213 -0
- package/src/cli/ui.ts +92 -0
- package/src/config/defaults.ts +20 -0
- package/src/config/index.ts +55 -0
- package/src/config/loader.ts +161 -0
- package/src/config/paths.ts +87 -0
- package/src/config/saver.ts +153 -0
- package/src/config/types.ts +280 -0
- package/src/converters/adapters/markitdownTs/adapter.ts +140 -0
- package/src/converters/adapters/officeparser/adapter.ts +126 -0
- package/src/converters/canonicalize.ts +89 -0
- package/src/converters/errors.ts +218 -0
- package/src/converters/index.ts +51 -0
- package/src/converters/mime.ts +163 -0
- package/src/converters/native/markdown.ts +115 -0
- package/src/converters/native/plaintext.ts +56 -0
- package/src/converters/path.ts +48 -0
- package/src/converters/pipeline.ts +159 -0
- package/src/converters/registry.ts +74 -0
- package/src/converters/types.ts +123 -0
- package/src/converters/versions.ts +24 -0
- package/src/index.ts +27 -0
- package/src/ingestion/chunker.ts +238 -0
- package/src/ingestion/index.ts +32 -0
- package/src/ingestion/language.ts +276 -0
- package/src/ingestion/sync.ts +671 -0
- package/src/ingestion/types.ts +219 -0
- package/src/ingestion/walker.ts +235 -0
- package/src/llm/cache.ts +467 -0
- package/src/llm/errors.ts +191 -0
- package/src/llm/index.ts +58 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +133 -0
- package/src/llm/nodeLlamaCpp/embedding.ts +165 -0
- package/src/llm/nodeLlamaCpp/generation.ts +88 -0
- package/src/llm/nodeLlamaCpp/lifecycle.ts +317 -0
- package/src/llm/nodeLlamaCpp/rerank.ts +94 -0
- package/src/llm/registry.ts +86 -0
- package/src/llm/types.ts +129 -0
- package/src/mcp/resources/index.ts +151 -0
- package/src/mcp/server.ts +229 -0
- package/src/mcp/tools/get.ts +220 -0
- package/src/mcp/tools/index.ts +160 -0
- package/src/mcp/tools/multi-get.ts +263 -0
- package/src/mcp/tools/query.ts +226 -0
- package/src/mcp/tools/search.ts +119 -0
- package/src/mcp/tools/status.ts +81 -0
- package/src/mcp/tools/vsearch.ts +198 -0
- package/src/pipeline/chunk-lookup.ts +44 -0
- package/src/pipeline/expansion.ts +256 -0
- package/src/pipeline/explain.ts +115 -0
- package/src/pipeline/fusion.ts +185 -0
- package/src/pipeline/hybrid.ts +535 -0
- package/src/pipeline/index.ts +64 -0
- package/src/pipeline/query-language.ts +118 -0
- package/src/pipeline/rerank.ts +223 -0
- package/src/pipeline/search.ts +261 -0
- package/src/pipeline/types.ts +328 -0
- package/src/pipeline/vsearch.ts +348 -0
- package/src/store/index.ts +41 -0
- package/src/store/migrations/001-initial.ts +196 -0
- package/src/store/migrations/index.ts +20 -0
- package/src/store/migrations/runner.ts +187 -0
- package/src/store/sqlite/adapter.ts +1242 -0
- package/src/store/sqlite/index.ts +7 -0
- package/src/store/sqlite/setup.ts +129 -0
- package/src/store/sqlite/types.ts +28 -0
- package/src/store/types.ts +506 -0
- package/src/store/vector/index.ts +13 -0
- package/src/store/vector/sqlite-vec.ts +373 -0
- package/src/store/vector/stats.ts +152 -0
- package/src/store/vector/types.ts +115 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: gno context list
|
|
3
|
+
*
|
|
4
|
+
* List all configured contexts.
|
|
5
|
+
*
|
|
6
|
+
* @module src/cli/commands/context/list
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadConfig } from '../../../config';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exit codes
|
|
13
|
+
*/
|
|
14
|
+
const EXIT_SUCCESS = 0;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Output format
|
|
18
|
+
*/
|
|
19
|
+
export type OutputFormat = 'terminal' | 'json' | 'md';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List all configured contexts.
|
|
23
|
+
*
|
|
24
|
+
* @param format - Output format (terminal, json, md)
|
|
25
|
+
* @returns Exit code
|
|
26
|
+
*/
|
|
27
|
+
export async function contextList(
|
|
28
|
+
format: OutputFormat = 'terminal'
|
|
29
|
+
): Promise<number> {
|
|
30
|
+
// Load config
|
|
31
|
+
const configResult = await loadConfig();
|
|
32
|
+
if (!configResult.ok) {
|
|
33
|
+
console.error(`Error: ${configResult.error.message}`);
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { contexts } = configResult.value;
|
|
38
|
+
|
|
39
|
+
// Format and output
|
|
40
|
+
formatOutput(format, contexts);
|
|
41
|
+
return EXIT_SUCCESS;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format and output context list
|
|
46
|
+
*/
|
|
47
|
+
function formatOutput(
|
|
48
|
+
format: OutputFormat,
|
|
49
|
+
contexts: Array<{ scopeKey: string; text: string }>
|
|
50
|
+
): void {
|
|
51
|
+
if (format === 'json') {
|
|
52
|
+
formatJson(contexts);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (format === 'md') {
|
|
57
|
+
formatMarkdown(contexts);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
formatTerminal(contexts);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format contexts as JSON
|
|
66
|
+
*/
|
|
67
|
+
function formatJson(contexts: Array<{ scopeKey: string; text: string }>): void {
|
|
68
|
+
const output = contexts.map((ctx) => ({
|
|
69
|
+
scope: ctx.scopeKey,
|
|
70
|
+
text: ctx.text,
|
|
71
|
+
}));
|
|
72
|
+
console.log(JSON.stringify(output, null, 2));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Format contexts as markdown
|
|
77
|
+
*/
|
|
78
|
+
function formatMarkdown(
|
|
79
|
+
contexts: Array<{ scopeKey: string; text: string }>
|
|
80
|
+
): void {
|
|
81
|
+
console.log('# Contexts\n');
|
|
82
|
+
if (contexts.length === 0) {
|
|
83
|
+
console.log('No contexts configured.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log('| Scope | Text |');
|
|
88
|
+
console.log('|-------|------|');
|
|
89
|
+
for (const ctx of contexts) {
|
|
90
|
+
console.log(`| ${ctx.scopeKey} | ${ctx.text} |`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Format contexts for terminal
|
|
96
|
+
*/
|
|
97
|
+
function formatTerminal(
|
|
98
|
+
contexts: Array<{ scopeKey: string; text: string }>
|
|
99
|
+
): void {
|
|
100
|
+
if (contexts.length === 0) {
|
|
101
|
+
console.log('No contexts configured.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log('Contexts:');
|
|
106
|
+
for (const ctx of contexts) {
|
|
107
|
+
console.log(` ${ctx.scopeKey} - ${ctx.text}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: gno context rm
|
|
3
|
+
*
|
|
4
|
+
* Remove a context.
|
|
5
|
+
*
|
|
6
|
+
* @module src/cli/commands/context/rm
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadConfig, saveConfig } from '../../../config';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exit codes
|
|
13
|
+
*/
|
|
14
|
+
const EXIT_SUCCESS = 0;
|
|
15
|
+
const EXIT_VALIDATION = 1;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Remove a context by scope.
|
|
19
|
+
*
|
|
20
|
+
* @param scope - Scope key to remove
|
|
21
|
+
* @returns Exit code
|
|
22
|
+
*/
|
|
23
|
+
export async function contextRm(scope: string): Promise<number> {
|
|
24
|
+
// Load config
|
|
25
|
+
const configResult = await loadConfig();
|
|
26
|
+
if (!configResult.ok) {
|
|
27
|
+
console.error(`Error: ${configResult.error.message}`);
|
|
28
|
+
return EXIT_VALIDATION;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const config = configResult.value;
|
|
32
|
+
|
|
33
|
+
// Find context
|
|
34
|
+
const index = config.contexts.findIndex((ctx) => ctx.scopeKey === scope);
|
|
35
|
+
if (index === -1) {
|
|
36
|
+
console.error(`Error: Context for scope "${scope}" not found`);
|
|
37
|
+
return EXIT_VALIDATION;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Remove context
|
|
41
|
+
config.contexts.splice(index, 1);
|
|
42
|
+
|
|
43
|
+
// Save config
|
|
44
|
+
const saveResult = await saveConfig(config);
|
|
45
|
+
if (!saveResult.ok) {
|
|
46
|
+
console.error(`Error: ${saveResult.error.message}`);
|
|
47
|
+
return EXIT_VALIDATION;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`Removed context for scope: ${scope}`);
|
|
51
|
+
return EXIT_SUCCESS;
|
|
52
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno doctor command implementation.
|
|
3
|
+
* Diagnose configuration and dependencies.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/doctor
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Database } from 'bun:sqlite';
|
|
9
|
+
import { stat } from 'node:fs/promises';
|
|
10
|
+
// node:os: arch/platform detection (no Bun equivalent)
|
|
11
|
+
import { arch, platform } from 'node:os';
|
|
12
|
+
import { getIndexDbPath, getModelsCachePath } from '../../app/constants';
|
|
13
|
+
import { getConfigPaths, isInitialized, loadConfig } from '../../config';
|
|
14
|
+
import type { Config } from '../../config/types';
|
|
15
|
+
import { ModelCache } from '../../llm/cache';
|
|
16
|
+
import { getActivePreset } from '../../llm/registry';
|
|
17
|
+
import {
|
|
18
|
+
getCustomSqlitePath,
|
|
19
|
+
getExtensionLoadingMode,
|
|
20
|
+
getLoadAttempts,
|
|
21
|
+
} from '../../store/sqlite/setup';
|
|
22
|
+
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Types
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
export type DoctorCheckStatus = 'ok' | 'warn' | 'error';
|
|
28
|
+
|
|
29
|
+
export interface DoctorCheck {
|
|
30
|
+
name: string;
|
|
31
|
+
status: DoctorCheckStatus;
|
|
32
|
+
message: string;
|
|
33
|
+
/** Additional diagnostic details (shown in verbose/json output) */
|
|
34
|
+
details?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DoctorOptions {
|
|
38
|
+
/** Override config path */
|
|
39
|
+
configPath?: string;
|
|
40
|
+
/** Output as JSON */
|
|
41
|
+
json?: boolean;
|
|
42
|
+
/** Output as Markdown */
|
|
43
|
+
md?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DoctorResult {
|
|
47
|
+
healthy: boolean;
|
|
48
|
+
checks: DoctorCheck[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
|
+
// Checks
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
async function checkConfig(configPath?: string): Promise<DoctorCheck> {
|
|
56
|
+
const initialized = await isInitialized(configPath);
|
|
57
|
+
if (!initialized) {
|
|
58
|
+
return {
|
|
59
|
+
name: 'config',
|
|
60
|
+
status: 'error',
|
|
61
|
+
message: 'Config not found. Run: gno init',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const configResult = await loadConfig(configPath);
|
|
66
|
+
if (!configResult.ok) {
|
|
67
|
+
return {
|
|
68
|
+
name: 'config',
|
|
69
|
+
status: 'error',
|
|
70
|
+
message: `Config invalid: ${configResult.error.message}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const paths = getConfigPaths();
|
|
75
|
+
return {
|
|
76
|
+
name: 'config',
|
|
77
|
+
status: 'ok',
|
|
78
|
+
message: `Config loaded: ${paths.configFile}`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function checkDatabase(): Promise<DoctorCheck> {
|
|
83
|
+
const dbPath = getIndexDbPath();
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await stat(dbPath);
|
|
87
|
+
return {
|
|
88
|
+
name: 'database',
|
|
89
|
+
status: 'ok',
|
|
90
|
+
message: `Database found: ${dbPath}`,
|
|
91
|
+
};
|
|
92
|
+
} catch {
|
|
93
|
+
return {
|
|
94
|
+
name: 'database',
|
|
95
|
+
status: 'warn',
|
|
96
|
+
message: 'Database not found. Run: gno init',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function checkModels(config: Config): Promise<DoctorCheck[]> {
|
|
102
|
+
const checks: DoctorCheck[] = [];
|
|
103
|
+
const cache = new ModelCache(getModelsCachePath());
|
|
104
|
+
const preset = getActivePreset(config);
|
|
105
|
+
|
|
106
|
+
for (const type of ['embed', 'rerank', 'gen'] as const) {
|
|
107
|
+
const uri = preset[type];
|
|
108
|
+
const cached = await cache.isCached(uri);
|
|
109
|
+
|
|
110
|
+
checks.push({
|
|
111
|
+
name: `${type}-model`,
|
|
112
|
+
status: cached ? 'ok' : 'warn',
|
|
113
|
+
message: cached
|
|
114
|
+
? `${type} model cached`
|
|
115
|
+
: `${type} model not cached. Run: gno models pull --${type}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return checks;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function checkNodeLlamaCpp(): Promise<DoctorCheck> {
|
|
123
|
+
try {
|
|
124
|
+
const { getLlama } = await import('node-llama-cpp');
|
|
125
|
+
// Just check that we can get the llama instance
|
|
126
|
+
await getLlama();
|
|
127
|
+
return {
|
|
128
|
+
name: 'node-llama-cpp',
|
|
129
|
+
status: 'ok',
|
|
130
|
+
message: 'node-llama-cpp loaded successfully',
|
|
131
|
+
};
|
|
132
|
+
} catch (e) {
|
|
133
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
134
|
+
return {
|
|
135
|
+
name: 'node-llama-cpp',
|
|
136
|
+
status: 'error',
|
|
137
|
+
message: `node-llama-cpp failed: ${message}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check SQLite extension support (FTS5, sqlite-vec).
|
|
144
|
+
* Uses runtime capability probes instead of compile_options strings.
|
|
145
|
+
*/
|
|
146
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: diagnostic checks with platform-specific handling
|
|
147
|
+
async function checkSqliteExtensions(): Promise<DoctorCheck[]> {
|
|
148
|
+
const checks: DoctorCheck[] = [];
|
|
149
|
+
const plat = platform();
|
|
150
|
+
const archName = arch();
|
|
151
|
+
const mode = getExtensionLoadingMode();
|
|
152
|
+
const customPath = getCustomSqlitePath();
|
|
153
|
+
const attempts = getLoadAttempts();
|
|
154
|
+
|
|
155
|
+
// Platform/mode info
|
|
156
|
+
let modeDesc = 'unavailable';
|
|
157
|
+
if (mode === 'native') {
|
|
158
|
+
modeDesc = 'native (bundled SQLite supports extensions)';
|
|
159
|
+
} else if (mode === 'custom') {
|
|
160
|
+
modeDesc = `custom (${customPath})`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const details: string[] = [
|
|
164
|
+
`Platform: ${plat}-${archName}`,
|
|
165
|
+
`Mode: ${modeDesc}`,
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
// Add load attempt details if there were failures
|
|
169
|
+
if (attempts.length > 0) {
|
|
170
|
+
details.push('Load attempts:');
|
|
171
|
+
for (const attempt of attempts) {
|
|
172
|
+
details.push(` ${attempt.path}: ${attempt.error}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Create in-memory DB for probes
|
|
177
|
+
const db = new Database(':memory:');
|
|
178
|
+
let version = 'unknown';
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const row = db.query('SELECT sqlite_version() as v').get() as { v: string };
|
|
182
|
+
version = row.v;
|
|
183
|
+
details.push(`SQLite version: ${version}`);
|
|
184
|
+
} catch {
|
|
185
|
+
// Continue with unknown version
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Probe FTS5 capability
|
|
189
|
+
let fts5Available = false;
|
|
190
|
+
try {
|
|
191
|
+
db.exec('CREATE VIRTUAL TABLE _fts5_probe USING fts5(x)');
|
|
192
|
+
db.exec('DROP TABLE _fts5_probe');
|
|
193
|
+
fts5Available = true;
|
|
194
|
+
} catch {
|
|
195
|
+
// FTS5 not available
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
checks.push({
|
|
199
|
+
name: 'sqlite-fts5',
|
|
200
|
+
status: fts5Available ? 'ok' : 'error',
|
|
201
|
+
message: fts5Available ? 'FTS5 available' : 'FTS5 not available (required)',
|
|
202
|
+
details: fts5Available
|
|
203
|
+
? undefined
|
|
204
|
+
: ['Full-text search requires FTS5 support'],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Probe JSON capability
|
|
208
|
+
let jsonAvailable = false;
|
|
209
|
+
try {
|
|
210
|
+
db.query("SELECT json_valid('{}')").get();
|
|
211
|
+
jsonAvailable = true;
|
|
212
|
+
} catch {
|
|
213
|
+
// JSON not available
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
checks.push({
|
|
217
|
+
name: 'sqlite-json',
|
|
218
|
+
status: jsonAvailable ? 'ok' : 'warn',
|
|
219
|
+
message: jsonAvailable ? 'JSON1 available' : 'JSON1 not available',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Probe sqlite-vec extension
|
|
223
|
+
let sqliteVecAvailable = false;
|
|
224
|
+
let sqliteVecVersion = '';
|
|
225
|
+
let sqliteVecError = '';
|
|
226
|
+
try {
|
|
227
|
+
const sqliteVec = await import('sqlite-vec');
|
|
228
|
+
sqliteVec.load(db);
|
|
229
|
+
sqliteVecAvailable = true;
|
|
230
|
+
// Try to get version
|
|
231
|
+
try {
|
|
232
|
+
const vrow = db.query('SELECT vec_version() as v').get() as { v: string };
|
|
233
|
+
sqliteVecVersion = vrow.v;
|
|
234
|
+
} catch {
|
|
235
|
+
// No version available
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
sqliteVecError = e instanceof Error ? e.message : String(e);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let vecMessage: string;
|
|
242
|
+
if (sqliteVecAvailable) {
|
|
243
|
+
vecMessage = sqliteVecVersion
|
|
244
|
+
? `sqlite-vec loaded (v${sqliteVecVersion})`
|
|
245
|
+
: 'sqlite-vec loaded';
|
|
246
|
+
} else if (mode === 'unavailable') {
|
|
247
|
+
vecMessage =
|
|
248
|
+
'sqlite-vec unavailable (no extension support on macOS without Homebrew)';
|
|
249
|
+
} else {
|
|
250
|
+
vecMessage = sqliteVecError
|
|
251
|
+
? `sqlite-vec failed: ${sqliteVecError}`
|
|
252
|
+
: 'sqlite-vec failed to load';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const vecDetails = [...details];
|
|
256
|
+
if (!sqliteVecAvailable && plat === 'darwin' && mode === 'unavailable') {
|
|
257
|
+
vecDetails.push('Install Homebrew SQLite: brew install sqlite3');
|
|
258
|
+
}
|
|
259
|
+
if (sqliteVecError) {
|
|
260
|
+
vecDetails.push(`Load error: ${sqliteVecError}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
checks.push({
|
|
264
|
+
name: 'sqlite-vec',
|
|
265
|
+
status: sqliteVecAvailable ? 'ok' : 'warn',
|
|
266
|
+
message: vecMessage,
|
|
267
|
+
details: vecDetails,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
db.close();
|
|
271
|
+
return checks;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
+
// Implementation
|
|
276
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Execute gno doctor command.
|
|
280
|
+
*/
|
|
281
|
+
export async function doctor(
|
|
282
|
+
options: DoctorOptions = {}
|
|
283
|
+
): Promise<DoctorResult> {
|
|
284
|
+
const checks: DoctorCheck[] = [];
|
|
285
|
+
|
|
286
|
+
// Config check
|
|
287
|
+
checks.push(await checkConfig(options.configPath));
|
|
288
|
+
|
|
289
|
+
// Database check
|
|
290
|
+
checks.push(await checkDatabase());
|
|
291
|
+
|
|
292
|
+
// Load config for model checks (if available)
|
|
293
|
+
const { createDefaultConfig } = await import('../../config');
|
|
294
|
+
const configResult = await loadConfig(options.configPath);
|
|
295
|
+
const config = configResult.ok ? configResult.value : createDefaultConfig();
|
|
296
|
+
|
|
297
|
+
// Model checks
|
|
298
|
+
const modelChecks = await checkModels(config);
|
|
299
|
+
checks.push(...modelChecks);
|
|
300
|
+
|
|
301
|
+
// node-llama-cpp check
|
|
302
|
+
checks.push(await checkNodeLlamaCpp());
|
|
303
|
+
|
|
304
|
+
// SQLite extension checks
|
|
305
|
+
const sqliteChecks = await checkSqliteExtensions();
|
|
306
|
+
checks.push(...sqliteChecks);
|
|
307
|
+
|
|
308
|
+
// Determine overall health
|
|
309
|
+
const hasErrors = checks.some((c) => c.status === 'error');
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
healthy: !hasErrors,
|
|
313
|
+
checks,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
// Formatting
|
|
319
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
function statusIcon(status: DoctorCheckStatus): string {
|
|
322
|
+
switch (status) {
|
|
323
|
+
case 'ok':
|
|
324
|
+
return '✓';
|
|
325
|
+
case 'warn':
|
|
326
|
+
return '!';
|
|
327
|
+
case 'error':
|
|
328
|
+
return '✗';
|
|
329
|
+
default:
|
|
330
|
+
return '?';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function formatTerminal(result: DoctorResult): string {
|
|
335
|
+
const lines: string[] = [];
|
|
336
|
+
|
|
337
|
+
lines.push('GNO Health Check');
|
|
338
|
+
lines.push('');
|
|
339
|
+
|
|
340
|
+
for (const check of result.checks) {
|
|
341
|
+
lines.push(` ${statusIcon(check.status)} ${check.name}: ${check.message}`);
|
|
342
|
+
// Show details for non-ok checks
|
|
343
|
+
if (check.details && check.status !== 'ok') {
|
|
344
|
+
for (const detail of check.details) {
|
|
345
|
+
lines.push(` ${detail}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
lines.push('');
|
|
351
|
+
lines.push(`Overall: ${result.healthy ? 'HEALTHY' : 'UNHEALTHY'}`);
|
|
352
|
+
|
|
353
|
+
return lines.join('\n');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function formatMarkdown(result: DoctorResult): string {
|
|
357
|
+
const lines: string[] = [];
|
|
358
|
+
|
|
359
|
+
lines.push('# GNO Health Check');
|
|
360
|
+
lines.push('');
|
|
361
|
+
lines.push(`**Status**: ${result.healthy ? '✓ Healthy' : '✗ Unhealthy'}`);
|
|
362
|
+
lines.push('');
|
|
363
|
+
lines.push('## Checks');
|
|
364
|
+
lines.push('');
|
|
365
|
+
lines.push('| Check | Status | Message |');
|
|
366
|
+
lines.push('|-------|--------|---------|');
|
|
367
|
+
|
|
368
|
+
for (const check of result.checks) {
|
|
369
|
+
lines.push(
|
|
370
|
+
`| ${check.name} | ${statusIcon(check.status)} | ${check.message} |`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return lines.join('\n');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Format doctor result for output.
|
|
379
|
+
*/
|
|
380
|
+
export function formatDoctor(
|
|
381
|
+
result: DoctorResult,
|
|
382
|
+
options: DoctorOptions
|
|
383
|
+
): string {
|
|
384
|
+
if (options.json) {
|
|
385
|
+
return JSON.stringify(result, null, 2);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (options.md) {
|
|
389
|
+
return formatMarkdown(result);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return formatTerminal(result);
|
|
393
|
+
}
|