@lunnoa/client 1.3.0 → 1.5.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 +15 -5
- package/dist/cli.js +232 -17
- package/dist/index.cjs +37 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1331 -1066
- package/dist/index.d.ts +1331 -1066
- package/dist/index.js +37 -3
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +56 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +704 -613
- package/dist/react.d.ts +704 -613
- package/dist/react.js +56 -3
- package/dist/react.js.map +1 -1
- package/package.json +3 -6
- package/skills/lunnoa-client/SKILL.md +18 -6
- package/skills/lunnoa-solution-design/SKILL.md +20 -4
package/README.md
CHANGED
|
@@ -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,12 +106,12 @@ 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
|
|
|
@@ -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).
|
|
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). 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 (no `--project-id`). 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_...
|
|
@@ -303,7 +311,7 @@ npx @lunnoa/client projects list --json
|
|
|
303
311
|
npx @lunnoa/client features --json
|
|
304
312
|
```
|
|
305
313
|
|
|
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.
|
|
314
|
+
`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
315
|
|
|
308
316
|
## Regenerating the API types
|
|
309
317
|
|
|
@@ -322,7 +330,9 @@ Two Cursor / Claude Code skills ship in the published package under `skills/`:
|
|
|
322
330
|
| **lunnoa-solution-design** | [`skills/lunnoa-solution-design/SKILL.md`](./skills/lunnoa-solution-design/SKILL.md) | Interview the human, map intent to Lunnoa primitives, produce a build brief |
|
|
323
331
|
| **lunnoa-client** | [`skills/lunnoa-client/SKILL.md`](./skills/lunnoa-client/SKILL.md) | Implement with auth, discovery, codegen, `define*` / `runs` / CLI deploy, React hooks |
|
|
324
332
|
|
|
325
|
-
|
|
333
|
+
Install them into your app with `npx @lunnoa/client skills install`. It copies the skills of the installed SDK version into `.claude/skills/` and `.cursor/skills/`. Add it as a `postinstall` script and gitignore both folders so the skills stay in step with the SDK. Prefer **solution-design** first when requirements are unclear, then **lunnoa-client**.
|
|
334
|
+
|
|
335
|
+
In this repo, edit `skills/` only; `pnpm install` copies it into the local `.cursor/skills/` and `.claude/skills/`.
|
|
326
336
|
|
|
327
337
|
Partner paste block:
|
|
328
338
|
|
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_";
|
|
@@ -1042,7 +1042,7 @@ var EntityTypesResource = class {
|
|
|
1042
1042
|
}
|
|
1043
1043
|
http;
|
|
1044
1044
|
/**
|
|
1045
|
-
* Lists entity types
|
|
1045
|
+
* Lists entity types granted to the API key's workspace. Request
|
|
1046
1046
|
* `expansion: ['attributeSchema', 'stateSchema']` for the field and state
|
|
1047
1047
|
* machine definitions the codegen CLI builds types from.
|
|
1048
1048
|
*/
|
|
@@ -1067,7 +1067,9 @@ var EntityTypesResource = class {
|
|
|
1067
1067
|
});
|
|
1068
1068
|
}
|
|
1069
1069
|
/**
|
|
1070
|
-
* Creates or updates an entity type by
|
|
1070
|
+
* Creates or updates an entity type by deployment-wide slug.
|
|
1071
|
+
* A new slug is granted to the API key's workspace. A slug already shared
|
|
1072
|
+
* with another workspace is rejected unless the key belongs to a platform admin.
|
|
1071
1073
|
* Prefer the CLI (`npx @lunnoa/client entity-types deploy`) for happy-path deploy.
|
|
1072
1074
|
*/
|
|
1073
1075
|
upsertBySlug(definition) {
|
|
@@ -1081,6 +1083,35 @@ var EntityTypesResource = class {
|
|
|
1081
1083
|
}
|
|
1082
1084
|
};
|
|
1083
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(options = {}) {
|
|
1094
|
+
return this.http.get("/api/tables", {
|
|
1095
|
+
query: { projectId: options.projectId }
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
/** Returns a single table by ID, including columns and rows. */
|
|
1099
|
+
get(id) {
|
|
1100
|
+
return this.http.get(`/api/tables/${id}`);
|
|
1101
|
+
}
|
|
1102
|
+
/** Returns a single table by its workspace slug, including columns and rows. */
|
|
1103
|
+
getBySlug(slug) {
|
|
1104
|
+
return this.http.get(`/api/tables/slug/${slug}`);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Creates or updates a table by workspace slug. Omitted columns or rows
|
|
1108
|
+
* keep their current value. Marks `managedByCode` by default.
|
|
1109
|
+
*/
|
|
1110
|
+
upsertBySlug(input) {
|
|
1111
|
+
return this.http.post("/api/tables/upsert", { body: input });
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1084
1115
|
// src/resources/approvals.ts
|
|
1085
1116
|
var ApprovalsResource = class {
|
|
1086
1117
|
constructor(http) {
|
|
@@ -1420,7 +1451,7 @@ function computeDurationMs(start, end) {
|
|
|
1420
1451
|
return b - a;
|
|
1421
1452
|
}
|
|
1422
1453
|
function defaultSleep(ms) {
|
|
1423
|
-
return new Promise((
|
|
1454
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1424
1455
|
}
|
|
1425
1456
|
|
|
1426
1457
|
// src/resources/knowledge.ts
|
|
@@ -2065,6 +2096,8 @@ var LunnoaClient = class {
|
|
|
2065
2096
|
approvals;
|
|
2066
2097
|
entities;
|
|
2067
2098
|
entityTypes;
|
|
2099
|
+
/** Small reference / lookup tables (rows stored inline; use entities for transactional data). */
|
|
2100
|
+
tables;
|
|
2068
2101
|
knowledge;
|
|
2069
2102
|
queues;
|
|
2070
2103
|
queueItems;
|
|
@@ -2088,6 +2121,7 @@ var LunnoaClient = class {
|
|
|
2088
2121
|
this.workflows = new WorkflowsResource(this.http, this.executions);
|
|
2089
2122
|
this.entities = new EntitiesResource(this.http);
|
|
2090
2123
|
this.entityTypes = new EntityTypesResource(this.http);
|
|
2124
|
+
this.tables = new TablesResource(this.http);
|
|
2091
2125
|
this.knowledge = new KnowledgeResource(this.http);
|
|
2092
2126
|
this.queues = new QueuesResource(this.http);
|
|
2093
2127
|
this.queueItems = new QueueItemsResource(this.http);
|
|
@@ -2104,10 +2138,12 @@ var GROUPS_WITH_SUBCOMMAND = /* @__PURE__ */ new Set([
|
|
|
2104
2138
|
"workflows",
|
|
2105
2139
|
"agents",
|
|
2106
2140
|
"entity-types",
|
|
2141
|
+
"tables",
|
|
2107
2142
|
"tools",
|
|
2108
2143
|
"apps",
|
|
2109
2144
|
"connections",
|
|
2110
|
-
"projects"
|
|
2145
|
+
"projects",
|
|
2146
|
+
"skills"
|
|
2111
2147
|
]);
|
|
2112
2148
|
var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
2113
2149
|
"--url",
|
|
@@ -2115,7 +2151,8 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
2115
2151
|
"--access-token",
|
|
2116
2152
|
"--project-id",
|
|
2117
2153
|
"--out",
|
|
2118
|
-
"--app"
|
|
2154
|
+
"--app",
|
|
2155
|
+
"--template"
|
|
2119
2156
|
]);
|
|
2120
2157
|
function requireFlagValue(flag, value) {
|
|
2121
2158
|
if (!value || value.startsWith("--")) {
|
|
@@ -2178,6 +2215,9 @@ function parseArgs(argv) {
|
|
|
2178
2215
|
case "--app":
|
|
2179
2216
|
args.app = value;
|
|
2180
2217
|
break;
|
|
2218
|
+
case "--template":
|
|
2219
|
+
args.template = value;
|
|
2220
|
+
break;
|
|
2181
2221
|
default:
|
|
2182
2222
|
break;
|
|
2183
2223
|
}
|
|
@@ -2193,6 +2233,7 @@ function parseArgs(argv) {
|
|
|
2193
2233
|
return args;
|
|
2194
2234
|
}
|
|
2195
2235
|
var USAGE = `Usage:
|
|
2236
|
+
npx @lunnoa/client init <dir> [--url <url>] [--api-key <lna_key>] [--template <owner/repo|path>]
|
|
2196
2237
|
npx @lunnoa/client codegen --url <url> --api-key <lna_key> [--out <dir>]
|
|
2197
2238
|
|
|
2198
2239
|
Discover (do not invent catalogue action ids):
|
|
@@ -2206,6 +2247,8 @@ var USAGE = `Usage:
|
|
|
2206
2247
|
npx @lunnoa/client agents list [--json]
|
|
2207
2248
|
npx @lunnoa/client workflows list [--json]
|
|
2208
2249
|
npx @lunnoa/client entity-types list [--json]
|
|
2250
|
+
npx @lunnoa/client tables list [--json]
|
|
2251
|
+
npx @lunnoa/client tables get <slugOrId> [--json]
|
|
2209
2252
|
npx @lunnoa/client projects list [--json]
|
|
2210
2253
|
npx @lunnoa/client features [--json]
|
|
2211
2254
|
|
|
@@ -2214,6 +2257,9 @@ var USAGE = `Usage:
|
|
|
2214
2257
|
npx @lunnoa/client agents deploy <path> --url <url> --api-key <key> --project-id <uuid>
|
|
2215
2258
|
npx @lunnoa/client entity-types deploy <path> --url <url> --api-key <key>
|
|
2216
2259
|
|
|
2260
|
+
Agent skills (copies this version's skills into .claude/skills and .cursor/skills):
|
|
2261
|
+
npx @lunnoa/client skills install [projectDir]
|
|
2262
|
+
|
|
2217
2263
|
Options:
|
|
2218
2264
|
--url Base URL of the Lunnoa Automate deployment
|
|
2219
2265
|
--api-key Machine API key (lna_...)
|
|
@@ -2222,15 +2268,17 @@ Options:
|
|
|
2222
2268
|
--out Output directory for lunnoa.generated.ts (codegen; default: ./src/lunnoa)
|
|
2223
2269
|
--app Restrict tools/connections to one workflow app id
|
|
2224
2270
|
--for-agent Only catalogue entries with availableForAgent
|
|
2271
|
+
--template init starter: GitHub owner/repo[#ref] or local folder (default: lunnoa-automate/lunnoa-starter)
|
|
2225
2272
|
--json Machine-readable JSON (preferred for coding agents)
|
|
2226
2273
|
--help Show this help
|
|
2227
2274
|
|
|
2228
|
-
Environment: LUNNOA_URL, LUNNOA_API_KEY, LUNNOA_PROJECT_ID
|
|
2275
|
+
Environment: LUNNOA_URL, LUNNOA_API_KEY, LUNNOA_PROJECT_ID (also read from ./.env)
|
|
2229
2276
|
Optional config file: lunnoa.config.ts | .mjs | .js | .json
|
|
2230
2277
|
|
|
2231
2278
|
tools get prints inputConfig and a defineAction() sketch. tools list is compact;
|
|
2232
|
-
run tools get for the schema. Entity-type deploy
|
|
2233
|
-
--project-id).
|
|
2279
|
+
run tools get for the schema. Entity-type deploy uses a deployment-wide slug
|
|
2280
|
+
and grants the API key's workspace when the slug is new (no --project-id).
|
|
2281
|
+
A slug already granted to other workspaces is not rewritten. Unknown instance attributes are rejected unless
|
|
2234
2282
|
allowUnknownAttributes is true; use extensions.
|
|
2235
2283
|
`;
|
|
2236
2284
|
|
|
@@ -2701,6 +2749,39 @@ async function runEntityTypesList(client, args) {
|
|
|
2701
2749
|
{ key: "managedByCode", header: "CODE" }
|
|
2702
2750
|
]);
|
|
2703
2751
|
}
|
|
2752
|
+
async function runTablesList(client, args) {
|
|
2753
|
+
const tables = await client.tables.list({ projectId: args.projectId });
|
|
2754
|
+
const rows = tables.map((table) => ({
|
|
2755
|
+
id: table.id,
|
|
2756
|
+
slug: table.slug,
|
|
2757
|
+
name: table.name,
|
|
2758
|
+
description: table.description ?? "",
|
|
2759
|
+
managedByCode: Boolean(table.managedByCode)
|
|
2760
|
+
}));
|
|
2761
|
+
if (args.json) {
|
|
2762
|
+
printJson(rows);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
printTable(rows, [
|
|
2766
|
+
{ key: "id", header: "ID" },
|
|
2767
|
+
{ key: "slug", header: "SLUG" },
|
|
2768
|
+
{ key: "name", header: "NAME" },
|
|
2769
|
+
{ key: "managedByCode", header: "CODE" }
|
|
2770
|
+
]);
|
|
2771
|
+
}
|
|
2772
|
+
async function runTablesGet(client, args) {
|
|
2773
|
+
const ref = requireTarget(args, "table slug or id (example: tables get cost_centres)");
|
|
2774
|
+
const table = /^[0-9a-f]{8}-[0-9a-f]{4}-/i.test(ref) ? await client.tables.get(ref) : await client.tables.getBySlug(ref);
|
|
2775
|
+
if (args.json) {
|
|
2776
|
+
printJson(table);
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
console.log(`id: ${table.id}`);
|
|
2780
|
+
console.log(`slug: ${table.slug}`);
|
|
2781
|
+
console.log(`name: ${table.name}`);
|
|
2782
|
+
console.log(`columns: ${(table.columns ?? []).map((c) => c.key).join(", ")}`);
|
|
2783
|
+
console.log(`rows: ${table.rows?.length ?? 0}`);
|
|
2784
|
+
}
|
|
2704
2785
|
async function runProjectsList(client, args) {
|
|
2705
2786
|
const projects = await client.projects.list({
|
|
2706
2787
|
expansion: ["description", "countAgents", "countWorkflows"]
|
|
@@ -3215,8 +3296,8 @@ async function loadOptionalConfig() {
|
|
|
3215
3296
|
continue;
|
|
3216
3297
|
}
|
|
3217
3298
|
if (name.endsWith(".json")) {
|
|
3218
|
-
const { readFile } = await import("fs/promises");
|
|
3219
|
-
const raw = await
|
|
3299
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
3300
|
+
const raw = await readFile2(absolute, "utf8");
|
|
3220
3301
|
return JSON.parse(raw);
|
|
3221
3302
|
}
|
|
3222
3303
|
const mod = await import(pathToFileURL(absolute).href);
|
|
@@ -3230,6 +3311,116 @@ async function loadOptionalConfig() {
|
|
|
3230
3311
|
return {};
|
|
3231
3312
|
}
|
|
3232
3313
|
|
|
3314
|
+
// src/cli/init.ts
|
|
3315
|
+
import { spawn } from "child_process";
|
|
3316
|
+
import { cp, mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
3317
|
+
import { basename, join, resolve as resolve2 } from "path";
|
|
3318
|
+
import { createInterface } from "readline/promises";
|
|
3319
|
+
var DEFAULT_TEMPLATE = "lunnoa-automate/lunnoa-starter";
|
|
3320
|
+
var SKIP_LOCAL = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", ".env", ".claude", ".cursor"]);
|
|
3321
|
+
function setEnvValues(source, values) {
|
|
3322
|
+
let out = source;
|
|
3323
|
+
for (const [key, value] of Object.entries(values)) {
|
|
3324
|
+
if (!value) continue;
|
|
3325
|
+
const line = `${key}=${value}`;
|
|
3326
|
+
const existing = new RegExp(`^${key}=.*$`, "m");
|
|
3327
|
+
out = existing.test(out) ? out.replace(existing, () => line) : `${out}${out === "" || out.endsWith("\n") ? "" : "\n"}${line}
|
|
3328
|
+
`;
|
|
3329
|
+
}
|
|
3330
|
+
return out;
|
|
3331
|
+
}
|
|
3332
|
+
async function isDirectory(path) {
|
|
3333
|
+
return (await stat(path).catch(() => void 0))?.isDirectory() ?? false;
|
|
3334
|
+
}
|
|
3335
|
+
async function downloadGithubTemplate(template, dest) {
|
|
3336
|
+
const [repo, ref = "HEAD"] = template.split("#");
|
|
3337
|
+
const url = `https://codeload.github.com/${repo}/tar.gz/${ref}`;
|
|
3338
|
+
const response = await fetch(url);
|
|
3339
|
+
if (!response.ok) {
|
|
3340
|
+
throw new Error(`Could not download template ${template} (${response.status} from ${url}).`);
|
|
3341
|
+
}
|
|
3342
|
+
const tarball = Buffer.from(await response.arrayBuffer());
|
|
3343
|
+
await new Promise((done, fail) => {
|
|
3344
|
+
const child = spawn("tar", ["-xzf", "-", "--strip-components=1"], {
|
|
3345
|
+
cwd: dest,
|
|
3346
|
+
stdio: ["pipe", "ignore", "inherit"]
|
|
3347
|
+
});
|
|
3348
|
+
child.on(
|
|
3349
|
+
"error",
|
|
3350
|
+
(error) => fail(new Error(`Could not run tar (${error.message}). Download ${url} manually.`))
|
|
3351
|
+
);
|
|
3352
|
+
child.on(
|
|
3353
|
+
"close",
|
|
3354
|
+
(code) => code === 0 ? done() : fail(new Error(`tar exited with code ${code}`))
|
|
3355
|
+
);
|
|
3356
|
+
child.stdin.end(tarball);
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
async function runInit(args) {
|
|
3360
|
+
if (!args.path) {
|
|
3361
|
+
throw new Error("Missing project directory. Example: init my-app --url https://lunnoa.example");
|
|
3362
|
+
}
|
|
3363
|
+
const dest = resolve2(process.cwd(), args.path);
|
|
3364
|
+
if ((await readdir(dest).catch(() => [])).length > 0) {
|
|
3365
|
+
throw new Error(`${dest} already exists and is not empty.`);
|
|
3366
|
+
}
|
|
3367
|
+
await mkdir(dest, { recursive: true });
|
|
3368
|
+
const template = args.template ?? DEFAULT_TEMPLATE;
|
|
3369
|
+
if (await isDirectory(resolve2(template))) {
|
|
3370
|
+
await cp(resolve2(template), dest, {
|
|
3371
|
+
recursive: true,
|
|
3372
|
+
filter: (src) => !SKIP_LOCAL.has(basename(src))
|
|
3373
|
+
});
|
|
3374
|
+
} else {
|
|
3375
|
+
console.log(`Downloading ${template} ...`);
|
|
3376
|
+
await downloadGithubTemplate(template, dest);
|
|
3377
|
+
}
|
|
3378
|
+
let { url, apiKey } = args;
|
|
3379
|
+
if (process.stdin.isTTY && (!url || !apiKey)) {
|
|
3380
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3381
|
+
url ||= (await rl.question("Lunnoa URL (e.g. https://lunnoa.your-company.example): ")).trim();
|
|
3382
|
+
apiKey ||= (await rl.question("API key lna_... (server-side only; Enter to skip and sign in as a user instead): ")).trim();
|
|
3383
|
+
rl.close();
|
|
3384
|
+
}
|
|
3385
|
+
const example = await readFile(join(dest, ".env.example"), "utf8").catch(() => "");
|
|
3386
|
+
await writeFile(
|
|
3387
|
+
join(dest, ".env"),
|
|
3388
|
+
setEnvValues(example, { LUNNOA_URL: url, LUNNOA_API_KEY: apiKey }),
|
|
3389
|
+
"utf8"
|
|
3390
|
+
);
|
|
3391
|
+
console.log(`
|
|
3392
|
+
Created ${dest}
|
|
3393
|
+
|
|
3394
|
+
Next steps:`);
|
|
3395
|
+
console.log(` cd ${args.path}`);
|
|
3396
|
+
console.log(" npm install");
|
|
3397
|
+
if (!apiKey) {
|
|
3398
|
+
console.log(" Add LUNNOA_API_KEY to .env, or set LUNNOA_AUTH_MODE=user to sign in with your Lunnoa account");
|
|
3399
|
+
}
|
|
3400
|
+
console.log(" npm run dev");
|
|
3401
|
+
console.log(" Open the folder in Claude Code / Cursor / Codex and describe your idea.");
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
// src/cli/skills.ts
|
|
3405
|
+
import { cp as cp2 } from "fs/promises";
|
|
3406
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
3407
|
+
import { fileURLToPath } from "url";
|
|
3408
|
+
var SKILL_TARGETS = [".claude/skills", ".cursor/skills"];
|
|
3409
|
+
var BUNDLED_SKILLS_DIR = resolve3(
|
|
3410
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
3411
|
+
"..",
|
|
3412
|
+
"skills"
|
|
3413
|
+
);
|
|
3414
|
+
async function installSkills(projectDir, sourceDir = BUNDLED_SKILLS_DIR) {
|
|
3415
|
+
const written = [];
|
|
3416
|
+
for (const target of SKILL_TARGETS) {
|
|
3417
|
+
const dest = resolve3(projectDir, target);
|
|
3418
|
+
await cp2(sourceDir, dest, { recursive: true, force: true });
|
|
3419
|
+
written.push(dest);
|
|
3420
|
+
}
|
|
3421
|
+
return written;
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3233
3424
|
// src/cli/index.ts
|
|
3234
3425
|
async function bindClient(args) {
|
|
3235
3426
|
const fileConfig = await loadOptionalConfig();
|
|
@@ -3285,10 +3476,10 @@ async function runCodegen(args) {
|
|
|
3285
3476
|
`Found ${definitions.entityTypes.length} entity type(s), ${definitions.workflows.length} workflow(s), ${definitions.agents.length} agent(s).`
|
|
3286
3477
|
);
|
|
3287
3478
|
const moduleSource = generateDeploymentModule(definitions);
|
|
3288
|
-
const outDir =
|
|
3289
|
-
await
|
|
3290
|
-
const outFile =
|
|
3291
|
-
await
|
|
3479
|
+
const outDir = resolve4(process.cwd(), args.out);
|
|
3480
|
+
await mkdir2(outDir, { recursive: true });
|
|
3481
|
+
const outFile = resolve4(outDir, "lunnoa.generated.ts");
|
|
3482
|
+
await writeFile2(outFile, moduleSource, "utf8");
|
|
3292
3483
|
console.log(`Wrote ${outFile}`);
|
|
3293
3484
|
}
|
|
3294
3485
|
async function deployWorkflow(args) {
|
|
@@ -3360,6 +3551,7 @@ var DISCOVERY_SUBCOMMANDS = {
|
|
|
3360
3551
|
tools: ["list", "get", "search"],
|
|
3361
3552
|
apps: ["list", "get"],
|
|
3362
3553
|
connections: ["list", "get"],
|
|
3554
|
+
tables: ["list", "get"],
|
|
3363
3555
|
features: void 0,
|
|
3364
3556
|
projects: ["list"]
|
|
3365
3557
|
};
|
|
@@ -3396,7 +3588,7 @@ ${USAGE}`
|
|
|
3396
3588
|
}
|
|
3397
3589
|
async function runDiscovery(args) {
|
|
3398
3590
|
const { command, subcommand } = args;
|
|
3399
|
-
if (command === "tools" || command === "apps" || command === "connections") {
|
|
3591
|
+
if (command === "tools" || command === "apps" || command === "connections" || command === "tables") {
|
|
3400
3592
|
assertKnownDiscoveryShape(args);
|
|
3401
3593
|
}
|
|
3402
3594
|
if (!isDiscoveryCommand(args)) {
|
|
@@ -3443,6 +3635,14 @@ async function runDiscovery(args) {
|
|
|
3443
3635
|
await runEntityTypesList(client, args);
|
|
3444
3636
|
return true;
|
|
3445
3637
|
}
|
|
3638
|
+
if (command === "tables" && subcommand === "list") {
|
|
3639
|
+
await runTablesList(client, args);
|
|
3640
|
+
return true;
|
|
3641
|
+
}
|
|
3642
|
+
if (command === "tables" && subcommand === "get") {
|
|
3643
|
+
await runTablesGet(client, args);
|
|
3644
|
+
return true;
|
|
3645
|
+
}
|
|
3446
3646
|
if (command === "projects" && (subcommand === "list" || !subcommand)) {
|
|
3447
3647
|
await runProjectsList(client, args);
|
|
3448
3648
|
return true;
|
|
@@ -3455,10 +3655,25 @@ async function runDiscovery(args) {
|
|
|
3455
3655
|
}
|
|
3456
3656
|
async function main() {
|
|
3457
3657
|
const args = parseArgs(process.argv.slice(2));
|
|
3658
|
+
try {
|
|
3659
|
+
process.loadEnvFile();
|
|
3660
|
+
} catch {
|
|
3661
|
+
}
|
|
3458
3662
|
if (!args.command || args.command === "--help" || args.command === "help" || args.help) {
|
|
3459
3663
|
console.log(USAGE);
|
|
3460
3664
|
return;
|
|
3461
3665
|
}
|
|
3666
|
+
if (args.command === "init") {
|
|
3667
|
+
await runInit(args);
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
if (args.command === "skills" && args.subcommand === "install") {
|
|
3671
|
+
const written = await installSkills(resolve4(process.cwd(), args.path ?? "."));
|
|
3672
|
+
for (const dest of written) {
|
|
3673
|
+
console.log(`Installed Lunnoa skills into ${dest}`);
|
|
3674
|
+
}
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3462
3677
|
if (args.command === "codegen") {
|
|
3463
3678
|
await runCodegen(args);
|
|
3464
3679
|
return;
|
package/dist/index.cjs
CHANGED
|
@@ -1084,7 +1084,7 @@ var EntityTypesResource = class {
|
|
|
1084
1084
|
}
|
|
1085
1085
|
http;
|
|
1086
1086
|
/**
|
|
1087
|
-
* Lists entity types
|
|
1087
|
+
* Lists entity types granted to the API key's workspace. Request
|
|
1088
1088
|
* `expansion: ['attributeSchema', 'stateSchema']` for the field and state
|
|
1089
1089
|
* machine definitions the codegen CLI builds types from.
|
|
1090
1090
|
*/
|
|
@@ -1109,7 +1109,9 @@ var EntityTypesResource = class {
|
|
|
1109
1109
|
});
|
|
1110
1110
|
}
|
|
1111
1111
|
/**
|
|
1112
|
-
* Creates or updates an entity type by
|
|
1112
|
+
* Creates or updates an entity type by deployment-wide slug.
|
|
1113
|
+
* A new slug is granted to the API key's workspace. A slug already shared
|
|
1114
|
+
* with another workspace is rejected unless the key belongs to a platform admin.
|
|
1113
1115
|
* Prefer the CLI (`npx @lunnoa/client entity-types deploy`) for happy-path deploy.
|
|
1114
1116
|
*/
|
|
1115
1117
|
upsertBySlug(definition) {
|
|
@@ -1123,6 +1125,35 @@ var EntityTypesResource = class {
|
|
|
1123
1125
|
}
|
|
1124
1126
|
};
|
|
1125
1127
|
|
|
1128
|
+
// src/resources/tables.ts
|
|
1129
|
+
var TablesResource = class {
|
|
1130
|
+
constructor(http) {
|
|
1131
|
+
this.http = http;
|
|
1132
|
+
}
|
|
1133
|
+
http;
|
|
1134
|
+
/** Lists tables in the API key's workspace (without columns and rows). */
|
|
1135
|
+
list(options = {}) {
|
|
1136
|
+
return this.http.get("/api/tables", {
|
|
1137
|
+
query: { projectId: options.projectId }
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
/** Returns a single table by ID, including columns and rows. */
|
|
1141
|
+
get(id) {
|
|
1142
|
+
return this.http.get(`/api/tables/${id}`);
|
|
1143
|
+
}
|
|
1144
|
+
/** Returns a single table by its workspace slug, including columns and rows. */
|
|
1145
|
+
getBySlug(slug) {
|
|
1146
|
+
return this.http.get(`/api/tables/slug/${slug}`);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Creates or updates a table by workspace slug. Omitted columns or rows
|
|
1150
|
+
* keep their current value. Marks `managedByCode` by default.
|
|
1151
|
+
*/
|
|
1152
|
+
upsertBySlug(input) {
|
|
1153
|
+
return this.http.post("/api/tables/upsert", { body: input });
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1126
1157
|
// src/resources/approvals.ts
|
|
1127
1158
|
var ApprovalsResource = class {
|
|
1128
1159
|
constructor(http) {
|
|
@@ -2107,6 +2138,8 @@ var LunnoaClient = class {
|
|
|
2107
2138
|
approvals;
|
|
2108
2139
|
entities;
|
|
2109
2140
|
entityTypes;
|
|
2141
|
+
/** Small reference / lookup tables (rows stored inline; use entities for transactional data). */
|
|
2142
|
+
tables;
|
|
2110
2143
|
knowledge;
|
|
2111
2144
|
queues;
|
|
2112
2145
|
queueItems;
|
|
@@ -2130,6 +2163,7 @@ var LunnoaClient = class {
|
|
|
2130
2163
|
this.workflows = new WorkflowsResource(this.http, this.executions);
|
|
2131
2164
|
this.entities = new EntitiesResource(this.http);
|
|
2132
2165
|
this.entityTypes = new EntityTypesResource(this.http);
|
|
2166
|
+
this.tables = new TablesResource(this.http);
|
|
2133
2167
|
this.knowledge = new KnowledgeResource(this.http);
|
|
2134
2168
|
this.queues = new QueuesResource(this.http);
|
|
2135
2169
|
this.queueItems = new QueueItemsResource(this.http);
|
|
@@ -2238,6 +2272,7 @@ exports.QueuesResource = QueuesResource;
|
|
|
2238
2272
|
exports.RunsResource = RunsResource;
|
|
2239
2273
|
exports.ScorecardsResource = ScorecardsResource;
|
|
2240
2274
|
exports.SsoAuthResource = SsoAuthResource;
|
|
2275
|
+
exports.TablesResource = TablesResource;
|
|
2241
2276
|
exports.TasksResource = TasksResource;
|
|
2242
2277
|
exports.VariablesResource = VariablesResource;
|
|
2243
2278
|
exports.WorkflowAppsResource = WorkflowAppsResource;
|