@lunnoa/client 2.2.0 → 2.4.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
@@ -264,7 +264,7 @@ npx @lunnoa/client agents deploy ./agents/triage.ts \
264
264
  --url https://lunnoa.example --api-key lna_...
265
265
  ```
266
266
 
267
- `defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
267
+ `defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. `defineWorkflow` takes an optional `defineTrigger`. Omit it for Manually Run with no input fields. `runs.start` ignores the trigger and runs the steps only. Pass data into step settings with `ref.trigger('caseId')` or `ref.step('load', 'rows', 0)` (give the source step a `key`); the server resolves them on deploy and the builder shows them as mapped fields. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
268
268
 
269
269
  ### CLI discovery
270
270
 
@@ -284,7 +284,7 @@ npx @lunnoa/client entity-types list --json
284
284
  npx @lunnoa/client features --json
285
285
  ```
286
286
 
287
- `tools list` is compact (`appId`, `actionId`, name, flags). `tools get` prints `inputConfig` and a `defineAction({ id, appId, input })` sketch. `ai-connections list --json` prints the model ids each AI connection accepts; use `models[].id` as `defineAgent({ model })`. Auth flags and `LUNNOA_URL` / `LUNNOA_API_KEY` / `lunnoa.config` work the same as deploy. The CLI also reads `./.env` (real environment variables win).
287
+ `tools list` is compact (`appId`, `actionId`, name, flags). `tools get` prints `inputConfig` and a `defineAction({ id, appId, input })` sketch. `apps get --json` includes each trigger and a `defineTrigger` sketch. Use `triggers[].triggerId`. Do not invent trigger ids. `ai-connections list --json` prints the model ids each AI connection accepts; use `models[].id` as `defineAgent({ model })`. Auth flags and `LUNNOA_URL` / `LUNNOA_API_KEY` / `lunnoa.config` work the same as deploy. The CLI also reads `./.env` (real environment variables win).
288
288
 
289
289
  ## Regenerating the API types
290
290
 
package/dist/cli.js CHANGED
@@ -541,6 +541,7 @@ var DEFINE_KINDS = {
541
541
  connection: "connection",
542
542
  aiConnection: "aiConnection",
543
543
  action: "action",
544
+ trigger: "trigger",
544
545
  workflow: "workflow",
545
546
  agent: "agent",
546
547
  entityType: "entityType"
@@ -563,6 +564,40 @@ function resolveActionAppId(action) {
563
564
  `Cannot infer appId for action "${id}". Pass appId explicitly.`
564
565
  );
565
566
  }
