@lunnoa/client 1.1.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/README.md +20 -0
- package/dist/cli.js +741 -65
- package/package.json +1 -1
- package/skills/lunnoa-client/SKILL.md +19 -3
- package/skills/lunnoa-solution-design/SKILL.md +9 -7
package/README.md
CHANGED
|
@@ -283,6 +283,26 @@ npx @lunnoa/client agents deploy ./agents/triage.ts \
|
|
|
283
283
|
|
|
284
284
|
`defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
|
|
285
285
|
|
|
286
|
+
### CLI discovery
|
|
287
|
+
|
|
288
|
+
Coding agents (and humans) should list the catalogue for a given key instead of guessing action ids. `--json` is the machine-readable form.
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
npx @lunnoa/client tools list --url … --api-key lna_… --for-agent --json
|
|
292
|
+
npx @lunnoa/client tools search email --for-agent --json
|
|
293
|
+
npx @lunnoa/client tools get http_action_send-request --json
|
|
294
|
+
npx @lunnoa/client apps list --json
|
|
295
|
+
npx @lunnoa/client apps get http --json
|
|
296
|
+
npx @lunnoa/client connections list --app gmail --json
|
|
297
|
+
npx @lunnoa/client agents list --json
|
|
298
|
+
npx @lunnoa/client workflows list --json
|
|
299
|
+
npx @lunnoa/client entity-types list --json
|
|
300
|
+
npx @lunnoa/client projects list --json
|
|
301
|
+
npx @lunnoa/client features --json
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`tools list` is compact (`appId`, `actionId`, name, flags). `tools get` prints `inputConfig` and a `defineAction({ id, appId, input })` sketch. Auth flags and `LUNNOA_URL` / `LUNNOA_API_KEY` / `lunnoa.config` work the same as deploy.
|
|
305
|
+
|
|
286
306
|
## Regenerating the API types
|
|
287
307
|
|
|
288
308
|
The generic types are generated from the deployment's public OpenAPI spec (`spec/openapi.public.json`, produced by the platform's `export-openapi --public`):
|
package/dist/cli.js
CHANGED
|
@@ -2049,6 +2049,646 @@ var LunnoaClient = class {
|
|
|
2049
2049
|
}
|
|
2050
2050
|
};
|
|
2051
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
|
+
|
|
2052
2692
|
// src/cli/generate.ts
|
|
2053
2693
|
function computeDeploymentSchemaFingerprint(entityTypes) {
|
|
2054
2694
|
const parts = [...entityTypes].map((type) => {
|
|
@@ -2537,70 +3177,6 @@ async function loadOptionalConfig() {
|
|
|
2537
3177
|
}
|
|
2538
3178
|
|
|
2539
3179
|
// src/cli/index.ts
|
|
2540
|
-
function parseArgs(argv) {
|
|
2541
|
-
const args = { command: argv[0], out: "./src/lunnoa" };
|
|
2542
|
-
let i = 1;
|
|
2543
|
-
if (args.command === "workflows" || args.command === "agents" || args.command === "entity-types") {
|
|
2544
|
-
args.subcommand = argv[1];
|
|
2545
|
-
i = 2;
|
|
2546
|
-
if (argv[2] && !argv[2].startsWith("--")) {
|
|
2547
|
-
args.path = argv[2];
|
|
2548
|
-
i = 3;
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
for (; i < argv.length; i += 1) {
|
|
2552
|
-
const flag = argv[i];
|
|
2553
|
-
const value = argv[i + 1];
|
|
2554
|
-
switch (flag) {
|
|
2555
|
-
case "--url":
|
|
2556
|
-
args.url = value;
|
|
2557
|
-
i += 1;
|
|
2558
|
-
break;
|
|
2559
|
-
case "--api-key":
|
|
2560
|
-
args.apiKey = value;
|
|
2561
|
-
i += 1;
|
|
2562
|
-
break;
|
|
2563
|
-
case "--access-token":
|
|
2564
|
-
args.accessToken = value;
|
|
2565
|
-
i += 1;
|
|
2566
|
-
break;
|
|
2567
|
-
case "--project-id":
|
|
2568
|
-
args.projectId = value;
|
|
2569
|
-
i += 1;
|
|
2570
|
-
break;
|
|
2571
|
-
case "--out":
|
|
2572
|
-
args.out = value;
|
|
2573
|
-
i += 1;
|
|
2574
|
-
break;
|
|
2575
|
-
default:
|
|
2576
|
-
if (!args.path && !flag.startsWith("--")) {
|
|
2577
|
-
args.path = flag;
|
|
2578
|
-
break;
|
|
2579
|
-
}
|
|
2580
|
-
throw new Error(`Unknown option: ${flag}`);
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
return args;
|
|
2584
|
-
}
|
|
2585
|
-
var USAGE = `Usage:
|
|
2586
|
-
npx @lunnoa/client codegen --url <deployment-url> --api-key <lna_key> [--out <dir>]
|
|
2587
|
-
npx @lunnoa/client workflows deploy <path> --url <url> --api-key <key> --project-id <uuid>
|
|
2588
|
-
npx @lunnoa/client agents deploy <path> --url <url> --api-key <key> --project-id <uuid>
|
|
2589
|
-
npx @lunnoa/client entity-types deploy <path> --url <url> --api-key <key>
|
|
2590
|
-
|
|
2591
|
-
Options:
|
|
2592
|
-
--url Base URL of the Lunnoa Automate deployment
|
|
2593
|
-
--api-key Machine API key (lna_...)
|
|
2594
|
-
--access-token User JWT as an alternative to --api-key
|
|
2595
|
-
--project-id Project UUID (required for workflow/agent deploy)
|
|
2596
|
-
--out Output directory for lunnoa.generated.ts (codegen; default: ./src/lunnoa)
|
|
2597
|
-
|
|
2598
|
-
Environment: LUNNOA_URL, LUNNOA_API_KEY, LUNNOA_PROJECT_ID
|
|
2599
|
-
Optional config file: lunnoa.config.ts | .mjs | .js | .json
|
|
2600
|
-
|
|
2601
|
-
Entity-type deploy is workspace-scoped (no --project-id). Unknown instance
|
|
2602
|
-
attributes are rejected unless allowUnknownAttributes is true; use extensions.
|
|
2603
|
-
`;
|
|
2604
3180
|
async function bindClient(args) {
|
|
2605
3181
|
const fileConfig = await loadOptionalConfig();
|
|
2606
3182
|
const url = args.url ?? process.env.LUNNOA_URL ?? fileConfig.url;
|
|
@@ -2726,9 +3302,106 @@ async function deployEntityType(args) {
|
|
|
2726
3302
|
`Upserted entity type ${result.id} (${payload.slug})` + (typeof result.schemaRevision === "number" ? ` schemaRevision=${result.schemaRevision}` : "")
|
|
2727
3303
|
);
|
|
2728
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
|
+
}
|
|
2729
3402
|
async function main() {
|
|
2730
3403
|
const args = parseArgs(process.argv.slice(2));
|
|
2731
|
-
if (!args.command || args.command === "--help" || args.command === "help") {
|
|
3404
|
+
if (!args.command || args.command === "--help" || args.command === "help" || args.help) {
|
|
2732
3405
|
console.log(USAGE);
|
|
2733
3406
|
return;
|
|
2734
3407
|
}
|
|
@@ -2736,6 +3409,9 @@ async function main() {
|
|
|
2736
3409
|
await runCodegen(args);
|
|
2737
3410
|
return;
|
|
2738
3411
|
}
|
|
3412
|
+
if (await runDiscovery(args)) {
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
2739
3415
|
if (args.command === "workflows" && args.subcommand === "deploy") {
|
|
2740
3416
|
await deployWorkflow(args);
|
|
2741
3417
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunnoa/client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "TypeScript SDK for building applications on top of Lunnoa Automate — typed client for the Public API (agents, workflows, executions, entities, knowledge, queues), SSE agent chat streaming, and deployment-specific codegen",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -54,8 +54,12 @@ Run these before inventing resources or shipping UI code:
|
|
|
54
54
|
browser (SDK throws when `window` exists).
|
|
55
55
|
2. **Codegen** — When building typed UIs, run
|
|
56
56
|
`npx @lunnoa/client codegen` against the deployment first.
|
|
57
|
-
3. **Discover before inventing** —
|
|
58
|
-
`
|
|
57
|
+
3. **Discover before inventing** — prefer the CLI (JSON for agents), then
|
|
58
|
+
the SDK: `npx @lunnoa/client tools list --json --for-agent`,
|
|
59
|
+
`tools get <actionId> --json`, `apps list`, `connections list`,
|
|
60
|
+
`agents|workflows|entity-types|projects list`, `features`.
|
|
61
|
+
SDK equivalents: `workflowApps.list()`, `agents.list`, `workflows.list`,
|
|
62
|
+
`entityTypes.list`, `discovery.enabledFeatures()`.
|
|
59
63
|
Do not invent catalogue action ids.
|
|
60
64
|
4. **Run vs deploy** — Ad-hoc: `actions.run` / `runs.start` /
|
|
61
65
|
`workflows.execute`. Durable shared automation: CLI
|
|
@@ -361,6 +365,18 @@ const result = await lunnoa.runs.start(ping);
|
|
|
361
365
|
// or lunnoa.runs.startAndWait(ping)
|
|
362
366
|
```
|
|
363
367
|
|
|
368
|
+
Discover the catalogue **before** writing `defineAction` (do not guess ids):
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
npx @lunnoa/client tools list --json --for-agent
|
|
372
|
+
npx @lunnoa/client tools search email --json --for-agent
|
|
373
|
+
npx @lunnoa/client tools get http_action_send-request --json
|
|
374
|
+
npx @lunnoa/client connections list --app gmail --json
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`tools get` returns `inputConfig` / `inputFields` and a `defineAction` sketch.
|
|
378
|
+
`tools list` is compact; use `get` for the schema. `--json` is for coding agents.
|
|
379
|
+
|
|
364
380
|
Deploy to Automate UI (upsert by slug; prefer CLI):
|
|
365
381
|
|
|
366
382
|
```bash
|
|
@@ -510,7 +526,7 @@ emails (capped; not Azure object IDs).
|
|
|
510
526
|
3. Call `discovery.enabledFeatures()` and hide unsupported areas.
|
|
511
527
|
4. Build forms/tables from `attributeSchema` / `customInputConfig` (or the generated types).
|
|
512
528
|
5. Render workflow progress from `executionPath`; handle `NEEDS_INPUT` with `submitInput`.
|
|
513
|
-
6. For single-action calls, use `actions.run`; for linear multi-step, use `runs.start` / `defineWorkflow`. Deploy with `npx @lunnoa/client workflows|agents deploy`. Discover catalogue via `workflowApps.list()
|
|
529
|
+
6. For single-action calls, use `actions.run`; for linear multi-step, use `runs.start` / `defineWorkflow`. Deploy with `npx @lunnoa/client workflows|agents deploy`. Discover catalogue via `npx @lunnoa/client tools list|get|search` (or `workflowApps.list()`).
|
|
514
530
|
7. For Request Approval nodes, use `approvals.inbox` / `approvals.decide` (or `executions.submitApproval`).
|
|
515
531
|
8. Chat: stream via SSE, handle resume (reconnects) and stop, load history from the task.
|
|
516
532
|
9. Handle `LunnoaApiError`: 401 → re-auth; 403 → hide the capability; 429 → back off.
|
|
@@ -63,8 +63,9 @@ interrogation.
|
|
|
63
63
|
### Integrations
|
|
64
64
|
|
|
65
65
|
- Which external systems? Prefer existing catalogue apps (Slack, HTTP, CRM…).
|
|
66
|
-
- Do not invent action ids. Implementation must
|
|
67
|
-
|
|
66
|
+
- Do not invent action ids. Implementation must discover the catalogue for
|
|
67
|
+
this API key (`npx @lunnoa/client tools list --json --for-agent`, then
|
|
68
|
+
`tools get <actionId> --json`, or `workflowApps.list()` / codegen).
|
|
68
69
|
|
|
69
70
|
### Auth for any custom UI
|
|
70
71
|
|
|
@@ -96,8 +97,9 @@ interrogation.
|
|
|
96
97
|
2. **Connections stay on Lunnoa.** Definitions only reference connection ids
|
|
97
98
|
(or omit for sole/default resolution). Never ask the partner to put OAuth
|
|
98
99
|
secrets in their app.
|
|
99
|
-
3. **Do not invent catalogue actions.** Discover with
|
|
100
|
-
|
|
100
|
+
3. **Do not invent catalogue actions.** Discover with
|
|
101
|
+
`npx @lunnoa/client tools list|get|search --json` (or `workflowApps.list()`
|
|
102
|
+
/ codegen). Client `defineAction` binds existing catalogue actions; toolkit
|
|
101
103
|
`createAction` is a different product (in-platform apps).
|
|
102
104
|
4. **Durable vs ephemeral:** experiments → `runs.start`; shared production
|
|
103
105
|
automation → CLI `workflows deploy` / `agents deploy` by slug.
|
|
@@ -131,8 +133,8 @@ can execute. Use this shape:
|
|
|
131
133
|
- Notes: <CORS, BFF, project id, env vars>
|
|
132
134
|
|
|
133
135
|
### Discovery (run first)
|
|
134
|
-
- [ ]
|
|
135
|
-
- [ ] agents
|
|
136
|
+
- [ ] `npx @lunnoa/client tools list --json --for-agent` then `tools get <id>`
|
|
137
|
+
- [ ] agents / workflows / entity-types / projects list — reuse before create
|
|
136
138
|
- [ ] codegen — if building typed entity/workflow accessors
|
|
137
139
|
|
|
138
140
|
### Resources
|
|
@@ -175,7 +177,7 @@ continue in a coding turn with that skill loaded.
|
|
|
175
177
|
action)? Need approvals? Existing HTTP/CRM connections?
|
|
176
178
|
|
|
177
179
|
**You conclude (example):** Pattern A BFF; Agent `order-status` with tools
|
|
178
|
-
`defineAction` for ERP lookup (after `
|
|
180
|
+
`defineAction` for ERP lookup (after `tools list` / `tools get`) + optional
|
|
179
181
|
workflow for staff notifications; UI = chat only; no agent schedule.
|
|
180
182
|
|
|
181
183
|
**You produce** the build brief, then hand off.
|