@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.
- package/README.md +32 -9
- package/dist/cli/args.d.ts +20 -4
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +20 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +30 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/options.d.ts +74 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +209 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +2 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp-install.d.ts +18 -0
- package/dist/commands/mcp-install.d.ts.map +1 -0
- package/dist/commands/mcp-install.js +151 -0
- package/dist/commands/mcp-install.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +5 -11
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/render.d.ts +7 -0
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +161 -155
- package/dist/commands/render.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +87 -60
- package/dist/commands/serve.js.map +1 -1
- package/dist/diagnostics/adapt.d.ts +12 -1
- package/dist/diagnostics/adapt.d.ts.map +1 -1
- package/dist/diagnostics/adapt.js +53 -1
- package/dist/diagnostics/adapt.js.map +1 -1
- package/dist/diagnostics/model.d.ts +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +2 -2
- package/dist/i18n/locale.d.ts.map +1 -1
- package/dist/i18n/locale.js +2 -5
- package/dist/i18n/locale.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/io/config.d.ts +1 -1
- package/dist/io/config.d.ts.map +1 -1
- package/dist/io/config.js +1 -1
- package/dist/io/config.js.map +1 -1
- package/dist/io/log.d.ts +17 -0
- package/dist/io/log.d.ts.map +1 -0
- package/dist/io/log.js +16 -0
- package/dist/io/log.js.map +1 -0
- package/dist/mcp-install/clients.d.ts +58 -0
- package/dist/mcp-install/clients.d.ts.map +1 -0
- package/dist/mcp-install/clients.js +304 -0
- package/dist/mcp-install/clients.js.map +1 -0
- package/dist/mcp-install/host.d.ts +29 -0
- package/dist/mcp-install/host.d.ts.map +1 -0
- package/dist/mcp-install/host.js +72 -0
- package/dist/mcp-install/host.js.map +1 -0
- package/dist/mcp-install/json-config.d.ts +29 -0
- package/dist/mcp-install/json-config.d.ts.map +1 -0
- package/dist/mcp-install/json-config.js +139 -0
- package/dist/mcp-install/json-config.js.map +1 -0
- package/dist/mcp-install/server-command.d.ts +26 -0
- package/dist/mcp-install/server-command.d.ts.map +1 -0
- package/dist/mcp-install/server-command.js +52 -0
- package/dist/mcp-install/server-command.js.map +1 -0
- package/man/fr/nowline.1 +246 -19
- package/man/fr/nowline.5 +819 -45
- package/man/nowline.1 +191 -18
- package/man/nowline.5 +764 -36
- package/package.json +19 -19
- package/src/cli/args.ts +55 -5
- package/src/cli/help.ts +30 -2
- package/src/cli/options.ts +286 -0
- package/src/commands/init.ts +2 -3
- package/src/commands/mcp-install.ts +211 -0
- package/src/commands/mcp.ts +8 -13
- package/src/commands/render.ts +198 -195
- package/src/commands/serve.ts +102 -74
- package/src/diagnostics/adapt.ts +62 -0
- package/src/diagnostics/model.ts +1 -1
- package/src/generated/version.ts +2 -2
- package/src/i18n/locale.ts +2 -6
- package/src/index.ts +3 -0
- package/src/io/config.ts +0 -1
- package/src/io/log.ts +33 -0
- package/src/mcp-install/clients.ts +443 -0
- package/src/mcp-install/host.ts +106 -0
- package/src/mcp-install/json-config.ts +179 -0
- package/src/mcp-install/server-command.ts +75 -0
package/src/commands/serve.ts
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { type FSWatcher, promises as fs, watch as fsWatch } from 'node:fs';
|
|
2
2
|
import * as http from 'node:http';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
type NormalizedZone,
|
|
8
|
-
normalizeZone,
|
|
9
|
-
resolveToday,
|
|
10
|
-
type ThemeName,
|
|
11
|
-
TimezoneError,
|
|
12
|
-
} from '@nowline/layout';
|
|
13
|
-
import { renderSvg } from '@nowline/renderer';
|
|
4
|
+
import { isRoutedResolveDiagnostic } from '@nowline/core';
|
|
5
|
+
import type { ThemeName } from '@nowline/layout';
|
|
6
|
+
import { renderSvg, stageRoadmap } from '@nowline/renderer';
|
|
14
7
|
import type { ParsedArgs } from '../cli/args.js';
|
|
8
|
+
import { resolveCliOptions } from '../cli/options.js';
|
|
15
9
|
import { getServices, parseSource } from '../core/parse.js';
|
|
16
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
adaptResolveDiagnostic,
|
|
12
|
+
type DiagnosticSource,
|
|
13
|
+
formatDiagnostics,
|
|
14
|
+
} from '../diagnostics/index.js';
|
|
17
15
|
import {
|
|
18
16
|
describeContentLocaleSource,
|
|
19
17
|
operatorLocale,
|
|
@@ -21,7 +19,8 @@ import {
|
|
|
21
19
|
resolveLocaleOverride,
|
|
22
20
|
} from '../i18n/locale.js';
|
|
23
21
|
import { CliError, ExitCode } from '../io/exit-codes.js';
|
|
24
|
-
import {
|
|
22
|
+
import { createLog, type Log } from '../io/log.js';
|
|
23
|
+
import { createAssetResolver, errorsOnlyWhenQuiet, loadConfigFor } from './render.js';
|
|
25
24
|
|
|
26
25
|
export interface ServeHandlerOptions {
|
|
27
26
|
args: ParsedArgs;
|
|
@@ -60,11 +59,20 @@ export async function serveHandler(options: ServeHandlerOptions): Promise<void>
|
|
|
60
59
|
);
|
|
61
60
|
}
|
|
62
61
|
|
|
62
|
+
const log = createLog(args.logLevel);
|
|
63
63
|
const port = parsePort(args.port);
|
|
64
64
|
const host = args.host ?? '127.0.0.1';
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
const { config, path: rcPath } = await loadConfigFor(args.positional, cwd);
|
|
66
|
+
// The same resolution as render: flag > .nowlinerc > default, so --width,
|
|
67
|
+
// the rc width, --no-links and --strict apply here too.
|
|
68
|
+
const cliOptions = resolveCliOptions(args, config, { rcPath, cwd });
|
|
69
|
+
cliOptions.assertValid();
|
|
70
|
+
const { theme, today, width, nonWorking } = cliOptions.surface;
|
|
71
|
+
const resolvedLocale = resolveLocaleOverride({
|
|
72
|
+
flag: args.locale,
|
|
73
|
+
env: process.env,
|
|
74
|
+
rc: cliOptions.rcLocale,
|
|
75
|
+
});
|
|
68
76
|
const locale = resolvedLocale.tag;
|
|
69
77
|
const opLocale = operatorLocale(resolvedLocale);
|
|
70
78
|
|
|
@@ -75,7 +83,9 @@ export async function serveHandler(options: ServeHandlerOptions): Promise<void>
|
|
|
75
83
|
throw new CliError(ExitCode.InputError, `nowline: file not found: ${args.positional}`);
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
|
|
86
|
+
// `--asset-root`, else the rc `assetRoot` (relative to the `.nowlinerc`), else
|
|
87
|
+
// the input's directory.
|
|
88
|
+
const assetRoot = cliOptions.assetRoot ?? path.dirname(inputPath);
|
|
79
89
|
|
|
80
90
|
const fileSink = args.output ? path.resolve(cwd, args.output) : undefined;
|
|
81
91
|
|
|
@@ -91,44 +101,94 @@ export async function serveHandler(options: ServeHandlerOptions): Promise<void>
|
|
|
91
101
|
const parse = await parseSource(text, inputPath, { validate: true });
|
|
92
102
|
if (parse.hasErrors) {
|
|
93
103
|
const sources = new Map<string, DiagnosticSource>([[inputPath, parse.source]]);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
const format = (diagnostics: typeof parse.diagnostics): string =>
|
|
105
|
+
formatDiagnostics(diagnostics, 'text', sources, {
|
|
106
|
+
color: false,
|
|
107
|
+
operatorLocale: opLocale,
|
|
108
|
+
});
|
|
109
|
+
// The browser always gets the full report; stderr honors -q.
|
|
110
|
+
const rendered = format(parse.diagnostics);
|
|
98
111
|
lastPayload = { kind: 'error', body: rendered };
|
|
99
|
-
process.stderr.write(`${
|
|
112
|
+
process.stderr.write(`${format(errorsOnlyWhenQuiet(parse.diagnostics, log))}\n`);
|
|
100
113
|
broadcast(clients, 'error', rendered);
|
|
101
114
|
return;
|
|
102
115
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
116
|
+
const directive = readDirectiveLocale(parse.ast);
|
|
117
|
+
const { tag, source } = describeContentLocaleSource(directive, resolvedLocale);
|
|
118
|
+
log.verbose(`nowline: locale=${tag} (${source})`);
|
|
119
|
+
// Included files' text, kept for the code frames of routed diagnostics.
|
|
120
|
+
const sources = new Map<string, DiagnosticSource>([[inputPath, parse.source]]);
|
|
121
|
+
const assetResolver = createAssetResolver(assetRoot);
|
|
122
|
+
const stage = await stageRoadmap({
|
|
123
|
+
source: text,
|
|
124
|
+
sourcePath: inputPath,
|
|
125
|
+
services: getServices(),
|
|
126
|
+
readSource: async (absPath) => {
|
|
127
|
+
const contents = await fs.readFile(absPath, 'utf-8');
|
|
128
|
+
sources.set(absPath, { file: absPath, contents });
|
|
129
|
+
return contents;
|
|
130
|
+
},
|
|
131
|
+
assetResolver,
|
|
132
|
+
document: parse.document,
|
|
133
|
+
layout: { theme, today, locale, width, nonWorking },
|
|
110
134
|
});
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
135
|
+
if (stage.kind === 'invalid') {
|
|
136
|
+
// The parse above found no errors, so the stage cannot disagree.
|
|
137
|
+
throw new Error(`${inputPath} failed validation.`);
|
|
138
|
+
}
|
|
139
|
+
if (stage.kind === 'include-error') {
|
|
140
|
+
// Uncoded include errors keep their one-line form, broadcast
|
|
141
|
+
// only, and take precedence: when any is present, routed
|
|
142
|
+
// (wave-rule) diagnostics are dropped. Otherwise routed
|
|
143
|
+
// diagnostics are reported like validator diagnostics
|
|
144
|
+
// (specs/waves.md §6.1).
|
|
145
|
+
const legacy = stage.resolved.diagnostics
|
|
146
|
+
.filter((d) => d.severity === 'error' && !isRoutedResolveDiagnostic(d))
|
|
114
147
|
.map((d) => `${d.sourcePath}: ${d.message}`)
|
|
115
148
|
.join('\n');
|
|
149
|
+
let msg = legacy;
|
|
150
|
+
if (!legacy && stage.routed.some((d) => d.severity === 'error')) {
|
|
151
|
+
const diagnostics = [
|
|
152
|
+
...parse.diagnostics,
|
|
153
|
+
...stage.routed.map((d) =>
|
|
154
|
+
adaptResolveDiagnostic(
|
|
155
|
+
d,
|
|
156
|
+
d.sourcePath,
|
|
157
|
+
sources.get(d.sourcePath)?.contents,
|
|
158
|
+
),
|
|
159
|
+
),
|
|
160
|
+
];
|
|
161
|
+
const format = (shown: typeof diagnostics): string =>
|
|
162
|
+
formatDiagnostics(shown, 'text', sources, {
|
|
163
|
+
color: false,
|
|
164
|
+
operatorLocale: opLocale,
|
|
165
|
+
});
|
|
166
|
+
process.stderr.write(`${format(errorsOnlyWhenQuiet(diagnostics, log))}\n`);
|
|
167
|
+
msg = format(diagnostics);
|
|
168
|
+
}
|
|
116
169
|
lastPayload = { kind: 'error', body: msg };
|
|
117
170
|
broadcast(clients, 'error', msg);
|
|
118
171
|
return;
|
|
119
172
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
173
|
+
// The logo's load warnings, then the renderer's; `--strict` makes
|
|
174
|
+
// any of them an error, as in render.
|
|
175
|
+
const warnings = [...stage.warnings];
|
|
176
|
+
const svg = await renderSvg(stage.model, {
|
|
177
|
+
assetResolver,
|
|
178
|
+
logo: stage.logo,
|
|
179
|
+
noLinks: args.noLinks,
|
|
180
|
+
strict: args.strict,
|
|
181
|
+
warn: (m) => warnings.push(m),
|
|
124
182
|
});
|
|
183
|
+
if (args.strict && warnings.length > 0) {
|
|
184
|
+
throw new Error(`render warnings (--strict): ${warnings.join('; ')}`);
|
|
185
|
+
}
|
|
186
|
+
for (const m of warnings) log.notice(`warning: ${m}`);
|
|
125
187
|
lastPayload = { kind: 'svg', body: svg };
|
|
126
188
|
if (fileSink) {
|
|
127
189
|
try {
|
|
128
190
|
await fs.writeFile(fileSink, svg);
|
|
129
|
-
|
|
130
|
-
process.stderr.write(`nowline: wrote ${fileSink}\n`);
|
|
131
|
-
}
|
|
191
|
+
log.verbose(`nowline: wrote ${fileSink}`);
|
|
132
192
|
} catch (err) {
|
|
133
193
|
const msg = err instanceof Error ? err.message : String(err);
|
|
134
194
|
process.stderr.write(`nowline: failed to write ${fileSink}: ${msg}\n`);
|
|
@@ -183,7 +243,7 @@ export async function serveHandler(options: ServeHandlerOptions): Promise<void>
|
|
|
183
243
|
process.stdout.write(`nowline serve: http://${host}:${port}\n`);
|
|
184
244
|
|
|
185
245
|
if (args.open) {
|
|
186
|
-
await openBrowser(`http://${host}:${port}
|
|
246
|
+
await openBrowser(`http://${host}:${port}`, log);
|
|
187
247
|
}
|
|
188
248
|
|
|
189
249
|
const shutdown = async (): Promise<void> => {
|
|
@@ -217,39 +277,6 @@ function parsePort(raw: string | undefined): number {
|
|
|
217
277
|
return port;
|
|
218
278
|
}
|
|
219
279
|
|
|
220
|
-
function parseTheme(raw: string | undefined): ThemeName {
|
|
221
|
-
const lower = (raw ?? 'light').toLowerCase();
|
|
222
|
-
if (lower !== 'light' && lower !== 'dark') {
|
|
223
|
-
throw new CliError(ExitCode.InputError, `nowline: invalid --theme "${raw}".`);
|
|
224
|
-
}
|
|
225
|
-
return lower;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Resolve the now-line date from the CLI flags. Mirrors render.ts.
|
|
229
|
-
// Delegates to the shared resolveToday; see render.ts for the full precedence table.
|
|
230
|
-
function resolveNowArg(args: { now?: string; timezone?: string }): Date | undefined {
|
|
231
|
-
let zone: NormalizedZone | undefined;
|
|
232
|
-
if (args.timezone) {
|
|
233
|
-
try {
|
|
234
|
-
zone = normalizeZone(args.timezone);
|
|
235
|
-
} catch (err) {
|
|
236
|
-
if (err instanceof TimezoneError) {
|
|
237
|
-
throw new CliError(ExitCode.InputError, err.message);
|
|
238
|
-
}
|
|
239
|
-
throw err;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const result = resolveToday({ now: args.now, zone });
|
|
243
|
-
if (result === undefined && args.now !== undefined && args.now !== '-') {
|
|
244
|
-
throw new CliError(
|
|
245
|
-
ExitCode.InputError,
|
|
246
|
-
`nowline: invalid --now "${args.now}". ` +
|
|
247
|
-
`Expected YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS[Z|±HH:MM], or "-".`,
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
return result;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
280
|
function sendEvent(res: http.ServerResponse, event: string, data: string): void {
|
|
254
281
|
const lines = data
|
|
255
282
|
.split('\n')
|
|
@@ -279,7 +306,7 @@ function watchFile(target: string, onChange: () => Promise<void>): FSWatcher {
|
|
|
279
306
|
return w;
|
|
280
307
|
}
|
|
281
308
|
|
|
282
|
-
async function openBrowser(url: string): Promise<void> {
|
|
309
|
+
async function openBrowser(url: string, log: Log): Promise<void> {
|
|
283
310
|
const { spawn } = await import('node:child_process');
|
|
284
311
|
const platform = process.platform;
|
|
285
312
|
const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
@@ -288,7 +315,7 @@ async function openBrowser(url: string): Promise<void> {
|
|
|
288
315
|
const child = spawn(command, argList, { stdio: 'ignore', detached: true });
|
|
289
316
|
child.unref();
|
|
290
317
|
} catch {
|
|
291
|
-
|
|
318
|
+
log.notice(`nowline: warning — failed to open browser; visit ${url} manually.`);
|
|
292
319
|
}
|
|
293
320
|
}
|
|
294
321
|
|
|
@@ -296,6 +323,7 @@ const emptySvg =
|
|
|
296
323
|
'<svg xmlns="http://www.w3.org/2000/svg" width="400" height="60"><text x="10" y="30" font-family="system-ui" font-size="14" fill="#999">no output yet</text></svg>';
|
|
297
324
|
|
|
298
325
|
function shellHtml(theme: ThemeName): string {
|
|
326
|
+
// Only dark needs dark chrome; grayscale sits on the light page.
|
|
299
327
|
const bg = theme === 'dark' ? '#121212' : '#ffffff';
|
|
300
328
|
const fg = theme === 'dark' ? '#e0e0e0' : '#212121';
|
|
301
329
|
return `<!doctype html>
|
package/src/diagnostics/adapt.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
extractSuggestion,
|
|
3
|
+
inferCodeFromMessage,
|
|
3
4
|
type LangiumLikeDiagnostic,
|
|
4
5
|
type LexerErrorLike,
|
|
5
6
|
type ParserErrorLike,
|
|
7
|
+
type ResolveDiagnostic,
|
|
6
8
|
resolveDiagnosticCode,
|
|
7
9
|
} from '@nowline/core';
|
|
8
10
|
import type { CliDiagnostic, DiagnosticSeverity, LocalizedMessageData } from './model.js';
|
|
@@ -87,3 +89,63 @@ export function adaptLexerError(err: LexerErrorLike, file: string): CliDiagnosti
|
|
|
87
89
|
function mapSeverity(severity: number | undefined): DiagnosticSeverity {
|
|
88
90
|
return severity === 2 ? 'warning' : 'error';
|
|
89
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Adapt a routed include-resolver diagnostic (a wave rule or an included
|
|
95
|
+
* file's NL.E0302). With a `range`, the column and span are the validator's
|
|
96
|
+
* for the same finding. Without one, the diagnostic carries only a 0-based
|
|
97
|
+
* line: `contents` is the text of the file it points into, when known, and
|
|
98
|
+
* the span covers the line's text, so the code frame still underlines the
|
|
99
|
+
* declaration. A coded diagnostic keeps `{ code, args }` in `data` for
|
|
100
|
+
* re-localization. An included file's syntax error is adapted like that
|
|
101
|
+
* file's own (`lex-error` / `parse-error`).
|
|
102
|
+
*/
|
|
103
|
+
export function adaptResolveDiagnostic(
|
|
104
|
+
diag: ResolveDiagnostic,
|
|
105
|
+
file: string,
|
|
106
|
+
contents?: string,
|
|
107
|
+
): CliDiagnostic {
|
|
108
|
+
// An included file's syntax error reads as it does when that file is
|
|
109
|
+
// checked on its own.
|
|
110
|
+
if (diag.syntax?.origin === 'lexer') return adaptLexerError(diag.syntax.error, file);
|
|
111
|
+
if (diag.syntax?.origin === 'parser') return adaptParserError(diag.syntax.error, file);
|
|
112
|
+
return {
|
|
113
|
+
file,
|
|
114
|
+
...resolveLocation(diag, contents),
|
|
115
|
+
severity: diag.severity,
|
|
116
|
+
// WV8 is uncoded; label it the way the validator's copy is labelled.
|
|
117
|
+
code: diag.code ?? inferCodeFromMessage(diag.message),
|
|
118
|
+
message: diag.message,
|
|
119
|
+
suggestion: extractSuggestion(diag.message),
|
|
120
|
+
data: diag.code === undefined ? undefined : { code: diag.code, args: diag.args ?? [] },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveLocation(
|
|
125
|
+
diag: ResolveDiagnostic,
|
|
126
|
+
contents: string | undefined,
|
|
127
|
+
): Pick<CliDiagnostic, 'line' | 'column' | 'span'> {
|
|
128
|
+
if (diag.range) {
|
|
129
|
+
const line = diag.range.start.line + 1;
|
|
130
|
+
const column = diag.range.start.character + 1;
|
|
131
|
+
return {
|
|
132
|
+
line,
|
|
133
|
+
column,
|
|
134
|
+
span: {
|
|
135
|
+
start: { line, column },
|
|
136
|
+
end: { line: diag.range.end.line + 1, column: diag.range.end.character + 1 },
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const line = (diag.line ?? 0) + 1;
|
|
141
|
+
const text = contents?.split(/\r?\n/)[line - 1]?.trimEnd() ?? '';
|
|
142
|
+
const column = text.length - text.trimStart().length + 1;
|
|
143
|
+
return {
|
|
144
|
+
line,
|
|
145
|
+
column,
|
|
146
|
+
span:
|
|
147
|
+
text.trim() === ''
|
|
148
|
+
? undefined
|
|
149
|
+
: { start: { line, column }, end: { line, column: text.length + 1 } },
|
|
150
|
+
};
|
|
151
|
+
}
|
package/src/diagnostics/model.ts
CHANGED
|
@@ -35,7 +35,7 @@ export interface CliDiagnostic {
|
|
|
35
35
|
* Stable code + args as captured by the validator. Present on
|
|
36
36
|
* messages that flow through the `tr()` registry; absent for
|
|
37
37
|
* literal-English validator strings, parser/lexer errors, and
|
|
38
|
-
* include-resolution diagnostics. The CLI re-formats `message`
|
|
38
|
+
* uncoded include-resolution diagnostics. The CLI re-formats `message`
|
|
39
39
|
* from this when an operator locale is supplied to
|
|
40
40
|
* `formatDiagnostics`.
|
|
41
41
|
*/
|
package/src/generated/version.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Auto-generated by scripts/bundle-templates.mjs. Do not edit by hand.
|
|
2
2
|
|
|
3
|
-
export const CLI_VERSION = "0.
|
|
3
|
+
export const CLI_VERSION = "0.9.0";
|
|
4
4
|
|
|
5
5
|
export interface CliBuild {
|
|
6
6
|
/** Short git SHA at build time, or empty when not in a git checkout. */
|
|
@@ -12,7 +12,7 @@ export interface CliBuild {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const CLI_BUILD: CliBuild = {
|
|
15
|
-
"sha": "
|
|
15
|
+
"sha": "06f9e6f",
|
|
16
16
|
"isRelease": true,
|
|
17
17
|
"isDirty": false
|
|
18
18
|
};
|
package/src/i18n/locale.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NowlineFile } from '@nowline/core';
|
|
2
|
+
import { findProp } from '@nowline/core/internal';
|
|
2
3
|
|
|
3
4
|
// Resolve the operator-locale override from CLI flag, environment, and
|
|
4
5
|
// `.nowlinerc`. See `specs/localization.md` for the two-chain model.
|
|
@@ -100,12 +101,7 @@ export function describeContentLocaleSource(
|
|
|
100
101
|
* absent, malformed, or omits the property.
|
|
101
102
|
*/
|
|
102
103
|
export function readDirectiveLocale(file: NowlineFile | undefined): string | undefined {
|
|
103
|
-
|
|
104
|
-
return prop?.value || undefined;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function stripColon(key: string): string {
|
|
108
|
-
return key.endsWith(':') ? key.slice(0, -1) : key;
|
|
104
|
+
return findProp(file?.directive?.properties ?? [], 'locale')?.value || undefined;
|
|
109
105
|
}
|
|
110
106
|
|
|
111
107
|
/**
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { parseArgv } from './cli/args.js';
|
|
|
3
3
|
import { renderHelp, renderVersion } from './cli/help.js';
|
|
4
4
|
import { initHandler } from './commands/init.js';
|
|
5
5
|
import { mcpHandler } from './commands/mcp.js';
|
|
6
|
+
import { mcpInstallHandler } from './commands/mcp-install.js';
|
|
6
7
|
import { renderHandler } from './commands/render.js';
|
|
7
8
|
import { serveHandler } from './commands/serve.js';
|
|
8
9
|
import { CliError, ExitCode } from './io/exit-codes.js';
|
|
@@ -33,6 +34,8 @@ async function run(): Promise<number> {
|
|
|
33
34
|
await serveHandler({ args: parsed });
|
|
34
35
|
} else if (parsed.mode === 'mcp') {
|
|
35
36
|
await mcpHandler({ args: parsed });
|
|
37
|
+
} else if (parsed.mode === 'mcp-install' || parsed.mode === 'mcp-uninstall') {
|
|
38
|
+
await mcpInstallHandler({ args: parsed });
|
|
36
39
|
} else {
|
|
37
40
|
await renderHandler({ args: parsed });
|
|
38
41
|
}
|
package/src/io/config.ts
CHANGED
package/src/io/log.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type LogLevel = 'verbose' | 'quiet' | 'normal';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The one place `-v` / `-q` decide what reaches stderr. Errors (usage errors,
|
|
5
|
+
* failed writes, validation errors) and machine output (JSON diagnostics)
|
|
6
|
+
* bypass this and always print; everything else goes through here so every
|
|
7
|
+
* command honors the flags the same way.
|
|
8
|
+
*/
|
|
9
|
+
export interface Log {
|
|
10
|
+
/** `-v` only: progress lines such as `nowline: wrote <path>`. */
|
|
11
|
+
verbose(line: string): void;
|
|
12
|
+
/** Non-error notices (warnings, lossy summaries, banners). Dropped under `-q`. */
|
|
13
|
+
notice(line: string): void;
|
|
14
|
+
/** True under `-q`; callers that filter mixed output (diagnostics) check it. */
|
|
15
|
+
readonly quiet: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createLog(
|
|
19
|
+
level: LogLevel,
|
|
20
|
+
write: (chunk: string) => void = (chunk) => {
|
|
21
|
+
process.stderr.write(chunk);
|
|
22
|
+
},
|
|
23
|
+
): Log {
|
|
24
|
+
return {
|
|
25
|
+
verbose: (line) => {
|
|
26
|
+
if (level === 'verbose') write(`${line}\n`);
|
|
27
|
+
},
|
|
28
|
+
notice: (line) => {
|
|
29
|
+
if (level !== 'quiet') write(`${line}\n`);
|
|
30
|
+
},
|
|
31
|
+
quiet: level === 'quiet',
|
|
32
|
+
};
|
|
33
|
+
}
|