@lunnoa/client 1.0.0 → 1.2.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/dist/cli.js CHANGED
@@ -537,7 +537,8 @@ var DEFINE_KINDS = {
537
537
  aiConnection: "aiConnection",
538
538
  action: "action",
539
539
  workflow: "workflow",
540
- agent: "agent"
540
+ agent: "agent",
541
+ entityType: "entityType"
541
542
  };
542
543
  function resolveActionAppId(action) {
543
544
  if (action.appId?.trim()) {
@@ -1016,6 +1017,21 @@ var EntitiesResource = class {
1016
1017
  };
1017
1018
 
1018
1019
  // src/resources/entity-types.ts
1020
+ function entityTypeDefinitionToUpsert(def) {
1021
+ return {
1022
+ slug: def.slug,
1023
+ name: def.name,
1024
+ namePlural: def.namePlural,
1025
+ description: def.description,
1026
+ icon: def.icon,
1027
+ color: def.color,
1028
+ attributeSchema: def.attributeSchema,
1029
+ stateSchema: def.stateSchema,
1030
+ isEnabled: def.isEnabled,
1031
+ allowUnknownAttributes: def.allowUnknownAttributes ?? false,
1032
+ managedByCode: true
1033
+ };
1034
+ }
1019
1035
  var EntityTypesResource = class {
1020
1036
  constructor(http) {
1021
1037
  this.http = http;
@@ -1046,6 +1062,19 @@ var EntityTypesResource = class {
1046
1062
  query: { expansion: serializeCsv(options.expansion) }
1047
1063
  });
1048
1064
  }
1065
+ /**
1066
+ * Creates or updates an entity type by workspace-scoped slug.
1067
+ * Prefer the CLI (`npx @lunnoa/client entity-types deploy`) for happy-path deploy.
1068
+ */
1069
+ upsertBySlug(definition) {
1070
+ const payload = "kind" in definition && definition.kind === "entityType" ? entityTypeDefinitionToUpsert(definition) : definition;
1071
+ return this.http.post("/api/object-types/upsert", {
1072
+ body: payload,
1073
+ query: {
1074
+ expansion: "attributeSchema,stateSchema,description"
1075
+ }
1076
+ });
1077
+ }
1049
1078
  };
1050
1079
 
1051
1080
  // src/resources/approvals.ts
@@ -2020,7 +2049,655 @@ var LunnoaClient = class {
2020
2049
  }
2021
2050
  };
2022
2051
 
