@layers/amba-mcp 4.0.3 → 4.0.4
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/expo-build-prompt.js +9 -1
- package/dist/index.js +513 -84
- package/dist/resources/amba-setup-infrastructure.d.ts +1 -1
- package/dist/resources/amba-setup.d.ts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/tools/ai-prompts-admin.d.ts +5 -3
- package/dist/tools/ai-providers-admin.d.ts +30 -0
- package/dist/tools/experiments.d.ts +24 -0
- package/dist/tools/leagues.d.ts +12 -0
- package/dist/tools/promotion.d.ts +28 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { INTEGRATION_PROVIDERS } from "@layers/amba-shared";
|
|
6
|
+
import { INTEGRATION_PROVIDERS, integrationConfigSummary } from "@layers/amba-shared";
|
|
7
7
|
//#region src/auth.ts
|
|
8
8
|
const CREDENTIALS_PATH = join(homedir(), ".amba", "credentials.json");
|
|
9
9
|
async function loadCredentials() {
|
|
@@ -308,7 +308,9 @@ const READ_VERBS = new Set([
|
|
|
308
308
|
"tiers",
|
|
309
309
|
"verify",
|
|
310
310
|
"logs",
|
|
311
|
-
"query"
|
|
311
|
+
"query",
|
|
312
|
+
"aggregate",
|
|
313
|
+
"results"
|
|
312
314
|
]);
|
|
313
315
|
/**
|
|
314
316
|
* Destructive verbs. A tool whose name contains one of these MAY perform
|
|
@@ -611,7 +613,7 @@ function registerPublicTool(server, name, description, schema, handler, aliases
|
|
|
611
613
|
}
|
|
612
614
|
//#endregion
|
|
613
615
|
//#region src/tools/projects.ts
|
|
614
|
-
function registerTools$
|
|
616
|
+
function registerTools$33(server, apiClient) {
|
|
615
617
|
registerTool(server, apiClient, "amba_projects_list", "List all Amba projects owned by the authenticated developer. Returns project id, name, bundle_id, platform, and environment.", {}, async (_, { client }) => {
|
|
616
618
|
const result = await client.get("/projects");
|
|
617
619
|
return { content: [{
|
|
@@ -718,7 +720,7 @@ function registerTools$29(server, apiClient) {
|
|
|
718
720
|
}
|
|
719
721
|
//#endregion
|
|
720
722
|
//#region src/tools/push.ts
|
|
721
|
-
function registerTools$
|
|
723
|
+
function registerTools$32(server, apiClient) {
|
|
722
724
|
registerTool(server, apiClient, "amba_push_campaigns_create", "Create a new push notification campaign for a project. The campaign starts in \"draft\" status. You can optionally target a segment and schedule delivery.", {
|
|
723
725
|
project_id: z.string().describe("The project ID"),
|
|
724
726
|
title: z.string().describe("Push notification title shown to the user"),
|
|
@@ -848,7 +850,7 @@ const segmentRulesSchema = z.object({
|
|
|
848
850
|
operator: z.enum(["AND", "OR"]).describe("Logical operator combining conditions"),
|
|
849
851
|
conditions: z.array(segmentConditionSchema).describe("Array of filter conditions")
|
|
850
852
|
});
|
|
851
|
-
function registerTools$
|
|
853
|
+
function registerTools$31(server, apiClient) {
|
|
852
854
|
registerTool(server, apiClient, "amba_segments_list", "List user segments for a project. Segments define groups of users based on rules (e.g. \"active in last 7 days\", \"premium users\"). Returns only project-custom segments by default. Pass include_system: true to also include the built-in system segments (e.g. \"all\", \"active_7d\", \"premium\").", {
|
|
853
855
|
project_id: z.string().describe("The project ID"),
|
|
854
856
|
include_system: z.boolean().optional().describe("Include built-in system segments in the result. Defaults to false.")
|
|
@@ -933,7 +935,7 @@ const configConditionSchema = z.object({
|
|
|
933
935
|
percentage: z.number().optional().describe("Percentage rollout (0-100)"),
|
|
934
936
|
value: z.unknown().describe("Override value for this condition")
|
|
935
937
|
});
|
|
936
|
-
function registerTools$
|
|
938
|
+
function registerTools$30(server, apiClient) {
|
|
937
939
|
registerTool(server, apiClient, "amba_remote_configs_list", "List remote-config keys for a project. Returns key, value, value_type, description, conditions, and version per active row. Remote config lets you change app behavior without deploying an update. Returns only project-created config keys by default. Pass include_system: true to also include platform-provided defaults (e.g. app_version_min, maintenance_mode).", {
|
|
938
940
|
project_id: z.string().describe("The project ID"),
|
|
939
941
|
include_system: z.boolean().optional().describe("Include platform-provided default config keys in the result. Defaults to false.")
|
|
@@ -1011,8 +1013,8 @@ const contentItemSchema = z.object({
|
|
|
1011
1013
|
metadata: z.record(z.unknown()).optional().describe("Arbitrary metadata key-value pairs"),
|
|
1012
1014
|
is_premium: z.boolean().optional().describe("Whether this content requires a premium entitlement")
|
|
1013
1015
|
});
|
|
1014
|
-
function registerTools$
|
|
1015
|
-
registerTool(server, apiClient, "amba_content_libraries_create", "Create a content library for a project. A content library is a collection of content items (tips, quotes, articles) that can be delivered on a schedule to keep users engaged.", {
|
|
1016
|
+
function registerTools$29(server, apiClient) {
|
|
1017
|
+
registerTool(server, apiClient, "amba_content_libraries_create", "Create a content library for a project. A content library is a collection of content items (tips, quotes, articles) that can be delivered on a schedule to keep users engaged. The library `name` IS the channel handle the in-app SDK uses: `Amba.content.library(name)` reads this library (names are unique). The response echoes it as `channel`.", {
|
|
1016
1018
|
project_id: z.string().describe("The project ID"),
|
|
1017
1019
|
name: z.string().describe("Library name (e.g. \"Daily Motivation\", \"Workout Tips\")"),
|
|
1018
1020
|
description: z.string().optional().describe("Description of the library content"),
|
|
@@ -1027,6 +1029,17 @@ function registerTools$25(server, apiClient) {
|
|
|
1027
1029
|
text: JSON.stringify(result, null, 2)
|
|
1028
1030
|
}] };
|
|
1029
1031
|
}, ["amba_create_content_library"]);
|
|
1032
|
+
registerTool(server, apiClient, "amba_content_libraries_delete", "Delete a content library. A library with items is refused unless cascade=true, which deletes the library and ALL its items, schedules, and deliveries in one call (irreversible). Use this to clean up orphan libraries instead of deleting items one at a time.", {
|
|
1033
|
+
project_id: z.string().describe("The project ID"),
|
|
1034
|
+
library_id: z.string().describe("The content library ID to delete"),
|
|
1035
|
+
cascade: z.boolean().optional().describe("Required to delete a non-empty library. When true, deletes the library and all its items, schedules, and deliveries. Defaults to false (a non-empty library returns 409 LIBRARY_NOT_EMPTY).")
|
|
1036
|
+
}, async ({ project_id, library_id, cascade }, { client }) => {
|
|
1037
|
+
const result = await client.delete(`/projects/${project_id}/content/libraries/${library_id}`, cascade ? { cascade: "true" } : void 0);
|
|
1038
|
+
return { content: [{
|
|
1039
|
+
type: "text",
|
|
1040
|
+
text: JSON.stringify(result, null, 2)
|
|
1041
|
+
}] };
|
|
1042
|
+
});
|
|
1030
1043
|
registerTool(server, apiClient, "amba_content_items_add", "Add one or more content items to an existing content library. Items can include text, media URLs, categories, tags, and metadata.", {
|
|
1031
1044
|
project_id: z.string().describe("The project ID"),
|
|
1032
1045
|
library_id: z.string().describe("The content library ID"),
|
|
@@ -1070,7 +1083,7 @@ function registerTools$25(server, apiClient) {
|
|
|
1070
1083
|
text: JSON.stringify(result, null, 2)
|
|
1071
1084
|
}] };
|
|
1072
1085
|
}, ["amba_create_content_schedule"]);
|
|
1073
|
-
registerTool(server, apiClient, "amba_content_list_libraries", "List content libraries for a project.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
1086
|
+
registerTool(server, apiClient, "amba_content_list_libraries", "List content libraries for a project. Each library exposes `channel` (= name) — the handle the in-app SDK passes to `Amba.content.library(channel)` (and `Amba.content.today(scheduleName)` for scheduled rotations).", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
1074
1087
|
const result = await client.get(`/projects/${project_id}/content/libraries`);
|
|
1075
1088
|
return { content: [{
|
|
1076
1089
|
type: "text",
|
|
@@ -1175,7 +1188,7 @@ function registerTools$25(server, apiClient) {
|
|
|
1175
1188
|
}
|
|
1176
1189
|
//#endregion
|
|
1177
1190
|
//#region src/tools/streaks.ts
|
|
1178
|
-
function registerTools$
|
|
1191
|
+
function registerTools$28(server, apiClient) {
|
|
1179
1192
|
registerTool(server, apiClient, "amba_streaks_create", "Create a streak definition for a project. Streaks track consecutive user engagement (e.g. daily logins, workout completions). Supports configurable periods, grace periods, and freeze mechanics.", {
|
|
1180
1193
|
project_id: z.string().describe("The project ID"),
|
|
1181
1194
|
key: z.string().regex(/^[a-z0-9_-]{1,64}$/).optional().describe("Stable identifier used by the SDK to qualify a streak without knowing its UUID: `Amba.streaks.qualify(\"daily_login\")`. Lowercase letters, digits, underscore, hyphen, 1-64 characters. Immutable after creation — changing it breaks live SDK calls. When omitted the server derives one from `name`."),
|
|
@@ -1267,11 +1280,11 @@ function registerTools$24(server, apiClient) {
|
|
|
1267
1280
|
//#endregion
|
|
1268
1281
|
//#region src/tools/integrations.ts
|
|
1269
1282
|
const providerEnum = z.enum(INTEGRATION_PROVIDERS);
|
|
1270
|
-
function registerTools$
|
|
1271
|
-
registerTool(server, apiClient, "amba_integrations_configure", "Configure (create or replace) a third-party integration for a project. Supported providers: \"apns\" (Apple Push), \"fcm\" (Firebase Cloud Messaging), \"revenuecat\" (subscription management), \"superwall\" (paywall management). Each provider requires specific config fields.", {
|
|
1283
|
+
function registerTools$27(server, apiClient) {
|
|
1284
|
+
registerTool(server, apiClient, "amba_integrations_configure", "Configure (create or replace) a third-party integration for a project. Supported providers: \"apns\" (Apple Push), \"fcm\" (Firebase Cloud Messaging), \"revenuecat\" (subscription management), \"superwall\" (paywall management). Each provider requires specific config fields. Push credentials (.p8 / service-account JSON) are passed inline here and stored server-side automatically — no separate secrets step.", {
|
|
1272
1285
|
project_id: z.string().describe("The project ID"),
|
|
1273
1286
|
provider: providerEnum.describe("Integration provider name"),
|
|
1274
|
-
config: z.record(z.unknown()).describe("Provider-specific configuration.
|
|
1287
|
+
config: z.record(z.unknown()).describe("Provider-specific configuration. " + integrationConfigSummary())
|
|
1275
1288
|
}, async ({ project_id, provider, config }, { client }) => {
|
|
1276
1289
|
const result = await client.post(`/projects/${project_id}/integrations`, {
|
|
1277
1290
|
provider,
|
|
@@ -1337,7 +1350,7 @@ const PERIOD_DAYS = {
|
|
|
1337
1350
|
"30d": "30",
|
|
1338
1351
|
"90d": "90"
|
|
1339
1352
|
};
|
|
1340
|
-
function registerTools$
|
|
1353
|
+
function registerTools$26(server, apiClient) {
|
|
1341
1354
|
registerTool(server, apiClient, "amba_analytics_get", "Get daily analytics for a project. Returns per-day event counts and unique-user counts for the requested window, plus totals. Use this to verify that end-to-end event tracking is working after seeding and testing.", {
|
|
1342
1355
|
project_id: z.string().describe("The project ID"),
|
|
1343
1356
|
period: z.enum([
|
|
@@ -1360,7 +1373,7 @@ function registerTools$22(server, apiClient) {
|
|
|
1360
1373
|
}
|
|
1361
1374
|
//#endregion
|
|
1362
1375
|
//#region src/tools/users.ts
|
|
1363
|
-
function registerTools$
|
|
1376
|
+
function registerTools$25(server, apiClient) {
|
|
1364
1377
|
registerTool(server, apiClient, "amba_users_list", "List app users for a project. Supports pagination with offset and limit, plus optional search (matches email / external_id / display_name with ILIKE) and segment filter. Returns user profiles including external_id, email, display_name, properties, and activity timestamps.", {
|
|
1365
1378
|
project_id: z.string().describe("The project ID"),
|
|
1366
1379
|
limit: z.number().optional().describe("Maximum number of users to return (default 50)"),
|
|
@@ -1379,6 +1392,30 @@ function registerTools$21(server, apiClient) {
|
|
|
1379
1392
|
text: JSON.stringify(result, null, 2)
|
|
1380
1393
|
}] };
|
|
1381
1394
|
}, ["amba_list_users"]);
|
|
1395
|
+
registerTool(server, apiClient, "amba_users_create", "Admin-create an app user from the developer/agent side. Use this to mint a system/seed user to attribute migrated rows to — e.g. after amba_users_reset_sandbox wiped everyone. All fields are optional; with no fields it mints an anonymous user. Collisions on external_id/anonymous_id/email return 409 USER_ALREADY_EXISTS.", {
|
|
1396
|
+
project_id: z.string().describe("The project ID"),
|
|
1397
|
+
external_id: z.string().optional().describe("Your stable external identifier for this user (unique). Optional."),
|
|
1398
|
+
anonymous_id: z.string().optional().describe("Anonymous device id (unique). Optional; auto-generated if no identity given."),
|
|
1399
|
+
email: z.string().optional().describe("Email address. Optional."),
|
|
1400
|
+
phone: z.string().optional().describe("Phone number. Optional."),
|
|
1401
|
+
display_name: z.string().optional().describe("Display name. Optional."),
|
|
1402
|
+
avatar_url: z.string().optional().describe("Avatar URL. Optional."),
|
|
1403
|
+
properties: z.record(z.unknown()).optional().describe("Arbitrary user properties (JSON object). Optional.")
|
|
1404
|
+
}, async ({ project_id, external_id, anonymous_id, email, phone, display_name, avatar_url, properties }, { client }) => {
|
|
1405
|
+
const payload = {};
|
|
1406
|
+
if (external_id !== void 0) payload.external_id = external_id;
|
|
1407
|
+
if (anonymous_id !== void 0) payload.anonymous_id = anonymous_id;
|
|
1408
|
+
if (email !== void 0) payload.email = email;
|
|
1409
|
+
if (phone !== void 0) payload.phone = phone;
|
|
1410
|
+
if (display_name !== void 0) payload.display_name = display_name;
|
|
1411
|
+
if (avatar_url !== void 0) payload.avatar_url = avatar_url;
|
|
1412
|
+
if (properties !== void 0) payload.properties = properties;
|
|
1413
|
+
const result = await client.post(`/projects/${project_id}/users`, payload);
|
|
1414
|
+
return { content: [{
|
|
1415
|
+
type: "text",
|
|
1416
|
+
text: JSON.stringify(result, null, 2)
|
|
1417
|
+
}] };
|
|
1418
|
+
}, ["amba_create_user"]);
|
|
1382
1419
|
registerTool(server, apiClient, "amba_users_get", "Get a single app user by id, with their streaks and entitlements joined in.", {
|
|
1383
1420
|
project_id: z.string().describe("The project ID"),
|
|
1384
1421
|
user_id: z.string().describe("The app user ID")
|
|
@@ -1638,12 +1675,18 @@ println("Current streak: \${streak.currentCount}")`,
|
|
|
1638
1675
|
// For Expo apps, use @layers/amba-expo instead.
|
|
1639
1676
|
import { Amba } from '@layers/amba-react-native';
|
|
1640
1677
|
|
|
1641
|
-
// Configure once at app startup
|
|
1678
|
+
// Configure once at app startup. The first authenticated call lazily
|
|
1679
|
+
// establishes an anonymous session, so reads work without a manual sign-in.
|
|
1680
|
+
// Call signInAnonymously() explicitly if you want to control when the
|
|
1681
|
+
// anonymous user is created.
|
|
1642
1682
|
await Amba.configure({
|
|
1643
1683
|
projectId: 'YOUR_PROJECT_ID',
|
|
1644
|
-
|
|
1684
|
+
clientKey: 'YOUR_CLIENT_API_KEY',
|
|
1645
1685
|
});
|
|
1646
1686
|
|
|
1687
|
+
// Optional — establish the session up front instead of lazily:
|
|
1688
|
+
// await Amba.auth.signInAnonymously();
|
|
1689
|
+
|
|
1647
1690
|
// Track events
|
|
1648
1691
|
await Amba.events.track('workout_completed', {
|
|
1649
1692
|
duration_minutes: 30,
|
|
@@ -1665,9 +1708,11 @@ import { Amba } from '@layers/amba-expo';
|
|
|
1665
1708
|
|
|
1666
1709
|
export default function RootLayout() {
|
|
1667
1710
|
useEffect(() => {
|
|
1668
|
-
|
|
1711
|
+
// configure() is async. Reads lazily establish an anonymous session on the
|
|
1712
|
+
// first authenticated call, so no manual sign-in is required to start.
|
|
1713
|
+
void Amba.configure({
|
|
1669
1714
|
projectId: process.env.EXPO_PUBLIC_AMBA_PROJECT_ID!,
|
|
1670
|
-
|
|
1715
|
+
clientKey: 'YOUR_CLIENT_API_KEY',
|
|
1671
1716
|
});
|
|
1672
1717
|
}, []);
|
|
1673
1718
|
|
|
@@ -1733,7 +1778,7 @@ final dailyLimit = await Amba.config.get<int>('daily_limit', defaultValue: 10);
|
|
|
1733
1778
|
final streak = await Amba.streaks.get('daily_login');
|
|
1734
1779
|
print('Current streak: \${streak.currentCount}');`
|
|
1735
1780
|
};
|
|
1736
|
-
function registerTools$
|
|
1781
|
+
function registerTools$24(server, apiClient) {
|
|
1737
1782
|
registerTool(server, apiClient, "amba_sdk_get_setup_instructions", "Get platform-specific code snippets and setup instructions for integrating the Amba client SDK into a mobile app. Supports iOS (Swift), Android (Kotlin), React Native, Expo, and Flutter.", { platform: z.enum([
|
|
1738
1783
|
"ios",
|
|
1739
1784
|
"android",
|
|
@@ -1818,7 +1863,7 @@ const criteriaSchema = z.object({
|
|
|
1818
1863
|
property_key: z.string().optional().describe("User property key (required for property_value type)"),
|
|
1819
1864
|
target_value: z.number().describe("Target value to unlock the achievement")
|
|
1820
1865
|
});
|
|
1821
|
-
function registerTools$
|
|
1866
|
+
function registerTools$23(server, apiClient) {
|
|
1822
1867
|
registerTool(server, apiClient, "amba_achievements_create", "Create an achievement/badge definition. Achievements unlock automatically when users meet the criteria (e.g. track 5 workouts, reach a 7-day streak, earn 5000 XP). Can optionally award bonus XP on unlock.", {
|
|
1823
1868
|
project_id: z.string().describe("The project ID"),
|
|
1824
1869
|
key: z.string().describe("Unique key for this achievement (e.g. \"first_workout\", \"streak_master_7\")"),
|
|
@@ -1923,7 +1968,7 @@ function registerTools$19(server, apiClient) {
|
|
|
1923
1968
|
}
|
|
1924
1969
|
//#endregion
|
|
1925
1970
|
//#region src/tools/challenges.ts
|
|
1926
|
-
function registerTools$
|
|
1971
|
+
function registerTools$22(server, apiClient) {
|
|
1927
1972
|
registerTool(server, apiClient, "amba_challenges_create", "Create a time-limited challenge. Challenges are events that run between start_at and end_at, where users try to reach a goal (e.g. track 10 workouts this week, earn 500 XP in 3 days). Can reward XP and/or unlock an achievement on completion.", {
|
|
1928
1973
|
project_id: z.string().describe("The project ID"),
|
|
1929
1974
|
name: z.string().describe("Challenge name (e.g. \"7-Day Fitness Sprint\", \"XP Weekend Blitz\")"),
|
|
@@ -2034,7 +2079,7 @@ function registerTools$18(server, apiClient) {
|
|
|
2034
2079
|
}
|
|
2035
2080
|
//#endregion
|
|
2036
2081
|
//#region src/tools/economy.ts
|
|
2037
|
-
function registerTools$
|
|
2082
|
+
function registerTools$21(server, apiClient) {
|
|
2038
2083
|
registerTool(server, apiClient, "amba_currencies_create", "Create a virtual currency for a project. Currencies can be soft (earned through gameplay) or premium (purchased with real money). Supports auto-recharge for time-gated mechanics like hearts/energy.", {
|
|
2039
2084
|
project_id: z.string().describe("The project ID"),
|
|
2040
2085
|
code: z.string().describe("Unique currency code (e.g. \"gold\", \"gems\", \"hearts\")"),
|
|
@@ -2069,38 +2114,42 @@ function registerTools$17(server, apiClient) {
|
|
|
2069
2114
|
text: JSON.stringify(result, null, 2)
|
|
2070
2115
|
}] };
|
|
2071
2116
|
}, ["amba_list_currencies"]);
|
|
2072
|
-
registerTool(server, apiClient, "amba_currencies_grant", "Grant virtual currency to a specific user. Useful for rewards, promotions, or admin adjustments.", {
|
|
2117
|
+
registerTool(server, apiClient, "amba_currencies_grant", "Grant virtual currency to a specific user. Useful for rewards, promotions, or admin adjustments. Pass idempotency_key to make a grant exactly-once: a repeat with the same key for the same (user, currency) returns the original transaction (HTTP 200, idempotent_replay:true) without re-applying — e.g. encode `share:{userId}:{yyyy-mm-dd}` to cap \"one per share per day\" without a guard collection.", {
|
|
2073
2118
|
project_id: z.string().describe("The project ID"),
|
|
2074
2119
|
app_user_id: z.string().describe("The user to grant currency to"),
|
|
2075
2120
|
currency_code: z.string().describe("Currency code (e.g. \"gold\")"),
|
|
2076
2121
|
amount: z.number().describe("Amount to grant (positive integer)"),
|
|
2077
|
-
reason: z.string().optional().describe("Reason for the grant (for audit trail)")
|
|
2078
|
-
|
|
2122
|
+
reason: z.string().optional().describe("Reason for the grant (for audit trail)"),
|
|
2123
|
+
idempotency_key: z.string().optional().describe("Dedup key (≤255 chars). A repeat with the same key for this (user, currency) returns the original grant without re-applying. The key is permanent — encode the window into it (e.g. include the date for once-per-day).")
|
|
2124
|
+
}, async ({ project_id, app_user_id, currency_code, amount, reason, idempotency_key }, { client }) => {
|
|
2079
2125
|
const payload = {
|
|
2080
2126
|
app_user_id,
|
|
2081
2127
|
currency_code,
|
|
2082
2128
|
amount
|
|
2083
2129
|
};
|
|
2084
2130
|
if (reason !== void 0) payload.reason = reason;
|
|
2131
|
+
if (idempotency_key !== void 0) payload.idempotency_key = idempotency_key;
|
|
2085
2132
|
const result = await client.post(`/projects/${project_id}/currencies/grant`, payload);
|
|
2086
2133
|
return { content: [{
|
|
2087
2134
|
type: "text",
|
|
2088
2135
|
text: JSON.stringify(result, null, 2)
|
|
2089
2136
|
}] };
|
|
2090
2137
|
}, ["amba_grant_currency"]);
|
|
2091
|
-
registerTool(server, apiClient, "amba_currencies_spend", "Debit virtual currency from a specific user. Atomic — returns 400 INSUFFICIENT_FUNDS if the balance is too low (no partial debit). Use for in-app purchases priced in soft currency, gameplay consumes, or admin adjustments. The amount parameter is positive; the ledger records the spend as a negative delta automatically.", {
|
|
2138
|
+
registerTool(server, apiClient, "amba_currencies_spend", "Debit virtual currency from a specific user. Atomic — returns 400 INSUFFICIENT_FUNDS if the balance is too low (no partial debit). Use for in-app purchases priced in soft currency, gameplay consumes, or admin adjustments. The amount parameter is positive; the ledger records the spend as a negative delta automatically. Pass idempotency_key to make a debit exactly-once: a repeat with the same key returns the original debit (HTTP 200, idempotent_replay:true) without double-charging.", {
|
|
2092
2139
|
project_id: z.string().describe("The project ID"),
|
|
2093
2140
|
app_user_id: z.string().describe("The user to debit currency from"),
|
|
2094
2141
|
currency_code: z.string().describe("Currency code (e.g. \"gold\")"),
|
|
2095
2142
|
amount: z.number().describe("Positive amount to debit. The endpoint records this as a negative delta on the ledger."),
|
|
2096
|
-
reason: z.string().optional().describe("Reason for the spend (stored as reference_id, useful for the audit trail)")
|
|
2097
|
-
|
|
2143
|
+
reason: z.string().optional().describe("Reason for the spend (stored as reference_id, useful for the audit trail)"),
|
|
2144
|
+
idempotency_key: z.string().optional().describe("Dedup key (≤255 chars). A repeat with the same key for this (user, currency) returns the original debit without double-charging. A spend that failed with INSUFFICIENT_FUNDS stores no key, so retrying it is allowed.")
|
|
2145
|
+
}, async ({ project_id, app_user_id, currency_code, amount, reason, idempotency_key }, { client }) => {
|
|
2098
2146
|
const payload = {
|
|
2099
2147
|
app_user_id,
|
|
2100
2148
|
currency_code,
|
|
2101
2149
|
amount
|
|
2102
2150
|
};
|
|
2103
2151
|
if (reason !== void 0) payload.reason = reason;
|
|
2152
|
+
if (idempotency_key !== void 0) payload.idempotency_key = idempotency_key;
|
|
2104
2153
|
const result = await client.post(`/projects/${project_id}/currencies/spend`, payload);
|
|
2105
2154
|
return { content: [{
|
|
2106
2155
|
type: "text",
|
|
@@ -2566,8 +2615,8 @@ function registerTools$17(server, apiClient) {
|
|
|
2566
2615
|
}
|
|
2567
2616
|
//#endregion
|
|
2568
2617
|
//#region src/tools/leaderboards.ts
|
|
2569
|
-
function registerTools$
|
|
2570
|
-
registerTool(server, apiClient, "amba_leaderboards_create", "Create a leaderboard definition. Leaderboards rank users by XP, streak length, or a custom metric. Supports all-time, daily, weekly, and monthly periods with configurable max entries.", {
|
|
2618
|
+
function registerTools$20(server, apiClient) {
|
|
2619
|
+
registerTool(server, apiClient, "amba_leaderboards_create", "Create a leaderboard definition. Leaderboards rank users by XP, streak length, or a custom metric. Supports all-time, daily, weekly, and monthly periods with configurable max entries. The returned `key` (which equals `name`) is the exact value the in-app SDK passes to `Amba.leaderboards.getEntries(key)` / `getRank(key)`.", {
|
|
2571
2620
|
project_id: z.string().describe("The project ID"),
|
|
2572
2621
|
name: z.string().describe("Leaderboard name (e.g. \"Top XP Earners\", \"Weekly Streak Leaders\")"),
|
|
2573
2622
|
metric: z.enum([
|
|
@@ -2612,7 +2661,7 @@ function registerTools$16(server, apiClient) {
|
|
|
2612
2661
|
text: JSON.stringify(result, null, 2)
|
|
2613
2662
|
}] };
|
|
2614
2663
|
}, ["amba_get_leaderboard"]);
|
|
2615
|
-
registerTool(server, apiClient, "amba_leaderboards_list", "List all leaderboard definitions for a project. Returns name, metric, period, and max_entries per leaderboard. (Use `amba_leaderboards_get` to fetch ranked entries for a specific leaderboard.)", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
2664
|
+
registerTool(server, apiClient, "amba_leaderboards_list", "List all leaderboard definitions for a project. Returns `key` (= name, the handle the in-app SDK `leaderboards.getEntries(key)` expects), name, metric, period, and max_entries per leaderboard. (Use `amba_leaderboards_get` to fetch ranked entries for a specific leaderboard.)", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
2616
2665
|
const result = await client.get(`/projects/${project_id}/leaderboards`);
|
|
2617
2666
|
return { content: [{
|
|
2618
2667
|
type: "text",
|
|
@@ -2671,8 +2720,80 @@ function registerTools$16(server, apiClient) {
|
|
|
2671
2720
|
}, ["amba_delete_leaderboard"]);
|
|
2672
2721
|
}
|
|
2673
2722
|
//#endregion
|
|
2723
|
+
//#region src/tools/leagues.ts
|
|
2724
|
+
/**
|
|
2725
|
+
* Leagues — the Duolingo-style promote/demote tiers (Bronze/Silver/Gold/…).
|
|
2726
|
+
* Distinct from leaderboards (a single ranked list): a league has weekly
|
|
2727
|
+
* cohorts that the rollover workflow reshuffles. These tools provision and
|
|
2728
|
+
* inspect leagues; the admin routes are mounted at
|
|
2729
|
+
* `/v1/admin/projects/:projectId/leagues`. Members are assigned by the weekly
|
|
2730
|
+
* rollover (the registered Temporal schedule) and score live off `xp_awarded`
|
|
2731
|
+
* engagement events.
|
|
2732
|
+
*/
|
|
2733
|
+
function registerTools$19(server, apiClient) {
|
|
2734
|
+
registerTool(server, apiClient, "amba_leagues_create", "Create a league tier (e.g. \"Bronze\", tier_order=1). Leagues are the promote/demote ladder: each week users are grouped into cohorts of `cohort_size`, the top `promote_count` move up a tier and the bottom `demote_count` move down. Create one league per tier (lowest tier_order = entry tier). Members are assigned by the weekly rollover, not at create time.", {
|
|
2735
|
+
project_id: z.string().describe("The project ID"),
|
|
2736
|
+
name: z.string().describe("League tier name, e.g. \"Bronze\", \"Silver\", \"Gold\""),
|
|
2737
|
+
tier_order: z.number().describe("Tier rank, 1 = lowest/entry tier. Higher tiers have higher tier_order."),
|
|
2738
|
+
promote_count: z.number().optional().describe("How many top members of a cohort promote each week (default 5)"),
|
|
2739
|
+
demote_count: z.number().optional().describe("How many bottom members of a cohort demote each week (default 5)"),
|
|
2740
|
+
cohort_size: z.number().optional().describe("Target members per weekly cohort (default 30)")
|
|
2741
|
+
}, async ({ project_id, name, tier_order, promote_count, demote_count, cohort_size }, { client }) => {
|
|
2742
|
+
const payload = {
|
|
2743
|
+
name,
|
|
2744
|
+
tier_order
|
|
2745
|
+
};
|
|
2746
|
+
if (promote_count !== void 0) payload.promote_count = promote_count;
|
|
2747
|
+
if (demote_count !== void 0) payload.demote_count = demote_count;
|
|
2748
|
+
if (cohort_size !== void 0) payload.cohort_size = cohort_size;
|
|
2749
|
+
const result = await client.post(`/projects/${project_id}/leagues`, payload);
|
|
2750
|
+
return { content: [{
|
|
2751
|
+
type: "text",
|
|
2752
|
+
text: JSON.stringify(result, null, 2)
|
|
2753
|
+
}] };
|
|
2754
|
+
}, ["amba_create_league"]);
|
|
2755
|
+
registerTool(server, apiClient, "amba_leagues_list", "List all league tiers for a project, ordered by tier. Returns name, tier_order, promote_count, demote_count, and cohort_size per league.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
2756
|
+
const result = await client.get(`/projects/${project_id}/leagues`);
|
|
2757
|
+
return { content: [{
|
|
2758
|
+
type: "text",
|
|
2759
|
+
text: JSON.stringify(result, null, 2)
|
|
2760
|
+
}] };
|
|
2761
|
+
}, ["amba_list_leagues"]);
|
|
2762
|
+
registerTool(server, apiClient, "amba_leagues_update", "Partially update a league tier. Only the supplied fields change. `tier_order` is intentionally immutable (re-ranking mid-week would require re-cohorting every active membership).", {
|
|
2763
|
+
project_id: z.string().describe("The project ID"),
|
|
2764
|
+
league_id: z.string().describe("The league ID"),
|
|
2765
|
+
name: z.string().optional().describe("New tier name"),
|
|
2766
|
+
promote_count: z.number().optional().describe("New weekly promote count"),
|
|
2767
|
+
demote_count: z.number().optional().describe("New weekly demote count"),
|
|
2768
|
+
cohort_size: z.number().optional().describe("New target cohort size"),
|
|
2769
|
+
is_active: z.boolean().optional().describe("Enable/disable the tier")
|
|
2770
|
+
}, async ({ project_id, league_id, name, promote_count, demote_count, cohort_size, is_active }, { client }) => {
|
|
2771
|
+
const payload = {};
|
|
2772
|
+
if (name !== void 0) payload.name = name;
|
|
2773
|
+
if (promote_count !== void 0) payload.promote_count = promote_count;
|
|
2774
|
+
if (demote_count !== void 0) payload.demote_count = demote_count;
|
|
2775
|
+
if (cohort_size !== void 0) payload.cohort_size = cohort_size;
|
|
2776
|
+
if (is_active !== void 0) payload.is_active = is_active;
|
|
2777
|
+
const result = await client.patch(`/projects/${project_id}/leagues/${league_id}`, payload);
|
|
2778
|
+
return { content: [{
|
|
2779
|
+
type: "text",
|
|
2780
|
+
text: JSON.stringify(result, null, 2)
|
|
2781
|
+
}] };
|
|
2782
|
+
}, ["amba_update_league"]);
|
|
2783
|
+
registerTool(server, apiClient, "amba_leagues_list_cohorts", "List this week's active cohorts for a league, each with a member_count. Cohorts (and their memberships) are created by the weekly rollover — a freshly created league shows no cohorts until the first rollover runs.", {
|
|
2784
|
+
project_id: z.string().describe("The project ID"),
|
|
2785
|
+
league_id: z.string().describe("The league ID")
|
|
2786
|
+
}, async ({ project_id, league_id }, { client }) => {
|
|
2787
|
+
const result = await client.get(`/projects/${project_id}/leagues/${league_id}/cohorts/current`);
|
|
2788
|
+
return { content: [{
|
|
2789
|
+
type: "text",
|
|
2790
|
+
text: JSON.stringify(result, null, 2)
|
|
2791
|
+
}] };
|
|
2792
|
+
}, ["amba_list_league_cohorts"]);
|
|
2793
|
+
}
|
|
2794
|
+
//#endregion
|
|
2674
2795
|
//#region src/tools/platform.ts
|
|
2675
|
-
function registerTools$
|
|
2796
|
+
function registerTools$18(server, apiClient) {
|
|
2676
2797
|
registerTool(server, apiClient, "amba_onboarding_create", "Create an onboarding flow for a project. Onboarding flows define step-by-step experiences for new users (e.g. welcome screens, permission prompts, feature tours).", {
|
|
2677
2798
|
project_id: z.string().describe("The project ID"),
|
|
2678
2799
|
name: z.string().describe("Flow name (e.g. \"Welcome Flow\", \"Premium Onboarding\")"),
|
|
@@ -3248,7 +3369,7 @@ function registerTools$15(server, apiClient) {
|
|
|
3248
3369
|
}
|
|
3249
3370
|
//#endregion
|
|
3250
3371
|
//#region src/tools/social.ts
|
|
3251
|
-
function registerTools$
|
|
3372
|
+
function registerTools$17(server, apiClient) {
|
|
3252
3373
|
registerTool(server, apiClient, "amba_friendships_get_stats", "Get friendship statistics for a project. Returns total friendships, pending requests, accepted friendships, and blocked count.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
3253
3374
|
const result = await client.get(`/projects/${project_id}/friends/stats`);
|
|
3254
3375
|
return { content: [{
|
|
@@ -3573,7 +3694,7 @@ function registerTools$14(server, apiClient) {
|
|
|
3573
3694
|
}
|
|
3574
3695
|
//#endregion
|
|
3575
3696
|
//#region src/tools/xp.ts
|
|
3576
|
-
function registerTools$
|
|
3697
|
+
function registerTools$16(server, apiClient) {
|
|
3577
3698
|
registerTool(server, apiClient, "amba_xp_rules_create", "Create an XP rule that auto-awards XP when a matching engagement event is tracked. For example, award 50 XP every time a user completes a workout, with optional daily caps and cooldowns.", {
|
|
3578
3699
|
project_id: z.string().describe("The project ID"),
|
|
3579
3700
|
name: z.string().describe("Rule name (e.g. \"Workout Completed\", \"Daily Login Bonus\")"),
|
|
@@ -3696,7 +3817,7 @@ function registerTools$13(server, apiClient) {
|
|
|
3696
3817
|
}
|
|
3697
3818
|
//#endregion
|
|
3698
3819
|
//#region src/tools/events.ts
|
|
3699
|
-
function registerTools$
|
|
3820
|
+
function registerTools$15(server, apiClient) {
|
|
3700
3821
|
registerTool(server, apiClient, "amba_events_list", "List engagement events for a project (most recent first) with cursor pagination on (occurred_at, id). Defaults to the last 24 hours when `since` is omitted. Pass `next_cursor` from a previous page back as `cursor` to continue.", {
|
|
3701
3822
|
project_id: z.string().describe("The project ID"),
|
|
3702
3823
|
since: z.string().optional().describe("ISO-8601 lower bound for occurred_at. Defaults to 24h ago."),
|
|
@@ -3763,7 +3884,7 @@ const funnelStepSchema = z.object({
|
|
|
3763
3884
|
event: z.string().describe("Event name to match (engagement event_name)."),
|
|
3764
3885
|
filters: z.array(funnelFilterSchema).optional().describe("Optional property predicates, ANDed together, that an event must satisfy.")
|
|
3765
3886
|
});
|
|
3766
|
-
function registerTools$
|
|
3887
|
+
function registerTools$14(server, apiClient) {
|
|
3767
3888
|
registerTool(server, apiClient, "amba_funnels_list", "List conversion funnels for a project. A funnel is a saved, ordered sequence of steps (event + optional property filters) plus a conversion window. Returns the definitions only — use amba_funnels_query to compute per-step counts.", { project_id: z.string().describe("The project ID") }, async ({ project_id }, { client }) => {
|
|
3768
3889
|
const result = await client.get(`/projects/${project_id}/funnels`);
|
|
3769
3890
|
return { content: [{
|
|
@@ -4029,7 +4150,7 @@ function enrichedAuthResult(result, agentInstructions) {
|
|
|
4029
4150
|
status: result.status
|
|
4030
4151
|
});
|
|
4031
4152
|
}
|
|
4032
|
-
function registerTools$
|
|
4153
|
+
function registerTools$13(server, apiClient) {
|
|
4033
4154
|
registerPublicTool(server, "amba_developer_signup", [
|
|
4034
4155
|
"Create a new Amba developer account. Returns a long-lived Personal Access Token (PAT)",
|
|
4035
4156
|
"plus a real isolated Amba project (provisioning asynchronously), plus ready-to-paste",
|
|
@@ -4163,6 +4284,18 @@ async function adminFetch(apiClient, options) {
|
|
|
4163
4284
|
body
|
|
4164
4285
|
};
|
|
4165
4286
|
}
|
|
4287
|
+
const CATALOG_SHAPE_KEYS = [
|
|
4288
|
+
"is_published",
|
|
4289
|
+
"published_at",
|
|
4290
|
+
"is_public",
|
|
4291
|
+
"published"
|
|
4292
|
+
];
|
|
4293
|
+
function catalogShapeWarning(row) {
|
|
4294
|
+
const hit = CATALOG_SHAPE_KEYS.filter((k) => k in row);
|
|
4295
|
+
if (hit.length === 0) return null;
|
|
4296
|
+
const ownership = "user_id" in row ? `attributed to user_id "${String(row["user_id"])}"` : "attributed to the seeding user (or rejected if the collection requires user_id)";
|
|
4297
|
+
return [`⚠️ This row has catalog-shaped column(s) [${hit.join(", ")}] but is being inserted into a per-user collection, so it will be ${ownership} — other users will NOT see it via the client SDK.`, "If this is shared/global content (a catalog, articles, a media library), use a Content Library (amba_content_libraries_create) or create the collection with shared:true so its rows are readable by every user. Proceeding anyway."].join(" ");
|
|
4298
|
+
}
|
|
4166
4299
|
async function clientFetch(apiClient, options) {
|
|
4167
4300
|
let url = `${apiClient.getApiRoot()}/client${options.path}`;
|
|
4168
4301
|
if (options.query && Object.keys(options.query).length > 0) url += `?${new URLSearchParams(options.query).toString()}`;
|
|
@@ -4200,7 +4333,7 @@ const MISSING_API_KEY_ERROR = jsonResult$1({
|
|
|
4200
4333
|
});
|
|
4201
4334
|
const columnSchema = z.object({
|
|
4202
4335
|
name: z.string().describe("Column name. Must match /^[a-z][a-z0-9_]*$/."),
|
|
4203
|
-
type: z.string().describe("Column type. One of: text, integer, bigint, boolean, jsonb, timestamptz, uuid, numeric,
|
|
4336
|
+
type: z.string().describe("Column type. One of: text, integer, bigint, boolean, jsonb, timestamptz, date, uuid, numeric, vector, or an array type (text[], integer[], bigint[], numeric[], boolean[], uuid[]). Array columns support the contains/containedBy/overlaps query operators. Vector columns require a \"dimensions\" hint."),
|
|
4204
4337
|
nullable: z.boolean().optional().describe("Whether the column accepts NULL. Defaults to true."),
|
|
4205
4338
|
default: z.unknown().optional().describe("Default SQL literal (e.g. \"NOW()\", 0, \"''\"). Optional."),
|
|
4206
4339
|
dimensions: z.number().optional().describe("Required for `vector` columns; ignored otherwise.")
|
|
@@ -4226,7 +4359,7 @@ const sdkOrderSchema = z.array(z.object({
|
|
|
4226
4359
|
})).describe("Ordering. `[{column: \"created_at\", direction: \"desc\"}, ...]`.");
|
|
4227
4360
|
const adminFindWhereSchema = z.unknown().describe("Server WhereClause shape: column-keyed FieldOps, e.g. {tag: {eq: \"x\"}, count: {gte: 5}} plus optional `and|or|not` combinators.");
|
|
4228
4361
|
const setSchema = z.record(z.unknown()).describe("Column-value map. Server-managed columns (id, created_at, etc.) are rejected.");
|
|
4229
|
-
function registerTools$
|
|
4362
|
+
function registerTools$12(server, apiClient) {
|
|
4230
4363
|
registerTool(server, apiClient, "amba_collections_create", [
|
|
4231
4364
|
"Create a new collection (schema-first Postgres table) in a project.",
|
|
4232
4365
|
"The DDL is emitted server-side and applied via a Temporal saga.",
|
|
@@ -4349,19 +4482,39 @@ function registerTools$9(server, apiClient) {
|
|
|
4349
4482
|
registerTool(server, apiClient, "amba_admin_insert_row", [
|
|
4350
4483
|
"Insert a row directly into a collection from the developer/agent side — BYPASSES auto-RLS.",
|
|
4351
4484
|
"Server-managed columns (id, created_at, updated_at, deleted_at) are stripped from the body. `user_id` is honored if present (admins can create rows attributed to any app_user).",
|
|
4485
|
+
"Atomic upsert: pass on_conflict=\"ignore\" (return the existing row, no change) or \"update\" (merge) plus conflict_target (the unique-index columns). Replaces read-then-write races.",
|
|
4352
4486
|
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4353
|
-
"Errors: 400 INVALID_COLUMN, 404 COLLECTION_NOT_FOUND, 500 CREATE_FAILED."
|
|
4487
|
+
"Errors: 400 INVALID_COLUMN / INVALID_CONFLICT_TARGET, 404 COLLECTION_NOT_FOUND, 500 CREATE_FAILED."
|
|
4354
4488
|
].join(" "), {
|
|
4355
4489
|
project_id: z.string().describe("The project ID."),
|
|
4356
4490
|
name: z.string().describe("Collection name."),
|
|
4357
|
-
row: z.record(z.unknown()).describe("Column-value map for the new row. Server-managed columns are stripped.")
|
|
4358
|
-
|
|
4359
|
-
|
|
4491
|
+
row: z.record(z.unknown()).describe("Column-value map for the new row. Server-managed columns are stripped."),
|
|
4492
|
+
on_conflict: z.enum([
|
|
4493
|
+
"error",
|
|
4494
|
+
"ignore",
|
|
4495
|
+
"update"
|
|
4496
|
+
]).optional().describe("Conflict policy. \"error\" (default) fails on a unique-constraint clash; \"ignore\" returns the existing row (HTTP 200) unchanged; \"update\" merges the provided columns. Requires conflict_target."),
|
|
4497
|
+
conflict_target: z.array(z.string()).optional().describe("Columns that form the unique index to conflict on (e.g. [\"user_id\",\"kind\"]). Required when on_conflict is \"ignore\" or \"update\"."),
|
|
4498
|
+
return_minimal: z.boolean().optional().describe("When true, the response returns only the inserted row id instead of the full row. Use this for rows with large columns (big JSON blobs) to avoid bloating the response — essential for bulk migrations where echoing full rows exhausts the token budget.")
|
|
4499
|
+
}, async ({ project_id, name, row, on_conflict, conflict_target, return_minimal }, { pat }) => {
|
|
4500
|
+
const query = {};
|
|
4501
|
+
if (on_conflict !== void 0) query.on_conflict = on_conflict;
|
|
4502
|
+
if (conflict_target !== void 0) query.conflict_target = conflict_target.join(",");
|
|
4503
|
+
if (return_minimal) query.return = "minimal";
|
|
4504
|
+
const result = await adminFetch(apiClient, {
|
|
4360
4505
|
method: "POST",
|
|
4361
4506
|
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows`,
|
|
4362
4507
|
body: row,
|
|
4508
|
+
query: Object.keys(query).length > 0 ? query : void 0,
|
|
4363
4509
|
bearer: pat
|
|
4364
|
-
})
|
|
4510
|
+
});
|
|
4511
|
+
const warning = catalogShapeWarning(row);
|
|
4512
|
+
const base = passthroughResult(result);
|
|
4513
|
+
if (warning) base.content.unshift({
|
|
4514
|
+
type: "text",
|
|
4515
|
+
text: warning
|
|
4516
|
+
});
|
|
4517
|
+
return base;
|
|
4365
4518
|
});
|
|
4366
4519
|
registerTool(server, apiClient, "amba_admin_insert_rows", [
|
|
4367
4520
|
"Bulk-insert up to 500 rows into a collection in a single atomic statement — BYPASSES auto-RLS.",
|
|
@@ -4374,20 +4527,22 @@ function registerTools$9(server, apiClient) {
|
|
|
4374
4527
|
project_id: z.string().describe("The project ID."),
|
|
4375
4528
|
name: z.string().describe("Collection name."),
|
|
4376
4529
|
rows: z.array(z.record(z.unknown())).min(1).max(500).describe("1–500 column-value maps. Server-managed columns are stripped per row."),
|
|
4377
|
-
on_conflict: z.enum(["error", "skip"]).optional().describe("Conflict policy. \"error\" (default) fails the batch; \"skip\" ignores conflicts.")
|
|
4378
|
-
|
|
4530
|
+
on_conflict: z.enum(["error", "skip"]).optional().describe("Conflict policy. \"error\" (default) fails the batch; \"skip\" ignores conflicts."),
|
|
4531
|
+
return_minimal: z.boolean().optional().describe("When true, returns only the inserted row ids instead of the full rows. Strongly recommended for large batches or rows with big columns — echoing 500 full rows can exhaust the token budget.")
|
|
4532
|
+
}, async ({ project_id, name, rows, on_conflict, return_minimal }, { pat }) => {
|
|
4379
4533
|
const body = { rows };
|
|
4380
4534
|
if (on_conflict !== void 0) body.on_conflict = on_conflict;
|
|
4381
4535
|
return passthroughResult(await adminFetch(apiClient, {
|
|
4382
4536
|
method: "POST",
|
|
4383
4537
|
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows:batch`,
|
|
4384
4538
|
body,
|
|
4539
|
+
query: return_minimal ? { return: "minimal" } : void 0,
|
|
4385
4540
|
bearer: pat
|
|
4386
4541
|
}));
|
|
4387
4542
|
});
|
|
4388
4543
|
registerTool(server, apiClient, "amba_admin_list_rows", [
|
|
4389
4544
|
"List rows in a collection from the admin side. BYPASSES auto-RLS — every row in the collection is visible.",
|
|
4390
|
-
"Default behavior
|
|
4545
|
+
"Default behavior EXCLUDES soft-deleted rows; pass `include_deleted=true` to include them (e.g. debugging \"where did the row go\").",
|
|
4391
4546
|
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4392
4547
|
"Supports the server FindQuery DSL via `where` + `order` + `limit` + `offset` + `cursor` + `select`."
|
|
4393
4548
|
].join(" "), {
|
|
@@ -4399,7 +4554,7 @@ function registerTools$9(server, apiClient) {
|
|
|
4399
4554
|
offset: z.number().optional().describe("Offset for pagination."),
|
|
4400
4555
|
cursor: z.string().optional().describe("Opaque cursor returned by a prior page."),
|
|
4401
4556
|
select: z.array(z.string()).optional().describe("Subset of columns to return."),
|
|
4402
|
-
include_deleted: z.boolean().optional().describe("Whether to include soft-deleted rows. Admin default is
|
|
4557
|
+
include_deleted: z.boolean().optional().describe("Whether to include soft-deleted rows. Admin default is FALSE.")
|
|
4403
4558
|
}, async ({ project_id, name, where, order, limit, offset, cursor, select, include_deleted }, { pat }) => {
|
|
4404
4559
|
const findQuery = {};
|
|
4405
4560
|
if (where !== void 0) findQuery.where = where;
|
|
@@ -4416,21 +4571,67 @@ function registerTools$9(server, apiClient) {
|
|
|
4416
4571
|
bearer: pat
|
|
4417
4572
|
}));
|
|
4418
4573
|
});
|
|
4574
|
+
registerTool(server, apiClient, "amba_admin_aggregate_rows", [
|
|
4575
|
+
"Run a group-by aggregation over a collection from the admin side — BYPASSES auto-RLS.",
|
|
4576
|
+
"Supports count / sum / avg / min / max, optionally grouped by one or more columns. count without a column = COUNT(*).",
|
|
4577
|
+
"Default EXCLUDES soft-deleted rows; pass include_deleted=true to include them.",
|
|
4578
|
+
"Authenticates as the developer/agent — pass `pat` or send as inbound Bearer.",
|
|
4579
|
+
"Returns one row per group (or a single row when group_by is omitted), with each aggregation under its alias."
|
|
4580
|
+
].join(" "), {
|
|
4581
|
+
project_id: z.string().describe("The project ID."),
|
|
4582
|
+
name: z.string().describe("Collection name."),
|
|
4583
|
+
select: z.array(z.object({
|
|
4584
|
+
fn: z.enum([
|
|
4585
|
+
"count",
|
|
4586
|
+
"sum",
|
|
4587
|
+
"avg",
|
|
4588
|
+
"min",
|
|
4589
|
+
"max"
|
|
4590
|
+
]),
|
|
4591
|
+
column: z.string().optional().describe("Column to aggregate. Required for sum/avg/min/max; omit for COUNT(*)."),
|
|
4592
|
+
as: z.string().optional().describe("Output alias (defaults to fn or fn_column).")
|
|
4593
|
+
})).min(1).describe("One or more aggregations to compute."),
|
|
4594
|
+
group_by: z.union([z.string(), z.array(z.string())]).optional().describe("Column(s) to group by. Omit for a whole-collection aggregate."),
|
|
4595
|
+
where: adminFindWhereSchema.optional(),
|
|
4596
|
+
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default FALSE.")
|
|
4597
|
+
}, async ({ project_id, name, select, group_by, where, include_deleted }, { pat }) => {
|
|
4598
|
+
const body = { select };
|
|
4599
|
+
if (group_by !== void 0) body.group_by = group_by;
|
|
4600
|
+
if (where !== void 0) body.where = where;
|
|
4601
|
+
if (include_deleted !== void 0) body.includeDeleted = include_deleted;
|
|
4602
|
+
return passthroughResult(await adminFetch(apiClient, {
|
|
4603
|
+
method: "POST",
|
|
4604
|
+
path: `/projects/${project_id}/collections/${encodeURIComponent(name)}/rows/aggregate`,
|
|
4605
|
+
body,
|
|
4606
|
+
bearer: pat
|
|
4607
|
+
}));
|
|
4608
|
+
});
|
|
4419
4609
|
server.tool("amba_client_insert_row", [
|
|
4420
4610
|
"Insert a row into a collection as the signed-in app_user. Auto-RLS injects `user_id = <session app_user>` server-side; the body cannot override it.",
|
|
4611
|
+
"Atomic upsert: pass on_conflict=\"ignore\" (return existing row, HTTP 200) or \"update\" (merge) plus conflict_target (unique-index columns, may include user_id). Removes read-then-write races (duplicate profiles, split rooms, etc.).",
|
|
4421
4612
|
"Authenticates as the end-user — pass `api_key` (project client X-Api-Key) plus `session_token` (the app_user's Bearer).",
|
|
4422
|
-
"Errors: 400 INVALID_COLUMN, 401 if api_key/session missing, 404 COLLECTION_NOT_FOUND."
|
|
4613
|
+
"Errors: 400 INVALID_COLUMN / INVALID_CONFLICT_TARGET, 401 if api_key/session missing, 404 COLLECTION_NOT_FOUND."
|
|
4423
4614
|
].join(" "), {
|
|
4424
4615
|
name: z.string().describe("Collection name."),
|
|
4425
4616
|
row: z.record(z.unknown()).describe("Column-value map. Server-managed columns and user_id are stripped/overridden."),
|
|
4617
|
+
on_conflict: z.enum([
|
|
4618
|
+
"error",
|
|
4619
|
+
"ignore",
|
|
4620
|
+
"update"
|
|
4621
|
+
]).optional().describe("Conflict policy. \"error\" (default), \"ignore\" (return existing unchanged), or \"update\" (merge). Requires conflict_target."),
|
|
4622
|
+
conflict_target: z.array(z.string()).optional().describe("Unique-index columns to conflict on (e.g. [\"user_id\",\"kind\"]). Required for ignore/update."),
|
|
4426
4623
|
api_key: z.string().describe("Project client X-Api-Key. End-user tools authenticate via this + an optional session token, NOT via a developer PAT."),
|
|
4427
4624
|
session_token: z.string().optional().describe("Optional app_user session Bearer token. Required by the route's clientSessionAuth.")
|
|
4428
|
-
}, async ({ name, row, api_key, session_token }) => {
|
|
4625
|
+
}, async ({ name, row, on_conflict, conflict_target, api_key, session_token }) => {
|
|
4429
4626
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
4627
|
+
const query = {};
|
|
4628
|
+
if (on_conflict !== void 0) query.on_conflict = on_conflict;
|
|
4629
|
+
if (conflict_target !== void 0) query.conflict_target = conflict_target.join(",");
|
|
4430
4630
|
return passthroughResult(await clientFetch(apiClient, {
|
|
4431
4631
|
method: "POST",
|
|
4432
4632
|
path: `/collections/${encodeURIComponent(name)}`,
|
|
4433
4633
|
body: row,
|
|
4634
|
+
...Object.keys(query).length > 0 && { query },
|
|
4434
4635
|
apiKey: api_key,
|
|
4435
4636
|
...session_token !== void 0 && { sessionToken: session_token }
|
|
4436
4637
|
}));
|
|
@@ -4488,6 +4689,7 @@ function registerTools$9(server, apiClient) {
|
|
|
4488
4689
|
});
|
|
4489
4690
|
server.tool("amba_client_update_row", [
|
|
4490
4691
|
"Update a single row by id. Auto-RLS — only succeeds if the row belongs to the signed-in app_user.",
|
|
4692
|
+
"Compare-and-set: pass `expected` (a column→value map) on a single-row update; the write applies only if the row still matches, else 409 PRECONDITION_FAILED. Null-safe. Optimistic concurrency without a transaction.",
|
|
4491
4693
|
"For bulk updates with a where-clause, omit `id` and pass `where` + `set`.",
|
|
4492
4694
|
"Server-managed columns and user_id are rejected with 400 INVALID_COLUMN.",
|
|
4493
4695
|
"Authenticates as the end-user — pass `api_key` + `session_token`."
|
|
@@ -4496,15 +4698,19 @@ function registerTools$9(server, apiClient) {
|
|
|
4496
4698
|
id: z.string().optional().describe("Row UUID for single-row update. Omit to bulk update via `where`."),
|
|
4497
4699
|
where: adminFindWhereSchema.optional().describe("Required for bulk update (when `id` is omitted). `where: {}` matches all rows the user owns."),
|
|
4498
4700
|
set: setSchema.describe("Column-value map to write. Required."),
|
|
4701
|
+
expected: z.record(z.unknown()).optional().describe("Compare-and-set precondition (single-row only): column→expected-value map. The update applies only if the row currently matches; otherwise 409 PRECONDITION_FAILED."),
|
|
4499
4702
|
limit: z.number().optional().describe("Bulk-update cap (default 1000, max 10000). Ignored when `id` is set."),
|
|
4500
4703
|
api_key: z.string().describe("Project client X-Api-Key."),
|
|
4501
4704
|
session_token: z.string().optional().describe("App_user session Bearer. Required by the route.")
|
|
4502
|
-
}, async ({ name, id, where, set, limit, api_key, session_token }) => {
|
|
4705
|
+
}, async ({ name, id, where, set, expected, limit, api_key, session_token }) => {
|
|
4503
4706
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
4504
4707
|
if (id !== void 0) return passthroughResult(await clientFetch(apiClient, {
|
|
4505
4708
|
method: "PATCH",
|
|
4506
4709
|
path: `/collections/${encodeURIComponent(name)}/${encodeURIComponent(id)}`,
|
|
4507
|
-
body:
|
|
4710
|
+
body: expected !== void 0 ? {
|
|
4711
|
+
set,
|
|
4712
|
+
expected
|
|
4713
|
+
} : { set },
|
|
4508
4714
|
apiKey: api_key,
|
|
4509
4715
|
...session_token !== void 0 && { sessionToken: session_token }
|
|
4510
4716
|
}));
|
|
@@ -4575,24 +4781,32 @@ function registerTools$9(server, apiClient) {
|
|
|
4575
4781
|
});
|
|
4576
4782
|
server.tool("amba_client_find_rows", [
|
|
4577
4783
|
"Query rows with the @layers/amba SDK-shaped filter body. POSTs to `/v1/client/collections/:name/find` — preferred over `amba_client_list_rows` for anything beyond a flat scan because GET-with-body is fragile across HTTP intermediaries.",
|
|
4578
|
-
"Filter shape: leaf `{column, op, value}` (op ∈ eq|ne|gt|gte|lt|lte|in|not_in|like|ilike|is_null|is_not_null) or combinator `{and: [...]}` / `{or: [...]}` / `{not: {...}}`.",
|
|
4579
|
-
"
|
|
4784
|
+
"Filter shape: leaf `{column, op, value}` (op ∈ eq|ne|gt|gte|lt|lte|in|not_in|like|ilike|is_null|is_not_null|contains|contained_by|overlaps) or combinator `{and: [...]}` / `{or: [...]}` / `{not: {...}}`.",
|
|
4785
|
+
"Keyword/fuzzy search: pass `search: { q, columns, fuzzy?, threshold? }` for classical catalog search across text columns (title/author/tags). Substring (ILIKE) by default; `fuzzy: true` is typo-tolerant and ranks by relevance.",
|
|
4786
|
+
"Auto-RLS — every match is AND'd with `user_id = <session app_user> AND deleted_at IS NULL` server-side (shared/unowned rows are also returned).",
|
|
4580
4787
|
"Authenticates as the end-user — pass `api_key` + `session_token`."
|
|
4581
4788
|
].join(" "), {
|
|
4582
4789
|
name: z.string().describe("Collection name."),
|
|
4583
4790
|
filter: sdkFilterSchema.optional(),
|
|
4584
4791
|
order: sdkOrderSchema.optional(),
|
|
4792
|
+
search: z.object({
|
|
4793
|
+
q: z.string().describe("Search text."),
|
|
4794
|
+
columns: z.array(z.string()).min(1).describe("Text columns to search."),
|
|
4795
|
+
fuzzy: z.boolean().optional().describe("Typo-tolerant trigram match + relevance ranking. Default false (substring)."),
|
|
4796
|
+
threshold: z.number().optional().describe("Fuzzy only: similarity threshold 0..1 (default 0.3).")
|
|
4797
|
+
}).optional().describe("Keyword / fuzzy text search across columns."),
|
|
4585
4798
|
limit: z.number().optional().describe("Max rows."),
|
|
4586
4799
|
cursor: z.string().optional().describe("Opaque cursor returned by a prior page."),
|
|
4587
4800
|
select: z.array(z.string()).optional().describe("Subset of columns to return."),
|
|
4588
4801
|
include_deleted: z.boolean().optional().describe("Include soft-deleted rows. Default FALSE."),
|
|
4589
4802
|
api_key: z.string().describe("Project client X-Api-Key."),
|
|
4590
4803
|
session_token: z.string().optional().describe("App_user session Bearer. Required by the route.")
|
|
4591
|
-
}, async ({ name, filter, order, limit, cursor, select, include_deleted, api_key, session_token }) => {
|
|
4804
|
+
}, async ({ name, filter, order, search, limit, cursor, select, include_deleted, api_key, session_token }) => {
|
|
4592
4805
|
if (!api_key) return MISSING_API_KEY_ERROR;
|
|
4593
4806
|
const body = {};
|
|
4594
4807
|
if (filter !== void 0) body.filter = filter;
|
|
4595
4808
|
if (order !== void 0) body.order = order;
|
|
4809
|
+
if (search !== void 0) body.search = search;
|
|
4596
4810
|
if (limit !== void 0) body.limit = limit;
|
|
4597
4811
|
if (cursor !== void 0) body.cursor = cursor;
|
|
4598
4812
|
if (select !== void 0) body.select = select;
|
|
@@ -4764,24 +4978,27 @@ function jsonResult(payload) {
|
|
|
4764
4978
|
* of a generic upstream 400.
|
|
4765
4979
|
*/
|
|
4766
4980
|
const MAX_BUNDLE_BYTES = 10 * 1024 * 1024;
|
|
4767
|
-
function registerTools$
|
|
4981
|
+
function registerTools$11(server, apiClient) {
|
|
4768
4982
|
registerTool(server, apiClient, "amba_functions_deploy", [
|
|
4769
4983
|
"Deploy a function to an Amba project. The supplied `code` is a fully bundled JavaScript module (one entry file) — the deploy endpoint uploads it server-side and records a new active deployment row, superseding any prior active version for the same `name`.",
|
|
4770
4984
|
"Function name must match /^[a-z][a-z0-9_-]*$/ and be ≤58 chars. Bundle size cap: 10 MiB (enforced client-side too — oversize uploads are rejected before the round-trip).",
|
|
4771
4985
|
"Optional `rate_limit` declares a per-function rate-limit config (validated server-side). Pass null/omit for no rate limit.",
|
|
4986
|
+
"Optional `public` (default false) deploys the function WITHOUT the X-Api-Key gate, so third-party webhook senders (RevenueCat, Stripe, GitHub, Slack) that cannot attach a custom header can reach it. SECURITY: when public, YOU must verify the webhook signature (HMAC) inside the function body — the runtime no longer requires a caller credential. `public: true` only skips the key requirement; rate-limiting and the per-request signed identity context still apply. Leave it false for functions only your own app calls.",
|
|
4772
4987
|
"Returns the function deployment row + the public URL (`fn_url`: `https://{project_slug}.fn.amba.host/{name}`)."
|
|
4773
4988
|
].join(" "), {
|
|
4774
4989
|
project_id: z.string().describe("The Amba project ID."),
|
|
4775
4990
|
name: z.string().describe("Function name. Lowercase, /^[a-z][a-z0-9_-]*$/, ≤58 chars."),
|
|
4776
4991
|
code: z.string().describe("Bundled JavaScript module source (one entry file)."),
|
|
4777
|
-
rate_limit: z.unknown().optional().describe("Optional rate-limit config object. Shape: see @layers/amba-shared:RateLimitConfig.")
|
|
4778
|
-
|
|
4992
|
+
rate_limit: z.unknown().optional().describe("Optional rate-limit config object. Shape: see @layers/amba-shared:RateLimitConfig."),
|
|
4993
|
+
public: z.boolean().optional().describe("Deploy the function as PUBLIC (no X-Api-Key required) so external webhook senders can reach it. Default false. When true, you MUST verify the webhook HMAC signature inside the function body — public skips the credential gate only; rate-limiting and signed identity context still apply.")
|
|
4994
|
+
}, async ({ project_id, name, code, rate_limit, public: isPublic }, { pat }) => {
|
|
4779
4995
|
const byteLen = Buffer.byteLength(code, "utf8");
|
|
4780
4996
|
if (byteLen > MAX_BUNDLE_BYTES) throw new Error(`Bundle size ${byteLen} bytes exceeds ${MAX_BUNDLE_BYTES}-byte cap (10 MiB).`);
|
|
4781
4997
|
const form = new FormData();
|
|
4782
4998
|
form.append("script", new Blob([code], { type: "application/javascript" }), "bundle.js");
|
|
4783
4999
|
const metadata = { name };
|
|
4784
5000
|
if (rate_limit !== void 0 && rate_limit !== null) metadata.rate_limit = rate_limit;
|
|
5001
|
+
if (isPublic === true) metadata.public = true;
|
|
4785
5002
|
form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" }), "metadata.json");
|
|
4786
5003
|
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/functions/deploy`, { formData: form }));
|
|
4787
5004
|
}, ["amba_deploy_function"]);
|
|
@@ -4889,7 +5106,7 @@ function registerTools$8(server, apiClient) {
|
|
|
4889
5106
|
}
|
|
4890
5107
|
//#endregion
|
|
4891
5108
|
//#region src/tools/sites.ts
|
|
4892
|
-
function registerTools$
|
|
5109
|
+
function registerTools$10(server, apiClient) {
|
|
4893
5110
|
registerTool(server, apiClient, "amba_sites_deploy", [
|
|
4894
5111
|
"Deploy a static site to an Amba project. Creates the site if it does not yet exist, then uploads the supplied files as a new deployment.",
|
|
4895
5112
|
"The deployment is immutable — its files live forever under the site's storage prefix — and the site flips to serve the new deployment atomically. Older deployments are kept for rollback via `amba_sites_list`.",
|
|
@@ -4981,7 +5198,7 @@ function registerTools$7(server, apiClient) {
|
|
|
4981
5198
|
}
|
|
4982
5199
|
//#endregion
|
|
4983
5200
|
//#region src/tools/domains.ts
|
|
4984
|
-
function registerTools$
|
|
5201
|
+
function registerTools$9(server, apiClient) {
|
|
4985
5202
|
registerTool(server, apiClient, "amba_domains_search", ["Search for available domains to buy through Amba. Pass a keyword or a full domain; returns candidate domains with availability and first-year price (USD).", "Free — searching never costs anything. Use this to help a user pick a name, then `amba_domains_check` to price specific picks and `amba_domains_purchase` to buy."].join(" "), {
|
|
4986
5203
|
project_id: z.string().describe("The Amba project ID."),
|
|
4987
5204
|
query: z.string().describe("A keyword (e.g. \"getunbury\") or a full domain (e.g. \"unbury.com\")."),
|
|
@@ -5037,7 +5254,7 @@ function registerTools$6(server, apiClient) {
|
|
|
5037
5254
|
}
|
|
5038
5255
|
//#endregion
|
|
5039
5256
|
//#region src/tools/secrets.ts
|
|
5040
|
-
function registerTools$
|
|
5257
|
+
function registerTools$8(server, apiClient) {
|
|
5041
5258
|
registerTool(server, apiClient, "amba_secrets_set", [
|
|
5042
5259
|
"Set or rotate a secret. Omit `function` for a PROJECT-WIDE secret (one value visible to every function in the project, including ones deployed later) — set it once instead of repeating per function. Pass `function` to scope the secret to just that function.",
|
|
5043
5260
|
"Writes a new version to the backing secret store and enqueues the sync. Subsequent function invocations see the new value once sync_status flips to \"synced\" (typically <30s).",
|
|
@@ -5107,22 +5324,26 @@ function registerTools$5(server, apiClient) {
|
|
|
5107
5324
|
}
|
|
5108
5325
|
//#endregion
|
|
5109
5326
|
//#region src/tools/ai-prompts-admin.ts
|
|
5110
|
-
const PROVIDER_ENUM = z.enum([
|
|
5327
|
+
const PROVIDER_ENUM$1 = z.enum([
|
|
5328
|
+
"anthropic",
|
|
5329
|
+
"openai",
|
|
5330
|
+
"mistral"
|
|
5331
|
+
]);
|
|
5111
5332
|
const RATE_LIMIT_SHAPE = z.object({
|
|
5112
5333
|
window: z.string().optional().describe("Window length (e.g. \"60s\", \"1m\", \"1h\"). Defaults to \"60s\"."),
|
|
5113
5334
|
max: z.number().int().min(1).optional().describe("Max invocations per window. Defaults to 20."),
|
|
5114
5335
|
key: z.string().optional().describe("Bucketing key. Typically \"user_id\" or \"session_id\". Defaults to \"user_id\".")
|
|
5115
5336
|
}).strict();
|
|
5116
|
-
function registerTools$
|
|
5337
|
+
function registerTools$7(server, apiClient) {
|
|
5117
5338
|
registerTool(server, apiClient, "amba_ai_prompts_create", [
|
|
5118
5339
|
"Register a new AI prompt template on a project. Returns the persisted prompt row (name, version, provider, model, client_invokable).",
|
|
5119
|
-
"Name must match /^[a-z][a-z0-9_-]{0,127}$/. Provider must be \"anthropic\" or \"
|
|
5340
|
+
"Name must match /^[a-z][a-z0-9_-]{0,127}$/. Provider must be \"anthropic\", \"openai\", or \"mistral\" and must already be registered via `amba_ai_providers_set`. Model is the provider-native model id (e.g. \"claude-sonnet-4-5-20250929\", \"gpt-4o-2024-08-06\").",
|
|
5120
5341
|
"Set `client_invokable=true` to allow SDK callers (e.g. amba-web, amba-react) to invoke the prompt via `Amba.ai.run()`. Defaults false — server-side / admin invocation only.",
|
|
5121
5342
|
"Re-issuing this tool with an existing `name` will bump the prompt to a new version; prefer `amba_ai_prompts_update` for that intent."
|
|
5122
5343
|
].join(" "), {
|
|
5123
5344
|
project_id: z.string().describe("The Amba project ID."),
|
|
5124
5345
|
name: z.string().describe("Prompt name. /^[a-z][a-z0-9_-]{0,127}$/."),
|
|
5125
|
-
provider: PROVIDER_ENUM.describe("AI provider. Must already be registered
|
|
5346
|
+
provider: PROVIDER_ENUM$1.describe("AI provider. Must already be registered via `amba_ai_providers_set`."),
|
|
5126
5347
|
model: z.string().describe("Provider-native model id."),
|
|
5127
5348
|
system_prompt: z.string().nullable().optional().describe("System prompt sent on every invocation. Omit or null for no system prompt."),
|
|
5128
5349
|
max_tokens: z.number().int().min(1).max(2e5).optional().describe("Max output tokens per invocation. Defaults to 4096."),
|
|
@@ -5152,7 +5373,7 @@ function registerTools$4(server, apiClient) {
|
|
|
5152
5373
|
registerTool(server, apiClient, "amba_ai_prompts_update", ["Update an existing AI prompt template. The new version replaces the old (incrementing `version`). Every field you supply overwrites the prior value; the API replaces ALL fields on every upsert, so always pass the full intended state.", "Returns the persisted row (with the bumped version)."].join(" "), {
|
|
5153
5374
|
project_id: z.string().describe("The Amba project ID."),
|
|
5154
5375
|
name: z.string().describe("Prompt name to update."),
|
|
5155
|
-
provider: PROVIDER_ENUM.describe("AI provider."),
|
|
5376
|
+
provider: PROVIDER_ENUM$1.describe("AI provider."),
|
|
5156
5377
|
model: z.string().describe("Provider-native model id."),
|
|
5157
5378
|
system_prompt: z.string().nullable().optional().describe("System prompt; null/omit to clear."),
|
|
5158
5379
|
max_tokens: z.number().int().min(1).max(2e5).optional().describe("Max output tokens."),
|
|
@@ -5232,6 +5453,179 @@ function registerTools$4(server, apiClient) {
|
|
|
5232
5453
|
}, ["amba_invoke_ai_prompt"]);
|
|
5233
5454
|
}
|
|
5234
5455
|
//#endregion
|
|
5456
|
+
//#region src/tools/ai-providers-admin.ts
|
|
5457
|
+
const PROVIDER_ENUM = z.enum([
|
|
5458
|
+
"anthropic",
|
|
5459
|
+
"openai",
|
|
5460
|
+
"mistral",
|
|
5461
|
+
"gemini"
|
|
5462
|
+
]);
|
|
5463
|
+
function registerTools$6(server, apiClient) {
|
|
5464
|
+
registerTool(server, apiClient, "amba_ai_providers_set", [
|
|
5465
|
+
"Register or rotate the upstream AI provider API key for a project. This is the key the AI gateway uses to call the model on your behalf when you invoke a prompt or `Amba.ai.*`.",
|
|
5466
|
+
"Call this BEFORE `amba_ai_prompts_create` — a prompt references a registered provider, and invocations fail with `provider_not_configured` (424) until a key is set.",
|
|
5467
|
+
"This is NOT `amba_secrets_set`: that writes function-scoped Worker secrets, which the AI gateway never reads. The provider key lives in a separate gateway-owned namespace and is set only here.",
|
|
5468
|
+
"The key is stored server-side; it is never returned. The response includes a short masked preview (`api_key_preview`) so you can confirm which key is active."
|
|
5469
|
+
].join(" "), {
|
|
5470
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5471
|
+
provider: PROVIDER_ENUM.describe("Provider the key belongs to: \"anthropic\", \"openai\", \"mistral\", or \"gemini\"."),
|
|
5472
|
+
api_key: z.string().min(10).describe("The upstream provider API key (e.g. an Anthropic or OpenAI account key).")
|
|
5473
|
+
}, async ({ project_id, provider, api_key }, { pat }) => {
|
|
5474
|
+
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/ai/providers`, { body: {
|
|
5475
|
+
name: provider,
|
|
5476
|
+
api_key
|
|
5477
|
+
} }));
|
|
5478
|
+
});
|
|
5479
|
+
registerTool(server, apiClient, "amba_ai_providers_list", "List the AI providers registered on a project. Returns each provider with a `configured` boolean (true once a key is set) and timestamps. Keys themselves are never returned.", { project_id: z.string().describe("The Amba project ID.") }, async ({ project_id }, { pat }) => {
|
|
5480
|
+
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/ai/providers`));
|
|
5481
|
+
});
|
|
5482
|
+
registerTool(server, apiClient, "amba_ai_providers_delete", "Revoke a registered AI provider key. Rejected with 409 PROVIDER_HAS_PROMPTS if any prompt still references the provider — delete those prompts first.", {
|
|
5483
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5484
|
+
provider: PROVIDER_ENUM.describe("Provider to revoke.")
|
|
5485
|
+
}, async ({ project_id, provider }, { pat }) => {
|
|
5486
|
+
return jsonResult(await callWithPat(apiClient, pat, "DELETE", `/projects/${encodeURIComponent(project_id)}/ai/providers/${encodeURIComponent(provider)}`) ?? {
|
|
5487
|
+
name: provider,
|
|
5488
|
+
deleted: true
|
|
5489
|
+
});
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5492
|
+
//#endregion
|
|
5493
|
+
//#region src/tools/experiments.ts
|
|
5494
|
+
const variantSchema = z.object({
|
|
5495
|
+
key: z.string().describe("Variant key (e.g. \"control\", \"treatment\"). Unique within the experiment."),
|
|
5496
|
+
weight: z.number().int().min(1).max(1e6).describe("Relative allocation weight (positive integer). Weights need not sum to 100.")
|
|
5497
|
+
});
|
|
5498
|
+
const STATUS_ENUM = z.enum([
|
|
5499
|
+
"active",
|
|
5500
|
+
"paused",
|
|
5501
|
+
"ended"
|
|
5502
|
+
]).describe("active = assigns + exposes new users; paused = serves existing assignments but issues no new ones; ended = read-only.");
|
|
5503
|
+
function registerTools$5(server, apiClient) {
|
|
5504
|
+
registerTool(server, apiClient, "amba_experiments_create", [
|
|
5505
|
+
"Create an A/B experiment: a stable `key`, a display `name`, and >= 2 weighted variants.",
|
|
5506
|
+
"End-users are bucketed into a STICKY variant the first time they request an assignment",
|
|
5507
|
+
"(via the SDK) using a deterministic hash over the variant weights; the bucket then never",
|
|
5508
|
+
"changes even if you re-weight later. The FIRST variant is the control for significance."
|
|
5509
|
+
].join(" "), {
|
|
5510
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5511
|
+
key: z.string().describe("Stable experiment key (the SDK addresses the experiment by this)."),
|
|
5512
|
+
name: z.string().describe("Human-readable experiment name."),
|
|
5513
|
+
description: z.string().nullable().optional().describe("Optional description."),
|
|
5514
|
+
variants: z.array(variantSchema).min(2).describe("At least 2 weighted variants. First is the control."),
|
|
5515
|
+
status: STATUS_ENUM.optional().describe("Initial status. Defaults to \"active\".")
|
|
5516
|
+
}, async ({ project_id, key, name, description, variants, status }, { client }) => {
|
|
5517
|
+
const payload = {
|
|
5518
|
+
key,
|
|
5519
|
+
name,
|
|
5520
|
+
variants
|
|
5521
|
+
};
|
|
5522
|
+
if (description !== void 0) payload.description = description;
|
|
5523
|
+
if (status !== void 0) payload.status = status;
|
|
5524
|
+
const result = await client.post(`/projects/${encodeURIComponent(project_id)}/experiments`, payload);
|
|
5525
|
+
return { content: [{
|
|
5526
|
+
type: "text",
|
|
5527
|
+
text: JSON.stringify(result, null, 2)
|
|
5528
|
+
}] };
|
|
5529
|
+
});
|
|
5530
|
+
registerTool(server, apiClient, "amba_experiments_list", "List every experiment on a project (key, name, variants, status, timestamps), ordered by key.", { project_id: z.string().describe("The Amba project ID.") }, async ({ project_id }, { client }) => {
|
|
5531
|
+
const result = await client.get(`/projects/${encodeURIComponent(project_id)}/experiments`);
|
|
5532
|
+
return { content: [{
|
|
5533
|
+
type: "text",
|
|
5534
|
+
text: JSON.stringify(result, null, 2)
|
|
5535
|
+
}] };
|
|
5536
|
+
});
|
|
5537
|
+
registerTool(server, apiClient, "amba_experiments_get", "Get a single experiment by key (name, variants, status, timestamps). 404 if not found.", {
|
|
5538
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5539
|
+
key: z.string().describe("Experiment key.")
|
|
5540
|
+
}, async ({ project_id, key }, { client }) => {
|
|
5541
|
+
const result = await client.get(`/projects/${encodeURIComponent(project_id)}/experiments/${encodeURIComponent(key)}`);
|
|
5542
|
+
return { content: [{
|
|
5543
|
+
type: "text",
|
|
5544
|
+
text: JSON.stringify(result, null, 2)
|
|
5545
|
+
}] };
|
|
5546
|
+
});
|
|
5547
|
+
registerTool(server, apiClient, "amba_experiments_update", [
|
|
5548
|
+
"Update an experiment's name, description, variants, and/or status. Only the supplied",
|
|
5549
|
+
"fields change; variants (when supplied) replace the whole list. Re-weighting NEVER",
|
|
5550
|
+
"re-rolls already-assigned users (assignments are sticky); it only affects users assigned",
|
|
5551
|
+
"after the edit. Set status to \"paused\" to stop new assignments or \"ended\" to freeze it."
|
|
5552
|
+
].join(" "), {
|
|
5553
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5554
|
+
key: z.string().describe("Experiment key to update."),
|
|
5555
|
+
name: z.string().optional().describe("New name."),
|
|
5556
|
+
description: z.string().nullable().optional().describe("New description; null to clear."),
|
|
5557
|
+
variants: z.array(variantSchema).min(2).optional().describe("Replacement variant list (>= 2). First is the control."),
|
|
5558
|
+
status: STATUS_ENUM.optional().describe("New status.")
|
|
5559
|
+
}, async ({ project_id, key, name, description, variants, status }, { client }) => {
|
|
5560
|
+
const payload = {};
|
|
5561
|
+
if (name !== void 0) payload.name = name;
|
|
5562
|
+
if (description !== void 0) payload.description = description;
|
|
5563
|
+
if (variants !== void 0) payload.variants = variants;
|
|
5564
|
+
if (status !== void 0) payload.status = status;
|
|
5565
|
+
const result = await client.patch(`/projects/${encodeURIComponent(project_id)}/experiments/${encodeURIComponent(key)}`, payload);
|
|
5566
|
+
return { content: [{
|
|
5567
|
+
type: "text",
|
|
5568
|
+
text: JSON.stringify(result, null, 2)
|
|
5569
|
+
}] };
|
|
5570
|
+
});
|
|
5571
|
+
registerTool(server, apiClient, "amba_experiments_delete", "Delete an experiment by key. Cascades its assignments + exposures. Hard delete.", {
|
|
5572
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5573
|
+
key: z.string().describe("Experiment key to delete.")
|
|
5574
|
+
}, async ({ project_id, key }, { client }) => {
|
|
5575
|
+
const result = await client.delete(`/projects/${encodeURIComponent(project_id)}/experiments/${encodeURIComponent(key)}`);
|
|
5576
|
+
return { content: [{
|
|
5577
|
+
type: "text",
|
|
5578
|
+
text: JSON.stringify(result ?? {
|
|
5579
|
+
key,
|
|
5580
|
+
deleted: true
|
|
5581
|
+
}, null, 2)
|
|
5582
|
+
}] };
|
|
5583
|
+
});
|
|
5584
|
+
registerTool(server, apiClient, "amba_experiments_results", [
|
|
5585
|
+
"Compute per-variant results for an experiment: assigned_count, exposed_count, and",
|
|
5586
|
+
"(when `conversion_event` is supplied) converted_count + conversion_rate — the distinct",
|
|
5587
|
+
"exposed users who emitted that analytics event at/after their assignment. Each non-control",
|
|
5588
|
+
"variant also gets a two-proportion z-test vs the control (first variant): a z_score and an",
|
|
5589
|
+
"approximate two-sided p_value. The control variant's z_score/p_value are null."
|
|
5590
|
+
].join(" "), {
|
|
5591
|
+
project_id: z.string().describe("The Amba project ID."),
|
|
5592
|
+
key: z.string().describe("Experiment key."),
|
|
5593
|
+
conversion_event: z.string().optional().describe("Analytics event_name to treat as the conversion goal. Omit to get assigned/exposed counts only (conversions reported as 0).")
|
|
5594
|
+
}, async ({ project_id, key, conversion_event }, { client }) => {
|
|
5595
|
+
const query = {};
|
|
5596
|
+
if (conversion_event !== void 0) query.conversion_event = conversion_event;
|
|
5597
|
+
const result = await client.get(`/projects/${encodeURIComponent(project_id)}/experiments/${encodeURIComponent(key)}/results`, query);
|
|
5598
|
+
return { content: [{
|
|
5599
|
+
type: "text",
|
|
5600
|
+
text: JSON.stringify(result, null, 2)
|
|
5601
|
+
}] };
|
|
5602
|
+
});
|
|
5603
|
+
}
|
|
5604
|
+
//#endregion
|
|
5605
|
+
//#region src/tools/promotion.ts
|
|
5606
|
+
function registerTools$4(server, apiClient) {
|
|
5607
|
+
registerTool(server, apiClient, "amba_project_export", [
|
|
5608
|
+
"Export a declarative bundle of a project's reusable configuration so it can be re-created in another project (e.g. promote dev → prod) in one operation.",
|
|
5609
|
+
"The bundle includes (each section only when present): remote configs, collection SCHEMAS (column defs + indexes, no rows), content libraries + their items, and the full gamification set — currencies, achievements, streaks, challenges, leaderboards, and xp rules — as DEFINITIONS only.",
|
|
5610
|
+
"It carries NO secrets, NO per-user data, and NO end-user rows. Pass the returned `bundle` to `amba_project_import` against the target project."
|
|
5611
|
+
].join(" "), { project_id: z.string().describe("The Amba project ID to export configuration FROM.") }, async ({ project_id }, { pat }) => {
|
|
5612
|
+
return jsonResult(await callWithPat(apiClient, pat, "GET", `/projects/${encodeURIComponent(project_id)}/promotion/export`));
|
|
5613
|
+
});
|
|
5614
|
+
registerTool(server, apiClient, "amba_project_import", [
|
|
5615
|
+
"Import a declarative configuration bundle (from `amba_project_export`) into a project. Idempotently creates remote configs, collections, content libraries + items, and gamification definitions (currencies, achievements, streaks, challenges, leaderboards, xp rules).",
|
|
5616
|
+
"mode=\"skip_existing\" (default) leaves any entity that already exists (by name/key/code) untouched; mode=\"merge\" refreshes the definition of existing entities. Either mode is safe to run twice — re-import never duplicates.",
|
|
5617
|
+
"Returns a per-section summary of { created, skipped, updated }. Collections are create-if-absent only (an existing collection is skipped; alter the schema via the dedicated collection tools)."
|
|
5618
|
+
].join(" "), {
|
|
5619
|
+
project_id: z.string().describe("The Amba project ID to import configuration INTO."),
|
|
5620
|
+
bundle: z.record(z.unknown()).describe("The bundle object returned by `amba_project_export` (its `data` field)."),
|
|
5621
|
+
mode: z.enum(["merge", "skip_existing"]).optional().describe("Conflict resolution. \"skip_existing\" (default) no-ops on a name/key/code conflict; \"merge\" updates the existing definition.")
|
|
5622
|
+
}, async ({ project_id, bundle, mode }, { pat }) => {
|
|
5623
|
+
const body = { bundle };
|
|
5624
|
+
if (mode !== void 0) body.mode = mode;
|
|
5625
|
+
return jsonResult(await callWithPat(apiClient, pat, "POST", `/projects/${encodeURIComponent(project_id)}/promotion/import`, { body }));
|
|
5626
|
+
});
|
|
5627
|
+
}
|
|
5628
|
+
//#endregion
|
|
5235
5629
|
//#region src/tools/billing.ts
|
|
5236
5630
|
/**
|
|
5237
5631
|
* Static tier catalog. Mirrors the marketing-page structure shipped in
|
|
@@ -6993,7 +7387,7 @@ Admin tools authenticate the developer/agent (pass \`pat\` or send it as the inb
|
|
|
6993
7387
|
| \`amba_collections_create\` | Create a typed collection. Pass \`shared: true\` for developer-seeded GLOBAL content (question banks, lookup tables) so \`user_id\` is nullable. | \`{ 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 }], shared: false }\` |
|
|
6994
7388
|
| \`amba_collections_list\` | List collections in this project. | \`{ project_id }\` |
|
|
6995
7389
|
| \`amba_collections_get\` | Read one collection's schema. | \`{ project_id, name: "todos" }\` |
|
|
6996
|
-
| \`amba_collections_alter\` | Exactly ONE of: \`add_column\`, \`add_index\`, \`drop_column\`, or \`relax_user_id\` per call. \`relax_user_id: true\` converts an existing collection to shared (drops the \`user_id\` NOT NULL). | \`{ project_id, name: "todos", add_column: { name: "priority", type: "
|
|
7390
|
+
| \`amba_collections_alter\` | Exactly ONE of: \`add_column\`, \`add_index\`, \`drop_column\`, or \`relax_user_id\` per call. \`relax_user_id: true\` converts an existing collection to shared (drops the \`user_id\` NOT NULL). | \`{ project_id, name: "todos", add_column: { name: "priority", type: "integer", nullable: true } }\` |
|
|
6997
7391
|
| \`amba_collections_delete\` | Drop the table (destructive). \`confirm\` must equal the collection name. | \`{ project_id, name: "todos", confirm: "todos" }\` |
|
|
6998
7392
|
| \`amba_admin_insert_row\` | Insert one row as the developer (bypasses user-scope; \`user_id\` honored if present). | \`{ project_id, name: "todos", row: { title: "Sample", done: false } }\` |
|
|
6999
7393
|
| \`amba_admin_insert_rows\` | Bulk-insert up to 500 rows in one atomic statement — the canonical seeding/migration path. \`on_conflict\`: \`"error"\` (default) or \`"skip"\`. | \`{ project_id, name: "questions", rows: [{ q: "..." }, { q: "..." }], on_conflict: "skip" }\` |
|
|
@@ -7007,7 +7401,7 @@ Admin tools authenticate the developer/agent (pass \`pat\` or send it as the inb
|
|
|
7007
7401
|
| \`amba_client_find_rows\` | Filter / sort / paginate rows (SDK-shaped \`filter\`). | \`{ api_key, session_token, name: "todos", filter: {...}, order: ["created_at desc"], limit: 50 }\` |
|
|
7008
7402
|
| \`amba_client_find_nearest_rows\` | Vector-similarity search (rows with a \`vector(<dim>)\` column). | \`{ api_key, session_token, name: "todos", column: "embedding", to_vector: [...], k: 10 }\` |
|
|
7009
7403
|
|
|
7010
|
-
Column types: \`text\`, \`
|
|
7404
|
+
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.
|
|
7011
7405
|
|
|
7012
7406
|
### Functions (serverless code)
|
|
7013
7407
|
|
|
@@ -7027,16 +7421,23 @@ Run user code in a sandbox triggered by HTTP, cron, or webhook. The function get
|
|
|
7027
7421
|
|
|
7028
7422
|
### AI prompts
|
|
7029
7423
|
|
|
7030
|
-
Managed LLM templates: stored prompt with model + system message
|
|
7424
|
+
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.
|
|
7425
|
+
|
|
7426
|
+
**Two steps, in order:** first 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.
|
|
7427
|
+
|
|
7428
|
+
> The provider key is **not** a function secret. \`amba_secrets_set\` writes function-scoped Worker secrets, which the AI gateway never reads. Provider keys live in a separate gateway-owned store and are set **only** via \`amba_ai_providers_set\`.
|
|
7031
7429
|
|
|
7032
7430
|
| Tool | Purpose | Example args |
|
|
7033
7431
|
| --- | --- | --- |
|
|
7034
|
-
| \`
|
|
7432
|
+
| \`amba_ai_providers_set\` | Register / rotate the upstream provider API key. **Do this first.** | \`{ project_id, provider: "anthropic", api_key: "sk-ant-..." }\` |
|
|
7433
|
+
| \`amba_ai_providers_list\` | List registered providers (\`configured\` = key set). | \`{ project_id }\` |
|
|
7434
|
+
| \`amba_ai_providers_delete\` | Revoke a provider key (fails if prompts still reference it). | \`{ project_id, provider: "anthropic" }\` |
|
|
7435
|
+
| \`amba_ai_prompts_create\` | Create a prompt template. \`client_invokable: true\` lets the device SDK invoke it directly. | \`{ project_id, name: "summarize", provider: "anthropic", model: "claude-opus-4-5", system_prompt: "Summarize the user's text in 2 sentences.", client_invokable: true }\` |
|
|
7035
7436
|
| \`amba_ai_prompts_list\` | List prompts. | \`{ project_id }\` |
|
|
7036
|
-
| \`amba_ai_prompts_get\` | Read one prompt. | \`{ project_id,
|
|
7037
|
-
| \`amba_ai_prompts_update\` | Edit a prompt. | \`{ project_id,
|
|
7038
|
-
| \`amba_ai_prompts_invoke\` | Invoke a prompt server-side (admin testing). | \`{ project_id,
|
|
7039
|
-
| \`amba_ai_prompts_delete\` | Delete. | \`{ project_id,
|
|
7437
|
+
| \`amba_ai_prompts_get\` | Read one prompt. | \`{ project_id, name }\` |
|
|
7438
|
+
| \`amba_ai_prompts_update\` | Edit a prompt (replaces all fields; bumps version). | \`{ project_id, name, provider, model, system_prompt: "..." }\` |
|
|
7439
|
+
| \`amba_ai_prompts_invoke\` | Invoke a prompt server-side (admin testing). \`messages\` is a provider-shaped array. | \`{ project_id, name, messages: [{ role: "user", content: "..." }] }\` |
|
|
7440
|
+
| \`amba_ai_prompts_delete\` | Delete. | \`{ project_id, name }\` |
|
|
7040
7441
|
|
|
7041
7442
|
### Analytics + events + sessions
|
|
7042
7443
|
|
|
@@ -7052,9 +7453,11 @@ Managed LLM templates: stored prompt with model + system message + variables, ca
|
|
|
7052
7453
|
|
|
7053
7454
|
### Secrets + configs + integrations
|
|
7054
7455
|
|
|
7456
|
+
Secrets here are **function-scoped** — they become environment bindings on your deployed functions. They are NOT where AI provider keys go (use \`amba_ai_providers_set\` for those — see AI prompts above).
|
|
7457
|
+
|
|
7055
7458
|
| Tool | Purpose | Example args |
|
|
7056
7459
|
| --- | --- | --- |
|
|
7057
|
-
| \`amba_secrets_set\` | Set a
|
|
7460
|
+
| \`amba_secrets_set\` | Set a function-scoped secret (encrypted at rest; bound on the next deploy). | \`{ project_id, name: "STRIPE_WEBHOOK_SECRET", value: "whsec_..." }\` |
|
|
7058
7461
|
| \`amba_secrets_get\` | Read a secret (returns \`"<redacted>"\` unless explicitly requested). | \`{ project_id, name }\` |
|
|
7059
7462
|
| \`amba_secrets_list\` | List secret names. | \`{ project_id }\` |
|
|
7060
7463
|
| \`amba_secrets_delete\` | Delete. | \`{ project_id, name }\` |
|
|
@@ -7113,9 +7516,9 @@ const newTodo = await Amba.collections.insert('todos', { title: 'Ship the app',
|
|
|
7113
7516
|
await Amba.collections.update('todos', newTodo.id, { done: true });
|
|
7114
7517
|
await Amba.collections.delete('todos', newTodo.id);
|
|
7115
7518
|
|
|
7116
|
-
// AI — call a managed prompt
|
|
7519
|
+
// AI — call a managed prompt (prompt_slug names the registered prompt)
|
|
7117
7520
|
const response = await Amba.ai.anthropic.messages.create({
|
|
7118
|
-
|
|
7521
|
+
prompt_slug: 'summarize',
|
|
7119
7522
|
variables: { text: 'A long article about backend services …' },
|
|
7120
7523
|
});
|
|
7121
7524
|
|
|
@@ -7183,8 +7586,7 @@ let showBeta = try await Amba.flags.get(name: "beta_feature")
|
|
|
7183
7586
|
try await Amba.events.track("app_opened", properties: ["source": "deep_link"])
|
|
7184
7587
|
|
|
7185
7588
|
let reply = try await Amba.ai.anthropic.messages.create(
|
|
7186
|
-
|
|
7187
|
-
variables: ["text": "A long article..."]
|
|
7589
|
+
request: AiMessageRequest(promptSlug: "summarize", variables: ["text": "A long article..."])
|
|
7188
7590
|
)
|
|
7189
7591
|
\`\`\`
|
|
7190
7592
|
|
|
@@ -7241,7 +7643,7 @@ Batch.
|
|
|
7241
7643
|
- [ ] Resend (transactional email)
|
|
7242
7644
|
- [ ] Stripe (web payments / subscriptions)
|
|
7243
7645
|
- [ ] Mixpanel / PostHog / Segment (analytics forwarding)
|
|
7244
|
-
- [ ] OpenAI / Anthropic
|
|
7646
|
+
- [ ] OpenAI / Anthropic / Mistral / Gemini LLM keys (required for \`Amba.ai.*\` — set via \`amba_ai_providers_set\`, **not** \`amba_integrations_configure\`)
|
|
7245
7647
|
|
|
7246
7648
|
6. **Feature flags:** seed any starter flags?
|
|
7247
7649
|
- Yes — wire \`beta_feature\` (off by default) so I can ship the wiring before the feature exists
|
|
@@ -7256,7 +7658,7 @@ Batch.
|
|
|
7256
7658
|
1. Before creating:
|
|
7257
7659
|
- \`amba_collections_list\` — match on \`name\`. Collisions: never silently recreate (data loss). Offer \`amba_collections_alter\` to add new columns instead.
|
|
7258
7660
|
- \`amba_functions_list\` — match on \`name\`. Collisions: ask to redeploy (with the new source) or skip.
|
|
7259
|
-
- \`amba_ai_prompts_list\` — match on \`
|
|
7661
|
+
- \`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.)
|
|
7260
7662
|
- \`amba_integrations_list\` — match on \`provider\`. Same.
|
|
7261
7663
|
- \`amba_configs_list\` — match on \`key\`. Same.
|
|
7262
7664
|
|
|
@@ -7438,6 +7840,8 @@ const TOOL_CATEGORY = {
|
|
|
7438
7840
|
amba_developer_rotate_pat: "identity",
|
|
7439
7841
|
amba_users_list: "identity",
|
|
7440
7842
|
amba_list_users: "identity",
|
|
7843
|
+
amba_users_create: "identity",
|
|
7844
|
+
amba_create_user: "identity",
|
|
7441
7845
|
amba_users_get: "identity",
|
|
7442
7846
|
amba_users_delete: "identity",
|
|
7443
7847
|
amba_users_export: "identity",
|
|
@@ -7486,6 +7890,7 @@ const TOOL_CATEGORY = {
|
|
|
7486
7890
|
amba_onboarding_get_stats: "engagement",
|
|
7487
7891
|
amba_get_onboarding_stats: "engagement",
|
|
7488
7892
|
amba_content_libraries_create: "engagement",
|
|
7893
|
+
amba_content_libraries_delete: "engagement",
|
|
7489
7894
|
amba_create_content_library: "engagement",
|
|
7490
7895
|
amba_content_list_libraries: "engagement",
|
|
7491
7896
|
amba_content_items_add: "engagement",
|
|
@@ -7555,6 +7960,14 @@ const TOOL_CATEGORY = {
|
|
|
7555
7960
|
amba_update_leaderboard: "gamification",
|
|
7556
7961
|
amba_leaderboards_delete: "gamification",
|
|
7557
7962
|
amba_delete_leaderboard: "gamification",
|
|
7963
|
+
amba_leagues_create: "gamification",
|
|
7964
|
+
amba_create_league: "gamification",
|
|
7965
|
+
amba_leagues_list: "gamification",
|
|
7966
|
+
amba_list_leagues: "gamification",
|
|
7967
|
+
amba_leagues_update: "gamification",
|
|
7968
|
+
amba_update_league: "gamification",
|
|
7969
|
+
amba_leagues_list_cohorts: "gamification",
|
|
7970
|
+
amba_list_league_cohorts: "gamification",
|
|
7558
7971
|
amba_challenges_create: "gamification",
|
|
7559
7972
|
amba_create_challenge: "gamification",
|
|
7560
7973
|
amba_challenges_list: "gamification",
|
|
@@ -7683,6 +8096,14 @@ const TOOL_CATEGORY = {
|
|
|
7683
8096
|
amba_funnels_update: "analytics",
|
|
7684
8097
|
amba_funnels_delete: "analytics",
|
|
7685
8098
|
amba_funnels_query: "analytics",
|
|
8099
|
+
amba_experiments_create: "analytics",
|
|
8100
|
+
amba_experiments_list: "analytics",
|
|
8101
|
+
amba_experiments_get: "analytics",
|
|
8102
|
+
amba_experiments_update: "analytics",
|
|
8103
|
+
amba_experiments_delete: "analytics",
|
|
8104
|
+
amba_experiments_results: "analytics",
|
|
8105
|
+
amba_project_export: "infrastructure",
|
|
8106
|
+
amba_project_import: "infrastructure",
|
|
7686
8107
|
amba_projects_create: "infrastructure",
|
|
7687
8108
|
amba_create_project: "infrastructure",
|
|
7688
8109
|
amba_projects_list: "infrastructure",
|
|
@@ -7709,6 +8130,7 @@ const TOOL_CATEGORY = {
|
|
|
7709
8130
|
amba_delete_collection: "infrastructure",
|
|
7710
8131
|
amba_admin_insert_row: "infrastructure",
|
|
7711
8132
|
amba_admin_list_rows: "infrastructure",
|
|
8133
|
+
amba_admin_aggregate_rows: "infrastructure",
|
|
7712
8134
|
amba_client_insert_row: "infrastructure",
|
|
7713
8135
|
amba_client_list_rows: "infrastructure",
|
|
7714
8136
|
amba_client_get_row: "infrastructure",
|
|
@@ -7784,6 +8206,9 @@ const TOOL_CATEGORY = {
|
|
|
7784
8206
|
amba_integrations_patch: "infrastructure",
|
|
7785
8207
|
amba_integrations_test: "infrastructure",
|
|
7786
8208
|
amba_test_integration: "infrastructure",
|
|
8209
|
+
amba_ai_providers_set: "infrastructure",
|
|
8210
|
+
amba_ai_providers_list: "infrastructure",
|
|
8211
|
+
amba_ai_providers_delete: "infrastructure",
|
|
7787
8212
|
amba_ai_prompts_create: "infrastructure",
|
|
7788
8213
|
amba_create_ai_prompt: "infrastructure",
|
|
7789
8214
|
amba_ai_prompts_list: "infrastructure",
|
|
@@ -7850,7 +8275,11 @@ function getToolCategory(toolName) {
|
|
|
7850
8275
|
* called against it once.
|
|
7851
8276
|
*/
|
|
7852
8277
|
function registerAllTools(server, apiClient) {
|
|
7853
|
-
registerTools$
|
|
8278
|
+
registerTools$13(server, apiClient);
|
|
8279
|
+
registerTools$33(server, apiClient);
|
|
8280
|
+
registerTools$32(server, apiClient);
|
|
8281
|
+
registerTools$31(server, apiClient);
|
|
8282
|
+
registerTools$30(server, apiClient);
|
|
7854
8283
|
registerTools$29(server, apiClient);
|
|
7855
8284
|
registerTools$28(server, apiClient);
|
|
7856
8285
|
registerTools$27(server, apiClient);
|
|
@@ -7867,9 +8296,9 @@ function registerAllTools(server, apiClient) {
|
|
|
7867
8296
|
registerTools$16(server, apiClient);
|
|
7868
8297
|
registerTools$15(server, apiClient);
|
|
7869
8298
|
registerTools$14(server, apiClient);
|
|
7870
|
-
registerTools$13(server, apiClient);
|
|
7871
8299
|
registerTools$12(server, apiClient);
|
|
7872
8300
|
registerTools$11(server, apiClient);
|
|
8301
|
+
registerTools$10(server, apiClient);
|
|
7873
8302
|
registerTools$9(server, apiClient);
|
|
7874
8303
|
registerTools$8(server, apiClient);
|
|
7875
8304
|
registerTools$7(server, apiClient);
|