@nowline/cli 0.8.6 → 0.9.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 (89) hide show
  1. package/README.md +32 -9
  2. package/dist/cli/args.d.ts +20 -4
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +20 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/help.d.ts.map +1 -1
  7. package/dist/cli/help.js +30 -2
  8. package/dist/cli/help.js.map +1 -1
  9. package/dist/cli/options.d.ts +74 -0
  10. package/dist/cli/options.d.ts.map +1 -0
  11. package/dist/cli/options.js +209 -0
  12. package/dist/cli/options.js.map +1 -0
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +2 -3
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/mcp-install.d.ts +18 -0
  17. package/dist/commands/mcp-install.d.ts.map +1 -0
  18. package/dist/commands/mcp-install.js +151 -0
  19. package/dist/commands/mcp-install.js.map +1 -0
  20. package/dist/commands/mcp.d.ts.map +1 -1
  21. package/dist/commands/mcp.js +5 -11
  22. package/dist/commands/mcp.js.map +1 -1
  23. package/dist/commands/render.d.ts +7 -0
  24. package/dist/commands/render.d.ts.map +1 -1
  25. package/dist/commands/render.js +161 -155
  26. package/dist/commands/render.js.map +1 -1
  27. package/dist/commands/serve.d.ts.map +1 -1
  28. package/dist/commands/serve.js +87 -60
  29. package/dist/commands/serve.js.map +1 -1
  30. package/dist/diagnostics/adapt.d.ts +12 -1
  31. package/dist/diagnostics/adapt.d.ts.map +1 -1
  32. package/dist/diagnostics/adapt.js +53 -1
  33. package/dist/diagnostics/adapt.js.map +1 -1
  34. package/dist/diagnostics/model.d.ts +1 -1
  35. package/dist/generated/version.d.ts +1 -1
  36. package/dist/generated/version.js +2 -2
  37. package/dist/i18n/locale.d.ts.map +1 -1
  38. package/dist/i18n/locale.js +2 -5
  39. package/dist/i18n/locale.js.map +1 -1
  40. package/dist/index.js +4 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/io/config.d.ts +1 -1
  43. package/dist/io/config.d.ts.map +1 -1
  44. package/dist/io/config.js +1 -1
  45. package/dist/io/config.js.map +1 -1
  46. package/dist/io/log.d.ts +17 -0
  47. package/dist/io/log.d.ts.map +1 -0
  48. package/dist/io/log.js +16 -0
  49. package/dist/io/log.js.map +1 -0
  50. package/dist/mcp-install/clients.d.ts +58 -0
  51. package/dist/mcp-install/clients.d.ts.map +1 -0
  52. package/dist/mcp-install/clients.js +304 -0
  53. package/dist/mcp-install/clients.js.map +1 -0
  54. package/dist/mcp-install/host.d.ts +29 -0
  55. package/dist/mcp-install/host.d.ts.map +1 -0
  56. package/dist/mcp-install/host.js +72 -0
  57. package/dist/mcp-install/host.js.map +1 -0
  58. package/dist/mcp-install/json-config.d.ts +29 -0
  59. package/dist/mcp-install/json-config.d.ts.map +1 -0
  60. package/dist/mcp-install/json-config.js +139 -0
  61. package/dist/mcp-install/json-config.js.map +1 -0
  62. package/dist/mcp-install/server-command.d.ts +26 -0
  63. package/dist/mcp-install/server-command.d.ts.map +1 -0
  64. package/dist/mcp-install/server-command.js +52 -0
  65. package/dist/mcp-install/server-command.js.map +1 -0
  66. package/man/fr/nowline.1 +246 -19
  67. package/man/fr/nowline.5 +819 -45
  68. package/man/nowline.1 +191 -18
  69. package/man/nowline.5 +764 -36
  70. package/package.json +19 -19
  71. package/src/cli/args.ts +55 -5
  72. package/src/cli/help.ts +30 -2
  73. package/src/cli/options.ts +286 -0
  74. package/src/commands/init.ts +2 -3
  75. package/src/commands/mcp-install.ts +211 -0
  76. package/src/commands/mcp.ts +8 -13
  77. package/src/commands/render.ts +198 -195
  78. package/src/commands/serve.ts +102 -74
  79. package/src/diagnostics/adapt.ts +62 -0
  80. package/src/diagnostics/model.ts +1 -1
  81. package/src/generated/version.ts +2 -2
  82. package/src/i18n/locale.ts +2 -6
  83. package/src/index.ts +3 -0
  84. package/src/io/config.ts +0 -1
  85. package/src/io/log.ts +33 -0
  86. package/src/mcp-install/clients.ts +443 -0
  87. package/src/mcp-install/host.ts +106 -0
  88. package/src/mcp-install/json-config.ts +179 -0
  89. package/src/mcp-install/server-command.ts +75 -0
