@pptb/types 1.2.2 → 1.2.3-beta.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 CHANGED
@@ -119,6 +119,7 @@ In addition to `package.json`, the validator automatically checks a `pptb.config
119
119
  | Field | Required | Rules |
120
120
  | --------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
121
121
  | `invocation.version` | ✅\*\* | Must be a valid **semantic version** string (e.g. `"1.0.0"`). Tool developers own this version and bump it when the invocation contract changes. |
122
+ | `invocation.capabilities` | ❌ | Array of non-empty string tags (e.g. `["entity-picker"]`). Used by callers to discover this tool via `findToolsByCapability`. |
122
123
  | `invocation.prefill` | ❌ | JSON-schema-style object describing data callers can pre-populate |
123
124
  | `invocation.prefill.properties` | ❌ | Map of property names to `{ type?, enum?, items? }` descriptors |
124
125
  | `invocation.returnTopic` | ❌ | JSON-schema-style object describing the data this tool returns to its caller |
@@ -132,6 +133,7 @@ In addition to `package.json`, the validator automatically checks a `pptb.config
132
133
  {
133
134
  "invocation": {
134
135
  "version": "1.0.0",
136
+ "capabilities": ["entity-picker"],
135
137
  "prefill": {
136
138
  "properties": {
137
139
  "entityName": { "type": "string" },
@@ -255,8 +257,8 @@ const terminal = await toolboxAPI.terminal.create({
255
257
  cwd: "/path/to/directory",
256
258
  });
257
259
 
258
- // Execute a command
259
- const result = await toolboxAPI.terminal.execute(terminal.id, "npm install");
260
+ // Execute a command (most commands are allowed; shells and privilege-escalation tools are blocked)
261
+ const result = await toolboxAPI.terminal.execute(terminal.id, "pac auth list");
260
262
  console.log("Exit code:", result.exitCode);
261
263
  console.log("Output:", result.output);
262
264
 
@@ -296,13 +298,29 @@ Tools can launch one another and pass data between them using the `invocation` n
296
298
 
297
299
  ```typescript
298
300
  // Tool A – launches the entity-picker tool and waits for a selection
301
+ // The callee automatically inherits this tool's FXS connection
299
302
  const result = await toolboxAPI.invocation.launchTool(
300
303
  "@my-org/entity-picker",
301
304
  { entityName: "account", allowMultiSelect: false },
302
305
  );
303
306
 
304
- if (result) {
307
+ if (result !== null) {
305
308
  console.log("Selected record id:", (result as { selectedId: string }).selectedId);
309
+ } else {
310
+ // User dismissed the picker (closed window or clicked "Return to Caller" banner)
311
+ }
312
+ ```
313
+
314
+ > **One-at-a-time**: only one active callee per caller is supported. A second `launchTool` call while a callee is open throws `"A callee invocation is already in progress"`.
315
+
316
+ #### Caller: tag-based capability discovery
317
+
318
+ ```typescript
319
+ // Find all installed tools that declare the "entity-picker" capability
320
+ const pickers = await toolboxAPI.invocation.findToolsByCapability("entity-picker");
321
+ if (pickers.length > 0) {
322
+ const picker = pickers[0] as { id: string };
323
+ const result = await toolboxAPI.invocation.launchTool(picker.id, { entityName: "account" });
306
324
  }
307
325
  ```
308
326
 
@@ -317,12 +335,17 @@ if (ctx) {
317
335
 
318
336
  // When the user makes their selection:
319
337
  await toolboxAPI.invocation.returnData({ selectedId: "a1b2c3...", selectedName: "Contoso" });
338
+ // PPTB automatically closes this window after delivering the result
320
339
  }
321
340
  ```
322
341
 
323
342
  > **Tip:** A tool that was *not* launched by another tool receives `null` from `getLaunchContext()`.
324
343
  > Use this to show a standalone UI or redirect accordingly.
325
344
 
345
+ > **Auto-close**: after calling `returnData`, PPTB automatically closes the callee window. The callee does **not** need to close itself.
346
+
347
+ > **Banner early-return**: PPTB injects a "Return to [CallerToolName]" banner in the callee window. If the user clicks it before `returnData` is called, the caller's Promise resolves with `null` and the callee window is closed.
348
+
326
349
  #### Declaring your invocation contract
327
350
 
328
351
  Add a `pptb.config.json` alongside your `package.json` to tell callers what data you expect and return:
@@ -331,6 +354,7 @@ Add a `pptb.config.json` alongside your `package.json` to tell callers what data
331
354
  {
332
355
  "invocation": {
333
356
  "version": "1.0.0",
357
+ "capabilities": ["entity-picker"],
334
358
  "prefill": {
335
359
  "properties": {
336
360
  "entityName": { "type": "string" },
@@ -557,9 +581,13 @@ Core platform features organized into namespaces:
557
581
 
558
582
  - **create(options: TerminalOptions)**: Promise<Terminal>
559
583
  - Creates a new terminal attached to the tool (tool ID is auto-determined)
584
+ - Uses the preferred shell specified via `TerminalOptions.shell` if it is installed on the machine, otherwise falls back to the system default shell
560
585
 
561
586
  - **execute(terminalId: string, command: string)**: Promise<TerminalCommandResult>
562
587
  - Executes a command in the specified terminal and returns its result
588
+ - Only commands that are **not** on the blocked list are executed; blocked commands are shell interpreters (`bash`, `sh`, `powershell`, `cmd`, etc.) and privilege-escalation tools (`sudo`, `su`, `runas`, etc.)
589
+ - `npx --shell/-c` flags are blocked to prevent shell pivot via npx; unquoted command substitution (`$(…)` and backticks) is also rejected
590
+ - Compound commands using `&&`, `||`, `;`, or `|` are supported — each segment is individually validated against the blocklist
563
591
 
564
592
  - **close(terminalId: string)**: Promise<void>
565
593
  - Closes the specified terminal
@@ -602,12 +630,17 @@ Core platform features organized into namespaces:
602
630
  - Returns the prefill data passed by the tool that launched this tool, or `null` when not launched via inter-tool invocation
603
631
 
604
632
  - **returnData(returnData: Record\<string, unknown\>)**: Promise\<void\>
605
- - Sends data back to the caller tool and signals completion; no-op if not launched by another tool
633
+ - Sends data back to the caller tool and signals completion; PPTB **automatically closes the callee window** after delivery; no-op if not launched by another tool
606
634
 
607
635
  - **launchTool(targetToolId, prefillData?, options?)**: Promise\<unknown\>
608
636
  - Launches the specified tool, optionally with prefill data
609
- - Returns a Promise that resolves with the data returned by the callee (or `null` if it closes without returning)
610
- - `options.primaryConnectionId` / `options.secondaryConnectionId` override connection for the callee
637
+ - Returns a Promise that resolves with the data returned by the callee, or `null` if it closes without returning or the user clicks the "Return to Caller" banner
638
+ - The callee automatically inherits the caller's FXS connection; pass `options.primaryConnectionId` to override
639
+ - Only one active callee per caller is allowed; throws `"A callee invocation is already in progress"` if a callee is already open
640
+ - Pass `options.noReturn: true` for one-way "Send To" flows; the banner is suppressed entirely for the callee
641
+
642
+ - **findToolsByCapability(tag: string)**: Promise\<unknown[]\>
643
+ - Returns all installed tools that declare the given capability tag in their `pptb.config.json`
611
644
 
612
645
  ### Dataverse API (`window.dataverseAPI`)
613
646
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  const fs = require("fs");
18
18
  const path = require("path");
19
- const { validatePackageJson, validatePPTBConfig } = require("../lib/validate");
19
+ const { validatePackageJson, validatePPTBConfig, KNOWN_CAPABILITY_TAGS } = require("../lib/validate");
20
20
 
21
21
  // ANSI colour helpers – gracefully degrade when colours are unsupported
22
22
  const NO_COLOR = !process.stdout.isTTY || process.env.NO_COLOR;
@@ -219,9 +219,15 @@ async function main() {
219
219
  console.log(` Features : multiConnection=${info.features.multiConnection}${info.features.minAPI ? `, minAPI=${info.features.minAPI}` : ""}`);
220
220
  }
221
221
  if (configResult !== null && configResult.packageInfo && configResult.packageInfo.invocation) {
222
- console.log(` Invocation : version=${configResult.packageInfo.invocation.version}`);
222
+ const inv = configResult.packageInfo.invocation;
223
+ console.log(` Invocation : version=${inv.version}`);
224
+ if (Array.isArray(inv.capabilities) && inv.capabilities.length > 0) {
225
+ console.log(` Capabilities: ${inv.capabilities.join(", ")}`);
226
+ }
223
227
  }
224
228
  console.log();
229
+ console.log(c.dim(`Known capability tags: ${KNOWN_CAPABILITY_TAGS.join(", ")}`));
230
+ console.log();
225
231
  } else {
226
232
  console.log(c.red(c.bold("✖ Validation failed")));
227
233
  console.log();
package/lib/validate.js CHANGED
@@ -55,6 +55,30 @@ const VALID_MULTI_CONNECTION_VALUES = ["required", "optional", "none"];
55
55
  // Semver regex for minAPI validation
56
56
  const SEMVER_REGEX = /^\d+\.\d+\.\d+(-[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/;
57
57
 
58
+ /**
59
+ * Built-in list of well-known capability tags.
60
+ * Mirrors the entries in the Supabase `capability_tags` table.
61
+ * New tags are added to Supabase first (configurable without a deploy) and are
62
+ * then reflected here in the next package release so offline validation stays current.
63
+ *
64
+ * When a tool declares a tag that is not in this list the validator emits a warning
65
+ * (not an error) so existing tools are not broken by registry updates.
66
+ *
67
+ * **Keep in sync with `BUILT_IN_CAPABILITY_TAGS` in
68
+ * `src/main/managers/toolRegistryManager.ts`.** Because this file is a standalone
69
+ * Node.js module that cannot import from the Electron/TypeScript source tree, both
70
+ * lists must be updated together whenever a new tag is added.
71
+ */
72
+ const KNOWN_CAPABILITY_TAGS = [
73
+ "fetchxml",
74
+ "entity-picker",
75
+ "record-selector",
76
+ "solution-selector",
77
+ "webresource-editor",
78
+ "plugin-inspector",
79
+ "pcf-control-builder",
80
+ ];
81
+
58
82
  /**
59
83
  * Checks if a string is a valid URL.
60
84
  * @param {string} url
@@ -334,7 +358,7 @@ async function validatePackageJson(packageJson, options = {}) {
334
358
  };
335
359
  }
336
360
 
337
- module.exports = { validatePackageJson, validatePPTBConfig, isValidUrl, APPROVED_LICENSES };
361
+ module.exports = { validatePackageJson, validatePPTBConfig, isValidUrl, APPROVED_LICENSES, KNOWN_CAPABILITY_TAGS };
338
362
 
339
363
  /**
340
364
  * Validates a tool's pptb.config.json against the official review criteria.
@@ -390,6 +414,25 @@ function validatePPTBConfig(config) {
390
414
  validateJsonSchemaProperties("invocation.returnTopic", inv.returnTopic.properties, errors);
391
415
  }
392
416
  }
417
+
418
+ // invocation.capabilities – optional array of non-empty strings
419
+ if (inv.capabilities !== undefined) {
420
+ if (!Array.isArray(inv.capabilities)) {
421
+ errors.push("invocation.capabilities must be an array");
422
+ } else {
423
+ inv.capabilities.forEach((cap, idx) => {
424
+ if (typeof cap !== "string" || cap.trim().length === 0) {
425
+ errors.push(`invocation.capabilities[${idx}] must be a non-empty string`);
426
+ } else if (!KNOWN_CAPABILITY_TAGS.includes(cap.trim())) {
427
+ warnings.push(
428
+ `invocation.capabilities[${idx}] "${cap}" is not a recognised capability tag. ` +
429
+ `Known tags: ${KNOWN_CAPABILITY_TAGS.join(", ")}. ` +
430
+ "If this is a new tag, ensure it has been added to the capability registry.",
431
+ );
432
+ }
433
+ });
434
+ }
435
+ }
393
436
  }
394
437
  }
395
438
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pptb/types",
3
- "version": "1.2.2",
3
+ "version": "1.2.3-beta.0",
4
4
  "description": "Type definitions for Power Platform ToolBox APIs and validity checks for tool packages",
5
5
  "main": "index.d.ts",
6
6
  "types": "index.d.ts",
package/pptbConfig.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * {
12
12
  * "invocation": {
13
13
  * "version": "1.0.0",
14
+ * "capabilities": ["fetchxml"],
14
15
  * "prefill": {
15
16
  * "properties": {
16
17
  * "entityName": { "type": "string" },
@@ -29,6 +30,50 @@
29
30
  * ```
30
31
  */
31
32
 
33
+ /**
34
+ * Well-known capability tags defined in the Power Platform ToolBox capability registry.
35
+ *
36
+ * Using one of these values provides IDE auto-complete and ensures compatibility with the
37
+ * official capability discovery system (`toolboxAPI.invocation.findToolsByCapability`).
38
+ * The authoritative list is maintained in the Supabase `capability_tags` table so new
39
+ * tags can be added without an app update. Fetch the current list at runtime via
40
+ * `toolboxAPI.invocation.getKnownCapabilityTags()`.
41
+ *
42
+ * | Tag | Description |
43
+ * | -------------------- | ----------------------------------------------------- |
44
+ * | `fetchxml` | Accept or process FetchXML queries |
45
+ * | `entity-picker` | Browse and select a Dataverse entity (table) |
46
+ * | `record-selector` | Browse and select a Dataverse record |
47
+ * | `solution-selector` | Pick a Power Platform solution |
48
+ * | `webresource-editor` | Edit or manage web resources |
49
+ * | `plugin-inspector` | Inspect or manage plugins and assemblies |
50
+ * | `pcf-control-builder`| Build or scaffold PCF controls |
51
+ */
52
+ export type KnownCapabilityTag =
53
+ | "fetchxml"
54
+ | "entity-picker"
55
+ | "record-selector"
56
+ | "solution-selector"
57
+ | "webresource-editor"
58
+ | "plugin-inspector"
59
+ | "pcf-control-builder";
60
+
61
+ /**
62
+ * A capability tag string accepted by `invocation.capabilities` and
63
+ * `toolboxAPI.invocation.findToolsByCapability()`.
64
+ *
65
+ * `KnownCapabilityTag` values offer IDE auto-complete and are validated by
66
+ * `pptb-validate`. Custom strings are permitted for organisation-internal tags,
67
+ * but will produce a validation warning unless the tag appears in the official
68
+ * capability registry.
69
+ *
70
+ * @example
71
+ * ```json
72
+ * { "invocation": { "version": "1.0.0", "capabilities": ["fetchxml", "entity-picker"] } }
73
+ * ```
74
+ */
75
+ export type CapabilityTag = KnownCapabilityTag | (string & {});
76
+
32
77
  /** A JSON-schema-style property descriptor used inside invocation definitions. */
33
78
  export interface JsonSchemaProperty {
34
79
  /** The JSON type of the value (e.g. "string", "number", "boolean", "object", "array"). */
@@ -67,6 +112,24 @@ export interface InvocationConfig {
67
112
  prefill?: JsonSchemaObject;
68
113
  /** Schema of the data this tool returns to its caller on completion. */
69
114
  returnTopic?: JsonSchemaObject;
115
+ /**
116
+ * Capability tags declared by this tool.
117
+ *
118
+ * Callers use `toolboxAPI.invocation.findToolsByCapability(tag)` to discover tools
119
+ * that advertise a given capability. Prefer `KnownCapabilityTag` values for IDE
120
+ * auto-complete; custom strings are accepted but will produce a `pptb-validate`
121
+ * warning unless the tag is present in the official capability registry.
122
+ *
123
+ * Use `toolboxAPI.invocation.getKnownCapabilityTags()` at runtime to retrieve the
124
+ * full list from the registry (backed by a configurable Supabase table so new tags
125
+ * can be added without an app update).
126
+ *
127
+ * @example
128
+ * ```json
129
+ * { "invocation": { "version": "1.0.0", "capabilities": ["entity-picker", "fetchxml"] } }
130
+ * ```
131
+ */
132
+ capabilities?: CapabilityTag[];
70
133
  }
71
134
 
72
135
  /**
package/toolboxAPI.d.ts CHANGED
@@ -82,7 +82,10 @@ declare namespace ToolBoxAPI {
82
82
  name: string;
83
83
  url: string;
84
84
  environment: "Dev" | "Test" | "UAT" | "Production";
85
- createdAt: string;
85
+ category?: string;
86
+ environmentColor?: string;
87
+ categoryColor?: string;
88
+ createdAt?: string;
86
89
  lastUsedAt?: string;
87
90
  /**
88
91
  * @deprecated isActive is a legacy field that is no longer persisted.
@@ -109,9 +112,9 @@ declare namespace ToolBoxAPI {
109
112
  */
110
113
  export interface TerminalOptions {
111
114
  name: string;
112
- shell?: string;
115
+ shell?: string; // Preferred shell executable (e.g. "pwsh", "/bin/zsh"). Falls back to the system default if the requested shell is not found on the machine.
113
116
  cwd?: string;
114
- env?: Record<string, string>;
117
+ env?: Record<string, string>; // PATH-like and shell bootstrap variables are filtered for tool security
115
118
  visible?: boolean; // Whether terminal should be visible initially (default: true)
116
119
  }
117
120
 
@@ -455,9 +458,9 @@ declare namespace ToolBoxAPI {
455
458
  * Returns data back to the caller tool that launched this tool.
456
459
  *
457
460
  * The value resolves the `Promise` returned by the caller's
458
- * `invocation.launchTool()` call. After calling `returnData`, the PPTB host
459
- * will notify the caller; it is the callee's responsibility to close itself (or
460
- * update its UI) after the return.
461
+ * `invocation.launchTool()` call. **After calling `returnData`, PPTB
462
+ * automatically closes the callee window** the callee does not need to
463
+ * close itself.
461
464
  *
462
465
  * If this tool was not launched by another tool, the call is a no-op.
463
466
  *
@@ -469,17 +472,62 @@ declare namespace ToolBoxAPI {
469
472
  * Launch another tool from within this tool and (optionally) pass prefill data.
470
473
  *
471
474
  * Returns a Promise that resolves with the data the target tool sends via
472
- * `invocation.returnData()`, or `null` if the target tool closes without
473
- * returning any data.
475
+ * `invocation.returnData()`, or `null` if:
476
+ * - the target tool closes without calling `returnData`, or
477
+ * - the user clicks the "Return to [this tool]" banner before the callee finalises.
478
+ *
479
+ * **One-at-a-time**: only one active callee per caller is supported. A second
480
+ * call while a callee is active throws `"A callee invocation is already in progress"`.
481
+ *
482
+ * **Connection auto-inheritance**: when `options.primaryConnectionId` is omitted,
483
+ * the callee automatically inherits the caller's active FXS connection.
484
+ *
485
+ * **Multi-connection auto-prompt**: when the callee declares
486
+ * `features.multiConnection: "required"` or `"optional"` and
487
+ * `options.secondaryConnectionId` is not provided, PPTB automatically shows
488
+ * the multi-connection selector before launching the callee. The Promise rejects
489
+ * if the user cancels the selector.
474
490
  *
475
- * The target tool must be installed and its `pptb.config.json` must declare an
476
- * `invocation.prefill` schema that matches the shape of `prefillData`.
491
+ * **`noReturn`**: pass `true` when the caller does not expect the callee to
492
+ * return data (e.g. a "Send To" pattern where data is only sent one-way).
493
+ * When set, the "Return to [Caller]" banner is suppressed entirely for the callee.
494
+ * The invocation lifecycle is otherwise identical — the Promise still resolves
495
+ * with `null` when the callee closes.
477
496
  *
478
497
  * @param targetToolId The npm package name (toolId) of the tool to launch
479
498
  * @param prefillData Data to pre-populate the target tool's state
480
- * @param options Optional connection overrides for the target tool
499
+ * @param options Optional connection overrides and launch flags
500
+ */
501
+ launchTool: (targetToolId: string, prefillData?: Record<string, unknown>, options?: { primaryConnectionId?: string | null; secondaryConnectionId?: string | null; noReturn?: boolean }) => Promise<unknown>;
502
+
503
+ /**
504
+ * Find installed tools that declare a given capability tag in their
505
+ * `pptb.config.json` (`invocation.capabilities` array).
506
+ *
507
+ * Use a `KnownCapabilityTag` literal from `@pptb/types` for IDE auto-complete:
508
+ * ```ts
509
+ * import type { KnownCapabilityTag } from "@pptb/types/pptbConfig";
510
+ * const tools = await toolboxAPI.invocation.findToolsByCapability("fetchxml");
511
+ * ```
512
+ *
513
+ * @param tag The capability tag to search for (e.g. `"entity-picker"`)
514
+ * @returns Array of matching installed `ToolManifest` objects
515
+ */
516
+ findToolsByCapability: (tag: import("./pptbConfig").CapabilityTag) => Promise<unknown[]>;
517
+
518
+ /**
519
+ * Returns the list of known (registered) capability tags from the capability registry.
520
+ *
521
+ * The registry is stored in a Supabase `capability_tags` table and fetched at
522
+ * startup (cached for 5 minutes). When Supabase is unavailable a built-in
523
+ * fallback list is returned, so the result is never empty.
524
+ *
525
+ * Use this at runtime to populate a "capabilities" picker or to validate a tag
526
+ * before calling `findToolsByCapability`.
527
+ *
528
+ * @returns Array of `{ tag: string; description: string }` entries ordered by tag name.
481
529
  */
482
- launchTool: (targetToolId: string, prefillData?: Record<string, unknown>, options?: { primaryConnectionId?: string | null; secondaryConnectionId?: string | null }) => Promise<unknown>;
530
+ getKnownCapabilityTags: () => Promise<Array<{ tag: string; description: string }>>;
483
531
  }
484
532
 
485
533
  /**