@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,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno init command implementation.
|
|
3
|
+
* Initializes GNO config, directories, and optionally adds a collection.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/init
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { basename } from 'node:path';
|
|
9
|
+
import { getIndexDbPath } from '../../app/constants';
|
|
10
|
+
import {
|
|
11
|
+
type Collection,
|
|
12
|
+
createDefaultConfig,
|
|
13
|
+
DEFAULT_EXCLUDES,
|
|
14
|
+
DEFAULT_PATTERN,
|
|
15
|
+
ensureDirectories,
|
|
16
|
+
FTS_TOKENIZERS,
|
|
17
|
+
type FtsTokenizer,
|
|
18
|
+
getConfigPaths,
|
|
19
|
+
isInitialized,
|
|
20
|
+
isValidLanguageHint,
|
|
21
|
+
loadConfigOrNull,
|
|
22
|
+
pathExists,
|
|
23
|
+
saveConfig,
|
|
24
|
+
toAbsolutePath,
|
|
25
|
+
} from '../../config';
|
|
26
|
+
|
|
27
|
+
/** Pattern to replace invalid chars in collection names with hyphens */
|
|
28
|
+
const INVALID_NAME_CHARS = /[^a-z0-9_-]/g;
|
|
29
|
+
|
|
30
|
+
/** Pattern to strip leading non-alphanumeric from collection names */
|
|
31
|
+
const LEADING_NON_ALPHANUMERIC = /^[^a-z0-9]+/;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for init command.
|
|
35
|
+
*/
|
|
36
|
+
export interface InitOptions {
|
|
37
|
+
/** Optional path to add as collection */
|
|
38
|
+
path?: string;
|
|
39
|
+
/** Collection name (defaults to directory basename if path given) */
|
|
40
|
+
name?: string;
|
|
41
|
+
/** Glob pattern for file matching */
|
|
42
|
+
pattern?: string;
|
|
43
|
+
/** Extension allowlist CSV (e.g., ".md,.pdf") */
|
|
44
|
+
include?: string;
|
|
45
|
+
/** Exclude patterns CSV */
|
|
46
|
+
exclude?: string;
|
|
47
|
+
/** Shell command to run before indexing */
|
|
48
|
+
update?: string;
|
|
49
|
+
/** Skip prompts, accept defaults */
|
|
50
|
+
yes?: boolean;
|
|
51
|
+
/** Override config path */
|
|
52
|
+
configPath?: string;
|
|
53
|
+
/** FTS tokenizer (unicode61, porter, trigram) */
|
|
54
|
+
tokenizer?: FtsTokenizer;
|
|
55
|
+
/** BCP-47 language hint for collection */
|
|
56
|
+
language?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Result of init command.
|
|
61
|
+
*/
|
|
62
|
+
export interface InitResult {
|
|
63
|
+
success: boolean;
|
|
64
|
+
alreadyInitialized?: boolean;
|
|
65
|
+
configPath: string;
|
|
66
|
+
dataDir: string;
|
|
67
|
+
dbPath: string;
|
|
68
|
+
collectionAdded?: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle case when already initialized.
|
|
74
|
+
*/
|
|
75
|
+
async function handleAlreadyInitialized(
|
|
76
|
+
options: InitOptions,
|
|
77
|
+
paths: ReturnType<typeof getConfigPaths>
|
|
78
|
+
): Promise<InitResult> {
|
|
79
|
+
// Ensure directories exist (may have been deleted by reset)
|
|
80
|
+
await ensureDirectories();
|
|
81
|
+
|
|
82
|
+
const config = await loadConfigOrNull(options.configPath);
|
|
83
|
+
const dbPath = getIndexDbPath();
|
|
84
|
+
|
|
85
|
+
if (!options.path) {
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
alreadyInitialized: true,
|
|
89
|
+
configPath: paths.configFile,
|
|
90
|
+
dataDir: paths.dataDir,
|
|
91
|
+
dbPath,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!config) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
configPath: paths.configFile,
|
|
99
|
+
dataDir: paths.dataDir,
|
|
100
|
+
dbPath,
|
|
101
|
+
error: 'Config exists but could not be loaded',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const collectionResult = await addCollectionToConfig(config, options);
|
|
106
|
+
if (!collectionResult.success) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
configPath: paths.configFile,
|
|
110
|
+
dataDir: paths.dataDir,
|
|
111
|
+
dbPath,
|
|
112
|
+
error: collectionResult.error,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const saveResult = await saveConfig(config, options.configPath);
|
|
117
|
+
if (!saveResult.ok) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
configPath: paths.configFile,
|
|
121
|
+
dataDir: paths.dataDir,
|
|
122
|
+
dbPath,
|
|
123
|
+
error: saveResult.error.message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
alreadyInitialized: true,
|
|
130
|
+
configPath: paths.configFile,
|
|
131
|
+
dataDir: paths.dataDir,
|
|
132
|
+
dbPath,
|
|
133
|
+
collectionAdded: collectionResult.collectionName,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Execute gno init command.
|
|
139
|
+
*/
|
|
140
|
+
export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
141
|
+
const paths = getConfigPaths();
|
|
142
|
+
|
|
143
|
+
// Check if already initialized
|
|
144
|
+
const initialized = await isInitialized(options.configPath);
|
|
145
|
+
if (initialized) {
|
|
146
|
+
return handleAlreadyInitialized(options, paths);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Create directories
|
|
150
|
+
const dirResult = await ensureDirectories();
|
|
151
|
+
if (!dirResult.ok) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
configPath: paths.configFile,
|
|
155
|
+
dataDir: paths.dataDir,
|
|
156
|
+
dbPath: getIndexDbPath(),
|
|
157
|
+
error: dirResult.error.message,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Validate tokenizer option if provided
|
|
162
|
+
if (options.tokenizer && !FTS_TOKENIZERS.includes(options.tokenizer)) {
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
configPath: paths.configFile,
|
|
166
|
+
dataDir: paths.dataDir,
|
|
167
|
+
dbPath: getIndexDbPath(),
|
|
168
|
+
error: `Invalid tokenizer: ${options.tokenizer}. Valid: ${FTS_TOKENIZERS.join(', ')}`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Create default config
|
|
173
|
+
const config = createDefaultConfig();
|
|
174
|
+
|
|
175
|
+
// Set tokenizer if provided
|
|
176
|
+
if (options.tokenizer) {
|
|
177
|
+
config.ftsTokenizer = options.tokenizer;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add collection if path provided
|
|
181
|
+
let collectionName: string | undefined;
|
|
182
|
+
if (options.path) {
|
|
183
|
+
const collectionResult = await addCollectionToConfig(config, options);
|
|
184
|
+
if (!collectionResult.success) {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
configPath: paths.configFile,
|
|
188
|
+
dataDir: paths.dataDir,
|
|
189
|
+
dbPath: getIndexDbPath(),
|
|
190
|
+
error: collectionResult.error,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
collectionName = collectionResult.collectionName;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Save config
|
|
197
|
+
const saveResult = await saveConfig(config, options.configPath);
|
|
198
|
+
if (!saveResult.ok) {
|
|
199
|
+
return {
|
|
200
|
+
success: false,
|
|
201
|
+
configPath: paths.configFile,
|
|
202
|
+
dataDir: paths.dataDir,
|
|
203
|
+
dbPath: getIndexDbPath(),
|
|
204
|
+
error: saveResult.error.message,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Create DB placeholder file only if it doesn't exist (don't truncate existing DB)
|
|
209
|
+
const dbPath = getIndexDbPath();
|
|
210
|
+
const dbFile = Bun.file(dbPath);
|
|
211
|
+
const dbExists = await dbFile.exists();
|
|
212
|
+
if (!dbExists) {
|
|
213
|
+
try {
|
|
214
|
+
await Bun.write(dbPath, '');
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
configPath: paths.configFile,
|
|
219
|
+
dataDir: paths.dataDir,
|
|
220
|
+
dbPath,
|
|
221
|
+
error: `Failed to create database file: ${error instanceof Error ? error.message : String(error)}`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
configPath: paths.configFile,
|
|
229
|
+
dataDir: paths.dataDir,
|
|
230
|
+
dbPath,
|
|
231
|
+
collectionAdded: collectionName,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Helper to add collection to config.
|
|
237
|
+
*/
|
|
238
|
+
async function addCollectionToConfig(
|
|
239
|
+
config: ReturnType<typeof createDefaultConfig>,
|
|
240
|
+
options: InitOptions
|
|
241
|
+
): Promise<
|
|
242
|
+
{ success: true; collectionName: string } | { success: false; error: string }
|
|
243
|
+
> {
|
|
244
|
+
if (!options.path) {
|
|
245
|
+
return { success: false, error: 'Path is required' };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Convert to absolute path
|
|
249
|
+
const absolutePath = toAbsolutePath(options.path);
|
|
250
|
+
|
|
251
|
+
// Check if path exists (as directory or file)
|
|
252
|
+
const exists = await pathExists(absolutePath);
|
|
253
|
+
if (!exists) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: `Path does not exist: ${absolutePath}`,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Determine collection name
|
|
261
|
+
let collectionName =
|
|
262
|
+
options.name ??
|
|
263
|
+
basename(absolutePath).toLowerCase().replace(INVALID_NAME_CHARS, '-');
|
|
264
|
+
|
|
265
|
+
// Ensure name starts with alphanumeric (strip leading non-alphanumeric)
|
|
266
|
+
collectionName = collectionName.replace(LEADING_NON_ALPHANUMERIC, '');
|
|
267
|
+
|
|
268
|
+
// Validate derived name
|
|
269
|
+
if (!collectionName || collectionName.length > 64) {
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
error:
|
|
273
|
+
'Cannot derive valid collection name from path. Please specify --name explicitly.',
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check for duplicate name
|
|
278
|
+
if (config.collections.some((c) => c.name === collectionName)) {
|
|
279
|
+
return {
|
|
280
|
+
success: false,
|
|
281
|
+
error: `Collection "${collectionName}" already exists`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Parse include/exclude CSV if provided (filter empty entries)
|
|
286
|
+
const include = options.include
|
|
287
|
+
? options.include
|
|
288
|
+
.split(',')
|
|
289
|
+
.map((ext) => ext.trim())
|
|
290
|
+
.filter(Boolean)
|
|
291
|
+
: [];
|
|
292
|
+
|
|
293
|
+
const exclude = options.exclude
|
|
294
|
+
? options.exclude
|
|
295
|
+
.split(',')
|
|
296
|
+
.map((pattern) => pattern.trim())
|
|
297
|
+
.filter(Boolean)
|
|
298
|
+
: [...DEFAULT_EXCLUDES];
|
|
299
|
+
|
|
300
|
+
// Create collection
|
|
301
|
+
const collection: Collection = {
|
|
302
|
+
name: collectionName,
|
|
303
|
+
path: absolutePath,
|
|
304
|
+
pattern: options.pattern ?? DEFAULT_PATTERN,
|
|
305
|
+
include,
|
|
306
|
+
exclude,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (options.update) {
|
|
310
|
+
collection.updateCmd = options.update;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Validate and set language hint if provided
|
|
314
|
+
if (options.language) {
|
|
315
|
+
if (!isValidLanguageHint(options.language)) {
|
|
316
|
+
return {
|
|
317
|
+
success: false,
|
|
318
|
+
error: `Invalid language hint: ${options.language}. Use BCP-47 format (e.g., en, de, zh-CN)`,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
collection.languageHint = options.language;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Add to config
|
|
325
|
+
config.collections.push(collection);
|
|
326
|
+
|
|
327
|
+
return { success: true, collectionName };
|
|
328
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno ls command implementation.
|
|
3
|
+
* List indexed documents.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/ls
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DocumentRow, StorePort, StoreResult } from '../../store/types';
|
|
9
|
+
import { initStore } from './shared';
|
|
10
|
+
|
|
11
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
// Types
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface LsCommandOptions {
|
|
16
|
+
/** Override config path */
|
|
17
|
+
configPath?: string;
|
|
18
|
+
/** Max results (default 20) */
|
|
19
|
+
limit?: number;
|
|
20
|
+
/** Skip first N results */
|
|
21
|
+
offset?: number;
|
|
22
|
+
/** JSON output */
|
|
23
|
+
json?: boolean;
|
|
24
|
+
/** File protocol output */
|
|
25
|
+
files?: boolean;
|
|
26
|
+
/** Markdown output */
|
|
27
|
+
md?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type LsResult =
|
|
31
|
+
| { success: true; data: LsResponse }
|
|
32
|
+
| { success: false; error: string; isValidation?: boolean };
|
|
33
|
+
|
|
34
|
+
export interface LsDocument {
|
|
35
|
+
docid: string;
|
|
36
|
+
uri: string;
|
|
37
|
+
title?: string;
|
|
38
|
+
source: { relPath: string; mime: string; ext: string };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface LsResponse {
|
|
42
|
+
documents: LsDocument[];
|
|
43
|
+
meta: {
|
|
44
|
+
total: number;
|
|
45
|
+
returned: number;
|
|
46
|
+
offset: number;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
// Scope validation regex
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
const URI_PREFIX_PATTERN = /^gno:\/\/[^/]+\//;
|
|
55
|
+
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
// Document Fetching Helper
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
async function fetchDocuments(
|
|
61
|
+
store: StorePort,
|
|
62
|
+
scope: string | undefined
|
|
63
|
+
): Promise<StoreResult<DocumentRow[]>> {
|
|
64
|
+
if (!scope) {
|
|
65
|
+
return store.listDocuments();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (scope.startsWith('gno://')) {
|
|
69
|
+
const allDocs = await store.listDocuments();
|
|
70
|
+
if (!allDocs.ok) {
|
|
71
|
+
return allDocs;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
value: allDocs.value.filter((d) => d.uri.startsWith(scope)),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return store.listDocuments(scope);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
// Command Implementation
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Execute gno ls command.
|
|
88
|
+
*/
|
|
89
|
+
export async function ls(
|
|
90
|
+
scope: string | undefined,
|
|
91
|
+
options: LsCommandOptions = {}
|
|
92
|
+
): Promise<LsResult> {
|
|
93
|
+
// Validate scope if it's a gno:// URI
|
|
94
|
+
if (scope?.startsWith('gno://')) {
|
|
95
|
+
if (scope === 'gno://') {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: 'Invalid scope: missing collection',
|
|
99
|
+
isValidation: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (!URI_PREFIX_PATTERN.test(scope)) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: 'Invalid scope: missing trailing path (use gno://collection/)',
|
|
106
|
+
isValidation: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const initResult = await initStore({ configPath: options.configPath });
|
|
112
|
+
if (!initResult.ok) {
|
|
113
|
+
return { success: false, error: initResult.error };
|
|
114
|
+
}
|
|
115
|
+
const { store } = initResult;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const docs = await fetchDocuments(store, scope);
|
|
119
|
+
if (!docs.ok) {
|
|
120
|
+
return { success: false, error: docs.error.message };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Filter active only, sort by URI
|
|
124
|
+
const allActive = docs.value
|
|
125
|
+
.filter((d) => d.active)
|
|
126
|
+
.map((d) => ({
|
|
127
|
+
docid: d.docid,
|
|
128
|
+
uri: d.uri,
|
|
129
|
+
title: d.title ?? undefined,
|
|
130
|
+
source: {
|
|
131
|
+
relPath: d.relPath,
|
|
132
|
+
mime: d.sourceMime,
|
|
133
|
+
ext: d.sourceExt,
|
|
134
|
+
},
|
|
135
|
+
}))
|
|
136
|
+
.sort((a, b) => a.uri.localeCompare(b.uri));
|
|
137
|
+
|
|
138
|
+
// Apply offset and limit
|
|
139
|
+
const offset = options.offset ?? 0;
|
|
140
|
+
const limit = options.limit ?? 20;
|
|
141
|
+
const paged = allActive.slice(offset, offset + limit);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
success: true,
|
|
145
|
+
data: {
|
|
146
|
+
documents: paged,
|
|
147
|
+
meta: {
|
|
148
|
+
total: allActive.length,
|
|
149
|
+
returned: paged.length,
|
|
150
|
+
offset,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
} finally {
|
|
155
|
+
await store.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Formatter
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format ls result for output.
|
|
165
|
+
*/
|
|
166
|
+
export function formatLs(result: LsResult, options: LsCommandOptions): string {
|
|
167
|
+
if (!result.success) {
|
|
168
|
+
if (options.json) {
|
|
169
|
+
return JSON.stringify({
|
|
170
|
+
error: { code: 'LS_FAILED', message: result.error },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return `Error: ${result.error}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const { data } = result;
|
|
177
|
+
const docs = data.documents;
|
|
178
|
+
|
|
179
|
+
if (options.json) {
|
|
180
|
+
return JSON.stringify(data, null, 2);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (options.files) {
|
|
184
|
+
return docs.map((d) => `${d.docid},${d.uri}`).join('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (options.md) {
|
|
188
|
+
if (docs.length === 0) {
|
|
189
|
+
return '# Documents\n\nNo documents found.';
|
|
190
|
+
}
|
|
191
|
+
const lines: string[] = [];
|
|
192
|
+
lines.push('# Documents');
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push(
|
|
195
|
+
`*Showing ${data.meta.returned} of ${data.meta.total} documents*`
|
|
196
|
+
);
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push('| DocID | URI | Title |');
|
|
199
|
+
lines.push('|-------|-----|-------|');
|
|
200
|
+
for (const d of docs) {
|
|
201
|
+
lines.push(`| \`${d.docid}\` | \`${d.uri}\` | ${d.title || '-'} |`);
|
|
202
|
+
}
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Terminal format
|
|
207
|
+
if (docs.length === 0) {
|
|
208
|
+
return 'No documents found.';
|
|
209
|
+
}
|
|
210
|
+
const lines = docs.map((d) => `${d.docid}\t${d.uri}`);
|
|
211
|
+
if (data.meta.returned < data.meta.total) {
|
|
212
|
+
lines.push(
|
|
213
|
+
`\n(${data.meta.returned} of ${data.meta.total} documents shown)`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|