@@ -0,0 +1,211 @@
1
+ import type { ParsedArgs } from '../cli/args.js';
2
+ import { CliError, ExitCode } from '../io/exit-codes.js';
3
+ import { createLog } from '../io/log.js';
4
+ import {
5
+ CLIENT_IDS,
6
+ CLIENTS,
7
+ type ClientDescriptor,
8
+ type CliTarget,
9
+ commandLine,
10
+ findClient,
11
+ type JsonTarget,
12
+ type PrintTarget,
13
+ type Scope,
14
+ SERVER_NAME,
15
+ } from '../mcp-install/clients.js';
16
+ import { createHostEnv, type HostEnv } from '../mcp-install/host.js';
17
+ import {
18
+ JsonConfigError,
19
+ readConfigFile,
20
+ removeEntry,
21
+ upsertEntry,
22
+ writeConfigFile,
23
+ } from '../mcp-install/json-config.js';
24
+ import { resolveServer, type ServerEntry } from '../mcp-install/server-command.js';
25
+
26
+ export interface McpInstallOptions {
27
+ args: ParsedArgs;
28
+ host?: HostEnv;
29
+ stdout?: (chunk: string) => void;
30
+ }
31
+
32
+ interface Run {
33
+ uninstall: boolean;
34
+ dryRun: boolean;
35
+ scope: Scope;
36
+ entry: ServerEntry;
37
+ host: HostEnv;
38
+ out: (line: string) => void;
39
+ }
40
+
41
+ /**
42
+ * `nowline --mcp-install [<client>]` / `--mcp-uninstall [<client>]`.
43
+ *
44
+ * Registers (or removes) the `nowline` MCP server in each harness's config.
45
+ * JSON configs are edited in place; Claude Code's user scope and Codex go
46
+ * through their own `mcp add`, since we can't safely edit ~/.claude.json or
47
+ * TOML; YAML and undocumented paths get a paste-ready snippet. One client
48
+ * failing doesn't stop the rest; the run then exits with OutputError.
49
+ */
50
+ export async function mcpInstallHandler({
51
+ args,
52
+ host = createHostEnv(),
53
+ stdout = (chunk) => {
54
+ process.stdout.write(chunk);
55
+ },
56
+ }: McpInstallOptions): Promise<void> {
57
+ const uninstall = args.mode === 'mcp-uninstall';
58
+ const flag = uninstall ? '--mcp-uninstall' : '--mcp-install';
59
+ const scope = args.scope ?? 'user';
60
+ const log = createLog(args.logLevel);
61
+ const out = (line: string) => stdout(`${line}\n`);
62
+
63
+ const clients = selectClients(args.positional, scope, flag, host, out);
64
+
65
+ const server = resolveServer(args.server ?? 'self', args.root, host);
66
+ if (!uninstall) for (const warning of server.warnings) log.notice(warning);
67
+
68
+ const run: Run = { uninstall, dryRun: args.dryRun, scope, entry: server.entry, host, out };
69
+ const failed: string[] = [];
70
+ for (const client of clients) {
71
+ try {
72
+ applyTarget(client, run);
73
+ } catch (err) {
74
+ if (!(err instanceof JsonConfigError || err instanceof ClientFailure)) throw err;
75
+ failed.push(client.id);
76
+ process.stderr.write(`nowline: ${client.id}: ${err.message}\n`);
77
+ }
78
+ }
79
+ if (failed.length > 0) {
80
+ throw new CliError(
81
+ ExitCode.OutputError,
82
+ `nowline: ${flag} failed for ${failed.join(', ')}.`,
83
+ );
84
+ }
85
+ }
86
+
87
+ class ClientFailure extends Error {}
88
+
89
+ function selectClients(
90
+ requested: string | undefined,
91
+ scope: Scope,
92
+ flag: string,
93
+ host: HostEnv,
94
+ out: (line: string) => void,
95
+ ): ClientDescriptor[] {
96
+ if (requested !== undefined && requested !== 'all') {
97
+ const client = findClient(requested);
98
+ if (!client) {
99
+ throw new CliError(
100
+ ExitCode.InputError,
101
+ `nowline: unknown client "${requested}". Expected one of: ${CLIENT_IDS.join(', ')}, all.`,
102
+ );
103
+ }
104
+ if (!client.scopes.includes(scope)) {
105
+ throw new CliError(
106
+ ExitCode.InputError,
107
+ `nowline: ${client.id} has no ${scope} config; use --scope ${client.scopes.join(' or ')}.`,
108
+ );
109
+ }
110
+ return [client];
111
+ }
112
+ const detected = CLIENTS.filter((client) => client.detect(host));
113
+ if (detected.length === 0) {
114
+ throw new CliError(
115
+ ExitCode.InputError,
116
+ `nowline: no supported agent found on this machine. Name one: nowline ${flag} <client>, where <client> is one of: ${CLIENT_IDS.join(', ')}.`,
117
+ );
118
+ }
119
+ return detected.filter((client) => {
120
+ if (client.scopes.includes(scope)) return true;
121
+ out(`${client.id}: skipped, it has no ${scope} config`);
122
+ return false;
123
+ });
124
+ }
125
+
126
+ function applyTarget(client: ClientDescriptor, run: Run): void {
127
+ const target = client.target(run.scope, run.entry, run.host);
128
+ if (target.kind === 'json') return applyJson(client, target, run);
129
+ if (target.kind === 'cli') {
130
+ if (run.host.findAllOnPath(target.binary).length === 0) {
131
+ return applyPrint(client, target.fallback, run);
132
+ }
133
+ return applyCli(client, target, run);
134
+ }
135
+ return applyPrint(client, target, run);
136
+ }
137
+
138
+ function applyJson(client: ClientDescriptor, target: JsonTarget, run: Run): void {
139
+ const existing = readConfigFile(target.path);
140
+ const edit = run.uninstall
141
+ ? removeEntry(existing, target, run.entry)
142
+ : upsertEntry(existing, target, run.entry);
143
+ const where = target.path;
144
+ if (edit.change === 'unchanged') return run.out(`${client.id}: already installed in ${where}`);
145
+ if (edit.change === 'absent') return run.out(`${client.id}: not installed in ${where}`);
146
+
147
+ const [verb, object] =
148
+ edit.change === 'created'
149
+ ? ['create', where]
150
+ : edit.change === 'removed'
151
+ ? ['remove', `${SERVER_NAME} from ${where}`]
152
+ : edit.change === 'added'
153
+ ? ['add', `${SERVER_NAME} to ${where}`]
154
+ : ['update', `${SERVER_NAME} in ${where}`];
155
+ if (run.dryRun) {
156
+ run.out(`${client.id}: would ${verb} ${object}`);
157
+ if (edit.value !== undefined) {
158
+ run.out(indent(JSON.stringify({ [edit.keyPath.join('.')]: edit.value }, null, 2)));
159
+ }
160
+ return;
161
+ }
162
+ if (edit.text !== undefined) writeConfigFile(target.path, edit.text);
163
+ const hint = target.hint !== undefined && !run.uninstall ? `; ${target.hint}` : '';
164
+ run.out(`${client.id}: ${verb.endsWith('e') ? `${verb}d` : `${verb}ed`} ${object}${hint}`);
165
+ }
166
+
167
+ function applyCli(client: ClientDescriptor, target: CliTarget, run: Run): void {
168
+ const remove = [target.binary, ...target.remove];
169
+ const add = [target.binary, ...target.add];
170
+ if (run.dryRun) {
171
+ run.out(`${client.id}: would run:`);
172
+ run.out(indent(commandLine(remove)));
173
+ if (!run.uninstall) run.out(indent(commandLine(add)));
174
+ return;
175
+ }
176
+ // Remove first so a re-run replaces a stale entry instead of failing on a duplicate.
177
+ const removed = run.host.spawn(target.binary, target.remove);
178
+ if (run.uninstall) {
179
+ if (removed.status === 0) {
180
+ return run.out(`${client.id}: removed ${SERVER_NAME} (${commandLine(remove)})`);
181
+ }
182
+ return run.out(
183
+ `${client.id}: nothing removed; ${commandLine(remove)} said: ${firstLine(removed.stderr || removed.stdout || removed.error)}`,
184
+ );
185
+ }
186
+ const added = run.host.spawn(target.binary, target.add);
187
+ if (added.status !== 0) {
188
+ const reason = firstLine(added.stderr || added.stdout || added.error);
189
+ throw new ClientFailure(
190
+ `${commandLine(add)} failed${added.status !== null ? ` (exit ${added.status})` : ''}: ${reason}`,
191
+ );
192
+ }
193
+ run.out(`${client.id}: registered (${commandLine(add)})`);
194
+ }
195
+
196
+ function applyPrint(client: ClientDescriptor, target: PrintTarget, run: Run): void {
197
+ const step = run.uninstall ? target.uninstall : target.install;
198
+ run.out(`${client.id}: ${step.instructions}`);
199
+ if (step.snippet !== undefined) run.out(indent(step.snippet));
200
+ }
201
+
202
+ function indent(text: string): string {
203
+ return text
204
+ .split('\n')
205
+ .map((line) => ` ${line}`)
206
+ .join('\n');
207
+ }
208
+
209
+ function firstLine(text: string | undefined): string {
210
+ return text?.trim().split('\n')[0] ?? 'no output';
211
+ }
@@ -1,8 +1,9 @@
1
1
  import { createServer } from 'node:http';
