@fnndsc/chili 3.5.0 → 3.6.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.
@@ -13,18 +13,31 @@ import { CLIoptions } from "../utils/cli.js";
13
13
  * Handles commands related to groups of ChRIS files, links, or directories.
14
14
  */
15
15
  export declare class FileGroupHandler {
16
- private baseGroupHandler;
17
16
  private controller;
18
17
  readonly assetName: string;
18
+ private readonly path?;
19
19
  private constructor();
20
20
  /**
21
- * Factory method to create a new FileGroupHandler instance.
21
+ * Creates a FileGroupHandler for an asset type. This performs no network work
22
+ * and resolves no ChRIS context — command registration needs only the asset
23
+ * name, and the controller is resolved lazily on first use (see
24
+ * {@link controller_ensure}). Keeping setup cheap means an unrelated command
25
+ * (or a directory that is not a ChRIS folder) pays no file-context cost.
22
26
  *
23
27
  * @param assetName - The type of asset to handle ('files', 'links', 'dirs').
24
- * @param path - Optional path within ChRIS FS. Defaults to current ChRIS folder context.
25
- * @returns A Promise resolving to a new FileGroupHandler instance.
28
+ * @param path - Optional path within ChRIS FS the controller binds to;
29
+ * defaults to the current ChRIS folder context.
30
+ * @returns A new FileGroupHandler instance.
31
+ */
32
+ static handler_create(assetName: string, path?: string): FileGroupHandler;
33
+ /**
34
+ * Lazily resolves and memoizes the FileController for this asset against the
35
+ * bound path (or the current ChRIS context).
36
+ *
37
+ * @returns The controller, or null if the context cannot be resolved here
38
+ * (e.g. the current directory is not a ChRIS folder).
26
39
  */
27
- static handler_create(assetName: string, path?: string): Promise<FileGroupHandler>;
40
+ private controller_ensure;
28
41
  /**
29
42
  * Lists files, links, or directories using the new command logic.
30
43
  */
@@ -17,31 +17,45 @@ import { chiliErrLog, chiliLog } from "../screen/output.js";
17
17
  * Handles commands related to groups of ChRIS files, links, or directories.
18
18
  */
19
19
  export class FileGroupHandler {
20
- constructor(controller, assetName) {
21
- this.controller = controller;
20
+ constructor(assetName, path) {
21
+ // Resolved lazily: only `share` needs a controller; list/fields/delete
22
+ // re-resolve their context through standalone salsa helpers.
23
+ this.controller = null;
22
24
  this.assetName = assetName;
23
- // baseGroupHandler will be used for fieldslist, delete, share until those are refactored
24
- this.baseGroupHandler = new BaseGroupHandler(this.assetName, this.controller.chrisObject);
25
+ this.path = path;
25
26
  }
26
27
  /**
27
- * Factory method to create a new FileGroupHandler instance.
28
+ * Creates a FileGroupHandler for an asset type. This performs no network work
29
+ * and resolves no ChRIS context — command registration needs only the asset
30
+ * name, and the controller is resolved lazily on first use (see
31
+ * {@link controller_ensure}). Keeping setup cheap means an unrelated command
32
+ * (or a directory that is not a ChRIS folder) pays no file-context cost.
28
33
  *
29
34
  * @param assetName - The type of asset to handle ('files', 'links', 'dirs').
30
- * @param path - Optional path within ChRIS FS. Defaults to current ChRIS folder context.
31
- * @returns A Promise resolving to a new FileGroupHandler instance.
35
+ * @param path - Optional path within ChRIS FS the controller binds to;
36
+ * defaults to the current ChRIS folder context.
37
+ * @returns A new FileGroupHandler instance.
32
38
  */
33
- static async handler_create(assetName, path) {
34
- try {
35
- const controller = await FileController.handler_create(assetName, path);
36
- if (controller === null) {
37
- throw new Error(`Failed to create FileController for asset type: ${assetName}`);
39
+ static handler_create(assetName, path) {
40
+ return new FileGroupHandler(assetName, path);
41
+ }
42
+ /**
43
+ * Lazily resolves and memoizes the FileController for this asset against the
44
+ * bound path (or the current ChRIS context).
45
+ *
46
+ * @returns The controller, or null if the context cannot be resolved here
47
+ * (e.g. the current directory is not a ChRIS folder).
48
+ */
49
+ async controller_ensure() {
50
+ if (!this.controller) {
51
+ try {
52
+ this.controller = await FileController.handler_create(this.assetName, this.path);
53
+ }
54
+ catch {
55
+ this.controller = null;
38
56
  }
39
- return new FileGroupHandler(controller, assetName);
40
- }
41
- catch (error) {
42
- const errorMessage = error instanceof Error ? error.message : String(error);
43
- throw new Error(`Failed to initialize FileGroupHandler for ${assetName}: ${errorMessage}`);
44
57
  }
58
+ return this.controller;
45
59
  }
46
60
  /**
47
61
  * Lists files, links, or directories using the new command logic.
@@ -137,12 +151,17 @@ export class FileGroupHandler {
137
151
  * @param options - CLI options for sharing files.
138
152
  */
139
153
  async files_share(options) {
154
+ const controller = await this.controller_ensure();
155
+ if (!controller) {
156
+ chiliErrLog(`Cannot share ${this.assetName}: no ChRIS context is available in the current directory.`);
157
+ return;
158
+ }
140
159
  try {
141
- chiliLog(`Sharing ${this.assetName} from ${this.controller.path_get}...`);
160
+ chiliLog(`Sharing ${this.assetName} from ${controller.path_get}...`);
142
161
  if (options.force) {
143
162
  chiliLog("Force sharing enabled");
144
163
  }
145
- await this.controller.files_share(options);
164
+ await controller.files_share(options);
146
165
  }
147
166
  catch (error) {
148
167
  if (error instanceof Error) {
@@ -162,7 +181,9 @@ export class FileGroupHandler {
162
181
  const fileGroupCommand = program
163
182
  .command(this.assetName)
164
183
  .description(`Interact with a group of ChRIS ${this.assetName}`);
165
- const listCommand = this.baseGroupHandler.baseListCommand_create(async (options) => {
184
+ // The list command only needs the asset name to register; a context-less
185
+ // BaseGroupHandler is enough to build it (its actions re-resolve context).
186
+ const listCommand = new BaseGroupHandler(this.assetName).baseListCommand_create(async (options) => {
166
187
  await this.files_list(options);
167
188
  });
168
189
  // Add path argument and override action to handle it properly
@@ -19,7 +19,7 @@ export async function inodeCommand_setup(program) {
19
19
  const args = command.args.slice(1);
20
20
  const subcommand = args[0];
21
21
  if (subcommand === "files") {
22
- const fileGroupHandler = await FileGroupHandler.handler_create("files", path);
22
+ const fileGroupHandler = FileGroupHandler.handler_create("files", path);
23
23
  const filesProgram = new Command();
24
24
  fileGroupHandler.fileGroupCommand_setup(filesProgram);
25
25
  await filesProgram.parseAsync(args);
@@ -14,8 +14,14 @@ import { TableOptions } from "../screen/screen.js";
14
14
  export declare class BaseGroupHandler {
15
15
  assetName: string;
16
16
  displayOptions: TableOptions;
17
- chrisObject: ChRISPluginGroup | ChRISFeedGroup | ChRISEmbeddedResourceGroup<unknown> | ChRISPACSGroup;
18
- constructor(assetName: string, chrisObject: ChRISPluginGroup | ChRISFeedGroup | ChRISEmbeddedResourceGroup<unknown> | ChRISPACSGroup);
17
+ chrisObject: ChRISPluginGroup | ChRISFeedGroup | ChRISEmbeddedResourceGroup<unknown> | ChRISPACSGroup | null;
18
+ constructor(assetName: string, chrisObject?: ChRISPluginGroup | ChRISFeedGroup | ChRISEmbeddedResourceGroup<unknown> | ChRISPACSGroup | null);
19
+ /**
20
+ * The resolved ChRIS context, for use inside actions. Throws if read before
21
+ * the context has been set — a lazy handler must resolve its context before
22
+ * an action dereferences it.
23
+ */
24
+ private get chrisObjectResolved();
19
25
  /**
20
26
  * Lists ChRIS resources based on provided CLI options.
21
27
  *
@@ -15,7 +15,7 @@ import { chiliErrLog, chiliLog } from "../screen/output.js";
15
15
  * Provides common functionality for listing and deleting resources.
16
16
  */
17
17
  export class BaseGroupHandler {
18
- constructor(assetName, chrisObject) {
18
+ constructor(assetName, chrisObject = null) {
19
19
  this.assetName = "";
20
20
  this.assetName = assetName;
21
21
  this.chrisObject = chrisObject;
@@ -23,6 +23,17 @@ export class BaseGroupHandler {
23
23
  title: { title: this.assetName, justification: "center" },
24
24
  };
25
25
  }
26
+ /**
27
+ * The resolved ChRIS context, for use inside actions. Throws if read before
28
+ * the context has been set — a lazy handler must resolve its context before
29
+ * an action dereferences it.
30
+ */
31
+ get chrisObjectResolved() {
32
+ if (!this.chrisObject) {
33
+ throw new Error(`ChRIS context for '${this.assetName}' is not available in the current context.`);
34
+ }
35
+ return this.chrisObject;
36
+ }
26
37
  /**
27
38
  * Lists ChRIS resources based on provided CLI options.
28
39
  *
@@ -50,7 +61,7 @@ export class BaseGroupHandler {
50
61
  options.fields = cleanFields.join(',');
51
62
  }
52
63
  const params = options_toParams(options);
53
- const results = await this.chrisObject.asset.resources_listAndFilterByOptions(params);
64
+ const results = await this.chrisObjectResolved.asset.resources_listAndFilterByOptions(params);
54
65
  if (!results) {
55
66
  chiliErrLog(`No ${this.assetName} resources found. Perhaps check your current context?`);
56
67
  return;
@@ -113,7 +124,7 @@ export class BaseGroupHandler {
113
124
  */
114
125
  async resourceFields_list() {
115
126
  try {
116
- const results = await this.chrisObject.asset.resourceFields_get();
127
+ const results = await this.chrisObjectResolved.asset.resourceFields_get();
117
128
  if (!results) {
118
129
  chiliErrLog(`An error occurred while fetching resource fields for ${this.assetName}.`);
119
130
  return;
@@ -139,7 +150,7 @@ export class BaseGroupHandler {
139
150
  */
140
151
  async resourceFields_render() {
141
152
  try {
142
- const results = await this.chrisObject.asset.resourceFields_get();
153
+ const results = await this.chrisObjectResolved.asset.resourceFields_get();
143
154
  if (!results) {
144
155
  return envelope_error('', undefined, `An error occurred while fetching resource fields for ${this.assetName}.\n`);
145
156
  }
@@ -241,7 +252,7 @@ export class BaseGroupHandler {
241
252
  let OKorNot = "";
242
253
  for (const id of IDs) {
243
254
  try {
244
- const searchResults = await this.chrisObject.asset.resources_listAndFilterByOptions({
255
+ const searchResults = await this.chrisObjectResolved.asset.resources_listAndFilterByOptions({
245
256
  id: id,
246
257
  });
247
258
  chiliLog(border_draw(`checking ${this.assetName} id ${id} ... ${this.msg_OKorNot(searchResults)}`));
@@ -251,7 +262,7 @@ export class BaseGroupHandler {
251
262
  continue;
252
263
  }
253
264
  }
254
- delop = await this.chrisObject.asset.resourceItem_delete(id);
265
+ delop = await this.chrisObjectResolved.asset.resourceItem_delete(id);
255
266
  border_draw(`deleting ${this.assetName} id ${id} ... ${this.msg_OKorNot(true)}`);
256
267
  }
257
268
  catch (error) {
@@ -269,7 +280,7 @@ export class BaseGroupHandler {
269
280
  */
270
281
  async IDs_getFromSearch(options) {
271
282
  const params = options_toParams(options);
272
- const searchResults = await this.chrisObject.asset.resources_listAndFilterByOptions(params);
283
+ const searchResults = await this.chrisObjectResolved.asset.resources_listAndFilterByOptions(params);
273
284
  if (!searchResults) {
274
285
  return null;
275
286
  }
@@ -13,16 +13,27 @@ export declare class PluginContextGroupHandler {
13
13
  private baseGroupHandler;
14
14
  private controller;
15
15
  readonly assetName: string;
16
+ private readonly id?;
16
17
  private constructor();
17
18
  /**
18
- * Factory method to create a new PluginContextGroupHandler instance.
19
+ * Creates a PluginContextGroupHandler for an asset type. This performs no
20
+ * network work and resolves no plugin context — command registration needs
21
+ * only the asset name; the controller is resolved lazily on first action (see
22
+ * {@link controller_ensure}).
19
23
  *
20
- * @param assetName - The type of plugin context to handle ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
21
- * @param id - Optional plugin ID. Defaults to current ChRIS plugin context.
22
- * @returns A Promise resolving to a new PluginContextGroupHandler instance.
23
- * @throws InitializationError if an unsupported asset type is provided.
24
+ * @param assetName - The type of plugin context ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
25
+ * @param id - Optional plugin ID the context binds to; defaults to the current plugin context.
26
+ * @returns A new PluginContextGroupHandler instance.
24
27
  */
25
- static handler_create(assetName: string, id?: number | null): Promise<PluginContextGroupHandler>;
28
+ static handler_create(assetName: string, id?: number | null): PluginContextGroupHandler;
29
+ /**
30
+ * Lazily resolves and memoizes the plugin-context controller against the bound
31
+ * plugin id (or the current ChRIS context), wiring it into the
32
+ * registration-only base handler so its actions can read the context.
33
+ *
34
+ * @returns The controller, or null if the context cannot be resolved here.
35
+ */
36
+ private controller_ensure;
26
37
  /**
27
38
  * Lists plugin parameters in a "man page" style format.
28
39
  * This is a specialized view for parameters that differs from the standard table view.
@@ -9,39 +9,49 @@ import { PluginContextController } from "../controllers/pluginContextController.
9
9
  import { pluginParameters_renderMan, pluginParameters_manRender } from "../views/pluginParameters.js";
10
10
  import { envelope_ok, envelope_error } from "@fnndsc/cumin";
11
11
  import { chiliErrLog, chiliLog } from "../screen/output.js";
12
- class InitializationError extends Error {
13
- constructor(message) {
14
- super(message);
15
- this.name = "InitializationError";
16
- }
17
- }
18
12
  /**
19
13
  * Handles commands related to groups of plugin contexts (computes, instances, parameters).
20
14
  */
21
15
  export class PluginContextGroupHandler {
22
- constructor(controller, assetName) {
23
- this.baseGroupHandler = null;
24
- this.controller = controller;
16
+ constructor(assetName, id) {
17
+ // Resolved lazily on first action.
18
+ this.controller = null;
25
19
  this.assetName = assetName;
26
- // Cast ChRISEmbeddedResourceGroup to ChRISEmbeddedResourceGroup<unknown> safely.
27
- this.baseGroupHandler = new BaseGroupHandler(this.assetName, this.controller.chrisObject);
20
+ this.id = id;
21
+ this.baseGroupHandler = new BaseGroupHandler(this.assetName);
28
22
  }
29
23
  /**
30
- * Factory method to create a new PluginContextGroupHandler instance.
24
+ * Creates a PluginContextGroupHandler for an asset type. This performs no
25
+ * network work and resolves no plugin context — command registration needs
26
+ * only the asset name; the controller is resolved lazily on first action (see
27
+ * {@link controller_ensure}).
31
28
  *
32
- * @param assetName - The type of plugin context to handle ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
33
- * @param id - Optional plugin ID. Defaults to current ChRIS plugin context.
34
- * @returns A Promise resolving to a new PluginContextGroupHandler instance.
35
- * @throws InitializationError if an unsupported asset type is provided.
29
+ * @param assetName - The type of plugin context ('computesofplugin', 'instancesofplugin', 'parametersofplugin').
30
+ * @param id - Optional plugin ID the context binds to; defaults to the current plugin context.
31
+ * @returns A new PluginContextGroupHandler instance.
36
32
  */
37
- static async handler_create(assetName, id) {
38
- try {
39
- const controller = await PluginContextController.controller_create(assetName, id);
40
- return new PluginContextGroupHandler(controller, assetName);
41
- }
42
- catch (error) {
43
- throw new InitializationError(`Failed to initialize PluginContextGroupHandler: ${error}`);
33
+ static handler_create(assetName, id) {
34
+ return new PluginContextGroupHandler(assetName, id);
35
+ }
36
+ /**
37
+ * Lazily resolves and memoizes the plugin-context controller against the bound
38
+ * plugin id (or the current ChRIS context), wiring it into the
39
+ * registration-only base handler so its actions can read the context.
40
+ *
41
+ * @returns The controller, or null if the context cannot be resolved here.
42
+ */
43
+ async controller_ensure() {
44
+ if (!this.controller) {
45
+ try {
46
+ this.controller = await PluginContextController.controller_create(this.assetName, this.id);
47
+ this.baseGroupHandler.chrisObject =
48
+ this.controller.chrisObject;
49
+ }
50
+ catch {
51
+ this.controller = null;
52
+ }
44
53
  }
54
+ return this.controller;
45
55
  }
46
56
  /**
47
57
  * Lists plugin parameters in a "man page" style format.
@@ -51,8 +61,13 @@ export class PluginContextGroupHandler {
51
61
  */
52
62
  async parameters_listMan(options) {
53
63
  try {
64
+ const controller = await this.controller_ensure();
65
+ if (!controller) {
66
+ chiliErrLog(`No plugin context is available for ${this.assetName} in the current context.`);
67
+ return;
68
+ }
54
69
  // We access the asset directly from the controller
55
- const asset = this.controller.chrisObject.asset;
70
+ const asset = controller.chrisObject.asset;
56
71
  if (!asset || typeof asset.resources_listAndFilterByOptions !== 'function') {
57
72
  chiliErrLog("Underlying resource does not support listing.");
58
73
  return;
@@ -74,9 +89,12 @@ export class PluginContextGroupHandler {
74
89
  * Lists available fields for the current plugin context resource.
75
90
  */
76
91
  async parameters_fieldsList() {
77
- if (this.baseGroupHandler) {
78
- await this.baseGroupHandler.resourceFields_list();
92
+ const controller = await this.controller_ensure();
93
+ if (!controller) {
94
+ chiliErrLog(`No plugin context is available for ${this.assetName} in the current context.`);
95
+ return;
79
96
  }
97
+ await this.baseGroupHandler.resourceFields_list();
80
98
  }
81
99
  /**
82
100
  * Renders plugin parameters in "man page" style as an envelope.
@@ -88,7 +106,11 @@ export class PluginContextGroupHandler {
88
106
  */
89
107
  async parameters_listManRender(options) {
90
108
  try {
91
- const asset = this.controller.chrisObject.asset;
109
+ const controller = await this.controller_ensure();
110
+ if (!controller) {
111
+ return envelope_error('', undefined, `No plugin context is available for ${this.assetName} in the current context.\n`);
112
+ }
113
+ const asset = controller.chrisObject.asset;
92
114
  if (!asset || typeof asset.resources_listAndFilterByOptions !== 'function') {
93
115
  return envelope_error('', undefined, "Underlying resource does not support listing.\n");
94
116
  }
@@ -111,10 +133,11 @@ export class PluginContextGroupHandler {
111
133
  * @returns An envelope carrying the field listing.
112
134
  */
113
135
  async parameters_fieldsRender() {
114
- if (this.baseGroupHandler) {
115
- return this.baseGroupHandler.resourceFields_render();
136
+ const controller = await this.controller_ensure();
137
+ if (!controller) {
138
+ return envelope_ok('');
116
139
  }
117
- return envelope_ok('');
140
+ return this.baseGroupHandler.resourceFields_render();
118
141
  }
119
142
  /**
120
143
  * Sets up the Commander.js commands for plugin context group operations.
@@ -122,9 +145,6 @@ export class PluginContextGroupHandler {
122
145
  * @param program - The Commander.js program instance.
123
146
  */
124
147
  pluginContextGroupCommand_setup(program) {
125
- if (this.baseGroupHandler) {
126
- this.baseGroupHandler.command_setup(program);
127
- }
128
- const fileGroupCommand = program.commands.find((cmd) => cmd.name() === this.assetName);
148
+ this.baseGroupHandler.command_setup(program);
129
149
  }
130
150
  }
package/dist/run.d.ts CHANGED
@@ -10,6 +10,15 @@
10
10
  * @module
11
11
  */
12
12
  import { type ChiliCaptured } from "./screen/output.js";
13
+ /**
14
+ * The set of top-level chili command names. Builds a fresh program and registers
15
+ * every handler — now a cheap, network-free operation — then reads the
16
+ * registered command names. A host (the brasa engine) uses this to decide
17
+ * whether an unknown chell command is worth delegating to chili at all.
18
+ *
19
+ * @returns The registered top-level command names.
20
+ */
21
+ export declare function commandNames_get(): Promise<Set<string>>;
13
22
  /**
14
23
  * Executes a single chili command in the current process.
15
24
  *
package/dist/run.js CHANGED
@@ -25,7 +25,7 @@ import { chefsCommand_setup } from "./chefs/chefs.js";
25
25
  import { PACSServerGroupHandler } from "./pacs/pacsServerHandler.js";
26
26
  import { PACSQueryGroupHandler } from "./pacs/pacsQueryHandler.js";
27
27
  import { PACSRetrieveGroupHandler } from "./pacs/pacsRetrieveHandler.js";
28
- import { chrisConnection_init, NodeStorageProvider, errorStack_getAllOfType, } from "@fnndsc/cumin";
28
+ import { chrisConnection_init, NodeStorageProvider, } from "@fnndsc/cumin";
29
29
  import { FileGroupHandler } from "./filesystem/fileGroupHandler.js";
30
30
  import { chiliErrLog, chiliLog, chili_capture } from "./screen/output.js";
31
31
  /**
@@ -73,51 +73,32 @@ async function handlers_initialize(program) {
73
73
  feedMemberHandler.feedCommand_setup(program);
74
74
  const pluginMemberHandler = new PluginMemberHandler();
75
75
  pluginMemberHandler.pluginCommand_setup(program);
76
- try {
77
- const filesGroupHandler = await FileGroupHandler.handler_create("files");
78
- filesGroupHandler.fileGroupCommand_setup(program);
79
- const linksGroupHandler = await FileGroupHandler.handler_create("links");
80
- linksGroupHandler.fileGroupCommand_setup(program);
81
- const dirsGroupHandler = await FileGroupHandler.handler_create("dirs");
82
- dirsGroupHandler.fileGroupCommand_setup(program);
83
- }
84
- catch (e) {
85
- const err = e instanceof Error ? e.message : String(e);
86
- const errors = errorStack_getAllOfType("error");
87
- const warnings = errorStack_getAllOfType("warning");
88
- chiliLog(`Note: File group commands (files, dirs, links) are unavailable. Reason: ${err}`);
89
- if (errors.length > 0) {
90
- chiliLog("Errors:");
91
- errors.forEach((msg) => chiliLog(` - ${msg}`));
92
- }
93
- if (warnings.length > 0) {
94
- chiliLog("Warnings:");
95
- warnings.forEach((msg) => chiliLog(` - ${msg}`));
96
- }
76
+ // File-group and plugin-context commands register with no ChRIS context: each
77
+ // handler resolves its context lazily, only when one of its actions runs. This
78
+ // keeps setup free of network work, so an unrelated command (or a directory
79
+ // that is not a ChRIS folder) pays no file-context cost and produces no
80
+ // context-init noise.
81
+ for (const assetName of ["files", "links", "dirs"]) {
82
+ FileGroupHandler.handler_create(assetName).fileGroupCommand_setup(program);
97
83
  }
98
- try {
99
- const computesOfPluginHandler = await PluginContextGroupHandler.handler_create("computesofplugin");
100
- computesOfPluginHandler.pluginContextGroupCommand_setup(program);
101
- const pluginInstancesHandler = await PluginContextGroupHandler.handler_create("instancesofplugin");
102
- pluginInstancesHandler.pluginContextGroupCommand_setup(program);
103
- const pluginParametersHandler = await PluginContextGroupHandler.handler_create("parametersofplugin");
104
- pluginParametersHandler.pluginContextGroupCommand_setup(program);
105
- }
106
- catch (e) {
107
- const err = e instanceof Error ? e.message : String(e);
108
- const errors = errorStack_getAllOfType("error");
109
- const warnings = errorStack_getAllOfType("warning");
110
- chiliLog(`Note: Plugin context commands are unavailable. Reason: ${err}`);
111
- if (errors.length > 0) {
112
- chiliLog("Errors:");
113
- errors.forEach((msg) => chiliLog(` - ${msg}`));
114
- }
115
- if (warnings.length > 0) {
116
- chiliLog("Warnings:");
117
- warnings.forEach((msg) => chiliLog(` - ${msg}`));
118
- }
84
+ for (const assetName of ["computesofplugin", "instancesofplugin", "parametersofplugin"]) {
85
+ PluginContextGroupHandler.handler_create(assetName).pluginContextGroupCommand_setup(program);
119
86
  }
120
87
  }
88
+ /**
89
+ * The set of top-level chili command names. Builds a fresh program and registers
90
+ * every handler — now a cheap, network-free operation — then reads the
91
+ * registered command names. A host (the brasa engine) uses this to decide
92
+ * whether an unknown chell command is worth delegating to chili at all.
93
+ *
94
+ * @returns The registered top-level command names.
95
+ */
96
+ export async function commandNames_get() {
97
+ const program = new Command();
98
+ connectCommand_setup(program);
99
+ await handlers_initialize(program);
100
+ return new Set(program.commands.map((cmd) => cmd.name()));
101
+ }
121
102
  /**
122
103
  * Extracts a leading `key=value` context string from an argv array.
123
104
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fnndsc/chili",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "ChILI handles Intelligent Line Interactions: A CLI and library for ChRIS, acting as the controller layer for business logic and user presentation.",
5
5
  "repository": {
6
6
  "type": "git",