@lunnoa/client 2.0.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
@@ -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
 
@@ -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' });
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) {
@@ -2050,7 +2050,7 @@ var WorkflowsResource = class {
2050
2050
  var LunnoaClient = class {
2051
2051
  /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
2052
2052
  http;
2053
- /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
2053
+ /** Login, refresh, SSO discovery, and session helpers for Lunnoa users. */
2054
2054
  auth;
2055
2055
  agents;
2056
2056
  tasks;
@@ -3026,7 +3026,7 @@ function generateFactory(entityEntries, workflowEntries, agentEntries) {
3026
3026
  * entity types, workflows, and agents.
3027
3027
  *
3028
3028
  * \`\`\`ts
3029
- * const lunnoa = createDeploymentClient(new LunnoaClient({ baseUrl, apiKey }));
3029
+ * const lunnoa = createDeploymentClient(createBrowserClient({ baseUrl }));
3030
3030
  * const open = await lunnoa.entities.invoice.list({ state: 'pending' });
3031
3031
  * \`\`\`
3032
3032
  */
@@ -3328,7 +3328,7 @@ async function runInit(args) {
3328
3328
  if (process.stdin.isTTY && (!url || !apiKey)) {
3329
3329
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3330
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();
3331
+ apiKey ||= (await rl.question("API key lna_... (CLI only: discovery, codegen, deploy. Enter to skip): ")).trim();
3332
3332
  rl.close();
3333
3333
  }
3334
3334
  const example = await readFile(join(dest, ".env.example"), "utf8").catch(() => "");
@@ -3347,7 +3347,7 @@ Next steps:`);
3347
3347
  console.log(` cd ${args.path}`);
3348
3348
  console.log(" npm install");
3349
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");
3350
+ console.log(" The app signs people in as Lunnoa users. Add LUNNOA_API_KEY to .env when you want CLI discovery and deploy");
3351
3351
  }
3352
3352
  console.log(" npm run dev");
3353
3353
  console.log(" Open the folder in Claude Code / Cursor / Codex and describe your idea.");
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) {
@@ -2092,7 +2092,7 @@ var WorkflowsResource = class {
2092
2092
  var LunnoaClient = class {
2093
2093
  /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
2094
2094
  http;
2095
- /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
2095
+ /** Login, refresh, SSO discovery, and session helpers for Lunnoa users. */
2096
2096
  auth;
2097
2097
  agents;
2098
2098
  tasks;