@layers/amba 4.1.5 → 4.1.7

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.
@@ -21,6 +21,8 @@ export interface ProjectSummary {
21
21
  environment?: string;
22
22
  bundle_id?: string | null;
23
23
  google_oauth_client_id?: string | null;
24
+ magic_link_redirect_url?: string | null;
25
+ auth_email_brand_name?: string | null;
24
26
  status?: string;
25
27
  created_at?: string;
26
28
  /** Persisted subscription/accounting tier. */
@@ -71,6 +73,8 @@ export declare function updateProject(projectId: string, patch: {
71
73
  name?: string;
72
74
  bundle_id?: string | null;
73
75
  google_oauth_client_id?: string | null;
76
+ magic_link_redirect_url?: string | null;
77
+ auth_email_brand_name?: string | null;
74
78
  platform?: string;
75
79
  environment?: string;
76
80
  }): Promise<ApiResponse<{
@@ -78,6 +82,8 @@ export declare function updateProject(projectId: string, patch: {
78
82
  name: string;
79
83
  bundle_id: string | null;
80
84
  google_oauth_client_id: string | null;
85
+ magic_link_redirect_url: string | null;
86
+ auth_email_brand_name: string | null;
81
87
  platform: string;
82
88
  environment: string;
83
89
  }>>;
@@ -87,6 +93,8 @@ export declare function getProject(projectId: string): Promise<ApiResponse<{
87
93
  platform: string;
88
94
  environment: string;
89
95
  bundle_id: string | null;
96
+ magic_link_redirect_url: string | null;
97
+ auth_email_brand_name: string | null;
90
98
  created_at: string;
91
99
  }>>;
92
100
  export declare function deleteProject(projectId: string): Promise<ApiResponse<{
@@ -674,7 +682,7 @@ export declare function dropCollection(projectId: string, name: string, confirm:
674
682
  deleted: boolean;
675
683
  version: number;
676
684
  }>>;
677
- export type AiProviderName = 'anthropic' | 'openai';
685
+ export type AiProviderName = 'anthropic' | 'openai' | 'mistral' | 'gemini';
678
686
  export interface AiProviderRow {
679
687
  name: AiProviderName;
680
688
  /** Whether an API key is currently configured for this provider. */
@@ -1,3 +1,4 @@
1
+ import { updateProject } from '../api-client.js';
1
2
  export declare function projectsListCommand(): Promise<void>;
2
3
  export declare function projectsCreateCommand(input: {
3
4
  name: string;
@@ -6,13 +7,19 @@ export declare function projectsCreateCommand(input: {
6
7
  googleOauthClientId?: string;
7
8
  platform?: string;
8
9
  }): Promise<void>;
9
- export declare function projectsUpdateCommand(projectId: string, input: {
10
+ export interface ProjectUpdateCommandInput {
10
11
  name?: string;
11
12
  bundleId?: string;
12
13
  googleOauthClientId?: string;
14
+ magicLinkRedirectUrl?: string;
15
+ authEmailBrandName?: string;
16
+ clearMagicLinkRedirectUrl?: boolean;
17
+ clearAuthEmailBrandName?: boolean;
13
18
  platform?: string;
14
19
  environment?: string;
15
- }): Promise<void>;
20
+ }
21
+ export declare function buildProjectUpdatePatch(input: ProjectUpdateCommandInput): Parameters<typeof updateProject>[1];
22
+ export declare function projectsUpdateCommand(projectId: string, input: ProjectUpdateCommandInput): Promise<void>;
16
23
  export declare function projectsShowCommand(projectId: string): Promise<void>;
17
24
  export declare function projectsDeleteCommand(projectId: string, opts?: {
18
25
  yes?: boolean;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
2
+ import { Command, Option } from "commander";
3
3
  import pc from "picocolors";
4
4
  import { access, chmod, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
5
5
  import { homedir, tmpdir } from "node:os";
@@ -660,6 +660,13 @@ function isPatShape(token) {
660
660
  */
661
661
  async function browserAuthFlow() {
662
662
  return new Promise((resolve, reject) => {
663
+ let timeout;
664
+ const settle = (outcome) => {
665
+ if (timeout !== void 0) clearTimeout(timeout);
666
+ server.close();
667
+ server.closeAllConnections();
668
+ outcome();
669
+ };
663
670
  const server = createServer((req, res) => {
664
671
  const url = new URL(req.url ?? "/", `http://localhost`);
665
672
  if (url.pathname !== "/callback") {
@@ -672,20 +679,16 @@ async function browserAuthFlow() {
672
679
  const expiresAt = url.searchParams.get("expires_at");
673
680
  if (!accessToken || !refreshToken || !expiresAt) {
674
681
  res.writeHead(400, { "Content-Type": "text/html" });
675
- res.end("<html><body><h2>Authentication failed</h2><p>Missing token parameters. Please try again.</p></body></html>");
676
- server.close();
677
- reject(/* @__PURE__ */ new Error("Missing token parameters in callback"));
682
+ res.end("<html><body><h2>Authentication failed</h2><p>Missing token parameters. Please try again.</p></body></html>", () => settle(() => reject(/* @__PURE__ */ new Error("Missing token parameters in callback"))));
678
683
  return;
679
684
  }
680
- res.writeHead(200, { "Content-Type": "text/html" });
681
- res.end("<html><body style=\"font-family:system-ui;text-align:center;padding:3rem\"><h2>Authenticated!</h2><p>You can close this window and return to the terminal.</p></body></html>");
682
685
  const creds = {
683
686
  access_token: accessToken,
684
687
  refresh_token: refreshToken,
685
688
  expires_at: expiresAt
686
689
  };
687
- server.close();
688
- resolve(creds);
690
+ res.writeHead(200, { "Content-Type": "text/html" });
691
+ res.end("<html><body style=\"font-family:system-ui;text-align:center;padding:3rem\"><h2>Authenticated!</h2><p>You can close this window and return to the terminal.</p></body></html>", () => settle(() => resolve(creds)));
689
692
  });
690
693
  server.listen(0, "127.0.0.1", () => {
691
694
  const addr = server.address();
@@ -703,9 +706,8 @@ async function browserAuthFlow() {
703
706
  console.log(pc.yellow(` Please open this URL manually:`));
704
707
  console.log(` ${pc.underline(authUrl)}`);
705
708
  });
706
- setTimeout(() => {
707
- server.close();
708
- reject(/* @__PURE__ */ new Error("Authentication timed out. Please try again."));
709
+ timeout = setTimeout(() => {
710
+ settle(() => reject(/* @__PURE__ */ new Error("Authentication timed out. Please try again.")));
709
711
  }, 12e4);
710
712
  });
711
713
  });
@@ -4738,22 +4740,34 @@ async function projectsCreateCommand(input) {
4738
4740
  handleError$2(err);
4739
4741
  }
4740
4742
  }
4741
- async function projectsUpdateCommand(projectId, input) {
4742
- console.log();
4743
- console.log(pc.bold(` amba projects update ${projectId}`));
4744
- console.log(pc.dim(" ─────────────────────────────────"));
4745
- console.log();
4743
+ function buildProjectUpdatePatch(input) {
4744
+ if (input.magicLinkRedirectUrl !== void 0 && input.clearMagicLinkRedirectUrl) throw new Error("--magic-link-redirect-url conflicts with --clear-magic-link-redirect-url");
4745
+ if (input.authEmailBrandName !== void 0 && input.clearAuthEmailBrandName) throw new Error("--auth-email-brand-name conflicts with --clear-auth-email-brand-name");
4746
4746
  const patch = {};
4747
4747
  if (input.name !== void 0) patch.name = input.name;
4748
4748
  if (input.bundleId !== void 0) patch.bundle_id = input.bundleId;
4749
4749
  if (input.googleOauthClientId !== void 0) patch.google_oauth_client_id = input.googleOauthClientId;
4750
4750
  if (input.platform !== void 0) patch.platform = input.platform;
4751
4751
  if (input.environment !== void 0) patch.environment = input.environment;
4752
+ if (input.magicLinkRedirectUrl !== void 0) patch.magic_link_redirect_url = input.magicLinkRedirectUrl;
4753
+ if (input.clearMagicLinkRedirectUrl) patch.magic_link_redirect_url = null;
4754
+ if (input.authEmailBrandName !== void 0) patch.auth_email_brand_name = input.authEmailBrandName;
4755
+ if (input.clearAuthEmailBrandName) patch.auth_email_brand_name = null;
4756
+ return patch;
4757
+ }
4758
+ async function projectsUpdateCommand(projectId, input) {
4759
+ console.log();
4760
+ console.log(pc.bold(` amba projects update ${projectId}`));
4761
+ console.log(pc.dim(" ─────────────────────────────────"));
4762
+ console.log();
4763
+ const patch = buildProjectUpdatePatch(input);
4752
4764
  try {
4753
4765
  const res = await updateProject(projectId, patch);
4754
4766
  console.log(pc.green(" ✓") + ` Updated ${pc.bold(res.data.name)} ${pc.dim(`(${res.data.id})`)}`);
4755
4767
  if (res.data.bundle_id) console.log(pc.dim(` Bundle ID: ${res.data.bundle_id}`));
4756
4768
  if (res.data.google_oauth_client_id) console.log(pc.dim(` Google OAuth client id: ${res.data.google_oauth_client_id}`));
4769
+ if (res.data.magic_link_redirect_url) console.log(pc.dim(` Magic-link callback: ${res.data.magic_link_redirect_url}`));
4770
+ if (res.data.auth_email_brand_name) console.log(pc.dim(` Auth email brand: ${res.data.auth_email_brand_name}`));
4757
4771
  console.log();
4758
4772
  } catch (err) {
4759
4773
  if (err instanceof ApiClientError && err.statusCode === 404) {
@@ -6602,7 +6616,12 @@ async function functionDomainsRemoveCommand(functionName, hostnameInput) {
6602
6616
  * server-side; the CLI never echoes plaintext back. The API returns a
6603
6617
  * `api_key_preview` (first-6 + last-4) for confirmation.
6604
6618
  */
6605
- const VALID_PROVIDERS = new Set(["anthropic", "openai"]);
6619
+ const VALID_PROVIDERS = new Set([
6620
+ "anthropic",
6621
+ "openai",
6622
+ "mistral",
6623
+ "gemini"
6624
+ ]);
6606
6625
  function assertValidProvider(name) {
6607
6626
  if (!VALID_PROVIDERS.has(name)) throw new Error(`Unknown AI provider '${name}'. Supported: ${[...VALID_PROVIDERS].join(", ")}.`);
6608
6627
  }
@@ -9761,7 +9780,7 @@ async function diffConfigCommand(file = DEFAULT_BUNDLE_FILE, options = {}) {
9761
9780
  //#endregion
9762
9781
  //#region src/index.ts
9763
9782
  const program = new Command();
9764
- program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.5");
9783
+ program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.7");
9765
9784
  program.option("--token <pat>", "Use a Personal Access Token for headless / CI / agent use (overrides ~/.amba/credentials.json + AMBA_PAT env)");
9766
9785
  program.hook("preAction", (thisCommand) => {
9767
9786
  setBearerOverride(resolveTokenSource({
@@ -9850,13 +9869,17 @@ projects.command("create").description("Create a new project").requiredOption("-
9850
9869
  platform: opts.platform
9851
9870
  }));
9852
9871
  });
9853
- projects.command("update <projectId>").description("Update mutable fields on a project").option("--name <name>", "Display name").option("--bundle-id <id>", "Bundle identifier (Apple Sign In audience)").option("--google-oauth-client-id <id>", "Google OAuth 2.0 client id (Google Sign In audience). Public identifier, not a secret.").option("--platform <platform>", "Platform: 'ios' | 'android' | 'all'").option("--environment <env>", "Environment label: 'development' | 'production'").action(async (projectId, opts) => {
9872
+ projects.command("update <projectId>").description("Update mutable fields on a project").option("--name <name>", "Display name").option("--bundle-id <id>", "Bundle identifier (Apple Sign In audience)").option("--google-oauth-client-id <id>", "Google OAuth 2.0 client id (Google Sign In audience). Public identifier, not a secret.").option("--platform <platform>", "Platform: 'ios' | 'android' | 'all'").option("--environment <env>", "Environment label: 'development' | 'production'").addOption(new Option("--magic-link-redirect-url <url>", "Exact HTTPS callback URL for magic-link sign-in (Amba appends the token query parameter)").conflicts("clearMagicLinkRedirectUrl")).addOption(new Option("--clear-magic-link-redirect-url", "Clear the project callback and restore the platform fallback").conflicts("magicLinkRedirectUrl")).addOption(new Option("--auth-email-brand-name <name>", "Brand shown in auth emails and sender attribution").conflicts("clearAuthEmailBrandName")).addOption(new Option("--clear-auth-email-brand-name", "Clear the auth-email brand and fall back to the project name").conflicts("authEmailBrandName")).action(async (projectId, opts) => {
9854
9873
  await runAction(() => projectsUpdateCommand(projectId, {
9855
9874
  name: opts.name,
9856
9875
  bundleId: opts.bundleId,
9857
9876
  googleOauthClientId: opts.googleOauthClientId,
9858
9877
  platform: opts.platform,
9859
- environment: opts.environment
9878
+ environment: opts.environment,
9879
+ magicLinkRedirectUrl: opts.magicLinkRedirectUrl,
9880
+ authEmailBrandName: opts.authEmailBrandName,
9881
+ clearMagicLinkRedirectUrl: opts.clearMagicLinkRedirectUrl,
9882
+ clearAuthEmailBrandName: opts.clearAuthEmailBrandName
9860
9883
  }));
9861
9884
  });
9862
9885
  projects.command("show <projectId>").description("Show full project details as JSON").action(async (projectId) => {
@@ -10156,7 +10179,7 @@ domains.command("buy <domain>").description("Buy a domain + connect it to a site
10156
10179
  domains.command("list").description("List domains this project has purchased through Amba").action(async () => {
10157
10180
  await runAction(domainsListCommand);
10158
10181
  });
10159
- const aiProviders = program.command("ai").description("AI gateway — manage provider keys + prompts").command("providers").description("Per-project provider key registration (Anthropic, OpenAI)");
10182
+ const aiProviders = program.command("ai").description("AI gateway — manage provider keys + prompts").command("providers").description("Per-project provider key registration (Anthropic, OpenAI, Mistral, Gemini)");
10160
10183
  aiProviders.command("add <provider>").description("Register an AI provider key. Plaintext stored securely server-side; a preview (first-6+last-4) is printed back. Use --from-stdin to keep the key out of shell history.").option("--key <value>", "Provider API key plaintext (alternative: --from-stdin)").option("--from-stdin", "Read the key from stdin instead of --key").action(async (provider, opts) => {
10161
10184
  await runAction(() => aiProvidersAddCommand(provider, opts));
10162
10185
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@layers/amba",
3
- "version": "4.1.5",
3
+ "version": "4.1.7",
4
4
  "description": "amba — agent-native backend-as-a-service. Functions, collections, storage, AI, email, queues, sites, and `amba ship` to take an Expo app live on the App Store + Google Play. `npx @layers/amba init` to start.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,8 +49,8 @@
49
49
  "tsdown": "^0.12.5",
50
50
  "typescript": "^5.8.3",
51
51
  "vitest": "^3.2.4",
52
- "@layers/amba-mcp": "4.0.9",
53
- "@layers/amba-shared": "4.0.4"
52
+ "@layers/amba-mcp": "4.0.10",
53
+ "@layers/amba-shared": "4.0.5"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsdown && tsc --emitDeclarationOnly",
@@ -9,8 +9,8 @@ There is no separate "identity provisioning" step — `auth` is the default surf
9
9
  | Tool | Purpose | Example args |
10
10
  | --- | --- | --- |
11
11
  | `amba_developer_me` | Verify the developer PAT and read the developer's profile. Pre-flight check before any provisioning. | `{}` |
12
- | `amba_projects_get` | Read a project's config (bundle id, OAuth client id, platform). | `{ project_id: "p_…" }` |
13
- | `amba_projects_update` | Set `bundle_id` (Apple audience) and `google_oauth_client_id` (Google audience). Required before Sign in with Apple / Google works. | `{ project_id, bundle_id: "com.example.fitness", google_oauth_client_id: "1234.apps.googleusercontent.com" }` |
12
+ | `amba_projects_get` | Read a project's config (social audiences, magic-link callback, auth-email brand, platform). | `{ project_id: "p_…" }` |
13
+ | `amba_projects_update` | Set social audiences plus the exact magic-link callback and auth-email brand. Pass null for an auth field to restore its fallback. | `{ project_id, magic_link_redirect_url: "https://app.example.com/auth/callback", auth_email_brand_name: "My App" }` |
14
14
  | `amba_users_list` | Browse end-users (app_users) of the project — useful as a smoke check after the first sign-in. | `{ project_id, limit: 20 }` |
15
15
  | `amba_users_get` | Fetch a single app_user by id. | `{ project_id, user_id }` |
16
16
  | `amba_users_bulk_update` | Set custom properties on many users at once (e.g. backfilling a `tier` property used for segmentation). | `{ project_id, user_ids: [...], properties: { tier: "trial" } }` |
@@ -18,7 +18,7 @@ There is no separate "identity provisioning" step — `auth` is the default surf
18
18
  | `amba_delete_api_key` / `amba_api_keys_delete` | Revoke a leaked key. | `{ project_id, api_key_id }` |
19
19
  | `amba_assign_role` / `amba_roles_assign` | Grant an RBAC role to an app_user (admin / moderator / etc.). | `{ project_id, user_id, role_id }` |
20
20
 
21
- There's no `amba_auth_*` namespace — auth is owned by the SDK on the client side, and there are no provisioning calls for it beyond setting the project's audience identifiers. If the user wants Apple/Google sign-in, the **mandatory** preflight is:
21
+ There's no `amba_auth_*` namespace — auth is owned by the SDK on the client side. Project-level routing, branding, and social audience configuration use `amba_projects_update`. If the user wants Apple/Google sign-in, the **mandatory** preflight is:
22
22
 
23
23
  ```
24
24
  mcp__amba__amba_projects_update({
@@ -30,6 +30,18 @@ mcp__amba__amba_projects_update({
30
30
 
31
31
  Without this, the server rejects identity tokens with `AUDIENCE_NOT_CONFIGURED` and the user thinks Amba is broken. If they don't know their bundle id, ask; if they don't have a Google OAuth client yet, tell them to create one at console.cloud.google.com and link it later.
32
32
 
33
+ For magic-link sign-in, configure the exact HTTPS callback before sending a production email:
34
+
35
+ ```
36
+ mcp__amba__amba_projects_update({
37
+ project_id: process.env.AMBA_PROJECT_ID,
38
+ magic_link_redirect_url: "https://app.example.com/auth/callback",
39
+ auth_email_brand_name: "Example App"
40
+ })
41
+ ```
42
+
43
+ Amba appends the one-time `token` query parameter to that exact URL. Auth-email copy uses the project brand; sends from the shared platform mailbox retain visible `via Amba` attribution. A pure customer-branded From address requires a separately verified sender domain.
44
+
33
45
  ## SDK init per stack
34
46
 
35
47
  ### Expo