@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/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
- * (developer row, token expiry, etc.) but we don't need them here.
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 three Amba lines in place; otherwise we append a
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
- * Used by the CLI's done-message to print per-client restart bullets
216
- * — and only for clients whose config we actually wrote to.
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
- * Claude Code skill installer for `amba init --sandbox`.
3
- *
4
- * Writes a project-local `.claude/skills/amba-build/SKILL.md` file
5
- * containing the canonical "/goal" prompt for building a full Expo
6
- * app with Amba as the only backend. Two behaviors:
7
- *
8
- * 1. **Live fetch first.** The skill's runtime instructions tell
9
- * Claude Code to `curl` the live MDX from
10
- * `https://docs.amba.dev/docs/prompts/expo-build.md`. So as long
11
- * as docs is reachable, the agent always uses the latest version.
12
- *
13
- * 2. **Inlined snapshot fallback.** The same SKILL.md file embeds a
14
- * verbatim copy of the prompt body — captured at CLI install
15
- * time from `@layers/amba-mcp/prompts`. Offline agents (or ones
16
- * whose curl 404s during the docs deploy gap) still get a
17
- * usable prompt.
18
- *
19
- * Out of scope (per DX-16): Cursor / Windsurf shortcut files. Those
20
- * editors don't ingest Claude Code skills; their users paste the
21
- * URL directly. The CLI's success line still surfaces the
22
- * `/amba-build` command + the docs URL so both audiences are served.
23
- *
24
- * Project-local install is the default because skills written into
25
- * `~/.claude/skills/` are user-global and would persist across
26
- * unrelated projects (and accumulate stale copies of the inlined
27
- * snapshot from old `amba init` runs). A project-scoped install
28
- * disappears when the user `rm -rf`s the project.
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
- * Always overwrites — the skill file is meant to be regenerated each
65
- * time `amba init --sandbox` runs (so the inlined snapshot stays
66
- * fresh). Anything the user customized above the snapshot markers
67
- * would be lost on a re-init; that's an accepted trade-off for the
68
- * agentic single-command flow.
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
- * If a future need for "preserve user edits across re-init" surfaces,
71
- * the right shape is a separate `amba update-skills` command that
72
- * surgically rewrites the inlined-snapshot region only — leaving the
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 declare function writeAmbaBuildSkill(options?: WriteAmbaBuildSkillOptions): Promise<WriteAmbaBuildSkillResult>;
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>;