@lunnoa/client 1.4.0 → 2.0.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 +17 -10
- package/dist/cli.js +219 -107
- package/dist/index.cjs +40 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1327 -1333
- package/dist/index.d.ts +1327 -1333
- package/dist/index.js +40 -37
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +59 -37
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +665 -718
- package/dist/react.d.ts +665 -718
- package/dist/react.js +59 -37
- package/dist/react.js.map +1 -1
- package/package.json +2 -6
- package/skills/lunnoa-client/SKILL.md +23 -11
- package/skills/lunnoa-solution-design/SKILL.md +21 -5
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ TypeScript SDK for building applications **on top of** [Lunnoa Automate](https:/
|
|
|
7
7
|
|
|
8
8
|
## What is this?
|
|
9
9
|
|
|
10
|
-
`@lunnoa/client` is the official consumption SDK for the Lunnoa Automate **Public API**: a typed client for agents and chat (with SSE streaming), workflows and executions, entities (Objects), knowledge, queues, variables,
|
|
10
|
+
`@lunnoa/client` is the official consumption SDK for the Lunnoa Automate **Public API**: a typed client for agents and chat (with SSE streaming), workflows and executions, entities (Objects), knowledge, queues, variables, and connections — plus a codegen command that types the SDK against **your** deployment's entity types, workflows, and agents.
|
|
11
11
|
|
|
12
12
|
**The boundary with the other Lunnoa SDK:** [`@lunnoa/toolkit`](https://github.com/lunnoa-automate/lunnoa_toolkit) is for building integration apps that run *inside* the platform (`createAction` / `createApp`); **`@lunnoa/client` is for building applications *on top of* the platform** — including `define*` factories, `runs.start`, and CLI deploy of workflows/agents.
|
|
13
13
|
|
|
@@ -21,6 +21,14 @@ npm install @lunnoa/client
|
|
|
21
21
|
|
|
22
22
|
**Requirements:** Node.js ≥ 20 (or any runtime with `fetch` and web streams).
|
|
23
23
|
|
|
24
|
+
### Start a new app
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @lunnoa/client init my-app --url https://lunnoa.your-company.example
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Scaffolds the [lunnoa-starter](https://github.com/lunnoa-automate/lunnoa-starter) Next.js template (thin, Docker-deployable, both auth patterns behind `LUNNOA_AUTH_MODE`) and writes `.env`, prompting for an API key when run interactively. `--template <owner/repo[#ref]|./local-folder>` overrides the source. Then `npm install` and open the folder in Claude Code / Cursor / Codex.
|
|
31
|
+
|
|
24
32
|
## Quickstart
|
|
25
33
|
|
|
26
34
|
```typescript
|
|
@@ -98,18 +106,18 @@ const { user, status, login, logout } = useLunnoaAuth();
|
|
|
98
106
|
const lunnoa = useLunnoaClient();
|
|
99
107
|
const { progress, status: progressStatus } = useExecutionProgress(executionId);
|
|
100
108
|
const { waiting, pendingInput, submitInput } = useNeedsInput(progress);
|
|
101
|
-
const { messages, send, stop, taskId } = useAgentChat(agentId);
|
|
109
|
+
const { messages, send, stop, taskId } = useAgentChat(agentId);
|
|
102
110
|
const { items, decide } = useApprovalsInbox({ pollIntervalMs: 15_000 });
|
|
103
111
|
const { data, page, setPage } = useEntityList({ objectTypeSlug: 'invoice' });
|
|
104
112
|
```
|
|
105
113
|
|
|
106
|
-
|
|
114
|
+
Peer: `react` ≥ 18 (required for `/react`). `@ai-sdk/react` (behind `useAgentChat`) ships as a dependency. Core `@lunnoa/client` stays headless.
|
|
107
115
|
|
|
108
116
|
## Resources
|
|
109
117
|
|
|
110
118
|
Most namespaces map to the Public API surface (`openapi.public.json`):
|
|
111
119
|
|
|
112
|
-
`auth` · `agents` · `tasks` · `agentChat` (SSE streaming) · `workflows` · `executions` · `actions` · `entities` · `entityTypes` · `knowledge` · `queues` · `queueItems` · `variables` · `connections` · `
|
|
120
|
+
`auth` · `agents` · `tasks` · `agentChat` (SSE streaming) · `workflows` · `executions` · `actions` · `entities` · `entityTypes` · `knowledge` · `queues` · `queueItems` · `variables` · `connections` · `workflowApps` · `discovery`
|
|
113
121
|
|
|
114
122
|
`approvals` uses the authenticated **workspace** API (`/api/approvals/...`), not Public API v1. The inbox is filtered to requests where the current user is an eligible approver.
|
|
115
123
|
|
|
@@ -268,7 +276,7 @@ Re-run codegen whenever entity types, workflows, or agents change on the deploym
|
|
|
268
276
|
|
|
269
277
|
## Define, run, and CLI deploy
|
|
270
278
|
|
|
271
|
-
Author local definitions with `defineWorkflow` / `defineAgent` / `defineEntityType`, run linear chains with `lunnoa.runs.start`, and upsert with the CLI (prefer CLI over calling upsert from app code). An entity type slug is unique on the deployment. Deploy grants that type to the API key's workspace when the slug is new, and will not rewrite a type already granted to other workspaces
|
|
279
|
+
Author local definitions with `defineWorkflow` / `defineAgent` / `defineEntityType`, run linear chains with `lunnoa.runs.start`, and upsert with the CLI (prefer CLI over calling upsert from app code). Agent and workflow upsert is by slug inside the workspace on the API key. An entity type slug is unique on the deployment. Deploy grants that type to the API key's workspace when the slug is new, and will not rewrite a type already granted to other workspaces. Instance writes reject unknown attribute keys unless `allowUnknownAttributes` is true — use the reserved `extensions` object for flexible bags:
|
|
272
280
|
|
|
273
281
|
```bash
|
|
274
282
|
npx @lunnoa/client entity-types deploy ./human.ts --url https://lunnoa.example --api-key lna_...
|
|
@@ -278,9 +286,9 @@ Workflows and agents:
|
|
|
278
286
|
|
|
279
287
|
```bash
|
|
280
288
|
npx @lunnoa/client workflows deploy ./workflows/sync-orders.ts \
|
|
281
|
-
--url https://lunnoa.example --api-key lna_...
|
|
289
|
+
--url https://lunnoa.example --api-key lna_...
|
|
282
290
|
npx @lunnoa/client agents deploy ./agents/triage.ts \
|
|
283
|
-
--url https://lunnoa.example --api-key lna_...
|
|
291
|
+
--url https://lunnoa.example --api-key lna_...
|
|
284
292
|
```
|
|
285
293
|
|
|
286
294
|
`defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
|
|
@@ -299,11 +307,10 @@ npx @lunnoa/client connections list --app gmail --json
|
|
|
299
307
|
npx @lunnoa/client agents list --json
|
|
300
308
|
npx @lunnoa/client workflows list --json
|
|
301
309
|
npx @lunnoa/client entity-types list --json
|
|
302
|
-
npx @lunnoa/client projects list --json
|
|
303
310
|
npx @lunnoa/client features --json
|
|
304
311
|
```
|
|
305
312
|
|
|
306
|
-
`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.
|
|
313
|
+
`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. The CLI also reads `./.env` (real environment variables win).
|
|
307
314
|
|
|
308
315
|
## Regenerating the API types
|
|
309
316
|
|
|
@@ -330,7 +337,7 @@ Partner paste block:
|
|
|
330
337
|
|
|
331
338
|
```text
|
|
332
339
|
Use @lunnoa/client skills: lunnoa-solution-design then lunnoa-client.
|
|
333
|
-
LUNNOA_URL=… LUNNOA_API_KEY=lna_… (server only).
|
|
340
|
+
LUNNOA_URL=… LUNNOA_API_KEY=lna_… (server only).
|
|
334
341
|
Start by interviewing me, then workflowApps.list() / codegen.
|
|
335
342
|
```
|
|
336
343
|
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
5
|
-
import { resolve as
|
|
4
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5
|
+
import { resolve as resolve4 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/core/auth.ts
|
|
8
8
|
var API_KEY_PREFIX = "lna_";
|
|
@@ -661,12 +661,12 @@ var AgentsResource = class {
|
|
|
661
661
|
});
|
|
662
662
|
}
|
|
663
663
|
/**
|
|
664
|
-
* Upsert an agent by
|
|
664
|
+
* Upsert an agent by slug inside the workspace on the API key.
|
|
665
665
|
* Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
|
|
666
666
|
*/
|
|
667
|
-
upsertBySlug(
|
|
667
|
+
upsertBySlug(body, options) {
|
|
668
668
|
const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
|
|
669
|
-
return this.http.post(
|
|
669
|
+
return this.http.post("/api/agents/upsert", {
|
|
670
670
|
body: payload
|
|
671
671
|
});
|
|
672
672
|
}
|
|
@@ -1083,6 +1083,33 @@ var EntityTypesResource = class {
|
|
|
1083
1083
|
}
|
|
1084
1084
|
};
|
|
1085
1085
|
|
|
1086
|
+
// src/resources/tables.ts
|
|
1087
|
+
var TablesResource = class {
|
|
1088
|
+
constructor(http) {
|
|
1089
|
+
this.http = http;
|
|
1090
|
+
}
|
|
1091
|
+
http;
|
|
1092
|
+
/** Lists tables in the API key's workspace (without columns and rows). */
|
|
1093
|
+
list() {
|
|
1094
|
+
return this.http.get("/api/tables");
|
|
1095
|
+
}
|
|
1096
|
+
/** Returns a single table by ID, including columns and rows. */
|
|
1097
|
+
get(id) {
|
|
1098
|
+
return this.http.get(`/api/tables/${id}`);
|
|
1099
|
+
}
|
|
1100
|
+
/** Returns a single table by its workspace slug, including columns and rows. */
|
|
1101
|
+
getBySlug(slug) {
|
|
1102
|
+
return this.http.get(`/api/tables/slug/${slug}`);
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Creates or updates a table by workspace slug. Omitted columns or rows
|
|
1106
|
+
* keep their current value. Marks `managedByCode` by default.
|
|
1107
|
+
*/
|
|
1108
|
+
upsertBySlug(input) {
|
|
1109
|
+
return this.http.post("/api/tables/upsert", { body: input });
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1086
1113
|
// src/resources/approvals.ts
|
|
1087
1114
|
var ApprovalsResource = class {
|
|
1088
1115
|
constructor(http) {
|
|
@@ -1422,7 +1449,7 @@ function computeDurationMs(start, end) {
|
|
|
1422
1449
|
return b - a;
|
|
1423
1450
|
}
|
|
1424
1451
|
function defaultSleep(ms) {
|
|
1425
|
-
return new Promise((
|
|
1452
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1426
1453
|
}
|
|
1427
1454
|
|
|
1428
1455
|
// src/resources/knowledge.ts
|
|
@@ -1484,30 +1511,6 @@ var KnowledgeResource = class {
|
|
|
1484
1511
|
}
|
|
1485
1512
|
};
|
|
1486
1513
|
|
|
1487
|
-
// src/resources/projects.ts
|
|
1488
|
-
var ProjectsResource = class {
|
|
1489
|
-
constructor(http) {
|
|
1490
|
-
this.http = http;
|
|
1491
|
-
}
|
|
1492
|
-
http;
|
|
1493
|
-
/** Lists projects in the workspace, most recently updated first. */
|
|
1494
|
-
list(options) {
|
|
1495
|
-
return this.http.get("/api/projects", {
|
|
1496
|
-
query: serializeListQuery(options)
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
|
-
/** Returns a single project. */
|
|
1500
|
-
get(projectId, options) {
|
|
1501
|
-
return this.http.get(`/api/projects/${projectId}`, {
|
|
1502
|
-
query: serializeListQuery(options)
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
/** Creates a project. */
|
|
1506
|
-
create(data) {
|
|
1507
|
-
return this.http.post("/api/projects", { body: data });
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
|
|
1511
1514
|
// src/resources/queue-items.ts
|
|
1512
1515
|
var QueueItemsResource = class {
|
|
1513
1516
|
constructor(http) {
|
|
@@ -1787,7 +1790,7 @@ var RunsResource = class {
|
|
|
1787
1790
|
* Start a linear multi-step ad-hoc Execution.
|
|
1788
1791
|
*
|
|
1789
1792
|
* Accepts a `defineWorkflow` result, a step array, or an explicit
|
|
1790
|
-
* `{ steps,
|
|
1793
|
+
* `{ steps, name? }` payload.
|
|
1791
1794
|
*/
|
|
1792
1795
|
start(input) {
|
|
1793
1796
|
const options = normalizeStartRunInput(input);
|
|
@@ -2005,15 +2008,14 @@ var WorkflowsResource = class {
|
|
|
2005
2008
|
});
|
|
2006
2009
|
}
|
|
2007
2010
|
/**
|
|
2008
|
-
* Upsert a workflow by
|
|
2011
|
+
* Upsert a workflow by slug inside the workspace on the API key.
|
|
2009
2012
|
* Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
|
|
2010
2013
|
*/
|
|
2011
|
-
upsertBySlug(
|
|
2014
|
+
upsertBySlug(body) {
|
|
2012
2015
|
const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
|
|
2013
|
-
return this.http.post(
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
);
|
|
2016
|
+
return this.http.post("/api/workflows/upsert", {
|
|
2017
|
+
body: payload
|
|
2018
|
+
});
|
|
2017
2019
|
}
|
|
2018
2020
|
/**
|
|
2019
2021
|
* Starts a new execution of the workflow with the given trigger inputs.
|
|
@@ -2067,13 +2069,14 @@ var LunnoaClient = class {
|
|
|
2067
2069
|
approvals;
|
|
2068
2070
|
entities;
|
|
2069
2071
|
entityTypes;
|
|
2072
|
+
/** Small reference / lookup tables (rows stored inline; use entities for transactional data). */
|
|
2073
|
+
tables;
|
|
2070
2074
|
knowledge;
|
|
2071
2075
|
queues;
|
|
2072
2076
|
queueItems;
|
|
2073
2077
|
variables;
|
|
2074
2078
|
scorecards;
|
|
2075
2079
|
connections;
|
|
2076
|
-
projects;
|
|
2077
2080
|
workflowApps;
|
|
2078
2081
|
discovery;
|
|
2079
2082
|
constructor(options) {
|
|
@@ -2090,13 +2093,13 @@ var LunnoaClient = class {
|
|
|
2090
2093
|
this.workflows = new WorkflowsResource(this.http, this.executions);
|
|
2091
2094
|
this.entities = new EntitiesResource(this.http);
|
|
2092
2095
|
this.entityTypes = new EntityTypesResource(this.http);
|
|
2096
|
+
this.tables = new TablesResource(this.http);
|
|
2093
2097
|
this.knowledge = new KnowledgeResource(this.http);
|
|
2094
2098
|
this.queues = new QueuesResource(this.http);
|
|
2095
2099
|
this.queueItems = new QueueItemsResource(this.http);
|
|
2096
2100
|
this.variables = new VariablesResource(this.http);
|
|
2097
2101
|
this.scorecards = new ScorecardsResource(this.http);
|
|
2098
2102
|
this.connections = new ConnectionsResource(this.http);
|
|
2099
|
-
this.projects = new ProjectsResource(this.http);
|
|
2100
2103
|
this.discovery = new DiscoveryResource(this.http);
|
|
2101
2104
|
}
|
|
2102
2105
|
};
|
|
@@ -2106,19 +2109,19 @@ var GROUPS_WITH_SUBCOMMAND = /* @__PURE__ */ new Set([
|
|
|
2106
2109
|
"workflows",
|
|
2107
2110
|
"agents",
|
|
2108
2111
|
"entity-types",
|
|
2112
|
+
"tables",
|
|
2109
2113
|
"tools",
|
|
2110
2114
|
"apps",
|
|
2111
2115
|
"connections",
|
|
2112
|
-
"projects",
|
|
2113
2116
|
"skills"
|
|
2114
2117
|
]);
|
|
2115
2118
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
2116
2119
|
"--url",
|
|
2117
2120
|
"--api-key",
|
|
2118
2121
|
"--access-token",
|
|
2119
|
-
"--project-id",
|
|
2120
2122
|
"--out",
|
|
2121
|
-
"--app"
|
|
2123
|
+
"--app",
|
|
2124
|
+
"--template"
|
|
2122
2125
|
]);
|
|
2123
2126
|
function requireFlagValue(flag, value) {
|
|
2124
2127
|
if (!value || value.startsWith("--")) {
|
|
@@ -2172,15 +2175,15 @@ function parseArgs(argv) {
|
|
|
2172
2175
|
case "--access-token":
|
|
2173
2176
|
args.accessToken = value;
|
|
2174
2177
|
break;
|
|
2175
|
-
case "--project-id":
|
|
2176
|
-
args.projectId = value;
|
|
2177
|
-
break;
|
|
2178
2178
|
case "--out":
|
|
2179
2179
|
args.out = value;
|
|
2180
2180
|
break;
|
|
2181
2181
|
case "--app":
|
|
2182
2182
|
args.app = value;
|
|
2183
2183
|
break;
|
|
2184
|
+
case "--template":
|
|
2185
|
+
args.template = value;
|
|
2186
|
+
break;
|
|
2184
2187
|
default:
|
|
2185
2188
|
break;
|
|
2186
2189
|
}
|
|
@@ -2196,6 +2199,7 @@ function parseArgs(argv) {
|
|
|
2196
2199
|
return args;
|
|
2197
2200
|
}
|
|
2198
2201
|
var USAGE = `Usage:
|
|
2202
|
+
npx @lunnoa/client init <dir> [--url <url>] [--api-key <lna_key>] [--template <owner/repo|path>]
|
|
2199
2203
|
npx @lunnoa/client codegen --url <url> --api-key <lna_key> [--out <dir>]
|
|
2200
2204
|
|
|
2201
2205
|
Discover (do not invent catalogue action ids):
|
|
@@ -2209,12 +2213,13 @@ var USAGE = `Usage:
|
|
|
2209
2213
|
npx @lunnoa/client agents list [--json]
|
|
2210
2214
|
npx @lunnoa/client workflows list [--json]
|
|
2211
2215
|
npx @lunnoa/client entity-types list [--json]
|
|
2212
|
-
npx @lunnoa/client
|
|
2216
|
+
npx @lunnoa/client tables list [--json]
|
|
2217
|
+
npx @lunnoa/client tables get <slugOrId> [--json]
|
|
2213
2218
|
npx @lunnoa/client features [--json]
|
|
2214
2219
|
|
|
2215
|
-
Deploy (upsert by slug):
|
|
2216
|
-
npx @lunnoa/client workflows deploy <path> --url <url> --api-key <key>
|
|
2217
|
-
npx @lunnoa/client agents deploy <path> --url <url> --api-key <key>
|
|
2220
|
+
Deploy (upsert by slug inside the workspace on the API key):
|
|
2221
|
+
npx @lunnoa/client workflows deploy <path> --url <url> --api-key <key>
|
|
2222
|
+
npx @lunnoa/client agents deploy <path> --url <url> --api-key <key>
|
|
2218
2223
|
npx @lunnoa/client entity-types deploy <path> --url <url> --api-key <key>
|
|
2219
2224
|
|
|
2220
2225
|
Agent skills (copies this version's skills into .claude/skills and .cursor/skills):
|
|
@@ -2224,19 +2229,19 @@ Options:
|
|
|
2224
2229
|
--url Base URL of the Lunnoa Automate deployment
|
|
2225
2230
|
--api-key Machine API key (lna_...)
|
|
2226
2231
|
--access-token User JWT as an alternative to --api-key
|
|
2227
|
-
--project-id Project UUID (required for workflow/agent deploy)
|
|
2228
2232
|
--out Output directory for lunnoa.generated.ts (codegen; default: ./src/lunnoa)
|
|
2229
2233
|
--app Restrict tools/connections to one workflow app id
|
|
2230
2234
|
--for-agent Only catalogue entries with availableForAgent
|
|
2235
|
+
--template init starter: GitHub owner/repo[#ref] or local folder (default: lunnoa-automate/lunnoa-starter)
|
|
2231
2236
|
--json Machine-readable JSON (preferred for coding agents)
|
|
2232
2237
|
--help Show this help
|
|
2233
2238
|
|
|
2234
|
-
Environment: LUNNOA_URL, LUNNOA_API_KEY
|
|
2239
|
+
Environment: LUNNOA_URL, LUNNOA_API_KEY (also read from ./.env)
|
|
2235
2240
|
Optional config file: lunnoa.config.ts | .mjs | .js | .json
|
|
2236
2241
|
|
|
2237
2242
|
tools get prints inputConfig and a defineAction() sketch. tools list is compact;
|
|
2238
|
-
run tools get for the schema.
|
|
2239
|
-
and grants the API key's workspace when the slug is new
|
|
2243
|
+
run tools get for the schema. An entity-type slug is unique on the deployment
|
|
2244
|
+
and grants the API key's workspace when the slug is new.
|
|
2240
2245
|
A slug already granted to other workspaces is not rewritten. Unknown instance attributes are rejected unless
|
|
2241
2246
|
allowUnknownAttributes is true; use extensions.
|
|
2242
2247
|
`;
|
|
@@ -2708,16 +2713,14 @@ async function runEntityTypesList(client, args) {
|
|
|
2708
2713
|
{ key: "managedByCode", header: "CODE" }
|
|
2709
2714
|
]);
|
|
2710
2715
|
}
|
|
2711
|
-
async function
|
|
2712
|
-
const
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
agents: project._count?.agents ?? "",
|
|
2720
|
-
workflows: project._count?.workflows ?? ""
|
|
2716
|
+
async function runTablesList(client, args) {
|
|
2717
|
+
const tables = await client.tables.list();
|
|
2718
|
+
const rows = tables.map((table) => ({
|
|
2719
|
+
id: table.id,
|
|
2720
|
+
slug: table.slug,
|
|
2721
|
+
name: table.name,
|
|
2722
|
+
description: table.description ?? "",
|
|
2723
|
+
managedByCode: Boolean(table.managedByCode)
|
|
2721
2724
|
}));
|
|
2722
2725
|
if (args.json) {
|
|
2723
2726
|
printJson(rows);
|
|
@@ -2725,11 +2728,24 @@ async function runProjectsList(client, args) {
|
|
|
2725
2728
|
}
|
|
2726
2729
|
printTable(rows, [
|
|
2727
2730
|
{ key: "id", header: "ID" },
|
|
2731
|
+
{ key: "slug", header: "SLUG" },
|
|
2728
2732
|
{ key: "name", header: "NAME" },
|
|
2729
|
-
{ key: "
|
|
2730
|
-
{ key: "workflows", header: "WORKFLOWS" }
|
|
2733
|
+
{ key: "managedByCode", header: "CODE" }
|
|
2731
2734
|
]);
|
|
2732
2735
|
}
|
|
2736
|
+
async function runTablesGet(client, args) {
|
|
2737
|
+
const ref = requireTarget(args, "table slug or id (example: tables get cost_centres)");
|
|
2738
|
+
const table = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(ref) ? await client.tables.get(ref) : await client.tables.getBySlug(ref);
|
|
2739
|
+
if (args.json) {
|
|
2740
|
+
printJson(table);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
console.log(`id: ${table.id}`);
|
|
2744
|
+
console.log(`slug: ${table.slug}`);
|
|
2745
|
+
console.log(`name: ${table.name}`);
|
|
2746
|
+
console.log(`columns: ${(table.columns ?? []).map((c) => c.key).join(", ")}`);
|
|
2747
|
+
console.log(`rows: ${table.rows?.length ?? 0}`);
|
|
2748
|
+
}
|
|
2733
2749
|
async function runFeatures(client, args) {
|
|
2734
2750
|
const features = await client.discovery.enabledFeatures();
|
|
2735
2751
|
if (args.json) {
|
|
@@ -3222,27 +3238,127 @@ async function loadOptionalConfig() {
|
|
|
3222
3238
|
continue;
|
|
3223
3239
|
}
|
|
3224
3240
|
if (name.endsWith(".json")) {
|
|
3225
|
-
const { readFile } = await import("fs/promises");
|
|
3226
|
-
const raw = await
|
|
3227
|
-
|
|
3241
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
3242
|
+
const raw = await readFile2(absolute, "utf8");
|
|
3243
|
+
const cfg2 = JSON.parse(raw);
|
|
3244
|
+
return { url: cfg2.url, apiKey: cfg2.apiKey };
|
|
3228
3245
|
}
|
|
3229
3246
|
const mod = await import(pathToFileURL(absolute).href);
|
|
3230
3247
|
const cfg = mod.default ?? mod;
|
|
3231
3248
|
return {
|
|
3232
3249
|
url: cfg.url,
|
|
3233
|
-
apiKey: cfg.apiKey
|
|
3234
|
-
projectId: cfg.projectId
|
|
3250
|
+
apiKey: cfg.apiKey
|
|
3235
3251
|
};
|
|
3236
3252
|
}
|
|
3237
3253
|
return {};
|
|
3238
3254
|
}
|
|
3239
3255
|
|
|
3256
|
+
// src/cli/init.ts
|
|
3257
|
+
import { spawn } from "child_process";
|
|
3258
|
+
import { cp, mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
3259
|
+
import { basename, join, resolve as resolve2 } from "path";
|
|
3260
|
+
import { createInterface } from "readline/promises";
|
|
3261
|
+
var DEFAULT_TEMPLATE = "lunnoa-automate/lunnoa-starter";
|
|
3262
|
+
var SKIP_LOCAL = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", ".env", ".claude", ".cursor"]);
|
|
3263
|
+
function setEnvValues(source, values) {
|
|
3264
|
+
let out = source;
|
|
3265
|
+
for (const [key, value] of Object.entries(values)) {
|
|
3266
|
+
if (!value) continue;
|
|
3267
|
+
const line = `${key}=${value}`;
|
|
3268
|
+
const existing = new RegExp(`^${key}=.*$`, "m");
|
|
3269
|
+
out = existing.test(out) ? out.replace(existing, () => line) : `${out}${out === "" || out.endsWith("\n") ? "" : "\n"}${line}
|
|
3270
|
+
`;
|
|
3271
|
+
}
|
|
3272
|
+
return out;
|
|
3273
|
+
}
|
|
3274
|
+
function dropRetiredProjectEnv(source) {
|
|
3275
|
+
const body = source.split("\n").filter(
|
|
3276
|
+
(line) => !line.includes("LUNNOA_PROJECT_ID") && !line.includes("projects list") && !/Project UUID/i.test(line)
|
|
3277
|
+
).join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n+$/, "");
|
|
3278
|
+
return body.length ? `${body}
|
|
3279
|
+
` : "";
|
|
3280
|
+
}
|
|
3281
|
+
async function isDirectory(path) {
|
|
3282
|
+
return (await stat(path).catch(() => void 0))?.isDirectory() ?? false;
|
|
3283
|
+
}
|
|
3284
|
+
async function downloadGithubTemplate(template, dest) {
|
|
3285
|
+
const [repo, ref = "HEAD"] = template.split("#");
|
|
3286
|
+
const url = `https://codeload.github.com/${repo}/tar.gz/${ref}`;
|
|
3287
|
+
const response = await fetch(url);
|
|
3288
|
+
if (!response.ok) {
|
|
3289
|
+
throw new Error(`Could not download template ${template} (${response.status} from ${url}).`);
|
|
3290
|
+
}
|
|
3291
|
+
const tarball = Buffer.from(await response.arrayBuffer());
|
|
3292
|
+
await new Promise((done, fail) => {
|
|
3293
|
+
const child = spawn("tar", ["-xzf", "-", "--strip-components=1"], {
|
|
3294
|
+
cwd: dest,
|
|
3295
|
+
stdio: ["pipe", "ignore", "inherit"]
|
|
3296
|
+
});
|
|
3297
|
+
child.on(
|
|
3298
|
+
"error",
|
|
3299
|
+
(error) => fail(new Error(`Could not run tar (${error.message}). Download ${url} manually.`))
|
|
3300
|
+
);
|
|
3301
|
+
child.on(
|
|
3302
|
+
"close",
|
|
3303
|
+
(code) => code === 0 ? done() : fail(new Error(`tar exited with code ${code}`))
|
|
3304
|
+
);
|
|
3305
|
+
child.stdin.end(tarball);
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
async function runInit(args) {
|
|
3309
|
+
if (!args.path) {
|
|
3310
|
+
throw new Error("Missing project directory. Example: init my-app --url https://lunnoa.example");
|
|
3311
|
+
}
|
|
3312
|
+
const dest = resolve2(process.cwd(), args.path);
|
|
3313
|
+
if ((await readdir(dest).catch(() => [])).length > 0) {
|
|
3314
|
+
throw new Error(`${dest} already exists and is not empty.`);
|
|
3315
|
+
}
|
|
3316
|
+
await mkdir(dest, { recursive: true });
|
|
3317
|
+
const template = args.template ?? DEFAULT_TEMPLATE;
|
|
3318
|
+
if (await isDirectory(resolve2(template))) {
|
|
3319
|
+
await cp(resolve2(template), dest, {
|
|
3320
|
+
recursive: true,
|
|
3321
|
+
filter: (src) => !SKIP_LOCAL.has(basename(src))
|
|
3322
|
+
});
|
|
3323
|
+
} else {
|
|
3324
|
+
console.log(`Downloading ${template} ...`);
|
|
3325
|
+
await downloadGithubTemplate(template, dest);
|
|
3326
|
+
}
|
|
3327
|
+
let { url, apiKey } = args;
|
|
3328
|
+
if (process.stdin.isTTY && (!url || !apiKey)) {
|
|
3329
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3330
|
+
url ||= (await rl.question("Lunnoa URL (e.g. https://lunnoa.your-company.example): ")).trim();
|
|
3331
|
+
apiKey ||= (await rl.question("API key lna_... (server-side only; Enter to skip and sign in as a user instead): ")).trim();
|
|
3332
|
+
rl.close();
|
|
3333
|
+
}
|
|
3334
|
+
const example = await readFile(join(dest, ".env.example"), "utf8").catch(() => "");
|
|
3335
|
+
await writeFile(
|
|
3336
|
+
join(dest, ".env"),
|
|
3337
|
+
setEnvValues(dropRetiredProjectEnv(example), {
|
|
3338
|
+
LUNNOA_URL: url,
|
|
3339
|
+
LUNNOA_API_KEY: apiKey
|
|
3340
|
+
}),
|
|
3341
|
+
"utf8"
|
|
3342
|
+
);
|
|
3343
|
+
console.log(`
|
|
3344
|
+
Created ${dest}
|
|
3345
|
+
|
|
3346
|
+
Next steps:`);
|
|
3347
|
+
console.log(` cd ${args.path}`);
|
|
3348
|
+
console.log(" npm install");
|
|
3349
|
+
if (!apiKey) {
|
|
3350
|
+
console.log(" Add LUNNOA_API_KEY to .env, or set LUNNOA_AUTH_MODE=user to sign in with your Lunnoa account");
|
|
3351
|
+
}
|
|
3352
|
+
console.log(" npm run dev");
|
|
3353
|
+
console.log(" Open the folder in Claude Code / Cursor / Codex and describe your idea.");
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3240
3356
|
// src/cli/skills.ts
|
|
3241
|
-
import { cp } from "fs/promises";
|
|
3242
|
-
import { dirname, resolve as
|
|
3357
|
+
import { cp as cp2 } from "fs/promises";
|
|
3358
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
3243
3359
|
import { fileURLToPath } from "url";
|
|
3244
3360
|
var SKILL_TARGETS = [".claude/skills", ".cursor/skills"];
|
|
3245
|
-
var BUNDLED_SKILLS_DIR =
|
|
3361
|
+
var BUNDLED_SKILLS_DIR = resolve3(
|
|
3246
3362
|
dirname(fileURLToPath(import.meta.url)),
|
|
3247
3363
|
"..",
|
|
3248
3364
|
"skills"
|
|
@@ -3250,8 +3366,8 @@ var BUNDLED_SKILLS_DIR = resolve2(
|
|
|
3250
3366
|
async function installSkills(projectDir, sourceDir = BUNDLED_SKILLS_DIR) {
|
|
3251
3367
|
const written = [];
|
|
3252
3368
|
for (const target of SKILL_TARGETS) {
|
|
3253
|
-
const dest =
|
|
3254
|
-
await
|
|
3369
|
+
const dest = resolve3(projectDir, target);
|
|
3370
|
+
await cp2(sourceDir, dest, { recursive: true, force: true });
|
|
3255
3371
|
written.push(dest);
|
|
3256
3372
|
}
|
|
3257
3373
|
return written;
|
|
@@ -3263,7 +3379,6 @@ async function bindClient(args) {
|
|
|
3263
3379
|
const url = args.url ?? process.env.LUNNOA_URL ?? fileConfig.url;
|
|
3264
3380
|
const apiKey = args.apiKey ?? process.env.LUNNOA_API_KEY ?? fileConfig.apiKey;
|
|
3265
3381
|
const accessToken = args.accessToken;
|
|
3266
|
-
const projectId = args.projectId ?? process.env.LUNNOA_PROJECT_ID ?? fileConfig.projectId;
|
|
3267
3382
|
if (!url) {
|
|
3268
3383
|
throw new Error("Missing --url (or LUNNOA_URL / lunnoa.config).");
|
|
3269
3384
|
}
|
|
@@ -3274,7 +3389,6 @@ async function bindClient(args) {
|
|
|
3274
3389
|
}
|
|
3275
3390
|
return {
|
|
3276
3391
|
client: new LunnoaClient({ baseUrl: url, apiKey, accessToken }),
|
|
3277
|
-
projectId,
|
|
3278
3392
|
url
|
|
3279
3393
|
};
|
|
3280
3394
|
}
|
|
@@ -3312,39 +3426,29 @@ async function runCodegen(args) {
|
|
|
3312
3426
|
`Found ${definitions.entityTypes.length} entity type(s), ${definitions.workflows.length} workflow(s), ${definitions.agents.length} agent(s).`
|
|
3313
3427
|
);
|
|
3314
3428
|
const moduleSource = generateDeploymentModule(definitions);
|
|
3315
|
-
const outDir =
|
|
3316
|
-
await
|
|
3317
|
-
const outFile =
|
|
3318
|
-
await
|
|
3429
|
+
const outDir = resolve4(process.cwd(), args.out);
|
|
3430
|
+
await mkdir2(outDir, { recursive: true });
|
|
3431
|
+
const outFile = resolve4(outDir, "lunnoa.generated.ts");
|
|
3432
|
+
await writeFile2(outFile, moduleSource, "utf8");
|
|
3319
3433
|
console.log(`Wrote ${outFile}`);
|
|
3320
3434
|
}
|
|
3321
3435
|
async function deployWorkflow(args) {
|
|
3322
3436
|
if (!args.path) {
|
|
3323
3437
|
throw new Error("Missing definition path. Example: workflows deploy ./workflow.ts");
|
|
3324
3438
|
}
|
|
3325
|
-
const { client
|
|
3326
|
-
if (!projectId) {
|
|
3327
|
-
throw new Error(
|
|
3328
|
-
"Missing --project-id (or LUNNOA_PROJECT_ID / lunnoa.config)."
|
|
3329
|
-
);
|
|
3330
|
-
}
|
|
3439
|
+
const { client } = await bindClient(args);
|
|
3331
3440
|
const mod = await loadDefinitionModule(args.path);
|
|
3332
3441
|
const def = pickWorkflowDefinition(mod);
|
|
3333
3442
|
const payload = workflowDefinitionToUpsert(def);
|
|
3334
|
-
console.log(`Deploying workflow slug="${payload.slug}"
|
|
3335
|
-
const result = await client.workflows.upsertBySlug(
|
|
3443
|
+
console.log(`Deploying workflow slug="${payload.slug}" ...`);
|
|
3444
|
+
const result = await client.workflows.upsertBySlug(payload);
|
|
3336
3445
|
console.log(`Upserted workflow ${result.id} (${payload.slug})`);
|
|
3337
3446
|
}
|
|
3338
3447
|
async function deployAgent(args) {
|
|
3339
3448
|
if (!args.path) {
|
|
3340
3449
|
throw new Error("Missing definition path. Example: agents deploy ./agent.ts");
|
|
3341
3450
|
}
|
|
3342
|
-
const { client
|
|
3343
|
-
if (!projectId) {
|
|
3344
|
-
throw new Error(
|
|
3345
|
-
"Missing --project-id (or LUNNOA_PROJECT_ID / lunnoa.config)."
|
|
3346
|
-
);
|
|
3347
|
-
}
|
|
3451
|
+
const { client } = await bindClient(args);
|
|
3348
3452
|
const mod = await loadDefinitionModule(args.path);
|
|
3349
3453
|
const def = pickAgentDefinition(mod);
|
|
3350
3454
|
const workflowIds = [];
|
|
@@ -3353,7 +3457,6 @@ async function deployAgent(args) {
|
|
|
3353
3457
|
if (unwrapped && typeof unwrapped === "object" && unwrapped.kind === DEFINE_KINDS.workflow) {
|
|
3354
3458
|
const wf = unwrapped;
|
|
3355
3459
|
const upserted = await client.workflows.upsertBySlug(
|
|
3356
|
-
projectId,
|
|
3357
3460
|
workflowDefinitionToUpsert(wf)
|
|
3358
3461
|
);
|
|
3359
3462
|
if (upserted.id) {
|
|
@@ -3363,8 +3466,8 @@ async function deployAgent(args) {
|
|
|
3363
3466
|
}
|
|
3364
3467
|
}
|
|
3365
3468
|
const payload = agentDefinitionToUpsert(def, { workflowIds });
|
|
3366
|
-
console.log(`Deploying agent slug="${payload.slug}"
|
|
3367
|
-
const result = await client.agents.upsertBySlug(
|
|
3469
|
+
console.log(`Deploying agent slug="${payload.slug}" ...`);
|
|
3470
|
+
const result = await client.agents.upsertBySlug(payload);
|
|
3368
3471
|
console.log(`Upserted agent ${result.id} (${payload.slug})`);
|
|
3369
3472
|
}
|
|
3370
3473
|
async function deployEntityType(args) {
|
|
@@ -3387,8 +3490,8 @@ var DISCOVERY_SUBCOMMANDS = {
|
|
|
3387
3490
|
tools: ["list", "get", "search"],
|
|
3388
3491
|
apps: ["list", "get"],
|
|
3389
3492
|
connections: ["list", "get"],
|
|
3390
|
-
|
|
3391
|
-
|
|
3493
|
+
tables: ["list", "get"],
|
|
3494
|
+
features: void 0
|
|
3392
3495
|
};
|
|
3393
3496
|
function isDiscoveryCommand(args) {
|
|
3394
3497
|
const { command, subcommand } = args;
|
|
@@ -3398,9 +3501,6 @@ function isDiscoveryCommand(args) {
|
|
|
3398
3501
|
if (command === "features") {
|
|
3399
3502
|
return true;
|
|
3400
3503
|
}
|
|
3401
|
-
if (command === "projects" && (subcommand === "list" || !subcommand)) {
|
|
3402
|
-
return true;
|
|
3403
|
-
}
|
|
3404
3504
|
if ((command === "agents" || command === "workflows" || command === "entity-types") && subcommand === "list") {
|
|
3405
3505
|
return true;
|
|
3406
3506
|
}
|
|
@@ -3423,7 +3523,7 @@ ${USAGE}`
|
|
|
3423
3523
|
}
|
|
3424
3524
|
async function runDiscovery(args) {
|
|
3425
3525
|
const { command, subcommand } = args;
|
|
3426
|
-
if (command === "tools" || command === "apps" || command === "connections") {
|
|
3526
|
+
if (command === "tools" || command === "apps" || command === "connections" || command === "tables") {
|
|
3427
3527
|
assertKnownDiscoveryShape(args);
|
|
3428
3528
|
}
|
|
3429
3529
|
if (!isDiscoveryCommand(args)) {
|
|
@@ -3470,8 +3570,12 @@ async function runDiscovery(args) {
|
|
|
3470
3570
|
await runEntityTypesList(client, args);
|
|
3471
3571
|
return true;
|
|
3472
3572
|
}
|
|
3473
|
-
if (command === "
|
|
3474
|
-
await
|
|
3573
|
+
if (command === "tables" && subcommand === "list") {
|
|
3574
|
+
await runTablesList(client, args);
|
|
3575
|
+
return true;
|
|
3576
|
+
}
|
|
3577
|
+
if (command === "tables" && subcommand === "get") {
|
|
3578
|
+
await runTablesGet(client, args);
|
|
3475
3579
|
return true;
|
|
3476
3580
|
}
|
|
3477
3581
|
if (command === "features") {
|
|
@@ -3482,12 +3586,20 @@ async function runDiscovery(args) {
|
|
|
3482
3586
|
}
|
|
3483
3587
|
async function main() {
|
|
3484
3588
|
const args = parseArgs(process.argv.slice(2));
|
|
3589
|
+
try {
|
|
3590
|
+
process.loadEnvFile();
|
|
3591
|
+
} catch {
|
|
3592
|
+
}
|
|
3485
3593
|
if (!args.command || args.command === "--help" || args.command === "help" || args.help) {
|
|
3486
3594
|
console.log(USAGE);
|
|
3487
3595
|
return;
|
|
3488
3596
|
}
|
|
3597
|
+
if (args.command === "init") {
|
|
3598
|
+
await runInit(args);
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3489
3601
|
if (args.command === "skills" && args.subcommand === "install") {
|
|
3490
|
-
const written = await installSkills(
|
|
3602
|
+
const written = await installSkills(resolve4(process.cwd(), args.path ?? "."));
|
|
3491
3603
|
for (const dest of written) {
|
|
3492
3604
|
console.log(`Installed Lunnoa skills into ${dest}`);
|
|
3493
3605
|
}
|