@layers/amba 4.1.1 → 4.1.3
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 +95 -14
- package/dist/bundle.d.ts +2 -2
- package/dist/commands/function-domains.d.ts +5 -0
- package/dist/commands/sites.d.ts +3 -0
- package/dist/index.js +203 -30
- package/package.json +3 -3
- package/skill-bundle/SKILL.md +1 -1
- package/skill-bundle/references/infrastructure.md +9 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -23,6 +23,26 @@ export interface ProjectSummary {
|
|
|
23
23
|
google_oauth_client_id?: string | null;
|
|
24
24
|
status?: string;
|
|
25
25
|
created_at?: string;
|
|
26
|
+
/** Persisted subscription/accounting tier. */
|
|
27
|
+
tier?: 'free' | 'pro' | 'scale' | 'enterprise';
|
|
28
|
+
/** Runtime entitlement tier; differs from `tier` for comped projects. */
|
|
29
|
+
effective_tier?: 'free' | 'pro' | 'scale' | 'enterprise';
|
|
30
|
+
comped?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface CreatedProject extends ProjectSummary {
|
|
33
|
+
api_keys?: {
|
|
34
|
+
client: {
|
|
35
|
+
key: string;
|
|
36
|
+
prefix?: string;
|
|
37
|
+
type?: 'client';
|
|
38
|
+
};
|
|
39
|
+
server: {
|
|
40
|
+
key: string;
|
|
41
|
+
prefix?: string;
|
|
42
|
+
type?: 'server';
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
provisioning_status?: string;
|
|
26
46
|
}
|
|
27
47
|
export declare function listProjects(): Promise<ApiListResponse<ProjectSummary>>;
|
|
28
48
|
export declare function createProject(input: {
|
|
@@ -41,12 +61,7 @@ export declare function createProject(input: {
|
|
|
41
61
|
* bootstrap. Defaults to `'production'` when omitted.
|
|
42
62
|
*/
|
|
43
63
|
environment?: 'development' | 'production';
|
|
44
|
-
}): Promise<ApiResponse<
|
|
45
|
-
id: string;
|
|
46
|
-
name: string;
|
|
47
|
-
platform: string;
|
|
48
|
-
environment?: string;
|
|
49
|
-
}>>;
|
|
64
|
+
}): Promise<ApiResponse<CreatedProject>>;
|
|
50
65
|
/**
|
|
51
66
|
* PATCH /admin/projects/:projectId — update mutable fields. Server
|
|
52
67
|
* silently ignores unknown keys; we filter to the documented allow-list
|
|
@@ -281,6 +296,48 @@ export interface FunctionDeploymentRow {
|
|
|
281
296
|
* function so external webhook senders can reach it. Default false. */
|
|
282
297
|
is_public?: boolean;
|
|
283
298
|
}
|
|
299
|
+
/** Provider-neutral lifecycle for an exact hostname bound to one function. */
|
|
300
|
+
export interface FunctionDomain {
|
|
301
|
+
id: string;
|
|
302
|
+
project_id: string;
|
|
303
|
+
function_name: string;
|
|
304
|
+
hostname: string;
|
|
305
|
+
cert_status: 'pending_validation' | 'pending_issuance' | 'pending_deployment' | 'active' | 'error';
|
|
306
|
+
ownership_status: 'pending' | 'active' | 'error';
|
|
307
|
+
/** True only when certificate/ownership are active and routing reconciliation succeeded. */
|
|
308
|
+
live: boolean;
|
|
309
|
+
created_at: string;
|
|
310
|
+
updated_at: string;
|
|
311
|
+
}
|
|
312
|
+
export interface FunctionDomainValidationRecord {
|
|
313
|
+
txt_name?: string;
|
|
314
|
+
txt_value?: string;
|
|
315
|
+
http_url?: string;
|
|
316
|
+
http_body?: string;
|
|
317
|
+
}
|
|
318
|
+
export interface FunctionDomainOwnershipVerification {
|
|
319
|
+
type?: string;
|
|
320
|
+
name?: string;
|
|
321
|
+
value?: string;
|
|
322
|
+
}
|
|
323
|
+
/** Attach always returns DNS instructions; refresh may add them when available. */
|
|
324
|
+
export interface AttachedFunctionDomain extends FunctionDomain {
|
|
325
|
+
dns_target: string;
|
|
326
|
+
/** Additive status when Amba controls the purchased domain's DNS zone. */
|
|
327
|
+
dns_autoconfigured?: boolean;
|
|
328
|
+
dns_records_published?: number;
|
|
329
|
+
/** True when automatic DNS publication failed and refresh should be retried. */
|
|
330
|
+
dns_error?: boolean;
|
|
331
|
+
ssl_validation: FunctionDomainValidationRecord[];
|
|
332
|
+
ownership_verification: FunctionDomainOwnershipVerification | null;
|
|
333
|
+
ownership_verification_http: {
|
|
334
|
+
http_url?: string;
|
|
335
|
+
http_body?: string;
|
|
336
|
+
} | null;
|
|
337
|
+
}
|
|
338
|
+
export type RefreshedFunctionDomain = FunctionDomain & Partial<Pick<AttachedFunctionDomain, 'dns_target' | 'dns_autoconfigured' | 'dns_records_published' | 'dns_error' | 'ssl_validation' | 'ownership_verification' | 'ownership_verification_http'>> & {
|
|
339
|
+
cleanup_pending?: boolean;
|
|
340
|
+
};
|
|
284
341
|
export declare function recordFunctionDeployment(projectId: string, input: {
|
|
285
342
|
name: string;
|
|
286
343
|
runtime_script_name: string;
|
|
@@ -324,6 +381,17 @@ export declare function deployFunctionViaApi(projectId: string, input: {
|
|
|
324
381
|
export declare function listFunctionDeployments(projectId: string, options?: {
|
|
325
382
|
activeOnly?: boolean;
|
|
326
383
|
}): Promise<ApiListResponse<FunctionDeploymentRow>>;
|
|
384
|
+
/** Attach one exact hostname to one deployed function. */
|
|
385
|
+
export declare function attachFunctionDomainViaApi(projectId: string, functionName: string, hostname: string): Promise<ApiResponse<AttachedFunctionDomain>>;
|
|
386
|
+
/** List provider-neutral ownership/certificate status for a function's hostnames. */
|
|
387
|
+
export declare function listFunctionDomainsViaApi(projectId: string, functionName: string): Promise<ApiListResponse<FunctionDomain>>;
|
|
388
|
+
/** Re-poll DNS ownership and certificate state for one exact hostname. */
|
|
389
|
+
export declare function refreshFunctionDomainViaApi(projectId: string, functionName: string, hostname: string): Promise<ApiResponse<RefreshedFunctionDomain>>;
|
|
390
|
+
/** Detach one exact hostname from a function. */
|
|
391
|
+
export declare function removeFunctionDomainViaApi(projectId: string, functionName: string, hostname: string): Promise<ApiResponse<{
|
|
392
|
+
hostname: string;
|
|
393
|
+
deleted: true;
|
|
394
|
+
}>>;
|
|
327
395
|
export interface FunctionDeployContext {
|
|
328
396
|
/** Dispatch namespace for the script PUT (e.g. `amba_tenant_functions`). */
|
|
329
397
|
dispatch_namespace: string;
|
|
@@ -335,6 +403,8 @@ export interface FunctionDeployContext {
|
|
|
335
403
|
* step skips the storage binding in that case.
|
|
336
404
|
*/
|
|
337
405
|
r2_bucket_name: string | null;
|
|
406
|
+
/** Per-project key prefix for shared storage; empty for a dedicated bucket. */
|
|
407
|
+
r2_path_prefix: string | null;
|
|
338
408
|
}
|
|
339
409
|
export declare function getFunctionDispatchNamespace(projectId: string): Promise<ApiResponse<FunctionDeployContext>>;
|
|
340
410
|
export interface InternalCredentialsResponse {
|
|
@@ -382,6 +452,11 @@ export interface FunctionLogEvent {
|
|
|
382
452
|
export interface FunctionLogsResponse {
|
|
383
453
|
events: FunctionLogEvent[];
|
|
384
454
|
truncated: boolean;
|
|
455
|
+
/** Provider-neutral freshness for the underlying log-ingestion stream. */
|
|
456
|
+
ingestion?: {
|
|
457
|
+
status: 'current' | 'delayed' | 'no_batches' | 'unknown' | 'historical';
|
|
458
|
+
latest_batch_at: string | null;
|
|
459
|
+
};
|
|
385
460
|
since: string;
|
|
386
461
|
until: string;
|
|
387
462
|
/** Present when `since_request_id` anchored the range — the anchor event's timestamp. */
|
|
@@ -419,7 +494,7 @@ export interface SecretSyncPendingRow {
|
|
|
419
494
|
function_name: string | null;
|
|
420
495
|
secret_name: string;
|
|
421
496
|
expected_version: number;
|
|
422
|
-
status: 'pending' | 'syncing' | 'sync_failed_retrying' | 'synced';
|
|
497
|
+
status: 'pending' | 'syncing' | 'sync_failed_retrying' | 'synced' | 'revocation_pending' | 'revoking' | 'revoke_failed_retrying';
|
|
423
498
|
last_error: string | null;
|
|
424
499
|
attempts: number;
|
|
425
500
|
created_at: string;
|
|
@@ -441,7 +516,7 @@ export interface SetSecretResultRow {
|
|
|
441
516
|
/** Backend version resource path — exposed for ops debugging. */
|
|
442
517
|
version_path: string;
|
|
443
518
|
/** `'pending'` until the secret sync completes. */
|
|
444
|
-
sync_status: 'pending'
|
|
519
|
+
sync_status: 'pending';
|
|
445
520
|
created_at: string;
|
|
446
521
|
}
|
|
447
522
|
export declare function setSecretViaApi(projectId: string, input: {
|
|
@@ -463,7 +538,7 @@ export interface ListSecretsRow {
|
|
|
463
538
|
name: string;
|
|
464
539
|
/** Latest secret-store version number. */
|
|
465
540
|
version: number;
|
|
466
|
-
sync_status: 'pending' | 'syncing' | 'sync_failed_retrying' | 'synced';
|
|
541
|
+
sync_status: 'pending' | 'syncing' | 'sync_failed_retrying' | 'synced' | 'revocation_pending' | 'revoking' | 'revoke_failed_retrying';
|
|
467
542
|
attempts: number;
|
|
468
543
|
last_error: string | null;
|
|
469
544
|
updated_at: string;
|
|
@@ -473,11 +548,7 @@ export declare function listSecretsViaApi(projectId: string, options?: {
|
|
|
473
548
|
}): Promise<ApiListResponse<ListSecretsRow>>;
|
|
474
549
|
export declare function deleteSecretViaApi(projectId: string, name: string, options?: {
|
|
475
550
|
function?: string;
|
|
476
|
-
}): Promise<
|
|
477
|
-
name: string;
|
|
478
|
-
function: string | null;
|
|
479
|
-
deleted: true;
|
|
480
|
-
}>>;
|
|
551
|
+
}): Promise<void>;
|
|
481
552
|
/** Closed set of column types the server accepts. */
|
|
482
553
|
export type CollectionColumnType = 'uuid' | 'text' | 'integer' | 'bigint' | 'numeric' | 'boolean' | 'timestamptz' | 'date' | 'jsonb' | 'vector' | 'text[]' | 'integer[]' | 'bigint[]' | 'numeric[]' | 'boolean[]' | 'uuid[]';
|
|
483
554
|
export interface CollectionColumn {
|
|
@@ -680,6 +751,16 @@ export declare function removeSiteDomainViaApi(projectId: string, siteName: stri
|
|
|
680
751
|
hostname: string;
|
|
681
752
|
deleted: true;
|
|
682
753
|
}>>;
|
|
754
|
+
/** Force the provider lifecycle to be re-read and persist both site-domain states. */
|
|
755
|
+
export declare function refreshSiteDomainViaApi(projectId: string, siteName: string, hostname: string): Promise<ApiResponse<SiteDomainRow & {
|
|
756
|
+
ssl_validation: DomainValidationRecord[];
|
|
757
|
+
ownership_verification: DomainOwnershipVerification | null;
|
|
758
|
+
ownership_verification_http: {
|
|
759
|
+
http_url?: string;
|
|
760
|
+
http_body?: string;
|
|
761
|
+
} | null;
|
|
762
|
+
note?: string;
|
|
763
|
+
}>>;
|
|
683
764
|
/**
|
|
684
765
|
* Roll a live deployment back to a prior `deployment_id`. Rollback creates
|
|
685
766
|
* a NEW deployment that serves the prior bundle (git-revert semantics,
|
package/dist/bundle.d.ts
CHANGED
|
@@ -76,8 +76,8 @@ export declare class BundleSizeError extends Error {
|
|
|
76
76
|
export declare const HANDLER_SHAPE_FIX_MESSAGE: string;
|
|
77
77
|
/** Message used when the entry file has no `export default` at all. */
|
|
78
78
|
export declare const HANDLER_SHAPE_MISSING_DEFAULT_MESSAGE: string;
|
|
79
|
-
/**
|
|
80
|
-
export declare const HANDLER_SHAPE_NO_FETCH_MESSAGE
|
|
79
|
+
/** Back-compatible export name for the no-supported-handler message. */
|
|
80
|
+
export declare const HANDLER_SHAPE_NO_FETCH_MESSAGE = "Function entrypoint must have a callable `fetch` or `scheduled` method on its default export.";
|
|
81
81
|
/**
|
|
82
82
|
* Thrown when the bundled entry's `export default` is not the Module Worker
|
|
83
83
|
* shape (`{ fetch(req, env, ctx) }`). Raised BEFORE any upload so a wrong
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** `amba functions domains ...` exact-hostname lifecycle commands. */
|
|
2
|
+
export declare function functionDomainsAttachCommand(functionName: string, hostnameInput: string): Promise<void>;
|
|
3
|
+
export declare function functionDomainsListCommand(functionName: string): Promise<void>;
|
|
4
|
+
export declare function functionDomainsRefreshCommand(functionName: string, hostnameInput: string): Promise<void>;
|
|
5
|
+
export declare function functionDomainsRemoveCommand(functionName: string, hostnameInput: string): Promise<void>;
|
package/dist/commands/sites.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface SitesDomainAddOptions {
|
|
|
41
41
|
}
|
|
42
42
|
export declare function sitesDomainAddCommand(hostname: string, options: SitesDomainAddOptions): Promise<void>;
|
|
43
43
|
export declare function sitesDomainListCommand(siteName: string): Promise<void>;
|
|
44
|
+
export declare function sitesDomainRefreshCommand(hostname: string, options: {
|
|
45
|
+
site: string;
|
|
46
|
+
}): Promise<void>;
|
|
44
47
|
export declare function sitesDomainRemoveCommand(hostname: string, options: {
|
|
45
48
|
site: string;
|
|
46
49
|
zoneId?: string;
|
package/dist/index.js
CHANGED
|
@@ -864,6 +864,7 @@ async function request(method, path, body) {
|
|
|
864
864
|
} catch {}
|
|
865
865
|
throw new ApiClientError(errorMessage, res.status, errorCode);
|
|
866
866
|
}
|
|
867
|
+
if (res.status === 204) return void 0;
|
|
867
868
|
return await res.json();
|
|
868
869
|
}
|
|
869
870
|
/**
|
|
@@ -880,7 +881,13 @@ async function listProjects() {
|
|
|
880
881
|
return request("GET", "/projects");
|
|
881
882
|
}
|
|
882
883
|
async function createProject(input) {
|
|
883
|
-
|
|
884
|
+
const response = await request("POST", "/projects", input);
|
|
885
|
+
if ("project" in response.data) return { data: {
|
|
886
|
+
...response.data.project,
|
|
887
|
+
api_keys: response.data.api_keys,
|
|
888
|
+
provisioning_status: response.data.provisioning_status
|
|
889
|
+
} };
|
|
890
|
+
return response;
|
|
884
891
|
}
|
|
885
892
|
/**
|
|
886
893
|
* PATCH /admin/projects/:projectId — update mutable fields. Server
|
|
@@ -1019,6 +1026,25 @@ async function deployFunctionViaApi(projectId, input) {
|
|
|
1019
1026
|
async function listFunctionDeployments(projectId, options = {}) {
|
|
1020
1027
|
return request("GET", `/projects/${projectId}/functions/deployments${options.activeOnly ? "?active=1" : ""}`);
|
|
1021
1028
|
}
|
|
1029
|
+
function functionDomainsPath(projectId, functionName) {
|
|
1030
|
+
return `/projects/${encodeURIComponent(projectId)}/functions/${encodeURIComponent(functionName)}/domains`;
|
|
1031
|
+
}
|
|
1032
|
+
/** Attach one exact hostname to one deployed function. */
|
|
1033
|
+
async function attachFunctionDomainViaApi(projectId, functionName, hostname) {
|
|
1034
|
+
return request("POST", functionDomainsPath(projectId, functionName), { hostname });
|
|
1035
|
+
}
|
|
1036
|
+
/** List provider-neutral ownership/certificate status for a function's hostnames. */
|
|
1037
|
+
async function listFunctionDomainsViaApi(projectId, functionName) {
|
|
1038
|
+
return request("GET", functionDomainsPath(projectId, functionName));
|
|
1039
|
+
}
|
|
1040
|
+
/** Re-poll DNS ownership and certificate state for one exact hostname. */
|
|
1041
|
+
async function refreshFunctionDomainViaApi(projectId, functionName, hostname) {
|
|
1042
|
+
return request("POST", `${functionDomainsPath(projectId, functionName)}/${encodeURIComponent(hostname)}/refresh`);
|
|
1043
|
+
}
|
|
1044
|
+
/** Detach one exact hostname from a function. */
|
|
1045
|
+
async function removeFunctionDomainViaApi(projectId, functionName, hostname) {
|
|
1046
|
+
return request("DELETE", `${functionDomainsPath(projectId, functionName)}/${encodeURIComponent(hostname)}`);
|
|
1047
|
+
}
|
|
1022
1048
|
async function scheduleFunction(projectId, input) {
|
|
1023
1049
|
return request("POST", `/projects/${projectId}/functions/schedules`, input);
|
|
1024
1050
|
}
|
|
@@ -1111,6 +1137,10 @@ async function addSiteDomainViaApi(projectId, siteName, hostname) {
|
|
|
1111
1137
|
async function removeSiteDomainViaApi(projectId, siteName, hostname) {
|
|
1112
1138
|
return request("DELETE", `/projects/${projectId}/sites/${encodeURIComponent(siteName)}/domains/${encodeURIComponent(hostname)}`);
|
|
1113
1139
|
}
|
|
1140
|
+
/** Force the provider lifecycle to be re-read and persist both site-domain states. */
|
|
1141
|
+
async function refreshSiteDomainViaApi(projectId, siteName, hostname) {
|
|
1142
|
+
return request("POST", `/projects/${projectId}/sites/${encodeURIComponent(siteName)}/domains/${encodeURIComponent(hostname.toLowerCase())}/refresh-cert`);
|
|
1143
|
+
}
|
|
1114
1144
|
/**
|
|
1115
1145
|
* Roll a live deployment back to a prior `deployment_id`. Rollback creates
|
|
1116
1146
|
* a NEW deployment that serves the prior bundle (git-revert semantics,
|
|
@@ -2002,7 +2032,11 @@ async function ensureProjectForCwd(cwd, options) {
|
|
|
2002
2032
|
name: uniqueName,
|
|
2003
2033
|
environment
|
|
2004
2034
|
});
|
|
2005
|
-
const
|
|
2035
|
+
const initialKeys = project.data.api_keys;
|
|
2036
|
+
const { clientKey, serverKey } = initialKeys ? {
|
|
2037
|
+
clientKey: initialKeys.client.key,
|
|
2038
|
+
serverKey: initialKeys.server.key
|
|
2039
|
+
} : await mintProjectKeyPair(project.data.id, environment);
|
|
2006
2040
|
const creds = {
|
|
2007
2041
|
version: 1,
|
|
2008
2042
|
project_id: project.data.id,
|
|
@@ -2924,7 +2958,7 @@ That's the entire setup. The CLI:
|
|
|
2924
2958
|
detects on disk — Claude Code, Cursor, Windsurf.
|
|
2925
2959
|
6. Verifies the PAT against the API and confirms it's good.
|
|
2926
2960
|
|
|
2927
|
-
The Amba MCP toolset (\`amba_*\` tools —
|
|
2961
|
+
The Amba MCP toolset (\`amba_*\` tools — 500+ of them) is available to
|
|
2928
2962
|
the agent immediately: pass the freshly-minted \`pat\` as an inline
|
|
2929
2963
|
argument on every \`amba_*\` call in the current session. The next time
|
|
2930
2964
|
your MCP client starts it picks the PAT up from the config as the
|
|
@@ -3874,6 +3908,7 @@ async function initCommand(options = {}) {
|
|
|
3874
3908
|
const defaultProjectName = basename(cwd) || "amba-project";
|
|
3875
3909
|
let projectId;
|
|
3876
3910
|
let projectName;
|
|
3911
|
+
let createdKeys = null;
|
|
3877
3912
|
if (linkedProject) {
|
|
3878
3913
|
projectId = linkedProject.project_id;
|
|
3879
3914
|
projectName = linkedProject.project_name;
|
|
@@ -3910,20 +3945,30 @@ async function initCommand(options = {}) {
|
|
|
3910
3945
|
} else {
|
|
3911
3946
|
const name = await prompt(` Project name (default: ${defaultProjectName}): `);
|
|
3912
3947
|
const finalName = name.length > 0 ? name : defaultProjectName;
|
|
3913
|
-
|
|
3948
|
+
const res = await createProject({
|
|
3914
3949
|
name: finalName,
|
|
3915
3950
|
environment
|
|
3916
|
-
})
|
|
3951
|
+
});
|
|
3952
|
+
projectId = res.data.id;
|
|
3917
3953
|
projectName = finalName;
|
|
3954
|
+
if (res.data.api_keys) createdKeys = {
|
|
3955
|
+
clientKey: res.data.api_keys.client.key,
|
|
3956
|
+
serverKey: res.data.api_keys.server.key
|
|
3957
|
+
};
|
|
3918
3958
|
}
|
|
3919
3959
|
} else {
|
|
3920
3960
|
const name = await prompt(` Project name (default: ${defaultProjectName}): `);
|
|
3921
3961
|
const finalName = name.length > 0 ? name : defaultProjectName;
|
|
3922
|
-
|
|
3962
|
+
const res = await createProject({
|
|
3923
3963
|
name: finalName,
|
|
3924
3964
|
environment
|
|
3925
|
-
})
|
|
3965
|
+
});
|
|
3966
|
+
projectId = res.data.id;
|
|
3926
3967
|
projectName = finalName;
|
|
3968
|
+
if (res.data.api_keys) createdKeys = {
|
|
3969
|
+
clientKey: res.data.api_keys.client.key,
|
|
3970
|
+
serverKey: res.data.api_keys.server.key
|
|
3971
|
+
};
|
|
3927
3972
|
}
|
|
3928
3973
|
}
|
|
3929
3974
|
if (spinner.stopped) spinner = createSpinner("minting keys");
|
|
@@ -3935,6 +3980,9 @@ async function initCommand(options = {}) {
|
|
|
3935
3980
|
clientKey = linkedProject.client_key;
|
|
3936
3981
|
if (linkedProject.server_key) serverKey = linkedProject.server_key;
|
|
3937
3982
|
else serverKey = (await createApiKey(projectId, "server", environment)).data.key;
|
|
3983
|
+
} else if (createdKeys) {
|
|
3984
|
+
clientKey = createdKeys.clientKey;
|
|
3985
|
+
serverKey = createdKeys.serverKey;
|
|
3938
3986
|
} else {
|
|
3939
3987
|
const clientRes = await createApiKey(projectId, "client", environment);
|
|
3940
3988
|
const serverRes = await createApiKey(projectId, "server", environment);
|
|
@@ -5435,7 +5483,7 @@ async function loadProjectConfig(cwd = process.cwd()) {
|
|
|
5435
5483
|
if (!apiUrl) apiUrl = parsed["AMBA_API_URL"];
|
|
5436
5484
|
if (projectId && apiUrl) break;
|
|
5437
5485
|
}
|
|
5438
|
-
if (!projectId) throw new Error(`AMBA_PROJECT_ID not found.
|
|
5486
|
+
if (!projectId) throw new Error(`AMBA_PROJECT_ID not found. Export it, pass --project where supported, or run ${pc.cyan("amba init")} in this directory. The CLI reads .env.local/.env from the current working directory and does not search parent directories.`);
|
|
5439
5487
|
return {
|
|
5440
5488
|
projectId,
|
|
5441
5489
|
apiUrl: apiUrl ?? "https://api.amba.dev"
|
|
@@ -5562,11 +5610,11 @@ var BundleSizeError = class extends Error {
|
|
|
5562
5610
|
* service-worker shape (`export default async function (req) { … }`) is
|
|
5563
5611
|
* rejected by the runtime at upload, so we reject it before the round-trip.
|
|
5564
5612
|
*/
|
|
5565
|
-
const HANDLER_SHAPE_FIX_MESSAGE = "Function entrypoint must `export default { async fetch(req, env, ctx) { … } }` (the Module Worker shape). Found a default-exported function instead.";
|
|
5613
|
+
const HANDLER_SHAPE_FIX_MESSAGE = "Function entrypoint must `export default { async fetch(req, env, ctx) { … } }` or `export default { async scheduled(controller, env, ctx) { … } }` (the Module Worker shape). Found a default-exported function instead.";
|
|
5566
5614
|
/** Message used when the entry file has no `export default` at all. */
|
|
5567
|
-
const HANDLER_SHAPE_MISSING_DEFAULT_MESSAGE = "Function entrypoint must `export default { async fetch(req, env, ctx) { … } }` (the Module Worker shape). No default export was found.";
|
|
5568
|
-
/**
|
|
5569
|
-
const HANDLER_SHAPE_NO_FETCH_MESSAGE = "Function entrypoint must
|
|
5615
|
+
const HANDLER_SHAPE_MISSING_DEFAULT_MESSAGE = "Function entrypoint must `export default { async fetch(req, env, ctx) { … } }` or `export default { async scheduled(controller, env, ctx) { … } }` (the Module Worker shape). No default export was found.";
|
|
5616
|
+
/** Back-compatible export name for the no-supported-handler message. */
|
|
5617
|
+
const HANDLER_SHAPE_NO_FETCH_MESSAGE = "Function entrypoint must have a callable `fetch` or `scheduled` method on its default export.";
|
|
5570
5618
|
/**
|
|
5571
5619
|
* Thrown when the bundled entry's `export default` is not the Module Worker
|
|
5572
5620
|
* shape (`{ fetch(req, env, ctx) }`). Raised BEFORE any upload so a wrong
|
|
@@ -5600,11 +5648,14 @@ function assertHandlerShape(mod) {
|
|
|
5600
5648
|
const def = mod.default;
|
|
5601
5649
|
if (def === void 0 || def === null) throw new HandlerShapeError(HANDLER_SHAPE_MISSING_DEFAULT_MESSAGE, "missing-default");
|
|
5602
5650
|
if (typeof def === "function") {
|
|
5603
|
-
|
|
5651
|
+
const fetchOnFn = def.fetch;
|
|
5652
|
+
const scheduledOnFn = def.scheduled;
|
|
5653
|
+
if (typeof fetchOnFn !== "function" && typeof scheduledOnFn !== "function") throw new HandlerShapeError(HANDLER_SHAPE_FIX_MESSAGE, "plain-function");
|
|
5604
5654
|
return;
|
|
5605
5655
|
}
|
|
5606
5656
|
if (typeof def !== "object") throw new HandlerShapeError(HANDLER_SHAPE_NO_FETCH_MESSAGE, "no-fetch");
|
|
5607
|
-
|
|
5657
|
+
const handler = def;
|
|
5658
|
+
if (typeof handler.fetch !== "function" && typeof handler.scheduled !== "function") throw new HandlerShapeError(HANDLER_SHAPE_NO_FETCH_MESSAGE, "no-fetch");
|
|
5608
5659
|
}
|
|
5609
5660
|
/**
|
|
5610
5661
|
* Validate that the bundled customer code exports the Module Worker handler
|
|
@@ -5727,7 +5778,7 @@ async function functionsDeployCommand(target, options = {}) {
|
|
|
5727
5778
|
const entryPoint = await resolveFunctionEntry(target, options.entry);
|
|
5728
5779
|
const defaultName = targetIsDir ? basename(target.replace(/[/\\]+$/, "")) : basename(entryPoint).replace(/\.[^.]+$/, "");
|
|
5729
5780
|
const functionName = options.name ?? defaultName;
|
|
5730
|
-
validateFunctionName(functionName);
|
|
5781
|
+
validateFunctionName$1(functionName);
|
|
5731
5782
|
const rateLimit = parseRateLimitFlags(options);
|
|
5732
5783
|
console.log();
|
|
5733
5784
|
console.log(pc.bold(` amba functions deploy ${pc.cyan(functionName)}`));
|
|
@@ -5775,7 +5826,7 @@ async function functionsListCommand() {
|
|
|
5775
5826
|
console.log();
|
|
5776
5827
|
}
|
|
5777
5828
|
async function functionsDeleteCommand(name, options = {}) {
|
|
5778
|
-
validateFunctionName(name);
|
|
5829
|
+
validateFunctionName$1(name);
|
|
5779
5830
|
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.`);
|
|
5780
5831
|
const cascade = (await deleteFunctionViaApi((await loadProjectConfig()).projectId, name, { confirm: name })).data.cascade;
|
|
5781
5832
|
console.log(pc.green(" ✓") + ` Deleted ${pc.bold(name)}.`);
|
|
@@ -5783,7 +5834,7 @@ async function functionsDeleteCommand(name, options = {}) {
|
|
|
5783
5834
|
}
|
|
5784
5835
|
async function functionsScheduleCommand(name, cron, options = {}) {
|
|
5785
5836
|
const projectConfig = await loadProjectConfig();
|
|
5786
|
-
validateFunctionName(name);
|
|
5837
|
+
validateFunctionName$1(name);
|
|
5787
5838
|
const timezone = options.tz ?? "UTC";
|
|
5788
5839
|
const res = await scheduleFunction(projectConfig.projectId, {
|
|
5789
5840
|
name,
|
|
@@ -6115,6 +6166,14 @@ try {
|
|
|
6115
6166
|
};
|
|
6116
6167
|
return fetchFn(req, env, ctx);
|
|
6117
6168
|
};
|
|
6169
|
+
} else if (mod.default && typeof mod.default.scheduled === 'function') {
|
|
6170
|
+
// Scheduled-only modules are valid in production. This command exposes
|
|
6171
|
+
// an HTTP server, so return a clear 405 instead of rejecting a deployable
|
|
6172
|
+
// handler shape during local startup.
|
|
6173
|
+
handler = () => new Response(
|
|
6174
|
+
'This function only exports scheduled(); use amba functions schedule/run-now to invoke it.',
|
|
6175
|
+
{ status: 405, headers: { 'content-type': 'text/plain; charset=utf-8' } },
|
|
6176
|
+
);
|
|
6118
6177
|
} else if (typeof mod.default === 'function') {
|
|
6119
6178
|
// Legacy service-worker shape (\`export default async function (req) { ... }\`).
|
|
6120
6179
|
// REJECTED — production runs functions as Module Workers and the runtime
|
|
@@ -6124,7 +6183,7 @@ try {
|
|
|
6124
6183
|
process.stderr.write(' Error: Function entrypoint must \`export default { async fetch(req, env, ctx) { … } }\` (the Module Worker shape). Found a default-exported function instead.\\n');
|
|
6125
6184
|
process.exit(2);
|
|
6126
6185
|
} else {
|
|
6127
|
-
process.stderr.write(' Error: Function entrypoint
|
|
6186
|
+
process.stderr.write(' Error: Function entrypoint default export must have a callable \`fetch\` or \`scheduled\` method.\\n');
|
|
6128
6187
|
process.exit(2);
|
|
6129
6188
|
}
|
|
6130
6189
|
} catch (err) {
|
|
@@ -6194,8 +6253,8 @@ process.on('SIGINT', () => shutdown('SIGINT'));
|
|
|
6194
6253
|
* function-name overwrites — same upsert semantics as `amba secrets set`.
|
|
6195
6254
|
*/
|
|
6196
6255
|
async function functionsConsumeCommand(queueName, functionName, options = {}) {
|
|
6197
|
-
validateFunctionName(functionName);
|
|
6198
|
-
validateFunctionName(queueName);
|
|
6256
|
+
validateFunctionName$1(functionName);
|
|
6257
|
+
validateFunctionName$1(queueName);
|
|
6199
6258
|
const res = await upsertQueueBinding((await loadProjectConfig()).projectId, {
|
|
6200
6259
|
queue_name: queueName,
|
|
6201
6260
|
function_name: functionName,
|
|
@@ -6225,11 +6284,11 @@ async function functionsConsumersListCommand() {
|
|
|
6225
6284
|
console.log();
|
|
6226
6285
|
}
|
|
6227
6286
|
async function functionsConsumersUnbindCommand(queueName) {
|
|
6228
|
-
validateFunctionName(queueName);
|
|
6287
|
+
validateFunctionName$1(queueName);
|
|
6229
6288
|
await deleteQueueBinding((await loadProjectConfig()).projectId, queueName);
|
|
6230
6289
|
console.log(pc.green(" ✓") + ` Unbound queue ${pc.cyan(queueName)}.`);
|
|
6231
6290
|
}
|
|
6232
|
-
function validateFunctionName(name) {
|
|
6291
|
+
function validateFunctionName$1(name) {
|
|
6233
6292
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Invalid function name '${name}'. Must match /^[a-z][a-z0-9_-]*$/ (lowercase ASCII, digits, underscore or hyphen; must start with a letter).`);
|
|
6234
6293
|
if (name.length > 58) throw new Error("Function name must be 58 characters or fewer.");
|
|
6235
6294
|
}
|
|
@@ -6302,6 +6361,10 @@ async function functionsLogsCommand(functionName, options = {}) {
|
|
|
6302
6361
|
});
|
|
6303
6362
|
if (res.data.anchor && !options.json) console.error(pc.dim(` Anchored at ${res.data.anchor} (--since-request-id).`));
|
|
6304
6363
|
printEvents(res.data.events, options.json);
|
|
6364
|
+
if (res.data.events.length === 0 && !options.json) {
|
|
6365
|
+
if (res.data.ingestion?.status === "delayed") console.error(pc.yellow(` ! Log ingestion is delayed${res.data.ingestion.latest_batch_at ? `; latest batch arrived ${res.data.ingestion.latest_batch_at}` : ""}. Retry shortly.`));
|
|
6366
|
+
else if (res.data.ingestion?.status === "no_batches") console.error(pc.yellow(" ! No log batches reached this window yet. Retry shortly."));
|
|
6367
|
+
}
|
|
6305
6368
|
if (res.data.truncated) if (options.json) process.stderr.write(`{"truncated":true}\n`);
|
|
6306
6369
|
else console.error(pc.yellow(` ! Result truncated at limit; narrow --since / --until or raise --limit.`));
|
|
6307
6370
|
}
|
|
@@ -6385,6 +6448,85 @@ function sleep$1(ms) {
|
|
|
6385
6448
|
return new Promise((r) => setTimeout(r, ms));
|
|
6386
6449
|
}
|
|
6387
6450
|
//#endregion
|
|
6451
|
+
//#region src/commands/function-domains.ts
|
|
6452
|
+
/** `amba functions domains ...` exact-hostname lifecycle commands. */
|
|
6453
|
+
const FUNCTION_NAME_RE = /^[a-z][a-z0-9_-]{0,57}$/;
|
|
6454
|
+
function validateFunctionName(name) {
|
|
6455
|
+
if (!FUNCTION_NAME_RE.test(name)) throw new Error(`Invalid function name "${name}". Use lowercase letters, numbers, hyphens, or underscores (max 58 chars; start with a letter).`);
|
|
6456
|
+
}
|
|
6457
|
+
/**
|
|
6458
|
+
* Normalize case/trailing-dot spelling without trying to replace the API's
|
|
6459
|
+
* authoritative DNS validation. This command accepts one exact hostname only:
|
|
6460
|
+
* schemes, paths, ports, and wildcard labels are never meaningful here.
|
|
6461
|
+
*/
|
|
6462
|
+
function normalizeExactHostname(input) {
|
|
6463
|
+
const hostname = input.trim().toLowerCase().replace(/\.$/, "");
|
|
6464
|
+
if (hostname.length === 0 || hostname.length > 253 || !hostname.includes(".") || hostname.includes("*") || hostname.includes("/") || hostname.includes(":") || /\s/.test(hostname)) throw new Error(`Invalid hostname "${input}". Pass one exact DNS hostname such as feeds.example.com (no scheme, path, port, or wildcard).`);
|
|
6465
|
+
return hostname;
|
|
6466
|
+
}
|
|
6467
|
+
function lifecycle(domain) {
|
|
6468
|
+
if (domain.live) return pc.green("active");
|
|
6469
|
+
if (domain.cert_status === "active" && domain.ownership_status === "active") return "certificate=active, ownership=active, reconciliation=pending";
|
|
6470
|
+
return `certificate=${domain.cert_status}, ownership=${domain.ownership_status}`;
|
|
6471
|
+
}
|
|
6472
|
+
function printDomain(domain) {
|
|
6473
|
+
console.log(` ${pc.bold(domain.hostname)} ${lifecycle(domain)}`);
|
|
6474
|
+
}
|
|
6475
|
+
function printDnsInstructions(domain) {
|
|
6476
|
+
if (domain.dns_error === true) console.log(pc.yellow(" Automatic DNS publication failed; retry with domains refresh."));
|
|
6477
|
+
if ("cleanup_pending" in domain && domain.cleanup_pending === true) console.log(pc.yellow(" Routing cleanup is incomplete; retry with domains refresh."));
|
|
6478
|
+
if ("dns_autoconfigured" in domain && domain.dns_autoconfigured === true) console.log(pc.dim(` DNS records published automatically${domain.dns_records_published === void 0 ? "" : ` (${domain.dns_records_published})`}.`));
|
|
6479
|
+
if (domain.dns_target) console.log(pc.dim(` DNS target: CNAME ${domain.hostname} → ${domain.dns_target}`));
|
|
6480
|
+
for (const record of domain.ssl_validation ?? []) if (record.txt_name && record.txt_value) console.log(pc.dim(` Certificate TXT: ${record.txt_name} = ${record.txt_value}`));
|
|
6481
|
+
else if (record.http_url && record.http_body) console.log(pc.dim(` Certificate HTTP: ${record.http_url} → ${record.http_body}`));
|
|
6482
|
+
const ownership = domain.ownership_verification;
|
|
6483
|
+
if (ownership?.name && ownership.value) console.log(pc.dim(` Ownership ${ownership.type ?? "TXT"}: ${ownership.name} = ${ownership.value}`));
|
|
6484
|
+
const ownershipHttp = domain.ownership_verification_http;
|
|
6485
|
+
if (ownershipHttp?.http_url && ownershipHttp.http_body) console.log(pc.dim(` Ownership HTTP: ${ownershipHttp.http_url} → ${ownershipHttp.http_body}`));
|
|
6486
|
+
}
|
|
6487
|
+
async function functionDomainsAttachCommand(functionName, hostnameInput) {
|
|
6488
|
+
validateFunctionName(functionName);
|
|
6489
|
+
const hostname = normalizeExactHostname(hostnameInput);
|
|
6490
|
+
const { projectId } = await loadProjectConfig();
|
|
6491
|
+
const result = await attachFunctionDomainViaApi(projectId, functionName, hostname);
|
|
6492
|
+
console.log();
|
|
6493
|
+
console.log(pc.green(" ✓") + ` Attached ${pc.bold(result.data.hostname)} to ${functionName}.`);
|
|
6494
|
+
console.log(pc.dim(` Status: ${lifecycle(result.data)}`));
|
|
6495
|
+
printDnsInstructions(result.data);
|
|
6496
|
+
console.log(pc.dim(" Exact hostname only; no automatic www hostname was created."));
|
|
6497
|
+
if (!result.data.live && result.data.ownership_status !== "active") console.log(pc.dim(" Unverified claims become eligible for reclaim after 24 hours."));
|
|
6498
|
+
console.log();
|
|
6499
|
+
}
|
|
6500
|
+
async function functionDomainsListCommand(functionName) {
|
|
6501
|
+
validateFunctionName(functionName);
|
|
6502
|
+
const { projectId } = await loadProjectConfig();
|
|
6503
|
+
const result = await listFunctionDomainsViaApi(projectId, functionName);
|
|
6504
|
+
console.log();
|
|
6505
|
+
if (result.data.length === 0) console.log(pc.dim(` No custom domains attached to ${functionName}.`));
|
|
6506
|
+
else for (const domain of result.data) printDomain(domain);
|
|
6507
|
+
console.log();
|
|
6508
|
+
}
|
|
6509
|
+
async function functionDomainsRefreshCommand(functionName, hostnameInput) {
|
|
6510
|
+
validateFunctionName(functionName);
|
|
6511
|
+
const hostname = normalizeExactHostname(hostnameInput);
|
|
6512
|
+
const { projectId } = await loadProjectConfig();
|
|
6513
|
+
const result = await refreshFunctionDomainViaApi(projectId, functionName, hostname);
|
|
6514
|
+
console.log();
|
|
6515
|
+
console.log(pc.green(" ✓") + ` Refreshed ${pc.bold(result.data.hostname)}.`);
|
|
6516
|
+
console.log(pc.dim(` Status: ${lifecycle(result.data)}`));
|
|
6517
|
+
printDnsInstructions(result.data);
|
|
6518
|
+
console.log();
|
|
6519
|
+
}
|
|
6520
|
+
async function functionDomainsRemoveCommand(functionName, hostnameInput) {
|
|
6521
|
+
validateFunctionName(functionName);
|
|
6522
|
+
const hostname = normalizeExactHostname(hostnameInput);
|
|
6523
|
+
const { projectId } = await loadProjectConfig();
|
|
6524
|
+
const result = await removeFunctionDomainViaApi(projectId, functionName, hostname);
|
|
6525
|
+
console.log();
|
|
6526
|
+
console.log(pc.green(" ✓") + ` Removed ${pc.bold(result.data.hostname)} from ${functionName}.`);
|
|
6527
|
+
console.log();
|
|
6528
|
+
}
|
|
6529
|
+
//#endregion
|
|
6388
6530
|
//#region src/commands/ai.ts
|
|
6389
6531
|
/**
|
|
6390
6532
|
* `amba ai providers ...` — register / list / remove AI provider API keys.
|
|
@@ -6517,8 +6659,8 @@ async function secretsListCommand(options = {}) {
|
|
|
6517
6659
|
async function secretsUnsetCommand(name, options) {
|
|
6518
6660
|
validateSecretName(name);
|
|
6519
6661
|
await deleteSecretViaApi((await loadProjectConfig()).projectId, name, { function: options.function });
|
|
6520
|
-
console.log(pc.green(" ✓") + `
|
|
6521
|
-
console.log(pc.dim(" Workers
|
|
6662
|
+
console.log(pc.green(" ✓") + ` Secret removed from the backing store.`);
|
|
6663
|
+
console.log(pc.dim(" Workers binding revocation queued; provider failures retry automatically."));
|
|
6522
6664
|
}
|
|
6523
6665
|
function validateSecretName(name) {
|
|
6524
6666
|
const reason = getBindingReservationReason(name);
|
|
@@ -6529,6 +6671,9 @@ function renderStatus(status) {
|
|
|
6529
6671
|
case "synced": return pc.green("synced");
|
|
6530
6672
|
case "syncing": return pc.cyan("syncing");
|
|
6531
6673
|
case "sync_failed_retrying": return pc.yellow("retrying");
|
|
6674
|
+
case "revocation_pending": return pc.yellow("revocation pending");
|
|
6675
|
+
case "revoking": return pc.cyan("revoking");
|
|
6676
|
+
case "revoke_failed_retrying": return pc.yellow("revocation retrying");
|
|
6532
6677
|
default: return pc.dim(status);
|
|
6533
6678
|
}
|
|
6534
6679
|
}
|
|
@@ -6618,7 +6763,10 @@ async function billingStatusCommand() {
|
|
|
6618
6763
|
try {
|
|
6619
6764
|
const { projectId } = await loadProjectConfig();
|
|
6620
6765
|
const s = (await adminGet(`/projects/${projectId}/billing/status`)).data;
|
|
6621
|
-
|
|
6766
|
+
const effectiveTier = s.effective_tier ?? s.tier;
|
|
6767
|
+
console.log(` Effective tier: ${pc.bold(effectiveTier)}`);
|
|
6768
|
+
if (effectiveTier !== s.tier) console.log(` Stored tier: ${s.tier}`);
|
|
6769
|
+
if (s.comped === true) console.log(` Billing: ${pc.green("comped")} ${pc.dim("(no customer charge)")}`);
|
|
6622
6770
|
console.log(` Subscription: ${s.subscription_status ?? pc.dim("—")}`);
|
|
6623
6771
|
console.log(` Next bill anchor: ${s.current_period_end ? new Date(s.current_period_end).toLocaleDateString() : pc.dim("—")}`);
|
|
6624
6772
|
const pctCeiling = s.ceiling_pct_consumed === null || s.ceiling_pct_consumed === void 0 ? null : Math.round(s.ceiling_pct_consumed * 100);
|
|
@@ -8929,6 +9077,15 @@ async function sitesDomainListCommand(siteName) {
|
|
|
8929
9077
|
for (const d of res.data) console.log(` ${pc.bold(d.hostname)} ${formatDomainStatus(d)}`);
|
|
8930
9078
|
console.log();
|
|
8931
9079
|
}
|
|
9080
|
+
async function sitesDomainRefreshCommand(hostname, options) {
|
|
9081
|
+
validateSiteName(options.site);
|
|
9082
|
+
validateHostname(hostname);
|
|
9083
|
+
const projectConfig = await loadProjectConfig();
|
|
9084
|
+
const normalisedHostname = hostname.toLowerCase();
|
|
9085
|
+
const res = await refreshSiteDomainViaApi(projectConfig.projectId, options.site, normalisedHostname);
|
|
9086
|
+
console.log(` ${pc.bold(res.data.hostname)} ${formatDomainStatus(res.data)}`);
|
|
9087
|
+
if (res.data.note) console.log(pc.dim(` ${res.data.note}`));
|
|
9088
|
+
}
|
|
8932
9089
|
async function sitesDomainRemoveCommand(hostname, options) {
|
|
8933
9090
|
validateSiteName(options.site);
|
|
8934
9091
|
validateHostname(hostname);
|
|
@@ -9530,7 +9687,7 @@ async function diffConfigCommand(file = DEFAULT_BUNDLE_FILE, options = {}) {
|
|
|
9530
9687
|
//#endregion
|
|
9531
9688
|
//#region src/index.ts
|
|
9532
9689
|
const program = new Command();
|
|
9533
|
-
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.
|
|
9690
|
+
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.3");
|
|
9534
9691
|
program.option("--token <pat>", "Use a Personal Access Token for headless / CI / agent use (overrides ~/.amba/credentials.json + AMBA_PAT env)");
|
|
9535
9692
|
program.hook("preAction", (thisCommand) => {
|
|
9536
9693
|
setBearerOverride(resolveTokenSource({
|
|
@@ -9678,8 +9835,8 @@ functions.command("deploy <target>").description("Bundle a function (a single fi
|
|
|
9678
9835
|
functions.command("list").description("List active functions for the current project").action(async () => {
|
|
9679
9836
|
await runAction(functionsListCommand);
|
|
9680
9837
|
});
|
|
9681
|
-
functions.command("delete <name>").description("Disable + remove a function from the dispatch namespace").action(async (name) => {
|
|
9682
|
-
await runAction(() => functionsDeleteCommand(name));
|
|
9838
|
+
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) => {
|
|
9839
|
+
await runAction(() => functionsDeleteCommand(name, { confirm: opts.confirm }));
|
|
9683
9840
|
});
|
|
9684
9841
|
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) => {
|
|
9685
9842
|
await runAction(() => functionsScheduleCommand(name, cron, opts));
|
|
@@ -9711,8 +9868,21 @@ consumers.command("list").description("List queue bindings for the current proje
|
|
|
9711
9868
|
consumers.command("unbind <queue-name>").description("Remove the binding for a queue (DROP)").action(async (queueName) => {
|
|
9712
9869
|
await runAction(() => functionsConsumersUnbindCommand(queueName));
|
|
9713
9870
|
});
|
|
9871
|
+
const functionDomains = functions.command("domains").description("Manage exact custom hostnames for functions");
|
|
9872
|
+
functionDomains.command("attach <name> <hostname>").description("Attach one exact hostname to a deployed function").action(async (name, hostname) => {
|
|
9873
|
+
await runAction(() => functionDomainsAttachCommand(name, hostname));
|
|
9874
|
+
});
|
|
9875
|
+
functionDomains.command("list <name>").description("List custom hostname ownership and certificate status").action(async (name) => {
|
|
9876
|
+
await runAction(() => functionDomainsListCommand(name));
|
|
9877
|
+
});
|
|
9878
|
+
functionDomains.command("refresh <name> <hostname>").description("Refresh DNS ownership and certificate status").action(async (name, hostname) => {
|
|
9879
|
+
await runAction(() => functionDomainsRefreshCommand(name, hostname));
|
|
9880
|
+
});
|
|
9881
|
+
functionDomains.command("remove <name> <hostname>").description("Detach an exact hostname from a function").action(async (name, hostname) => {
|
|
9882
|
+
await runAction(() => functionDomainsRemoveCommand(name, hostname));
|
|
9883
|
+
});
|
|
9714
9884
|
const secrets = program.command("secrets").description("Function secrets (GCP canonical, Workers-Secret synced)");
|
|
9715
|
-
secrets.command("set <name> [value]").description("Store a secret and queue its sync to your deployed function(s). Omit --function for a project-wide secret. Use --from-stdin to keep the value out of shell history.").option("--function <name>", "Function the secret binds to. OMIT this flag to set a PROJECT-WIDE secret — one value visible to every function in the project (and to any function deployed later). With it, the secret is scoped to just that function. Use the same name as the entry passed to 'amba functions deploy'. Secrets may be set before the function is deployed; the sync drains once you deploy.").option("--env <env>", "'dev' | 'prod' — INFORMATIONAL ONLY. Both share one secret namespace per project. Use distinct secret names (e.g. STRIPE_KEY_DEV / STRIPE_KEY_PROD) or two amba projects for real env isolation.").option("--from-stdin", "Read the secret value from stdin instead of the positional arg. Mutually exclusive with <value>. Pipe in: `echo $KEY | amba secrets set NAME --from-stdin`.").action(async (name, value, opts) => {
|
|
9885
|
+
secrets.command("set <name> [value]").description("Store a secret and queue its sync to your deployed function(s). Omit --function for a project-wide secret. Use --from-stdin to keep the value out of shell history.").option("--function <name>", "Function the secret binds to. OMIT this flag to set a PROJECT-WIDE secret — one value visible to every function in the project (and to any function deployed later). With it, the secret is scoped to just that function and overrides a same-name project-wide value. Use the same name as the entry passed to 'amba functions deploy'. Secrets may be set before the function is deployed; the sync drains once you deploy.").option("--env <env>", "'dev' | 'prod' — INFORMATIONAL ONLY. Both share one secret namespace per project. Use distinct secret names (e.g. STRIPE_KEY_DEV / STRIPE_KEY_PROD) or two amba projects for real env isolation.").option("--from-stdin", "Read the secret value from stdin instead of the positional arg. Mutually exclusive with <value>. Pipe in: `echo $KEY | amba secrets set NAME --from-stdin`.").action(async (name, value, opts) => {
|
|
9716
9886
|
await runAction(() => secretsSetCommand(name, value, {
|
|
9717
9887
|
function: opts.function,
|
|
9718
9888
|
env: opts.env ?? "dev",
|
|
@@ -9722,7 +9892,7 @@ secrets.command("set <name> [value]").description("Store a secret and queue its
|
|
|
9722
9892
|
secrets.command("list").description("List secret sync status for the current project").option("--function <name>", "Filter to secrets for a single function (avoids listing all functions at once)").action(async (opts) => {
|
|
9723
9893
|
await runAction(() => secretsListCommand({ function: opts.function }));
|
|
9724
9894
|
});
|
|
9725
|
-
secrets.command("unset <name>").description("Remove a secret
|
|
9895
|
+
secrets.command("unset <name>").description("Remove a secret and revoke its Worker binding").option("--function <name>", "Function the secret was bound to. Omit to remove the project-wide secret of this name. A surviving same-name value at the other scope is restored automatically.").action(async (name, opts) => {
|
|
9726
9896
|
await runAction(() => secretsUnsetCommand(name, opts));
|
|
9727
9897
|
});
|
|
9728
9898
|
const billing = program.command("billing").description("Per-project subscription, headroom, and spend controls");
|
|
@@ -9878,6 +10048,9 @@ sitesDomain.command("add <hostname>").description("Attach a custom hostname (DV
|
|
|
9878
10048
|
sitesDomain.command("list <site>").description("List custom hostnames attached to a site").action(async (site) => {
|
|
9879
10049
|
await runAction(() => sitesDomainListCommand(site));
|
|
9880
10050
|
});
|
|
10051
|
+
sitesDomain.command("refresh <hostname>").description("Re-check hostname ownership and certificate status").requiredOption("--site <name>", "Site name the hostname is attached to").action(async (hostname, opts) => {
|
|
10052
|
+
await runAction(() => sitesDomainRefreshCommand(hostname, { site: opts.site }));
|
|
10053
|
+
});
|
|
9881
10054
|
sitesDomain.command("remove <hostname>").description("Detach a custom hostname (best-effort edge detach + control-plane row delete)").requiredOption("--site <name>", "Site name the hostname is attached to").option("--zone-id <id>", "DNS zone id (default: env AMBA_DNS_ZONE_ID)").action(async (hostname, opts) => {
|
|
9882
10055
|
await runAction(() => sitesDomainRemoveCommand(hostname, {
|
|
9883
10056
|
site: opts.site,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layers/amba",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.3",
|
|
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-
|
|
53
|
-
"@layers/amba-
|
|
52
|
+
"@layers/amba-shared": "4.0.4",
|
|
53
|
+
"@layers/amba-mcp": "4.0.7"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsdown && tsc --emitDeclarationOnly",
|
package/skill-bundle/SKILL.md
CHANGED
|
@@ -89,7 +89,7 @@ Check whether a project exists in this directory:
|
|
|
89
89
|
> }'
|
|
90
90
|
> ```
|
|
91
91
|
> The response is the standard MCP envelope `{"jsonrpc":"2.0","id":"1","result":{"content":[{"type":"text","text":"<JSON body>"}]}}`. You construct calls by hand the same way you would with typed tools — every `amba_*` tool name from the catalog works. This is the same wire the typed-tool surface uses; you just skip the client-side configuration loop.
|
|
92
|
-
> 2. **The `amba` CLI via bash.** Already authenticated via `~/.amba/credentials.json`. Coverage: `amba projects {list,create,update,show,delete}`, `amba collections {create,alter,list,drop}`, `amba functions {deploy,list,delete,schedule,dev,logs,consume}`, `amba push test`, `amba secrets {set,list,unset}`, `amba config {list,set}`, `amba sites <subcommands>`, `amba seed`, `amba status --detailed`, `amba logs tail`, `amba analytics export`, `amba schema export`, `amba ai <subcommands>`, `amba db migrate`, `amba billing {status,upgrade,portal,set-ceiling}`, `amba claim <email>` (sandbox → verified_free magic link). Run `amba --help` once if you need to see the full subcommand surface. Use this when you want a typed wrapper for a common op; the JSON-RPC path is strictly more flexible.
|
|
92
|
+
> 2. **The `amba` CLI via bash.** Already authenticated via `~/.amba/credentials.json`. Coverage: `amba projects {list,create,update,show,delete}`, `amba collections {create,alter,list,drop}`, `amba functions {deploy,list,delete,schedule,dev,logs,consume,domains}`, `amba push test`, `amba secrets {set,list,unset}`, `amba config {list,set}`, `amba sites <subcommands>`, `amba seed`, `amba status --detailed`, `amba logs tail`, `amba analytics export`, `amba schema export`, `amba ai <subcommands>`, `amba db migrate`, `amba billing {status,upgrade,portal,set-ceiling}`, `amba claim <email>` (sandbox → verified_free magic link). Run `amba --help` once if you need to see the full subcommand surface. Use this when you want a typed wrapper for a common op; the JSON-RPC path is strictly more flexible.
|
|
93
93
|
> 3. **Wait for next agent launch.** The MCP entry is already persisted; on the next launch, `mcp__amba__*` tools appear automatically.
|
|
94
94
|
>
|
|
95
95
|
> A SEPARATE case: if Amba MCP IS wired in this session but the inbound Bearer is stale (e.g., the user just ran `amba_developer_signup` and got a fresh PAT), pass `pat: "<the new PAT>"` as an inline argument on every subsequent `amba_*` tool call to override the inbound Bearer for that one call. This only helps when the `mcp__amba__*` tools already exist in your tool list — it does not summon them into existence.
|
|
@@ -37,6 +37,9 @@ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function get
|
|
|
37
37
|
| Tool | Purpose | Example args |
|
|
38
38
|
| --- | --- | --- |
|
|
39
39
|
| `amba_functions_deploy` / `amba_deploy_function` | Deploy a function from source (TypeScript / JavaScript / Python). | `{ project_id, name: "send_welcome_email", runtime: "node22", source: "export default async (req) => { ... }", trigger: { type: "http" } }` |
|
|
40
|
+
| `amba_function_domains_attach` | Attach an exact hostname to a function and return DNS validation records. | `{ project_id, name: "feed", hostname: "feeds.example.com" }` |
|
|
41
|
+
| `amba_function_domains_list` / `amba_function_domains_refresh` | Read or refresh ownership/certificate status. | `{ project_id, name: "feed", hostname: "feeds.example.com" }` |
|
|
42
|
+
| `amba_function_domains_remove` | Detach an exact function hostname. | `{ project_id, name: "feed", hostname: "feeds.example.com" }` |
|
|
40
43
|
| `amba_functions_list` / `amba_list_functions` | List functions. | `{ project_id }` |
|
|
41
44
|
| `amba_functions_get` / `amba_get_function` | Read function metadata. | `{ project_id, function_id }` |
|
|
42
45
|
| `amba_functions_get_logs` / `amba_get_function_logs` | Recent invocation logs. | `{ project_id, function_id, limit: 100 }` |
|
|
@@ -46,6 +49,11 @@ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function get
|
|
|
46
49
|
| `amba_functions_resume_schedule` / `amba_resume_function_schedule` | Resume. | `{ project_id, function_id }` |
|
|
47
50
|
| `amba_functions_trigger_schedule` / `amba_trigger_function_schedule` | Fire a scheduled function ad-hoc (testing). | `{ project_id, function_id }` |
|
|
48
51
|
|
|
52
|
+
Function-domain MVP limits use effective tier: free 1, pro 5, scale 20,
|
|
53
|
+
enterprise/comped 50. Attach is limited to five attempts per project per hour.
|
|
54
|
+
Unverified claims become eligible for exact-host reclaim after 24 hours; Worker
|
|
55
|
+
route/KV allocation waits until provider ownership is active.
|
|
56
|
+
|
|
49
57
|
### AI prompts
|
|
50
58
|
|
|
51
59
|
Managed LLM templates: a stored prompt with provider + model + system message, invoked by name from the SDK. The actual LLM call is rewritten server-side per-tenant — the customer's provider API key (Anthropic / OpenAI / Mistral / Gemini) stays server-side, never on the device.
|
|
@@ -80,7 +88,7 @@ Managed LLM templates: a stored prompt with provider + model + system message, i
|
|
|
80
88
|
|
|
81
89
|
| Tool | Purpose | Example args |
|
|
82
90
|
| --- | --- | --- |
|
|
83
|
-
| `amba_secrets_set` / `amba_set_secret` | Set a
|
|
91
|
+
| `amba_secrets_set` / `amba_set_secret` | Set or rotate a function secret. Omit `function` for a project-wide value or pass it for one function. Already-deployed functions receive the new binding asynchronously; a future deploy also reconciles it. For third-party API keys called from your functions — NOT AI provider keys (use `amba_ai_providers_set` for those). | `{ project_id, name: "STRIPE_WEBHOOK_SECRET", value: "whsec_..." }` |
|
|
84
92
|
| `amba_secrets_get` / `amba_get_secret` | Read a secret (returns `"<redacted>"` unless explicitly requested). | `{ project_id, name }` |
|
|
85
93
|
| `amba_secrets_list` / `amba_list_secrets` | List secret names. | `{ project_id }` |
|
|
86
94
|
| `amba_secrets_delete` / `amba_delete_secret` | Delete. | `{ project_id, name }` |
|