@lunnoa/client 1.5.0 → 2.1.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
@@ -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, connections, and projects — plus a codegen command that types the SDK against **your** deployment's entity types, workflows, and agents.
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
 
@@ -27,49 +27,17 @@ npm install @lunnoa/client
27
27
  npx @lunnoa/client init my-app --url https://lunnoa.your-company.example
28
28
  ```
29
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.
30
+ Scaffolds the [lunnoa-starter](https://github.com/lunnoa-automate/lunnoa-starter) Next.js template (thin, Docker-deployable, Lunnoa user login) and writes `.env`, prompting for an API key for CLI commands 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
31
 
32
32
  ## Quickstart
33
33
 
34
34
  ```typescript
35
- import { LunnoaClient } from '@lunnoa/client';
35
+ import { createBrowserClient, isLoginRequires2FA } from '@lunnoa/client';
36
36
 
37
- const lunnoa = new LunnoaClient({
37
+ const lunnoa = createBrowserClient({
38
38
  baseUrl: 'https://lunnoa.your-company.example',
39
- apiKey: process.env.LUNNOA_API_KEY, // lna_... — server-side only
40
39
  });
41
40
 
42
- const { data: invoices } = await lunnoa.entities.list({
43
- objectTypeSlug: 'invoice',
44
- state: 'pending',
45
- expansion: ['attributes'],
46
- });
47
- ```
48
-
49
- A SuperAdmin creates API keys in **Adminspace → API keys**, choosing the workspace and an RBAC role for the key's service account. The role is the key's entire capability model — grant the minimum permission set, not Admin.
50
-
51
- ## Two auth patterns
52
-
53
- | Pattern | Credential | Where |
54
- |---|---|---|
55
- | **A. Backend holds the key** | `apiKey: 'lna_...'` via `createServerClient` | Your server, background jobs, server-rendered pages. The key acts as a workspace-scoped service account. |
56
- | **B. End users are Lunnoa users** | User JWT via `createBrowserClient` + `lunnoa.auth.*` | Browser apps. Login, 2FA, refresh, and SSO discovery are wrapped; per-user permissions apply. |
57
-
58
- ```typescript
59
- import {
60
- createBrowserClient,
61
- createServerClient,
62
- isLoginRequires2FA,
63
- } from '@lunnoa/client';
64
-
65
- // Pattern A — server-side
66
- const server = createServerClient({
67
- baseUrl,
68
- apiKey: process.env.LUNNOA_API_KEY!,
69
- });
70
-
71
- // Pattern B — browser (tokens in localStorage by default; auto-refresh on 401)
72
- const lunnoa = createBrowserClient({ baseUrl });
73
41
  const result = await lunnoa.auth.login({ email, password });
74
42
  if (isLoginRequires2FA(result)) {
75
43
  await lunnoa.auth.verify2faLogin({
@@ -77,12 +45,17 @@ if (isLoginRequires2FA(result)) {
77
45
  token: totpCode,
78
46
  });
79
47
  }
80
- const me = await lunnoa.auth.me();
48
+
49
+ const { data: invoices } = await lunnoa.entities.list({
50
+ objectTypeSlug: 'invoice',
51
+ state: 'pending',
52
+ expansion: ['attributes'],
53
+ });
81
54
  ```
82
55
 
83
- **API keys never ship to browsers.** The SDK enforces this: constructing a client with an `lna_` key while `window` exists throws immediately. Browser apps either use Pattern B or call your own backend, which holds the key (backend-for-frontend).
56
+ People using the app sign in as Lunnoa users (invite, SSO, or SCIM). Their permissions apply. The deployment must allow your origin via `CORS_ALLOWED_ORIGINS`.
84
57
 
85
- For Pattern B the deployment must allow your origin via its `CORS_ALLOWED_ORIGINS` configuration. See the Authentication guide under `guides/develop/client/auth` in Lunnoa Docs for SSO redirect constraints and token stores.
58
+ `lna_...` API keys are for the CLI (discovery, codegen, deploy), not for the app session. A SuperAdmin creates them in **Adminspace → API keys**, with a minimal RBAC role. The SDK throws if an `lna_` key is used while `window` exists. See the Authentication guide under `guides/develop/client/auth` in Lunnoa Docs for SSO redirect constraints and token stores.
86
59
 
87
60
  ### React (optional)
88
61
 
@@ -117,7 +90,7 @@ Peer: `react` ≥ 18 (required for `/react`). `@ai-sdk/react` (behind `useAgentC
117
90
 
118
91
  Most namespaces map to the Public API surface (`openapi.public.json`):
119
92
 
120
- `auth` · `agents` · `tasks` · `agentChat` (SSE streaming) · `workflows` · `executions` · `actions` · `entities` · `entityTypes` · `knowledge` · `queues` · `queueItems` · `variables` · `connections` · `projects` · `workflowApps` · `discovery`
93
+ `auth` · `agents` · `tasks` · `agentChat` (SSE streaming) · `workflows` · `executions` · `actions` · `entities` · `entityTypes` · `knowledge` · `queues` · `queueItems` · `variables` · `connections` · `workflowApps` · `discovery`
121
94
 
122
95
  `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.
123
96
 
@@ -253,10 +226,10 @@ npx @lunnoa/client codegen --url https://lunnoa.your-company.example --api-key l
253
226
  This writes `lunnoa.generated.ts`:
254
227
 
255
228
  ```typescript
256
- import { LunnoaClient } from '@lunnoa/client';
229
+ import { createBrowserClient } from '@lunnoa/client';
257
230
  import { createDeploymentClient } from './lunnoa/lunnoa.generated';
258
231
 
259
- const lunnoa = createDeploymentClient(new LunnoaClient({ baseUrl, apiKey }));
232
+ const lunnoa = createDeploymentClient(createBrowserClient({ baseUrl }));
260
233
 
261
234
  // Entity attributes and states are compile-time checked:
262
235
  const open = await lunnoa.entities.invoice.list({ state: 'pending' });
@@ -276,7 +249,7 @@ Re-run codegen whenever entity types, workflows, or agents change on the deploym
276
249
 
277
250
  ## Define, run, and CLI deploy
278
251
 
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:
252
+ 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:
280
253
 
281
254
  ```bash
282
255
  npx @lunnoa/client entity-types deploy ./human.ts --url https://lunnoa.example --api-key lna_...
@@ -286,9 +259,9 @@ Workflows and agents:
286
259
 
287
260
  ```bash
288
261
  npx @lunnoa/client workflows deploy ./workflows/sync-orders.ts \
289
- --url https://lunnoa.example --api-key lna_... --project-id <uuid>
262
+ --url https://lunnoa.example --api-key lna_...
290
263
  npx @lunnoa/client agents deploy ./agents/triage.ts \
291
- --url https://lunnoa.example --api-key lna_... --project-id <uuid>
264
+ --url https://lunnoa.example --api-key lna_...
292
265
  ```
293
266
 
294
267
  `defineAction` binds catalogue actions only; toolkit `createAction` is for in-platform apps. See Lunnoa Docs under `guides/develop/client/define`, `runs`, and `cli-deploy`.
@@ -307,7 +280,6 @@ npx @lunnoa/client connections list --app gmail --json
307
280
  npx @lunnoa/client agents list --json
308
281
  npx @lunnoa/client workflows list --json
309
282
  npx @lunnoa/client entity-types list --json
310
- npx @lunnoa/client projects list --json
311
283
  npx @lunnoa/client features --json
312
284
  ```
313
285
 
@@ -338,7 +310,7 @@ Partner paste block:
338
310
 
339
311
  ```text
340
312
  Use @lunnoa/client skills: lunnoa-solution-design then lunnoa-client.
341
- LUNNOA_URL=… LUNNOA_API_KEY=lna_… (server only). Project ID=…
313
+ LUNNOA_URL=… LUNNOA_API_KEY=lna_… (server only).
342
314
  Start by interviewing me, then workflowApps.list() / codegen.
343
315
  ```
344
316
 
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ function isBrowserEnvironment() {
12
12
  function assertApiKeyAllowedInEnvironment(apiKey) {
13
13
  if (isBrowserEnvironment()) {
14
14
  throw new Error(
15
- "Refusing to use a Lunnoa API key (lna_...) in a browser environment. API keys are server-side credentials: anyone opening dev tools could read the key and act as the service account across its whole workspace. Either call the Lunnoa API from your own backend which holds the key (backend-for-frontend pattern), or authenticate the end user with a Lunnoa user JWT and pass it as `accessToken`."
15
+ "Refusing to use a Lunnoa API key (lna_...) in a browser environment. API keys are for the CLI and operator scripts, not for app login. Anyone opening dev tools could read the key and act as the service account across its whole workspace. Sign the end user in with lunnoa.auth.login and pass their JWT as `accessToken`."
16
16
  );
17
17
  }
18
18
  if (!apiKey.startsWith(API_KEY_PREFIX)) {
@@ -94,7 +94,7 @@ var HttpClient = class {
94
94
  }
95
95
  if (!options.apiKey && !options.accessToken && !options.tokenStore) {
96
96
  throw new Error(
97
- "LunnoaClient requires an apiKey (server-side), an accessToken (user JWT), or a tokenStore (Pattern B)."
97
+ "LunnoaClient requires an accessToken or tokenStore (Lunnoa user JWT), or an apiKey for operator scripts."
98
98
  );
99
99
  }
100
100
  if (options.apiKey) {
@@ -661,12 +661,12 @@ var AgentsResource = class {
661
661
  });
662
662
  }
663
663
  /**
664
- * Upsert an agent by project-scoped slug.
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(projectId, body, options) {
667
+ upsertBySlug(body, options) {
668
668
  const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
669
- return this.http.post(`/api/projects/${projectId}/agents/upsert`, {
669
+ return this.http.post("/api/agents/upsert", {
670
670
  body: payload
671
671
  });
672
672
  }
@@ -1090,10 +1090,8 @@ var TablesResource = class {
1090
1090
  }
1091
1091
  http;
1092
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
- });
1093
+ list() {
1094
+ return this.http.get("/api/tables");
1097
1095
  }
1098
1096
  /** Returns a single table by ID, including columns and rows. */
1099
1097
  get(id) {
@@ -1513,30 +1511,6 @@ var KnowledgeResource = class {
1513
1511
  }
1514
1512
  };
1515
1513
 
1516
- // src/resources/projects.ts
1517
- var ProjectsResource = class {
1518
- constructor(http) {
1519
- this.http = http;
1520
- }
1521
- http;
1522
- /** Lists projects in the workspace, most recently updated first. */
1523
- list(options) {
1524
- return this.http.get("/api/projects", {
1525
- query: serializeListQuery(options)
1526
- });
1527
- }
1528
- /** Returns a single project. */
1529
- get(projectId, options) {
1530
- return this.http.get(`/api/projects/${projectId}`, {
1531
- query: serializeListQuery(options)
1532
- });
1533
- }
1534
- /** Creates a project. */
1535
- create(data) {
1536
- return this.http.post("/api/projects", { body: data });
1537
- }
1538
- };
1539
-
1540
1514
  // src/resources/queue-items.ts
1541
1515
  var QueueItemsResource = class {
1542
1516
  constructor(http) {
@@ -1816,7 +1790,7 @@ var RunsResource = class {
1816
1790
  * Start a linear multi-step ad-hoc Execution.
1817
1791
  *
1818
1792
  * Accepts a `defineWorkflow` result, a step array, or an explicit
1819
- * `{ steps, projectId?, name? }` payload.
1793
+ * `{ steps, name? }` payload.
1820
1794
  */
1821
1795
  start(input) {
1822
1796
  const options = normalizeStartRunInput(input);
@@ -2034,15 +2008,14 @@ var WorkflowsResource = class {
2034
2008
  });
2035
2009
  }
2036
2010
  /**
2037
- * Upsert a workflow by project-scoped slug from a linear step list.
2011
+ * Upsert a workflow by slug inside the workspace on the API key.
2038
2012
  * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
2039
2013
  */
2040
- upsertBySlug(projectId, body) {
2014
+ upsertBySlug(body) {
2041
2015
  const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
2042
- return this.http.post(
2043
- `/api/projects/${projectId}/workflows/upsert`,
2044
- { body: payload }
2045
- );
2016
+ return this.http.post("/api/workflows/upsert", {
2017
+ body: payload
2018
+ });
2046
2019
  }
2047
2020
  /**
2048
2021
  * Starts a new execution of the workflow with the given trigger inputs.
@@ -2077,7 +2050,7 @@ var WorkflowsResource = class {
2077
2050
  var LunnoaClient = class {
2078
2051
  /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
2079
2052
  http;
2080
- /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
2053
+ /** Login, refresh, SSO discovery, and session helpers for Lunnoa users. */
2081
2054
  auth;
2082
2055
  agents;
2083
2056
  tasks;
@@ -2104,7 +2077,6 @@ var LunnoaClient = class {
2104
2077
  variables;
2105
2078
  scorecards;
2106
2079
  connections;
2107
- projects;
2108
2080
  workflowApps;
2109
2081
  discovery;
2110
2082
  constructor(options) {
@@ -2128,7 +2100,6 @@ var LunnoaClient = class {
2128
2100
  this.variables = new VariablesResource(this.http);
2129
2101
  this.scorecards = new ScorecardsResource(this.http);
2130
2102
  this.connections = new ConnectionsResource(this.http);
2131
- this.projects = new ProjectsResource(this.http);
2132
2103
  this.discovery = new DiscoveryResource(this.http);
2133
2104
  }
2134
2105
  };
@@ -2142,14 +2113,12 @@ var GROUPS_WITH_SUBCOMMAND = /* @__PURE__ */ new Set([
2142
2113
  "tools",
2143
2114
  "apps",
2144
2115
  "connections",
2145
- "projects",
2146
2116
  "skills"
2147
2117
  ]);
2148
2118
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
2149
2119
  "--url",
2150
2120
  "--api-key",
2151
2121
  "--access-token",
2152
- "--project-id",
2153
2122
  "--out",
2154
2123
  "--app",
2155
2124
  "--template"
@@ -2206,9 +2175,6 @@ function parseArgs(argv) {
2206
2175
  case "--access-token":
2207
2176
  args.accessToken = value;
2208
2177
  break;
2209
- case "--project-id":
2210
- args.projectId = value;
2211
- break;
2212
2178
  case "--out":
2213
2179
  args.out = value;
2214
2180
  break;
@@ -2249,12 +2215,11 @@ var USAGE = `Usage:
2249
2215
  npx @lunnoa/client entity-types list [--json]
2250
2216
  npx @lunnoa/client tables list [--json]
2251
2217
  npx @lunnoa/client tables get <slugOrId> [--json]
2252
- npx @lunnoa/client projects list [--json]
2253
2218
  npx @lunnoa/client features [--json]
2254
2219
 
2255
- Deploy (upsert by slug):
2256
- npx @lunnoa/client workflows deploy <path> --url <url> --api-key <key> --project-id <uuid>
2257
- npx @lunnoa/client agents deploy <path> --url <url> --api-key <key> --project-id <uuid>
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>
2258
2223
  npx @lunnoa/client entity-types deploy <path> --url <url> --api-key <key>
2259
2224
 
2260
2225
  Agent skills (copies this version's skills into .claude/skills and .cursor/skills):
@@ -2264,7 +2229,6 @@ Options:
2264
2229
  --url Base URL of the Lunnoa Automate deployment
2265
2230
  --api-key Machine API key (lna_...)
2266
2231
  --access-token User JWT as an alternative to --api-key
2267
- --project-id Project UUID (required for workflow/agent deploy)
2268
2232
  --out Output directory for lunnoa.generated.ts (codegen; default: ./src/lunnoa)
2269
2233
  --app Restrict tools/connections to one workflow app id
2270
2234
  --for-agent Only catalogue entries with availableForAgent
@@ -2272,12 +2236,12 @@ Options:
2272
2236
  --json Machine-readable JSON (preferred for coding agents)
2273
2237
  --help Show this help
2274
2238
 
2275
- Environment: LUNNOA_URL, LUNNOA_API_KEY, LUNNOA_PROJECT_ID (also read from ./.env)
2239
+ Environment: LUNNOA_URL, LUNNOA_API_KEY (also read from ./.env)
2276
2240
  Optional config file: lunnoa.config.ts | .mjs | .js | .json
2277
2241
 
2278
2242
  tools get prints inputConfig and a defineAction() sketch. tools list is compact;
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).
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.
2281
2245
  A slug already granted to other workspaces is not rewritten. Unknown instance attributes are rejected unless
2282
2246
  allowUnknownAttributes is true; use extensions.
2283
2247
  `;
@@ -2750,7 +2714,7 @@ async function runEntityTypesList(client, args) {
2750
2714
  ]);
2751
2715
  }
2752
2716
  async function runTablesList(client, args) {
2753
- const tables = await client.tables.list({ projectId: args.projectId });
2717
+ const tables = await client.tables.list();
2754
2718
  const rows = tables.map((table) => ({
2755
2719
  id: table.id,
2756
2720
  slug: table.slug,
@@ -2782,28 +2746,6 @@ async function runTablesGet(client, args) {
2782
2746
  console.log(`columns: ${(table.columns ?? []).map((c) => c.key).join(", ")}`);
2783
2747
  console.log(`rows: ${table.rows?.length ?? 0}`);
2784
2748
  }
2785
- async function runProjectsList(client, args) {
2786
- const projects = await client.projects.list({
2787
- expansion: ["description", "countAgents", "countWorkflows"]
2788
- });
2789
- const rows = projects.map((project) => ({
2790
- id: project.id,
2791
- name: project.name,
2792
- description: project.description ?? "",
2793
- agents: project._count?.agents ?? "",
2794
- workflows: project._count?.workflows ?? ""
2795
- }));
2796
- if (args.json) {
2797
- printJson(rows);
2798
- return;
2799
- }
2800
- printTable(rows, [
2801
- { key: "id", header: "ID" },
2802
- { key: "name", header: "NAME" },
2803
- { key: "agents", header: "AGENTS" },
2804
- { key: "workflows", header: "WORKFLOWS" }
2805
- ]);
2806
- }
2807
2749
  async function runFeatures(client, args) {
2808
2750
  const features = await client.discovery.enabledFeatures();
2809
2751
  if (args.json) {
@@ -3084,7 +3026,7 @@ function generateFactory(entityEntries, workflowEntries, agentEntries) {
3084
3026
  * entity types, workflows, and agents.
3085
3027
  *
3086
3028
  * \`\`\`ts
3087
- * const lunnoa = createDeploymentClient(new LunnoaClient({ baseUrl, apiKey }));
3029
+ * const lunnoa = createDeploymentClient(createBrowserClient({ baseUrl }));
3088
3030
  * const open = await lunnoa.entities.invoice.list({ state: 'pending' });
3089
3031
  * \`\`\`
3090
3032
  */
@@ -3298,14 +3240,14 @@ async function loadOptionalConfig() {
3298
3240
  if (name.endsWith(".json")) {
3299
3241
  const { readFile: readFile2 } = await import("fs/promises");
3300
3242
  const raw = await readFile2(absolute, "utf8");
3301
- return JSON.parse(raw);
3243
+ const cfg2 = JSON.parse(raw);
3244
+ return { url: cfg2.url, apiKey: cfg2.apiKey };
3302
3245
  }
3303
3246
  const mod = await import(pathToFileURL(absolute).href);
3304
3247
  const cfg = mod.default ?? mod;
3305
3248
  return {
3306
3249
  url: cfg.url,
3307
- apiKey: cfg.apiKey,
3308
- projectId: cfg.projectId
3250
+ apiKey: cfg.apiKey
3309
3251
  };
3310
3252
  }
3311
3253
  return {};
@@ -3329,6 +3271,13 @@ function setEnvValues(source, values) {
3329
3271
  }
3330
3272
  return out;
3331
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
+ }
3332
3281
  async function isDirectory(path) {
3333
3282
  return (await stat(path).catch(() => void 0))?.isDirectory() ?? false;
3334
3283
  }
@@ -3379,13 +3328,16 @@ async function runInit(args) {
3379
3328
  if (process.stdin.isTTY && (!url || !apiKey)) {
3380
3329
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3381
3330
  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();
3331
+ apiKey ||= (await rl.question("API key lna_... (CLI only: discovery, codegen, deploy. Enter to skip): ")).trim();
3383
3332
  rl.close();
3384
3333
  }
3385
3334
  const example = await readFile(join(dest, ".env.example"), "utf8").catch(() => "");
3386
3335
  await writeFile(
3387
3336
  join(dest, ".env"),
3388
- setEnvValues(example, { LUNNOA_URL: url, LUNNOA_API_KEY: apiKey }),
3337
+ setEnvValues(dropRetiredProjectEnv(example), {
3338
+ LUNNOA_URL: url,
3339
+ LUNNOA_API_KEY: apiKey
3340
+ }),
3389
3341
  "utf8"
3390
3342
  );
3391
3343
  console.log(`
@@ -3395,7 +3347,7 @@ Next steps:`);
3395
3347
  console.log(` cd ${args.path}`);
3396
3348
  console.log(" npm install");
3397
3349
  if (!apiKey) {
3398
- console.log(" Add LUNNOA_API_KEY to .env, or set LUNNOA_AUTH_MODE=user to sign in with your Lunnoa account");
3350
+ console.log(" The app signs people in as Lunnoa users. Add LUNNOA_API_KEY to .env when you want CLI discovery and deploy");
3399
3351
  }
3400
3352
  console.log(" npm run dev");
3401
3353
  console.log(" Open the folder in Claude Code / Cursor / Codex and describe your idea.");
@@ -3427,7 +3379,6 @@ async function bindClient(args) {
3427
3379
  const url = args.url ?? process.env.LUNNOA_URL ?? fileConfig.url;
3428
3380
  const apiKey = args.apiKey ?? process.env.LUNNOA_API_KEY ?? fileConfig.apiKey;
3429
3381
  const accessToken = args.accessToken;
3430
- const projectId = args.projectId ?? process.env.LUNNOA_PROJECT_ID ?? fileConfig.projectId;
3431
3382
  if (!url) {
3432
3383
  throw new Error("Missing --url (or LUNNOA_URL / lunnoa.config).");
3433
3384
  }
@@ -3438,7 +3389,6 @@ async function bindClient(args) {
3438
3389
  }
3439
3390
  return {
3440
3391
  client: new LunnoaClient({ baseUrl: url, apiKey, accessToken }),
3441
- projectId,
3442
3392
  url
3443
3393
  };
3444
3394
  }
@@ -3486,29 +3436,19 @@ async function deployWorkflow(args) {
3486
3436
  if (!args.path) {
3487
3437
  throw new Error("Missing definition path. Example: workflows deploy ./workflow.ts");
3488
3438
  }
3489
- const { client, projectId } = await bindClient(args);
3490
- if (!projectId) {
3491
- throw new Error(
3492
- "Missing --project-id (or LUNNOA_PROJECT_ID / lunnoa.config)."
3493
- );
3494
- }
3439
+ const { client } = await bindClient(args);
3495
3440
  const mod = await loadDefinitionModule(args.path);
3496
3441
  const def = pickWorkflowDefinition(mod);
3497
3442
  const payload = workflowDefinitionToUpsert(def);
3498
- console.log(`Deploying workflow slug="${payload.slug}" to project ${projectId} ...`);
3499
- const result = await client.workflows.upsertBySlug(projectId, payload);
3443
+ console.log(`Deploying workflow slug="${payload.slug}" ...`);
3444
+ const result = await client.workflows.upsertBySlug(payload);
3500
3445
  console.log(`Upserted workflow ${result.id} (${payload.slug})`);
3501
3446
  }
3502
3447
  async function deployAgent(args) {
3503
3448
  if (!args.path) {
3504
3449
  throw new Error("Missing definition path. Example: agents deploy ./agent.ts");
3505
3450
  }
3506
- const { client, projectId } = await bindClient(args);
3507
- if (!projectId) {
3508
- throw new Error(
3509
- "Missing --project-id (or LUNNOA_PROJECT_ID / lunnoa.config)."
3510
- );
3511
- }
3451
+ const { client } = await bindClient(args);
3512
3452
  const mod = await loadDefinitionModule(args.path);
3513
3453
  const def = pickAgentDefinition(mod);
3514
3454
  const workflowIds = [];
@@ -3517,7 +3457,6 @@ async function deployAgent(args) {
3517
3457
  if (unwrapped && typeof unwrapped === "object" && unwrapped.kind === DEFINE_KINDS.workflow) {
3518
3458
  const wf = unwrapped;
3519
3459
  const upserted = await client.workflows.upsertBySlug(
3520
- projectId,
3521
3460
  workflowDefinitionToUpsert(wf)
3522
3461
  );
3523
3462
  if (upserted.id) {
@@ -3527,8 +3466,8 @@ async function deployAgent(args) {
3527
3466
  }
3528
3467
  }
3529
3468
  const payload = agentDefinitionToUpsert(def, { workflowIds });
3530
- console.log(`Deploying agent slug="${payload.slug}" to project ${projectId} ...`);
3531
- const result = await client.agents.upsertBySlug(projectId, payload);
3469
+ console.log(`Deploying agent slug="${payload.slug}" ...`);
3470
+ const result = await client.agents.upsertBySlug(payload);
3532
3471
  console.log(`Upserted agent ${result.id} (${payload.slug})`);
3533
3472
  }
3534
3473
  async function deployEntityType(args) {
@@ -3552,8 +3491,7 @@ var DISCOVERY_SUBCOMMANDS = {
3552
3491
  apps: ["list", "get"],
3553
3492
  connections: ["list", "get"],
3554
3493
  tables: ["list", "get"],
3555
- features: void 0,
3556
- projects: ["list"]
3494
+ features: void 0
3557
3495
  };
3558
3496
  function isDiscoveryCommand(args) {
3559
3497
  const { command, subcommand } = args;
@@ -3563,9 +3501,6 @@ function isDiscoveryCommand(args) {
3563
3501
  if (command === "features") {
3564
3502
  return true;
3565
3503
  }
3566
- if (command === "projects" && (subcommand === "list" || !subcommand)) {
3567
- return true;
3568
- }
3569
3504
  if ((command === "agents" || command === "workflows" || command === "entity-types") && subcommand === "list") {
3570
3505
  return true;
3571
3506
  }
@@ -3643,10 +3578,6 @@ async function runDiscovery(args) {
3643
3578
  await runTablesGet(client, args);
3644
3579
  return true;
3645
3580
  }
3646
- if (command === "projects" && (subcommand === "list" || !subcommand)) {
3647
- await runProjectsList(client, args);
3648
- return true;
3649
- }
3650
3581
  if (command === "features") {
3651
3582
  await runFeatures(client, args);
3652
3583
  return true;
package/dist/index.cjs CHANGED
@@ -8,7 +8,7 @@ function isBrowserEnvironment() {
8
8
  function assertApiKeyAllowedInEnvironment(apiKey) {
9
9
  if (isBrowserEnvironment()) {
10
10
  throw new Error(
11
- "Refusing to use a Lunnoa API key (lna_...) in a browser environment. API keys are server-side credentials: anyone opening dev tools could read the key and act as the service account across its whole workspace. Either call the Lunnoa API from your own backend which holds the key (backend-for-frontend pattern), or authenticate the end user with a Lunnoa user JWT and pass it as `accessToken`."
11
+ "Refusing to use a Lunnoa API key (lna_...) in a browser environment. API keys are for the CLI and operator scripts, not for app login. Anyone opening dev tools could read the key and act as the service account across its whole workspace. Sign the end user in with lunnoa.auth.login and pass their JWT as `accessToken`."
12
12
  );
13
13
  }
14
14
  if (!apiKey.startsWith(API_KEY_PREFIX)) {
@@ -90,7 +90,7 @@ var HttpClient = class {
90
90
  }
91
91
  if (!options.apiKey && !options.accessToken && !options.tokenStore) {
92
92
  throw new Error(
93
- "LunnoaClient requires an apiKey (server-side), an accessToken (user JWT), or a tokenStore (Pattern B)."
93
+ "LunnoaClient requires an accessToken or tokenStore (Lunnoa user JWT), or an apiKey for operator scripts."
94
94
  );
95
95
  }
96
96
  if (options.apiKey) {
@@ -703,12 +703,12 @@ var AgentsResource = class {
703
703
  });
704
704
  }
705
705
  /**
706
- * Upsert an agent by project-scoped slug.
706
+ * Upsert an agent by slug inside the workspace on the API key.
707
707
  * Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
708
708
  */
709
- upsertBySlug(projectId, body, options) {
709
+ upsertBySlug(body, options) {
710
710
  const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
711
- return this.http.post(`/api/projects/${projectId}/agents/upsert`, {
711
+ return this.http.post("/api/agents/upsert", {
712
712
  body: payload
713
713
  });
714
714
  }
@@ -1132,10 +1132,8 @@ var TablesResource = class {
1132
1132
  }
1133
1133
  http;
1134
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
- });
1135
+ list() {
1136
+ return this.http.get("/api/tables");
1139
1137
  }
1140
1138
  /** Returns a single table by ID, including columns and rows. */
1141
1139
  get(id) {
@@ -1555,30 +1553,6 @@ var KnowledgeResource = class {
1555
1553
  }
1556
1554
  };
1557
1555
 
1558
- // src/resources/projects.ts
1559
- var ProjectsResource = class {
1560
- constructor(http) {
1561
- this.http = http;
1562
- }
1563
- http;
1564
- /** Lists projects in the workspace, most recently updated first. */
1565
- list(options) {
1566
- return this.http.get("/api/projects", {
1567
- query: serializeListQuery(options)
1568
- });
1569
- }
1570
- /** Returns a single project. */
1571
- get(projectId, options) {
1572
- return this.http.get(`/api/projects/${projectId}`, {
1573
- query: serializeListQuery(options)
1574
- });
1575
- }
1576
- /** Creates a project. */
1577
- create(data) {
1578
- return this.http.post("/api/projects", { body: data });
1579
- }
1580
- };
1581
-
1582
1556
  // src/resources/queue-items.ts
1583
1557
  var QueueItemsResource = class {
1584
1558
  constructor(http) {
@@ -1858,7 +1832,7 @@ var RunsResource = class {
1858
1832
  * Start a linear multi-step ad-hoc Execution.
1859
1833
  *
1860
1834
  * Accepts a `defineWorkflow` result, a step array, or an explicit
1861
- * `{ steps, projectId?, name? }` payload.
1835
+ * `{ steps, name? }` payload.
1862
1836
  */
1863
1837
  start(input) {
1864
1838
  const options = normalizeStartRunInput(input);
@@ -2076,15 +2050,14 @@ var WorkflowsResource = class {
2076
2050
  });
2077
2051
  }
2078
2052
  /**
2079
- * Upsert a workflow by project-scoped slug from a linear step list.
2053
+ * Upsert a workflow by slug inside the workspace on the API key.
2080
2054
  * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
2081
2055
  */
2082
- upsertBySlug(projectId, body) {
2056
+ upsertBySlug(body) {
2083
2057
  const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
2084
- return this.http.post(
2085
- `/api/projects/${projectId}/workflows/upsert`,
2086
- { body: payload }
2087
- );
2058
+ return this.http.post("/api/workflows/upsert", {
2059
+ body: payload
2060
+ });
2088
2061
  }
2089
2062
  /**
2090
2063
  * Starts a new execution of the workflow with the given trigger inputs.
@@ -2119,7 +2092,7 @@ var WorkflowsResource = class {
2119
2092
  var LunnoaClient = class {
2120
2093
  /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
2121
2094
  http;
2122
- /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
2095
+ /** Login, refresh, SSO discovery, and session helpers for Lunnoa users. */
2123
2096
  auth;
2124
2097
  agents;
2125
2098
  tasks;
@@ -2146,7 +2119,6 @@ var LunnoaClient = class {
2146
2119
  variables;
2147
2120
  scorecards;
2148
2121
  connections;
2149
- projects;
2150
2122
  workflowApps;
2151
2123
  discovery;
2152
2124
  constructor(options) {
@@ -2170,7 +2142,6 @@ var LunnoaClient = class {
2170
2142
  this.variables = new VariablesResource(this.http);
2171
2143
  this.scorecards = new ScorecardsResource(this.http);
2172
2144
  this.connections = new ConnectionsResource(this.http);
2173
- this.projects = new ProjectsResource(this.http);
2174
2145
  this.discovery = new DiscoveryResource(this.http);
2175
2146
  }
2176
2147
  };
@@ -2266,7 +2237,6 @@ exports.HttpClient = HttpClient;
2266
2237
  exports.KnowledgeResource = KnowledgeResource;
2267
2238
  exports.LunnoaApiError = LunnoaApiError;
2268
2239
  exports.LunnoaClient = LunnoaClient;
2269
- exports.ProjectsResource = ProjectsResource;
2270
2240
  exports.QueueItemsResource = QueueItemsResource;
2271
2241
  exports.QueuesResource = QueuesResource;
2272
2242
  exports.RunsResource = RunsResource;