@layers/amba 4.0.3 → 4.1.0
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 +3 -3
- package/dist/api-client.d.ts +36 -1
- package/dist/bundle.d.ts +86 -0
- package/dist/commands/collections.d.ts +55 -0
- package/dist/commands/functions-logs.d.ts +13 -0
- package/dist/commands/functions.d.ts +21 -2
- package/dist/commands/iac.d.ts +116 -0
- package/dist/commands/monetization.d.ts +46 -0
- package/dist/commands/payments.d.ts +33 -0
- package/dist/commands/ship.d.ts +47 -0
- package/dist/index.js +2923 -188
- package/dist/ship/config.d.ts +98 -0
- package/dist/ship/exec.d.ts +53 -0
- package/dist/ship/orchestrator.d.ts +52 -0
- package/dist/ship/phases.d.ts +87 -0
- package/dist/ship/state.d.ts +52 -0
- package/package.json +4 -4
- package/skill-bundle/SKILL.md +2 -0
- package/skill-bundle/references/infrastructure.md +20 -17
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba ship` config — the typed, hand-validated launch config.
|
|
3
|
+
*
|
|
4
|
+
* One file per repo (default `amba.ship.json`) describes how to take this Expo
|
|
5
|
+
* app from code-complete to live on the App Store and Google Play. It carries
|
|
6
|
+
* NON-SECRET identifiers and the NAME of the env var holding the Expo token —
|
|
7
|
+
* never secret values (the App Store Connect `.p8` and Play service-account
|
|
8
|
+
* JSON live in `eas.json`, owned by the app repo, exactly where the build runs).
|
|
9
|
+
*
|
|
10
|
+
* Validation is hand-rolled (the CLI deliberately ships no `zod` — see
|
|
11
|
+
* `_internal/shared.ts` for the same return-all-errors-at-once pattern). The
|
|
12
|
+
* field names are the canonical contract; `validateShipConfig` reports every
|
|
13
|
+
* problem in one pass and enforces the cross-field invariants.
|
|
14
|
+
*
|
|
15
|
+
* Monetization is NOT described here: the App Store / Play product catalog is
|
|
16
|
+
* Amba's monetization control plane (`amba monetization`, the durable Track H
|
|
17
|
+
* apply against the provider). `ship` only flips `monetization.enabled` to
|
|
18
|
+
* decide whether to verify that catalog is in sync before building.
|
|
19
|
+
*/
|
|
20
|
+
/** The ordered launch phases. `ship` runs them in this sequence. */
|
|
21
|
+
export declare const SHIP_PHASES: readonly ["preflight", "monetization", "build", "submit", "metadata", "release"];
|
|
22
|
+
export type Phase = (typeof SHIP_PHASES)[number];
|
|
23
|
+
export declare const SHIP_PLATFORMS: readonly ["ios", "android"];
|
|
24
|
+
export type Platform = (typeof SHIP_PLATFORMS)[number];
|
|
25
|
+
/** Google Play release tracks `ship` can submit to. */
|
|
26
|
+
export declare const PLAY_TRACKS: readonly ["internal", "alpha", "beta", "production"];
|
|
27
|
+
export type PlayTrack = (typeof PLAY_TRACKS)[number];
|
|
28
|
+
/** Default config filename when no `--config <path>` is passed. */
|
|
29
|
+
export declare const DEFAULT_SHIP_CONFIG_FILE = "amba.ship.json";
|
|
30
|
+
export interface ShipConfig {
|
|
31
|
+
/** Schema version of this config file (forward-compatible migrations). */
|
|
32
|
+
version: number;
|
|
33
|
+
app: {
|
|
34
|
+
/** Lowercase key used to scope the per-app launch state file. */
|
|
35
|
+
slug: string;
|
|
36
|
+
/** Human-facing app name (App Store / Play display). */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Marketing version MAJOR.MINOR.PATCH. */
|
|
39
|
+
version: string;
|
|
40
|
+
/** iOS bundle identifier (reverse-DNS). */
|
|
41
|
+
bundleId: string;
|
|
42
|
+
/** Android application id (reverse-DNS); may equal bundleId. */
|
|
43
|
+
androidPackage: string;
|
|
44
|
+
};
|
|
45
|
+
build: {
|
|
46
|
+
/** EAS build profile (a `build.<profile>` key in eas.json), e.g. "production". */
|
|
47
|
+
profile: string;
|
|
48
|
+
/** Platforms to build/submit/release. At least one. */
|
|
49
|
+
platforms: Platform[];
|
|
50
|
+
};
|
|
51
|
+
ios: {
|
|
52
|
+
/**
|
|
53
|
+
* App Store Connect numeric app id. `null` until the app record exists.
|
|
54
|
+
* EAS Submit cannot create the record (and under --non-interactive cannot
|
|
55
|
+
* prompt for it, eas-cli #2418), so `ship` treats a null value as a manual
|
|
56
|
+
* gate on the submit phase.
|
|
57
|
+
*/
|
|
58
|
+
ascAppId: string | null;
|
|
59
|
+
};
|
|
60
|
+
android: {
|
|
61
|
+
/** Track the submit phase lands on; release promotes it to production. */
|
|
62
|
+
track: PlayTrack;
|
|
63
|
+
};
|
|
64
|
+
monetization: {
|
|
65
|
+
/**
|
|
66
|
+
* When true, `ship` verifies the App Store / Play product catalog is in
|
|
67
|
+
* sync with the Amba monetization control plane before building, and gates
|
|
68
|
+
* if it has drifted (run `amba monetization apply`). When false (the
|
|
69
|
+
* default for apps that don't sell anything), the monetization phase is a
|
|
70
|
+
* no-op skip.
|
|
71
|
+
*/
|
|
72
|
+
enabled: boolean;
|
|
73
|
+
};
|
|
74
|
+
secrets: {
|
|
75
|
+
/** NAME of the env var holding the Expo token for non-interactive EAS auth. */
|
|
76
|
+
expoTokenEnvVar: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export type ValidateResult = {
|
|
80
|
+
ok: true;
|
|
81
|
+
config: ShipConfig;
|
|
82
|
+
} | {
|
|
83
|
+
ok: false;
|
|
84
|
+
errors: string[];
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Validate an untrusted parsed JSON value into a typed `ShipConfig`,
|
|
88
|
+
* collecting EVERY problem (not just the first) so a misconfigured file
|
|
89
|
+
* surfaces all its issues in one run. Returns the typed config on success.
|
|
90
|
+
*/
|
|
91
|
+
export declare function validateShipConfig(raw: unknown): ValidateResult;
|
|
92
|
+
/**
|
|
93
|
+
* A starter config written by `amba ship init`. Placeholders are intentionally
|
|
94
|
+
* obvious so the validator's invariants don't pass on an unedited file's IDs
|
|
95
|
+
* (the reverse-DNS / semver placeholders DO validate so `--phase preflight`
|
|
96
|
+
* works immediately; the operator swaps in their real values).
|
|
97
|
+
*/
|
|
98
|
+
export declare function shipConfigTemplate(slug: string): ShipConfig;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The external-command boundary for `amba ship`.
|
|
3
|
+
*
|
|
4
|
+
* Every shell-out (always `eas`) goes through an `Executor`, so the phase
|
|
5
|
+
* logic — which args get built, how `--json` output is parsed, which failures
|
|
6
|
+
* map to which manual gate — is unit-tested against a fake executor with NO
|
|
7
|
+
* live cloud, no EAS account, and no Apple/Google credentials. The default
|
|
8
|
+
* `createNodeExecutor()` is a thin, shell-free wrapper around
|
|
9
|
+
* `node:child_process.spawn` (mirrors the launch toolkit's `lib/exec`).
|
|
10
|
+
*/
|
|
11
|
+
export interface ExecRunOptions {
|
|
12
|
+
/** Extra environment variables merged over process.env. */
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
/** Working directory for the child. Defaults to process.cwd(). */
|
|
15
|
+
cwd?: string;
|
|
16
|
+
/** Hard timeout in ms. On expiry the child is SIGKILLed and the call rejects. */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
/** If true, stream child stdout/stderr live to this process. Default false. */
|
|
19
|
+
stream?: boolean;
|
|
20
|
+
/** If true, log the command and return a synthetic success WITHOUT running. */
|
|
21
|
+
dryRun?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ExecResult {
|
|
24
|
+
/** The redacted command line, for logging / state. */
|
|
25
|
+
command: string;
|
|
26
|
+
code: number;
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
/** True when the call was skipped because of dry-run. */
|
|
30
|
+
dryRun: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface Executor {
|
|
33
|
+
/** Run a command. Resolves on exit 0; REJECTS (with an actionable message) otherwise. */
|
|
34
|
+
run(cmd: string, args: string[], opts?: ExecRunOptions): Promise<ExecResult>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Redact secret-shaped tokens from a string before it is logged. A backstop,
|
|
38
|
+
* not a license — `ship` never puts secret values on a command line (EAS reads
|
|
39
|
+
* its token from the env and its store creds from eas.json). Covers Expo /
|
|
40
|
+
* common access-token shapes and `Bearer`/`sk_` prefixes.
|
|
41
|
+
*/
|
|
42
|
+
export declare function redactSecrets(text: string): string;
|
|
43
|
+
/** A backing executor over `node:child_process.spawn`. Never invokes a shell. */
|
|
44
|
+
export declare function createNodeExecutor(globalDryRun?: boolean): Executor;
|
|
45
|
+
/**
|
|
46
|
+
* Run a command and report success/failure WITHOUT throwing — for read-only
|
|
47
|
+
* "is this CLI installed/authed?" probes where a non-zero exit is data, not a
|
|
48
|
+
* fatal error.
|
|
49
|
+
*/
|
|
50
|
+
export declare function probe(exec: Executor, cmd: string, args: string[], opts?: ExecRunOptions): Promise<{
|
|
51
|
+
ok: boolean;
|
|
52
|
+
result?: ExecResult;
|
|
53
|
+
}>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `amba ship` orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Sequences the requested phases in canonical order, honoring per-app
|
|
5
|
+
* idempotency state (skip phases already `done` unless `--force`), propagating
|
|
6
|
+
* a global `--dry-run` to every phase, and stopping the chain at the first gate
|
|
7
|
+
* or failure (later phases depend on earlier ones — the "clear a gate → re-run"
|
|
8
|
+
* loop). It owns ALL state writes and the clock; the phase runners stay pure of
|
|
9
|
+
* both. Returns structured rows for the command layer to render.
|
|
10
|
+
*/
|
|
11
|
+
import type { Phase, Platform, ShipConfig } from './config.js';
|
|
12
|
+
import type { Executor } from './exec.js';
|
|
13
|
+
import { type PhaseRunner } from './phases.js';
|
|
14
|
+
export type RowStatus = 'done' | 'skipped' | 'gate' | 'failed' | 'pending' | 'dry-run';
|
|
15
|
+
export interface ShipRow {
|
|
16
|
+
phase: Phase;
|
|
17
|
+
status: RowStatus;
|
|
18
|
+
detail: string;
|
|
19
|
+
gates?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface ShipResult {
|
|
22
|
+
rows: ShipRow[];
|
|
23
|
+
/** A gate stopped the chain (human store action needed) — not an error. */
|
|
24
|
+
blocked: boolean;
|
|
25
|
+
/** A phase errored. */
|
|
26
|
+
failed: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ShipRunOptions {
|
|
29
|
+
config: ShipConfig;
|
|
30
|
+
/** Which phases to run (any subset of SHIP_PHASES). */
|
|
31
|
+
phases: Phase[];
|
|
32
|
+
platforms: Platform[];
|
|
33
|
+
exec: Executor;
|
|
34
|
+
dryRun: boolean;
|
|
35
|
+
force: boolean;
|
|
36
|
+
stateBaseDir?: string;
|
|
37
|
+
env?: NodeJS.ProcessEnv;
|
|
38
|
+
now?: () => string;
|
|
39
|
+
log?: (line: string) => void;
|
|
40
|
+
sleep?: (ms: number) => Promise<void>;
|
|
41
|
+
/** Resolve the linked Amba project id (default: from .env.local). */
|
|
42
|
+
resolveProjectId?: () => Promise<string | null>;
|
|
43
|
+
/** Admin GET (default: the real api-client). Injected in tests. */
|
|
44
|
+
adminGet?: <T>(path: string) => Promise<{
|
|
45
|
+
data: T;
|
|
46
|
+
}>;
|
|
47
|
+
/** Phase runners (default: the real registry). Injected in tests. */
|
|
48
|
+
runners?: Record<Phase, PhaseRunner>;
|
|
49
|
+
releasePollMs?: number;
|
|
50
|
+
releaseMaxWaitMs?: number;
|
|
51
|
+
}
|
|
52
|
+
export declare function runShip(opts: ShipRunOptions): Promise<ShipResult>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six `amba ship` phases.
|
|
3
|
+
*
|
|
4
|
+
* Each runner receives a `ShipContext` (config + an injected `Executor` for
|
|
5
|
+
* `eas`, an injected admin-API `adminGet`, a project resolver, and state
|
|
6
|
+
* accessors) and returns a `PhaseOutcome`. The cloud-touching work is all
|
|
7
|
+
* behind those injection points, so the arg-building, `--json` parsing, and
|
|
8
|
+
* failure→manual-gate mapping below are unit-tested with no live cloud.
|
|
9
|
+
*
|
|
10
|
+
* Honest gates: several launch steps are NOT API-automatable (create the App
|
|
11
|
+
* Store Connect / Play app record, the Apple Paid-Apps agreement, screenshots,
|
|
12
|
+
* store review). A runner DETECTS such a step and returns `status: 'gate'` with
|
|
13
|
+
* the exact action + console URL, rather than pretending it is automated.
|
|
14
|
+
*/
|
|
15
|
+
import type { Executor } from './exec.js';
|
|
16
|
+
import type { Phase, Platform, ShipConfig } from './config.js';
|
|
17
|
+
import type { PhaseStatus } from './state.js';
|
|
18
|
+
export interface ShipContext {
|
|
19
|
+
config: ShipConfig;
|
|
20
|
+
exec: Executor;
|
|
21
|
+
platforms: Platform[];
|
|
22
|
+
dryRun: boolean;
|
|
23
|
+
force: boolean;
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
now: () => string;
|
|
26
|
+
log: (line: string) => void;
|
|
27
|
+
/** Read a phase's persisted note (a JSON string) or null. */
|
|
28
|
+
getNote: (phase: Phase) => string | null;
|
|
29
|
+
/** Checkpoint a phase's status + note mid-run. No-op under dryRun. */
|
|
30
|
+
checkpoint: (phase: Phase, status: PhaseStatus, note: string) => void;
|
|
31
|
+
/** Resolve the linked Amba project id, or null when no project is linked. */
|
|
32
|
+
resolveProjectId: () => Promise<string | null>;
|
|
33
|
+
/** GET an admin API path → `{ data }`. */
|
|
34
|
+
adminGet: <T>(path: string) => Promise<{
|
|
35
|
+
data: T;
|
|
36
|
+
}>;
|
|
37
|
+
/** Sleep helper (injected so release-polling is instant in tests). */
|
|
38
|
+
sleep: (ms: number) => Promise<void>;
|
|
39
|
+
/** Build-completion poll cadence (release phase). Small values in tests. */
|
|
40
|
+
releasePollMs: number;
|
|
41
|
+
releaseMaxWaitMs: number;
|
|
42
|
+
}
|
|
43
|
+
export interface PhaseOutcome {
|
|
44
|
+
status: 'done' | 'gate' | 'failed' | 'skipped';
|
|
45
|
+
detail: string;
|
|
46
|
+
/** Structured JSON note to persist (build/submit/release maps). */
|
|
47
|
+
note?: string;
|
|
48
|
+
/** Human-action items when status === 'gate' (printed prominently). */
|
|
49
|
+
gates?: string[];
|
|
50
|
+
}
|
|
51
|
+
export type PhaseRunner = (ctx: ShipContext) => Promise<PhaseOutcome>;
|
|
52
|
+
export declare function easBuildArgs(platform: Platform, profile: string): string[];
|
|
53
|
+
export declare function easSubmitArgs(input: {
|
|
54
|
+
platform: Platform;
|
|
55
|
+
profile: string;
|
|
56
|
+
buildId: string | null;
|
|
57
|
+
track?: string;
|
|
58
|
+
}): string[];
|
|
59
|
+
export declare function easBuildViewArgs(buildId: string): string[];
|
|
60
|
+
export declare function easMetadataArgs(profile: string): string[];
|
|
61
|
+
export interface EasBuild {
|
|
62
|
+
id: string;
|
|
63
|
+
status?: string;
|
|
64
|
+
platform?: string;
|
|
65
|
+
appVersion?: string;
|
|
66
|
+
artifacts?: {
|
|
67
|
+
applicationArchiveUrl?: string;
|
|
68
|
+
buildUrl?: string;
|
|
69
|
+
};
|
|
70
|
+
buildDetailsPageUrl?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface EasSubmission {
|
|
73
|
+
id?: string;
|
|
74
|
+
status?: string;
|
|
75
|
+
platform?: string;
|
|
76
|
+
buildId?: string;
|
|
77
|
+
submissionDetailsPageUrl?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Pull the build object for `platform` out of `eas build --json` stdout. */
|
|
80
|
+
export declare function extractBuild(raw: string, platform: Platform): EasBuild;
|
|
81
|
+
export declare function artifactUrlOf(b: EasBuild): string | null;
|
|
82
|
+
/** Parse the submission object for `platform` out of `eas submit --json` stdout. */
|
|
83
|
+
export declare function parseSubmission(raw: string, platform: Platform): EasSubmission;
|
|
84
|
+
export declare function diagnoseBuildFailure(message: string): string;
|
|
85
|
+
/** A submit failure that needs a human store action, or null for a plain error. */
|
|
86
|
+
export declare function diagnoseSubmitGate(platform: Platform, message: string): string | null;
|
|
87
|
+
export declare const PHASE_RUNNERS: Record<Phase, PhaseRunner>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `amba ship` per-app idempotency state.
|
|
3
|
+
*
|
|
4
|
+
* Each app's launch progress is a JSON file at `.amba/ship-state/<slug>.json`
|
|
5
|
+
* recording, per phase, whether it is done / failed / blocked-by-a-human-gate /
|
|
6
|
+
* in-progress, when, and an optional structured `note` (e.g. the build ids a
|
|
7
|
+
* later phase reads back). The orchestrator uses it to skip already-done phases
|
|
8
|
+
* and to resume the "clear a gate → re-run" loop.
|
|
9
|
+
*
|
|
10
|
+
* PURITY RULE: this module NEVER reads the clock itself — callers pass an
|
|
11
|
+
* ISO-8601 timestamp into every mutation so it stays deterministically
|
|
12
|
+
* testable (the orchestrator owns the clock). The file is purely an
|
|
13
|
+
* optimization/audit log: deleting it is safe because every phase re-detects
|
|
14
|
+
* real cloud state before mutating.
|
|
15
|
+
*/
|
|
16
|
+
import type { Phase } from './config.js';
|
|
17
|
+
export type PhaseStatus = 'pending' | 'in-progress' | 'done' | 'failed' | 'gate';
|
|
18
|
+
export interface PhaseRecord {
|
|
19
|
+
status: PhaseStatus;
|
|
20
|
+
/** ISO timestamp of the last status change (supplied by the caller). */
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
/** Free-form note: build ids, a short reason, etc. NEVER secrets. */
|
|
23
|
+
note?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ShipState {
|
|
26
|
+
slug: string;
|
|
27
|
+
/** Schema version of this state file (forward-compatible migrations). */
|
|
28
|
+
stateVersion: 1;
|
|
29
|
+
/**
|
|
30
|
+
* The app marketing version the recorded phases shipped. When it changes, the
|
|
31
|
+
* orchestrator invalidates the binary-scoped phases (build → release) so a
|
|
32
|
+
* version bump rebuilds instead of skipping a stale `done`.
|
|
33
|
+
*/
|
|
34
|
+
appVersion?: string;
|
|
35
|
+
phases: Partial<Record<Phase, PhaseRecord>>;
|
|
36
|
+
}
|
|
37
|
+
/** Path to the state file for a slug, under `<baseDir>/.amba/ship-state/`. */
|
|
38
|
+
export declare function shipStatePath(slug: string, baseDir?: string): string;
|
|
39
|
+
/** Load state for a slug, or a fresh empty state if none exists. Pure read. */
|
|
40
|
+
export declare function loadShipState(slug: string, baseDir?: string): ShipState;
|
|
41
|
+
/** Write state atomically (write temp, then rename) to avoid partial files. */
|
|
42
|
+
export declare function saveShipState(state: ShipState, baseDir?: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Return a NEW state with one phase updated. Pure: does not touch disk and does
|
|
45
|
+
* not read the clock — the caller passes `timestamp`. Use `saveShipState` to
|
|
46
|
+
* persist.
|
|
47
|
+
*/
|
|
48
|
+
export declare function withPhase(state: ShipState, phase: Phase, status: PhaseStatus, timestamp: string, note?: string): ShipState;
|
|
49
|
+
/** True if a phase is recorded as done. Used by the orchestrator for idempotency. */
|
|
50
|
+
export declare function isPhaseDone(state: ShipState, phase: Phase): boolean;
|
|
51
|
+
/** The record for a phase, or a synthetic pending record if never run. */
|
|
52
|
+
export declare function phaseRecord(state: ShipState, phase: Phase): PhaseRecord;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layers/amba",
|
|
3
|
-
"version": "4.0
|
|
4
|
-
"description": "amba — agent-native backend-as-a-service. Functions, collections, storage, AI, email, queues, sites
|
|
3
|
+
"version": "4.1.0",
|
|
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": {
|
|
7
7
|
"amba": "dist/index.js"
|
|
@@ -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.6"
|
|
54
54
|
},
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsdown && tsc --emitDeclarationOnly",
|
package/skill-bundle/SKILL.md
CHANGED
|
@@ -71,6 +71,8 @@ Check whether a project exists in this directory:
|
|
|
71
71
|
|
|
72
72
|
> **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.
|
|
73
73
|
>
|
|
74
|
+
> **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
|
+
>
|
|
74
76
|
> Do not pause to ask the user about restarting. You have **three** working paths for admin operations in this session, in order of preference:
|
|
75
77
|
>
|
|
76
78
|
> 1. **POST JSON-RPC directly to `https://mcp.amba.dev/mcp`** (recommended — full 178-tool surface, no client wiring needed). Amba's MCP is plain HTTP, not stdio. Read the PAT from `~/.amba/credentials.json` (`.pat` field), then `curl` (or `fetch`) the endpoint:
|
|
@@ -15,7 +15,7 @@ A collection is a schema-first table inside the project's isolated tenant databa
|
|
|
15
15
|
| `amba_collections_create` / `amba_create_collection` | Create a typed collection. | `{ project_id, name: "todos", columns: [{ name: "title", type: "text", nullable: false }, { name: "done", type: "boolean", nullable: false, default: false }, { name: "due_at", type: "timestamptz", nullable: true }] }` |
|
|
16
16
|
| `amba_collections_list` / `amba_list_collections` | List collections in this project. | `{ project_id }` |
|
|
17
17
|
| `amba_collections_get` / `amba_get_collection` | Read one collection's schema. | `{ project_id, collection_name: "todos" }` |
|
|
18
|
-
| `amba_collections_alter` / `amba_alter_collection` | Add / drop columns, add / drop indexes. | `{ project_id, collection_name: "todos", add_columns: [{ name: "priority", type: "
|
|
18
|
+
| `amba_collections_alter` / `amba_alter_collection` | Add / drop columns, add / drop indexes. | `{ project_id, collection_name: "todos", add_columns: [{ name: "priority", type: "integer", nullable: true }] }` |
|
|
19
19
|
| `amba_collections_delete` / `amba_delete_collection` | Drop the table (destructive). | `{ project_id, collection_name }` |
|
|
20
20
|
| `amba_admin_insert_row` | Insert a row as the developer (bypasses user-scope). Useful for seed data. | `{ project_id, collection: "todos", row: { title: "Sample todo", done: false } }` |
|
|
21
21
|
| `amba_admin_list_rows` | Read rows as the developer (bypasses user-scope — sees every user's rows). | `{ project_id, collection: "todos", limit: 100 }` |
|
|
@@ -28,7 +28,7 @@ A collection is a schema-first table inside the project's isolated tenant databa
|
|
|
28
28
|
| `amba_client_find_rows` | Filter / sort / paginate rows (end-user). | `{ project_id, api_key, session_token, collection, filter: {...}, order_by: [...], limit: 50 }` |
|
|
29
29
|
| `amba_client_find_nearest_rows` | Vector-similarity search (rows with a `vector(<dim>)` column). | `{ project_id, api_key, session_token, collection, vector_column: "embedding", query_vector: [...], k: 10 }` |
|
|
30
30
|
|
|
31
|
-
Column types: `text`, `
|
|
31
|
+
Column types: `text`, `integer`, `bigint`, `numeric`, `boolean`, `timestamptz`, `date`, `jsonb`, `uuid`, `vector(<dim>)` (e.g. `vector(1536)` for OpenAI embeddings), plus array forms `text[]`, `integer[]`, `bigint[]`, `numeric[]`, `boolean[]`, `uuid[]`. Use `integer` (not `int`), `numeric` (not `float`/`real`/`double`), and `jsonb` (not `json`) — the validator rejects the aliases.
|
|
32
32
|
|
|
33
33
|
### Functions (serverless code)
|
|
34
34
|
|
|
@@ -48,16 +48,21 @@ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function get
|
|
|
48
48
|
|
|
49
49
|
### AI prompts
|
|
50
50
|
|
|
51
|
-
Managed LLM templates: stored prompt with model + system message
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
**Two steps, in order:** register the provider key with `amba_ai_providers_set`, then create prompts against it. A prompt registered before its provider has a key still saves, but invocations fail with `provider_not_configured` (424) until the key is set. The provider key is **not** a function secret — `amba_secrets_set` writes function-scoped Worker secrets the AI gateway never reads; provider keys live in a separate gateway-owned store set only via `amba_ai_providers_set`.
|
|
52
54
|
|
|
53
55
|
| Tool | Purpose | Example args |
|
|
54
56
|
| --- | --- | --- |
|
|
55
|
-
| `
|
|
57
|
+
| `amba_ai_providers_set` | Register / rotate the upstream provider API key. **Do this first.** | `{ project_id, provider: "anthropic", api_key: "sk-ant-..." }` |
|
|
58
|
+
| `amba_ai_providers_list` | List registered providers (`configured` = key set). | `{ project_id }` |
|
|
59
|
+
| `amba_ai_providers_delete` | Revoke a provider key (fails if prompts still reference it). | `{ project_id, provider: "anthropic" }` |
|
|
60
|
+
| `amba_ai_prompts_create` / `amba_create_ai_prompt` | Create a prompt template. | `{ project_id, name: "summarize", provider: "anthropic", model: "claude-opus-4-5", system_prompt: "Summarize the user's text in 2 sentences.", client_invokable: true }` |
|
|
56
61
|
| `amba_ai_prompts_list` / `amba_list_ai_prompts` | List prompts. | `{ project_id }` |
|
|
57
|
-
| `amba_ai_prompts_get` / `amba_get_ai_prompt` | Read one prompt. | `{ project_id,
|
|
58
|
-
| `amba_ai_prompts_update` / `amba_update_ai_prompt` | Edit a prompt (
|
|
59
|
-
| `amba_ai_prompts_invoke` / `amba_invoke_ai_prompt` | Invoke a prompt server-side (
|
|
60
|
-
| `amba_ai_prompts_delete` / `amba_delete_ai_prompt` | Delete. | `{ project_id,
|
|
62
|
+
| `amba_ai_prompts_get` / `amba_get_ai_prompt` | Read one prompt. | `{ project_id, name }` |
|
|
63
|
+
| `amba_ai_prompts_update` / `amba_update_ai_prompt` | Edit a prompt (replaces all fields; bumps version). | `{ project_id, name, provider, model, system_prompt: "..." }` |
|
|
64
|
+
| `amba_ai_prompts_invoke` / `amba_invoke_ai_prompt` | Invoke a prompt server-side (admin testing). `messages` is a provider-shaped array. | `{ project_id, name, messages: [{ role: "user", content: "..." }] }` |
|
|
65
|
+
| `amba_ai_prompts_delete` / `amba_delete_ai_prompt` | Delete. | `{ project_id, name }` |
|
|
61
66
|
|
|
62
67
|
### Analytics + events + sessions
|
|
63
68
|
|
|
@@ -75,7 +80,7 @@ Managed LLM templates: stored prompt with model + system message + variables, ca
|
|
|
75
80
|
|
|
76
81
|
| Tool | Purpose | Example args |
|
|
77
82
|
| --- | --- | --- |
|
|
78
|
-
| `amba_secrets_set` / `amba_set_secret` | Set a
|
|
83
|
+
| `amba_secrets_set` / `amba_set_secret` | Set a **function-scoped** secret (encrypted at rest; bound on the next deploy). 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_..." }` |
|
|
79
84
|
| `amba_secrets_get` / `amba_get_secret` | Read a secret (returns `"<redacted>"` unless explicitly requested). | `{ project_id, name }` |
|
|
80
85
|
| `amba_secrets_list` / `amba_list_secrets` | List secret names. | `{ project_id }` |
|
|
81
86
|
| `amba_secrets_delete` / `amba_delete_secret` | Delete. | `{ project_id, name }` |
|
|
@@ -138,9 +143,9 @@ const newTodo = await Amba.collections.insert('todos', {
|
|
|
138
143
|
await Amba.collections.update('todos', newTodo.id, { done: true });
|
|
139
144
|
await Amba.collections.delete('todos', newTodo.id);
|
|
140
145
|
|
|
141
|
-
// AI — call a managed prompt
|
|
146
|
+
// AI — call a managed prompt (prompt_slug names the registered prompt)
|
|
142
147
|
const response = await Amba.ai.anthropic.messages.create({
|
|
143
|
-
|
|
148
|
+
prompt_slug: 'summarize',
|
|
144
149
|
variables: { text: 'A long article about backend services …' },
|
|
145
150
|
});
|
|
146
151
|
// response.content — the model's reply
|
|
@@ -230,8 +235,7 @@ try await Amba.events.track("app_opened", properties: ["source": "deep_link"])
|
|
|
230
235
|
|
|
231
236
|
// AI
|
|
232
237
|
let reply = try await Amba.ai.anthropic.messages.create(
|
|
233
|
-
|
|
234
|
-
variables: ["text": "A long article..."]
|
|
238
|
+
request: AiMessageRequest(promptSlug: "summarize", variables: ["text": "A long article..."])
|
|
235
239
|
)
|
|
236
240
|
```
|
|
237
241
|
|
|
@@ -248,8 +252,7 @@ val showBeta = Amba.flags.get("beta_feature")
|
|
|
248
252
|
Amba.events.track("app_opened", mapOf("source" to "deep_link"))
|
|
249
253
|
|
|
250
254
|
val reply = Amba.ai.anthropic.messages.create(
|
|
251
|
-
|
|
252
|
-
variables = mapOf("text" to "A long article…")
|
|
255
|
+
AiMessageRequest(promptSlug = "summarize", variables = mapOf("text" to "A long article…"))
|
|
253
256
|
)
|
|
254
257
|
```
|
|
255
258
|
|
|
@@ -268,7 +271,7 @@ final showBeta = await Amba.flags.get('beta_feature');
|
|
|
268
271
|
await Amba.events.track('app_opened', {'source': 'deep_link'});
|
|
269
272
|
|
|
270
273
|
final reply = await Amba.ai.anthropic.messages.create(
|
|
271
|
-
|
|
274
|
+
promptSlug: 'summarize',
|
|
272
275
|
variables: {'text': 'A long article…'},
|
|
273
276
|
);
|
|
274
277
|
```
|
|
@@ -333,7 +336,7 @@ Batch.
|
|
|
333
336
|
2. Before creating:
|
|
334
337
|
- `amba_collections_list` — match on `name`. Collisions: never silently recreate (data loss). Offer `amba_collections_alter` to add new columns instead.
|
|
335
338
|
- `amba_functions_list` — match on `name`. Collisions: ask to redeploy (with the new source) or skip.
|
|
336
|
-
- `amba_ai_prompts_list` — match on `
|
|
339
|
+
- `amba_ai_prompts_list` — match on `name`. Same. (And `amba_ai_providers_list` — match on `provider`; re-running `amba_ai_providers_set` rotates the key in place.)
|
|
337
340
|
- `amba_integrations_list` — match on `provider`. Same.
|
|
338
341
|
- `amba_configs_list` — match on `key`. Same.
|
|
339
342
|
|