@nseng-ai/kernel 0.1.2

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.
@@ -0,0 +1,668 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ ClinkrGroup,
7
+ clinkrFormatFromArgs,
8
+ ok,
9
+ renderCapabilitiesForTerminal,
10
+ type Caps,
11
+ type ClinkrCommandSpec,
12
+ type ClinkrDynamicCompletionRequest,
13
+ } from "@nseng-ai/clinkr";
14
+ import { renderCompletionCandidatesNewline } from "@nseng-ai/clinkr/completion";
15
+ import { rawCommand } from "@nseng-ai/clinkr/raw";
16
+ import { defineCli, readStdin, type CliEntrypointDeps } from "@nseng-ai/foundation/cli-runtime";
17
+ import { optionalEntries, optionalEntry, resolveHomeDir } from "@nseng-ai/foundation/primitives";
18
+
19
+ import {
20
+ buildNsCompletionScript,
21
+ renderNsCompletionScriptResult,
22
+ nsCompletionScriptResultSchema,
23
+ } from "./completion.ts";
24
+ import {
25
+ createRealNsCommandContext,
26
+ createNsCliInteraction,
27
+ type NsCliContext,
28
+ } from "./context.ts";
29
+ import {
30
+ renderNsShellInstall,
31
+ renderNsShellShow,
32
+ runNsShellInstall,
33
+ runNsShellShow,
34
+ nsShellInstallRequestSchema,
35
+ nsShellInstallResultSchema,
36
+ nsShellShowRequestSchema,
37
+ nsShellShowResultSchema,
38
+ } from "./shell.ts";
39
+ import { createCliCommandIo, noopNsProgress } from "../runtime/command-io.ts";
40
+ import {
41
+ classifyExtensionDiagnosticsForInvocation,
42
+ commandInfosForSelectedCommand,
43
+ formatExtensionErrorDiagnostics,
44
+ formatExtensionWarningDiagnostics,
45
+ loadListingCommandInfos,
46
+ loadNsCommandCatalog,
47
+ loadSelectedNsCommand,
48
+ type ExtensionCommandCandidate,
49
+ type LoadNsCommandCatalogOptions,
50
+ type PreinstalledNsCommandCatalogLoader,
51
+ type SelectedNsCommandLoadResult,
52
+ type NsCommandCatalog,
53
+ } from "../extensions/registry.ts";
54
+ import type {
55
+ RenderCapabilities,
56
+ NsCommand,
57
+ NsConfirmPrompt,
58
+ NsExtensionApi,
59
+ NsOutputStream,
60
+ NsProgressPhaseListener,
61
+ } from "../sdk/index.ts";
62
+ import {
63
+ commandDisplayName,
64
+ commandKey,
65
+ commandLeafName,
66
+ commandPathMatches,
67
+ commandSegments,
68
+ executeNsCommand,
69
+ formatUnknownError,
70
+ listStaticNsCommandInfos,
71
+ validateNsClinkrExit,
72
+ type NsCommandInfo,
73
+ type NsCommandCliInfo,
74
+ type NsCommandPath,
75
+ } from "../extensions/command-registry.ts";
76
+
77
+ export type { NsCliContext } from "./context.ts";
78
+ export type { NsCommandInfo } from "../extensions/command-registry.ts";
79
+ export type {
80
+ PreinstalledNsCommandCatalogEntry,
81
+ PreinstalledNsCommandCatalogLoader,
82
+ } from "../extensions/registry.ts";
83
+ export {
84
+ repoLocalNsCommandDescriptorToPreinstalledCatalogEntry,
85
+ repoLocalNsExtensionToPreinstalledCatalog,
86
+ } from "../extensions/repo-local-catalog.ts";
87
+
88
+ interface NsCliExtensionRegistryDeps {
89
+ loadCommandCatalog?: (options: LoadNsCommandCatalogOptions) => Promise<NsCommandCatalog>;
90
+ loadSelectedCommand?: (
91
+ candidate: ExtensionCommandCandidate,
92
+ ) => Promise<SelectedNsCommandLoadResult>;
93
+ }
94
+
95
+ export interface NsCliDeps extends Pick<CliEntrypointDeps, "cwd" | "env" | "stdout" | "stderr"> {
96
+ context?: NsExtensionApi;
97
+ homeDir?: string;
98
+ onOutput?: (stream: NsOutputStream, text: string) => void;
99
+ onProgress?: NsProgressPhaseListener;
100
+ confirm?: NsConfirmPrompt;
101
+ preinstalledCommandCatalog?: PreinstalledNsCommandCatalogLoader;
102
+ extensionRegistry?: NsCliExtensionRegistryDeps;
103
+ }
104
+
105
+ export interface BuildNsCliOptions {
106
+ commandInfos?: readonly NsCommandCliInfo[];
107
+ selectedCommand?: NsCommand;
108
+ selectedCommandPath?: NsCommandPath;
109
+ }
110
+
111
+ interface NsCliBuildState {
112
+ commandInfos: readonly NsCommandCliInfo[];
113
+ selectedCommand?: NsCommand;
114
+ selectedCommandPath?: NsCommandPath;
115
+ }
116
+
117
+ const entry = defineCli<NsCliContext, NsCliDeps, NsCliBuildState>({
118
+ metaUrl: new URL("../cli.ts", import.meta.url).href,
119
+ runtime: "typescript",
120
+ description: "ns tools.",
121
+ prepareRun: async ({ args, deps, cwd, env, stdout, stderr, io }) => {
122
+ const injectedContext = deps.context;
123
+ const resolvedStdout = deps.stdout ?? injectedContext?.stdout ?? stdout;
124
+ const resolvedStderr = deps.stderr ?? injectedContext?.stderr ?? stderr;
125
+ const resolvedCwd = deps.cwd ?? injectedContext?.cwd ?? cwd;
126
+ const resolvedEnv = deps.env ?? injectedContext?.env ?? env;
127
+ const homeDir = resolveHomeDir(deps.homeDir, resolvedEnv) ?? injectedContext?.homeDir;
128
+ const commandCatalog = await (
129
+ deps.extensionRegistry?.loadCommandCatalog ?? loadNsCommandCatalog
130
+ )({
131
+ cwd: resolvedCwd,
132
+ env: resolvedEnv,
133
+ ...optionalEntry("homeDir", homeDir),
134
+ ...optionalEntry("preinstalledCommandCatalog", deps.preinstalledCommandCatalog),
135
+ });
136
+ if (isCompletionResolverInvocation(args)) {
137
+ return await handleCompletionResolverInvocation({
138
+ args,
139
+ commandCatalog,
140
+ cwd: resolvedCwd,
141
+ env: resolvedEnv,
142
+ ...optionalEntry("homeDir", homeDir),
143
+ stdout: resolvedStdout,
144
+ stderr: resolvedStderr,
145
+ ...optionalEntries({
146
+ loadSelectedCommand: deps.extensionRegistry?.loadSelectedCommand,
147
+ injectedContext,
148
+ onOutput: deps.onOutput,
149
+ onProgress: deps.onProgress,
150
+ confirm: deps.confirm,
151
+ caps: io.caps,
152
+ }),
153
+ });
154
+ }
155
+ const isCompletionScriptRequest = isCompletionScriptInvocation(args);
156
+ const selectedCommandKey = isCompletionScriptRequest
157
+ ? undefined
158
+ : requestedCommandKey(args, commandCatalog.commandInfos);
159
+ const selectedCandidate =
160
+ selectedCommandKey === undefined
161
+ ? undefined
162
+ : commandCatalog.candidates.get(selectedCommandKey);
163
+ const diagnosticClassification = classifyExtensionDiagnosticsForInvocation({
164
+ diagnostics: commandCatalog.diagnostics,
165
+ requestedCommandName: selectedCommandKey,
166
+ selectedCandidate,
167
+ });
168
+ if (!isCompletionScriptRequest && diagnosticClassification.fatal.length > 0) {
169
+ resolvedStderr(`${formatExtensionErrorDiagnostics(diagnosticClassification.fatal)}\n`);
170
+ return { type: "handled", exitCode: 2 };
171
+ }
172
+
173
+ let commandInfos = commandCatalog.commandInfos;
174
+ let listingDiagnostics: typeof diagnosticClassification.warnings = [];
175
+ if (
176
+ !isCompletionScriptRequest &&
177
+ selectedCommandKey === undefined &&
178
+ !isStaticTopLevelMetadataRequest(args)
179
+ ) {
180
+ const loadedListing = await loadListingCommandInfos(commandCatalog);
181
+ commandInfos = loadedListing.commandInfos;
182
+ listingDiagnostics = loadedListing.diagnostics;
183
+ }
184
+ const warnings = isCompletionScriptRequest
185
+ ? []
186
+ : [...diagnosticClassification.warnings, ...listingDiagnostics];
187
+ if (warnings.length > 0) {
188
+ resolvedStderr(`${formatExtensionWarningDiagnostics(warnings)}\n`);
189
+ }
190
+
191
+ const selectedCommandResolution = await resolveSelectedNsCommand({
192
+ candidate: selectedCandidate,
193
+ commandInfos,
194
+ ...optionalEntry("loadSelectedCommand", deps.extensionRegistry?.loadSelectedCommand),
195
+ stderr: resolvedStderr,
196
+ failureExitCode: 2,
197
+ });
198
+ if (!selectedCommandResolution.ok) return selectedCommandResolution.handled;
199
+
200
+ const contextWithIO = await buildNsCliContext({
201
+ args,
202
+ cwd: resolvedCwd,
203
+ env: resolvedEnv,
204
+ ...optionalEntry("homeDir", homeDir),
205
+ stdout: resolvedStdout,
206
+ stderr: resolvedStderr,
207
+ ...optionalEntries({
208
+ injectedContext,
209
+ onOutput: deps.onOutput,
210
+ onProgress: deps.onProgress,
211
+ confirm: deps.confirm,
212
+ caps: io.caps,
213
+ }),
214
+ });
215
+ return {
216
+ type: "run",
217
+ context: contextWithIO,
218
+ buildState: selectedCommandResolution.resolution,
219
+ };
220
+ },
221
+ configureCli: ({ root, buildState }) => {
222
+ const groups = new Map<string, ClinkrGroup<NsCliContext>>();
223
+ for (const commandInfo of buildState.commandInfos) {
224
+ const parent = groupForCommand(root, groups, commandInfo);
225
+ const selectedCommand =
226
+ buildState.selectedCommandPath !== undefined &&
227
+ commandPathMatches(buildState.selectedCommandPath, commandInfo)
228
+ ? buildState.selectedCommand
229
+ : undefined;
230
+ const schema = selectedCommand?.schema ?? z.object({});
231
+ const commandOptions = {
232
+ name: cliLeafCommandName(commandInfo),
233
+ description: commandInfo.fullDescription,
234
+ summary: commandInfo.description,
235
+ schema,
236
+ ...(selectedCommand?.positionals === undefined
237
+ ? {}
238
+ : { positionals: selectedCommand.positionals }),
239
+ ...(selectedCommand?.options === undefined ? {} : { options: selectedCommand.options }),
240
+ ...(selectedCommand?.completionProvider === undefined
241
+ ? {}
242
+ : {
243
+ completionProvider: (ctx: NsCliContext, request: ClinkrDynamicCompletionRequest) =>
244
+ selectedCommand.completionProvider?.(ctx.context, request) ?? [],
245
+ }),
246
+ };
247
+ if (selectedCommand?.resultSchema !== undefined) {
248
+ parent.command({
249
+ ...commandOptions,
250
+ resultSchema: selectedCommand.resultSchema,
251
+ ...(selectedCommand.renderHuman === undefined
252
+ ? {}
253
+ : { renderHuman: selectedCommand.renderHuman }),
254
+ ...(selectedCommand.renderMarkdown === undefined
255
+ ? {}
256
+ : { renderMarkdown: selectedCommand.renderMarkdown }),
257
+ handler: async (ctx, request) => {
258
+ const result = await selectedCommand.run(ctx.context, request);
259
+ return validateNsClinkrExit(result, selectedCommand.name);
260
+ },
261
+ });
262
+ continue;
263
+ }
264
+ parent.command(
265
+ rawCommand({
266
+ ...commandOptions,
267
+ run: async (ctx, request) => {
268
+ const result =
269
+ selectedCommand === undefined
270
+ ? {
271
+ ok: false as const,
272
+ exitCode: 2,
273
+ message: `Unknown ns command: ${commandDisplayName(commandInfo)}`,
274
+ }
275
+ : await executeNsCommand(ctx.context, selectedCommand, request);
276
+ writeNsResultOutput(result, ctx);
277
+ return result.ok ? 0 : result.exitCode;
278
+ },
279
+ }),
280
+ );
281
+ }
282
+ root.group(buildNsShellGroup());
283
+ root.group(buildNsCompletionGroup());
284
+ },
285
+ });
286
+
287
+ export function buildCli(options: BuildNsCliOptions = {}): ClinkrGroup<NsCliContext> {
288
+ return entry.buildCli({
289
+ commandInfos: options.commandInfos ?? listStaticNsCommandInfos(),
290
+ ...optionalEntries({
291
+ selectedCommand: options.selectedCommand,
292
+ selectedCommandPath: options.selectedCommandPath,
293
+ }),
294
+ });
295
+ }
296
+
297
+ export function listNsCommands(): NsCommandInfo[] {
298
+ return listStaticNsCommandInfos().map(({ group, name, description }) => ({
299
+ ...(group === undefined ? {} : { group }),
300
+ name,
301
+ description,
302
+ }));
303
+ }
304
+
305
+ export async function runCli(args: readonly string[], deps: NsCliDeps = {}): Promise<number> {
306
+ return await entry.run(args, deps);
307
+ }
308
+
309
+ async function handleCompletionResolverInvocation(options: {
310
+ args: readonly string[];
311
+ commandCatalog: NsCommandCatalog;
312
+ loadSelectedCommand?: (
313
+ candidate: ExtensionCommandCandidate,
314
+ ) => Promise<SelectedNsCommandLoadResult>;
315
+ cwd: string;
316
+ env: NodeJS.ProcessEnv;
317
+ homeDir?: string;
318
+ stdout: (text: string) => void;
319
+ stderr: (text: string) => void;
320
+ injectedContext?: NsExtensionApi;
321
+ onOutput?: (stream: NsOutputStream, text: string) => void;
322
+ onProgress?: NsProgressPhaseListener;
323
+ confirm?: NsConfirmPrompt;
324
+ caps?: Caps;
325
+ }): Promise<{ type: "handled"; exitCode: number }> {
326
+ const words = completionResolverWords(options.args);
327
+ const selectedCommandKey = requestedCommandKey(words, options.commandCatalog.commandInfos);
328
+ const selectedCandidate =
329
+ selectedCommandKey === undefined
330
+ ? undefined
331
+ : options.commandCatalog.candidates.get(selectedCommandKey);
332
+ const selectedCommandResolution = await resolveSelectedNsCommand({
333
+ candidate: selectedCandidate,
334
+ commandInfos: options.commandCatalog.commandInfos,
335
+ ...optionalEntry("loadSelectedCommand", options.loadSelectedCommand),
336
+ stderr: options.stderr,
337
+ failureExitCode: 0,
338
+ });
339
+ if (!selectedCommandResolution.ok) return selectedCommandResolution.handled;
340
+ const context = await buildNsCliContext({
341
+ args: options.args,
342
+ cwd: options.cwd,
343
+ env: options.env,
344
+ ...optionalEntry("homeDir", options.homeDir),
345
+ stdout: options.stdout,
346
+ stderr: options.stderr,
347
+ ...optionalEntries({
348
+ injectedContext: options.injectedContext,
349
+ onOutput: options.onOutput,
350
+ onProgress: options.onProgress,
351
+ confirm: options.confirm,
352
+ caps: options.caps,
353
+ }),
354
+ });
355
+ const candidates = await buildCli(selectedCommandResolution.resolution).completeAsync(
356
+ { words },
357
+ {
358
+ context,
359
+ onDynamicCompletionError: (error) => {
360
+ options.stderr(`completion provider failed: ${formatUnknownError(error)}\n`);
361
+ },
362
+ },
363
+ );
364
+ options.stdout(renderCompletionCandidatesNewline(candidates));
365
+ return { type: "handled", exitCode: 0 };
366
+ }
367
+
368
+ async function resolveSelectedNsCommand(options: {
369
+ candidate: ExtensionCommandCandidate | undefined;
370
+ commandInfos: readonly NsCommandCliInfo[];
371
+ loadSelectedCommand?: (
372
+ candidate: ExtensionCommandCandidate,
373
+ ) => Promise<SelectedNsCommandLoadResult>;
374
+ stderr: (text: string) => void;
375
+ failureExitCode: number;
376
+ }): Promise<
377
+ | { ok: true; resolution: NsCliBuildState }
378
+ | { ok: false; handled: { type: "handled"; exitCode: number } }
379
+ > {
380
+ const selectedCommandLoader = options.loadSelectedCommand ?? loadSelectedNsCommand;
381
+ const loadedSelectedCommand =
382
+ options.candidate === undefined ? undefined : await selectedCommandLoader(options.candidate);
383
+ if (loadedSelectedCommand !== undefined && !loadedSelectedCommand.ok) {
384
+ options.stderr(`${formatExtensionErrorDiagnostics([loadedSelectedCommand.diagnostic])}\n`);
385
+ return { ok: false, handled: { type: "handled", exitCode: options.failureExitCode } };
386
+ }
387
+ const selectedCommand = loadedSelectedCommand?.command;
388
+ const selectedSource = loadedSelectedCommand?.source;
389
+ const selectedPath = loadedSelectedCommand?.path;
390
+ const commandInfos = commandInfosForSelectedCommand(
391
+ options.commandInfos,
392
+ selectedCommand === undefined || selectedSource === undefined || selectedPath === undefined
393
+ ? undefined
394
+ : { command: selectedCommand, source: selectedSource, path: selectedPath },
395
+ );
396
+ return {
397
+ ok: true,
398
+ resolution: {
399
+ commandInfos,
400
+ ...optionalEntries({ selectedCommand, selectedCommandPath: selectedPath }),
401
+ },
402
+ };
403
+ }
404
+
405
+ async function buildNsCliContext(options: {
406
+ args: readonly string[];
407
+ cwd: string;
408
+ env: NodeJS.ProcessEnv;
409
+ homeDir?: string;
410
+ stdout: (text: string) => void;
411
+ stderr: (text: string) => void;
412
+ injectedContext?: NsExtensionApi;
413
+ onOutput?: (stream: NsOutputStream, text: string) => void;
414
+ onProgress?: NsProgressPhaseListener;
415
+ confirm?: NsConfirmPrompt;
416
+ caps?: Caps;
417
+ }): Promise<NsCliContext> {
418
+ const baseContext =
419
+ options.injectedContext ??
420
+ createRealNsCommandContext({
421
+ cwd: options.cwd,
422
+ env: options.env,
423
+ ...optionalEntry("homeDir", options.homeDir),
424
+ });
425
+ const onOutput = options.onOutput ?? baseContext.onOutput;
426
+ const confirm = options.confirm ?? baseContext.confirm;
427
+ const stdin = baseContext.stdin ?? readStdin;
428
+ const renderCapabilities: RenderCapabilities = renderCapabilitiesForTerminal(options.caps);
429
+ const contextExtensions = baseContext.extensions;
430
+ const commandIo = createCliCommandIo({
431
+ stdout: options.stdout,
432
+ stderr: options.stderr,
433
+ ...optionalEntry("onOutput", onOutput),
434
+ });
435
+ const context: NsExtensionApi = {
436
+ cwd: options.cwd,
437
+ env: options.env,
438
+ ...optionalEntry("homeDir", options.homeDir),
439
+ textGenerator: baseContext.textGenerator,
440
+ commandIo,
441
+ progress:
442
+ options.onProgress === undefined
443
+ ? noopNsProgress
444
+ : { isLive: true, phase: options.onProgress },
445
+ renderCapabilities,
446
+ outputFormat: clinkrFormatFromArgs(options.args),
447
+ exec: baseContext.exec.bind(baseContext),
448
+ stdout: options.stdout,
449
+ stderr: options.stderr,
450
+ stdin,
451
+ ...optionalEntries({ onOutput, confirm, extensions: contextExtensions }),
452
+ };
453
+ return {
454
+ context,
455
+ cwd: options.cwd,
456
+ env: options.env,
457
+ interaction: createNsCliInteraction({ stderr: options.stderr }),
458
+ stdout: options.stdout,
459
+ stderr: options.stderr,
460
+ };
461
+ }
462
+
463
+ function isCompletionResolverInvocation(args: readonly string[]): boolean {
464
+ return args[0] === "completion" && args[1] === NS_EXEC_GROUP_NAME && args[2] === "resolve";
465
+ }
466
+
467
+ function isCompletionScriptInvocation(args: readonly string[]): boolean {
468
+ return args[0] === "completion" && ["bash", "zsh", "fish"].includes(args[1] ?? "");
469
+ }
470
+
471
+ function completionResolverWords(args: readonly string[]): readonly string[] {
472
+ const resolverArgs = args.slice(3);
473
+ if (resolverArgs[0] !== "--") return resolverArgs;
474
+ return resolverArgs.slice(1);
475
+ }
476
+
477
+ function requestedCommandKey(
478
+ args: readonly string[],
479
+ commandInfos: readonly NsCommandCliInfo[],
480
+ ): string | undefined {
481
+ const commandArgs = args.filter((arg) => !arg.startsWith("-"));
482
+ if (commandArgs.length === 0) return undefined;
483
+
484
+ const candidates = commandInfos
485
+ .map((commandInfo) => ({
486
+ commandInfo,
487
+ displaySegments: displaySegmentsForCommand(commandInfo),
488
+ }))
489
+ .filter(({ displaySegments }) => pathPrefixMatches(commandArgs, displaySegments))
490
+ .sort((left, right) => right.displaySegments.length - left.displaySegments.length);
491
+ const selected = candidates[0];
492
+ if (selected === undefined) return commandArgs[0];
493
+ if (commandArgs.length < selected.displaySegments.length) return undefined;
494
+ return commandKey(selected.commandInfo);
495
+ }
496
+
497
+ const NS_EXEC_GROUP_NAME = "exec";
498
+ const NS_BUILT_IN_HELP_GROUP = "Built-in Commands:";
499
+ const NS_EXTENSION_HELP_GROUP = "Extensions:";
500
+ // Dynamic ns extensions are one group deep today. A grouped command named
501
+ // `exec-<name>` is mounted as hidden `ns <group> exec <name>` so agent-only
502
+ // operations keep the same nested exec contract as preinstalled Clinkr groups.
503
+ const NS_EXEC_COMMAND_PREFIX = "exec-";
504
+
505
+ function isGroupedExecCommand(commandInfo: NsCommandCliInfo): boolean {
506
+ return commandInfo.group !== undefined && commandInfo.name.startsWith(NS_EXEC_COMMAND_PREFIX);
507
+ }
508
+
509
+ function cliLeafCommandName(commandInfo: NsCommandCliInfo): string {
510
+ if (commandInfo.segments !== undefined) return commandLeafName(commandInfo);
511
+ if (!isGroupedExecCommand(commandInfo)) return commandInfo.name;
512
+ return commandInfo.name.slice(NS_EXEC_COMMAND_PREFIX.length);
513
+ }
514
+
515
+ function buildNsCompletionGroup(): ClinkrGroup<NsCliContext> {
516
+ const completion = new ClinkrGroup<NsCliContext>({
517
+ name: "completion",
518
+ description: "Print shell completion setup scripts.",
519
+ helpGroup: NS_BUILT_IN_HELP_GROUP,
520
+ });
521
+ for (const shell of ["bash", "zsh", "fish"] as const) {
522
+ completion.command({
523
+ name: shell,
524
+ description: `Print ${shell} completion setup for ns.`,
525
+ schema: z.object({}),
526
+ resultSchema: nsCompletionScriptResultSchema,
527
+ handler: async () => ok(buildNsCompletionScript(shell)),
528
+ renderHuman: renderNsCompletionScriptResult,
529
+ });
530
+ }
531
+ const exec = new ClinkrGroup<NsCliContext>({
532
+ name: NS_EXEC_GROUP_NAME,
533
+ description: "Shell completion resolver operations.",
534
+ isHidden: true,
535
+ });
536
+ exec.command(
537
+ rawCommand({
538
+ name: "resolve",
539
+ description: "Resolve newline-delimited shell completion candidates.",
540
+ schema: z.object({ words: z.array(z.string()).default([]) }),
541
+ positionals: { words: { position: 0 } },
542
+ run: async () => 0,
543
+ }),
544
+ );
545
+ completion.group(exec);
546
+ return completion;
547
+ }
548
+
549
+ function displaySegmentsForCommand(commandInfo: NsCommandCliInfo): readonly string[] {
550
+ if (commandInfo.segments !== undefined) return commandInfo.segments;
551
+ if (!isGroupedExecCommand(commandInfo)) return commandSegments(commandInfo);
552
+ return [commandInfo.group ?? "", NS_EXEC_GROUP_NAME, cliLeafCommandName(commandInfo)].filter(
553
+ (segment) => segment !== "",
554
+ );
555
+ }
556
+
557
+ function pathPrefixMatches(args: readonly string[], path: readonly string[]): boolean {
558
+ return path.every((segment, index) => args[index] === segment);
559
+ }
560
+
561
+ type ShellCommandSchema = z.ZodObject<{ shell: z.ZodOptional<z.ZodString> }>;
562
+
563
+ type ShellCommandSpec<T> = Omit<
564
+ ClinkrCommandSpec<NsCliContext, ShellCommandSchema, T>,
565
+ "name" | "description"
566
+ >;
567
+
568
+ // Keep this shell command face kernel-owned instead of delegating parent-shell integration
569
+ // to one extension. The reusable abstraction we expect here is future typed shell
570
+ // contributions rendered inside one managed shell integration, not extension-owned rc-file
571
+ // mutation or command helpers that each install their own wrapper.
572
+ function buildNsShellGroup(): ClinkrGroup<NsCliContext> {
573
+ const shell = new ClinkrGroup<NsCliContext>({
574
+ name: "shell",
575
+ description: "Show or install parent-shell integration.",
576
+ helpGroup: NS_BUILT_IN_HELP_GROUP,
577
+ });
578
+ shell.command({
579
+ name: "show",
580
+ description: "Print the parent-shell wrapper script.",
581
+ ...withShellOption({
582
+ schema: nsShellShowRequestSchema,
583
+ resultSchema: nsShellShowResultSchema,
584
+ handler: runNsShellShow,
585
+ renderHuman: renderNsShellShow,
586
+ }),
587
+ });
588
+ shell.command({
589
+ name: "install",
590
+ description: "Install the parent-shell wrapper in the detected or selected rc file.",
591
+ schema: nsShellInstallRequestSchema,
592
+ options: { shell: { short: "-s" }, yes: { short: "-y" } },
593
+ resultSchema: nsShellInstallResultSchema,
594
+ handler: runNsShellInstall,
595
+ renderHuman: renderNsShellInstall,
596
+ });
597
+ return shell;
598
+ }
599
+
600
+ function withShellOption<T>(spec: ShellCommandSpec<T>): ShellCommandSpec<T> {
601
+ return {
602
+ ...spec,
603
+ options: { shell: { short: "-s" }, ...(spec.options ?? {}) },
604
+ };
605
+ }
606
+
607
+ function groupForCommand(
608
+ root: ClinkrGroup<NsCliContext>,
609
+ groupCache: Map<string, ClinkrGroup<NsCliContext>>,
610
+ commandInfo: NsCommandCliInfo,
611
+ ): ClinkrGroup<NsCliContext> {
612
+ const displaySegments = displaySegmentsForCommand(commandInfo);
613
+ const parentSegments = displaySegments.slice(0, -1);
614
+ let parent = root;
615
+ for (let index = 0; index < parentSegments.length; index += 1) {
616
+ const segment = parentSegments[index];
617
+ if (segment === undefined) continue;
618
+ const groupKey = parentSegments.slice(0, index + 1).join("/");
619
+ const existing = groupCache.get(groupKey);
620
+ if (existing !== undefined) {
621
+ parent = existing;
622
+ continue;
623
+ }
624
+ const group = new ClinkrGroup<NsCliContext>({
625
+ name: segment,
626
+ description: groupDescription(parentSegments.slice(0, index + 1), commandInfo),
627
+ ...(index === 0 ? { helpGroup: NS_EXTENSION_HELP_GROUP } : {}),
628
+ ...(segment === NS_EXEC_GROUP_NAME && isGroupedExecCommand(commandInfo)
629
+ ? { isHidden: true }
630
+ : {}),
631
+ });
632
+ groupCache.set(groupKey, group);
633
+ parent.group(group);
634
+ parent = group;
635
+ }
636
+ return parent;
637
+ }
638
+
639
+ function groupDescription(segments: readonly string[], commandInfo: NsCommandCliInfo): string {
640
+ if (segments.at(-1) === NS_EXEC_GROUP_NAME && isGroupedExecCommand(commandInfo)) {
641
+ return `Skill-invoked NS ${segments[0] ?? "extension"} operations.`;
642
+ }
643
+ if (segments.length === 1 && commandInfo.groupDescription !== undefined) {
644
+ return commandInfo.groupDescription;
645
+ }
646
+ return `NS ${segments.join(" ")} commands.`;
647
+ }
648
+
649
+ function isStaticTopLevelMetadataRequest(args: readonly string[]): boolean {
650
+ return args.includes("--version") || args.includes("--runtime");
651
+ }
652
+
653
+ function writeNsResultOutput(
654
+ result: { ok: true; message: string } | { ok: false; message: string },
655
+ deps: Pick<NsCliContext, "stdout" | "stderr">,
656
+ ): void {
657
+ if (result.message === "") return;
658
+ const output = `${result.message}\n`;
659
+ if (result.ok) {
660
+ deps.stdout(output);
661
+ return;
662
+ }
663
+ deps.stderr(output);
664
+ }
665
+
666
+ export const VERSION = entry.version;
667
+
668
+ await entry.runIfMain({ isImportMetaMain: import.meta.main });