567
+ function resolveTriggerAppId(trigger) {
568
+ if (trigger.appId?.trim()) {
569
+ return trigger.appId.trim();
570
+ }
571
+ const id = trigger.id.trim();
572
+ const marker = "_trigger_";
573
+ const markerIndex = id.indexOf(marker);
574
+ if (markerIndex > 0) {
575
+ return id.slice(0, markerIndex);
576
+ }
577
+ const dot = id.indexOf(".");
578
+ if (dot > 0) {
579
+ return id.slice(0, dot);
580
+ }
581
+ throw new Error(
582
+ `Cannot infer appId for trigger "${id}". Pass appId explicitly.`
583
+ );
584
+ }
585
+ function resolveTriggerId(trigger) {
586
+ const id = trigger.id.trim();
587
+ const appId = trigger.appId?.trim();
588
+ if (appId && id.startsWith(`${appId}.`)) {
589
+ return id.slice(appId.length + 1);
590
+ }
591
+ const marker = "_trigger_";
592
+ if (id.includes(marker)) {
593
+ return id;
594
+ }
595
+ const dot = id.indexOf(".");
596
+ if (dot > 0) {
597
+ return id.slice(dot + 1);
598
+ }
599
+ return id;
600
+ }
566
601
  function resolveActionId(action) {
567
602
  const id = action.id.trim();
568
603
  const appId = action.appId?.trim();
@@ -1775,7 +1810,9 @@ function stepFromDefinition(step) {
1775
1810
  ...use.input ?? {},
1776
1811
  ...step.input ?? {}
1777
1812
  };
1813
+ const key = step.key;
1778
1814
  return {
1815
+ ...key ? { key } : {},
1779
1816
  appId: resolveActionAppId(use),
1780
1817
  actionId: resolveActionId(use),
1781
1818
  ...use.connection?.id ? { connectionId: use.connection.id } : {},
@@ -1977,9 +2014,21 @@ var WorkflowAppsResource = class {
1977
2014
  };
1978
2015
 
1979
2016
  // src/resources/workflows.ts
2017
+ function triggerToUpsertInput(trigger) {
2018
+ return {
2019
+ triggerId: resolveTriggerId(trigger),
2020
+ appId: resolveTriggerAppId(trigger),
2021
+ ...trigger.connection?.id ? { connectionId: trigger.connection.id } : {},
2022
+ ...trigger.input ? { input: trigger.input } : {},
2023
+ ...trigger.inputs?.length ? { inputs: trigger.inputs } : {},
2024
+ ...trigger.name ? { name: trigger.name } : {},
2025
+ ...trigger.description ? { description: trigger.description } : {}
2026
+ };
2027
+ }
1980
2028
  function stepToUpsertInput(step) {
1981
2029
  const use = step.use;
1982
2030
  return {
2031
+ ...step.key ? { key: step.key } : {},
1983
2032
  appId: resolveActionAppId(use),
1984
2033
  actionId: resolveActionId(use),
1985
2034
  connectionId: use.connection?.id,
@@ -1997,6 +2046,7 @@ function workflowDefinitionToUpsert(def) {
1997
2046
  name: def.name,
1998
2047
  description: def.description,
1999
2048
  isActive: def.isActive,
2049
+ ...def.trigger ? { trigger: triggerToUpsertInput(def.trigger) } : {},
2000
2050
  managedByCode: true,
2001
2051
  steps: def.steps.map(stepToUpsertInput)
2002
2052
  };
@@ -2264,7 +2314,8 @@ ai-connections list prints the model ids each AI connection accepts. Use
2264
2314
  models[].id as defineAgent({ model }). connections list is app connections
2265
2315
  (Gmail, HTTP), not model providers.
2266
2316
  tools get prints inputConfig and a defineAction() sketch. tools list is compact;
2267
- run tools get for the schema. An entity-type slug is unique on the deployment
2317
+ run tools get for the schema. apps get --json includes triggers and a
2318
+ defineTrigger() sketch. Do not invent trigger ids. An entity-type slug is unique on the deployment
2268
2319
  and grants the API key's workspace when the slug is new.
2269
2320
  A slug already granted to other workspaces is not rewritten. Unknown instance attributes are rejected unless
2270
2321
  allowUnknownAttributes is true; use extensions.
@@ -2277,6 +2328,32 @@ function asRecord(value) {
2277
2328
  }
2278
2329
  return void 0;
2279
2330
  }
2331
+ function flattenTriggers(apps) {
2332
+ const out = [];
2333
+ for (const app of apps) {
2334
+ const appId = typeof app.id === "string" ? app.id : "";
2335
+ if (!appId) {
2336
+ continue;
2337
+ }
2338
+ const triggers = Array.isArray(app.triggers) ? app.triggers : [];
2339
+ for (const raw of triggers) {
2340
+ const trigger = asRecord(raw);
2341
+ if (!trigger || typeof trigger.id !== "string" || !trigger.id) {
2342
+ continue;
2343
+ }
2344
+ out.push({
2345
+ appId,
2346
+ appName: typeof app.name === "string" ? app.name : appId,
2347
+ triggerId: trigger.id,
2348
+ name: typeof trigger.name === "string" ? trigger.name : trigger.id,
2349
+ description: typeof trigger.description === "string" ? trigger.description : "",
2350
+ needsConnection: typeof trigger.needsConnection === "boolean" ? trigger.needsConnection : Boolean(app.needsConnection),
2351
+ inputConfig: trigger.inputConfig
2352
+ });
2353
+ }
2354
+ }
2355
+ return out;
2356
+ }
2280
2357
  function flattenActions(apps) {
2281
2358
  const out = [];
2282
2359
  for (const app of apps) {
@@ -2438,6 +2515,49 @@ function formatDefineActionSnippet(action) {
2438
2515
  "})"
2439
2516
  ].join("\n");
2440
2517
  }
2518
+ function formatDefineTriggerSnippet(trigger) {
2519
+ if (trigger.triggerId === "flow-control_trigger_manual") {
2520
+ return [
2521
+ "defineTrigger({",
2522
+ ` id: '${trigger.triggerId}',`,
2523
+ " // inputs become the Manually Run form (customInputConfig)",
2524
+ " inputs: [",
2525
+ " // { id, label, inputType, required }",
2526
+ " ],",
2527
+ "})"
2528
+ ].join("\n");
2529
+ }
2530
+ const fields = extractInputFields(trigger.inputConfig);
2531
+ const inputBlock = fields.length === 0 ? " // no inputConfig fields advertised" : fields.map((field) => {
2532
+ const notes = [
2533
+ field.label && field.label !== field.id ? field.label : void 0,
2534
+ field.type,
2535
+ field.required ? "required" : void 0
2536
+ ].filter(Boolean).join(", ");
2537
+ return notes ? ` // ${field.id} \u2014 ${notes}` : ` // ${field.id}`;
2538
+ }).join("\n");
2539
+ return [
2540
+ "defineTrigger({",
2541
+ ` id: '${trigger.triggerId}',`,
2542
+ ` appId: '${trigger.appId}',`,
2543
+ " input: {",
2544
+ inputBlock,
2545
+ " },",
2546
+ "})"
2547
+ ].join("\n");
2548
+ }
2549
+ function triggerDetail(trigger) {
2550
+ return {
2551
+ appId: trigger.appId,
2552
+ triggerId: trigger.triggerId,
2553
+ name: trigger.name,
2554
+ description: trigger.description,
2555
+ needsConnection: trigger.needsConnection,
2556
+ inputFields: extractInputFields(trigger.inputConfig),
2557
+ inputConfig: trigger.inputConfig ?? null,
2558
+ defineTrigger: formatDefineTriggerSnippet(trigger)
2559
+ };
2560
+ }
2441
2561
  function actionDetail(action) {
2442
2562
  return {
2443
2563
  ...summarizeAction(action),
@@ -2597,6 +2717,7 @@ async function runAppsGet(client, args) {
2597
2717
  throw new Error(`Unknown app "${appId}". Try: apps list`);
2598
2718
  }
2599
2719
  const actions = flattenActions([app]).map(summarizeAction);
2720
+ const triggers = flattenTriggers([app]).map(triggerDetail);
2600
2721
  const payload = {
2601
2722
  id: app.id,
2602
2723
  name: app.name,
@@ -2605,6 +2726,7 @@ async function runAppsGet(client, args) {
2605
2726
  needsConnection: Boolean(app.needsConnection),
2606
2727
  isPublished: Boolean(app.isPublished),
2607
2728
  actions,
2729
+ triggers,
2608
2730
  connectionTypes: Array.isArray(app.connections) ? app.connections : []
2609
2731
  };
2610
2732
  if (args.json) {
@@ -2625,6 +2747,22 @@ async function runAppsGet(client, args) {
2625
2747
  { key: "availableForAgent", header: "FOR_AGENT" },
2626
2748
  { key: "needsConnection", header: "NEEDS_CONN" }
2627
2749
  ]);
2750
+ if (triggers.length) {
2751
+ console.log("");
2752
+ console.log("triggers:");
2753
+ printTable(
2754
+ triggers.map((trigger) => ({
2755
+ triggerId: trigger.triggerId,
2756
+ name: trigger.name,
2757
+ needsConnection: trigger.needsConnection
2758
+ })),
2759
+ [
2760
+ { key: "triggerId", header: "TRIGGER" },
2761
+ { key: "name", header: "NAME" },
2762
+ { key: "needsConnection", header: "NEEDS_CONN" }
2763
+ ]
2764
+ );
2765
+ }
2628
2766
  }
2629
2767
  async function runAiConnectionsList(client, args) {
2630
2768
  const connections = await client.aiConnections.list();
package/dist/index.cjs CHANGED
@@ -537,6 +537,7 @@ var DEFINE_KINDS = {
537
537
  connection: "connection",
538
538
  aiConnection: "aiConnection",
539
539
  action: "action",
540
+ trigger: "trigger",
540
541
  workflow: "workflow",
541
542
  agent: "agent",
542
543
  entityType: "entityType"
@@ -553,6 +554,92 @@ function defineAction(config) {
553
554
  }
554
555
  return { kind: DEFINE_KINDS.action, ...config };
555
556
  }
557
+ function assertTriggerConfig(config) {
558
+ if (!config.id?.trim()) {
559
+ throw new Error("defineTrigger requires id (catalogue trigger id)");
560
+ }
561
+ for (const field of config.inputs ?? []) {
562
+ if (!field?.id?.trim()) {
563
+ throw new Error("defineTrigger inputs require id");
564
+ }
565
+ }
566
+ if (config.inputs?.length && config.id.trim() !== MANUAL_TRIGGER_ID) {
567
+ throw new Error(
568
+ "defineTrigger inputs are only valid on flow-control_trigger_manual"
569
+ );
570
+ }
571
+ }
572
+ function defineTrigger(config) {
573
+ assertTriggerConfig(config);
574
+ return { kind: DEFINE_KINDS.trigger, ...config };
575
+ }
576
+ var MANUAL_TRIGGER_ID = "flow-control_trigger_manual";
577
+ var TRIGGER_KEY = "trigger";
578
+ var STEP_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
579
+ var STEP_REF_PATTERN = /={{step:([^}]*)}}/g;
580
+ function refString(key, path) {
581
+ const parts = [key, ...path.map(String)];
582
+ for (const part of parts) {
583
+ if (!part || /[,{}]/.test(part)) {
584
+ throw new Error(`ref path parts must be non-empty and contain no , { or }: "${part}"`);
585
+ }
586
+ }
587
+ return `={{step:${parts.join(",")}}}`;
588
+ }
589
+ var ref = {
590
+ /** A trigger output field, e.g. a Manually Run input id. */
591
+ trigger: (field, ...path) => refString(TRIGGER_KEY, [field, ...path]),
592
+ /** An earlier step's output, by its `key`. Array indexes are numbers. */
593
+ step: (key, ...path) => refString(key, path)
594
+ };
595
+ function collectRefs(value, out) {
596
+ if (typeof value === "string") {
597
+ for (const match of value.matchAll(STEP_REF_PATTERN)) {
598
+ out.push(match[1].split(",").map((part) => part.trim()));
599
+ }
600
+ } else if (Array.isArray(value)) {
601
+ value.forEach((item) => collectRefs(item, out));
602
+ } else if (value && typeof value === "object") {
603
+ Object.values(value).forEach((item) => collectRefs(item, out));
604
+ }
605
+ }
606
+ function assertStepRefs(config) {
607
+ const triggerId = config.trigger?.id.trim() ?? MANUAL_TRIGGER_ID;
608
+ const triggerFields = triggerId === MANUAL_TRIGGER_ID ? new Set((config.trigger?.inputs ?? []).map((f) => f.id.trim())) : void 0;
609
+ const allKeys = /* @__PURE__ */ new Set();
610
+ for (const step of config.steps) {
611
+ if (step.key === void 0) continue;
612
+ if (!STEP_KEY_PATTERN.test(step.key) || step.key === TRIGGER_KEY) {
613
+ throw new Error(
614
+ `Step key "${step.key}" must start with a letter, use letters, digits, - or _, and not be "${TRIGGER_KEY}".`
615
+ );
616
+ }
617
+ if (allKeys.has(step.key)) {
618
+ throw new Error(`Step key "${step.key}" is used twice.`);
619
+ }
620
+ allKeys.add(step.key);
621
+ }
622
+ const earlier = /* @__PURE__ */ new Set([TRIGGER_KEY]);
623
+ config.steps.forEach((step, index) => {
624
+ const refs = [];
625
+ collectRefs([step.use?.input, step.input], refs);
626
+ const where = `Step "${step.key ?? step.name ?? index + 1}"`;
627
+ for (const [key = "", ...path] of refs) {
628
+ const shown = `={{step:${[key, ...path].join(",")}}}`;
629
+ if (!earlier.has(key)) {
630
+ throw new Error(
631
+ allKeys.has(key) ? `${where}: ${shown} refers to itself or a later step. Use the trigger or an earlier step.` : `${where}: ${shown} names unknown key "${key}". Use "trigger" or an earlier step's key.`
632
+ );
633
+ }
634
+ if (key === TRIGGER_KEY && triggerFields && !triggerFields.has(path[0] ?? "")) {
635
+ throw new Error(
636
+ `${where}: ${shown} uses trigger field "${path[0] ?? ""}", which is not declared in trigger inputs.`
637
+ );
638
+ }
639
+ }
640
+ if (step.key) earlier.add(step.key);
641
+ });
642
+ }
556
643
  function defineWorkflow(config) {
557
644
  if (!config.slug?.trim()) {
558
645
  throw new Error("defineWorkflow requires slug");
@@ -560,6 +647,10 @@ function defineWorkflow(config) {
560
647
  if (!config.steps?.length) {
561
648
  throw new Error("defineWorkflow requires at least one step");
562
649
  }
650
+ if (config.trigger) {
651
+ assertTriggerConfig(config.trigger);
652
+ }
653
+ assertStepRefs(config);
563
654
  return { kind: DEFINE_KINDS.workflow, ...config };
564
655
  }
565
656
  function defineAgent(config) {
@@ -605,6 +696,40 @@ function resolveActionAppId(action) {
605
696
  `Cannot infer appId for action "${id}". Pass appId explicitly.`
606
697
  );
607
698
  }
699
+ function resolveTriggerAppId(trigger) {
700
+ if (trigger.appId?.trim()) {
701
+ return trigger.appId.trim();
702
+ }
703
+ const id = trigger.id.trim();
704
+ const marker = "_trigger_";
705
+ const markerIndex = id.indexOf(marker);
706
+ if (markerIndex > 0) {
707
+ return id.slice(0, markerIndex);
708
+ }
709
+ const dot = id.indexOf(".");
710
+ if (dot > 0) {
711
+ return id.slice(0, dot);
712
+ }
713
+ throw new Error(
714
+ `Cannot infer appId for trigger "${id}". Pass appId explicitly.`
715
+ );
716
+ }
717
+ function resolveTriggerId(trigger) {
718
+ const id = trigger.id.trim();
719
+ const appId = trigger.appId?.trim();
720
+ if (appId && id.startsWith(`${appId}.`)) {
721
+ return id.slice(appId.length + 1);
722
+ }
723
+ const marker = "_trigger_";
724
+ if (id.includes(marker)) {
725
+ return id;
726
+ }
727
+ const dot = id.indexOf(".");
728
+ if (dot > 0) {
729
+ return id.slice(dot + 1);
730
+ }
731
+ return id;
732
+ }
608
733
  function resolveActionId(action) {
609
734
  const id = action.id.trim();
610
735
  const appId = action.appId?.trim();
@@ -1817,7 +1942,9 @@ function stepFromDefinition(step) {
1817
1942
  ...use.input ?? {},
1818
1943
  ...step.input ?? {}
1819
1944
  };
1945
+ const key = step.key;
1820
1946
  return {
1947
+ ...key ? { key } : {},
1821
1948
  appId: resolveActionAppId(use),
1822
1949
  actionId: resolveActionId(use),
1823
1950
  ...use.connection?.id ? { connectionId: use.connection.id } : {},
@@ -2019,9 +2146,21 @@ var WorkflowAppsResource = class {
2019
2146
  };
2020
2147
 
2021
2148
  // src/resources/workflows.ts
2149
+ function triggerToUpsertInput(trigger) {
2150
+ return {
2151
+ triggerId: resolveTriggerId(trigger),
2152
+ appId: resolveTriggerAppId(trigger),
2153
+ ...trigger.connection?.id ? { connectionId: trigger.connection.id } : {},
2154
+ ...trigger.input ? { input: trigger.input } : {},
2155
+ ...trigger.inputs?.length ? { inputs: trigger.inputs } : {},
2156
+ ...trigger.name ? { name: trigger.name } : {},
2157
+ ...trigger.description ? { description: trigger.description } : {}
2158
+ };
2159
+ }
2022
2160
  function stepToUpsertInput(step) {
2023
2161
  const use = step.use;
2024
2162
  return {
2163
+ ...step.key ? { key: step.key } : {},
2025
2164
  appId: resolveActionAppId(use),
2026
2165
  actionId: resolveActionId(use),
2027
2166
  connectionId: use.connection?.id,
@@ -2039,6 +2178,7 @@ function workflowDefinitionToUpsert(def) {
2039
2178
  name: def.name,
2040
2179
  description: def.description,
2041
2180
  isActive: def.isActive,
2181
+ ...def.trigger ? { trigger: triggerToUpsertInput(def.trigger) } : {},
2042
2182
  managedByCode: true,
2043
2183
  steps: def.steps.map(stepToUpsertInput)
2044
2184
  };
@@ -2277,6 +2417,7 @@ exports.defineAgent = defineAgent;
2277
2417
  exports.defineAiConnection = defineAiConnection;
2278
2418
  exports.defineConnection = defineConnection;
2279
2419
  exports.defineEntityType = defineEntityType;
2420
+ exports.defineTrigger = defineTrigger;
2280
2421
  exports.defineWorkflow = defineWorkflow;
2281
2422
  exports.entityTypeDefinitionToUpsert = entityTypeDefinitionToUpsert;
2282
2423
  exports.isBrowserEnvironment = isBrowserEnvironment;
@@ -2285,7 +2426,10 @@ exports.iterateOffset = iterateOffset;
2285
2426
  exports.iteratePages = iteratePages;
2286
2427
  exports.parseSseEvents = parseSseEvents;
2287
2428
  exports.parseSseStream = parseSseStream;
2429
+ exports.ref = ref;
2288
2430
  exports.resolveActionAppId = resolveActionAppId;
2289
2431
  exports.resolveActionId = resolveActionId;
2432
+ exports.resolveTriggerAppId = resolveTriggerAppId;
2433
+ exports.resolveTriggerId = resolveTriggerId;
2290
2434
  //# sourceMappingURL=index.cjs.map
2291
2435
  //# sourceMappingURL=index.cjs.map