@layers/amba 1.1.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api-client.d.ts +33 -6
- package/dist/auth.d.ts +8 -0
- package/dist/bundle.d.ts +23 -9
- package/dist/commands/billing.d.ts +34 -0
- package/dist/commands/claim.d.ts +41 -0
- package/dist/commands/init.d.ts +24 -24
- package/dist/commands/projects.d.ts +8 -0
- package/dist/credentials.d.ts +259 -0
- package/dist/index.js +2418 -657
- package/dist/sandbox.d.ts +35 -37
- package/dist/skill-installer.d.ts +95 -0
- package/dist/skills/presets.d.ts +146 -0
- package/dist/skills.d.ts +239 -48
- package/package.json +4 -2
- package/skill-bundle/SKILL.md +324 -0
- package/skill-bundle/references/economy.md +331 -0
- package/skill-bundle/references/engagement.md +400 -0
- package/skill-bundle/references/gamification.md +316 -0
- package/skill-bundle/references/identity.md +395 -0
- package/skill-bundle/references/infrastructure.md +348 -0
- package/skill-bundle/references/social.md +366 -0
package/dist/sandbox.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface SandboxSignupRequest {
|
|
|
47
47
|
/**
|
|
48
48
|
* Shape returned by `POST /v1/auth/developer/signup`. Only the fields the
|
|
49
49
|
* sandbox flow actually consumes are typed — the response body has more
|
|
50
|
-
* (
|
|
50
|
+
* (refresh token, expiry, etc.) but we don't need them here.
|
|
51
51
|
*/
|
|
52
52
|
export interface SandboxSignupResponse {
|
|
53
53
|
pat: string;
|
|
@@ -61,6 +61,15 @@ export interface SandboxSignupResponse {
|
|
|
61
61
|
verify_url?: string;
|
|
62
62
|
/** The actual email we used (may be the auto-generated one). */
|
|
63
63
|
email: string;
|
|
64
|
+
/**
|
|
65
|
+
* Developer row returned in `data.developer`. Required for callers
|
|
66
|
+
* (e.g. `ensureDeveloperIdentity`) to populate the developer_id on
|
|
67
|
+
* the freshly-minted local credentials without a second round trip
|
|
68
|
+
* to `/developer/me`.
|
|
69
|
+
*/
|
|
70
|
+
developer_id: string;
|
|
71
|
+
/** Developer name, if set on the row. */
|
|
72
|
+
developer_name?: string;
|
|
64
73
|
}
|
|
65
74
|
export interface McpClientConfigTarget {
|
|
66
75
|
/** Display name for logging. */
|
|
@@ -96,6 +105,18 @@ export interface SandboxResult {
|
|
|
96
105
|
* (e.g. `--no-skills`).
|
|
97
106
|
*/
|
|
98
107
|
skillPath: string | null;
|
|
108
|
+
/**
|
|
109
|
+
* Per-agent setup-guide fan-out result. One entry per writer that
|
|
110
|
+
* succeeded. Empty array when `--no-skills` was passed (the same flag
|
|
111
|
+
* gates both the build-task skill and the cross-agent fan-out — they
|
|
112
|
+
* write into the same project surfaces and there's no use-case for
|
|
113
|
+
* skipping one but not the other).
|
|
114
|
+
*/
|
|
115
|
+
setupTargets: Array<{
|
|
116
|
+
target: string;
|
|
117
|
+
path: string;
|
|
118
|
+
mode: 'created' | 'refreshed';
|
|
119
|
+
}>;
|
|
99
120
|
}
|
|
100
121
|
/**
|
|
101
122
|
* Generate a deterministic-looking but globally-unique sandbox email.
|
|
@@ -142,48 +163,21 @@ export declare function performSandboxSignup(req: SandboxSignupRequest, options?
|
|
|
142
163
|
apiUrl?: string;
|
|
143
164
|
fetchImpl?: typeof fetch;
|
|
144
165
|
}): Promise<SandboxSignupResponse>;
|
|
145
|
-
/**
|
|
146
|
-
* Result of writing sandbox credentials. The optional `backedUpTo` lets
|
|
147
|
-
* the CLI surface a "we moved your existing creds aside" notice so a
|
|
148
|
-
* developer who accidentally ran `--sandbox` on top of a real OAuth
|
|
149
|
-
* session can recover.
|
|
150
|
-
*/
|
|
151
|
-
export interface WriteCredentialsResult {
|
|
152
|
-
path: string;
|
|
153
|
-
/** Absolute path of the backup file, or null if no backup was made. */
|
|
154
|
-
backedUpTo: string | null;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Write the PAT to `~/.amba/credentials.json` (chmod 0600) in a shape
|
|
158
|
-
* the existing `loadCredentials` reader recognises.
|
|
159
|
-
*
|
|
160
|
-
* `auth.ts` was built around browser-OAuth tokens (`access_token` +
|
|
161
|
-
* `refresh_token` + `expires_at`). PATs are long-lived and don't refresh
|
|
162
|
-
* — but the stored-creds reader only inspects `access_token`, so we
|
|
163
|
-
* write the PAT there and leave `refresh_token` empty + a far-future
|
|
164
|
-
* `expires_at` so the expiry guard never fires.
|
|
165
|
-
*
|
|
166
|
-
* Real-credential safety: if the file already exists AND its `source`
|
|
167
|
-
* is NOT `'sandbox-init'` AND `access_token` is non-empty, we treat it
|
|
168
|
-
* as a real OAuth/PAT session and back it up to
|
|
169
|
-
* `credentials.json.bak-<unix-ms>` before overwriting. The next
|
|
170
|
-
* `--sandbox` run reuses our own previous sandbox creds without
|
|
171
|
-
* back-up. This keeps the agentic flow idempotent while preventing a
|
|
172
|
-
* silent clobber of a developer's real account.
|
|
173
|
-
*/
|
|
174
|
-
export declare function writeSandboxCredentials(pat: string, options?: {
|
|
175
|
-
homeDir?: string;
|
|
176
|
-
}): Promise<WriteCredentialsResult>;
|
|
177
166
|
/**
|
|
178
167
|
* Write or update `<cwd>/.env.local` with the sandbox project's keys.
|
|
179
168
|
*
|
|
180
169
|
* Mirrors the `init` interactive flow exactly so the existing env-read
|
|
181
170
|
* conventions in the SDKs and CLI commands keep working. The merge
|
|
182
171
|
* logic: if the file already exists and contains an `AMBA_PROJECT_ID`
|
|
183
|
-
* line we replace the
|
|
172
|
+
* line we replace the Amba lines in place; otherwise we append a
|
|
184
173
|
* fresh stanza.
|
|
174
|
+
*
|
|
175
|
+
* `serverKey` is optional — pass it on the new two-scope credential
|
|
176
|
+
* model where init mints both client+server. Pre-existing AMBA_SERVER_KEY
|
|
177
|
+
* lines are refreshed when a new value is provided and removed when
|
|
178
|
+
* serverKey is null AND no prior line existed (no-op on second case).
|
|
185
179
|
*/
|
|
186
|
-
export declare function writeSandboxEnvLocal(cwd: string, projectId: string, clientKey: string, apiUrl: string): Promise<string>;
|
|
180
|
+
export declare function writeSandboxEnvLocal(cwd: string, projectId: string, clientKey: string, apiUrl: string, serverKey?: string | null): Promise<string>;
|
|
187
181
|
/**
|
|
188
182
|
* Write the AMBA.md sandbox-tier guide to `<cwd>/AMBA.md`.
|
|
189
183
|
*
|
|
@@ -212,8 +206,12 @@ export declare function writeSandboxAmbaMd(cwd: string, ctx: {
|
|
|
212
206
|
export declare function buildAmbaMcpEntry(pat: string): Record<string, unknown>;
|
|
213
207
|
/**
|
|
214
208
|
* Identifier for the MCP client family a config file belongs to.
|
|
215
|
-
*
|
|
216
|
-
*
|
|
209
|
+
* Kept around so callers can reason about which agent runtime a given
|
|
210
|
+
* config path targets — the CLI no longer prints per-client restart
|
|
211
|
+
* bullets (the new flow frames MCP wiring as "active next agent
|
|
212
|
+
* launch" rather than asking the developer to restart), but the
|
|
213
|
+
* classifier is still useful for callers that want to render per-
|
|
214
|
+
* client labels in their own UI.
|
|
217
215
|
*/
|
|
218
216
|
export type McpClientKind = 'claude-code' | 'cursor' | 'windsurf';
|
|
219
217
|
/**
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Amba skill bundle installer.
|
|
3
|
+
*
|
|
4
|
+
* The bundled `skill-bundle/` directory contains `SKILL.md` plus a
|
|
5
|
+
* `references/` folder with one file per Amba surface area. The skill
|
|
6
|
+
* teaches the agent the classify → confirm → wire-up playbook for
|
|
7
|
+
* adding Amba primitives to a developer's codebase. See
|
|
8
|
+
* `packages/cli/skill-bundle/SKILL.md` for the source.
|
|
9
|
+
*
|
|
10
|
+
* Why ship a bundled skill (instead of `npx skills add layers/amba`):
|
|
11
|
+
* the CLI run is the same install step. Bundling avoids a second
|
|
12
|
+
* fetch, keeps the skill version locked to the CLI version, and means
|
|
13
|
+
* `amba init` produces a fully-wired agent on offline networks too.
|
|
14
|
+
*
|
|
15
|
+
* Cross-agent install — we drop the same body into every detected
|
|
16
|
+
* coding agent's skill directory. Agents read their own location:
|
|
17
|
+
*
|
|
18
|
+
* - Claude Code: `.claude/skills/amba/`
|
|
19
|
+
* - Cursor: `.cursor/skills/amba/`
|
|
20
|
+
* - Codex CLI: `.codex/skills/amba/`
|
|
21
|
+
* - Windsurf: `.windsurf/skills/amba/`
|
|
22
|
+
*
|
|
23
|
+
* We also write a project-root copy at `.agents/skills/amba/` which
|
|
24
|
+
* the `npx skills add ...` distribution tool reads from (and which any
|
|
25
|
+
* agent that pre-registers an `.agents/skills/` lookup picks up). Five
|
|
26
|
+
* locations, one body — same fan-out pattern `writeAllSetupTargets`
|
|
27
|
+
* already uses for the legacy setup guide.
|
|
28
|
+
*
|
|
29
|
+
* Idempotency: re-running `amba init` overwrites the bundled
|
|
30
|
+
* `SKILL.md` and `references/*.md` so every developer ends up on the
|
|
31
|
+
* latest playbook. We back up a pre-existing `SKILL.md` to a sibling
|
|
32
|
+
* `.bak-<unix-ms>` ONLY when its first frontmatter key (`name:`) is
|
|
33
|
+
* not `amba` — that's the signal it was hand-authored / unrelated and
|
|
34
|
+
* shouldn't be silently clobbered. Bundled Amba files are refreshed
|
|
35
|
+
* without backup.
|
|
36
|
+
*/
|
|
37
|
+
export type SkillInstallTargetKind = 'claude-code' | 'cursor' | 'codex' | 'windsurf' | 'generic-agents';
|
|
38
|
+
export interface SkillInstallTarget {
|
|
39
|
+
kind: SkillInstallTargetKind;
|
|
40
|
+
/** Absolute path to the `amba/` directory inside the agent's skill root. */
|
|
41
|
+
path: string;
|
|
42
|
+
}
|
|
43
|
+
export interface SkillFileWritten {
|
|
44
|
+
/** Absolute path of the file written. */
|
|
45
|
+
path: string;
|
|
46
|
+
/** Backup path when an existing non-Amba SKILL.md was preserved; otherwise null. */
|
|
47
|
+
backedUpTo: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface SkillInstallResult {
|
|
50
|
+
/** Target directory the skill was installed under. */
|
|
51
|
+
target: SkillInstallTarget;
|
|
52
|
+
/** Files written this run. */
|
|
53
|
+
files: SkillFileWritten[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the bundled skill source directory.
|
|
57
|
+
*
|
|
58
|
+
* The bundle lives at `<package-root>/skill-bundle/` in both the
|
|
59
|
+
* source tree and the published tarball (via `files[]` in
|
|
60
|
+
* `package.json`). From a built `dist/commands/init.js` the path is
|
|
61
|
+
* `../../skill-bundle/`. From the source tree
|
|
62
|
+
* (`src/skill-installer.ts`) the path is `../skill-bundle/`. We try
|
|
63
|
+
* both relative to `import.meta.url` and pick the one that exists.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveBundleDir(): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* List the five install targets the CLI fans out to. Project-local
|
|
68
|
+
* directories (`<cwd>/.claude/skills/amba/`, etc.) — the agent reads
|
|
69
|
+
* project-local skills with priority over global ones, so this is the
|
|
70
|
+
* canonical install location for a tool meant to wire up THIS project.
|
|
71
|
+
*/
|
|
72
|
+
export declare function skillInstallTargets(cwd: string): SkillInstallTarget[];
|
|
73
|
+
/**
|
|
74
|
+
* Copy SKILL.md + every file under references/ into the target
|
|
75
|
+
* directory. Creates the directory tree if missing. Returns the list
|
|
76
|
+
* of files touched and any backup paths.
|
|
77
|
+
*
|
|
78
|
+
* Backup rule: a pre-existing `SKILL.md` is backed up to
|
|
79
|
+
* `SKILL.md.bak-<unix-ms>` ONLY when its first `name:` frontmatter
|
|
80
|
+
* line is NOT `name: amba`. That's the signal it was authored by the
|
|
81
|
+
* user for an unrelated purpose and shouldn't be silently overwritten.
|
|
82
|
+
* Amba-owned files get refreshed without backup so developers
|
|
83
|
+
* tracking the latest playbook don't accumulate junk.
|
|
84
|
+
*/
|
|
85
|
+
export declare function installSkillBundle(cwd: string, options?: {
|
|
86
|
+
bundleDir?: string;
|
|
87
|
+
}): Promise<SkillInstallResult[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Convenience: returns the count of skill files written and the list
|
|
90
|
+
* of target kinds, for the CLI's done-message summary.
|
|
91
|
+
*/
|
|
92
|
+
export declare function summarizeSkillInstall(results: SkillInstallResult[]): {
|
|
93
|
+
totalFiles: number;
|
|
94
|
+
targetKinds: SkillInstallTargetKind[];
|
|
95
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App-kind presets for the "Hey Claude, get amba.dev in my app" onboarding.
|
|
3
|
+
*
|
|
4
|
+
* The classifier picks one of 10 kinds (9 opinionated + `custom`); the
|
|
5
|
+
* wire-up engine reads the matching preset to know:
|
|
6
|
+
*
|
|
7
|
+
* - which Amba surface areas to enable (used to scope the docs the
|
|
8
|
+
* coding agent pulls into context),
|
|
9
|
+
* - which follow-up questions to ask the developer when the preset has
|
|
10
|
+
* genuine ambiguity (e.g. friends-vs-global leaderboard scope),
|
|
11
|
+
* - the exact MCP tool calls to issue against `mcp.amba.dev` to create
|
|
12
|
+
* the starter resources (currencies, achievements, streaks, …),
|
|
13
|
+
* - the literal SDK init snippet to paste into the developer's project
|
|
14
|
+
* for whichever of the six supported stacks they're on.
|
|
15
|
+
*
|
|
16
|
+
* The MCP tool names and argument shapes here MUST match the registered
|
|
17
|
+
* tools in `packages/mcp/src/tools/*`. The validation surface for that
|
|
18
|
+
* is the per-tool zod schema; this file is the single source of truth
|
|
19
|
+
* for "what does Amba install for a fitness app" and lives in the CLI
|
|
20
|
+
* because the CLI's classifier (Task #3) reads it directly. The MCP
|
|
21
|
+
* server doesn't import this — its job is to expose the underlying
|
|
22
|
+
* tools, not to know about preset choices.
|
|
23
|
+
*
|
|
24
|
+
* Naming discipline (see CLAUDE.md): no `cf_*` / `neon_*` / `temporal_*`
|
|
25
|
+
* field names; achievement icons reference customer-replaceable URLs on
|
|
26
|
+
* the Amba CDN; SDK snippets never name an underlying vendor.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* The closed set of app kinds the classifier returns. `custom` is the
|
|
30
|
+
* escape hatch — when the classifier can't confidently pick, the CLI
|
|
31
|
+
* presents the full surface-area picker instead of applying a preset.
|
|
32
|
+
*/
|
|
33
|
+
export type AppKind = 'fitness' | 'social' | 'marketplace' | 'productivity' | 'education' | 'game' | 'dating' | 'content_creator' | 'ai_chatbot' | 'custom';
|
|
34
|
+
/**
|
|
35
|
+
* The surface areas (Amba primitives) a preset can opt into.
|
|
36
|
+
*
|
|
37
|
+
* `identity` is implicit in every preset — anonymous user provisioning is
|
|
38
|
+
* a precondition of every other surface — but listing it explicitly in
|
|
39
|
+
* `surfaces` makes the wire-up output self-describing.
|
|
40
|
+
*/
|
|
41
|
+
export type AmbaSurface = 'identity' | 'xp' | 'streaks' | 'achievements' | 'leaderboards' | 'currencies' | 'catalog' | 'stores' | 'inventory' | 'friendships' | 'groups' | 'feeds' | 'messaging' | 'moderation' | 'reviews' | 'push' | 'content' | 'referrals' | 'deeplinks' | 'segments' | 'onboarding' | 'ai_prompts';
|
|
42
|
+
/** The 6 SDK stacks for which we emit a copy-pasteable init snippet. */
|
|
43
|
+
export type SdkStack = 'expo' | 'reactNative' | 'web' | 'swift' | 'kotlin' | 'flutter';
|
|
44
|
+
export interface FollowUpOption {
|
|
45
|
+
value: string;
|
|
46
|
+
label: string;
|
|
47
|
+
/**
|
|
48
|
+
* Marks the option the CLI selects by default if the developer accepts
|
|
49
|
+
* the recommended answer without interaction. Exactly zero or one
|
|
50
|
+
* option per question carries this flag.
|
|
51
|
+
*/
|
|
52
|
+
recommended?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface FollowUpQuestion {
|
|
55
|
+
/** The surface the answer mutates. Used by the wire-up engine to route. */
|
|
56
|
+
surface: AmbaSurface;
|
|
57
|
+
/**
|
|
58
|
+
* Stable identifier used to look up the answer in the wire-up engine.
|
|
59
|
+
* Tied to `WireUpAction.dependsOn` references.
|
|
60
|
+
*/
|
|
61
|
+
id: string;
|
|
62
|
+
question: string;
|
|
63
|
+
options: FollowUpOption[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A single MCP tool call the wire-up engine should issue.
|
|
67
|
+
*
|
|
68
|
+
* `args` mirrors the tool's zod input schema MINUS `project_id`, which
|
|
69
|
+
* the engine injects from the active project context — the developer
|
|
70
|
+
* never passes a project id at this layer.
|
|
71
|
+
*
|
|
72
|
+
* `dependsOn` is the id of a follow-up question whose answer mutates
|
|
73
|
+
* the action (e.g. leaderboard scope drives `metric` / `name`). The
|
|
74
|
+
* engine reads the answer, applies `whenAnswer` to transform `args`,
|
|
75
|
+
* and skips the action entirely if `whenAnswer` returns `null`.
|
|
76
|
+
*/
|
|
77
|
+
export interface WireUpAction {
|
|
78
|
+
tool: string;
|
|
79
|
+
args: Record<string, unknown>;
|
|
80
|
+
/**
|
|
81
|
+
* Optional follow-up dependency. The engine resolves the developer's
|
|
82
|
+
* answer to `dependsOn.id`, then calls `dependsOn.whenAnswer(value)`
|
|
83
|
+
* to produce the final args. Returning `null` means "skip this
|
|
84
|
+
* action for that answer."
|
|
85
|
+
*/
|
|
86
|
+
dependsOn?: {
|
|
87
|
+
id: string;
|
|
88
|
+
whenAnswer: (answer: string) => Record<string, unknown> | null;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export interface SdkInitSnippets {
|
|
92
|
+
/** Expo (managed) — uses `@layers/amba-expo`. */
|
|
93
|
+
expo: string;
|
|
94
|
+
/** Bare React Native — uses `@layers/amba-react-native`. */
|
|
95
|
+
reactNative: string;
|
|
96
|
+
/** Browser / SSR — uses `@layers/amba-web`. */
|
|
97
|
+
web: string;
|
|
98
|
+
/** iOS / macOS / tvOS / watchOS — Swift Package Manager. */
|
|
99
|
+
swift: string;
|
|
100
|
+
/** Android — Maven Central artifact `com.layers.amba:amba-sdk-android`. */
|
|
101
|
+
kotlin: string;
|
|
102
|
+
/** Flutter / Dart — `amba` on pub.dev. */
|
|
103
|
+
flutter: string;
|
|
104
|
+
}
|
|
105
|
+
export interface AppKindPreset {
|
|
106
|
+
kind: AppKind;
|
|
107
|
+
label: string;
|
|
108
|
+
description: string;
|
|
109
|
+
/**
|
|
110
|
+
* Surfaces to enable. `identity` always appears first — every preset
|
|
111
|
+
* requires user identity. The order in the array is the order the
|
|
112
|
+
* wire-up engine renders to the developer (logical, not arbitrary).
|
|
113
|
+
*/
|
|
114
|
+
surfaces: AmbaSurface[];
|
|
115
|
+
/**
|
|
116
|
+
* Genuinely ambiguous choices the developer needs to make. Decisions
|
|
117
|
+
* with a clear default (e.g. "should we install identity?" — yes,
|
|
118
|
+
* always) are NOT follow-ups; they're baked into `wireUp.actions`.
|
|
119
|
+
*/
|
|
120
|
+
followUpQuestions: FollowUpQuestion[];
|
|
121
|
+
wireUp: {
|
|
122
|
+
actions: WireUpAction[];
|
|
123
|
+
sdkInit: SdkInitSnippets;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Canonical preset registry. Lookup is by `AppKind`; iteration order is
|
|
128
|
+
* the order the CLI presents to the developer when classification is
|
|
129
|
+
* ambiguous and we fall back to a manual menu (alphabetical-ish by
|
|
130
|
+
* common usage, with `custom` last).
|
|
131
|
+
*/
|
|
132
|
+
export declare const PRESETS: Record<AppKind, AppKindPreset>;
|
|
133
|
+
/**
|
|
134
|
+
* Ordered list of the 9 non-custom presets. Useful when the CLI
|
|
135
|
+
* renders the "pick an app type" menu — `custom` is appended by the
|
|
136
|
+
* caller as the final escape-hatch option.
|
|
137
|
+
*/
|
|
138
|
+
export declare const PRESET_ORDER: readonly AppKind[];
|
|
139
|
+
/**
|
|
140
|
+
* Type-narrowing helper for the classifier output. Returns the preset
|
|
141
|
+
* verbatim when the kind is known; the caller is expected to surface a
|
|
142
|
+
* helpful error if classification produced a value outside the closed
|
|
143
|
+
* set (the type system blocks that in TypeScript, but the boundary
|
|
144
|
+
* between classifier-JSON and this module is a runtime trust check).
|
|
145
|
+
*/
|
|
146
|
+
export declare function getPreset(kind: AppKind): AppKindPreset;
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,31 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
2
|
+
* Per-agent skill / rule file installer for `amba init`.
|
|
3
|
+
*
|
|
4
|
+
* Two distinct surfaces, both project-local:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Build task skill** — `.claude/skills/amba-build/SKILL.md`.
|
|
7
|
+
* Scaffolds a full Expo app via the canonical "/goal" prompt.
|
|
8
|
+
* Task-shaped: the user invokes it explicitly. Lives behind
|
|
9
|
+
* `writeAmbaBuildSkill` (legacy export, unchanged).
|
|
10
|
+
*
|
|
11
|
+
* 2. **Reference / setup skill** — fanned out into five locations,
|
|
12
|
+
* one per agent family, so the same Amba setup guide reaches
|
|
13
|
+
* whatever coding agent the user has installed:
|
|
14
|
+
*
|
|
15
|
+
* | Surface | Path | Wrapper |
|
|
16
|
+
* |------------------------------------------|-------------------------------|--------------------------|
|
|
17
|
+
* | Claude Code (proactive, auto-injected) | \`CLAUDE.md\` (append) | plain markdown |
|
|
18
|
+
* | Claude Code (invokable skill) | \`.claude/skills/amba/SKILL.md\` | \`description:\` frontmatter |
|
|
19
|
+
* | Cursor | \`.cursor/rules/amba.mdc\` | \`alwaysApply\`/\`description\`/\`globs\` |
|
|
20
|
+
* | Codex / Aider / Zed / Copilot / Gemini | \`AGENTS.md\` (append) | plain markdown |
|
|
21
|
+
* | Windsurf | \`.windsurf/rules/amba.md\` | \`trigger: always_on\` |
|
|
22
|
+
*
|
|
23
|
+
* The two append targets (\`CLAUDE.md\`, \`AGENTS.md\`) use marker
|
|
24
|
+
* fencing — \`<!-- AMBA-SETUP-START -->\` / \`<!-- AMBA-SETUP-END -->\` —
|
|
25
|
+
* so a re-init refreshes only Amba's section without clobbering user
|
|
26
|
+
* edits to the surrounding file. The standalone targets (\`.cursor\`,
|
|
27
|
+
* \`.windsurf\`, \`.claude/skills/amba\`) live in their own files and
|
|
28
|
+
* are overwritten wholesale per re-init.
|
|
29
|
+
*
|
|
30
|
+
* The shared body comes from \`@layers/amba-mcp/prompts\`
|
|
31
|
+
* (\`AMBA_SETUP_GUIDE_MD\`) — one canonical source, five wrappers. The
|
|
32
|
+
* CLI bundles that constant at publish time via tsdown's
|
|
33
|
+
* \`noExternal: [/^@layers\\/amba-/]\` rule (same path \`EXPO_BUILD_PROMPT_MD\`
|
|
34
|
+
* already uses).
|
|
35
|
+
*
|
|
36
|
+
* Vendor-name discipline
|
|
37
|
+
* ----------------------
|
|
38
|
+
* Everything written by this module is customer-facing. The body
|
|
39
|
+
* (sourced from the MCP package) is vetted there; the wrappers below
|
|
40
|
+
* intentionally avoid naming Cloudflare / GCP / Neon / Temporal /
|
|
41
|
+
* Rust / WASM / UniFFI / Resend / Doppler. See \`skills.test.ts\` for
|
|
42
|
+
* the per-writer drift gate.
|
|
29
43
|
*/
|
|
30
44
|
export interface WriteAmbaBuildSkillOptions {
|
|
31
45
|
/**
|
|
@@ -45,31 +59,208 @@ export interface WriteAmbaBuildSkillResult {
|
|
|
45
59
|
* Exported as a pure function so the unit tests can assert structural
|
|
46
60
|
* properties (frontmatter, fetcher block, inlined snapshot fence)
|
|
47
61
|
* without round-tripping through the filesystem.
|
|
48
|
-
*
|
|
49
|
-
* Structure:
|
|
50
|
-
* 1. YAML frontmatter — `description` so Claude Code's skill
|
|
51
|
-
* indexer picks it up.
|
|
52
|
-
* 2. Skill body — invocation instructions, fetcher one-liner,
|
|
53
|
-
* fallback rule.
|
|
54
|
-
* 3. Inlined snapshot — fenced code block containing the
|
|
55
|
-
* EXPO_BUILD_PROMPT_MD body verbatim. The snapshot is bounded
|
|
56
|
-
* by a marker comment so a future `amba update-skills` command
|
|
57
|
-
* can find and refresh just the inlined region without
|
|
58
|
-
* clobbering user customizations above it.
|
|
59
62
|
*/
|
|
60
63
|
export declare function buildAmbaBuildSkillContent(): string;
|
|
61
64
|
/**
|
|
62
65
|
* Write `.claude/skills/amba-build/SKILL.md` into the target project.
|
|
66
|
+
* Always overwrites — the inlined snapshot is meant to be regenerated
|
|
67
|
+
* on each `amba init --sandbox` run.
|
|
68
|
+
*/
|
|
69
|
+
export declare function writeAmbaBuildSkill(options?: WriteAmbaBuildSkillOptions): Promise<WriteAmbaBuildSkillResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Common write-option shape for every per-agent writer. The only knob
|
|
72
|
+
* is `baseDir` — the project root we anchor relative paths against.
|
|
73
|
+
* Production callers pass `cwd`; tests pass a tmp dir.
|
|
74
|
+
*/
|
|
75
|
+
export interface WriteSetupTargetOptions {
|
|
76
|
+
baseDir?: string;
|
|
77
|
+
}
|
|
78
|
+
/** Result returned by every per-agent writer. */
|
|
79
|
+
export interface WriteSetupTargetResult {
|
|
80
|
+
/** Absolute path of the file we wrote (or refreshed). */
|
|
81
|
+
path: string;
|
|
82
|
+
/**
|
|
83
|
+
* `'created'` when the file did not exist before this call,
|
|
84
|
+
* `'refreshed'` when we updated an existing file (overwrote a
|
|
85
|
+
* standalone file or refreshed a marker-fenced region in an
|
|
86
|
+
* append-target).
|
|
87
|
+
*/
|
|
88
|
+
mode: 'created' | 'refreshed';
|
|
89
|
+
}
|
|
90
|
+
/** Identifier for each fan-out target. */
|
|
91
|
+
export type SetupTarget = 'claude-md' | 'claude-skill' | 'cursor-rule' | 'agents-md' | 'windsurf-rule';
|
|
92
|
+
/**
|
|
93
|
+
* Marker fence sentinels for the two append-targets (`CLAUDE.md`,
|
|
94
|
+
* `AGENTS.md`). Used by `markerFencedAppend` to find + refresh the
|
|
95
|
+
* Amba section without clobbering surrounding user content.
|
|
96
|
+
*/
|
|
97
|
+
export declare const AMBA_SETUP_START_MARKER = "<!-- AMBA-SETUP-START -->";
|
|
98
|
+
export declare const AMBA_SETUP_END_MARKER = "<!-- AMBA-SETUP-END -->";
|
|
99
|
+
/**
|
|
100
|
+
* Thrown by `markerFencedAppend` when the target file does not
|
|
101
|
+
* contain exactly one `<start>` and one `<end>` marker in canonical
|
|
102
|
+
* order (end after start). Any deviation — orphan single marker,
|
|
103
|
+
* duplicate paired blocks, end-before-start, mixed counts — is
|
|
104
|
+
* treated as corruption and surfaced to the caller.
|
|
63
105
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
106
|
+
* The helper refuses to auto-recover because every other shape risks
|
|
107
|
+
* deleting user content outside the Amba block (e.g. a stray end
|
|
108
|
+
* marker in user-authored text would cause a strip pass to remove
|
|
109
|
+
* everything above it). The caller's `warn` sink shows the developer
|
|
110
|
+
* the path + marker counts so they can resolve manually and re-run
|
|
111
|
+
* `amba init`.
|
|
69
112
|
*
|
|
70
|
-
*
|
|
71
|
-
* the
|
|
72
|
-
*
|
|
73
|
-
* surrounding text untouched. Out of scope for DX-16.
|
|
113
|
+
* The counts + indices the helper observed at the moment of failure
|
|
114
|
+
* are exposed on the error so tests + tooling can introspect without
|
|
115
|
+
* regex-scraping the message.
|
|
74
116
|
*/
|
|
75
|
-
export
|
|
117
|
+
export interface AmbaSkillFileMarkerShape {
|
|
118
|
+
startCount: number;
|
|
119
|
+
endCount: number;
|
|
120
|
+
/** Index of the first start marker, or -1 if absent. */
|
|
121
|
+
startIdx: number;
|
|
122
|
+
/** Index of the first end marker, or -1 if absent. */
|
|
123
|
+
endIdx: number;
|
|
124
|
+
}
|
|
125
|
+
export declare class AmbaSkillFileCorrupted extends Error {
|
|
126
|
+
readonly path: string;
|
|
127
|
+
readonly shape: AmbaSkillFileMarkerShape;
|
|
128
|
+
constructor(filePath: string, shape: AmbaSkillFileMarkerShape);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Insert or refresh a marker-fenced block in a file.
|
|
132
|
+
*
|
|
133
|
+
* Marker-shape invariant: the target file must have either
|
|
134
|
+
* (a) zero start/end markers (clean append), or
|
|
135
|
+
* (b) exactly one start marker and one end marker, with the end
|
|
136
|
+
* marker after the start (clean in-place refresh).
|
|
137
|
+
*
|
|
138
|
+
* Any other shape — orphan single marker, duplicate paired blocks,
|
|
139
|
+
* end-before-start, mixed counts (e.g. 1 start + 2 ends) — is
|
|
140
|
+
* treated as corruption and throws `AmbaSkillFileCorrupted`. The
|
|
141
|
+
* caller's `warn` sink surfaces the error to the developer; the
|
|
142
|
+
* file is left untouched. Earlier revisions tried to auto-recover
|
|
143
|
+
* malformed states via strip-and-replace, but every recovery
|
|
144
|
+
* heuristic risked deleting user content outside the Amba block
|
|
145
|
+
* (BugBot cycle-5..7 all flagged adjacent failure modes — the
|
|
146
|
+
* strict invariant kills the whole class).
|
|
147
|
+
*
|
|
148
|
+
* Behavior:
|
|
149
|
+
*
|
|
150
|
+
* - File does not exist (`ENOENT`) → create with just the block.
|
|
151
|
+
* **Any other read error (EACCES, EISDIR, transient I/O) is
|
|
152
|
+
* re-thrown** — we never silently overwrite a file we couldn't
|
|
153
|
+
* read.
|
|
154
|
+
* - File exists, zero markers → append the block (with a blank-
|
|
155
|
+
* line separator so it doesn't fuse onto the last paragraph).
|
|
156
|
+
* - File exists, well-formed 1+1 pair (end after start) → replace
|
|
157
|
+
* the content between markers, preserving surrounding text.
|
|
158
|
+
* - Any other marker shape → throw `AmbaSkillFileCorrupted` with
|
|
159
|
+
* the observed (startCount, endCount, startIdx, endIdx).
|
|
160
|
+
*
|
|
161
|
+
* Returns the absolute path + whether this was a fresh create or a
|
|
162
|
+
* refresh.
|
|
163
|
+
*
|
|
164
|
+
* The `body` argument is the text we want **inside** the markers —
|
|
165
|
+
* the markers themselves are added by this helper. Callers must NOT
|
|
166
|
+
* include the start/end marker lines in `body`.
|
|
167
|
+
*/
|
|
168
|
+
export declare function markerFencedAppend(filePath: string, body: string, startMarker: string, endMarker: string): Promise<WriteSetupTargetResult>;
|
|
169
|
+
/**
|
|
170
|
+
* Build the canonical setup body. Sourced from the MCP package so
|
|
171
|
+
* docs + MCP + every coding-agent surface stay in sync.
|
|
172
|
+
*
|
|
173
|
+
* Exposed as a function (not a const) so future versions can swap in
|
|
174
|
+
* a build-time generator without breaking import sites.
|
|
175
|
+
*/
|
|
176
|
+
export declare function buildAmbaSetupBody(): string;
|
|
177
|
+
/**
|
|
178
|
+
* Build the Claude-Code-skill flavor of the setup guide
|
|
179
|
+
* (`.claude/skills/amba/SKILL.md`).
|
|
180
|
+
*
|
|
181
|
+
* The `description:` frontmatter is what Claude Code's skill indexer
|
|
182
|
+
* reads to decide whether to invoke the skill. Spell out every Amba
|
|
183
|
+
* primitive family so a model reasoning over "this app needs push
|
|
184
|
+
* notifications" or "we want gamification" maps the request onto
|
|
185
|
+
* Amba primitives rather than re-rolling a backend.
|
|
186
|
+
*/
|
|
187
|
+
export declare function buildClaudeSkillContent(): string;
|
|
188
|
+
/**
|
|
189
|
+
* Build the Cursor `.cursor/rules/amba.mdc` flavor.
|
|
190
|
+
*
|
|
191
|
+
* `alwaysApply: true` makes Cursor inject the rule at the start of
|
|
192
|
+
* every turn (Cursor's most-proactive mode). `globs: ""` keeps the
|
|
193
|
+
* rule globally-scoped instead of file-pattern-attached.
|
|
194
|
+
*/
|
|
195
|
+
export declare function buildCursorRuleContent(): string;
|
|
196
|
+
/**
|
|
197
|
+
* Build the Windsurf `.windsurf/rules/amba.md` flavor.
|
|
198
|
+
*
|
|
199
|
+
* `trigger: always_on` is Windsurf's equivalent of Cursor's
|
|
200
|
+
* `alwaysApply: true`. Workspace rules **cap at 12k chars** — a hard
|
|
201
|
+
* Windsurf limit, not negotiable. The canonical
|
|
202
|
+
* `AMBA_SETUP_GUIDE_MD` body is the full classify → confirm →
|
|
203
|
+
* wire-up playbook (~17k) and won't fit, so Windsurf gets a
|
|
204
|
+
* trimmed-down summary that points at the long-form resource
|
|
205
|
+
* (`amba://setup`) for full detail. Same posture as
|
|
206
|
+
* `AMBA_INIT_INSTRUCTIONS` in the hosted MCP server.
|
|
207
|
+
*/
|
|
208
|
+
export declare function buildWindsurfRuleContent(): string;
|
|
209
|
+
/**
|
|
210
|
+
* Body for the marker-fenced append into `CLAUDE.md` or `AGENTS.md`.
|
|
211
|
+
*
|
|
212
|
+
* Plain markdown, no frontmatter — both conventions are
|
|
213
|
+
* frontmatter-free. Returns the inner body only; the marker fence is
|
|
214
|
+
* added by `markerFencedAppend`.
|
|
215
|
+
*/
|
|
216
|
+
export declare function buildAppendableSetupBody(): string;
|
|
217
|
+
/**
|
|
218
|
+
* Write `.claude/skills/amba/SKILL.md` — the **reference** Claude Code
|
|
219
|
+
* skill. (The build-task skill `.claude/skills/amba-build/SKILL.md`
|
|
220
|
+
* is separate; see `writeAmbaBuildSkill`.)
|
|
221
|
+
*/
|
|
222
|
+
export declare function writeAmbaSkill(options?: WriteSetupTargetOptions): Promise<WriteSetupTargetResult>;
|
|
223
|
+
/** Write `.cursor/rules/amba.mdc`. */
|
|
224
|
+
export declare function writeCursorRule(options?: WriteSetupTargetOptions): Promise<WriteSetupTargetResult>;
|
|
225
|
+
/** Write `.windsurf/rules/amba.md`. */
|
|
226
|
+
export declare function writeWindsurfRule(options?: WriteSetupTargetOptions): Promise<WriteSetupTargetResult>;
|
|
227
|
+
/**
|
|
228
|
+
* Append (or refresh) the Amba setup section in `CLAUDE.md` at the
|
|
229
|
+
* project root. Marker-fenced so it can be safely refreshed by
|
|
230
|
+
* subsequent re-inits.
|
|
231
|
+
*/
|
|
232
|
+
export declare function writeClaudeMd(options?: WriteSetupTargetOptions): Promise<WriteSetupTargetResult>;
|
|
233
|
+
/**
|
|
234
|
+
* Append (or refresh) the Amba setup section in `AGENTS.md` at the
|
|
235
|
+
* project root. The `AGENTS.md` convention is read by 20+ agentic
|
|
236
|
+
* tools (Codex, Aider, Zed, Copilot, Gemini CLI, Warp, etc.) so this
|
|
237
|
+
* single file covers most of the long tail.
|
|
238
|
+
*/
|
|
239
|
+
export declare function writeAgentsMd(options?: WriteSetupTargetOptions): Promise<WriteSetupTargetResult>;
|
|
240
|
+
/**
|
|
241
|
+
* Convenience: emit ALL five setup-guide targets in one call. Used by
|
|
242
|
+
* `amba init` so a single sandbox provision drops Amba context into
|
|
243
|
+
* whatever agent the developer happens to have configured.
|
|
244
|
+
*
|
|
245
|
+
* Each writer is best-effort independent — a failure in one (e.g. the
|
|
246
|
+
* filesystem rejects a write under one of the dot-prefixed dirs)
|
|
247
|
+
* doesn't prevent the others from succeeding. Errors are captured
|
|
248
|
+
* per-target and surfaced via the optional `warn` callback so the
|
|
249
|
+
* caller can decide whether to fail the parent flow.
|
|
250
|
+
*/
|
|
251
|
+
export interface WriteAllSetupTargetsOptions {
|
|
252
|
+
baseDir?: string;
|
|
253
|
+
/** Per-target failure sink. Defaults to a no-op. */
|
|
254
|
+
warn?: (message: string) => void;
|
|
255
|
+
}
|
|
256
|
+
export interface WriteAllSetupTargetsResult {
|
|
257
|
+
/** Per-target outcome, indexed by `SetupTarget`. */
|
|
258
|
+
written: Array<{
|
|
259
|
+
target: SetupTarget;
|
|
260
|
+
path: string;
|
|
261
|
+
mode: 'created' | 'refreshed';
|
|
262
|
+
}>;
|
|
263
|
+
/** Body version stamped into each file (echoed via the marker). */
|
|
264
|
+
bodyVersion: string;
|
|
265
|
+
}
|
|
266
|
+
export declare function writeAllSetupTargets(options?: WriteAllSetupTargetsOptions): Promise<WriteAllSetupTargetsResult>;
|