2052
+ // src/cli/args.ts
2053
+ var GROUPS_WITH_SUBCOMMAND = /* @__PURE__ */ new Set([
2054
+ "workflows",
2055
+ "agents",
2056
+ "entity-types",
2057
+ "tools",
2058
+ "apps",
2059
+ "connections",
2060
+ "projects"
2061
+ ]);
2062
+ var VALUE_FLAGS = /* @__PURE__ */ new Set([
2063
+ "--url",
2064
+ "--api-key",
2065
+ "--access-token",
2066
+ "--project-id",
2067
+ "--out",
2068
+ "--app"
2069
+ ]);
2070
+ function requireFlagValue(flag, value) {
2071
+ if (!value || value.startsWith("--")) {
2072
+ throw new Error(`Missing value for ${flag}`);
2073
+ }
2074
+ return value;
2075
+ }
2076
+ function parseArgs(argv) {
2077
+ const args = {
2078
+ command: argv[0],
2079
+ out: "./src/lunnoa",
2080
+ json: false,
2081
+ forAgent: false,
2082
+ help: false
2083
+ };
2084
+ let i = 1;
2085
+ if (args.command && GROUPS_WITH_SUBCOMMAND.has(args.command)) {
2086
+ const next = argv[1];
2087
+ if (next && !next.startsWith("--")) {
2088
+ args.subcommand = next;
2089
+ i = 2;
2090
+ if (argv[2] && !argv[2].startsWith("--")) {
2091
+ args.path = argv[2];
2092
+ i = 3;
2093
+ }
2094
+ }
2095
+ }
2096
+ for (; i < argv.length; i += 1) {
2097
+ const flag = argv[i];
2098
+ if (flag === "--help" || flag === "-h") {
2099
+ args.help = true;
2100
+ continue;
2101
+ }
2102
+ if (flag === "--json") {
2103
+ args.json = true;
2104
+ continue;
2105
+ }
2106
+ if (flag === "--for-agent") {
2107
+ args.forAgent = true;
2108
+ continue;
2109
+ }
2110
+ if (flag && VALUE_FLAGS.has(flag)) {
2111
+ const value = requireFlagValue(flag, argv[i + 1]);
2112
+ switch (flag) {
2113
+ case "--url":
2114
+ args.url = value;
2115
+ break;
2116
+ case "--api-key":
2117
+ args.apiKey = value;
2118
+ break;
2119
+ case "--access-token":
2120
+ args.accessToken = value;
2121
+ break;
2122
+ case "--project-id":
2123
+ args.projectId = value;
2124
+ break;
2125
+ case "--out":
2126
+ args.out = value;
2127
+ break;
2128
+ case "--app":
2129
+ args.app = value;
2130
+ break;
2131
+ default:
2132
+ break;
2133
+ }
2134
+ i += 1;
2135
+ continue;
2136
+ }
2137
+ if (flag && !flag.startsWith("--") && !args.path) {
2138
+ args.path = flag;
2139
+ continue;
2140
+ }
2141
+ throw new Error(`Unknown option: ${flag}`);
2142
+ }
2143
+ return args;
2144
+ }
2145
+ var USAGE = `Usage:
2146
+ npx @lunnoa/client codegen --url <url> --api-key <lna_key> [--out <dir>]
2147
+
2148
+ Discover (do not invent catalogue action ids):
2149
+ npx @lunnoa/client tools list [--app <appId>] [--for-agent] [--json]
2150
+ npx @lunnoa/client tools get <actionId> [--app <appId>] [--json]
2151
+ npx @lunnoa/client tools search <query> [--app <appId>] [--for-agent] [--json]
2152
+ npx @lunnoa/client apps list [--for-agent] [--json]
2153
+ npx @lunnoa/client apps get <appId> [--json]
2154
+ npx @lunnoa/client connections list [--app <appId>] [--json]
2155
+ npx @lunnoa/client connections get <connectionId> [--json]
2156
+ npx @lunnoa/client agents list [--json]
2157
+ npx @lunnoa/client workflows list [--json]
2158
+ npx @lunnoa/client entity-types list [--json]
2159
+ npx @lunnoa/client projects list [--json]
2160
+ npx @lunnoa/client features [--json]
2161
+
2162
+ Deploy (upsert by slug):
2163
+ npx @lunnoa/client workflows deploy <path> --url <url> --api-key <key> --project-id <uuid>
2164
+ npx @lunnoa/client agents deploy <path> --url <url> --api-key <key> --project-id <uuid>
2165
+ npx @lunnoa/client entity-types deploy <path> --url <url> --api-key <key>
2166
+
2167
+ Options:
2168
+ --url Base URL of the Lunnoa Automate deployment
2169
+ --api-key Machine API key (lna_...)
2170
+ --access-token User JWT as an alternative to --api-key
2171
+ --project-id Project UUID (required for workflow/agent deploy)
2172
+ --out Output directory for lunnoa.generated.ts (codegen; default: ./src/lunnoa)
2173
+ --app Restrict tools/connections to one workflow app id
2174
+ --for-agent Only catalogue entries with availableForAgent
2175
+ --json Machine-readable JSON (preferred for coding agents)
2176
+ --help Show this help
2177
+
2178
+ Environment: LUNNOA_URL, LUNNOA_API_KEY, LUNNOA_PROJECT_ID
2179
+ Optional config file: lunnoa.config.ts | .mjs | .js | .json
2180
+
2181
+ tools get prints inputConfig and a defineAction() sketch. tools list is compact;
2182
+ run tools get for the schema. Entity-type deploy is workspace-scoped (no
2183
+ --project-id). Unknown instance attributes are rejected unless
2184
+ allowUnknownAttributes is true; use extensions.
2185
+ `;
2186
+
2187
+ // src/cli/catalog.ts
2188
+ function asRecord(value) {
2189
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2190
+ return value;
2191
+ }
2192
+ return void 0;
2193
+ }
2194
+ function flattenActions(apps) {
2195
+ const out = [];
2196
+ for (const app of apps) {
2197
+ const appId = typeof app.id === "string" ? app.id : "";
2198
+ if (!appId) {
2199
+ continue;
2200
+ }
2201
+ const actions = Array.isArray(app.actions) ? app.actions : [];
2202
+ for (const raw of actions) {
2203
+ const action = asRecord(raw);
2204
+ if (!action || typeof action.id !== "string" || !action.id) {
2205
+ continue;
2206
+ }
2207
+ const actionId = action.id;
2208
+ out.push({
2209
+ appId,
2210
+ appName: typeof app.name === "string" ? app.name : appId,
2211
+ actionId,
2212
+ name: typeof action.name === "string" ? action.name : actionId,
2213
+ description: typeof action.description === "string" ? action.description : "",
2214
+ availableForAgent: typeof action.availableForAgent === "boolean" ? action.availableForAgent : Boolean(app.availableForAgent),
2215
+ needsConnection: typeof action.needsConnection === "boolean" ? action.needsConnection : Boolean(app.needsConnection),
2216
+ inputConfig: action.inputConfig
2217
+ });
2218
+ }
2219
+ }
2220
+ return out;
2221
+ }
2222
+ function filterActions(actions, filter = {}) {
2223
+ const appId = filter.appId?.trim();
2224
+ const query = filter.query?.trim().toLowerCase();
2225
+ return actions.filter((action) => {
2226
+ if (appId && action.appId !== appId) {
2227
+ return false;
2228
+ }
2229
+ if (filter.forAgent && !action.availableForAgent) {
2230
+ return false;
2231
+ }
2232
+ if (query) {
2233
+ const haystack = [
2234
+ action.appId,
2235
+ action.appName,
2236
+ action.actionId,
2237
+ action.name,
2238
+ action.description
2239
+ ].join(" ").toLowerCase();
2240
+ if (!haystack.includes(query)) {
2241
+ return false;
2242
+ }
2243
+ }
2244
+ return true;
2245
+ });
2246
+ }
2247
+ function parseActionRef(raw) {
2248
+ const trimmed = raw.trim();
2249
+ const dot = trimmed.indexOf(".");
2250
+ if (dot > 0) {
2251
+ const maybeApp = trimmed.slice(0, dot);
2252
+ const maybeAction = trimmed.slice(dot + 1);
2253
+ if (maybeApp && maybeAction && !maybeApp.includes("_action_")) {
2254
+ return { appId: maybeApp, actionId: maybeAction };
2255
+ }
2256
+ }
2257
+ return { actionId: trimmed };
2258
+ }
2259
+ function resolveAction(actions, rawRef, appId) {
2260
+ const parsed = parseActionRef(rawRef);
2261
+ const scope = appId?.trim() || parsed.appId;
2262
+ const matches = actions.filter((action) => {
2263
+ if (action.actionId !== parsed.actionId) {
2264
+ return false;
2265
+ }
2266
+ return scope ? action.appId === scope : true;
2267
+ });
2268
+ if (matches.length === 1) {
2269
+ return matches[0];
2270
+ }
2271
+ if (matches.length === 0) {
2272
+ const hint = scope ? `No action "${parsed.actionId}" on app "${scope}". Try: tools search ${parsed.actionId}` : `Unknown action "${parsed.actionId}". Try: tools search ${parsed.actionId}`;
2273
+ throw new Error(hint);
2274
+ }
2275
+ const apps = matches.map((m) => m.appId).join(", ");
2276
+ throw new Error(
2277
+ `Action "${parsed.actionId}" exists on multiple apps (${apps}). Pass --app <appId>.`
2278
+ );
2279
+ }
2280
+ function extractInputFields(inputConfig) {
2281
+ if (!inputConfig) {
2282
+ return [];
2283
+ }
2284
+ if (Array.isArray(inputConfig)) {
2285
+ return inputConfig.map((item) => fieldFromUnknown(item)).filter((field) => Boolean(field));
2286
+ }
2287
+ const record = asRecord(inputConfig);
2288
+ if (!record) {
2289
+ return [];
2290
+ }
2291
+ if (Array.isArray(record.fields)) {
2292
+ return extractInputFields(record.fields);
2293
+ }
2294
+ if (Array.isArray(record.sections)) {
2295
+ return record.sections.flatMap((section) => {
2296
+ const sectionRecord = asRecord(section);
2297
+ return extractInputFields(sectionRecord?.fields ?? section);
2298
+ });
2299
+ }
2300
+ return Object.entries(record).filter(([, value]) => value === null || typeof value !== "object" || !Array.isArray(value)).map(([id, value]) => {
2301
+ const field = asRecord(value);
2302
+ if (!field) {
2303
+ return { id };
2304
+ }
2305
+ return {
2306
+ id: typeof field.id === "string" ? field.id : id,
2307
+ label: typeof field.label === "string" ? field.label : void 0,
2308
+ type: typeof field.type === "string" ? field.type : typeof field.inputType === "string" ? field.inputType : void 0,
2309
+ required: typeof field.required === "boolean" ? field.required : void 0
2310
+ };
2311
+ });
2312
+ }
2313
+ function fieldFromUnknown(value) {
2314
+ const field = asRecord(value);
2315
+ if (!field || typeof field.id !== "string" || !field.id) {
2316
+ return void 0;
2317
+ }
2318
+ return {
2319
+ id: field.id,
2320
+ label: typeof field.label === "string" ? field.label : void 0,
2321
+ type: typeof field.type === "string" ? field.type : typeof field.inputType === "string" ? field.inputType : void 0,
2322
+ required: typeof field.required === "boolean" ? field.required : void 0
2323
+ };
2324
+ }
2325
+ function summarizeAction(action) {
2326
+ return {
2327
+ appId: action.appId,
2328
+ actionId: action.actionId,
2329
+ name: action.name,
2330
+ description: action.description,
2331
+ availableForAgent: action.availableForAgent,
2332
+ needsConnection: action.needsConnection
2333
+ };
2334
+ }
2335
+ function formatDefineActionSnippet(action) {
2336
+ const fields = extractInputFields(action.inputConfig);
2337
+ const inputBlock = fields.length === 0 ? " // no inputConfig fields advertised" : fields.map((field) => {
2338
+ const notes = [
2339
+ field.label && field.label !== field.id ? field.label : void 0,
2340
+ field.type,
2341
+ field.required ? "required" : void 0
2342
+ ].filter(Boolean).join(", ");
2343
+ return notes ? ` // ${field.id}${notes ? ` \u2014 ${notes}` : ""}` : ` // ${field.id}`;
2344
+ }).join("\n");
2345
+ return [
2346
+ "defineAction({",
2347
+ ` id: '${action.actionId}',`,
2348
+ ` appId: '${action.appId}',`,
2349
+ " input: {",
2350
+ inputBlock,
2351
+ " },",
2352
+ "})"
2353
+ ].join("\n");
2354
+ }
2355
+ function actionDetail(action) {
2356
+ return {
2357
+ ...summarizeAction(action),
2358
+ inputFields: extractInputFields(action.inputConfig),
2359
+ inputConfig: action.inputConfig ?? null,
2360
+ defineAction: formatDefineActionSnippet(action)
2361
+ };
2362
+ }
2363
+
2364
+ // src/cli/print.ts
2365
+ function printJson(value) {
2366
+ console.log(JSON.stringify(value, null, 2));
2367
+ }
2368
+ function formatTable(rows, columns) {
2369
+ if (rows.length === 0) {
2370
+ return "No results.";
2371
+ }
2372
+ const widths = columns.map((col) => {
2373
+ let width = col.header.length;
2374
+ for (const row of rows) {
2375
+ width = Math.max(width, stringifyCell(row[col.key]).length);
2376
+ }
2377
+ return width;
2378
+ });
2379
+ const header = columns.map((col, i) => pad(col.header, widths[i])).join(" ");
2380
+ const divider = widths.map((w) => "-".repeat(w)).join(" ");
2381
+ const body = rows.map(
2382
+ (row) => columns.map((col, i) => pad(stringifyCell(row[col.key]), widths[i])).join(" ")
2383
+ );
2384
+ return [header, divider, ...body].join("\n");
2385
+ }
2386
+ function printTable(rows, columns) {
2387
+ console.log(formatTable(rows, columns));
2388
+ }
2389
+ function stringifyCell(value) {
2390
+ if (value === null || value === void 0) {
2391
+ return "";
2392
+ }
2393
+ if (typeof value === "boolean") {
2394
+ return value ? "yes" : "no";
2395
+ }
2396
+ return String(value);
2397
+ }
2398
+ function pad(value, width) {
2399
+ return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
2400
+ }
2401
+
2402
+ // src/cli/discover.ts
2403
+ function requireTarget(args, label) {
2404
+ const value = args.path?.trim();
2405
+ if (!value) {
2406
+ throw new Error(`Missing ${label}.`);
2407
+ }
2408
+ return value;
2409
+ }
2410
+ async function loadCatalog(client) {
2411
+ return flattenActions(await client.workflowApps.list());
2412
+ }
2413
+ async function runToolsList(client, args) {
2414
+ const actions = filterActions(await loadCatalog(client), {
2415
+ appId: args.app,
2416
+ forAgent: args.forAgent
2417
+ });
2418
+ const rows = actions.map(summarizeAction);
2419
+ if (args.json) {
2420
+ printJson(rows);
2421
+ return;
2422
+ }
2423
+ printTable(rows, [
2424
+ { key: "appId", header: "APP" },
2425
+ { key: "actionId", header: "ACTION" },
2426
+ { key: "name", header: "NAME" },
2427
+ { key: "availableForAgent", header: "FOR_AGENT" },
2428
+ { key: "needsConnection", header: "NEEDS_CONN" }
2429
+ ]);
2430
+ }
2431
+ async function runToolsGet(client, args) {
2432
+ const ref = requireTarget(args, "action id (example: tools get http_action_send-request)");
2433
+ const action = resolveAction(await loadCatalog(client), ref, args.app);
2434
+ const detail = actionDetail(action);
2435
+ if (args.json) {
2436
+ printJson(detail);
2437
+ return;
2438
+ }
2439
+ console.log(`appId: ${detail.appId}`);
2440
+ console.log(`actionId: ${detail.actionId}`);
2441
+ console.log(`name: ${detail.name}`);
2442
+ if (detail.description) {
2443
+ console.log(`description: ${detail.description}`);
2444
+ }
2445
+ console.log(`availableForAgent: ${detail.availableForAgent ? "yes" : "no"}`);
2446
+ console.log(`needsConnection: ${detail.needsConnection ? "yes" : "no"}`);
2447
+ console.log("");
2448
+ console.log("inputFields:");
2449
+ if (detail.inputFields.length === 0) {
2450
+ console.log(" (none advertised)");
2451
+ } else {
2452
+ for (const field of detail.inputFields) {
2453
+ const bits = [
2454
+ field.label && field.label !== field.id ? field.label : void 0,
2455
+ field.type,
2456
+ field.required ? "required" : void 0
2457
+ ].filter(Boolean);
2458
+ console.log(` ${field.id}${bits.length ? ` (${bits.join(", ")})` : ""}`);
2459
+ }
2460
+ }
2461
+ console.log("");
2462
+ console.log("defineAction:");
2463
+ console.log(detail.defineAction);
2464
+ }
2465
+ async function runToolsSearch(client, args) {
2466
+ const query = requireTarget(args, "search query (example: tools search email)");
2467
+ const actions = filterActions(await loadCatalog(client), {
2468
+ appId: args.app,
2469
+ forAgent: args.forAgent,
2470
+ query
2471
+ });
2472
+ const rows = actions.map(summarizeAction);
2473
+ if (args.json) {
2474
+ printJson(rows);
2475
+ return;
2476
+ }
2477
+ printTable(rows, [
2478
+ { key: "appId", header: "APP" },
2479
+ { key: "actionId", header: "ACTION" },
2480
+ { key: "name", header: "NAME" },
2481
+ { key: "availableForAgent", header: "FOR_AGENT" },
2482
+ { key: "needsConnection", header: "NEEDS_CONN" }
2483
+ ]);
2484
+ }
2485
+ async function runAppsList(client, args) {
2486
+ const apps = await client.workflowApps.list();
2487
+ const rows = apps.filter((app) => args.forAgent ? Boolean(app.availableForAgent) : true).filter((app) => args.app ? app.id === args.app : true).map((app) => ({
2488
+ id: app.id ?? "",
2489
+ name: app.name ?? app.id ?? "",
2490
+ availableForAgent: Boolean(app.availableForAgent),
2491
+ needsConnection: Boolean(app.needsConnection),
2492
+ actions: Array.isArray(app.actions) ? app.actions.length : 0
2493
+ }));
2494
+ if (args.json) {
2495
+ printJson(rows);
2496
+ return;
2497
+ }
2498
+ printTable(rows, [
2499
+ { key: "id", header: "APP" },
2500
+ { key: "name", header: "NAME" },
2501
+ { key: "availableForAgent", header: "FOR_AGENT" },
2502
+ { key: "needsConnection", header: "NEEDS_CONN" },
2503
+ { key: "actions", header: "ACTIONS" }
2504
+ ]);
2505
+ }
2506
+ async function runAppsGet(client, args) {
2507
+ const appId = requireTarget(args, "app id (example: apps get http)");
2508
+ const apps = await client.workflowApps.list();
2509
+ const app = apps.find((item) => item.id === appId);
2510
+ if (!app) {
2511
+ throw new Error(`Unknown app "${appId}". Try: apps list`);
2512
+ }
2513
+ const actions = flattenActions([app]).map(summarizeAction);
2514
+ const payload = {
2515
+ id: app.id,
2516
+ name: app.name,
2517
+ description: app.description ?? "",
2518
+ availableForAgent: Boolean(app.availableForAgent),
2519
+ needsConnection: Boolean(app.needsConnection),
2520
+ isPublished: Boolean(app.isPublished),
2521
+ actions,
2522
+ connectionTypes: Array.isArray(app.connections) ? app.connections : []
2523
+ };
2524
+ if (args.json) {
2525
+ printJson(payload);
2526
+ return;
2527
+ }
2528
+ console.log(`id: ${payload.id}`);
2529
+ console.log(`name: ${payload.name}`);
2530
+ if (payload.description) {
2531
+ console.log(`description: ${payload.description}`);
2532
+ }
2533
+ console.log(`availableForAgent: ${payload.availableForAgent ? "yes" : "no"}`);
2534
+ console.log(`needsConnection: ${payload.needsConnection ? "yes" : "no"}`);
2535
+ console.log("");
2536
+ printTable(actions, [
2537
+ { key: "actionId", header: "ACTION" },
2538
+ { key: "name", header: "NAME" },
2539
+ { key: "availableForAgent", header: "FOR_AGENT" },
2540
+ { key: "needsConnection", header: "NEEDS_CONN" }
2541
+ ]);
2542
+ }
2543
+ async function runConnectionsList(client, args) {
2544
+ const connections = await client.connections.list({
2545
+ expansion: ["workflowAppId", "connectionId", "createdAt"]
2546
+ });
2547
+ const rows = connections.filter(
2548
+ (connection) => args.app ? connection.workflowAppId === args.app : true
2549
+ ).map((connection) => ({
2550
+ id: connection.id,
2551
+ name: connection.name,
2552
+ workflowAppId: connection.workflowAppId ?? "",
2553
+ connectionId: connection.connectionId ?? ""
2554
+ }));
2555
+ if (args.json) {
2556
+ printJson(rows);
2557
+ return;
2558
+ }
2559
+ printTable(rows, [
2560
+ { key: "id", header: "ID" },
2561
+ { key: "name", header: "NAME" },
2562
+ { key: "workflowAppId", header: "APP" },
2563
+ { key: "connectionId", header: "TYPE" }
2564
+ ]);
2565
+ }
2566
+ async function runConnectionsGet(client, args) {
2567
+ const id = requireTarget(args, "connection id (example: connections get <uuid>)");
2568
+ const connection = await client.connections.get(id, {
2569
+ expansion: ["workflowAppId", "connectionId", "createdAt", "updatedAt"]
2570
+ });
2571
+ const payload = {
2572
+ id: connection.id,
2573
+ name: connection.name,
2574
+ workflowAppId: connection.workflowAppId ?? null,
2575
+ connectionId: connection.connectionId ?? null,
2576
+ createdAt: connection.createdAt ?? null,
2577
+ updatedAt: connection.updatedAt ?? null
2578
+ };
2579
+ if (args.json) {
2580
+ printJson(payload);
2581
+ return;
2582
+ }
2583
+ console.log(`id: ${payload.id}`);
2584
+ console.log(`name: ${payload.name}`);
2585
+ console.log(`workflowAppId: ${payload.workflowAppId ?? ""}`);
2586
+ console.log(`connectionId: ${payload.connectionId ?? ""}`);
2587
+ }
2588
+ async function runAgentsList(client, args) {
2589
+ const agents = await client.agents.list({
2590
+ expansion: ["description", "slug", "managedByCode"]
2591
+ });
2592
+ const rows = agents.map((agent) => ({
2593
+ id: agent.id,
2594
+ name: agent.name,
2595
+ slug: agent.slug ?? "",
2596
+ managedByCode: Boolean(agent.managedByCode),
2597
+ description: agent.description ?? ""
2598
+ }));
2599
+ if (args.json) {
2600
+ printJson(rows);
2601
+ return;
2602
+ }
2603
+ printTable(rows, [
2604
+ { key: "id", header: "ID" },
2605
+ { key: "slug", header: "SLUG" },
2606
+ { key: "name", header: "NAME" },
2607
+ { key: "managedByCode", header: "CODE" }
2608
+ ]);
2609
+ }
2610
+ async function runWorkflowsList(client, args) {
2611
+ const workflows = await client.workflows.list({
2612
+ expansion: ["description", "isActive", "slug", "managedByCode"]
2613
+ });
2614
+ const rows = workflows.map((workflow) => ({
2615
+ id: workflow.id,
2616
+ name: workflow.name,
2617
+ slug: workflow.slug ?? "",
2618
+ isActive: Boolean(workflow.isActive),
2619
+ managedByCode: Boolean(workflow.managedByCode)
2620
+ }));
2621
+ if (args.json) {
2622
+ printJson(rows);
2623
+ return;
2624
+ }
2625
+ printTable(rows, [
2626
+ { key: "id", header: "ID" },
2627
+ { key: "slug", header: "SLUG" },
2628
+ { key: "name", header: "NAME" },
2629
+ { key: "isActive", header: "ACTIVE" },
2630
+ { key: "managedByCode", header: "CODE" }
2631
+ ]);
2632
+ }
2633
+ async function runEntityTypesList(client, args) {
2634
+ const types = await client.entityTypes.list({
2635
+ expansion: ["description"]
2636
+ });
2637
+ const rows = types.map((type) => ({
2638
+ id: type.id,
2639
+ slug: type.slug,
2640
+ name: type.name,
2641
+ managedByCode: Boolean(type.managedByCode)
2642
+ }));
2643
+ if (args.json) {
2644
+ printJson(rows);
2645
+ return;
2646
+ }
2647
+ printTable(rows, [
2648
+ { key: "id", header: "ID" },
2649
+ { key: "slug", header: "SLUG" },
2650
+ { key: "name", header: "NAME" },
2651
+ { key: "managedByCode", header: "CODE" }
2652
+ ]);
2653
+ }
2654
+ async function runProjectsList(client, args) {
2655
+ const projects = await client.projects.list({
2656
+ expansion: ["description", "countAgents", "countWorkflows"]
2657
+ });
2658
+ const rows = projects.map((project) => ({
2659
+ id: project.id,
2660
+ name: project.name,
2661
+ description: project.description ?? "",
2662
+ agents: project._count?.agents ?? "",
2663
+ workflows: project._count?.workflows ?? ""
2664
+ }));
2665
+ if (args.json) {
2666
+ printJson(rows);
2667
+ return;
2668
+ }
2669
+ printTable(rows, [
2670
+ { key: "id", header: "ID" },
2671
+ { key: "name", header: "NAME" },
2672
+ { key: "agents", header: "AGENTS" },
2673
+ { key: "workflows", header: "WORKFLOWS" }
2674
+ ]);
2675
+ }
2676
+ async function runFeatures(client, args) {
2677
+ const features = await client.discovery.enabledFeatures();
2678
+ if (args.json) {
2679
+ printJson(features);
2680
+ return;
2681
+ }
2682
+ const rows = Object.entries(features).filter(([, value]) => typeof value === "boolean").map(([key, value]) => ({
2683
+ feature: key,
2684
+ enabled: value
2685
+ }));
2686
+ printTable(rows, [
2687
+ { key: "feature", header: "FEATURE" },
2688
+ { key: "enabled", header: "ENABLED" }
2689
+ ]);
2690
+ }
2691
+
2023
2692
  // src/cli/generate.ts
