@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.
- package/dist/api-client.d.ts +9 -1
- package/dist/commands/projects.d.ts +9 -2
- package/dist/index.js +44 -21
- package/package.json +3 -3
- package/skill-bundle/references/identity.md +15 -3
package/dist/api-client.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
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([
|
|
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.
|
|
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.
|
|
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.
|
|
53
|
-
"@layers/amba-shared": "4.0.
|
|
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 (
|
|
13
|
-
| `amba_projects_update` | Set
|
|
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
|
|
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
|