@jskit-ai/jskit-cli 0.2.41 → 0.2.43

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 (26) hide show
  1. package/package.json +4 -3
  2. package/src/server/cliRuntime/completion.js +1177 -0
  3. package/src/server/cliRuntime/descriptorValidation.js +18 -3
  4. package/src/server/cliRuntime/ioAndMigrations.js +2 -2
  5. package/src/server/cliRuntime/mutationApplication.js +1 -1
  6. package/src/server/cliRuntime/mutationWhen.js +2 -0
  7. package/src/server/cliRuntime/mutations/fileMutations.js +188 -143
  8. package/src/server/cliRuntime/mutations/installMigrationMutation.js +11 -38
  9. package/src/server/cliRuntime/mutations/templateContext.js +8 -14
  10. package/src/server/cliRuntime/mutations/textMutations.js +11 -6
  11. package/src/server/cliRuntime/packageInstallFlow.js +36 -21
  12. package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +13 -22
  13. package/src/server/cliRuntime/packageOptions.js +149 -3
  14. package/src/server/cliRuntime/packageRegistries.js +3 -2
  15. package/src/server/commandHandlers/completion.js +129 -0
  16. package/src/server/commandHandlers/list.js +4 -6
  17. package/src/server/commandHandlers/packageCommands/add.js +31 -11
  18. package/src/server/commandHandlers/packageCommands/discoverabilityHelp.js +10 -2
  19. package/src/server/commandHandlers/packageCommands/generate.js +29 -31
  20. package/src/server/commandHandlers/packageCommands/tabLinkItemProvisioning.js +123 -164
  21. package/src/server/commandHandlers/shared.js +23 -3
  22. package/src/server/commandHandlers/show/renderPackageText.js +3 -3
  23. package/src/server/core/argParser.js +12 -2
  24. package/src/server/core/commandCatalog.js +36 -13
  25. package/src/server/core/createCommandHandlers.js +3 -0
  26. package/src/server/shared/optionInterpolation.js +93 -0
