@epilot/cli 0.1.108 → 0.1.110

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.
@@ -8,7 +8,7 @@ import {
8
8
  uploadDirectoryAsZip,
9
9
  uploadFileToPresignedUrl,
10
10
  writeManifest
11
- } from "./chunk-QOD77YLW.js";
11
+ } from "./chunk-UDGF4AVJ.js";
12
12
  import "./chunk-M3M3C5WH.js";
13
13
  import "./chunk-YHQA2AVG.js";
14
14
  import "./chunk-7ZQ666ZQ.js";
@@ -105,15 +105,67 @@ var deploy_default = defineCommand({
105
105
  log.warn(`Logo not found: ${logoPath}`);
106
106
  }
107
107
  }
108
- if (manifest.permissions?.length || manifest.blueprint?.manifest_id) {
108
+ let functionsPayload;
109
+ if (manifest.functions) {
110
+ functionsPayload = [];
111
+ for (const fn of manifest.functions) {
112
+ const handlerPath = resolve(manifestDir, fn.handler);
113
+ if (!existsSync(handlerPath)) {
114
+ log.error(`Handler not found for function "${fn.name}": ${handlerPath} \u2014 run "npm run build" first`);
115
+ process.exit(1);
116
+ }
117
+ const { handler, assets: fnAssets, ...rest } = fn;
118
+ const payload = { ...rest, code: readFileSync(handlerPath, "utf-8") };
119
+ if (fnAssets?.zip) {
120
+ const zipPath = resolve(manifestDir, fnAssets.zip);
121
+ if (!existsSync(zipPath)) {
122
+ log.warn(`Config UI directory not found for function "${fn.name}": ${zipPath} \u2014 skipping surface`);
123
+ } else if (dryRun) {
124
+ log.info(`[dry-run] Would zip and upload config UI for function ${fn.name}`);
125
+ } else {
126
+ const { upload_url, artifact_url } = await client.createZipUploadUrl(
127
+ appId,
128
+ targetVersion,
129
+ `fn-${fn.name}`
130
+ );
131
+ const zipSize = await uploadDirectoryAsZip(upload_url, zipPath);
132
+ log.success(`Uploaded config UI for function ${fn.name} (${formatFileSize(zipSize)})`);
133
+ payload.surfaces = {
134
+ flow_action_config: {
135
+ app_url: artifact_url.replace(/\/[^/]+$/, "/index.html"),
136
+ zip_url: artifact_url
137
+ }
138
+ };
139
+ }
140
+ }
141
+ functionsPayload.push(payload);
142
+ if (dryRun) {
143
+ log.info(
144
+ `[dry-run] Would deploy ${fn.type} function ${fn.name}${fn.schedule ? ` (schedule: ${fn.schedule})` : ""}`
145
+ );
146
+ }
147
+ }
148
+ }
149
+ if (manifest.permissions?.length || manifest.blueprint?.manifest_id || functionsPayload) {
109
150
  if (dryRun) {
110
151
  if (manifest.permissions?.length) {
111
- log.info("[dry-run] Would upsert app role in developer org");
152
+ log.info("[dry-run] Would upsert app role in developer org (if grants changed)");
112
153
  }
113
154
  log.info("[dry-run] Would update version permissions/blueprint");
114
155
  } else {
156
+ let grantsChanged = true;
157
+ if (!isNew && manifest.permissions?.length) {
158
+ try {
159
+ const remoteVersion = await client.getVersion(appId, targetVersion);
160
+ const remoteGrants = remoteVersion.role?.grants;
161
+ grantsChanged = normalizeGrants(remoteGrants) !== normalizeGrants(manifest.permissions);
162
+ } catch {
163
+ }
164
+ }
115
165
  let roleId;
116
- if (manifest.permissions?.length) {
166
+ if (manifest.permissions?.length && !grantsChanged) {
167
+ log.dim("Permissions unchanged \u2014 skipping grant re-provisioning");
168
+ } else if (manifest.permissions?.length) {
117
169
  const orgId = resolveOrgId(args.token, args.profile);
118
170
  if (orgId) {
119
171
  try {
@@ -131,11 +183,17 @@ var deploy_default = defineCommand({
131
183
  log.warn("Could not resolve org id \u2014 attaching grants without a developer-org role");
132
184
  }
133
185
  }
134
- await client.patchVersion(appId, targetVersion, {
135
- ...manifest.permissions?.length ? { grants: manifest.permissions, ...roleId ? { role_id: roleId } : {} } : {},
136
- ...manifest.blueprint?.manifest_id ? { manifest_id: manifest.blueprint.manifest_id } : {}
137
- });
138
- log.success(`Updated version ${targetVersion} (permissions/blueprint)`);
186
+ const sendGrants = Boolean(manifest.permissions?.length && grantsChanged);
187
+ if (sendGrants || manifest.blueprint?.manifest_id || functionsPayload) {
188
+ await client.patchVersion(appId, targetVersion, {
189
+ ...sendGrants ? { grants: manifest.permissions, ...roleId ? { role_id: roleId } : {} } : {},
190
+ ...manifest.blueprint?.manifest_id ? { manifest_id: manifest.blueprint.manifest_id } : {},
191
+ ...functionsPayload ? { functions: functionsPayload } : {}
192
+ });
193
+ log.success(
194
+ `Updated version ${targetVersion} (permissions/blueprint${functionsPayload ? `, ${functionsPayload.length} function(s)` : ""})`
195
+ );
196
+ }
139
197
  }
140
198
  }
141
199
  for (const comp of manifest.components) {
@@ -222,6 +280,24 @@ var deploy_default = defineCommand({
222
280
  }
223
281
  }
224
282
  }
283
+ if (!isNew) {
284
+ if (dryRun) {
285
+ log.info("[dry-run] Would re-sync the installation in this org (if installed)");
286
+ } else {
287
+ try {
288
+ const installation = await client.getInstallation(appId);
289
+ if (installation) {
290
+ await client.patchInstallation(appId, { version: targetVersion });
291
+ log.success(`Re-synced installation in this org to v${targetVersion}`);
292
+ log.warn(
293
+ "Re-syncing disables the installation \u2014 open the app in epilot (Settings \u2192 Apps) and save its configuration to re-enable it."
294
+ );
295
+ }
296
+ } catch (err) {
297
+ log.warn(`Could not re-sync installation: ${err.message}`);
298
+ }
299
+ }
300
+ }
225
301
  if (dryRun) {
226
302
  log.header("Dry run complete. No changes were made.");
227
303
  } else {
@@ -229,6 +305,11 @@ var deploy_default = defineCommand({
229
305
  }
230
306
  }
231
307
  });
308
+ function normalizeGrants(grants = []) {
309
+ return JSON.stringify(
310
+ grants.map((g) => ({ action: g.action, resource: g.resource ?? null })).sort((a, b) => `${a.action}|${a.resource}`.localeCompare(`${b.action}|${b.resource}`))
311
+ );
312
+ }
232
313
  export {
233
314
  deploy_default as default
234
315
  };
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createAppApiClient,
4
+ log,
5
+ readManifest
6
+ } from "./chunk-UDGF4AVJ.js";
7
+ import "./chunk-M3M3C5WH.js";
8
+ import "./chunk-YHQA2AVG.js";
9
+ import "./chunk-7ZQ666ZQ.js";
10
+
11
+ // src/commands/app/dev.ts
12
+ import { defineCommand } from "citty";
13
+ import { resolve } from "path";
14
+ var dev_default = defineCommand({
15
+ meta: { name: "dev", description: "Serve a component from localhost inside epilot (dev mode)" },
16
+ args: {
17
+ path: { type: "positional", description: "Path to manifest.json", required: false },
18
+ component: { type: "string", alias: "c", description: "Component to override (folder name in components/)" },
19
+ url: { type: "string", alias: "u", description: "Local dev server URL (default: http://localhost:5173)" },
20
+ off: { type: "boolean", description: "Disable dev mode and remove the override" },
21
+ token: { type: "string", alias: "t", description: "Bearer token" },
22
+ server: { type: "string", alias: "s", description: "Override server base URL" },
23
+ profile: { type: "string", description: "Use a named profile" }
24
+ },
25
+ run: async ({ args }) => {
26
+ const manifestPath = resolve(args.path ?? "manifest.json");
27
+ const manifest = readManifest(manifestPath);
28
+ const client = createAppApiClient({ token: args.token, server: args.server, profile: args.profile });
29
+ if (!manifest.app_id) {
30
+ log.error("No app_id in manifest \u2014 deploy the app once before using dev mode.");
31
+ process.exit(1);
32
+ }
33
+ const appId = manifest.app_id;
34
+ const overrideUrl = args.url ?? "http://localhost:5173";
35
+ const overridable = manifest.components.filter(
36
+ (c) => c.component_type === "CUSTOM_JOURNEY_BLOCK" || c.surfaces && Object.keys(c.surfaces).length > 0
37
+ );
38
+ let localComponent = args.component ? manifest.components.find((c) => c._dir === args.component) : void 0;
39
+ if (args.component && !localComponent) {
40
+ log.error(`Component "${args.component}" not found in manifest (expected its folder name in components/).`);
41
+ process.exit(1);
42
+ }
43
+ if (!localComponent) {
44
+ if (overridable.length === 1) {
45
+ localComponent = overridable[0];
46
+ } else if (overridable.length === 0) {
47
+ log.error(
48
+ "No overridable component found \u2014 dev mode works for UI components (capabilities, pages, portal blocks, journey blocks)."
49
+ );
50
+ process.exit(1);
51
+ } else {
52
+ log.error(
53
+ `Multiple UI components found \u2014 pick one with --component <name>: ${overridable.map((c) => c._dir).filter(Boolean).join(", ")}`
54
+ );
55
+ process.exit(1);
56
+ }
57
+ }
58
+ const config = await client.getConfiguration(appId);
59
+ const version = config.latest_version;
60
+ const remoteVersion = await client.getVersion(appId, version);
61
+ const remoteComponents = remoteVersion.components ?? [];
62
+ const remoteComponent = remoteComponents.find((c) => c.id === localComponent.id);
63
+ if (!remoteComponent) {
64
+ log.error(`Component ${localComponent.id} not found in deployed version ${version} \u2014 deploy first.`);
65
+ process.exit(1);
66
+ }
67
+ if (remoteComponent.component_type === "CUSTOM_JOURNEY_BLOCK") {
68
+ const configuration = remoteComponent.configuration ?? {};
69
+ if (args.off) {
70
+ delete configuration.override_dev_mode;
71
+ } else {
72
+ configuration.override_dev_mode = { override_url: overrideUrl };
73
+ }
74
+ remoteComponent.configuration = configuration;
75
+ } else {
76
+ const surfaces = remoteComponent.surfaces ?? {};
77
+ for (const surface of Object.values(surfaces)) {
78
+ if (surface && typeof surface === "object") {
79
+ if (args.off) {
80
+ delete surface.override_url;
81
+ } else {
82
+ surface.override_url = overrideUrl;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ await client.upsertComponent(appId, version, remoteComponent);
88
+ await client.patchMetadata(appId, { dev_mode: !args.off });
89
+ if (args.off) {
90
+ log.header("Dev mode disabled.");
91
+ log.dim("The component is served from the CDN again.");
92
+ } else {
93
+ log.header(`Dev mode enabled for ${localComponent._dir ?? localComponent.id}`);
94
+ log.info(`epilot now loads this component from ${overrideUrl}`);
95
+ log.info("");
96
+ log.info(` 1. cd components/${localComponent._dir ?? "<component>"} && npm run dev`);
97
+ log.info(" 2. Reload the page in epilot to see your changes");
98
+ log.info("");
99
+ log.dim("Turn off with: epilot app dev --off (required before cloning a new version)");
100
+ }
101
+ }
102
+ });
103
+ export {
104
+ dev_default as default
105
+ };
@@ -4,7 +4,7 @@ import {
4
4
  log,
5
5
  toManifest,
6
6
  writeManifest
7
- } from "./chunk-QOD77YLW.js";
7
+ } from "./chunk-UDGF4AVJ.js";
8
8
  import "./chunk-M3M3C5WH.js";
9
9
  import "./chunk-YHQA2AVG.js";
10
10
  import "./chunk-7ZQ666ZQ.js";
@@ -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.110";
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.110",
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
- };