@layers/amba 4.1.8 → 4.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/commands/billing.d.ts +32 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/credentials.d.ts +5 -0
- package/dist/index.js +163 -27
- package/dist/sandbox.d.ts +36 -1
- package/package.json +3 -3
- package/skill-bundle/SKILL.md +3 -1
package/README.md
CHANGED
|
@@ -42,6 +42,21 @@ fetches the canonical prompt from
|
|
|
42
42
|
falls back to an inlined snapshot when offline. Use `--no-skills` to
|
|
43
43
|
skip the skill install.
|
|
44
44
|
|
|
45
|
+
### Invite-only signups
|
|
46
|
+
|
|
47
|
+
While new accounts are invite-only, pass your invite code:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx @layers/amba init --invite <code-from-your-invite-email>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`AMBA_INVITE_CODE` works too; the flag wins when both are set. Without
|
|
54
|
+
a valid code, init stops with `INVITE_REQUIRED`, `INVITE_INVALID` or
|
|
55
|
+
`SIGNUPS_CLOSED` and prints where to request access
|
|
56
|
+
(<https://app.amba.dev/request-access>). The invite code arrives by
|
|
57
|
+
email. With `--json`, the failure is a JSON document on stdout carrying
|
|
58
|
+
`error.code`.
|
|
59
|
+
|
|
45
60
|
## Configure + first call
|
|
46
61
|
|
|
47
62
|
```bash
|
|
@@ -23,6 +23,37 @@
|
|
|
23
23
|
* Project is resolved via `loadProjectConfig` (AMBA_PROJECT_ID env, then
|
|
24
24
|
* .env / .env.local in the cwd) — same pattern as `amba secrets *`.
|
|
25
25
|
*/
|
|
26
|
+
/** Database limits block (free-tier compute + storage caps). */
|
|
27
|
+
interface DatabaseLimits {
|
|
28
|
+
limits_enforced: boolean;
|
|
29
|
+
source?: 'tier' | 'override' | 'unmanaged';
|
|
30
|
+
compute: {
|
|
31
|
+
max_cu: number;
|
|
32
|
+
used_cu_hours: number | null;
|
|
33
|
+
limit_cu_hours: number | null;
|
|
34
|
+
pct: number | null;
|
|
35
|
+
};
|
|
36
|
+
storage: {
|
|
37
|
+
used_mb: number | null;
|
|
38
|
+
limit_mb: number | null;
|
|
39
|
+
pct: number | null;
|
|
40
|
+
};
|
|
41
|
+
period: {
|
|
42
|
+
start: string;
|
|
43
|
+
resets_at: string;
|
|
44
|
+
} | null;
|
|
45
|
+
limit_reached: ('database_compute' | 'database_storage')[];
|
|
46
|
+
observed_at: string | null;
|
|
47
|
+
upgrade?: {
|
|
48
|
+
cli?: string;
|
|
49
|
+
mcp_tool?: string;
|
|
50
|
+
} | null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Print the database limits block. Exported for `amba status`'s one-line
|
|
54
|
+
* summary + tests.
|
|
55
|
+
*/
|
|
56
|
+
export declare function formatDatabaseLimits(db: DatabaseLimits): string[];
|
|
26
57
|
export declare function billingStatusCommand(): Promise<void>;
|
|
27
58
|
export declare function billingUpgradeCommand(input: {
|
|
28
59
|
tier: 'pro' | 'scale';
|
|
@@ -32,3 +63,4 @@ export declare function billingPortalCommand(): Promise<void>;
|
|
|
32
63
|
export declare function billingSetCeilingCommand(input: {
|
|
33
64
|
ceiling: number | null;
|
|
34
65
|
}): Promise<void>;
|
|
66
|
+
export {};
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export interface InitOptions {
|
|
|
21
21
|
sandbox?: boolean;
|
|
22
22
|
/** Override the auto-generated sandbox email. Only meaningful with `--sandbox`. */
|
|
23
23
|
sandboxEmail?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Platform invite code (`--invite`) sent with the sandbox signup when
|
|
26
|
+
* new accounts are invite-only. Falls back to `AMBA_INVITE_CODE`.
|
|
27
|
+
* Passing the flag selects the headless signup path.
|
|
28
|
+
*/
|
|
29
|
+
inviteCode?: string;
|
|
24
30
|
/** Skip MCP-client-config writes. Mostly for testing. */
|
|
25
31
|
noMcpConfig?: boolean;
|
|
26
32
|
/**
|
|
@@ -38,6 +44,8 @@ export interface InitOptions {
|
|
|
38
44
|
export declare function initCommand(options?: InitOptions): Promise<void>;
|
|
39
45
|
export declare function runSandboxInit(cwd: string, options: {
|
|
40
46
|
sandboxEmail?: string;
|
|
47
|
+
/** Platform invite code for a fresh signup; falls back to `AMBA_INVITE_CODE`. */
|
|
48
|
+
inviteCode?: string;
|
|
41
49
|
noMcpConfig?: boolean;
|
|
42
50
|
/**
|
|
43
51
|
* Skip the project-local skill install. Default is to install. Set
|
|
@@ -52,6 +60,11 @@ export declare function runSandboxInit(cwd: string, options: {
|
|
|
52
60
|
*/
|
|
53
61
|
homeDir?: string;
|
|
54
62
|
}): Promise<SandboxResult>;
|
|
63
|
+
/**
|
|
64
|
+
* `--json` failure document. A gated signup carries its 403 code plus
|
|
65
|
+
* where to request access; every other failure reports `INIT_FAILED`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function sandboxErrorToJson(err: unknown): Record<string, unknown>;
|
|
55
68
|
/**
|
|
56
69
|
* Build the plaintext (no ANSI) success-output block printed at the
|
|
57
70
|
* end of `amba init --sandbox`. Pure function — exported for the
|
package/dist/credentials.d.ts
CHANGED
|
@@ -148,6 +148,11 @@ export interface EnsureDeveloperOptions {
|
|
|
148
148
|
* identity is reused.
|
|
149
149
|
*/
|
|
150
150
|
sandboxEmail?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Platform invite code for the sandbox signup (`--invite`). Falls back
|
|
153
|
+
* to `AMBA_INVITE_CODE`. Ignored when an existing identity is reused.
|
|
154
|
+
*/
|
|
155
|
+
inviteCode?: string;
|
|
151
156
|
}
|
|
152
157
|
export interface EnsureDeveloperResult {
|
|
153
158
|
credentials: DeveloperCredentials;
|
package/dist/index.js
CHANGED
|
@@ -1326,6 +1326,64 @@ function generateSandboxPassword() {
|
|
|
1326
1326
|
}
|
|
1327
1327
|
/** Request header carrying AMBA_SIGNUP_TOKEN on the signup call. */
|
|
1328
1328
|
const SIGNUP_TOKEN_HEADER = "X-Amba-Signup-Token";
|
|
1329
|
+
/** Environment variable read when `--invite` is absent. */
|
|
1330
|
+
const INVITE_CODE_ENV = "AMBA_INVITE_CODE";
|
|
1331
|
+
/** The 403 codes signup answers with when the platform is gated. */
|
|
1332
|
+
const SIGNUP_ACCESS_ERROR_CODES = [
|
|
1333
|
+
"SIGNUPS_CLOSED",
|
|
1334
|
+
"INVITE_REQUIRED",
|
|
1335
|
+
"INVITE_INVALID"
|
|
1336
|
+
];
|
|
1337
|
+
const DEFAULT_REQUEST_ACCESS_URL = `${CONSOLE_URL}/request-access`;
|
|
1338
|
+
const REQUEST_ACCESS_ENDPOINT = `POST /v1/auth/developer/waitlist`;
|
|
1339
|
+
const RERUN_WITH_INVITE = "npx @layers/amba init --invite <code>";
|
|
1340
|
+
function isSignupAccessErrorCode(code) {
|
|
1341
|
+
return SIGNUP_ACCESS_ERROR_CODES.includes(code);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* The flag value when set, else `AMBA_INVITE_CODE`, else undefined. Blank
|
|
1345
|
+
* values count as absent so an empty env var never sends `invite_code: ""`.
|
|
1346
|
+
*/
|
|
1347
|
+
function resolveInviteCode(flag) {
|
|
1348
|
+
const fromFlag = flag?.trim();
|
|
1349
|
+
if (fromFlag) return fromFlag;
|
|
1350
|
+
return process.env["AMBA_INVITE_CODE"]?.trim() || void 0;
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Ordered next steps for a gated signup, phrased for whoever runs the CLI
|
|
1354
|
+
* (a developer or their coding agent).
|
|
1355
|
+
*/
|
|
1356
|
+
function signupAccessNextSteps(code, requestAccessUrl) {
|
|
1357
|
+
const requestAccess = `Request access at ${requestAccessUrl}; the invite code arrives by email.`;
|
|
1358
|
+
switch (code) {
|
|
1359
|
+
case "INVITE_REQUIRED": return [`Have an invite code? Rerun \`${RERUN_WITH_INVITE}\` (or set ${INVITE_CODE_ENV}).`, `No code yet? ${requestAccess}`];
|
|
1360
|
+
case "INVITE_INVALID": return [`Check the code and rerun \`${RERUN_WITH_INVITE}\` (or correct ${INVITE_CODE_ENV}).`, `Need a fresh code? ${requestAccess}`];
|
|
1361
|
+
case "SIGNUPS_CLOSED": return [`${requestAccess} Rerun \`${RERUN_WITH_INVITE}\` once it lands.`];
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Thrown when signup answers 403 SIGNUPS_CLOSED / INVITE_REQUIRED /
|
|
1366
|
+
* INVITE_INVALID. `message` carries the server's sentence plus the next
|
|
1367
|
+
* steps; the structured fields feed `amba init --json`.
|
|
1368
|
+
*/
|
|
1369
|
+
var SignupAccessError = class extends Error {
|
|
1370
|
+
code;
|
|
1371
|
+
serverMessage;
|
|
1372
|
+
requestAccessUrl;
|
|
1373
|
+
requestAccessEndpoint;
|
|
1374
|
+
nextSteps;
|
|
1375
|
+
constructor(input) {
|
|
1376
|
+
const requestAccessUrl = input.requestAccessUrl ?? DEFAULT_REQUEST_ACCESS_URL;
|
|
1377
|
+
const nextSteps = signupAccessNextSteps(input.code, requestAccessUrl);
|
|
1378
|
+
super([`Sandbox signup failed (${input.code}): ${input.serverMessage}`, ...nextSteps].join("\n "));
|
|
1379
|
+
this.name = "SignupAccessError";
|
|
1380
|
+
this.code = input.code;
|
|
1381
|
+
this.serverMessage = input.serverMessage;
|
|
1382
|
+
this.requestAccessUrl = requestAccessUrl;
|
|
1383
|
+
this.requestAccessEndpoint = input.requestAccessEndpoint ?? REQUEST_ACCESS_ENDPOINT;
|
|
1384
|
+
this.nextSteps = nextSteps;
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1329
1387
|
/**
|
|
1330
1388
|
* POST the synthesized credentials at the public signup endpoint.
|
|
1331
1389
|
*
|
|
@@ -1337,7 +1395,8 @@ const SIGNUP_TOKEN_HEADER = "X-Amba-Signup-Token";
|
|
|
1337
1395
|
* Returns the unwrapped, flattened shape consumed by the rest of the
|
|
1338
1396
|
* sandbox flow. Throws with a human-readable message on any non-2xx so
|
|
1339
1397
|
* the CLI's `runAction` wrapper can surface it without crashing on a
|
|
1340
|
-
* generic 'fetch failed'.
|
|
1398
|
+
* generic 'fetch failed'. A gated platform (403 SIGNUPS_CLOSED /
|
|
1399
|
+
* INVITE_REQUIRED / INVITE_INVALID) throws `SignupAccessError`.
|
|
1341
1400
|
*/
|
|
1342
1401
|
async function performSandboxSignup(req, options = {}) {
|
|
1343
1402
|
const apiUrl = options.apiUrl ?? process.env["AMBA_API_URL"] ?? "https://api.amba.dev";
|
|
@@ -1349,21 +1408,32 @@ async function performSandboxSignup(req, options = {}) {
|
|
|
1349
1408
|
};
|
|
1350
1409
|
const signupToken = process.env["AMBA_SIGNUP_TOKEN"]?.trim();
|
|
1351
1410
|
if (signupToken) headers[SIGNUP_TOKEN_HEADER] = signupToken;
|
|
1411
|
+
const body = {
|
|
1412
|
+
email: req.email,
|
|
1413
|
+
password: req.password,
|
|
1414
|
+
name: req.name ?? "amba-sandbox-cli"
|
|
1415
|
+
};
|
|
1416
|
+
const inviteCode = resolveInviteCode(req.inviteCode);
|
|
1417
|
+
if (inviteCode) body["invite_code"] = inviteCode;
|
|
1352
1418
|
const res = await fetchImpl(url, {
|
|
1353
1419
|
method: "POST",
|
|
1354
1420
|
headers,
|
|
1355
|
-
body: JSON.stringify(
|
|
1356
|
-
email: req.email,
|
|
1357
|
-
password: req.password,
|
|
1358
|
-
name: req.name ?? "amba-sandbox-cli"
|
|
1359
|
-
})
|
|
1421
|
+
body: JSON.stringify(body)
|
|
1360
1422
|
});
|
|
1361
1423
|
if (!res.ok) {
|
|
1362
1424
|
let detail = `${res.status} ${res.statusText}`;
|
|
1425
|
+
let errorBody = {};
|
|
1363
1426
|
try {
|
|
1364
|
-
|
|
1365
|
-
if (
|
|
1427
|
+
errorBody = await res.json();
|
|
1428
|
+
if (errorBody.error?.message) detail = errorBody.error.message;
|
|
1366
1429
|
} catch {}
|
|
1430
|
+
const code = errorBody.error?.code;
|
|
1431
|
+
if (res.status === 403 && isSignupAccessErrorCode(code)) throw new SignupAccessError({
|
|
1432
|
+
code,
|
|
1433
|
+
serverMessage: detail,
|
|
1434
|
+
requestAccessUrl: errorBody.error?.details?.request_access_url,
|
|
1435
|
+
requestAccessEndpoint: errorBody.error?.details?.request_access_endpoint
|
|
1436
|
+
});
|
|
1367
1437
|
throw new Error(`Sandbox signup failed: ${detail}`);
|
|
1368
1438
|
}
|
|
1369
1439
|
let raw;
|
|
@@ -1963,7 +2033,8 @@ async function ensureDeveloperIdentity(options = {}) {
|
|
|
1963
2033
|
if (options.signupOnMissing === false) throw new Error(`No verified Amba identity at ${developerCredentialsPath(options.homeDir)} and signup-on-missing is disabled. Run \`amba login\` to authenticate.`);
|
|
1964
2034
|
const signup = await performSandboxSignup({
|
|
1965
2035
|
email: options.sandboxEmail?.trim() || generateSandboxEmail(),
|
|
1966
|
-
password: generateSandboxPassword()
|
|
2036
|
+
password: generateSandboxPassword(),
|
|
2037
|
+
inviteCode: options.inviteCode
|
|
1967
2038
|
}, {
|
|
1968
2039
|
apiUrl,
|
|
1969
2040
|
fetchImpl
|
|
@@ -2716,6 +2787,8 @@ Tell the user where their credentials live:
|
|
|
2716
2787
|
- \`project_id\`, \`client_key\` — the values they paste into their app's \`.env.local\` / \`.env\`.
|
|
2717
2788
|
- \`server_key\` — never ship to user devices; only into a server \`.env\` or a secret manager. The \`amb_dev_sk_\` / \`amb_live_sk_\` prefix is the marker.
|
|
2718
2789
|
|
|
2790
|
+
**Signup refused with 403 \`INVITE_REQUIRED\` or \`INVITE_INVALID\`?** New accounts need an invite code right now. Ask the developer for theirs (it starts with \`AMBA-\` and arrives in their invite email) and call \`amba_developer_signup\` again with the same arguments plus \`invite_code\`. If they have no code, or the answer is \`SIGNUPS_CLOSED\`, offer to request access for them: \`POST https://api.amba.dev/v1/auth/developer/waitlist\` with \`{ "email": "<their own email>", "note": "<optional>" }\`, or send them to the \`error.details.request_access_url\` page. Tell them the invite code arrives by email, then stop. Retry only with a code the developer hands you; never retry in a loop, invent codes, or try other email addresses. From a terminal, the code goes to \`npx @layers/amba init --invite <code>\` (or the \`AMBA_INVITE_CODE\` env var).
|
|
2791
|
+
|
|
2719
2792
|
**Already have a PAT?** Skip the signup. Call \`amba_developer_me({})\` to verify the Bearer; if it succeeds, either reuse the most recent project (\`amba_projects_list\`) or call \`amba_projects_create({ name: "<app-name>", platform: "all" })\` and then \`amba_api_keys_create\` twice to mint client + server keys for \`environment: "development"\`.
|
|
2720
2793
|
|
|
2721
2794
|
**Can't reach \`amba_developer_signup\` — it's not in your tool list, or your harness blocks the call?** Do NOT try to work around it by adding the MCP server yourself, running an installer, or editing your own settings/permissions — refusing to self-modify or widen your own permissions is the correct call, and you should not circumvent it. Instead, hand the developer **one** command to run in **their own** terminal (which is not subject to your sandbox):
|
|
@@ -2869,8 +2942,8 @@ Be specific. List resources by key, not "some achievements". If something needs
|
|
|
2869
2942
|
## Get credentials (cheat sheet)
|
|
2870
2943
|
|
|
2871
2944
|
- No terminal, in an MCP client: call \`amba_developer_signup\` (no Bearer required) — this guide's Step 0.
|
|
2872
|
-
- With a terminal: \`npx -y @layers/amba init\` signs up, mints a project + client/server keys, writes \`.env.local\` + \`AMBA.md\`, installs the \`/amba\` skill, and wires \`mcpServers.amba\` into every detected MCP-client config in one command. Auto-detects non-TTY invocations (the coding-agent bash-tool case) and runs headlessly.
|
|
2873
|
-
- Bind the sandbox account to
|
|
2945
|
+
- With a terminal: \`npx -y @layers/amba init\` signs up, mints a project + client/server keys, writes \`.env.local\` + \`AMBA.md\`, installs the \`/amba\` skill, and wires \`mcpServers.amba\` into every detected MCP-client config in one command. Auto-detects non-TTY invocations (the coding-agent bash-tool case) and runs headlessly. While signups are invite-only, add \`--invite <code>\` (or set \`AMBA_INVITE_CODE\`).
|
|
2946
|
+
- Bind the sandbox account to its human owner as soon as you know who that is: \`amba_developer_claim\` with their email (or \`npx @layers/amba claim me@example.com\`). The backend emails a one-click magic link; clicking it lifts the sandbox cap to the Free tier. Each free project of an unclaimed sandbox runs its own clock from when it was created: a claim notice from day 7, a hold on day 14, and an archive on day 90 after a full hold (archiving deletes nothing); claiming lifts all of it.
|
|
2874
2947
|
- Hosted MCP endpoint: \`https://mcp.amba.dev/mcp\` (Streamable HTTP, Bearer auth).
|
|
2875
2948
|
|
|
2876
2949
|
## SDKs
|
|
@@ -3882,14 +3955,21 @@ Docs: https://docs.amba.dev
|
|
|
3882
3955
|
async function initCommand(options = {}) {
|
|
3883
3956
|
const cwd = process.cwd();
|
|
3884
3957
|
const isNonTTY = process.stdin.isTTY !== true;
|
|
3885
|
-
if (options.sandbox === true || options.json === true || isNonTTY) {
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3958
|
+
if (options.sandbox === true || options.json === true || Boolean(options.inviteCode?.trim()) || isNonTTY) {
|
|
3959
|
+
let result;
|
|
3960
|
+
try {
|
|
3961
|
+
result = await runSandboxInit(cwd, {
|
|
3962
|
+
sandboxEmail: options.sandboxEmail,
|
|
3963
|
+
inviteCode: options.inviteCode,
|
|
3964
|
+
noMcpConfig: options.noMcpConfig,
|
|
3965
|
+
noSkills: options.noSkills,
|
|
3966
|
+
json: options.json,
|
|
3967
|
+
homeDir: options.homeDir
|
|
3968
|
+
});
|
|
3969
|
+
} catch (err) {
|
|
3970
|
+
if (options.json) process.stdout.write(JSON.stringify(sandboxErrorToJson(err), null, 2) + "\n");
|
|
3971
|
+
throw err;
|
|
3972
|
+
}
|
|
3893
3973
|
if (options.json) process.stdout.write(JSON.stringify(sandboxResultToJson(result), null, 2) + "\n");
|
|
3894
3974
|
else printSandboxNextSteps(result);
|
|
3895
3975
|
return;
|
|
@@ -4134,7 +4214,8 @@ async function runSandboxInit(cwd, options) {
|
|
|
4134
4214
|
};
|
|
4135
4215
|
} else identity = await ensureDeveloperIdentity({
|
|
4136
4216
|
homeDir: options.homeDir,
|
|
4137
|
-
sandboxEmail: options.sandboxEmail
|
|
4217
|
+
sandboxEmail: options.sandboxEmail,
|
|
4218
|
+
inviteCode: options.inviteCode
|
|
4138
4219
|
});
|
|
4139
4220
|
setBearerOverride(identity.credentials.pat);
|
|
4140
4221
|
const framework = await detectFramework(cwd);
|
|
@@ -4203,6 +4284,31 @@ async function runSandboxInit(cwd, options) {
|
|
|
4203
4284
|
setupTargets
|
|
4204
4285
|
};
|
|
4205
4286
|
}
|
|
4287
|
+
/**
|
|
4288
|
+
* `--json` failure document. A gated signup carries its 403 code plus
|
|
4289
|
+
* where to request access; every other failure reports `INIT_FAILED`.
|
|
4290
|
+
*/
|
|
4291
|
+
function sandboxErrorToJson(err) {
|
|
4292
|
+
if (err instanceof SignupAccessError) return {
|
|
4293
|
+
ok: false,
|
|
4294
|
+
mode: "sandbox",
|
|
4295
|
+
error: {
|
|
4296
|
+
code: err.code,
|
|
4297
|
+
message: err.serverMessage,
|
|
4298
|
+
request_access_url: err.requestAccessUrl,
|
|
4299
|
+
request_access_endpoint: err.requestAccessEndpoint
|
|
4300
|
+
},
|
|
4301
|
+
next_steps: err.nextSteps
|
|
4302
|
+
};
|
|
4303
|
+
return {
|
|
4304
|
+
ok: false,
|
|
4305
|
+
mode: "sandbox",
|
|
4306
|
+
error: {
|
|
4307
|
+
code: "INIT_FAILED",
|
|
4308
|
+
message: err instanceof Error ? err.message : String(err)
|
|
4309
|
+
}
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4206
4312
|
function sandboxResultToJson(r) {
|
|
4207
4313
|
return {
|
|
4208
4314
|
ok: true,
|
|
@@ -6861,6 +6967,28 @@ async function adminWrite$1(method, path, body) {
|
|
|
6861
6967
|
}
|
|
6862
6968
|
return await res.json();
|
|
6863
6969
|
}
|
|
6970
|
+
function pctLabel(pct) {
|
|
6971
|
+
return pct === null ? "" : pc.dim(` (${Math.round(pct * 100)}%)`);
|
|
6972
|
+
}
|
|
6973
|
+
/**
|
|
6974
|
+
* Print the database limits block. Exported for `amba status`'s one-line
|
|
6975
|
+
* summary + tests.
|
|
6976
|
+
*/
|
|
6977
|
+
function formatDatabaseLimits(db) {
|
|
6978
|
+
const lines = [];
|
|
6979
|
+
const computeUsed = db.compute.used_cu_hours === null ? "—" : `${db.compute.used_cu_hours}`;
|
|
6980
|
+
const computeLimit = db.compute.limit_cu_hours === null ? "no cap" : `${db.compute.limit_cu_hours} CU-hours`;
|
|
6981
|
+
lines.push(` ${pc.dim("Database compute".padEnd(22))} ${computeUsed} / ${computeLimit}${pctLabel(db.compute.pct)}` + pc.dim(` max ${db.compute.max_cu} CU`));
|
|
6982
|
+
const storageUsed = db.storage.used_mb === null ? "—" : `${db.storage.used_mb} MB`;
|
|
6983
|
+
const storageLimit = db.storage.limit_mb === null ? "no cap" : `${db.storage.limit_mb} MB`;
|
|
6984
|
+
lines.push(` ${pc.dim("Database size".padEnd(22))} ${storageUsed} / ${storageLimit}${pctLabel(db.storage.pct)}`);
|
|
6985
|
+
if (db.period && db.compute.limit_cu_hours !== null) lines.push(` ${pc.dim("Compute resets".padEnd(22))} ${new Date(db.period.resets_at).toLocaleDateString(void 0, { timeZone: "UTC" })} UTC`);
|
|
6986
|
+
const upgrade = db.upgrade?.cli ?? "amba billing upgrade --tier pro";
|
|
6987
|
+
if (db.limit_reached.includes("database_compute")) lines.push(pc.red(" Database paused: ") + `compute limit reached; database calls return 402 FREE_TIER_LIMIT_REACHED until the period resets. Upgrade now: ${pc.bold(upgrade)}`);
|
|
6988
|
+
if (db.limit_reached.includes("database_storage")) lines.push(pc.red(" Database full: ") + `storage limit reached; writes that add data return 402. Delete data or upgrade: ${pc.bold(upgrade)}`);
|
|
6989
|
+
else if (!db.limit_reached.includes("database_compute") && db.compute.pct !== null && db.compute.pct >= .8) lines.push(pc.yellow(" Near the compute limit: ") + `upgrade before the database pauses: ${pc.bold(upgrade)}`);
|
|
6990
|
+
return lines;
|
|
6991
|
+
}
|
|
6864
6992
|
/** Append "· $X.XX" to a usage line when the meter accrued cost this period. */
|
|
6865
6993
|
function costSuffix(cost) {
|
|
6866
6994
|
if (!cost || cost.cost_usd <= 0) return "";
|
|
@@ -6909,6 +7037,11 @@ async function billingStatusCommand() {
|
|
|
6909
7037
|
console.log(formatAxis("Database storage", s.headroom.db_storage_mb, true) + costSuffix(cost.db_storage_mb));
|
|
6910
7038
|
console.log(formatAxis("Media storage", s.headroom.media_storage_mb, true) + costSuffix(cost.media_storage_mb));
|
|
6911
7039
|
if (s.headroom.app_mcp_tool_calls) console.log(formatAxis("Agent tool calls", s.headroom.app_mcp_tool_calls, false) + costSuffix(cost.app_mcp_tool_calls));
|
|
7040
|
+
if (s.database) {
|
|
7041
|
+
console.log();
|
|
7042
|
+
console.log(pc.bold(" Database limits") + (s.database.observed_at ? pc.dim(` — measured ${new Date(s.database.observed_at).toLocaleString()}`) : ""));
|
|
7043
|
+
for (const line of formatDatabaseLimits(s.database)) console.log(line);
|
|
7044
|
+
}
|
|
6912
7045
|
console.log();
|
|
6913
7046
|
if (s.human_action_required !== "none") {
|
|
6914
7047
|
const action = s.human_action_required.replaceAll("_", " ");
|
|
@@ -6939,6 +7072,7 @@ async function billingUpgradeCommand(input) {
|
|
|
6939
7072
|
console.log(` ${pc.cyan(res.data.url)}`);
|
|
6940
7073
|
console.log();
|
|
6941
7074
|
console.log(pc.dim(" Subscription status updates automatically once Stripe confirms (a few seconds)."));
|
|
7075
|
+
console.log(pc.dim(" Paid plans lift the free-tier database compute cap; a paused database resumes within seconds."));
|
|
6942
7076
|
console.log();
|
|
6943
7077
|
} catch (err) {
|
|
6944
7078
|
handleError$1(err);
|
|
@@ -9811,7 +9945,7 @@ async function diffConfigCommand(file = DEFAULT_BUNDLE_FILE, options = {}) {
|
|
|
9811
9945
|
//#endregion
|
|
9812
9946
|
//#region src/index.ts
|
|
9813
9947
|
const program = new Command();
|
|
9814
|
-
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.
|
|
9948
|
+
program.name("amba").description("amba — agent-native backend-as-a-service for mobile apps.").version("4.1.9");
|
|
9815
9949
|
program.option("--token <pat>", "Use a Personal Access Token for headless / CI / agent use (overrides ~/.amba/credentials.json + AMBA_PAT env)");
|
|
9816
9950
|
program.hook("preAction", (thisCommand) => {
|
|
9817
9951
|
setBearerOverride(resolveTokenSource({
|
|
@@ -9826,7 +9960,7 @@ function runAction(fn) {
|
|
|
9826
9960
|
process.exit(1);
|
|
9827
9961
|
});
|
|
9828
9962
|
}
|
|
9829
|
-
program.command("init").description("Initialize Amba in the current project (mints a personal dev project by default)").option("--with-example", "Scaffold a sample app.tsx + README snippet into the current directory").option("--env <env>", "'development' (default) or 'production'").option("--sandbox", "Headless agentic mode: auto-signup, write .env.local + AMBA.md, auto-wire MCP client configs. No prompts.").option("--email <email>", "Override the auto-generated sandbox email (requires --sandbox)").option("--no-mcp-config", "Skip writing MCP client config files (rare; mostly for testing)").option("--no-skills", "Skip installing the project-local /amba-build Claude Code skill (default: install during --sandbox)").option("--json", "Emit a machine-readable JSON summary on stdout instead of human-readable lines").action(async (opts) => {
|
|
9963
|
+
program.command("init").description("Initialize Amba in the current project (mints a personal dev project by default)").option("--with-example", "Scaffold a sample app.tsx + README snippet into the current directory").option("--env <env>", "'development' (default) or 'production'").option("--sandbox", "Headless agentic mode: auto-signup, write .env.local + AMBA.md, auto-wire MCP client configs. No prompts.").option("--email <email>", "Override the auto-generated sandbox email (requires --sandbox)").option("--invite <code>", "Invite code for creating a new account while signups are invite-only (implies --sandbox; also read from AMBA_INVITE_CODE)").option("--no-mcp-config", "Skip writing MCP client config files (rare; mostly for testing)").option("--no-skills", "Skip installing the project-local /amba-build Claude Code skill (default: install during --sandbox)").option("--json", "Emit a machine-readable JSON summary on stdout instead of human-readable lines").action(async (opts) => {
|
|
9830
9964
|
let env;
|
|
9831
9965
|
if (opts.env === "development" || opts.env === "dev") env = "development";
|
|
9832
9966
|
else if (opts.env === "production" || opts.env === "prod") env = "production";
|
|
@@ -9834,27 +9968,29 @@ program.command("init").description("Initialize Amba in the current project (min
|
|
|
9834
9968
|
console.error(`Error: --env must be 'development' or 'production' (got '${opts.env}').`);
|
|
9835
9969
|
process.exit(1);
|
|
9836
9970
|
}
|
|
9837
|
-
|
|
9971
|
+
const sandbox = opts.sandbox === true || Boolean(opts.invite?.trim());
|
|
9972
|
+
if (opts.email && !sandbox) {
|
|
9838
9973
|
console.error("Error: --email is only valid with --sandbox.");
|
|
9839
9974
|
process.exit(1);
|
|
9840
9975
|
}
|
|
9841
|
-
if (opts.json && !
|
|
9976
|
+
if (opts.json && !sandbox) {
|
|
9842
9977
|
console.error("Error: --json is only valid with --sandbox.");
|
|
9843
9978
|
process.exit(1);
|
|
9844
9979
|
}
|
|
9845
|
-
if (opts.mcpConfig === false && !
|
|
9980
|
+
if (opts.mcpConfig === false && !sandbox) {
|
|
9846
9981
|
console.error("Error: --no-mcp-config is only valid with --sandbox.");
|
|
9847
9982
|
process.exit(1);
|
|
9848
9983
|
}
|
|
9849
|
-
if (opts.skills === false && !
|
|
9984
|
+
if (opts.skills === false && !sandbox) {
|
|
9850
9985
|
console.error("Error: --no-skills is only valid with --sandbox.");
|
|
9851
9986
|
process.exit(1);
|
|
9852
9987
|
}
|
|
9853
9988
|
await runAction(() => initCommand({
|
|
9854
9989
|
withExample: opts.withExample,
|
|
9855
9990
|
env,
|
|
9856
|
-
sandbox
|
|
9991
|
+
sandbox,
|
|
9857
9992
|
sandboxEmail: opts.email,
|
|
9993
|
+
inviteCode: opts.invite,
|
|
9858
9994
|
noMcpConfig: opts.mcpConfig === false,
|
|
9859
9995
|
noSkills: opts.skills === false,
|
|
9860
9996
|
json: opts.json
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -43,6 +43,12 @@ export interface SandboxSignupRequest {
|
|
|
43
43
|
email: string;
|
|
44
44
|
password: string;
|
|
45
45
|
name?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Platform invite code (`--invite`). Falls back to `AMBA_INVITE_CODE`
|
|
48
|
+
* when absent. Sent as `invite_code`; the server ignores case, spaces
|
|
49
|
+
* and dashes.
|
|
50
|
+
*/
|
|
51
|
+
inviteCode?: string;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
54
|
* Shape returned by `POST /v1/auth/developer/signup`. Only the fields the
|
|
@@ -148,6 +154,34 @@ export declare function generateSandboxEmail(): string;
|
|
|
148
154
|
export declare function generateSandboxPassword(): string;
|
|
149
155
|
/** Request header carrying AMBA_SIGNUP_TOKEN on the signup call. */
|
|
150
156
|
export declare const SIGNUP_TOKEN_HEADER = "X-Amba-Signup-Token";
|
|
157
|
+
/** Environment variable read when `--invite` is absent. */
|
|
158
|
+
export declare const INVITE_CODE_ENV = "AMBA_INVITE_CODE";
|
|
159
|
+
/** The 403 codes signup answers with when the platform is gated. */
|
|
160
|
+
export declare const SIGNUP_ACCESS_ERROR_CODES: readonly ["SIGNUPS_CLOSED", "INVITE_REQUIRED", "INVITE_INVALID"];
|
|
161
|
+
export type SignupAccessErrorCode = (typeof SIGNUP_ACCESS_ERROR_CODES)[number];
|
|
162
|
+
/**
|
|
163
|
+
* The flag value when set, else `AMBA_INVITE_CODE`, else undefined. Blank
|
|
164
|
+
* values count as absent so an empty env var never sends `invite_code: ""`.
|
|
165
|
+
*/
|
|
166
|
+
export declare function resolveInviteCode(flag?: string): string | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Thrown when signup answers 403 SIGNUPS_CLOSED / INVITE_REQUIRED /
|
|
169
|
+
* INVITE_INVALID. `message` carries the server's sentence plus the next
|
|
170
|
+
* steps; the structured fields feed `amba init --json`.
|
|
171
|
+
*/
|
|
172
|
+
export declare class SignupAccessError extends Error {
|
|
173
|
+
readonly code: SignupAccessErrorCode;
|
|
174
|
+
readonly serverMessage: string;
|
|
175
|
+
readonly requestAccessUrl: string;
|
|
176
|
+
readonly requestAccessEndpoint: string;
|
|
177
|
+
readonly nextSteps: string[];
|
|
178
|
+
constructor(input: {
|
|
179
|
+
code: SignupAccessErrorCode;
|
|
180
|
+
serverMessage: string;
|
|
181
|
+
requestAccessUrl?: string;
|
|
182
|
+
requestAccessEndpoint?: string;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
151
185
|
/**
|
|
152
186
|
* POST the synthesized credentials at the public signup endpoint.
|
|
153
187
|
*
|
|
@@ -159,7 +193,8 @@ export declare const SIGNUP_TOKEN_HEADER = "X-Amba-Signup-Token";
|
|
|
159
193
|
* Returns the unwrapped, flattened shape consumed by the rest of the
|
|
160
194
|
* sandbox flow. Throws with a human-readable message on any non-2xx so
|
|
161
195
|
* the CLI's `runAction` wrapper can surface it without crashing on a
|
|
162
|
-
* generic 'fetch failed'.
|
|
196
|
+
* generic 'fetch failed'. A gated platform (403 SIGNUPS_CLOSED /
|
|
197
|
+
* INVITE_REQUIRED / INVITE_INVALID) throws `SignupAccessError`.
|
|
163
198
|
*/
|
|
164
199
|
export declare function performSandboxSignup(req: SandboxSignupRequest, options?: {
|
|
165
200
|
apiUrl?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layers/amba",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.9",
|
|
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-mcp": "4.0.12",
|
|
53
|
+
"@layers/amba-shared": "4.0.6"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsdown && tsc --emitDeclarationOnly",
|
package/skill-bundle/SKILL.md
CHANGED
|
@@ -69,7 +69,9 @@ Check whether a project exists in this directory:
|
|
|
69
69
|
|
|
70
70
|
The project status is `"provisioning"` immediately after signup; the per-tenant DB activates within ~5 seconds. Don't block — your next SDK or MCP call may briefly retry, that's fine. If you want to be polite, call `amba_projects_get_provisioning_status({ project_id })` once and proceed when it returns `"active"` (or after 15s, whichever first).
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
**Signup refused with 403 `INVITE_REQUIRED` or `INVITE_INVALID`?** New accounts need an invite code right now. Ask the developer for theirs (it starts with `AMBA-` and arrives in their invite email) and call `amba_developer_signup` again with the same arguments plus `invite_code` (or rerun `npx -y @layers/amba init --invite <code>`; `AMBA_INVITE_CODE` works too). If they have no code, or the answer is `SIGNUPS_CLOSED`, offer to request access for them: `POST https://api.amba.dev/v1/auth/developer/waitlist` with `{ "email": "<their own email>", "note": "<optional>" }`, or send them to the `error.details.request_access_url` page. Tell them the invite code arrives by email, then stop. Retry only with a code the developer hands you; never retry in a loop, invent codes, or try other email addresses.
|
|
73
|
+
|
|
74
|
+
> **If you don't see Amba MCP tools available** (no `mcp__amba__*` in your tool list), Amba's MCP server isn't wired into your agent's tool surface this session. Run `npx -y @layers/amba init` via your bash tool — it provisions the account/project, writes `.env.local`, installs this skill, and wires the MCP entry into the user's agent config so the typed tools light up on the next agent launch. While signups are invite-only, add `--invite <code>` with the developer's invite code; without one, init stops and prints where to request access.
|
|
73
75
|
>
|
|
74
76
|
> **If your harness ALSO blocks that bash command** (a locked-down agent: no installer, no network egress, no settings edits) — do NOT try to add the MCP server yourself, run the installer another way, or edit your own settings/permissions to authorize it. Refusing to self-modify or widen your own permissions is correct; don't circumvent it. Instead, ask the developer to run **one** command in **their own** terminal (not subject to your sandbox): `npx @layers/amba init`. It creates the account with no browser and writes `AMBA_PROJECT_ID` + `AMBA_CLIENT_KEY` to `.env.local`. Ask them to run it, say when it's done, then resume from Step 0 by reading `.env.local`. Pasting an `amb_ck_…` client key is the last-resort lite path — enough to wire `Amba.configure()` + verify events land, but a client key alone can't do admin provisioning (achievements, collections, segments), which needs the PAT/server key.
|
|
75
77
|
>
|