2693
+ function computeDeploymentSchemaFingerprint(entityTypes) {
2694
+ const parts = [...entityTypes].map((type) => {
2695
+ const revision = type.schemaRevision ?? 1;
2696
+ const allowUnknown = type.allowUnknownAttributes ?? false;
2697
+ return `${type.slug}:${revision}:${allowUnknown ? 1 : 0}`;
2698
+ }).sort();
2699
+ return parts.join("|") || "empty";
2700
+ }
2024
2701
  function toCamelCase(input) {
2025
2702
  const parts = input.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/);
2026
2703
  if (parts.length === 0 || parts[0] === "") {
@@ -2140,6 +2817,9 @@ function isFieldRequired(field) {
2140
2817
  }
2141
2818
  function generateDeploymentModule(definitions) {
2142
2819
  const chunks = [];
2820
+ const schemaFingerprint = computeDeploymentSchemaFingerprint(
2821
+ definitions.entityTypes
2822
+ );
2143
2823
  chunks.push(
2144
2824
  `/* eslint-disable */
2145
2825
  /**
@@ -2148,8 +2828,23 @@ function generateDeploymentModule(definitions) {
2148
2828
  * Generated by \`npx @lunnoa/client codegen\` \u2014 do not edit by hand.
2149
2829
  * Re-run codegen whenever entity types, workflows, or agents change on
2150
2830
  * the deployment.
2831
+ *
2832
+ * Schema fingerprint: ${schemaFingerprint}
2151
2833
  */
2152
2834
  import type { LunnoaClient, Entity, Execution, ExecuteWorkflowResult, PaginatedEntities, Task, AgentChatStream, WaitUntilFinishedOptions, ListEntitiesOptions, UIMessage } from '@lunnoa/client';
2835
+
2836
+ /** Fingerprint of entity-type schemas at codegen time (slug:revision:allowUnknown). */
2837
+ export const DEPLOYMENT_SCHEMA_FINGERPRINT = ${JSON.stringify(schemaFingerprint)};
2838
+
2839
+ /** Per-type schemaRevision values captured at codegen time. */
2840
+ export const ENTITY_TYPE_SCHEMA_REVISIONS = ${JSON.stringify(
2841
+ Object.fromEntries(
2842
+ definitions.entityTypes.map((type) => [
2843
+ type.slug,
2844
+ type.schemaRevision ?? 1
2845
+ ])
2846
+ )
2847
+ )} as const;
2153
2848
  `
2154
2849
  );
2155
2850
  chunks.push(GENERATED_RUNTIME_HELPERS);
@@ -2438,6 +3133,21 @@ function pickAgentDefinition(mod) {
2438
3133
  "No defineAgent() export found. Export the definition as default or `agent`."
2439
3134
  );
2440
3135
  }
3136
+ function pickEntityTypeDefinition(mod) {
3137
+ const candidates = [
3138
+ mod.default,
3139
+ mod.entityType,
3140
+ ...Object.values(mod)
3141
+ ];
3142
+ for (const value of candidates) {
3143
+ if (value && typeof value === "object" && value.kind === DEFINE_KINDS.entityType) {
3144
+ return value;
3145
+ }
3146
+ }
3147
+ throw new Error(
3148
+ "No defineEntityType() export found. Export the definition as default or `entityType`."
3149
+ );
3150
+ }
2441
3151
  async function loadOptionalConfig() {
2442
3152
  const candidates = [
2443
3153
  "lunnoa.config.ts",
@@ -2467,66 +3177,6 @@ async function loadOptionalConfig() {
2467
3177
  }
2468
3178
 
2469
3179
  // src/cli/index.ts
2470
- function parseArgs(argv) {
2471
- const args = { command: argv[0], out: "./src/lunnoa" };
2472
- let i = 1;
2473
- if (args.command === "workflows" || args.command === "agents") {
2474
- args.subcommand = argv[1];
2475
- i = 2;
2476
- if (argv[2] && !argv[2].startsWith("--")) {
2477
- args.path = argv[2];
2478
- i = 3;
2479
- }
2480
- }
2481
- for (; i < argv.length; i += 1) {
2482
- const flag = argv[i];
2483
- const value = argv[i + 1];
2484
- switch (flag) {
2485
- case "--url":
2486
- args.url = value;
2487
- i += 1;
2488
- break;
2489
- case "--api-key":
2490
- args.apiKey = value;
2491
- i += 1;
2492
- break;
2493
- case "--access-token":
2494
- args.accessToken = value;
2495
- i += 1;
2496
- break;
2497
- case "--project-id":
2498
- args.projectId = value;
2499
- i += 1;
2500
- break;
2501
- case "--out":
2502
- args.out = value;
2503
- i += 1;
2504
- break;
2505
- default:
2506
- if (!args.path && !flag.startsWith("--")) {
2507
- args.path = flag;
2508
- break;
2509
- }
2510
- throw new Error(`Unknown option: ${flag}`);
2511
- }
2512
- }
2513
- return args;
2514
- }
2515
- var USAGE = `Usage:
2516
- npx @lunnoa/client codegen --url <deployment-url> --api-key <lna_key> [--out <dir>]
2517
- npx @lunnoa/client workflows deploy <path> --url <url> --api-key <key> --project-id <uuid>
2518
- npx @lunnoa/client agents deploy <path> --url <url> --api-key <key> --project-id <uuid>
2519
-
2520
- Options:
2521
- --url Base URL of the Lunnoa Automate deployment
2522
- --api-key Machine API key (lna_...)
2523
- --access-token User JWT as an alternative to --api-key
2524
- --project-id Project UUID (required for deploy)
2525
- --out Output directory for lunnoa.generated.ts (codegen; default: ./src/lunnoa)
2526
-
2527
- Environment: LUNNOA_URL, LUNNOA_API_KEY, LUNNOA_PROJECT_ID
2528
- Optional config file: lunnoa.config.ts | .mjs | .js | .json
2529
- `;
2530
3180
  async function bindClient(args) {
2531
3181
  const fileConfig = await loadOptionalConfig();
2532
3182
  const url = args.url ?? process.env.LUNNOA_URL ?? fileConfig.url;
@@ -2636,9 +3286,122 @@ async function deployAgent(args) {
2636
3286
  const result = await client.agents.upsertBySlug(projectId, payload);
2637
3287
  console.log(`Upserted agent ${result.id} (${payload.slug})`);
2638
3288
  }
3289
+ async function deployEntityType(args) {
3290
+ if (!args.path) {
3291
+ throw new Error(
3292
+ "Missing definition path. Example: entity-types deploy ./entity-type.ts"
3293
+ );
3294
+ }
3295
+ const { client } = await bindClient(args);
3296
+ const mod = await loadDefinitionModule(args.path);
3297
+ const def = pickEntityTypeDefinition(mod);
3298
+ const payload = entityTypeDefinitionToUpsert(def);
3299
+ console.log(`Deploying entity type slug="${payload.slug}" ...`);
3300
+ const result = await client.entityTypes.upsertBySlug(payload);
3301
+ console.log(
3302
+ `Upserted entity type ${result.id} (${payload.slug})` + (typeof result.schemaRevision === "number" ? ` schemaRevision=${result.schemaRevision}` : "")
3303
+ );
3304
+ }
3305
+ var DISCOVERY_SUBCOMMANDS = {
3306
+ tools: ["list", "get", "search"],
3307
+ apps: ["list", "get"],
3308
+ connections: ["list", "get"],
3309
+ features: void 0,
3310
+ projects: ["list"]
3311
+ };
3312
+ function isDiscoveryCommand(args) {
3313
+ const { command, subcommand } = args;
3314
+ if (!command) {
3315
+ return false;
3316
+ }
3317
+ if (command === "features") {
3318
+ return true;
3319
+ }
3320
+ if (command === "projects" && (subcommand === "list" || !subcommand)) {
3321
+ return true;
3322
+ }
3323
+ if ((command === "agents" || command === "workflows" || command === "entity-types") && subcommand === "list") {
3324
+ return true;
3325
+ }
3326
+ const allowed = DISCOVERY_SUBCOMMANDS[command];
3327
+ return Array.isArray(allowed) && Boolean(subcommand && allowed.includes(subcommand));
3328
+ }
3329
+ function assertKnownDiscoveryShape(args) {
3330
+ const { command, subcommand } = args;
3331
+ const allowed = command ? DISCOVERY_SUBCOMMANDS[command] : void 0;
3332
+ if (!command || allowed === void 0) {
3333
+ return;
3334
+ }
3335
+ if (!subcommand || !allowed.includes(subcommand)) {
3336
+ throw new Error(
3337
+ `Unknown command: ${command} ${subcommand ?? ""}
3338
+
3339
+ ${USAGE}`
3340
+ );
3341
+ }
3342
+ }
3343
+ async function runDiscovery(args) {
3344
+ const { command, subcommand } = args;
3345
+ if (command === "tools" || command === "apps" || command === "connections") {
3346
+ assertKnownDiscoveryShape(args);
3347
+ }
3348
+ if (!isDiscoveryCommand(args)) {
3349
+ return false;
3350
+ }
3351
+ const { client } = await bindClient(args);
3352
+ if (command === "tools" && subcommand === "list") {
3353
+ await runToolsList(client, args);
3354
+ return true;
3355
+ }
3356
+ if (command === "tools" && subcommand === "get") {
3357
+ await runToolsGet(client, args);
3358
+ return true;
3359
+ }
3360
+ if (command === "tools" && subcommand === "search") {
3361
+ await runToolsSearch(client, args);
3362
+ return true;
3363
+ }
3364
+ if (command === "apps" && subcommand === "list") {
3365
+ await runAppsList(client, args);
3366
+ return true;
3367
+ }
3368
+ if (command === "apps" && subcommand === "get") {
3369
+ await runAppsGet(client, args);
3370
+ return true;
3371
+ }
3372
+ if (command === "connections" && subcommand === "list") {
3373
+ await runConnectionsList(client, args);
3374
+ return true;
3375
+ }
3376
+ if (command === "connections" && subcommand === "get") {
3377
+ await runConnectionsGet(client, args);
3378
+ return true;
3379
+ }
3380
+ if (command === "agents" && subcommand === "list") {
3381
+ await runAgentsList(client, args);
3382
+ return true;
3383
+ }
3384
+ if (command === "workflows" && subcommand === "list") {
3385
+ await runWorkflowsList(client, args);
3386
+ return true;
3387
+ }
3388
+ if (command === "entity-types" && subcommand === "list") {
3389
+ await runEntityTypesList(client, args);
3390
+ return true;
3391
+ }
3392
+ if (command === "projects" && (subcommand === "list" || !subcommand)) {
3393
+ await runProjectsList(client, args);
3394
+ return true;
3395
+ }
3396
+ if (command === "features") {
3397
+ await runFeatures(client, args);
3398
+ return true;
3399
+ }
3400
+ return false;
3401
+ }
2639
3402
  async function main() {
2640
3403
  const args = parseArgs(process.argv.slice(2));
2641
- if (!args.command || args.command === "--help" || args.command === "help") {
3404
+ if (!args.command || args.command === "--help" || args.command === "help" || args.help) {
2642
3405
  console.log(USAGE);
2643
3406
  return;
2644
3407
  }
@@ -2646,6 +3409,9 @@ async function main() {
2646
3409
  await runCodegen(args);
2647
3410
  return;
2648
3411
  }
3412
+ if (await runDiscovery(args)) {
3413
+ return;
3414
+ }
2649
3415
  if (args.command === "workflows" && args.subcommand === "deploy") {
2650
3416
  await deployWorkflow(args);
2651
3417
  return;
@@ -2654,6 +3420,10 @@ async function main() {
2654
3420
  await deployAgent(args);
2655
3421
  return;
2656
3422
  }
3423
+ if (args.command === "entity-types" && args.subcommand === "deploy") {
3424
+ await deployEntityType(args);
3425
+ return;
3426
+ }
2657
3427
  console.error(`Unknown command: ${args.command} ${args.subcommand ?? ""}
2658
3428
  `);
2659
3429
  console.log(USAGE);