@lunnoa/client 2.0.0 → 2.2.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' });
@@ -304,13 +277,14 @@ npx @lunnoa/client tools get http_action_send-request --json
304
277
  npx @lunnoa/client apps list --json
305
278
  npx @lunnoa/client apps get http --json
306
279
  npx @lunnoa/client connections list --app gmail --json
280
+ npx @lunnoa/client ai-connections list --json
307
281
  npx @lunnoa/client agents list --json
308
282
  npx @lunnoa/client workflows list --json
309
283
  npx @lunnoa/client entity-types list --json
310
284
  npx @lunnoa/client features --json
311
285
  ```
312
286
 
313
- `tools list` is compact (`appId`, `actionId`, name, flags). `tools get` prints `inputConfig` and a `defineAction({ id, appId, input })` sketch. Auth flags and `LUNNOA_URL` / `LUNNOA_API_KEY` / `lunnoa.config` work the same as deploy. The CLI also reads `./.env` (real environment variables win).
287
+ `tools list` is compact (`appId`, `actionId`, name, flags). `tools get` prints `inputConfig` and a `defineAction({ id, appId, input })` sketch. `ai-connections list --json` prints the model ids each AI connection accepts; use `models[].id` as `defineAgent({ model })`. Auth flags and `LUNNOA_URL` / `LUNNOA_API_KEY` / `lunnoa.config` work the same as deploy. The CLI also reads `./.env` (real environment variables win).
314
288
 
315
289
  ## Regenerating the API types
316
290
 
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) {
@@ -472,7 +472,7 @@ var AgentChatClient = class {
472
472
  function messageTaskBody(message, options = {}) {
473
473
  return {
474
474
  messages: [message],
475
- ...options.chatMode ? { chatMode: options.chatMode } : {},
475
+ chatMode: options.chatMode ?? "preview",
476
476
  ...options.data ? { data: options.data } : {},
477
477
  ...options.piiResolution ? { piiResolution: options.piiResolution } : {}
478
478
  };
@@ -852,6 +852,22 @@ var SsoAuthResource = class {
852
852
  }
853
853
  };
854
854
 
855
+ // src/resources/ai-connections.ts
856
+ var AiConnectionsResource = class {
857
+ constructor(http) {
858
+ this.http = http;
859
+ }
860
+ http;
861
+ /**
862
+ * Lists AI connections the caller can assign to an agent, with the model
863
+ * ids each connection accepts. Credentials are never included.
864
+ * Requires `agents:create` (the same permission as agent deploy).
865
+ */
866
+ list() {
867
+ return this.http.get("/api/ai-connections");
868
+ }
869
+ };
870
+
855
871
  // src/resources/connections.ts
856
872
  var ConnectionsResource = class {
857
873
  constructor(http) {
@@ -2050,7 +2066,7 @@ var WorkflowsResource = class {
2050
2066
  var LunnoaClient = class {
2051
2067
  /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
2052
2068
  http;
2053
- /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
2069
+ /** Login, refresh, SSO discovery, and session helpers for Lunnoa users. */
2054
2070
  auth;
2055
2071
  agents;
2056
2072
  tasks;
@@ -2077,6 +2093,8 @@ var LunnoaClient = class {
2077
2093
  variables;
2078
2094
  scorecards;
2079
2095
  connections;
2096
+ /** AI provider connections and the model ids each one accepts. */
2097
+ aiConnections;
2080
2098
  workflowApps;
2081
2099
  discovery;
2082
2100
  constructor(options) {
@@ -2100,6 +2118,7 @@ var LunnoaClient = class {
2100
2118
  this.variables = new VariablesResource(this.http);
2101
2119
  this.scorecards = new ScorecardsResource(this.http);
2102
2120
  this.connections = new ConnectionsResource(this.http);
2121
+ this.aiConnections = new AiConnectionsResource(this.http);
2103
2122
  this.discovery = new DiscoveryResource(this.http);
2104
2123
  }
2105
2124
  };
@@ -2113,6 +2132,7 @@ var GROUPS_WITH_SUBCOMMAND = /* @__PURE__ */ new Set([
2113
2132
  "tools",
2114
2133
  "apps",
2115
2134
  "connections",
2135
+ "ai-connections",
2116
2136
  "skills"
2117
2137
  ]);
2118
2138
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
@@ -2210,6 +2230,7 @@ var USAGE = `Usage:
2210
2230
  npx @lunnoa/client apps get <appId> [--json]
2211
2231
  npx @lunnoa/client connections list [--app <appId>] [--json]
