@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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP config file operations.
|
|
3
|
+
* Shared between install/uninstall to avoid drift.
|
|
4
|
+
*
|
|
5
|
+
* @module src/cli/commands/mcp/config
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { copyFile, mkdir, rename, stat, unlink } from 'node:fs/promises';
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
import { CliError } from '../../errors.js';
|
|
11
|
+
import type { McpConfigFormat } from './paths.js';
|
|
12
|
+
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
// Types
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/** Standard mcpServers entry (Claude Desktop, Cursor, Windsurf, LM Studio) */
|
|
18
|
+
export interface StandardMcpEntry {
|
|
19
|
+
command: string;
|
|
20
|
+
args: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** OpenCode mcp entry (command is array, has type and enabled) */
|
|
24
|
+
export interface OpenCodeMcpEntry {
|
|
25
|
+
type: 'local';
|
|
26
|
+
command: string[];
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Config with standard mcpServers key */
|
|
31
|
+
export interface McpConfig {
|
|
32
|
+
mcpServers?: Record<string, StandardMcpEntry>;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Zed config with context_servers key */
|
|
37
|
+
export interface ZedConfig {
|
|
38
|
+
context_servers?: Record<string, StandardMcpEntry>;
|
|
39
|
+
[key: string]: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** OpenCode config with mcp key */
|
|
43
|
+
export interface OpenCodeConfig {
|
|
44
|
+
mcp?: Record<string, OpenCodeMcpEntry>;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Amp config with amp.mcpServers key */
|
|
49
|
+
export interface AmpConfig {
|
|
50
|
+
'amp.mcpServers'?: Record<string, StandardMcpEntry>;
|
|
51
|
+
[key: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Union of all config types */
|
|
55
|
+
export type AnyMcpConfig = McpConfig | ZedConfig | OpenCodeConfig | AmpConfig;
|
|
56
|
+
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
// Read/Write Operations
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if path exists and is a file (not directory).
|
|
63
|
+
* Returns null if doesn't exist, throws if exists but not a file.
|
|
64
|
+
*/
|
|
65
|
+
async function checkConfigPath(
|
|
66
|
+
configPath: string
|
|
67
|
+
): Promise<'file' | 'missing'> {
|
|
68
|
+
try {
|
|
69
|
+
const stats = await stat(configPath);
|
|
70
|
+
if (!stats.isFile()) {
|
|
71
|
+
throw new CliError(
|
|
72
|
+
'RUNTIME',
|
|
73
|
+
`Config path exists but is not a file: ${configPath}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return 'file';
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
79
|
+
return 'missing';
|
|
80
|
+
}
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read and parse MCP config file.
|
|
87
|
+
* Returns empty config if file doesn't exist.
|
|
88
|
+
* Returns null if file doesn't exist AND returnNullOnMissing is true.
|
|
89
|
+
* Throws on malformed JSON/YAML or if path is a directory.
|
|
90
|
+
*/
|
|
91
|
+
export async function readMcpConfig(
|
|
92
|
+
configPath: string,
|
|
93
|
+
options?: { returnNullOnMissing?: boolean; yaml?: boolean }
|
|
94
|
+
): Promise<McpConfig | null> {
|
|
95
|
+
const pathStatus = await checkConfigPath(configPath);
|
|
96
|
+
|
|
97
|
+
if (pathStatus === 'missing') {
|
|
98
|
+
return options?.returnNullOnMissing ? null : {};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const file = Bun.file(configPath);
|
|
102
|
+
const content = await file.text();
|
|
103
|
+
|
|
104
|
+
// Handle empty file
|
|
105
|
+
if (!content.trim()) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
if (options?.yaml) {
|
|
111
|
+
return Bun.YAML.parse(content) as McpConfig;
|
|
112
|
+
}
|
|
113
|
+
return JSON.parse(content) as McpConfig;
|
|
114
|
+
} catch {
|
|
115
|
+
const format = options?.yaml ? 'YAML' : 'JSON';
|
|
116
|
+
throw new CliError(
|
|
117
|
+
'RUNTIME',
|
|
118
|
+
`Malformed ${format} in ${configPath}. Please fix or backup and delete the file.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Write MCP config atomically via temp file + rename.
|
|
125
|
+
* Creates backup of existing file first.
|
|
126
|
+
*/
|
|
127
|
+
export async function writeMcpConfig(
|
|
128
|
+
configPath: string,
|
|
129
|
+
config: AnyMcpConfig,
|
|
130
|
+
options?: { yaml?: boolean }
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const dir = dirname(configPath);
|
|
133
|
+
|
|
134
|
+
// Ensure directory exists
|
|
135
|
+
await mkdir(dir, { recursive: true });
|
|
136
|
+
|
|
137
|
+
// Create backup of existing file
|
|
138
|
+
try {
|
|
139
|
+
const stats = await stat(configPath);
|
|
140
|
+
if (stats.isFile()) {
|
|
141
|
+
await copyFile(configPath, `${configPath}.bak`);
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// File doesn't exist, no backup needed
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Serialize content
|
|
148
|
+
const content = options?.yaml
|
|
149
|
+
? Bun.YAML.stringify(config)
|
|
150
|
+
: JSON.stringify(config, null, 2);
|
|
151
|
+
|
|
152
|
+
// Write to temp file first
|
|
153
|
+
const tmpPath = `${configPath}.tmp.${Date.now()}.${process.pid}`;
|
|
154
|
+
try {
|
|
155
|
+
await Bun.write(tmpPath, content);
|
|
156
|
+
// Atomic rename
|
|
157
|
+
await rename(tmpPath, configPath);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// Cleanup temp file on error
|
|
160
|
+
try {
|
|
161
|
+
await unlink(tmpPath);
|
|
162
|
+
} catch {
|
|
163
|
+
// Ignore cleanup errors
|
|
164
|
+
}
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
170
|
+
// Format-aware Operations
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get the servers record key for a config format.
|
|
175
|
+
*/
|
|
176
|
+
export function getServersKey(
|
|
177
|
+
format: McpConfigFormat
|
|
178
|
+
): 'mcpServers' | 'context_servers' | 'mcp' | 'amp.mcpServers' {
|
|
179
|
+
switch (format) {
|
|
180
|
+
case 'standard':
|
|
181
|
+
case 'yaml_standard':
|
|
182
|
+
return 'mcpServers';
|
|
183
|
+
case 'context_servers':
|
|
184
|
+
return 'context_servers';
|
|
185
|
+
case 'mcp':
|
|
186
|
+
return 'mcp';
|
|
187
|
+
case 'amp_mcp':
|
|
188
|
+
return 'amp.mcpServers';
|
|
189
|
+
default: {
|
|
190
|
+
const _exhaustive: never = format;
|
|
191
|
+
throw new Error(`Unknown format: ${_exhaustive}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if format uses YAML.
|
|
198
|
+
*/
|
|
199
|
+
export function isYamlFormat(format: McpConfigFormat): boolean {
|
|
200
|
+
return format === 'yaml_standard';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if a server entry exists in config for given format.
|
|
205
|
+
*/
|
|
206
|
+
export function hasServerEntry(
|
|
207
|
+
config: AnyMcpConfig,
|
|
208
|
+
serverName: string,
|
|
209
|
+
format: McpConfigFormat
|
|
210
|
+
): boolean {
|
|
211
|
+
const key = getServersKey(format);
|
|
212
|
+
const servers = config[key] as Record<string, unknown> | undefined;
|
|
213
|
+
return !!servers?.[serverName];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get server entry from config.
|
|
218
|
+
*/
|
|
219
|
+
export function getServerEntry(
|
|
220
|
+
config: AnyMcpConfig,
|
|
221
|
+
serverName: string,
|
|
222
|
+
format: McpConfigFormat
|
|
223
|
+
): StandardMcpEntry | OpenCodeMcpEntry | undefined {
|
|
224
|
+
const key = getServersKey(format);
|
|
225
|
+
const servers = config[key] as
|
|
226
|
+
| Record<string, StandardMcpEntry | OpenCodeMcpEntry>
|
|
227
|
+
| undefined;
|
|
228
|
+
return servers?.[serverName];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build entry for a specific format from standard command/args.
|
|
233
|
+
*/
|
|
234
|
+
export function buildEntry(
|
|
235
|
+
command: string,
|
|
236
|
+
args: string[],
|
|
237
|
+
format: McpConfigFormat
|
|
238
|
+
): StandardMcpEntry | OpenCodeMcpEntry {
|
|
239
|
+
if (format === 'mcp') {
|
|
240
|
+
// OpenCode: command is array [command, ...args]
|
|
241
|
+
return {
|
|
242
|
+
type: 'local',
|
|
243
|
+
command: [command, ...args],
|
|
244
|
+
enabled: true,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
// All others use standard format
|
|
248
|
+
return { command, args };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Add or update server entry in config.
|
|
253
|
+
*/
|
|
254
|
+
export function setServerEntry(
|
|
255
|
+
config: AnyMcpConfig,
|
|
256
|
+
serverName: string,
|
|
257
|
+
entry: StandardMcpEntry | OpenCodeMcpEntry,
|
|
258
|
+
format: McpConfigFormat
|
|
259
|
+
): void {
|
|
260
|
+
const key = getServersKey(format);
|
|
261
|
+
|
|
262
|
+
// Initialize servers record if needed
|
|
263
|
+
if (!config[key]) {
|
|
264
|
+
(config as Record<string, unknown>)[key] = {};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const servers = config[key] as Record<
|
|
268
|
+
string,
|
|
269
|
+
StandardMcpEntry | OpenCodeMcpEntry
|
|
270
|
+
>;
|
|
271
|
+
servers[serverName] = entry;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Remove server entry from config.
|
|
276
|
+
* Returns true if entry was removed, false if not found.
|
|
277
|
+
*/
|
|
278
|
+
export function removeServerEntry(
|
|
279
|
+
config: AnyMcpConfig,
|
|
280
|
+
serverName: string,
|
|
281
|
+
format: McpConfigFormat
|
|
282
|
+
): boolean {
|
|
283
|
+
const key = getServersKey(format);
|
|
284
|
+
const servers = config[key] as
|
|
285
|
+
| Record<string, StandardMcpEntry | OpenCodeMcpEntry>
|
|
286
|
+
| undefined;
|
|
287
|
+
|
|
288
|
+
if (!servers?.[serverName]) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
delete servers[serverName];
|
|
293
|
+
|
|
294
|
+
// Clean up empty servers object
|
|
295
|
+
if (Object.keys(servers).length === 0) {
|
|
296
|
+
delete (config as Record<string, unknown>)[key];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP command exports.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/mcp
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { type InstallOptions, installMcp } from './install.js';
|
|
8
|
+
export {
|
|
9
|
+
buildMcpServerEntry,
|
|
10
|
+
findBunPath,
|
|
11
|
+
getTargetDisplayName,
|
|
12
|
+
MCP_SERVER_NAME,
|
|
13
|
+
MCP_TARGETS,
|
|
14
|
+
type McpConfigPaths,
|
|
15
|
+
type McpPathOptions,
|
|
16
|
+
type McpScope,
|
|
17
|
+
type McpServerEntry,
|
|
18
|
+
type McpTarget,
|
|
19
|
+
resolveAllMcpPaths,
|
|
20
|
+
resolveMcpConfigPath,
|
|
21
|
+
TARGETS_WITH_PROJECT_SCOPE,
|
|
22
|
+
} from './paths.js';
|
|
23
|
+
export { type StatusOptions, statusMcp } from './status.js';
|
|
24
|
+
export { type UninstallOptions, uninstallMcp } from './uninstall.js';
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install gno as MCP server in client configurations.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/mcp/install
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CliError } from '../../errors.js';
|
|
8
|
+
import { getGlobals } from '../../program.js';
|
|
9
|
+
import {
|
|
10
|
+
type AnyMcpConfig,
|
|
11
|
+
buildEntry,
|
|
12
|
+
hasServerEntry,
|
|
13
|
+
isYamlFormat,
|
|
14
|
+
readMcpConfig,
|
|
15
|
+
type StandardMcpEntry,
|
|
16
|
+
setServerEntry,
|
|
17
|
+
writeMcpConfig,
|
|
18
|
+
} from './config.js';
|
|
19
|
+
import {
|
|
20
|
+
buildMcpServerEntry,
|
|
21
|
+
getTargetDisplayName,
|
|
22
|
+
MCP_SERVER_NAME,
|
|
23
|
+
type McpScope,
|
|
24
|
+
type McpTarget,
|
|
25
|
+
resolveMcpConfigPath,
|
|
26
|
+
TARGETS_WITH_PROJECT_SCOPE,
|
|
27
|
+
} from './paths.js';
|
|
28
|
+
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
// Types
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export interface InstallOptions {
|
|
34
|
+
target?: McpTarget;
|
|
35
|
+
scope?: McpScope;
|
|
36
|
+
force?: boolean;
|
|
37
|
+
dryRun?: boolean;
|
|
38
|
+
/** Override cwd (testing) */
|
|
39
|
+
cwd?: string;
|
|
40
|
+
/** Override home dir (testing) */
|
|
41
|
+
homeDir?: string;
|
|
42
|
+
/** JSON output */
|
|
43
|
+
json?: boolean;
|
|
44
|
+
/** Quiet mode */
|
|
45
|
+
quiet?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface InstallResult {
|
|
49
|
+
target: McpTarget;
|
|
50
|
+
scope: McpScope;
|
|
51
|
+
configPath: string;
|
|
52
|
+
action: 'created' | 'updated' | 'dry_run_create' | 'dry_run_update';
|
|
53
|
+
serverEntry: { command: string; args: string[] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
// Install Logic
|
|
58
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Install gno to a single target.
|
|
62
|
+
*/
|
|
63
|
+
async function installToTarget(
|
|
64
|
+
target: McpTarget,
|
|
65
|
+
scope: McpScope,
|
|
66
|
+
serverEntry: StandardMcpEntry,
|
|
67
|
+
options: {
|
|
68
|
+
force?: boolean;
|
|
69
|
+
dryRun?: boolean;
|
|
70
|
+
cwd?: string;
|
|
71
|
+
homeDir?: string;
|
|
72
|
+
}
|
|
73
|
+
): Promise<InstallResult> {
|
|
74
|
+
const { force = false, dryRun = false, cwd, homeDir } = options;
|
|
75
|
+
|
|
76
|
+
const { configPath, configFormat } = resolveMcpConfigPath({
|
|
77
|
+
target,
|
|
78
|
+
scope,
|
|
79
|
+
cwd,
|
|
80
|
+
homeDir,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Read existing config (needed for both dry-run preview and actual install)
|
|
84
|
+
// readMcpConfig returns {} for missing files when returnNullOnMissing is not set
|
|
85
|
+
const useYaml = isYamlFormat(configFormat);
|
|
86
|
+
const config = ((await readMcpConfig(configPath, {
|
|
87
|
+
yaml: useYaml,
|
|
88
|
+
})) ?? {}) as AnyMcpConfig;
|
|
89
|
+
|
|
90
|
+
// Check if already exists using format-aware helper
|
|
91
|
+
const alreadyExists = hasServerEntry(config, MCP_SERVER_NAME, configFormat);
|
|
92
|
+
if (alreadyExists && !force) {
|
|
93
|
+
throw new CliError(
|
|
94
|
+
'VALIDATION',
|
|
95
|
+
`${getTargetDisplayName(target)} already has gno configured.\n` +
|
|
96
|
+
` Config: ${configPath}\n` +
|
|
97
|
+
' Use --force to overwrite.'
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const wouldCreate = !alreadyExists;
|
|
102
|
+
|
|
103
|
+
if (dryRun) {
|
|
104
|
+
return {
|
|
105
|
+
target,
|
|
106
|
+
scope,
|
|
107
|
+
configPath,
|
|
108
|
+
action: wouldCreate ? 'dry_run_create' : 'dry_run_update',
|
|
109
|
+
serverEntry,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const action = wouldCreate ? 'created' : 'updated';
|
|
114
|
+
|
|
115
|
+
// Build format-specific entry and add to config
|
|
116
|
+
const entry = buildEntry(serverEntry.command, serverEntry.args, configFormat);
|
|
117
|
+
setServerEntry(config, MCP_SERVER_NAME, entry, configFormat);
|
|
118
|
+
|
|
119
|
+
// Write atomically
|
|
120
|
+
await writeMcpConfig(configPath, config, { yaml: useYaml });
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
target,
|
|
124
|
+
scope,
|
|
125
|
+
configPath,
|
|
126
|
+
action,
|
|
127
|
+
serverEntry,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get globals safely for testing.
|
|
133
|
+
*/
|
|
134
|
+
function safeGetGlobals(): { json: boolean; quiet: boolean } {
|
|
135
|
+
try {
|
|
136
|
+
return getGlobals();
|
|
137
|
+
} catch {
|
|
138
|
+
return { json: false, quiet: false };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Install gno MCP server.
|
|
144
|
+
*/
|
|
145
|
+
export async function installMcp(opts: InstallOptions = {}): Promise<void> {
|
|
146
|
+
const target = opts.target ?? 'claude-desktop';
|
|
147
|
+
const scope = opts.scope ?? 'user';
|
|
148
|
+
const force = opts.force ?? false;
|
|
149
|
+
const dryRun = opts.dryRun ?? false;
|
|
150
|
+
const globals = safeGetGlobals();
|
|
151
|
+
const json = opts.json ?? globals.json;
|
|
152
|
+
const quiet = opts.quiet ?? globals.quiet;
|
|
153
|
+
|
|
154
|
+
// Validate scope - only some targets support project scope
|
|
155
|
+
if (scope === 'project' && !TARGETS_WITH_PROJECT_SCOPE.includes(target)) {
|
|
156
|
+
throw new CliError(
|
|
157
|
+
'VALIDATION',
|
|
158
|
+
`${getTargetDisplayName(target)} does not support project scope. Use --scope user.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Build server entry (uses process.execPath, always succeeds)
|
|
163
|
+
const serverEntry = buildMcpServerEntry();
|
|
164
|
+
|
|
165
|
+
// Install
|
|
166
|
+
const result = await installToTarget(target, scope, serverEntry, {
|
|
167
|
+
force,
|
|
168
|
+
dryRun,
|
|
169
|
+
cwd: opts.cwd,
|
|
170
|
+
homeDir: opts.homeDir,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Output
|
|
174
|
+
if (json) {
|
|
175
|
+
process.stdout.write(`${JSON.stringify({ installed: result }, null, 2)}\n`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (quiet) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (dryRun) {
|
|
184
|
+
const dryRunVerb = result.action === 'dry_run_create' ? 'create' : 'update';
|
|
185
|
+
process.stdout.write('Dry run - no changes made.\n\n');
|
|
186
|
+
process.stdout.write(
|
|
187
|
+
`Would ${dryRunVerb} gno in ${getTargetDisplayName(target)}:\n`
|
|
188
|
+
);
|
|
189
|
+
process.stdout.write(` Config: ${result.configPath}\n`);
|
|
190
|
+
process.stdout.write(` Command: ${serverEntry.command}\n`);
|
|
191
|
+
process.stdout.write(` Args: ${serverEntry.args.join(' ')}\n`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const verb = result.action === 'created' ? 'Installed' : 'Updated';
|
|
196
|
+
process.stdout.write(
|
|
197
|
+
`${verb} gno MCP server in ${getTargetDisplayName(target)}.\n`
|
|
198
|
+
);
|
|
199
|
+
process.stdout.write(` Config: ${result.configPath}\n\n`);
|
|
200
|
+
process.stdout.write(
|
|
201
|
+
`Restart ${getTargetDisplayName(target)} to load the server.\n`
|
|
202
|
+
);
|
|
203
|
+
}
|