@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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show/preview GNO skill files without installing.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/skill/show
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readdir } from 'node:fs/promises';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { CliError } from '../../errors.js';
|
|
11
|
+
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// Source Path Resolution
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
function getSkillSourceDir(): string {
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
return join(__dirname, '../../../../assets/skill');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Show Command
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export interface ShowOptions {
|
|
26
|
+
file?: string;
|
|
27
|
+
all?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_FILE = 'SKILL.md';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Show skill file content.
|
|
34
|
+
*/
|
|
35
|
+
export async function showSkill(opts: ShowOptions = {}): Promise<void> {
|
|
36
|
+
const sourceDir = getSkillSourceDir();
|
|
37
|
+
|
|
38
|
+
// Get available files
|
|
39
|
+
let files: string[];
|
|
40
|
+
try {
|
|
41
|
+
files = await readdir(sourceDir);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new CliError('RUNTIME', `Skill files not found at ${sourceDir}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mdFiles = files.filter((f) => f.endsWith('.md')).sort();
|
|
47
|
+
|
|
48
|
+
if (opts.all) {
|
|
49
|
+
// Show all files with separators
|
|
50
|
+
for (const file of mdFiles) {
|
|
51
|
+
process.stdout.write(`--- ${file} ---\n`);
|
|
52
|
+
const content = await Bun.file(join(sourceDir, file)).text();
|
|
53
|
+
process.stdout.write(`${content}\n`);
|
|
54
|
+
process.stdout.write('\n');
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// Show single file
|
|
58
|
+
const fileName = opts.file ?? DEFAULT_FILE;
|
|
59
|
+
|
|
60
|
+
if (!mdFiles.includes(fileName)) {
|
|
61
|
+
throw new CliError(
|
|
62
|
+
'VALIDATION',
|
|
63
|
+
`Unknown file: ${fileName}. Available: ${mdFiles.join(', ')}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const content = await Bun.file(join(sourceDir, fileName)).text();
|
|
68
|
+
process.stdout.write(`${content}\n`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Always list available files at end
|
|
72
|
+
process.stdout.write(`\nFiles: ${mdFiles.join(', ')}\n`);
|
|
73
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uninstall GNO agent skill from Claude Code or Codex.
|
|
3
|
+
* Includes safety checks before deletion.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/skill/uninstall
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { rm } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { CliError } from '../../errors.js';
|
|
11
|
+
import { getGlobals } from '../../program.js';
|
|
12
|
+
import {
|
|
13
|
+
resolveSkillPaths,
|
|
14
|
+
type SkillScope,
|
|
15
|
+
type SkillTarget,
|
|
16
|
+
validatePathForDeletion,
|
|
17
|
+
} from './paths.js';
|
|
18
|
+
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
// Uninstall Command
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export interface UninstallOptions {
|
|
24
|
+
scope?: SkillScope;
|
|
25
|
+
target?: SkillTarget | 'all';
|
|
26
|
+
/** Override for testing */
|
|
27
|
+
cwd?: string;
|
|
28
|
+
/** Override for testing */
|
|
29
|
+
homeDir?: string;
|
|
30
|
+
/** JSON output (defaults to globals.json) */
|
|
31
|
+
json?: boolean;
|
|
32
|
+
/** Quiet mode (defaults to globals.quiet) */
|
|
33
|
+
quiet?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface UninstallResult {
|
|
37
|
+
target: SkillTarget;
|
|
38
|
+
scope: SkillScope;
|
|
39
|
+
path: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Uninstall skill from a single target.
|
|
44
|
+
*/
|
|
45
|
+
async function uninstallFromTarget(
|
|
46
|
+
scope: SkillScope,
|
|
47
|
+
target: SkillTarget,
|
|
48
|
+
overrides?: { cwd?: string; homeDir?: string }
|
|
49
|
+
): Promise<UninstallResult | null> {
|
|
50
|
+
const paths = resolveSkillPaths({ scope, target, ...overrides });
|
|
51
|
+
|
|
52
|
+
// Check if exists
|
|
53
|
+
const exists = await Bun.file(join(paths.gnoDir, 'SKILL.md')).exists();
|
|
54
|
+
if (!exists) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Safety validation
|
|
59
|
+
const validationError = validatePathForDeletion(paths.gnoDir, paths.base);
|
|
60
|
+
if (validationError) {
|
|
61
|
+
throw new CliError(
|
|
62
|
+
'RUNTIME',
|
|
63
|
+
`Safety check failed for ${paths.gnoDir}: ${validationError}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove directory
|
|
68
|
+
try {
|
|
69
|
+
await rm(paths.gnoDir, { recursive: true, force: true });
|
|
70
|
+
return { target, scope, path: paths.gnoDir };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new CliError(
|
|
73
|
+
'RUNTIME',
|
|
74
|
+
`Failed to remove skill: ${err instanceof Error ? err.message : String(err)}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get globals with fallback for testing.
|
|
81
|
+
*/
|
|
82
|
+
function safeGetGlobals(): { json: boolean; quiet: boolean } {
|
|
83
|
+
try {
|
|
84
|
+
return getGlobals();
|
|
85
|
+
} catch {
|
|
86
|
+
return { json: false, quiet: false };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Uninstall GNO skill.
|
|
92
|
+
*/
|
|
93
|
+
export async function uninstallSkill(
|
|
94
|
+
opts: UninstallOptions = {}
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const scope = opts.scope ?? 'project';
|
|
97
|
+
const target = opts.target ?? 'claude';
|
|
98
|
+
const globals = safeGetGlobals();
|
|
99
|
+
const json = opts.json ?? globals.json;
|
|
100
|
+
const quiet = opts.quiet ?? globals.quiet;
|
|
101
|
+
|
|
102
|
+
const targets: SkillTarget[] =
|
|
103
|
+
target === 'all' ? ['claude', 'codex'] : [target];
|
|
104
|
+
|
|
105
|
+
const results: UninstallResult[] = [];
|
|
106
|
+
const notFound: string[] = [];
|
|
107
|
+
|
|
108
|
+
for (const t of targets) {
|
|
109
|
+
const result = await uninstallFromTarget(scope, t, {
|
|
110
|
+
cwd: opts.cwd,
|
|
111
|
+
homeDir: opts.homeDir,
|
|
112
|
+
});
|
|
113
|
+
if (result) {
|
|
114
|
+
results.push(result);
|
|
115
|
+
} else {
|
|
116
|
+
notFound.push(t);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If nothing was uninstalled
|
|
121
|
+
if (results.length === 0) {
|
|
122
|
+
throw new CliError(
|
|
123
|
+
'VALIDATION',
|
|
124
|
+
`GNO skill not found for ${targets.join(', ')} (${scope} scope)`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Output
|
|
129
|
+
if (json) {
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
`${JSON.stringify({ uninstalled: results }, null, 2)}\n`
|
|
132
|
+
);
|
|
133
|
+
} else if (!quiet) {
|
|
134
|
+
for (const r of results) {
|
|
135
|
+
process.stdout.write(`Uninstalled GNO skill from ${r.path}\n`);
|
|
136
|
+
}
|
|
137
|
+
if (notFound.length > 0) {
|
|
138
|
+
process.stdout.write(`(Not found for: ${notFound.join(', ')})\n`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno status command implementation.
|
|
3
|
+
* Display index status and health information.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/status
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getIndexDbPath } from '../../app/constants';
|
|
9
|
+
import { getConfigPaths, isInitialized, loadConfig } from '../../config';
|
|
10
|
+
import { SqliteAdapter } from '../../store/sqlite/adapter';
|
|
11
|
+
import type { IndexStatus } from '../../store/types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for status command.
|
|
15
|
+
*/
|
|
16
|
+
export interface StatusOptions {
|
|
17
|
+
/** Override config path */
|
|
18
|
+
configPath?: string;
|
|
19
|
+
/** Output as JSON */
|
|
20
|
+
json?: boolean;
|
|
21
|
+
/** Output as Markdown */
|
|
22
|
+
md?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Result of status command.
|
|
27
|
+
*/
|
|
28
|
+
export type StatusResult =
|
|
29
|
+
| { success: true; status: IndexStatus }
|
|
30
|
+
| { success: false; error: string };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format status as terminal output.
|
|
34
|
+
*/
|
|
35
|
+
function formatTerminal(indexStatus: IndexStatus): string {
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
|
|
38
|
+
lines.push(`Index: ${indexStatus.indexName}`);
|
|
39
|
+
lines.push(`Config: ${indexStatus.configPath}`);
|
|
40
|
+
lines.push(`Database: ${indexStatus.dbPath}`);
|
|
41
|
+
lines.push('');
|
|
42
|
+
|
|
43
|
+
if (indexStatus.collections.length === 0) {
|
|
44
|
+
lines.push('No collections configured.');
|
|
45
|
+
} else {
|
|
46
|
+
lines.push('Collections:');
|
|
47
|
+
for (const c of indexStatus.collections) {
|
|
48
|
+
lines.push(
|
|
49
|
+
` ${c.name}: ${c.activeDocuments} docs, ${c.totalChunks} chunks` +
|
|
50
|
+
(c.embeddedChunks > 0 ? `, ${c.embeddedChunks} embedded` : '')
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lines.push('');
|
|
56
|
+
lines.push(
|
|
57
|
+
`Total: ${indexStatus.activeDocuments} documents, ${indexStatus.totalChunks} chunks`
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (indexStatus.embeddingBacklog > 0) {
|
|
61
|
+
lines.push(`Embedding backlog: ${indexStatus.embeddingBacklog} chunks`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (indexStatus.recentErrors > 0) {
|
|
65
|
+
lines.push(`Recent errors: ${indexStatus.recentErrors} (last 24h)`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (indexStatus.lastUpdatedAt) {
|
|
69
|
+
lines.push(`Last updated: ${indexStatus.lastUpdatedAt}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
lines.push(`Health: ${indexStatus.healthy ? 'OK' : 'DEGRADED'}`);
|
|
73
|
+
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Format status as Markdown.
|
|
79
|
+
*/
|
|
80
|
+
function formatMarkdown(indexStatus: IndexStatus): string {
|
|
81
|
+
const lines: string[] = [];
|
|
82
|
+
|
|
83
|
+
lines.push(`# Index Status: ${indexStatus.indexName}`);
|
|
84
|
+
lines.push('');
|
|
85
|
+
lines.push(`- **Config**: ${indexStatus.configPath}`);
|
|
86
|
+
lines.push(`- **Database**: ${indexStatus.dbPath}`);
|
|
87
|
+
lines.push(`- **Health**: ${indexStatus.healthy ? '✓ OK' : '⚠ DEGRADED'}`);
|
|
88
|
+
lines.push('');
|
|
89
|
+
|
|
90
|
+
if (indexStatus.collections.length > 0) {
|
|
91
|
+
lines.push('## Collections');
|
|
92
|
+
lines.push('');
|
|
93
|
+
lines.push('| Name | Path | Docs | Chunks | Embedded |');
|
|
94
|
+
lines.push('|------|------|------|--------|----------|');
|
|
95
|
+
for (const c of indexStatus.collections) {
|
|
96
|
+
lines.push(
|
|
97
|
+
`| ${c.name} | ${c.path} | ${c.activeDocuments} | ${c.totalChunks} | ${c.embeddedChunks} |`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
lines.push('');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
lines.push('## Summary');
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(`- **Documents**: ${indexStatus.activeDocuments}`);
|
|
106
|
+
lines.push(`- **Chunks**: ${indexStatus.totalChunks}`);
|
|
107
|
+
lines.push(`- **Embedding backlog**: ${indexStatus.embeddingBacklog}`);
|
|
108
|
+
lines.push(`- **Recent errors**: ${indexStatus.recentErrors}`);
|
|
109
|
+
|
|
110
|
+
if (indexStatus.lastUpdatedAt) {
|
|
111
|
+
lines.push(`- **Last updated**: ${indexStatus.lastUpdatedAt}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Execute gno status command.
|
|
119
|
+
*/
|
|
120
|
+
export async function status(
|
|
121
|
+
options: StatusOptions = {}
|
|
122
|
+
): Promise<StatusResult> {
|
|
123
|
+
// Check if initialized
|
|
124
|
+
const initialized = await isInitialized(options.configPath);
|
|
125
|
+
if (!initialized) {
|
|
126
|
+
return { success: false, error: 'GNO not initialized. Run: gno init' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Load config
|
|
130
|
+
const configResult = await loadConfig(options.configPath);
|
|
131
|
+
if (!configResult.ok) {
|
|
132
|
+
return { success: false, error: configResult.error.message };
|
|
133
|
+
}
|
|
134
|
+
const config = configResult.value;
|
|
135
|
+
|
|
136
|
+
// Open database
|
|
137
|
+
const store = new SqliteAdapter();
|
|
138
|
+
const dbPath = getIndexDbPath();
|
|
139
|
+
const paths = getConfigPaths();
|
|
140
|
+
|
|
141
|
+
// Set configPath for status output
|
|
142
|
+
store.setConfigPath(paths.configFile);
|
|
143
|
+
|
|
144
|
+
const openResult = await store.open(dbPath, config.ftsTokenizer);
|
|
145
|
+
if (!openResult.ok) {
|
|
146
|
+
return { success: false, error: openResult.error.message };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const statusResult = await store.getStatus();
|
|
151
|
+
if (!statusResult.ok) {
|
|
152
|
+
return { success: false, error: statusResult.error.message };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { success: true, status: statusResult.value };
|
|
156
|
+
} finally {
|
|
157
|
+
await store.close();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Format status result for output.
|
|
163
|
+
*/
|
|
164
|
+
export function formatStatus(
|
|
165
|
+
result: StatusResult,
|
|
166
|
+
options: StatusOptions
|
|
167
|
+
): string {
|
|
168
|
+
if (!result.success) {
|
|
169
|
+
return options.json
|
|
170
|
+
? JSON.stringify({ error: { code: 'RUNTIME', message: result.error } })
|
|
171
|
+
: `Error: ${result.error}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (options.json) {
|
|
175
|
+
// Transform to match CLI spec output format
|
|
176
|
+
const s = result.status;
|
|
177
|
+
return JSON.stringify(
|
|
178
|
+
{
|
|
179
|
+
indexName: s.indexName,
|
|
180
|
+
configPath: s.configPath,
|
|
181
|
+
dbPath: s.dbPath,
|
|
182
|
+
collections: s.collections.map((c) => ({
|
|
183
|
+
name: c.name,
|
|
184
|
+
path: c.path,
|
|
185
|
+
documentCount: c.activeDocuments,
|
|
186
|
+
chunkCount: c.totalChunks,
|
|
187
|
+
embeddedCount: c.embeddedChunks,
|
|
188
|
+
})),
|
|
189
|
+
totalDocuments: s.activeDocuments,
|
|
190
|
+
totalChunks: s.totalChunks,
|
|
191
|
+
embeddingBacklog: s.embeddingBacklog,
|
|
192
|
+
lastUpdated: s.lastUpdatedAt,
|
|
193
|
+
healthy: s.healthy,
|
|
194
|
+
},
|
|
195
|
+
null,
|
|
196
|
+
2
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (options.md) {
|
|
201
|
+
return formatMarkdown(result.status);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return formatTerminal(result.status);
|
|
205
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno update command implementation.
|
|
3
|
+
* Sync files from disk into the index (ingestion without embedding).
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/update
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defaultSyncService, type SyncResult } from '../../ingestion';
|
|
9
|
+
import { formatSyncResultLines, initStore } from './shared';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for update command.
|
|
13
|
+
*/
|
|
14
|
+
export interface UpdateOptions {
|
|
15
|
+
/** Override config path */
|
|
16
|
+
configPath?: string;
|
|
17
|
+
/** Run git pull in git repositories before scanning */
|
|
18
|
+
gitPull?: boolean;
|
|
19
|
+
/** Verbose output */
|
|
20
|
+
verbose?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Result of update command.
|
|
25
|
+
*/
|
|
26
|
+
export type UpdateResult =
|
|
27
|
+
| { success: true; result: SyncResult }
|
|
28
|
+
| { success: false; error: string };
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Execute gno update command.
|
|
32
|
+
*/
|
|
33
|
+
export async function update(
|
|
34
|
+
options: UpdateOptions = {}
|
|
35
|
+
): Promise<UpdateResult> {
|
|
36
|
+
const initResult = await initStore({ configPath: options.configPath });
|
|
37
|
+
if (!initResult.ok) {
|
|
38
|
+
return { success: false, error: initResult.error };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { store, collections } = initResult;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Run sync service
|
|
45
|
+
const result = await defaultSyncService.syncAll(collections, store, {
|
|
46
|
+
gitPull: options.gitPull,
|
|
47
|
+
runUpdateCmd: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return { success: true, result };
|
|
51
|
+
} finally {
|
|
52
|
+
await store.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format update result for output.
|
|
58
|
+
*/
|
|
59
|
+
export function formatUpdate(
|
|
60
|
+
result: UpdateResult,
|
|
61
|
+
options: UpdateOptions
|
|
62
|
+
): string {
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
return `Error: ${result.error}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return formatSyncResultLines(result.result, options).join('\n');
|
|
68
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno vsearch command implementation.
|
|
3
|
+
* Vector semantic search over indexed documents.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/vsearch
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { LlmAdapter } from '../../llm/nodeLlamaCpp/adapter';
|
|
9
|
+
import { getActivePreset } from '../../llm/registry';
|
|
10
|
+
import type { SearchOptions, SearchResults } from '../../pipeline/types';
|
|
11
|
+
import {
|
|
12
|
+
searchVectorWithEmbedding,
|
|
13
|
+
type VectorSearchDeps,
|
|
14
|
+
} from '../../pipeline/vsearch';
|
|
15
|
+
import { createVectorIndexPort } from '../../store/vector';
|
|
16
|
+
import {
|
|
17
|
+
type FormatOptions,
|
|
18
|
+
formatSearchResults,
|
|
19
|
+
} from '../format/search-results';
|
|
20
|
+
import { initStore } from './shared';
|
|
21
|
+
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// Types
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export type VsearchCommandOptions = SearchOptions & {
|
|
27
|
+
/** Override config path */
|
|
28
|
+
configPath?: string;
|
|
29
|
+
/** Override model URI */
|
|
30
|
+
model?: string;
|
|
31
|
+
/** Output as JSON */
|
|
32
|
+
json?: boolean;
|
|
33
|
+
/** Output as Markdown */
|
|
34
|
+
md?: boolean;
|
|
35
|
+
/** Output as CSV */
|
|
36
|
+
csv?: boolean;
|
|
37
|
+
/** Output as XML */
|
|
38
|
+
xml?: boolean;
|
|
39
|
+
/** Output files only */
|
|
40
|
+
files?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type VsearchResult =
|
|
44
|
+
| { success: true; data: SearchResults }
|
|
45
|
+
| { success: false; error: string };
|
|
46
|
+
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
// Command Implementation
|
|
49
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Execute gno vsearch command.
|
|
53
|
+
*/
|
|
54
|
+
export async function vsearch(
|
|
55
|
+
query: string,
|
|
56
|
+
options: VsearchCommandOptions = {}
|
|
57
|
+
): Promise<VsearchResult> {
|
|
58
|
+
// Adjust default limit based on output format
|
|
59
|
+
const isStructured =
|
|
60
|
+
options.json || options.files || options.csv || options.xml;
|
|
61
|
+
const limit = options.limit ?? (isStructured ? 20 : 5);
|
|
62
|
+
|
|
63
|
+
const initResult = await initStore({
|
|
64
|
+
configPath: options.configPath,
|
|
65
|
+
collection: options.collection,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!initResult.ok) {
|
|
69
|
+
return { success: false, error: initResult.error };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { store, config } = initResult;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Get model URI from preset
|
|
76
|
+
const preset = getActivePreset(config);
|
|
77
|
+
const modelUri = options.model ?? preset.embed;
|
|
78
|
+
|
|
79
|
+
// Create LLM adapter for embeddings
|
|
80
|
+
const llm = new LlmAdapter(config);
|
|
81
|
+
const embedResult = await llm.createEmbeddingPort(modelUri);
|
|
82
|
+
if (!embedResult.ok) {
|
|
83
|
+
return { success: false, error: embedResult.error.message };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const embedPort = embedResult.value;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Embed query (also determines dimensions - avoids double embed)
|
|
90
|
+
const queryEmbedResult = await embedPort.embed(query);
|
|
91
|
+
if (!queryEmbedResult.ok) {
|
|
92
|
+
return { success: false, error: queryEmbedResult.error.message };
|
|
93
|
+
}
|
|
94
|
+
const queryEmbedding = new Float32Array(queryEmbedResult.value);
|
|
95
|
+
const dimensions = queryEmbedding.length;
|
|
96
|
+
|
|
97
|
+
// Create vector index port
|
|
98
|
+
const db = store.getRawDb();
|
|
99
|
+
const vectorResult = await createVectorIndexPort(db, {
|
|
100
|
+
model: modelUri,
|
|
101
|
+
dimensions,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!vectorResult.ok) {
|
|
105
|
+
return { success: false, error: vectorResult.error.message };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const vectorIndex = vectorResult.value;
|
|
109
|
+
|
|
110
|
+
const deps: VectorSearchDeps = {
|
|
111
|
+
store,
|
|
112
|
+
vectorIndex,
|
|
113
|
+
embedPort,
|
|
114
|
+
config,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Pass pre-computed embedding to avoid double-embed
|
|
118
|
+
const result = await searchVectorWithEmbedding(
|
|
119
|
+
deps,
|
|
120
|
+
query,
|
|
121
|
+
queryEmbedding,
|
|
122
|
+
{ ...options, limit }
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
return { success: false, error: result.error.message };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { success: true, data: result.value };
|
|
130
|
+
} finally {
|
|
131
|
+
await embedPort.dispose();
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
await store.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// Formatter
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get output format from options.
|
|
144
|
+
*/
|
|
145
|
+
function getFormatType(
|
|
146
|
+
options: VsearchCommandOptions
|
|
147
|
+
): FormatOptions['format'] {
|
|
148
|
+
if (options.json) {
|
|
149
|
+
return 'json';
|
|
150
|
+
}
|
|
151
|
+
if (options.files) {
|
|
152
|
+
return 'files';
|
|
153
|
+
}
|
|
154
|
+
if (options.csv) {
|
|
155
|
+
return 'csv';
|
|
156
|
+
}
|
|
157
|
+
if (options.md) {
|
|
158
|
+
return 'md';
|
|
159
|
+
}
|
|
160
|
+
if (options.xml) {
|
|
161
|
+
return 'xml';
|
|
162
|
+
}
|
|
163
|
+
return 'terminal';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Format vsearch result for output.
|
|
168
|
+
*/
|
|
169
|
+
export function formatVsearch(
|
|
170
|
+
result: VsearchResult,
|
|
171
|
+
options: VsearchCommandOptions
|
|
172
|
+
): string {
|
|
173
|
+
if (!result.success) {
|
|
174
|
+
return options.json
|
|
175
|
+
? JSON.stringify({
|
|
176
|
+
error: { code: 'QUERY_FAILED', message: result.error },
|
|
177
|
+
})
|
|
178
|
+
: `Error: ${result.error}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const formatOpts: FormatOptions = {
|
|
182
|
+
format: getFormatType(options),
|
|
183
|
+
full: options.full,
|
|
184
|
+
lineNumbers: options.lineNumbers,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return formatSearchResults(result.data, formatOpts);
|
|
188
|
+
}
|