@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,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno collection add - Add a new collection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type Collection,
|
|
7
|
+
CollectionSchema,
|
|
8
|
+
DEFAULT_EXCLUDES,
|
|
9
|
+
DEFAULT_PATTERN,
|
|
10
|
+
loadConfig,
|
|
11
|
+
pathExists,
|
|
12
|
+
saveConfig,
|
|
13
|
+
toAbsolutePath,
|
|
14
|
+
} from '../../../config';
|
|
15
|
+
import { CliError } from '../../errors';
|
|
16
|
+
|
|
17
|
+
interface AddOptions {
|
|
18
|
+
name?: string;
|
|
19
|
+
pattern?: string;
|
|
20
|
+
include?: string;
|
|
21
|
+
exclude?: string;
|
|
22
|
+
update?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function collectionAdd(
|
|
26
|
+
path: string,
|
|
27
|
+
options: AddOptions
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
// Validate required name
|
|
30
|
+
if (!options.name) {
|
|
31
|
+
throw new CliError('VALIDATION', '--name is required');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const collectionName = options.name.toLowerCase();
|
|
35
|
+
|
|
36
|
+
// Expand and validate path
|
|
37
|
+
const absolutePath = toAbsolutePath(path);
|
|
38
|
+
|
|
39
|
+
// Check if path exists
|
|
40
|
+
const exists = await pathExists(absolutePath);
|
|
41
|
+
if (!exists) {
|
|
42
|
+
throw new CliError('VALIDATION', `Path does not exist: ${absolutePath}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load config
|
|
46
|
+
const result = await loadConfig();
|
|
47
|
+
if (!result.ok) {
|
|
48
|
+
throw new CliError(
|
|
49
|
+
'RUNTIME',
|
|
50
|
+
`Failed to load config: ${result.error.message}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const config = result.value;
|
|
55
|
+
|
|
56
|
+
// Check for duplicate name
|
|
57
|
+
const existing = config.collections.find((c) => c.name === collectionName);
|
|
58
|
+
if (existing) {
|
|
59
|
+
throw new CliError(
|
|
60
|
+
'VALIDATION',
|
|
61
|
+
`Collection "${collectionName}" already exists`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Parse options - filter empty, dedupe
|
|
66
|
+
const includeList = options.include
|
|
67
|
+
? [
|
|
68
|
+
...new Set(
|
|
69
|
+
options.include
|
|
70
|
+
.split(',')
|
|
71
|
+
.map((s) => s.trim())
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
),
|
|
74
|
+
]
|
|
75
|
+
: [];
|
|
76
|
+
const excludeList = options.exclude
|
|
77
|
+
? [
|
|
78
|
+
...new Set(
|
|
79
|
+
options.exclude
|
|
80
|
+
.split(',')
|
|
81
|
+
.map((s) => s.trim())
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
),
|
|
84
|
+
]
|
|
85
|
+
: [...DEFAULT_EXCLUDES];
|
|
86
|
+
|
|
87
|
+
// Build collection
|
|
88
|
+
const collection: Collection = {
|
|
89
|
+
name: collectionName,
|
|
90
|
+
path: absolutePath,
|
|
91
|
+
pattern: options.pattern ?? DEFAULT_PATTERN,
|
|
92
|
+
include: includeList,
|
|
93
|
+
exclude: excludeList,
|
|
94
|
+
updateCmd: options.update,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Validate collection
|
|
98
|
+
const validation = CollectionSchema.safeParse(collection);
|
|
99
|
+
if (!validation.success) {
|
|
100
|
+
throw new CliError(
|
|
101
|
+
'VALIDATION',
|
|
102
|
+
`Invalid collection: ${validation.error.issues[0]?.message ?? 'unknown error'}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add to config
|
|
107
|
+
config.collections.push(validation.data);
|
|
108
|
+
|
|
109
|
+
// Save config
|
|
110
|
+
const saveResult = await saveConfig(config);
|
|
111
|
+
if (!saveResult.ok) {
|
|
112
|
+
throw new CliError(
|
|
113
|
+
'RUNTIME',
|
|
114
|
+
`Failed to save config: ${saveResult.error.message}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
process.stdout.write(`Collection "${collectionName}" added successfully\n`);
|
|
119
|
+
process.stdout.write(`Path: ${absolutePath}\n`);
|
|
120
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection CLI commands
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/collection
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { collectionAdd } from './add';
|
|
8
|
+
export { collectionList } from './list';
|
|
9
|
+
export { collectionRemove } from './remove';
|
|
10
|
+
export { collectionRename } from './rename';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno collection list - List all collections
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Collection } from '../../../config';
|
|
6
|
+
import { loadConfig } from '../../../config';
|
|
7
|
+
import { bold, cyan, dim } from '../../colors';
|
|
8
|
+
import { CliError } from '../../errors';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Strip ANSI escape sequences and control characters from string.
|
|
12
|
+
* Prevents terminal injection from user-controlled config values.
|
|
13
|
+
*/
|
|
14
|
+
function sanitize(input: string): string {
|
|
15
|
+
// Strip ANSI escape sequences
|
|
16
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentional for sanitization
|
|
17
|
+
return input.replace(/\x1b\[[0-9;]*m/g, '').replace(/[\x00-\x1f\x7f]/g, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ListOptions {
|
|
21
|
+
json?: boolean;
|
|
22
|
+
md?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatMarkdown(collections: Collection[]): string {
|
|
26
|
+
const lines: string[] = ['# Collections', ''];
|
|
27
|
+
if (collections.length === 0) {
|
|
28
|
+
lines.push('No collections configured.');
|
|
29
|
+
return lines.join('\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const coll of collections) {
|
|
33
|
+
lines.push(`## ${coll.name}`, '');
|
|
34
|
+
lines.push(`- **Path:** ${coll.path}`);
|
|
35
|
+
lines.push(`- **Pattern:** ${coll.pattern}`);
|
|
36
|
+
if (coll.include.length > 0) {
|
|
37
|
+
lines.push(`- **Include:** ${coll.include.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
if (coll.exclude.length > 0) {
|
|
40
|
+
lines.push(`- **Exclude:** ${coll.exclude.join(', ')}`);
|
|
41
|
+
}
|
|
42
|
+
if (coll.updateCmd) {
|
|
43
|
+
lines.push(`- **Update Command:** \`${coll.updateCmd}\``);
|
|
44
|
+
}
|
|
45
|
+
lines.push('');
|
|
46
|
+
}
|
|
47
|
+
return lines.join('\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatTerminal(collections: Collection[]): string {
|
|
51
|
+
if (collections.length === 0) {
|
|
52
|
+
return dim('No collections configured.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const lines: string[] = [
|
|
56
|
+
`${bold('Collections')} ${dim(`(${collections.length})`)}`,
|
|
57
|
+
'',
|
|
58
|
+
];
|
|
59
|
+
for (const coll of collections) {
|
|
60
|
+
// Sanitize all user-controlled values to prevent terminal injection
|
|
61
|
+
const name = sanitize(coll.name);
|
|
62
|
+
const path = sanitize(coll.path);
|
|
63
|
+
const pattern = sanitize(coll.pattern);
|
|
64
|
+
const include = coll.include.map(sanitize);
|
|
65
|
+
const exclude = coll.exclude.map(sanitize);
|
|
66
|
+
const updateCmd = coll.updateCmd ? sanitize(coll.updateCmd) : undefined;
|
|
67
|
+
|
|
68
|
+
lines.push(` ${cyan(bold(name))}`);
|
|
69
|
+
lines.push(` ${dim('Path:')} ${path}`);
|
|
70
|
+
lines.push(` ${dim('Pattern:')} ${pattern}`);
|
|
71
|
+
if (include.length > 0) {
|
|
72
|
+
lines.push(` ${dim('Include:')} ${include.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
if (exclude.length > 0) {
|
|
75
|
+
lines.push(` ${dim('Exclude:')} ${dim(exclude.join(', '))}`);
|
|
76
|
+
}
|
|
77
|
+
if (updateCmd) {
|
|
78
|
+
lines.push(` ${dim('Update:')} ${updateCmd}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push('');
|
|
81
|
+
}
|
|
82
|
+
return lines.join('\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function collectionList(options: ListOptions): Promise<void> {
|
|
86
|
+
// Load config
|
|
87
|
+
const result = await loadConfig();
|
|
88
|
+
if (!result.ok) {
|
|
89
|
+
throw new CliError(
|
|
90
|
+
'RUNTIME',
|
|
91
|
+
`Failed to load config: ${result.error.message}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const config = result.value;
|
|
96
|
+
|
|
97
|
+
// Format and output
|
|
98
|
+
let output: string;
|
|
99
|
+
if (options.json) {
|
|
100
|
+
output = JSON.stringify(config.collections, null, 2);
|
|
101
|
+
} else if (options.md) {
|
|
102
|
+
output = formatMarkdown(config.collections);
|
|
103
|
+
} else {
|
|
104
|
+
output = formatTerminal(config.collections);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
process.stdout.write(`${output}\n`);
|
|
108
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno collection remove - Remove a collection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getCollectionFromScope,
|
|
7
|
+
loadConfig,
|
|
8
|
+
saveConfig,
|
|
9
|
+
} from '../../../config';
|
|
10
|
+
import { CliError } from '../../errors';
|
|
11
|
+
|
|
12
|
+
export async function collectionRemove(name: string): Promise<void> {
|
|
13
|
+
const collectionName = name.toLowerCase();
|
|
14
|
+
|
|
15
|
+
// Load config
|
|
16
|
+
const result = await loadConfig();
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
throw new CliError(
|
|
19
|
+
'RUNTIME',
|
|
20
|
+
`Failed to load config: ${result.error.message}`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const config = result.value;
|
|
25
|
+
|
|
26
|
+
// Find collection
|
|
27
|
+
const collectionIndex = config.collections.findIndex(
|
|
28
|
+
(c) => c.name === collectionName
|
|
29
|
+
);
|
|
30
|
+
if (collectionIndex === -1) {
|
|
31
|
+
throw new CliError(
|
|
32
|
+
'VALIDATION',
|
|
33
|
+
`Collection "${collectionName}" not found`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if any contexts reference this collection
|
|
38
|
+
const referencingContexts = config.contexts.filter((ctx) => {
|
|
39
|
+
const collFromScope = getCollectionFromScope(ctx.scopeKey);
|
|
40
|
+
return collFromScope === collectionName;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (referencingContexts.length > 0) {
|
|
44
|
+
const scopes = referencingContexts.map((ctx) => ctx.scopeKey).join(', ');
|
|
45
|
+
throw new CliError(
|
|
46
|
+
'VALIDATION',
|
|
47
|
+
`Collection "${collectionName}" is referenced by contexts: ${scopes}. Remove the contexts first or rename the collection.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Remove collection
|
|
52
|
+
config.collections.splice(collectionIndex, 1);
|
|
53
|
+
|
|
54
|
+
// Save config
|
|
55
|
+
const saveResult = await saveConfig(config);
|
|
56
|
+
if (!saveResult.ok) {
|
|
57
|
+
throw new CliError(
|
|
58
|
+
'RUNTIME',
|
|
59
|
+
`Failed to save config: ${saveResult.error.message}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.stdout.write(`Collection "${collectionName}" removed successfully\n`);
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gno collection rename - Rename a collection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CollectionSchema,
|
|
7
|
+
getCollectionFromScope,
|
|
8
|
+
loadConfig,
|
|
9
|
+
saveConfig,
|
|
10
|
+
} from '../../../config';
|
|
11
|
+
import { CliError } from '../../errors';
|
|
12
|
+
|
|
13
|
+
export async function collectionRename(
|
|
14
|
+
oldName: string,
|
|
15
|
+
newName: string
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
const oldCollectionName = oldName.toLowerCase();
|
|
18
|
+
const newCollectionName = newName.toLowerCase();
|
|
19
|
+
|
|
20
|
+
// Load config
|
|
21
|
+
const result = await loadConfig();
|
|
22
|
+
if (!result.ok) {
|
|
23
|
+
throw new CliError(
|
|
24
|
+
'RUNTIME',
|
|
25
|
+
`Failed to load config: ${result.error.message}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const config = result.value;
|
|
30
|
+
|
|
31
|
+
// Find old collection
|
|
32
|
+
const collection = config.collections.find(
|
|
33
|
+
(c) => c.name === oldCollectionName
|
|
34
|
+
);
|
|
35
|
+
if (!collection) {
|
|
36
|
+
throw new CliError(
|
|
37
|
+
'VALIDATION',
|
|
38
|
+
`Collection "${oldCollectionName}" not found`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check if new name already exists
|
|
43
|
+
const existingNew = config.collections.find(
|
|
44
|
+
(c) => c.name === newCollectionName
|
|
45
|
+
);
|
|
46
|
+
if (existingNew) {
|
|
47
|
+
throw new CliError(
|
|
48
|
+
'VALIDATION',
|
|
49
|
+
`Collection "${newCollectionName}" already exists`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate new name
|
|
54
|
+
const testCollection = { ...collection, name: newCollectionName };
|
|
55
|
+
const validation = CollectionSchema.safeParse(testCollection);
|
|
56
|
+
if (!validation.success) {
|
|
57
|
+
throw new CliError(
|
|
58
|
+
'VALIDATION',
|
|
59
|
+
`Invalid collection name: ${validation.error.issues[0]?.message ?? 'unknown error'}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Rename collection
|
|
64
|
+
collection.name = newCollectionName;
|
|
65
|
+
|
|
66
|
+
// Update contexts that reference this collection
|
|
67
|
+
for (const context of config.contexts) {
|
|
68
|
+
const collFromScope = getCollectionFromScope(context.scopeKey);
|
|
69
|
+
if (collFromScope === oldCollectionName) {
|
|
70
|
+
// Update scope key
|
|
71
|
+
if (context.scopeType === 'collection') {
|
|
72
|
+
context.scopeKey = `${newCollectionName}:`;
|
|
73
|
+
} else if (context.scopeType === 'prefix') {
|
|
74
|
+
// Replace collection name in URI
|
|
75
|
+
context.scopeKey = context.scopeKey.replace(
|
|
76
|
+
`gno://${oldCollectionName}/`,
|
|
77
|
+
`gno://${newCollectionName}/`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Save config
|
|
84
|
+
const saveResult = await saveConfig(config);
|
|
85
|
+
if (!saveResult.ok) {
|
|
86
|
+
throw new CliError(
|
|
87
|
+
'RUNTIME',
|
|
88
|
+
`Failed to save config: ${saveResult.error.message}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
process.stdout.write(
|
|
93
|
+
`Collection "${oldCollectionName}" renamed to "${newCollectionName}"\n`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: gno context add
|
|
3
|
+
*
|
|
4
|
+
* Add context metadata for a scope.
|
|
5
|
+
*
|
|
6
|
+
* @module src/cli/commands/context/add
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { loadConfig, parseScope, saveConfig } from '../../../config';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Exit codes
|
|
13
|
+
*/
|
|
14
|
+
const EXIT_SUCCESS = 0;
|
|
15
|
+
const EXIT_VALIDATION = 1;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Add context metadata for a scope.
|
|
19
|
+
*
|
|
20
|
+
* @param scope - Scope string (/, collection:, or gno://collection/path)
|
|
21
|
+
* @param text - Context description text
|
|
22
|
+
* @returns Exit code
|
|
23
|
+
*/
|
|
24
|
+
export async function contextAdd(scope: string, text: string): Promise<number> {
|
|
25
|
+
// Parse scope
|
|
26
|
+
const parsed = parseScope(scope);
|
|
27
|
+
if (!parsed) {
|
|
28
|
+
console.error(`Error: Invalid scope format: ${scope}`);
|
|
29
|
+
console.error(
|
|
30
|
+
'Valid formats: "/" (global), "name:" (collection), or "gno://collection/path" (prefix)'
|
|
31
|
+
);
|
|
32
|
+
return EXIT_VALIDATION;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Load config
|
|
36
|
+
const configResult = await loadConfig();
|
|
37
|
+
if (!configResult.ok) {
|
|
38
|
+
console.error(`Error: ${configResult.error.message}`);
|
|
39
|
+
return EXIT_VALIDATION;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const config = configResult.value;
|
|
43
|
+
|
|
44
|
+
// Check for duplicate scope
|
|
45
|
+
const existing = config.contexts.find((ctx) => ctx.scopeKey === parsed.key);
|
|
46
|
+
if (existing) {
|
|
47
|
+
console.error(`Error: Context for scope "${scope}" already exists`);
|
|
48
|
+
return EXIT_VALIDATION;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Add context
|
|
52
|
+
config.contexts.push({
|
|
53
|
+
scopeType: parsed.type,
|
|
54
|
+
scopeKey: parsed.key,
|
|
55
|
+
text,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Save config
|
|
59
|
+
const saveResult = await saveConfig(config);
|
|
60
|
+
if (!saveResult.ok) {
|
|
61
|
+
console.error(`Error: ${saveResult.error.message}`);
|
|
62
|
+
return EXIT_VALIDATION;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`Added context for scope: ${scope}`);
|
|
66
|
+
return EXIT_SUCCESS;
|
|
67
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: gno context check
|
|
3
|
+
*
|
|
4
|
+
* Validate context configuration.
|
|
5
|
+
*
|
|
6
|
+
* @module src/cli/commands/context/check
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getCollectionFromScope, 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
|
+
* Check result
|
|
23
|
+
*/
|
|
24
|
+
export interface CheckResult {
|
|
25
|
+
valid: boolean;
|
|
26
|
+
warnings: string[];
|
|
27
|
+
errors: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate context configuration.
|
|
32
|
+
*
|
|
33
|
+
* Checks:
|
|
34
|
+
* - Global scope exists
|
|
35
|
+
* - Collection scopes reference existing collections
|
|
36
|
+
* - Prefix scopes reference existing collections
|
|
37
|
+
*
|
|
38
|
+
* @param format - Output format (terminal, json, md)
|
|
39
|
+
* @returns Exit code
|
|
40
|
+
*/
|
|
41
|
+
export async function contextCheck(
|
|
42
|
+
format: OutputFormat = 'terminal'
|
|
43
|
+
): Promise<number> {
|
|
44
|
+
// Load config
|
|
45
|
+
const configResult = await loadConfig();
|
|
46
|
+
if (!configResult.ok) {
|
|
47
|
+
console.error(`Error: ${configResult.error.message}`);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { contexts, collections } = configResult.value;
|
|
52
|
+
|
|
53
|
+
const warnings: string[] = [];
|
|
54
|
+
const errors: string[] = [];
|
|
55
|
+
|
|
56
|
+
// Check global scope exists
|
|
57
|
+
const hasGlobalScope = contexts.some((ctx) => ctx.scopeType === 'global');
|
|
58
|
+
if (!hasGlobalScope) {
|
|
59
|
+
warnings.push('No global scope (/) configured');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check collection and prefix scopes reference existing collections
|
|
63
|
+
const collectionNames = new Set(collections.map((c) => c.name));
|
|
64
|
+
|
|
65
|
+
for (const ctx of contexts) {
|
|
66
|
+
const collectionName = getCollectionFromScope(ctx.scopeKey);
|
|
67
|
+
if (collectionName && !collectionNames.has(collectionName)) {
|
|
68
|
+
errors.push(
|
|
69
|
+
`Scope "${ctx.scopeKey}" references non-existent collection: ${collectionName}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const result: CheckResult = {
|
|
75
|
+
valid: errors.length === 0,
|
|
76
|
+
warnings,
|
|
77
|
+
errors,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Format and output
|
|
81
|
+
formatOutput(format, result);
|
|
82
|
+
return EXIT_SUCCESS;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format and output check results
|
|
87
|
+
*/
|
|
88
|
+
function formatOutput(format: OutputFormat, result: CheckResult): void {
|
|
89
|
+
if (format === 'json') {
|
|
90
|
+
console.log(JSON.stringify(result, null, 2));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (format === 'md') {
|
|
95
|
+
formatMarkdown(result);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
formatTerminal(result);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Format check results as markdown
|
|
104
|
+
*/
|
|
105
|
+
function formatMarkdown(result: CheckResult): void {
|
|
106
|
+
console.log('# Context Check\n');
|
|
107
|
+
console.log(`**Valid:** ${result.valid}\n`);
|
|
108
|
+
|
|
109
|
+
if (result.errors.length > 0) {
|
|
110
|
+
console.log('## Errors\n');
|
|
111
|
+
for (const error of result.errors) {
|
|
112
|
+
console.log(`- ${error}`);
|
|
113
|
+
}
|
|
114
|
+
console.log('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (result.warnings.length > 0) {
|
|
118
|
+
console.log('## Warnings\n');
|
|
119
|
+
for (const warning of result.warnings) {
|
|
120
|
+
console.log(`- ${warning}`);
|
|
121
|
+
}
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (result.valid && result.warnings.length === 0) {
|
|
126
|
+
console.log('No issues found.');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Format check results for terminal
|
|
132
|
+
*/
|
|
133
|
+
function formatTerminal(result: CheckResult): void {
|
|
134
|
+
if (result.valid) {
|
|
135
|
+
console.log('✓ Context configuration is valid');
|
|
136
|
+
} else {
|
|
137
|
+
console.log('✗ Context configuration has errors');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (result.errors.length > 0) {
|
|
141
|
+
console.log('\nErrors:');
|
|
142
|
+
for (const error of result.errors) {
|
|
143
|
+
console.log(` - ${error}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (result.warnings.length > 0) {
|
|
148
|
+
console.log('\nWarnings:');
|
|
149
|
+
for (const warning of result.warnings) {
|
|
150
|
+
console.log(` - ${warning}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { contextAdd } from './add';
|
|
8
|
+
export { type CheckResult, contextCheck, type OutputFormat } from './check';
|
|
9
|
+
export { contextList } from './list';
|
|
10
|
+
export { contextRm } from './rm';
|