2212
2232
  npx @lunnoa/client connections get <connectionId> [--json]
2233
+ npx @lunnoa/client ai-connections list [--json]
2213
2234
  npx @lunnoa/client agents list [--json]
2214
2235
  npx @lunnoa/client workflows list [--json]
2215
2236
  npx @lunnoa/client entity-types list [--json]
@@ -2239,6 +2260,9 @@ Options:
2239
2260
  Environment: LUNNOA_URL, LUNNOA_API_KEY (also read from ./.env)
2240
2261
  Optional config file: lunnoa.config.ts | .mjs | .js | .json
2241
2262
 
2263
+ ai-connections list prints the model ids each AI connection accepts. Use
2264
+ models[].id as defineAgent({ model }). connections list is app connections
2265
+ (Gmail, HTTP), not model providers.
2242
2266
  tools get prints inputConfig and a defineAction() sketch. tools list is compact;
2243
2267
  run tools get for the schema. An entity-type slug is unique on the deployment
2244
2268
  and grants the API key's workspace when the slug is new.
@@ -2602,6 +2626,40 @@ async function runAppsGet(client, args) {
2602
2626
  { key: "needsConnection", header: "NEEDS_CONN" }
2603
2627
  ]);
2604
2628
  }
2629
+ async function runAiConnectionsList(client, args) {
2630
+ const connections = await client.aiConnections.list();
2631
+ if (args.json) {
2632
+ printJson(connections);
2633
+ return;
2634
+ }
2635
+ const rows = connections.flatMap((connection) => {
2636
+ if (connection.models.length === 0) {
2637
+ return [
2638
+ {
2639
+ name: connection.name,
2640
+ provider: connection.provider,
2641
+ model: "",
2642
+ isDefault: connection.isDefault,
2643
+ id: connection.id
2644
+ }
2645
+ ];
2646
+ }
2647
+ return connection.models.map((model) => ({
2648
+ name: connection.name,
2649
+ provider: connection.provider,
2650
+ model: model.id,
2651
+ isDefault: connection.isDefault && model.isDefault,
2652
+ id: connection.id
2653
+ }));
2654
+ });
2655
+ printTable(rows, [
2656
+ { key: "name", header: "CONNECTION" },
2657
+ { key: "provider", header: "PROVIDER" },
2658
+ { key: "model", header: "MODEL" },
2659
+ { key: "isDefault", header: "DEFAULT" },
2660
+ { key: "id", header: "ID" }
2661
+ ]);
2662
+ }
2605
2663
  async function runConnectionsList(client, args) {
2606
2664
  const connections = await client.connections.list({
2607
2665
  expansion: ["workflowAppId", "connectionId", "createdAt"]
@@ -3026,7 +3084,7 @@ function generateFactory(entityEntries, workflowEntries, agentEntries) {
3026
3084
  * entity types, workflows, and agents.
3027
3085
  *
3028
3086
  * \`\`\`ts
3029
- * const lunnoa = createDeploymentClient(new LunnoaClient({ baseUrl, apiKey }));
3087
+ * const lunnoa = createDeploymentClient(createBrowserClient({ baseUrl }));
3030
3088
  * const open = await lunnoa.entities.invoice.list({ state: 'pending' });
3031
3089
  * \`\`\`
3032
3090
  */
@@ -3328,7 +3386,7 @@ async function runInit(args) {
3328
3386
  if (process.stdin.isTTY && (!url || !apiKey)) {
3329
3387
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3330
3388
  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();
3389
+ apiKey ||= (await rl.question("API key lna_... (CLI only: discovery, codegen, deploy. Enter to skip): ")).trim();
3332
3390
  rl.close();
3333
3391
  }
3334
3392
  const example = await readFile(join(dest, ".env.example"), "utf8").catch(() => "");
@@ -3347,7 +3405,7 @@ Next steps:`);
3347
3405
  console.log(` cd ${args.path}`);
3348
3406
  console.log(" npm install");
3349
3407
  if (!apiKey) {
3350
- console.log(" Add LUNNOA_API_KEY to .env, or set LUNNOA_AUTH_MODE=user to sign in with your Lunnoa account");
3408
+ console.log(" The app signs people in as Lunnoa users. Add LUNNOA_API_KEY to .env when you want CLI discovery and deploy");
3351
3409
  }
3352
3410
  console.log(" npm run dev");
3353
3411
  console.log(" Open the folder in Claude Code / Cursor / Codex and describe your idea.");
@@ -3490,6 +3548,7 @@ var DISCOVERY_SUBCOMMANDS = {
3490
3548
  tools: ["list", "get", "search"],
3491
3549
  apps: ["list", "get"],
3492
3550
  connections: ["list", "get"],
3551
+ "ai-connections": ["list"],
3493
3552
  tables: ["list", "get"],
3494
3553
  features: void 0
3495
3554
  };
@@ -3523,7 +3582,7 @@ ${USAGE}`
3523
3582
  }
3524
3583
  async function runDiscovery(args) {
3525
3584
  const { command, subcommand } = args;
3526
- if (command === "tools" || command === "apps" || command === "connections" || command === "tables") {
3585
+ if (command === "tools" || command === "apps" || command === "connections" || command === "ai-connections" || command === "tables") {
3527
3586
  assertKnownDiscoveryShape(args);
3528
3587
  }
3529
3588
  if (!isDiscoveryCommand(args)) {
@@ -3558,6 +3617,10 @@ async function runDiscovery(args) {
3558
3617
  await runConnectionsGet(client, args);
3559
3618
  return true;
3560
3619
  }
3620
+ if (command === "ai-connections" && subcommand === "list") {
3621
+ await runAiConnectionsList(client, args);
3622
+ return true;
3623
+ }
3561
3624
  if (command === "agents" && subcommand === "list") {
3562
3625
  await runAgentsList(client, args);
3563
3626
  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) {
@@ -468,7 +468,7 @@ var AgentChatClient = class {
468
468
  function messageTaskBody(message, options = {}) {
469
469
  return {
470
470
  messages: [message],
471
- ...options.chatMode ? { chatMode: options.chatMode } : {},
471
+ chatMode: options.chatMode ?? "preview",
472
472
  ...options.data ? { data: options.data } : {},
473
473
  ...options.piiResolution ? { piiResolution: options.piiResolution } : {}
474
474
  };
@@ -894,6 +894,22 @@ var SsoAuthResource = class {
894
894
  }
895
895
  };
896
896
 
897
+ // src/resources/ai-connections.ts
898
+ var AiConnectionsResource = class {
899
+ constructor(http) {
900
+ this.http = http;
901
+ }
902
+ http;
903
+ /**
904
+ * Lists AI connections the caller can assign to an agent, with the model
905
+ * ids each connection accepts. Credentials are never included.
906
+ * Requires `agents:create` (the same permission as agent deploy).
907
+ */
908
+ list() {
909
+ return this.http.get("/api/ai-connections");
910
+ }
911
+ };
912
+
897
913
  // src/resources/connections.ts
898
914
  var ConnectionsResource = class {
899
915
  constructor(http) {
@@ -2092,7 +2108,7 @@ var WorkflowsResource = class {
2092
2108
  var LunnoaClient = class {
2093
2109
  /** The underlying HTTP layer, exposed for advanced/escape-hatch requests. */
2094
2110
  http;
2095
- /** Login, refresh, SSO discovery, and session helpers (Pattern B). */
2111
+ /** Login, refresh, SSO discovery, and session helpers for Lunnoa users. */
2096
2112
  auth;
2097
2113
  agents;
2098
2114
  tasks;
@@ -2119,6 +2135,8 @@ var LunnoaClient = class {
2119
2135
  variables;
2120
2136
  scorecards;
2121
2137
  connections;
2138
+ /** AI provider connections and the model ids each one accepts. */
2139
+ aiConnections;
2122
2140
  workflowApps;
2123
2141
  discovery;
2124
2142
  constructor(options) {
@@ -2142,6 +2160,7 @@ var LunnoaClient = class {
2142
2160
  this.variables = new VariablesResource(this.http);
2143
2161
  this.scorecards = new ScorecardsResource(this.http);
2144
2162
  this.connections = new ConnectionsResource(this.http);
2163
+ this.aiConnections = new AiConnectionsResource(this.http);
2145
2164
  this.discovery = new DiscoveryResource(this.http);
2146
2165
  }
2147
2166
  };
@@ -2223,6 +2242,7 @@ exports.ActionsResource = ActionsResource;
2223
2242
  exports.AgentChatClient = AgentChatClient;
2224
2243
  exports.AgentChatStream = AgentChatStream;
2225
2244
  exports.AgentsResource = AgentsResource;
2245
+ exports.AiConnectionsResource = AiConnectionsResource;
2226
2246
  exports.ApprovalsResource = ApprovalsResource;
2227
2247
  exports.AuthResource = AuthResource;
2228
2248
  exports.ConnectionsResource = ConnectionsResource;