2
- import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
3
2
  import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
3
+ import { createMcpHttpHandler } from '@nowline/mcp/http';
4
4
  import { createMcpServer } from '@nowline/mcp/server';
5
5
  import type { ParsedArgs } from '../cli/args.js';
6
+ import { createLog } from '../io/log.js';
6
7
 
7
8
  /**
8
9
  * `nowline --mcp` handler.
@@ -18,7 +19,6 @@ import type { ParsedArgs } from '../cli/args.js';
18
19
  */
19
20
  export async function mcpHandler({ args }: { args: ParsedArgs }): Promise<void> {
20
21
  const root = args.root ?? process.cwd();
21
- const server = createMcpServer({ allowedRoot: root });
22
22
 
23
23
  const portStr = args.port;
24
24
  if (portStr !== undefined) {
@@ -29,19 +29,14 @@ export async function mcpHandler({ args }: { args: ParsedArgs }): Promise<void>
29
29
  );
30
30
  }
31
31
 
32
- const transport = new NodeStreamableHTTPServerTransport({
33
- sessionIdGenerator: undefined,
34
- });
35
- await server.connect(transport);
36
-
37
- const httpServer = createServer(async (req, res) => {
38
- await transport.handleRequest(req, res);
39
- });
32
+ const httpServer = createServer(
33
+ createMcpHttpHandler(() => createMcpServer({ allowedRoot: root })),
34
+ );
40
35
 
41
36
  await new Promise<void>((resolve) => {
42
37
  httpServer.listen(port, '127.0.0.1', () => {
43
- process.stderr.write(
44
- `nowline: MCP Streamable HTTP listening on http://127.0.0.1:${port}\n`,
38
+ createLog(args.logLevel).notice(
39
+ `nowline: MCP Streamable HTTP listening on http://127.0.0.1:${port}`,
45
40
  );
46
41
  });
47
42
  httpServer.on('close', resolve);
@@ -50,7 +45,7 @@ export async function mcpHandler({ args }: { args: ParsedArgs }): Promise<void>
50
45
  });
51
46
  } else {
52
47
  const transport = new StdioServerTransport();
53
- await server.connect(transport);
48
+ await createMcpServer({ allowedRoot: root }).connect(transport);
54
49
  await new Promise<void>((resolve) => {
55
50
  transport.onclose = resolve;
56
51
  });