@layers/amba 4.1.4 → 4.1.6
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 +42 -1
- package/dist/commands/functions.d.ts +4 -0
- package/dist/commands/projects.d.ts +9 -2
- package/dist/index.js +110 -17
- 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<{
|
|
@@ -295,6 +303,8 @@ export interface FunctionDeploymentRow {
|
|
|
295
303
|
/** Opt-in public flag (#17). True = X-Api-Key gate skipped for this
|
|
296
304
|
* function so external webhook senders can reach it. Default false. */
|
|
297
305
|
is_public?: boolean;
|
|
306
|
+
/** Explicit per-deployment opt-in. Default false. */
|
|
307
|
+
forward_authorization?: boolean;
|
|
298
308
|
}
|
|
299
309
|
/** Provider-neutral lifecycle for an exact hostname bound to one function. */
|
|
300
310
|
export interface FunctionDomain {
|
|
@@ -310,6 +320,9 @@ export interface FunctionDomain {
|
|
|
310
320
|
dns_note?: string | null;
|
|
311
321
|
cert_status: 'pending_validation' | 'pending_issuance' | 'pending_deployment' | 'active' | 'error';
|
|
312
322
|
ownership_status: 'pending' | 'active' | 'error';
|
|
323
|
+
/** Raw point-in-time upstream states returned by attach/refresh. */
|
|
324
|
+
provider_status?: string | null;
|
|
325
|
+
provider_cert_status?: string | null;
|
|
313
326
|
/** True only when certificate/ownership are active and routing reconciliation succeeded. */
|
|
314
327
|
live: boolean;
|
|
315
328
|
created_at: string;
|
|
@@ -383,10 +396,23 @@ export declare function deployFunctionViaApi(projectId: string, input: {
|
|
|
383
396
|
* HMAC inside the handler.
|
|
384
397
|
*/
|
|
385
398
|
public?: boolean;
|
|
399
|
+
/** Forward a verified client Authorization header to customer code. */
|
|
400
|
+
forward_authorization?: boolean;
|
|
386
401
|
}): Promise<DeployFunctionResult>;
|
|
387
402
|
export declare function listFunctionDeployments(projectId: string, options?: {
|
|
388
403
|
activeOnly?: boolean;
|
|
389
404
|
}): Promise<ApiListResponse<FunctionDeploymentRow>>;
|
|
405
|
+
export interface FunctionDeployStatus {
|
|
406
|
+
status: 'available' | 'scheduled' | 'paused';
|
|
407
|
+
available: boolean;
|
|
408
|
+
paused: boolean;
|
|
409
|
+
retry_after_seconds: number | null;
|
|
410
|
+
retry_after_is_poll_interval: boolean;
|
|
411
|
+
maintenance_start_at: string | null;
|
|
412
|
+
estimated_resume_at: string | null;
|
|
413
|
+
reason: string | null;
|
|
414
|
+
}
|
|
415
|
+
export declare function getFunctionDeployStatus(projectId: string): Promise<ApiResponse<FunctionDeployStatus>>;
|
|
390
416
|
/** Attach one exact hostname to one deployed function. */
|
|
391
417
|
export declare function attachFunctionDomainViaApi(projectId: string, functionName: string, hostname: string): Promise<ApiResponse<AttachedFunctionDomain>>;
|
|
392
418
|
/** List provider-neutral ownership/certificate status for a function's hostnames. */
|
|
@@ -433,6 +459,17 @@ export declare function scheduleFunction(projectId: string, input: {
|
|
|
433
459
|
}): Promise<ApiResponse<{
|
|
434
460
|
schedule_id: string;
|
|
435
461
|
}>>;
|
|
462
|
+
export interface FunctionScheduleRow {
|
|
463
|
+
schedule_id: string;
|
|
464
|
+
function_name: string;
|
|
465
|
+
enriched: boolean;
|
|
466
|
+
cron: string | null;
|
|
467
|
+
timezone: string | null;
|
|
468
|
+
next_fire_at: string | null;
|
|
469
|
+
last_fire_at: string | null;
|
|
470
|
+
paused: boolean | null;
|
|
471
|
+
}
|
|
472
|
+
export declare function listFunctionSchedules(projectId: string): Promise<ApiResponse<FunctionScheduleRow[]>>;
|
|
436
473
|
export interface FunctionLogEvent {
|
|
437
474
|
/** Sanitized log row — see `r2-log-reader.ts:SanitizedLogEvent`. Field-shape stable. */
|
|
438
475
|
Event?: {
|
|
@@ -645,7 +682,7 @@ export declare function dropCollection(projectId: string, name: string, confirm:
|
|
|
645
682
|
deleted: boolean;
|
|
646
683
|
version: number;
|
|
647
684
|
}>>;
|
|
648
|
-
export type AiProviderName = 'anthropic' | 'openai';
|
|
685
|
+
export type AiProviderName = 'anthropic' | 'openai' | 'mistral' | 'gemini';
|
|
649
686
|
export interface AiProviderRow {
|
|
650
687
|
name: AiProviderName;
|
|
651
688
|
/** Whether an API key is currently configured for this provider. */
|
|
@@ -680,6 +717,9 @@ export interface SiteDomainRow {
|
|
|
680
717
|
provider_hostname_id: string | null;
|
|
681
718
|
cert_status: 'pending_validation' | 'pending_issuance' | 'pending_deployment' | 'active' | 'error';
|
|
682
719
|
ownership_status: 'pending' | 'active' | 'error';
|
|
720
|
+
/** Raw point-in-time upstream values returned by attach/refresh. */
|
|
721
|
+
provider_status?: string | null;
|
|
722
|
+
provider_cert_status?: string | null;
|
|
683
723
|
/** Edge Worker route reconciliation. */
|
|
684
724
|
route_status: 'pending' | 'active' | 'error';
|
|
685
725
|
/** Host-to-site KV reconciliation. */
|
|
@@ -740,6 +780,7 @@ export declare function archiveSite(projectId: string, name: string, confirm: st
|
|
|
740
780
|
export interface DeleteCascadeSummary {
|
|
741
781
|
domains_removed?: number;
|
|
742
782
|
site_runtime_removed?: boolean;
|
|
783
|
+
function_schedule_removed?: boolean;
|
|
743
784
|
runtime_script_removed?: boolean;
|
|
744
785
|
function_deployments_marked_disabled?: number;
|
|
745
786
|
}
|
|
@@ -42,15 +42,19 @@ export interface DeployOptions {
|
|
|
42
42
|
* the handler — public skips the credential gate only.
|
|
43
43
|
*/
|
|
44
44
|
public?: boolean;
|
|
45
|
+
/** Opt in to forwarding verified client Authorization to this function. */
|
|
46
|
+
forwardAuthorization?: boolean;
|
|
45
47
|
}
|
|
46
48
|
export declare function functionsDeployCommand(target: string, options?: DeployOptions): Promise<void>;
|
|
47
49
|
export declare function functionsListCommand(): Promise<void>;
|
|
50
|
+
export declare function functionsDeployStatusCommand(): Promise<void>;
|
|
48
51
|
export declare function functionsDeleteCommand(name: string, options?: {
|
|
49
52
|
confirm?: string;
|
|
50
53
|
}): Promise<void>;
|
|
51
54
|
export declare function functionsScheduleCommand(name: string, cron: string, options?: {
|
|
52
55
|
tz?: string;
|
|
53
56
|
}): Promise<void>;
|
|
57
|
+
export declare function functionsSchedulesCommand(): Promise<void>;
|
|
54
58
|
export interface DevOptions {
|
|
55
59
|
/** Local port. Default 8787 (overridable per project). */
|
|
56
60
|
port?: number;
|
|
@@ -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";
|
|
@@ -1018,7 +1018,8 @@ async function deployFunctionViaApi(projectId, input) {
|
|
|
1018
1018
|
const metadata = {
|
|
1019
1019
|
name: input.name,
|
|
1020
1020
|
...input.rate_limit !== void 0 ? { rate_limit: input.rate_limit } : {},
|
|
1021
|
-
...input.public === true ? { public: true } : {}
|
|
1021
|
+
...input.public === true ? { public: true } : {},
|
|
1022
|
+
...input.forward_authorization === true ? { forward_authorization: true } : {}
|
|
1022
1023
|
};
|
|
1023
1024
|
fd.set("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }), "metadata.json");
|
|
1024
1025
|
return request("POST", `/projects/${projectId}/functions/deploy`, fd);
|
|
@@ -1026,6 +1027,9 @@ async function deployFunctionViaApi(projectId, input) {
|
|
|
1026
1027
|
async function listFunctionDeployments(projectId, options = {}) {
|
|
1027
1028
|
return request("GET", `/projects/${projectId}/functions/deployments${options.activeOnly ? "?active=1" : ""}`);
|
|
1028
1029
|
}
|
|
1030
|
+
async function getFunctionDeployStatus(projectId) {
|
|
1031
|
+
return request("GET", `/projects/${projectId}/functions/deploy-status`);
|
|
1032
|
+
}
|
|
1029
1033
|
function functionDomainsPath(projectId, functionName) {
|
|
1030
1034
|
return `/projects/${encodeURIComponent(projectId)}/functions/${encodeURIComponent(functionName)}/domains`;
|
|
1031
1035
|
}
|
|
@@ -1048,6 +1052,9 @@ async function removeFunctionDomainViaApi(projectId, functionName, hostname) {
|
|
|
1048
1052
|
async function scheduleFunction(projectId, input) {
|
|
1049
1053
|
return request("POST", `/projects/${projectId}/functions/schedules`, input);
|
|
1050
1054
|
}
|
|
1055
|
+
async function listFunctionSchedules(projectId) {
|
|
1056
|
+
return request("GET", `/projects/${projectId}/functions/schedules`);
|
|
1057
|
+
}
|
|
1051
1058
|
async function getFunctionLogs(projectId, functionName, options = {}) {
|
|
1052
1059
|
const params = new URLSearchParams();
|
|
1053
1060
|
if (options.since) params.set("since", options.since);
|
|
@@ -4731,22 +4738,34 @@ async function projectsCreateCommand(input) {
|
|
|
4731
4738
|
handleError$2(err);
|
|
4732
4739
|
}
|
|
4733
4740
|
}
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
console.log(pc.dim(" ─────────────────────────────────"));
|
|
4738
|
-
console.log();
|
|
4741
|
+
function buildProjectUpdatePatch(input) {
|
|
4742
|
+
if (input.magicLinkRedirectUrl !== void 0 && input.clearMagicLinkRedirectUrl) throw new Error("--magic-link-redirect-url conflicts with --clear-magic-link-redirect-url");
|
|
4743
|
+
if (input.authEmailBrandName !== void 0 && input.clearAuthEmailBrandName) throw new Error("--auth-email-brand-name conflicts with --clear-auth-email-brand-name");
|
|
4739
4744
|
const patch = {};
|
|
4740
4745
|
if (input.name !== void 0) patch.name = input.name;
|
|
4741
4746
|
if (input.bundleId !== void 0) patch.bundle_id = input.bundleId;
|
|
4742
4747
|
if (input.googleOauthClientId !== void 0) patch.google_oauth_client_id = input.googleOauthClientId;
|
|
4743
4748
|
if (input.platform !== void 0) patch.platform = input.platform;
|
|
4744
4749
|
if (input.environment !== void 0) patch.environment = input.environment;
|
|
4750
|
+
if (input.magicLinkRedirectUrl !== void 0) patch.magic_link_redirect_url = input.magicLinkRedirectUrl;
|
|
4751
|
+
if (input.clearMagicLinkRedirectUrl) patch.magic_link_redirect_url = null;
|
|
4752
|
+
if (input.authEmailBrandName !== void 0) patch.auth_email_brand_name = input.authEmailBrandName;
|
|
4753
|
+
if (input.clearAuthEmailBrandName) patch.auth_email_brand_name = null;
|
|
4754
|
+
return patch;
|
|
4755
|
+
}
|
|
4756
|
+
async function projectsUpdateCommand(projectId, input) {
|
|
4757
|
+
console.log();
|
|
4758
|
+
console.log(pc.bold(` amba projects update ${projectId}`));
|
|
4759
|
+
console.log(pc.dim(" ─────────────────────────────────"));
|
|
4760
|
+
console.log();
|
|
4761
|
+
const patch = buildProjectUpdatePatch(input);
|
|
4745
4762
|
try {
|
|
4746
4763
|
const res = await updateProject(projectId, patch);
|
|
4747
4764
|
console.log(pc.green(" ✓") + ` Updated ${pc.bold(res.data.name)} ${pc.dim(`(${res.data.id})`)}`);
|
|
4748
4765
|
if (res.data.bundle_id) console.log(pc.dim(` Bundle ID: ${res.data.bundle_id}`));
|
|
4749
4766
|
if (res.data.google_oauth_client_id) console.log(pc.dim(` Google OAuth client id: ${res.data.google_oauth_client_id}`));
|
|
4767
|
+
if (res.data.magic_link_redirect_url) console.log(pc.dim(` Magic-link callback: ${res.data.magic_link_redirect_url}`));
|
|
4768
|
+
if (res.data.auth_email_brand_name) console.log(pc.dim(` Auth email brand: ${res.data.auth_email_brand_name}`));
|
|
4750
4769
|
console.log();
|
|
4751
4770
|
} catch (err) {
|
|
4752
4771
|
if (err instanceof ApiClientError && err.statusCode === 404) {
|
|
@@ -5784,6 +5803,16 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5784
5803
|
console.log(pc.bold(` amba functions deploy ${pc.cyan(functionName)}`));
|
|
5785
5804
|
console.log(pc.dim(" ─────────────────────────────────"));
|
|
5786
5805
|
console.log();
|
|
5806
|
+
if (!options.dryRun) {
|
|
5807
|
+
let deployStatus = null;
|
|
5808
|
+
try {
|
|
5809
|
+
deployStatus = (await getFunctionDeployStatus(projectId)).data;
|
|
5810
|
+
} catch (err) {
|
|
5811
|
+
if (!(err instanceof ApiClientError && err.statusCode === 404)) throw err;
|
|
5812
|
+
}
|
|
5813
|
+
if (deployStatus && !deployStatus.available) throw new Error(formatDeployUnavailableMessage(deployStatus));
|
|
5814
|
+
if (deployStatus?.status === "scheduled") console.log(pc.yellow(" !") + ` Maintenance is scheduled for ${deployStatus.maintenance_start_at ?? "a forthcoming window"}.`);
|
|
5815
|
+
}
|
|
5787
5816
|
console.log(pc.dim(` Bundling ${entryPoint}…`));
|
|
5788
5817
|
const bundle = await bundleFunction({ entryPoint });
|
|
5789
5818
|
printBundleReport(bundle);
|
|
@@ -5798,13 +5827,20 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5798
5827
|
name: functionName,
|
|
5799
5828
|
bundleCode: bundle.code,
|
|
5800
5829
|
rate_limit: rateLimit,
|
|
5801
|
-
public: options.public === true ? true : void 0
|
|
5830
|
+
public: options.public === true ? true : void 0,
|
|
5831
|
+
forward_authorization: options.forwardAuthorization === true ? true : void 0
|
|
5802
5832
|
});
|
|
5803
5833
|
} catch (err) {
|
|
5804
5834
|
if (err instanceof ApiClientError && err.statusCode === 503) {
|
|
5835
|
+
try {
|
|
5836
|
+
const status = (await getFunctionDeployStatus(projectId)).data;
|
|
5837
|
+
if (!status.available) throw new Error(formatDeployUnavailableMessage(status));
|
|
5838
|
+
} catch (statusErr) {
|
|
5839
|
+
if (!(statusErr instanceof ApiClientError)) throw statusErr;
|
|
5840
|
+
}
|
|
5805
5841
|
const base = err.message.trimEnd();
|
|
5806
5842
|
const sep = /[.!?]$/.test(base) ? " " : ". ";
|
|
5807
|
-
throw new Error(`${base}${sep}
|
|
5843
|
+
throw new Error(`${base}${sep}Check \`amba functions deploy-status\` before retrying.`);
|
|
5808
5844
|
}
|
|
5809
5845
|
throw err;
|
|
5810
5846
|
}
|
|
@@ -5812,8 +5848,15 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5812
5848
|
console.log(pc.green(" ✓") + ` URL: ${pc.underline(result.fn_url)}`);
|
|
5813
5849
|
if (rateLimit) console.log(pc.dim(` Rate limit: ${rateLimit.max} per ${rateLimit.window} (key=${rateLimit.key}) — enforced pre-dispatch`));
|
|
5814
5850
|
if (options.public === true) console.log(pc.yellow(" ! Public") + pc.dim(" — no X-Api-Key required. Verify the webhook signature inside your handler."));
|
|
5851
|
+
if (options.forwardAuthorization === true) console.log(pc.yellow(" ! Authorization forwarding enabled") + pc.dim(" — the verified client Bearer is visible to this function; X-Api-Key is never forwarded."));
|
|
5815
5852
|
console.log();
|
|
5816
5853
|
}
|
|
5854
|
+
function formatDeployUnavailableMessage(status) {
|
|
5855
|
+
const reason = status.reason ? ` (${status.reason})` : "";
|
|
5856
|
+
if (status.estimated_resume_at) return `Function deployments are paused${reason}. Estimated resume: ${status.estimated_resume_at}.`;
|
|
5857
|
+
if (status.retry_after_seconds !== null) return `Function deployments are paused${reason}. ${status.retry_after_is_poll_interval ? "Poll deploy-status again" : "Retry"} in ${status.retry_after_seconds}s; no end time is known yet.`;
|
|
5858
|
+
return `Function deployments are paused${reason}. Check \`amba functions deploy-status\` before retrying.`;
|
|
5859
|
+
}
|
|
5817
5860
|
async function functionsListCommand() {
|
|
5818
5861
|
const res = await listFunctionDeployments((await loadProjectConfig()).projectId, { activeOnly: true });
|
|
5819
5862
|
console.log();
|
|
@@ -5825,12 +5868,25 @@ async function functionsListCommand() {
|
|
|
5825
5868
|
for (const d of res.data) console.log(` ${pc.bold(d.name)} ${pc.dim("v" + d.version)} ` + pc.dim(`sha=${d.bundle_sha.slice(0, 12)}… ${d.created_at}`));
|
|
5826
5869
|
console.log();
|
|
5827
5870
|
}
|
|
5871
|
+
async function functionsDeployStatusCommand() {
|
|
5872
|
+
const res = await getFunctionDeployStatus((await loadProjectConfig()).projectId);
|
|
5873
|
+
if (res.data.available) {
|
|
5874
|
+
console.log(pc.green(" ✓") + " Function deployments are available.");
|
|
5875
|
+
if (res.data.status === "scheduled") {
|
|
5876
|
+
console.log(pc.yellow(" !") + ` Maintenance is scheduled for ${res.data.maintenance_start_at ?? "a forthcoming window"}.`);
|
|
5877
|
+
if (res.data.estimated_resume_at) console.log(pc.dim(` Estimated resume: ${res.data.estimated_resume_at}.`));
|
|
5878
|
+
}
|
|
5879
|
+
return;
|
|
5880
|
+
}
|
|
5881
|
+
console.log(pc.yellow(" !") + " Function deployments are paused for platform maintenance.");
|
|
5882
|
+
if (res.data.retry_after_seconds !== null) console.log(pc.dim(res.data.retry_after_is_poll_interval ? ` Poll deploy-status again in ${res.data.retry_after_seconds}s; no end time is known yet.` : ` Estimated resume: ${res.data.estimated_resume_at} (Retry-After ${res.data.retry_after_seconds}s).`));
|
|
5883
|
+
}
|
|
5828
5884
|
async function functionsDeleteCommand(name, options = {}) {
|
|
5829
5885
|
validateFunctionName$1(name);
|
|
5830
5886
|
if (!options.confirm || options.confirm !== name) throw new Error(`Delete is destructive. Pass --confirm ${name} to proceed. Clients calling this function will start 404'ing immediately.`);
|
|
5831
5887
|
const cascade = (await deleteFunctionViaApi((await loadProjectConfig()).projectId, name, { confirm: name })).data.cascade;
|
|
5832
5888
|
console.log(pc.green(" ✓") + ` Deleted ${pc.bold(name)}.`);
|
|
5833
|
-
console.log(pc.dim(` Cascade: runtime_script_removed=${cascade.runtime_script_removed ?? false}, function_deployments_marked_disabled=${cascade.function_deployments_marked_disabled ?? 0}`));
|
|
5889
|
+
console.log(pc.dim(` Cascade: function_schedule_removed=${cascade.function_schedule_removed ?? false}, runtime_script_removed=${cascade.runtime_script_removed ?? false}, function_deployments_marked_disabled=${cascade.function_deployments_marked_disabled ?? 0}`));
|
|
5834
5890
|
}
|
|
5835
5891
|
async function functionsScheduleCommand(name, cron, options = {}) {
|
|
5836
5892
|
const projectConfig = await loadProjectConfig();
|
|
@@ -5846,6 +5902,25 @@ async function functionsScheduleCommand(name, cron, options = {}) {
|
|
|
5846
5902
|
console.log(pc.dim(` schedule_id: ${res.data.schedule_id}`));
|
|
5847
5903
|
console.log();
|
|
5848
5904
|
}
|
|
5905
|
+
async function functionsSchedulesCommand() {
|
|
5906
|
+
const res = await listFunctionSchedules((await loadProjectConfig()).projectId);
|
|
5907
|
+
console.log();
|
|
5908
|
+
if (res.data.length === 0) {
|
|
5909
|
+
console.log(pc.dim(" No function schedules registered."));
|
|
5910
|
+
console.log();
|
|
5911
|
+
return;
|
|
5912
|
+
}
|
|
5913
|
+
for (const schedule of res.data) {
|
|
5914
|
+
if (!schedule.enriched) {
|
|
5915
|
+
console.log(`${pc.bold(schedule.function_name)} ${pc.yellow("metadata unavailable")}`);
|
|
5916
|
+
continue;
|
|
5917
|
+
}
|
|
5918
|
+
const state = schedule.paused ? pc.yellow("paused") : pc.green("active");
|
|
5919
|
+
console.log(` ${pc.bold(schedule.function_name)} ${pc.cyan(schedule.cron ?? "?")} ${pc.dim(schedule.timezone ?? "UTC")} ${state}`);
|
|
5920
|
+
console.log(pc.dim(` last=${schedule.last_fire_at ?? "never"} next=${schedule.next_fire_at ?? "unknown"}`));
|
|
5921
|
+
}
|
|
5922
|
+
console.log();
|
|
5923
|
+
}
|
|
5849
5924
|
/**
|
|
5850
5925
|
* `amba functions dev <file>` — run the function locally with file-change
|
|
5851
5926
|
* hot reload.
|
|
@@ -6420,7 +6495,10 @@ function printEvents(events, asJson) {
|
|
|
6420
6495
|
console.log(`${pc.dim(ts)} ${pc.cyan(script)} ${level} ${msg}`);
|
|
6421
6496
|
}
|
|
6422
6497
|
if (e.Exceptions && e.Exceptions.length > 0) for (const ex of e.Exceptions) console.log(`${pc.dim(ts)} ${pc.cyan(script)} ${pc.red("[exception]")} ${ex.Name ?? "Error"}: ${ex.Message ?? ""}`);
|
|
6423
|
-
if ((!e.Logs || e.Logs.length === 0) && (!e.Exceptions || e.Exceptions.length === 0) && e.Outcome
|
|
6498
|
+
if ((!e.Logs || e.Logs.length === 0) && (!e.Exceptions || e.Exceptions.length === 0) && e.Outcome) {
|
|
6499
|
+
const outcome = e.Outcome === "ok" ? pc.green("[outcome]") : pc.yellow("[outcome]");
|
|
6500
|
+
console.log(`${pc.dim(ts)} ${pc.cyan(script)} ${outcome} ${e.Outcome}`);
|
|
6501
|
+
}
|
|
6424
6502
|
}
|
|
6425
6503
|
}
|
|
6426
6504
|
function renderLevel(level) {
|
|
@@ -6536,7 +6614,12 @@ async function functionDomainsRemoveCommand(functionName, hostnameInput) {
|
|
|
6536
6614
|
* server-side; the CLI never echoes plaintext back. The API returns a
|
|
6537
6615
|
* `api_key_preview` (first-6 + last-4) for confirmation.
|
|
6538
6616
|
*/
|
|
6539
|
-
const VALID_PROVIDERS = new Set([
|
|
6617
|
+
const VALID_PROVIDERS = new Set([
|
|
6618
|
+
"anthropic",
|
|
6619
|
+
"openai",
|
|
6620
|
+
"mistral",
|
|
6621
|
+
"gemini"
|
|
6622
|
+
]);
|
|
6540
6623
|
function assertValidProvider(name) {
|
|
6541
6624
|
if (!VALID_PROVIDERS.has(name)) throw new Error(`Unknown AI provider '${name}'. Supported: ${[...VALID_PROVIDERS].join(", ")}.`);
|
|
6542
6625
|
}
|
|
@@ -9695,7 +9778,7 @@ async function diffConfigCommand(file = DEFAULT_BUNDLE_FILE, options = {}) {
|
|
|
9695
9778
|
//#endregion
|
|
9696
9779
|
//#region src/index.ts
|
|
9697
9780
|
const program = new Command();
|
|
9698
|
-
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.
|
|
9781
|
+
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.6");
|
|
9699
9782
|
program.option("--token <pat>", "Use a Personal Access Token for headless / CI / agent use (overrides ~/.amba/credentials.json + AMBA_PAT env)");
|
|
9700
9783
|
program.hook("preAction", (thisCommand) => {
|
|
9701
9784
|
setBearerOverride(resolveTokenSource({
|
|
@@ -9784,13 +9867,17 @@ projects.command("create").description("Create a new project").requiredOption("-
|
|
|
9784
9867
|
platform: opts.platform
|
|
9785
9868
|
}));
|
|
9786
9869
|
});
|
|
9787
|
-
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) => {
|
|
9870
|
+
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) => {
|
|
9788
9871
|
await runAction(() => projectsUpdateCommand(projectId, {
|
|
9789
9872
|
name: opts.name,
|
|
9790
9873
|
bundleId: opts.bundleId,
|
|
9791
9874
|
googleOauthClientId: opts.googleOauthClientId,
|
|
9792
9875
|
platform: opts.platform,
|
|
9793
|
-
environment: opts.environment
|
|
9876
|
+
environment: opts.environment,
|
|
9877
|
+
magicLinkRedirectUrl: opts.magicLinkRedirectUrl,
|
|
9878
|
+
authEmailBrandName: opts.authEmailBrandName,
|
|
9879
|
+
clearMagicLinkRedirectUrl: opts.clearMagicLinkRedirectUrl,
|
|
9880
|
+
clearAuthEmailBrandName: opts.clearAuthEmailBrandName
|
|
9794
9881
|
}));
|
|
9795
9882
|
});
|
|
9796
9883
|
projects.command("show <projectId>").description("Show full project details as JSON").action(async (projectId) => {
|
|
@@ -9837,18 +9924,24 @@ program.command("schema").description("Schema export commands").command("export"
|
|
|
9837
9924
|
}));
|
|
9838
9925
|
});
|
|
9839
9926
|
const functions = program.command("functions").description("Customer serverless functions deployed to the edge");
|
|
9840
|
-
functions.command("deploy <target>").description("Bundle a function (a single file OR a directory) and deploy to the dispatch namespace. A directory resolves its entry by convention (index.ts/js/mjs) or via --entry.").option("--name <name>", "Function name (default: file name without extension, or the directory leaf name)").option("--entry <file>", "Entry file when <target> is a directory (relative to it). Default: index.ts/js/mjs.").option("--dry-run", "Bundle and report size without uploading").option("--rate-limit-window <duration>", "Rate-limit window: 60s | 5m | 1h").option("--rate-limit-max <int>", "Rate-limit max requests per window", (v) => Number.parseInt(v, 10)).option("--rate-limit-key <kind>", "Rate-limit bucket key: user_id | ip").option("--public", "Deploy WITHOUT the X-Api-Key gate so external webhook senders can call it. You MUST verify the webhook signature in your handler. Default: private.").action(async (target, opts) => {
|
|
9927
|
+
functions.command("deploy <target>").description("Bundle a function (a single file OR a directory) and deploy to the dispatch namespace. A directory resolves its entry by convention (index.ts/js/mjs) or via --entry.").option("--name <name>", "Function name (default: file name without extension, or the directory leaf name)").option("--entry <file>", "Entry file when <target> is a directory (relative to it). Default: index.ts/js/mjs.").option("--dry-run", "Bundle and report size without uploading").option("--rate-limit-window <duration>", "Rate-limit window: 60s | 5m | 1h").option("--rate-limit-max <int>", "Rate-limit max requests per window", (v) => Number.parseInt(v, 10)).option("--rate-limit-key <kind>", "Rate-limit bucket key: user_id | ip").option("--public", "Deploy WITHOUT the X-Api-Key gate so external webhook senders can call it. You MUST verify the webhook signature in your handler. Default: private.").option("--forward-authorization", "Forward a verified client Authorization Bearer to this function. X-Api-Key and internal trigger credentials remain stripped.").action(async (target, opts) => {
|
|
9841
9928
|
await runAction(() => functionsDeployCommand(target, opts));
|
|
9842
9929
|
});
|
|
9843
9930
|
functions.command("list").description("List active functions for the current project").action(async () => {
|
|
9844
9931
|
await runAction(functionsListCommand);
|
|
9845
9932
|
});
|
|
9933
|
+
functions.command("deploy-status").description("Show whether function deployments are available or paused for maintenance").action(async () => {
|
|
9934
|
+
await runAction(functionsDeployStatusCommand);
|
|
9935
|
+
});
|
|
9846
9936
|
functions.command("delete <name>").description("Disable + remove a function from the dispatch namespace (destructive)").option("--confirm <name>", "Pass the function name to confirm deletion").action(async (name, opts) => {
|
|
9847
9937
|
await runAction(() => functionsDeleteCommand(name, { confirm: opts.confirm }));
|
|
9848
9938
|
});
|
|
9849
9939
|
functions.command("schedule <name> <cron>").description("Register a cron schedule that invokes a deployed function").option("--tz <iana>", "IANA timezone for the schedule (default: UTC)").action(async (name, cron, opts) => {
|
|
9850
9940
|
await runAction(() => functionsScheduleCommand(name, cron, opts));
|
|
9851
9941
|
});
|
|
9942
|
+
functions.command("schedules").description("List function schedules with cron, timezone, state, and last/next fire times").action(async () => {
|
|
9943
|
+
await runAction(functionsSchedulesCommand);
|
|
9944
|
+
});
|
|
9852
9945
|
functions.command("dev <file>").description("Run a local dev server for your function with hot reload on file changes").option("--port <n>", "Port to listen on (default 8787)", (v) => parseInt(v, 10)).option("--no-watch", "Disable file-change hot reload").action(async (file, opts) => {
|
|
9853
9946
|
await runAction(() => functionsDevCommand(file, {
|
|
9854
9947
|
port: opts.port,
|
|
@@ -10084,7 +10177,7 @@ domains.command("buy <domain>").description("Buy a domain + connect it to a site
|
|
|
10084
10177
|
domains.command("list").description("List domains this project has purchased through Amba").action(async () => {
|
|
10085
10178
|
await runAction(domainsListCommand);
|
|
10086
10179
|
});
|
|
10087
|
-
const aiProviders = program.command("ai").description("AI gateway — manage provider keys + prompts").command("providers").description("Per-project provider key registration (Anthropic, OpenAI)");
|
|
10180
|
+
const aiProviders = program.command("ai").description("AI gateway — manage provider keys + prompts").command("providers").description("Per-project provider key registration (Anthropic, OpenAI, Mistral, Gemini)");
|
|
10088
10181
|
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) => {
|
|
10089
10182
|
await runAction(() => aiProvidersAddCommand(provider, opts));
|
|
10090
10183
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layers/amba",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.6",
|
|
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
|