@epilot/cli 0.1.108 → 0.1.109

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  log,
4
4
  writeManifest
5
- } from "./chunk-QOD77YLW.js";
5
+ } from "./chunk-UDGF4AVJ.js";
6
6
  import "./chunk-M3M3C5WH.js";
7
7
  import "./chunk-YHQA2AVG.js";
8
8
  import "./chunk-7ZQ666ZQ.js";
@@ -108,6 +108,9 @@ var init_default = defineCommand({
108
108
  "- `CUSTOM_FLOW_ACTION_EXTERNAL` \u2014 External integration webhook",
109
109
  "- `PORTAL_EXTENSION` \u2014 Portal extension hooks",
110
110
  "- `EXTERNAL_PRODUCT_CATALOG` \u2014 External product catalog hooks",
111
+ "",
112
+ "Inline in manifest.json (no directory):",
113
+ "- `API_PROXY` \u2014 Server-side API proxy with credential injection",
111
114
  ""
112
115
  ].join("\n")
113
116
  );
@@ -250,6 +253,21 @@ Deploy the app:
250
253
 
251
254
  The deploy command reads \`components/<dir>/configuration.json\` at deploy time and uses it as the component's configuration. This means you edit config in the component directory, not in manifest.json.
252
255
 
256
+ **Deploy does not update existing installations.** Installations keep a frozen snapshot of
257
+ components, options and grants. After deploying to an app that is already installed:
258
+
259
+ \`\`\`bash
260
+ epilot app api patchInstallation <appId> -d '{"version":"<version>"}'
261
+ \`\`\`
262
+
263
+ This refreshes components and grants **and preserves configured option values**
264
+ (uninstall/reinstall loses them; \`promote-to\` to the already-installed version is a silent
265
+ no-op). The patch resets \`enabled\` to \`false\` \u2014 an org admin must re-save the app
266
+ configuration in the epilot UI afterwards.
267
+
268
+ **Auth tokens expire after ~1 hour.** A mid-deploy 403 that reads like a permissions problem
269
+ is usually just an expired token \u2014 run \`epilot auth login\` and deploy again.
270
+
253
271
  ### \`epilot app export --app-id <id> [-o manifest.json]\`
254
272
  Export an existing app from the API as a manifest.json.
255
273
 
@@ -440,9 +458,95 @@ Hooks that provide products from an external catalog to epilot Journeys.
440
458
 
441
459
  **Hook types:** \`products\`, \`product-recommendations\`.
442
460
 
461
+ ### Inline components (no directory at all)
462
+
463
+ #### API_PROXY
464
+ A server-side proxy that lets your frontend components call an external API without exposing
465
+ credentials to the browser. Added with \`epilot app add-component <name> --type API_PROXY\`
466
+ (prompts for proxy name, target URL and auth type). Unlike every other component type it has
467
+ **no \`_dir\` and no folder** \u2014 it lives inline in \`manifest.json\`:
468
+
469
+ \`\`\`json
470
+ {
471
+ "component_type": "API_PROXY",
472
+ "configuration": {
473
+ "name": "sap",
474
+ "target": "https://my-gateway.example.com",
475
+ "auth_type": "header"
476
+ }
477
+ }
478
+ \`\`\`
479
+
480
+ Call it from the frontend with the \`@epilot/app-sdk\` helper (do not hand-roll fetch):
481
+
482
+ \`\`\`ts
483
+ import { proxy } from '@epilot/app-sdk';
484
+
485
+ const data = await proxy('sap', '/API_BUSINESS_PARTNER/A_BusinessPartner', {
486
+ appId,
487
+ token, // App Bridge session token
488
+ });
489
+ \`\`\`
490
+
491
+ Current limits \u2014 probe your target with curl through the proxy before building on it:
492
+
493
+ - **Query parameters are rejected** (400) by the proxy route's own request validation.
494
+ Workaround until fixed: percent-encode the \`?\` into the path
495
+ (\`/path%3F$expand=to_Customer\`) \u2014 fragile, leave a comment where you do it.
496
+ - Only **GET and POST** are proxied \u2014 no PUT/PATCH/DELETE.
497
+ - \`auth_type\` is \`none | header | bearer | oauth2\` \u2014 there is **no \`basic\`**. For HTTP Basic
498
+ targets, add one secret option holding a pre-computed Base64 \`user:pass\` and configure
499
+ \`"headers": { "Authorization": "Basic {{my_basic_secret}}" }\`.
500
+
501
+ ## Local Development (Dev Mode)
502
+
503
+ \`npm run dev\` alone is of limited use for capabilities/pages: outside the epilot iframe there
504
+ is no token and no entity. **Dev mode** makes epilot load your component from localhost inside
505
+ the real epilot UI (localhost is a trustworthy origin, so the HTTPS iframe allows it).
506
+
507
+ \`\`\`bash
508
+ epilot app dev # enable (default URL http://localhost:5173)
509
+ epilot app dev -c my-tab -u http://localhost:3000 # pick component and URL
510
+ epilot app dev --off # disable \u2014 required before cloning a version
511
+ \`\`\`
512
+
513
+ Then \`npm run dev\` in the component folder and reload the entity page in epilot.
514
+
515
+ If your CLI version predates \`app dev\`, it wraps these two raw API patches:
516
+
517
+ \`\`\`bash
518
+ # 1. Enable dev mode on the app
519
+ epilot app api patchMetadata <appId> -d '{"dev_mode": true}'
520
+
521
+ # 2. Add the override URL to the component.
522
+ # The PATCH does NOT merge \u2014 fetch the full component object first and re-send
523
+ # ALL of it with the override added, or you get: 400 must have required property 'id'.
524
+ epilot app api getConfiguration <appId>
525
+ epilot app api patchComponent <appId> <version> <componentId> -d '<full component + override>'
526
+ \`\`\`
527
+
528
+ The override location **depends on the component type**:
529
+
530
+ | Component type | Override field |
531
+ | --- | --- |
532
+ | \`CUSTOM_CAPABILITY\` (and other zip surfaces) | \`surfaces.capability_config.override_url\` |
533
+ | \`CUSTOM_JOURNEY_BLOCK\` | \`configuration.override_dev_mode.override_url\` |
534
+
535
+ Do not copy the journey-block shape onto a capability \u2014 it silently does nothing.
536
+
537
+ To leave dev mode, set \`dev_mode: false\` and remove the override (again re-sending the full
538
+ component).
539
+
540
+ Note: the dev-mode switch in the epilot portal UI is a different mechanism and does not write
541
+ these fields.
542
+
443
543
  ## Workflow: Building an App from Scratch
444
544
 
445
545
  \`\`\`bash
546
+ # 0. If the app calls an external API: probe it with curl FIRST.
547
+ # Get auth + one real response working in the terminal before writing any code.
548
+ # (Through the proxy too, once deployed \u2014 its limits bite real targets, see API_PROXY.)
549
+
446
550
  # 1. Scaffold the project
447
551
  epilot app init my-app
448
552
  cd my-app
@@ -492,16 +596,72 @@ For every component with a \`_dir\` field, the CLI reads \`components/<dir>/conf
492
596
  5. Injects the CDN URL into the component configuration/surfaces before upserting
493
597
 
494
598
  ### Permissions
495
- Apps can request permissions via the \`permissions\` array in the manifest. These are created as a role when the app is installed. Common actions: \`entity:read\`, \`entity:write\`, \`entity:delete\`, \`workflow:read\`, \`workflow:write\`.
599
+
600
+ Apps request permissions via the \`permissions\` array in the manifest. On installation, epilot
601
+ creates a role with these grants in the installing organization. Common actions:
602
+ \`entity:read\`, \`entity:write\`, \`entity:delete\`, \`workflow:read\`, \`workflow:write\`.
603
+
604
+ What permissions do and do not cover:
605
+
606
+ - They gate the app's **server-side** access to epilot APIs.
607
+ - They do **not** make the App Bridge token work against the Entity API \u2014 that call returns
608
+ 403 even with the permission granted. To show the current entity's data in a capability,
609
+ read it from the App Bridge context (see the App Bridge section); that path needs **no
610
+ permission at all**. Only request \`entity:*\` permissions for genuine server-side API use.
611
+ - **Every \`epilot app deploy\` re-patches the version's grants and desyncs existing
612
+ installations** \u2014 the installed app starts getting 403s while the permissions UI still
613
+ shows everything granted. After deploying to an already-installed app, re-sync with
614
+ \`epilot app api patchInstallation <appId> -d '{"version":"<version>"}'\`, then have an org
615
+ admin re-save the app configuration in epilot (the patch resets \`enabled\` to false).
496
616
 
497
617
  ### Options
498
- Components can declare \`options\` \u2014 configuration values set by the installing organization. Types: \`text\`, \`number\`, \`boolean\`, \`secret\`. Secret values are encrypted and never included in the manifest. Use \`{{option_key}}\` in configuration URLs/headers for interpolation.
618
+ Components can declare \`options\` \u2014 configuration values set by the installing organization. Types: \`text\`, \`number\`, \`boolean\`, \`secret\`. Use \`{{option_key}}\` in configuration URLs/headers for interpolation.
619
+
620
+ Two caveats:
621
+ - **Options merge on upsert.** Deploying a component without an \`options\` key leaves the old
622
+ options in place, so removed options stay visible to installing admins. Send \`"options": []\`
623
+ once to clear them, then patch the installation.
624
+ - **"Secret" means not delivered to the app's browser code.** Secret values are never stored
625
+ in the manifest and are injected server-side \u2014 but organization admins with API access can
626
+ read stored values back. Do not describe them to users as unreadable.
499
627
 
500
628
  ### Descriptions
501
629
  All user-facing text (app name, component names, descriptions) must include a \`de\` (German) translation. \`en\` is optional but recommended.
502
630
 
503
631
  ### App Bridge
504
- Components that render inside epilot (capabilities, pages, portal blocks) use \`@epilot/app-bridge\` to communicate with the parent window. The bridge provides an auth token and language setting. Always wrap your React app in an \`AppBridgeProvider\`.
632
+
633
+ Capabilities, pages and portal blocks run in an iframe and talk to epilot via
634
+ \`@epilot/app-bridge\`. Wrap your app in an \`AppBridgeProvider\`.
635
+
636
+ **The context already contains the entity.** \`getEntityContext()\` returns the full entity
637
+ next to its id (the \`EntityContext\` type may not declare the \`entity\` field yet \u2014 it is
638
+ there at runtime). A tab that renders entity data needs no Entity API call and no
639
+ \`entity:read\` permission:
640
+
641
+ \`\`\`ts
642
+ const ctx = await getEntityContext()
643
+ ctx.entityId // '53a9f8c4-\u2026'
644
+ (ctx as any).entity // the whole entity \u2014 prefer this over fetching it
645
+ \`\`\`
646
+
647
+ Prefer this over the Entity API: it is one round-trip cheaper, and app tokens are **not**
648
+ guaranteed to authorise direct Entity API calls (expect 403 there even when the permission
649
+ shows as granted).
650
+
651
+ **\`initialize()\` makes one attempt and rejects after \`timeout\` (default 5000 ms).** There is
652
+ no internal retry, so if the parent is not listening yet the message is lost. Retry 2\u20133 times
653
+ before concluding you are outside epilot, and keep the session token even when a later
654
+ context request fails \u2014 otherwise "epilot answered oddly" is indistinguishable from "no epilot".
655
+
656
+ **Report your height.** The iframe does not auto-size. Call \`updateContentHeight(px)\` from a
657
+ \`ResizeObserver\` on \`document.body\`. Do **not** set \`html, body, #root { height: 100% }\` while
658
+ measuring \`scrollHeight\` \u2014 the body then can never exceed the iframe, so the reported height
659
+ never grows and the tab stays a small scrolling box.
660
+
661
+ **Refresh on becoming visible.** \`onVisibilityChange(cb)\` fires when the user switches tabs.
662
+
663
+ **Debugging.** Your console output goes to the iframe's JS context (\`cdn.app.sls.epilot.io\`),
664
+ not \`top\`. Switch the DevTools context selector or you will see nothing and assume nothing ran.
505
665
 
506
666
  ### Volt UI
507
667
  Use \`@epilot/volt-ui\` for UI components in App Bridge surfaces (capabilities, pages, portal blocks). It provides cards, buttons, forms, selectors, and more \u2014 consistent with epilot's design system.
@@ -3,7 +3,7 @@ import {
3
3
  log,
4
4
  readManifest,
5
5
  writeManifest
6
- } from "./chunk-QOD77YLW.js";
6
+ } from "./chunk-UDGF4AVJ.js";
7
7
  import "./chunk-M3M3C5WH.js";
8
8
  import "./chunk-YHQA2AVG.js";
9
9
  import "./chunk-7ZQ666ZQ.js";
@@ -3,7 +3,7 @@ import {
3
3
  createAppApiClient,
4
4
  log,
5
5
  readManifest
6
- } from "./chunk-QOD77YLW.js";
6
+ } from "./chunk-UDGF4AVJ.js";
7
7
  import "./chunk-M3M3C5WH.js";
8
8
  import "./chunk-YHQA2AVG.js";
9
9
  import "./chunk-7ZQ666ZQ.js";
@@ -72,7 +72,7 @@ ${GREEN}${BOLD}Upgraded to @epilot/cli@${latest}${RESET}
72
72
  }
73
73
  });
74
74
  var getCurrentVersion = () => {
75
- if (true) return "0.1.108";
75
+ if (true) return "0.1.109";
76
76
  try {
77
77
  const output = execSync("npm ls -g @epilot/cli --depth=0 --json 2>/dev/null", {
78
78
  encoding: "utf-8",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  log,
4
4
  validateManifest
5
- } from "./chunk-QOD77YLW.js";
5
+ } from "./chunk-UDGF4AVJ.js";
6
6
  import "./chunk-M3M3C5WH.js";
7
7
  import "./chunk-YHQA2AVG.js";
8
8
  import "./chunk-7ZQ666ZQ.js";
@@ -40,6 +40,14 @@ var validate_default = defineCommand({
40
40
  const manifest = result.manifest;
41
41
  log.success(`${manifestPath} is valid`);
42
42
  log.info(`${manifest.components.length} component(s) defined`);
43
+ if (manifest.functions?.length) {
44
+ const scheduled = manifest.functions.filter((f) => f.type === "scheduled");
45
+ const workflow = manifest.functions.filter((f) => f.type === "workflow");
46
+ log.info(`${manifest.functions.length} function(s): ${workflow.length} workflow, ${scheduled.length} scheduled`);
47
+ for (const fn of scheduled) {
48
+ log.dim(`${fn.name}: ${fn.schedule}${fn.schedule_timezone ? ` (${fn.schedule_timezone})` : ""}`);
49
+ }
50
+ }
43
51
  const secretOptions = manifest.components.flatMap((c) => (c.options ?? []).filter((o) => o.type === "secret"));
44
52
  if (secretOptions.length > 0) log.info(`${secretOptions.length} secret option(s) (set per-installation)`);
45
53
  if (manifest.assets?.logo) log.info(`Logo: ${manifest.assets.logo}`);
@@ -3,7 +3,7 @@ import {
3
3
  createAppApiClient,
4
4
  log,
5
5
  readManifest
6
- } from "./chunk-QOD77YLW.js";
6
+ } from "./chunk-UDGF4AVJ.js";
7
7
  import "./chunk-M3M3C5WH.js";
8
8
  import "./chunk-YHQA2AVG.js";
9
9
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epilot/cli",
3
- "version": "0.1.108",
3
+ "version": "0.1.109",
4
4
  "description": "CLI for epilot APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/commands/app/index.ts
4
- import { defineCommand } from "citty";
5
- var app_default = defineCommand({
6
- meta: {
7
- name: "app",
8
- description: "Manage epilot Apps \u2014 create, deploy, and manage app manifests"
9
- },
10
- subCommands: {
11
- init: () => import("./init-BXAGJAPS.js").then((m) => m.default),
12
- "add-component": () => import("./add-component-UPHG3VNG.js").then((m) => m.default),
13
- "remove-component": () => import("./remove-component-LPTHVN4P.js").then((m) => m.default),
14
- validate: () => import("./validate-TLSOTJAY.js").then((m) => m.default),
15
- deploy: () => import("./deploy-NRQHZ635.js").then((m) => m.default),
16
- export: () => import("./export-ZRDJCALM.js").then((m) => m.default),
17
- versions: () => import("./versions-VA4H3EPK.js").then((m) => m.default),
18
- review: () => import("./review-OZTM3XBD.js").then((m) => m.default),
19
- api: () => import("./api-5W2UMWCW.js").then((m) => m.default)
20
- }
21
- });
22
- export {
23
- app_default as default
24
- };