@@ -30,9 +30,8 @@ function canDelegateAddInlineOptions(positional = []) {
30
30
  function canDelegateGenerateInlineOptions(positional = []) {
31
31
  const normalizedPositionals = Array.isArray(positional) ? positional : [];
32
32
  const first = String(normalizedPositionals[0] || "").trim();
33
- const second = String(normalizedPositionals[1] || "").trim();
34
33
  const last = String(normalizedPositionals[normalizedPositionals.length - 1] || "").trim();
35
- if (!first || !second || isHelpToken(first) || isHelpToken(second) || isHelpToken(last)) {
34
+ if (!first || isHelpToken(first) || isHelpToken(last)) {
36
35
  return false;
37
36
  }
38
37
  return true;
@@ -77,6 +76,31 @@ const COMMAND_DESCRIPTORS = Object.freeze({
77
76
  inlineOptionMode: "none",
78
77
  allowedValueOptionNames: Object.freeze([])
79
78
  }),
79
+ completion: Object.freeze({
80
+ command: "completion",
81
+ aliases: Object.freeze([]),
82
+ showInOverview: true,
83
+ summary: "Print shell completion script support.",
84
+ minimalUse: "jskit completion bash",
85
+ parameters: Object.freeze([
86
+ Object.freeze({
87
+ name: "<shell>",
88
+ description: "Shell name. Currently only bash is supported."
89
+ })
90
+ ]),
91
+ defaults: Object.freeze([
92
+ "Prints a shell completion script to stdout.",
93
+ "Use --install to write a small Bash loader file and wire ~/.bashrc automatically.",
94
+ "Use source <(npx jskit completion bash) to enable completion in the current shell.",
95
+ "The internal __complete__ mode is reserved for the generated shell function."
96
+ ]),
97
+ fullUse: "jskit completion bash [--install]",
98
+ showHelpOnBareInvocation: true,
99
+ handlerName: "commandCompletion",
100
+ allowedFlagKeys: Object.freeze([]),
101
+ inlineOptionMode: "enumerated",
102
+ allowedValueOptionNames: Object.freeze(["install"])
103
+ }),
80
104
  create: Object.freeze({
81
105
  command: "create",
82
106
  aliases: Object.freeze([]),
@@ -135,7 +159,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
135
159
  }),
136
160
  generate: Object.freeze({
137
161
  command: "generate",
138
- aliases: Object.freeze(["gen"]),
162
+ aliases: Object.freeze([]),
139
163
  showInOverview: true,
140
164
  summary: "Run a generator package (or generator subcommand).",
141
165
  minimalUse: "jskit generate <generatorId>",
@@ -188,7 +212,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
188
212
  }),
189
213
  list: Object.freeze({
190
214
  command: "list",
191
- aliases: Object.freeze(["ls"]),
215
+ aliases: Object.freeze([]),
192
216
  showInOverview: true,
193
217
  summary: "List bundles, runtime packages, or generator packages.",
194
218
  minimalUse: "jskit list",
@@ -212,7 +236,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
212
236
  }),
213
237
  "list-placements": Object.freeze({
214
238
  command: "list-placements",
215
- aliases: Object.freeze(["lp"]),
239
+ aliases: Object.freeze([]),
216
240
  showInOverview: true,
217
241
  summary: "List discovered UI placement targets.",
218
242
  minimalUse: "jskit list-placements",
@@ -229,12 +253,12 @@ const COMMAND_DESCRIPTORS = Object.freeze({
229
253
  inlineOptionMode: "none",
230
254
  allowedValueOptionNames: Object.freeze([])
231
255
  }),
232
- "list-link-items": Object.freeze({
233
- command: "list-link-items",
234
- aliases: Object.freeze(["lpct", "list-placement-component-tokens"]),
256
+ "list-component-tokens": Object.freeze({
257
+ command: "list-component-tokens",
258
+ aliases: Object.freeze([]),
235
259
  showInOverview: true,
236
- summary: "List available placement link-item component tokens.",
237
- minimalUse: "jskit list-link-items",
260
+ summary: "List available placement component tokens.",
261
+ minimalUse: "jskit list-component-tokens",
238
262
  parameters: Object.freeze([
239
263
  Object.freeze({
240
264
  name: "[--prefix <value>]",
@@ -252,7 +276,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
252
276
  "Use --all when you want the full discovered token set.",
253
277
  "Shows plain text by default; use --json for structured output."
254
278
  ]),
255
- fullUse: "jskit list-link-items [--prefix <value>] [--all] [--json]",
279
+ fullUse: "jskit list-component-tokens [--prefix <value>] [--all] [--json]",
256
280
  showHelpOnBareInvocation: false,
257
281
  handlerName: "commandListLinkItems",
258
282
  allowedFlagKeys: Object.freeze(["json", "all"]),
@@ -261,7 +285,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
261
285
  }),
262
286
  show: Object.freeze({
263
287
  command: "show",
264
- aliases: Object.freeze(["view"]),
288
+ aliases: Object.freeze([]),
265
289
  showInOverview: true,
266
290
  summary: "Show detailed metadata for a bundle or package.",
267
291
  minimalUse: "jskit show <id>",
@@ -272,7 +296,6 @@ const COMMAND_DESCRIPTORS = Object.freeze({
272
296
  })
273
297
  ]),
274
298
  defaults: Object.freeze([
275
- "view is an alias of show.",
276
299
  "Basic output is compact; --details expands capability and runtime sections.",
277
300
  "--debug-exports implies --details."
278
301
  ]),
@@ -3,6 +3,7 @@ import { createListCommands } from "../commandHandlers/list.js";
3
3
  import { createShowCommand } from "../commandHandlers/show.js";
4
4
  import { createPackageCommands } from "../commandHandlers/package.js";
5
5
  import { createHealthCommands } from "../commandHandlers/health.js";
6
+ import { createCompletionCommands } from "../commandHandlers/completion.js";
6
7
 
7
8
  function createCommandHandlers(deps = {}) {
8
9
  const shared = createCommandHandlerShared(deps);
@@ -23,11 +24,13 @@ function createCommandHandlers(deps = {}) {
23
24
  commandRemove
24
25
  } = createPackageCommands(commandContext);
25
26
  const { commandDoctor, commandLintDescriptors } = createHealthCommands(commandContext);
27
+ const { commandCompletion } = createCompletionCommands(commandContext);
26
28
 
27
29
  return {
28
30
  commandList,
29
31
  commandListPlacements,
30
32
  commandListLinkItems,
33
+ commandCompletion,
31
34
  commandShow,
32
35
  commandCreate,
33
36
  commandAdd,
@@ -209,6 +209,80 @@ function normalizePathValue(value) {
209
209
  .join("/");
210
210
  }
211
211
 
212
+ function normalizePromptChoices(rawChoices = []) {
213
+ return ensureArray(rawChoices)
214
+ .map((entry) => {
215
+ if (!entry || typeof entry !== "object") {
216
+ return null;
217
+ }
218
+ const value = String(entry.value || "").trim();
219
+ const label = String(entry.label || value).trim();
220
+ if (!value || !label) {
221
+ return null;
222
+ }
223
+ return Object.freeze({ value, label });
224
+ })
225
+ .filter(Boolean);
226
+ }
227
+
228
+ function findPromptChoice(promptChoices = [], answer = "") {
229
+ const normalizedAnswer = String(answer || "").trim();
230
+ if (!normalizedAnswer) {
231
+ return null;
232
+ }
233
+
234
+ if (/^\d+$/.test(normalizedAnswer)) {
235
+ const index = Number.parseInt(normalizedAnswer, 10) - 1;
236
+ if (index >= 0 && index < promptChoices.length) {
237
+ return promptChoices[index];
238
+ }
239
+ }
240
+
241
+ return promptChoices.find((entry) => {
242
+ return entry.value === normalizedAnswer || entry.label === normalizedAnswer;
243
+ }) || null;
244
+ }
245
+
246
+ async function promptForChoice({
247
+ promptText = "",
248
+ promptChoices = [],
249
+ stdin,
250
+ stdout,
251
+ required = false,
252
+ defaultValue = ""
253
+ }) {
254
+ const rl = createInterface({
255
+ input: stdin,
256
+ output: stdout
257
+ });
258
+
259
+ try {
260
+ while (true) {
261
+ const answer = String(await rl.question(promptText)).trim();
262
+ if (!answer && defaultValue) {
263
+ return defaultValue;
264
+ }
265
+ if (!answer && !required) {
266
+ return "";
267
+ }
268
+ if (!answer && required) {
269
+ throw createCliError("A selection is required.");
270
+ }
271
+
272
+ const matchedChoice = findPromptChoice(promptChoices, answer);
273
+ if (matchedChoice) {
274
+ return matchedChoice.value;
275
+ }
276
+
277
+ const values = promptChoices.map((entry) => entry.value).join(", ");
278
+ stdout.write(`Invalid selection. Enter a number or one of: ${values}.
279
+ `);
280
+ }
281
+ } finally {
282
+ rl.close();
283
+ }
284
+ }
285
+
212
286
  function parseTransformSpec(transform) {
213
287
  const normalized = String(transform || "").trim();
214
288
  if (!normalized) {
@@ -359,6 +433,7 @@ async function promptForRequiredOption({
359
433
  ownerId,
360
434
  optionName,
361
435
  optionSchema,
436
+ promptChoices = [],
362
437
  stdin = process.stdin,
363
438
  stdout = process.stdout
364
439
  }) {
@@ -387,6 +462,24 @@ async function promptForRequiredOption({
387
462
  const defaultHint = defaultValue ? ` [default: ${defaultValue}]` : "";
388
463
  const hintSuffix = promptHint ? ` ${promptHint}` : "";
389
464
  const promptText = `${label}${defaultHint}${hintSuffix}: `;
465
+ const normalizedPromptChoices = normalizePromptChoices(promptChoices);
466
+
467
+ if (normalizedPromptChoices.length > 0) {
468
+ stdout.write(`${label}${defaultHint}${hintSuffix}
469
+ `);
470
+ normalizedPromptChoices.forEach((entry, index) => {
471
+ stdout.write(` ${index + 1}) ${entry.label}
472
+ `);
473
+ });
474
+ return promptForChoice({
475
+ promptText: "Select a surface by number or id: ",
476
+ promptChoices: normalizedPromptChoices,
477
+ stdin,
478
+ stdout,
479
+ required,
480
+ defaultValue
481
+ });
482
+ }
390
483
 
391
484
  let answer = "";
392
485