@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 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); // needs @ai-sdk/react
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
- Peers: `react` ≥ 18 (required for `/react`); `@ai-sdk/react` only for `useAgentChat`. Core `@lunnoa/client` stays headless.
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). Entity types are workspace-scoped (no `--project-id`); instance writes reject unknown attribute keys unless `allowUnknownAttributes` is true — use the reserved `extensions` object for flexible bags:
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
- After install, copy or enable them from `node_modules/@lunnoa/client/skills/` into your app’s `.cursor/skills/` (or Claude Code skills directory). Prefer **solution-design** first when requirements are unclear, then **lunnoa-client**.
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 resolve2 } from "path";
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 in the workspace. Request
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 workspace-scoped slug.
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((resolve3) => setTimeout(resolve3, ms));
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 is workspace-scoped (no
2233
- --project-id). Unknown instance attributes are rejected unless
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 readFile(absolute, "utf8");
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 = resolve2(process.cwd(), args.out);
3289
- await mkdir(outDir, { recursive: true });
3290
- const outFile = resolve2(outDir, "lunnoa.generated.ts");
3291
- await writeFile(outFile, moduleSource, "utf8");
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 in the workspace. Request
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 workspace-scoped slug.
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;