@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.
Files changed (131) hide show
  1. package/README.md +256 -0
  2. package/assets/skill/SKILL.md +112 -0
  3. package/assets/skill/cli-reference.md +327 -0
  4. package/assets/skill/examples.md +234 -0
  5. package/assets/skill/mcp-reference.md +159 -0
  6. package/package.json +90 -0
  7. package/src/app/constants.ts +313 -0
  8. package/src/cli/colors.ts +65 -0
  9. package/src/cli/commands/ask.ts +545 -0
  10. package/src/cli/commands/cleanup.ts +105 -0
  11. package/src/cli/commands/collection/add.ts +120 -0
  12. package/src/cli/commands/collection/index.ts +10 -0
  13. package/src/cli/commands/collection/list.ts +108 -0
  14. package/src/cli/commands/collection/remove.ts +64 -0
  15. package/src/cli/commands/collection/rename.ts +95 -0
  16. package/src/cli/commands/context/add.ts +67 -0
  17. package/src/cli/commands/context/check.ts +153 -0
  18. package/src/cli/commands/context/index.ts +10 -0
  19. package/src/cli/commands/context/list.ts +109 -0
  20. package/src/cli/commands/context/rm.ts +52 -0
  21. package/src/cli/commands/doctor.ts +393 -0
  22. package/src/cli/commands/embed.ts +462 -0
  23. package/src/cli/commands/get.ts +356 -0
  24. package/src/cli/commands/index-cmd.ts +119 -0
  25. package/src/cli/commands/index.ts +102 -0
  26. package/src/cli/commands/init.ts +328 -0
  27. package/src/cli/commands/ls.ts +217 -0
  28. package/src/cli/commands/mcp/config.ts +300 -0
  29. package/src/cli/commands/mcp/index.ts +24 -0
  30. package/src/cli/commands/mcp/install.ts +203 -0
  31. package/src/cli/commands/mcp/paths.ts +470 -0
  32. package/src/cli/commands/mcp/status.ts +222 -0
  33. package/src/cli/commands/mcp/uninstall.ts +158 -0
  34. package/src/cli/commands/mcp.ts +20 -0
  35. package/src/cli/commands/models/clear.ts +103 -0
  36. package/src/cli/commands/models/index.ts +32 -0
  37. package/src/cli/commands/models/list.ts +214 -0
  38. package/src/cli/commands/models/path.ts +51 -0
  39. package/src/cli/commands/models/pull.ts +199 -0
  40. package/src/cli/commands/models/use.ts +85 -0
  41. package/src/cli/commands/multi-get.ts +400 -0
  42. package/src/cli/commands/query.ts +220 -0
  43. package/src/cli/commands/ref-parser.ts +108 -0
  44. package/src/cli/commands/reset.ts +191 -0
  45. package/src/cli/commands/search.ts +136 -0
  46. package/src/cli/commands/shared.ts +156 -0
  47. package/src/cli/commands/skill/index.ts +19 -0
  48. package/src/cli/commands/skill/install.ts +197 -0
  49. package/src/cli/commands/skill/paths-cmd.ts +81 -0
  50. package/src/cli/commands/skill/paths.ts +191 -0
  51. package/src/cli/commands/skill/show.ts +73 -0
  52. package/src/cli/commands/skill/uninstall.ts +141 -0
  53. package/src/cli/commands/status.ts +205 -0
  54. package/src/cli/commands/update.ts +68 -0
  55. package/src/cli/commands/vsearch.ts +188 -0
  56. package/src/cli/context.ts +64 -0
  57. package/src/cli/errors.ts +64 -0
  58. package/src/cli/format/search-results.ts +211 -0
  59. package/src/cli/options.ts +183 -0
  60. package/src/cli/program.ts +1330 -0
  61. package/src/cli/run.ts +213 -0
  62. package/src/cli/ui.ts +92 -0
  63. package/src/config/defaults.ts +20 -0
  64. package/src/config/index.ts +55 -0
  65. package/src/config/loader.ts +161 -0
  66. package/src/config/paths.ts +87 -0
  67. package/src/config/saver.ts +153 -0
  68. package/src/config/types.ts +280 -0
  69. package/src/converters/adapters/markitdownTs/adapter.ts +140 -0
  70. package/src/converters/adapters/officeparser/adapter.ts +126 -0
  71. package/src/converters/canonicalize.ts +89 -0
  72. package/src/converters/errors.ts +218 -0
  73. package/src/converters/index.ts +51 -0
  74. package/src/converters/mime.ts +163 -0
  75. package/src/converters/native/markdown.ts +115 -0
  76. package/src/converters/native/plaintext.ts +56 -0
  77. package/src/converters/path.ts +48 -0
  78. package/src/converters/pipeline.ts +159 -0
  79. package/src/converters/registry.ts +74 -0
  80. package/src/converters/types.ts +123 -0
  81. package/src/converters/versions.ts +24 -0
  82. package/src/index.ts +27 -0
  83. package/src/ingestion/chunker.ts +238 -0
  84. package/src/ingestion/index.ts +32 -0
  85. package/src/ingestion/language.ts +276 -0
  86. package/src/ingestion/sync.ts +671 -0
  87. package/src/ingestion/types.ts +219 -0
  88. package/src/ingestion/walker.ts +235 -0
  89. package/src/llm/cache.ts +467 -0
  90. package/src/llm/errors.ts +191 -0
  91. package/src/llm/index.ts +58 -0
  92. package/src/llm/nodeLlamaCpp/adapter.ts +133 -0
  93. package/src/llm/nodeLlamaCpp/embedding.ts +165 -0
  94. package/src/llm/nodeLlamaCpp/generation.ts +88 -0
  95. package/src/llm/nodeLlamaCpp/lifecycle.ts +317 -0
  96. package/src/llm/nodeLlamaCpp/rerank.ts +94 -0
  97. package/src/llm/registry.ts +86 -0
  98. package/src/llm/types.ts +129 -0
  99. package/src/mcp/resources/index.ts +151 -0
  100. package/src/mcp/server.ts +229 -0
  101. package/src/mcp/tools/get.ts +220 -0
  102. package/src/mcp/tools/index.ts +160 -0
  103. package/src/mcp/tools/multi-get.ts +263 -0
  104. package/src/mcp/tools/query.ts +226 -0
  105. package/src/mcp/tools/search.ts +119 -0
  106. package/src/mcp/tools/status.ts +81 -0
  107. package/src/mcp/tools/vsearch.ts +198 -0
  108. package/src/pipeline/chunk-lookup.ts +44 -0
  109. package/src/pipeline/expansion.ts +256 -0
  110. package/src/pipeline/explain.ts +115 -0
  111. package/src/pipeline/fusion.ts +185 -0
  112. package/src/pipeline/hybrid.ts +535 -0
  113. package/src/pipeline/index.ts +64 -0
  114. package/src/pipeline/query-language.ts +118 -0
  115. package/src/pipeline/rerank.ts +223 -0
  116. package/src/pipeline/search.ts +261 -0
  117. package/src/pipeline/types.ts +328 -0
  118. package/src/pipeline/vsearch.ts +348 -0
  119. package/src/store/index.ts +41 -0
  120. package/src/store/migrations/001-initial.ts +196 -0
  121. package/src/store/migrations/index.ts +20 -0
  122. package/src/store/migrations/runner.ts +187 -0
  123. package/src/store/sqlite/adapter.ts +1242 -0
  124. package/src/store/sqlite/index.ts +7 -0
  125. package/src/store/sqlite/setup.ts +129 -0
  126. package/src/store/sqlite/types.ts +28 -0
  127. package/src/store/types.ts +506 -0
  128. package/src/store/vector/index.ts +13 -0
  129. package/src/store/vector/sqlite-vec.ts +373 -0
  130. package/src/store/vector/stats.ts +152 -0
  131. 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
+ }