crumbtrail 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,279 @@
1
+ import { Stack } from 'crumbtrail-design-system';
2
+
3
+ /** Injection recipes, ordered most-specific-first during detection. */
4
+ type Recipe = "tauri" | "next" | "sveltekit" | "nuxt" | "remix" | "astro" | "angular" | "vite-spa" | "nestjs" | "express" | "hono" | "fastify" | "react-native" | "node" | "otlp";
5
+ type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
6
+ interface WorkspacePackage {
7
+ /** package.json `name`, falling back to the directory basename. */
8
+ name: string;
9
+ /** Absolute path to the workspace package directory. */
10
+ dir: string;
11
+ }
12
+ interface DetectResult {
13
+ cwd: string;
14
+ packageJsonPath: string | null;
15
+ /** Winning recipe, or null when nothing matched. */
16
+ recipe: Recipe | null;
17
+ packageManager: PackageManager | null;
18
+ /**
19
+ * Absolute path to the file the recipe would edit (vite-spa / node), when it
20
+ * could be resolved with confidence. null for create-a-new-file recipes and
21
+ * whenever resolution was ambiguous.
22
+ */
23
+ entryFile: string | null;
24
+ /** Raw `next` version range from package.json, when the recipe is `next`. */
25
+ nextVersion: string | null;
26
+ /**
27
+ * The non-JS backend Stack resolved from filesystem markers when `recipe` is
28
+ * `"otlp"` (django/flask/fastapi/go/rails/dotnet). null for every other recipe.
29
+ * This is the ONE recipe that carries a variable Stack; call sites must prefer
30
+ * it over the static `RECIPE_REGISTRY["otlp"].stack` placeholder.
31
+ */
32
+ otlpStack: Stack | null;
33
+ isMonorepo: boolean;
34
+ /** Workspace packages when `isMonorepo` — the caller picks which app to wire. */
35
+ workspaces: WorkspacePackage[];
36
+ /**
37
+ * True when the caller must resolve a choice before injecting: monorepo root,
38
+ * no recipe matched, or the recipe's entry file could not be resolved.
39
+ */
40
+ ambiguous: boolean;
41
+ /** Human-readable trail of why detection landed where it did. */
42
+ reasons: string[];
43
+ /**
44
+ * Non-blocking, informational messages about the project that do NOT affect
45
+ * `recipe`/`ambiguous`/`isMonorepo`/`entryFile` — e.g. a "Docker files found,
46
+ * infra evidence sources coming soon" note. Kept separate from `reasons` (the
47
+ * detection trail); the caller surfaces these on both success and no-recipe
48
+ * paths without changing any outcome.
49
+ */
50
+ notes: string[];
51
+ }
52
+ interface PackageJson {
53
+ name?: string;
54
+ main?: string;
55
+ bin?: string | Record<string, string>;
56
+ scripts?: Record<string, string>;
57
+ dependencies?: Record<string, string>;
58
+ devDependencies?: Record<string, string>;
59
+ workspaces?: string[] | {
60
+ packages?: string[];
61
+ };
62
+ }
63
+ /** Walk up from `startDir` to the filesystem root looking for a known lockfile. */
64
+ declare function detectPackageManager(startDir: string): PackageManager | null;
65
+ /** Extract the `packages:` list from a pnpm-workspace.yaml without a YAML dep. */
66
+ declare function parsePnpmWorkspace(text: string): string[];
67
+ /** Resolve the Vite entry from index.html's `<script type="module" src>` tag. */
68
+ declare function resolveViteEntry(cwd: string): string | null;
69
+ /**
70
+ * Resolve the React Native / Expo injection entry. Prefers, in order:
71
+ * 1. `app/_layout.{tsx,jsx,js}` — the expo-router root layout,
72
+ * 2. `App.{tsx,jsx,ts,js}` — the classic Expo / bare-RN root component,
73
+ * 3. `index.{js,ts}` — the bare-RN `AppRegistry` entry.
74
+ * First existing file wins. Returns null when none exist (→ ambiguous), so the
75
+ * resolver never points at an `expo/AppEntry` path inside node_modules.
76
+ */
77
+ declare function resolveReactNativeEntry(cwd: string): string | null;
78
+ /** Pull a node script/module path out of a `start`-style command. */
79
+ declare function parseNodeInvocation(script: string): string | null;
80
+ /**
81
+ * Resolve the NestJS injection entry. Nest's bootstrap lives at `src/main.ts`
82
+ * (or `src/main.js`); `resolveNodeEntry` cannot find it because the `start`
83
+ * script is `nest start` (not a bare node invocation) and `main` points at
84
+ * `dist/`. First existing file wins; null when neither exists (→ ambiguous).
85
+ */
86
+ declare function resolveNestEntry(cwd: string): string | null;
87
+ /**
88
+ * Resolve the Remix / React Router v7 hydration entry. Prefers
89
+ * `app/entry.client.{tsx,jsx,js}` in that order. Returns null when none exist —
90
+ * the recipe then FALLS BACK rather than creating one, because a bare init-only
91
+ * entry.client would omit hydrateRoot/<RemixBrowser> and break the app.
92
+ */
93
+ declare function resolveRemixEntry(cwd: string): string | null;
94
+ /**
95
+ * Resolve the Angular bootstrap entry — deterministically `src/main.ts`. Null
96
+ * when absent (→ ambiguous). Prepending an import + `Crumbtrail.init` above
97
+ * Angular's `bootstrapApplication`/`platformBrowserDynamic` call is safe.
98
+ */
99
+ declare function resolveAngularEntry(cwd: string): string | null;
100
+ /**
101
+ * Resolve a non-JS backend from filesystem markers — the `otlp` recipe's Stack.
102
+ * Filesystem-only: no code is executed; Python/Ruby manifests are parsed purely
103
+ * as text. Precedence is deliberate and documented:
104
+ * 1. `manage.py` → django
105
+ * 2. pyproject.toml/requirements.txt dep → fastapi (wins over flask if BOTH
106
+ * tokens appear) else flask
107
+ * 3. `go.mod` → go
108
+ * 4. `Gemfile` referencing rails → rails
109
+ * 5. any `*.csproj` in the root → dotnet
110
+ * Returns null when no marker matches. Works with NO package.json present.
111
+ */
112
+ declare function resolveOtlpStack(root: string): {
113
+ stack: Stack;
114
+ reason: string;
115
+ } | null;
116
+ /**
117
+ * Distinct `reasons` string pushed when a Deno project is detected (deno.json /
118
+ * deno.jsonc with no package.json). Exported so the wizard can recognize the
119
+ * signal and print a Deno-specific line instead of the generic unsupported hint.
120
+ */
121
+ declare const DENO_UNSUPPORTED_REASON = "Deno projects aren't supported yet";
122
+ /**
123
+ * Informational, non-blocking note emitted when Docker/Compose files are present.
124
+ * References that infra evidence sources (e.g. the `docker` stack in
125
+ * install-shared's INFRA_STACKS) are coming soon. NEVER affects recipe/ambiguity.
126
+ */
127
+ declare const DOCKER_COMING_SOON_NOTE = "Docker/Compose files found \u2014 infra evidence sources (e.g. docker) are coming soon.";
128
+ /** Inputs shared by every recipe matcher — filesystem-derived facts only. */
129
+ interface MatchContext {
130
+ root: string;
131
+ deps: Record<string, string>;
132
+ pkg: PackageJson | null;
133
+ /** Matchers push their user-facing detection reasons here (side effect). */
134
+ reasons: string[];
135
+ }
136
+ /**
137
+ * Evaluate the ordered matcher ladder, returning the first match. When nothing
138
+ * matches, recipe is null with no entry/version. Reasons are recorded as a side
139
+ * effect on `ctx.reasons`, exactly as the original if/else chain did.
140
+ */
141
+ declare function matchRecipe(ctx: MatchContext): {
142
+ recipe: Recipe | null;
143
+ entryFile: string | null;
144
+ nextVersion: string | null;
145
+ otlpStack: Stack | null;
146
+ };
147
+ /**
148
+ * Classify a single package directory. For a monorepo root this reports the
149
+ * workspace list and marks the result ambiguous — the caller picks a workspace
150
+ * and re-runs `detect` inside it.
151
+ */
152
+ declare function detect(cwd: string): DetectResult;
153
+
154
+ /** The shape of a plan the executor knows how to (or refuses to) apply. */
155
+ type PlanKind = "create" | "prepend" | "skip-already-wired" | "needs-confirm-dirty" | "fallback-ai" | "otlp-guidance";
156
+ /** A create/append to `.env` carried alongside the Node recipe's plan. */
157
+ interface EnvAction {
158
+ /** Absolute path to the `.env` file. */
159
+ targetPath: string;
160
+ /** The `CRUMBTRAIL_KEY=...` line to ensure is present. */
161
+ line: string;
162
+ /** Set when `.env` is not covered by `.gitignore` (key could get committed). */
163
+ gitignoreWarning?: string;
164
+ }
165
+ /**
166
+ * A fully-resolved, side-effect-free description of what injection would do.
167
+ * The executor turns this into filesystem writes; nothing here performs I/O.
168
+ */
169
+ interface Plan {
170
+ recipe: Recipe;
171
+ kind: PlanKind;
172
+ /** Absolute path of the file to create/edit. null for skip/fallback plans. */
173
+ targetPath: string | null;
174
+ /**
175
+ * For `create`: the full file body. For `prepend`/`needs-confirm-dirty`: the
176
+ * block to prepend. null for skip/fallback plans.
177
+ */
178
+ content: string | null;
179
+ /** Non-fatal notes to surface to the user. */
180
+ warnings: string[];
181
+ /** fallback-ai: the ready-to-paste code snippet, key already filled in. */
182
+ snippet?: string;
183
+ /** fallback-ai: the `buildAgentPrompt` output for a coding agent. */
184
+ agentPrompt?: string;
185
+ /** Node recipe: the `.env` write to perform alongside the entry edit. */
186
+ envAction?: EnvAction;
187
+ }
188
+
189
+ interface GitTargetStatus {
190
+ /** false when `cwd` is not inside a git work tree. */
191
+ isRepo: boolean;
192
+ /** false for an untracked (never-committed) file. */
193
+ tracked: boolean;
194
+ /** true when the target has uncommitted changes or is untracked. */
195
+ dirty: boolean;
196
+ }
197
+ /** Read-only inspection surface consumed by the injection plan-builders. */
198
+ interface InjectIO {
199
+ exists(p: string): boolean;
200
+ /** File contents, or null when it does not exist / cannot be read. */
201
+ readFile(p: string): string | null;
202
+ /** git porcelain status for a single target path. */
203
+ gitStatus(cwd: string, target: string): GitTargetStatus;
204
+ /** `.gitignore` contents at `cwd`, or null when absent. */
205
+ readGitignore(cwd: string): string | null;
206
+ }
207
+ /** The default real-filesystem + git implementation. */
208
+ declare const defaultInjectIO: InjectIO;
209
+
210
+ interface BuildPlanOptions {
211
+ /** Prepend into a dirty (uncommitted) target instead of asking to confirm. */
212
+ force?: boolean;
213
+ }
214
+ interface BuildPlanInput {
215
+ cwd: string;
216
+ recipe: Recipe;
217
+ endpoint: string;
218
+ apiKey: string;
219
+ /** Absolute entry path for vite-spa / node (from detection). */
220
+ entryFile?: string | null;
221
+ /** Raw `next` version range from detection (drives new-file vs legacy-prepend). */
222
+ nextVersion?: string | null;
223
+ /**
224
+ * The detected non-JS backend Stack for the `otlp` recipe (from
225
+ * `DetectResult.otlpStack`). Drives the guidance agent prompt; ignored for
226
+ * every other recipe.
227
+ */
228
+ stack?: Stack;
229
+ options?: BuildPlanOptions;
230
+ }
231
+ /**
232
+ * True when this package already depends on a Crumbtrail SDK. Load-bearing for
233
+ * the batch installer, which must decide whether to provision a service BEFORE
234
+ * it builds a plan — `buildPlan` uses this to self-cancel into
235
+ * `skip-already-wired`, so a re-run must not mint a second key for a service
236
+ * that is going to be skipped anyway.
237
+ */
238
+ declare function projectAlreadyWired(cwd: string, io: InjectIO): boolean;
239
+ /**
240
+ * `instrumentation-client.ts` is auto-loaded from Next 15.3+. Parse the leading
241
+ * numeric range; treat non-numeric ranges ("latest", "canary", workspace:) as
242
+ * new-enough.
243
+ */
244
+ declare function supportsInstrumentationClient(version: string | null | undefined): boolean;
245
+ /**
246
+ * Build the injection Plan for a detected recipe. Reads only (via `io`); the
247
+ * returned Plan is plain data the executor applies all-or-nothing.
248
+ */
249
+ declare function buildPlan(input: BuildPlanInput, io?: InjectIO): Plan;
250
+
251
+ /** Write boundary for the executor — swappable in tests. */
252
+ interface ExecutorIO {
253
+ exists(p: string): boolean;
254
+ readFile(p: string): string | null;
255
+ writeFile(p: string, content: string): void;
256
+ mkdirp(dir: string): void;
257
+ remove(p: string): void;
258
+ }
259
+ declare const defaultExecutorIO: ExecutorIO;
260
+ interface ExecuteOptions {
261
+ /** Apply a `needs-confirm-dirty` plan (the user confirmed / passed --force). */
262
+ confirmDirty?: boolean;
263
+ }
264
+ interface ExecuteResult {
265
+ kind: Plan["kind"];
266
+ /** Absolute paths actually written. */
267
+ written: string[];
268
+ /** True when nothing was written (skip / fallback / unconfirmed-dirty). */
269
+ skipped: boolean;
270
+ message: string;
271
+ }
272
+ /**
273
+ * Execute a Plan. skip/fallback plans (and unconfirmed dirty plans) perform no
274
+ * writes. create/prepend plans — plus any Node `.env` action — are applied
275
+ * all-or-nothing.
276
+ */
277
+ declare function executePlan(plan: Plan, io?: ExecutorIO, options?: ExecuteOptions): ExecuteResult;
278
+
279
+ export { type BuildPlanInput as B, type DetectResult as D, type EnvAction as E, type GitTargetStatus as G, type InjectIO as I, type PackageManager as P, type Recipe as R, type WorkspacePackage as W, type BuildPlanOptions as a, buildPlan as b, DENO_UNSUPPORTED_REASON as c, DOCKER_COMING_SOON_NOTE as d, executePlan as e, type ExecuteOptions as f, type ExecuteResult as g, type ExecutorIO as h, type Plan as i, type PlanKind as j, defaultExecutorIO as k, defaultInjectIO as l, detect as m, detectPackageManager as n, matchRecipe as o, parseNodeInvocation as p, parsePnpmWorkspace as q, projectAlreadyWired as r, resolveAngularEntry as s, resolveNestEntry as t, resolveOtlpStack as u, resolveReactNativeEntry as v, resolveRemixEntry as w, resolveViteEntry as x, supportsInstrumentationClient as y };
@@ -0,0 +1,279 @@
1
+ import { Stack } from 'crumbtrail-design-system';
2
+
3
+ /** Injection recipes, ordered most-specific-first during detection. */
4
+ type Recipe = "tauri" | "next" | "sveltekit" | "nuxt" | "remix" | "astro" | "angular" | "vite-spa" | "nestjs" | "express" | "hono" | "fastify" | "react-native" | "node" | "otlp";
5
+ type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
6
+ interface WorkspacePackage {
7
+ /** package.json `name`, falling back to the directory basename. */
8
+ name: string;
9
+ /** Absolute path to the workspace package directory. */
10
+ dir: string;
11
+ }
12
+ interface DetectResult {
13
+ cwd: string;
14
+ packageJsonPath: string | null;
15
+ /** Winning recipe, or null when nothing matched. */
16
+ recipe: Recipe | null;
17
+ packageManager: PackageManager | null;
18
+ /**
19
+ * Absolute path to the file the recipe would edit (vite-spa / node), when it
20
+ * could be resolved with confidence. null for create-a-new-file recipes and
21
+ * whenever resolution was ambiguous.
22
+ */
23
+ entryFile: string | null;
24
+ /** Raw `next` version range from package.json, when the recipe is `next`. */
25
+ nextVersion: string | null;
26
+ /**
27
+ * The non-JS backend Stack resolved from filesystem markers when `recipe` is
28
+ * `"otlp"` (django/flask/fastapi/go/rails/dotnet). null for every other recipe.
29
+ * This is the ONE recipe that carries a variable Stack; call sites must prefer
30
+ * it over the static `RECIPE_REGISTRY["otlp"].stack` placeholder.
31
+ */
32
+ otlpStack: Stack | null;
33
+ isMonorepo: boolean;
34
+ /** Workspace packages when `isMonorepo` — the caller picks which app to wire. */
35
+ workspaces: WorkspacePackage[];
36
+ /**
37
+ * True when the caller must resolve a choice before injecting: monorepo root,
38
+ * no recipe matched, or the recipe's entry file could not be resolved.
39
+ */
40
+ ambiguous: boolean;
41
+ /** Human-readable trail of why detection landed where it did. */
42
+ reasons: string[];
43
+ /**
44
+ * Non-blocking, informational messages about the project that do NOT affect
45
+ * `recipe`/`ambiguous`/`isMonorepo`/`entryFile` — e.g. a "Docker files found,
46
+ * infra evidence sources coming soon" note. Kept separate from `reasons` (the
47
+ * detection trail); the caller surfaces these on both success and no-recipe
48
+ * paths without changing any outcome.
49
+ */
50
+ notes: string[];
51
+ }
52
+ interface PackageJson {
53
+ name?: string;
54
+ main?: string;
55
+ bin?: string | Record<string, string>;
56
+ scripts?: Record<string, string>;
57
+ dependencies?: Record<string, string>;
58
+ devDependencies?: Record<string, string>;
59
+ workspaces?: string[] | {
60
+ packages?: string[];
61
+ };
62
+ }
63
+ /** Walk up from `startDir` to the filesystem root looking for a known lockfile. */
64
+ declare function detectPackageManager(startDir: string): PackageManager | null;
65
+ /** Extract the `packages:` list from a pnpm-workspace.yaml without a YAML dep. */
66
+ declare function parsePnpmWorkspace(text: string): string[];
67
+ /** Resolve the Vite entry from index.html's `<script type="module" src>` tag. */
68
+ declare function resolveViteEntry(cwd: string): string | null;
69
+ /**
70
+ * Resolve the React Native / Expo injection entry. Prefers, in order:
71
+ * 1. `app/_layout.{tsx,jsx,js}` — the expo-router root layout,
72
+ * 2. `App.{tsx,jsx,ts,js}` — the classic Expo / bare-RN root component,
73
+ * 3. `index.{js,ts}` — the bare-RN `AppRegistry` entry.
74
+ * First existing file wins. Returns null when none exist (→ ambiguous), so the
75
+ * resolver never points at an `expo/AppEntry` path inside node_modules.
76
+ */
77
+ declare function resolveReactNativeEntry(cwd: string): string | null;
78
+ /** Pull a node script/module path out of a `start`-style command. */
79
+ declare function parseNodeInvocation(script: string): string | null;
80
+ /**
81
+ * Resolve the NestJS injection entry. Nest's bootstrap lives at `src/main.ts`
82
+ * (or `src/main.js`); `resolveNodeEntry` cannot find it because the `start`
83
+ * script is `nest start` (not a bare node invocation) and `main` points at
84
+ * `dist/`. First existing file wins; null when neither exists (→ ambiguous).
85
+ */
86
+ declare function resolveNestEntry(cwd: string): string | null;
87
+ /**
88
+ * Resolve the Remix / React Router v7 hydration entry. Prefers
89
+ * `app/entry.client.{tsx,jsx,js}` in that order. Returns null when none exist —
90
+ * the recipe then FALLS BACK rather than creating one, because a bare init-only
91
+ * entry.client would omit hydrateRoot/<RemixBrowser> and break the app.
92
+ */
93
+ declare function resolveRemixEntry(cwd: string): string | null;
94
+ /**
95
+ * Resolve the Angular bootstrap entry — deterministically `src/main.ts`. Null
96
+ * when absent (→ ambiguous). Prepending an import + `Crumbtrail.init` above
97
+ * Angular's `bootstrapApplication`/`platformBrowserDynamic` call is safe.
98
+ */
99
+ declare function resolveAngularEntry(cwd: string): string | null;
100
+ /**
101
+ * Resolve a non-JS backend from filesystem markers — the `otlp` recipe's Stack.
102
+ * Filesystem-only: no code is executed; Python/Ruby manifests are parsed purely
103
+ * as text. Precedence is deliberate and documented:
104
+ * 1. `manage.py` → django
105
+ * 2. pyproject.toml/requirements.txt dep → fastapi (wins over flask if BOTH
106
+ * tokens appear) else flask
107
+ * 3. `go.mod` → go
108
+ * 4. `Gemfile` referencing rails → rails
109
+ * 5. any `*.csproj` in the root → dotnet
110
+ * Returns null when no marker matches. Works with NO package.json present.
111
+ */
112
+ declare function resolveOtlpStack(root: string): {
113
+ stack: Stack;
114
+ reason: string;
115
+ } | null;
116
+ /**
117
+ * Distinct `reasons` string pushed when a Deno project is detected (deno.json /
118
+ * deno.jsonc with no package.json). Exported so the wizard can recognize the
119
+ * signal and print a Deno-specific line instead of the generic unsupported hint.
120
+ */
121
+ declare const DENO_UNSUPPORTED_REASON = "Deno projects aren't supported yet";
122
+ /**
123
+ * Informational, non-blocking note emitted when Docker/Compose files are present.
124
+ * References that infra evidence sources (e.g. the `docker` stack in
125
+ * install-shared's INFRA_STACKS) are coming soon. NEVER affects recipe/ambiguity.
126
+ */
127
+ declare const DOCKER_COMING_SOON_NOTE = "Docker/Compose files found \u2014 infra evidence sources (e.g. docker) are coming soon.";
128
+ /** Inputs shared by every recipe matcher — filesystem-derived facts only. */
129
+ interface MatchContext {
130
+ root: string;
131
+ deps: Record<string, string>;
132
+ pkg: PackageJson | null;
133
+ /** Matchers push their user-facing detection reasons here (side effect). */
134
+ reasons: string[];
135
+ }
136
+ /**
137
+ * Evaluate the ordered matcher ladder, returning the first match. When nothing
138
+ * matches, recipe is null with no entry/version. Reasons are recorded as a side
139
+ * effect on `ctx.reasons`, exactly as the original if/else chain did.
140
+ */
141
+ declare function matchRecipe(ctx: MatchContext): {
142
+ recipe: Recipe | null;
143
+ entryFile: string | null;
144
+ nextVersion: string | null;
145
+ otlpStack: Stack | null;
146
+ };
147
+ /**
148
+ * Classify a single package directory. For a monorepo root this reports the
149
+ * workspace list and marks the result ambiguous — the caller picks a workspace
150
+ * and re-runs `detect` inside it.
151
+ */
152
+ declare function detect(cwd: string): DetectResult;
153
+
154
+ /** The shape of a plan the executor knows how to (or refuses to) apply. */
155
+ type PlanKind = "create" | "prepend" | "skip-already-wired" | "needs-confirm-dirty" | "fallback-ai" | "otlp-guidance";
156
+ /** A create/append to `.env` carried alongside the Node recipe's plan. */
157
+ interface EnvAction {
158
+ /** Absolute path to the `.env` file. */
159
+ targetPath: string;
160
+ /** The `CRUMBTRAIL_KEY=...` line to ensure is present. */
161
+ line: string;
162
+ /** Set when `.env` is not covered by `.gitignore` (key could get committed). */
163
+ gitignoreWarning?: string;
164
+ }
165
+ /**
166
+ * A fully-resolved, side-effect-free description of what injection would do.
167
+ * The executor turns this into filesystem writes; nothing here performs I/O.
168
+ */
169
+ interface Plan {
170
+ recipe: Recipe;
171
+ kind: PlanKind;
172
+ /** Absolute path of the file to create/edit. null for skip/fallback plans. */
173
+ targetPath: string | null;
174
+ /**
175
+ * For `create`: the full file body. For `prepend`/`needs-confirm-dirty`: the
176
+ * block to prepend. null for skip/fallback plans.
177
+ */
178
+ content: string | null;
179
+ /** Non-fatal notes to surface to the user. */
180
+ warnings: string[];
181
+ /** fallback-ai: the ready-to-paste code snippet, key already filled in. */
182
+ snippet?: string;
183
+ /** fallback-ai: the `buildAgentPrompt` output for a coding agent. */
184
+ agentPrompt?: string;
185
+ /** Node recipe: the `.env` write to perform alongside the entry edit. */
186
+ envAction?: EnvAction;
187
+ }
188
+
189
+ interface GitTargetStatus {
190
+ /** false when `cwd` is not inside a git work tree. */
191
+ isRepo: boolean;
192
+ /** false for an untracked (never-committed) file. */
193
+ tracked: boolean;
194
+ /** true when the target has uncommitted changes or is untracked. */
195
+ dirty: boolean;
196
+ }
197
+ /** Read-only inspection surface consumed by the injection plan-builders. */
198
+ interface InjectIO {
199
+ exists(p: string): boolean;
200
+ /** File contents, or null when it does not exist / cannot be read. */
201
+ readFile(p: string): string | null;
202
+ /** git porcelain status for a single target path. */
203
+ gitStatus(cwd: string, target: string): GitTargetStatus;
204
+ /** `.gitignore` contents at `cwd`, or null when absent. */
205
+ readGitignore(cwd: string): string | null;
206
+ }
207
+ /** The default real-filesystem + git implementation. */
208
+ declare const defaultInjectIO: InjectIO;
209
+
210
+ interface BuildPlanOptions {
211
+ /** Prepend into a dirty (uncommitted) target instead of asking to confirm. */
212
+ force?: boolean;
213
+ }
214
+ interface BuildPlanInput {
215
+ cwd: string;
216
+ recipe: Recipe;
217
+ endpoint: string;
218
+ apiKey: string;
219
+ /** Absolute entry path for vite-spa / node (from detection). */
220
+ entryFile?: string | null;
221
+ /** Raw `next` version range from detection (drives new-file vs legacy-prepend). */
222
+ nextVersion?: string | null;
223
+ /**
224
+ * The detected non-JS backend Stack for the `otlp` recipe (from
225
+ * `DetectResult.otlpStack`). Drives the guidance agent prompt; ignored for
226
+ * every other recipe.
227
+ */
228
+ stack?: Stack;
229
+ options?: BuildPlanOptions;
230
+ }
231
+ /**
232
+ * True when this package already depends on a Crumbtrail SDK. Load-bearing for
233
+ * the batch installer, which must decide whether to provision a service BEFORE
234
+ * it builds a plan — `buildPlan` uses this to self-cancel into
235
+ * `skip-already-wired`, so a re-run must not mint a second key for a service
236
+ * that is going to be skipped anyway.
237
+ */
238
+ declare function projectAlreadyWired(cwd: string, io: InjectIO): boolean;
239
+ /**
240
+ * `instrumentation-client.ts` is auto-loaded from Next 15.3+. Parse the leading
241
+ * numeric range; treat non-numeric ranges ("latest", "canary", workspace:) as
242
+ * new-enough.
243
+ */
244
+ declare function supportsInstrumentationClient(version: string | null | undefined): boolean;
245
+ /**
246
+ * Build the injection Plan for a detected recipe. Reads only (via `io`); the
247
+ * returned Plan is plain data the executor applies all-or-nothing.
248
+ */
249
+ declare function buildPlan(input: BuildPlanInput, io?: InjectIO): Plan;
250
+
251
+ /** Write boundary for the executor — swappable in tests. */
252
+ interface ExecutorIO {
253
+ exists(p: string): boolean;
254
+ readFile(p: string): string | null;
255
+ writeFile(p: string, content: string): void;
256
+ mkdirp(dir: string): void;
257
+ remove(p: string): void;
258
+ }
259
+ declare const defaultExecutorIO: ExecutorIO;
260
+ interface ExecuteOptions {
261
+ /** Apply a `needs-confirm-dirty` plan (the user confirmed / passed --force). */
262
+ confirmDirty?: boolean;
263
+ }
264
+ interface ExecuteResult {
265
+ kind: Plan["kind"];
266
+ /** Absolute paths actually written. */
267
+ written: string[];
268
+ /** True when nothing was written (skip / fallback / unconfirmed-dirty). */
269
+ skipped: boolean;
270
+ message: string;
271
+ }
272
+ /**
273
+ * Execute a Plan. skip/fallback plans (and unconfirmed dirty plans) perform no
274
+ * writes. create/prepend plans — plus any Node `.env` action — are applied
275
+ * all-or-nothing.
276
+ */
277
+ declare function executePlan(plan: Plan, io?: ExecutorIO, options?: ExecuteOptions): ExecuteResult;
278
+
279
+ export { type BuildPlanInput as B, type DetectResult as D, type EnvAction as E, type GitTargetStatus as G, type InjectIO as I, type PackageManager as P, type Recipe as R, type WorkspacePackage as W, type BuildPlanOptions as a, buildPlan as b, DENO_UNSUPPORTED_REASON as c, DOCKER_COMING_SOON_NOTE as d, executePlan as e, type ExecuteOptions as f, type ExecuteResult as g, type ExecutorIO as h, type Plan as i, type PlanKind as j, defaultExecutorIO as k, defaultInjectIO as l, detect as m, detectPackageManager as n, matchRecipe as o, parseNodeInvocation as p, parsePnpmWorkspace as q, projectAlreadyWired as r, resolveAngularEntry as s, resolveNestEntry as t, resolveOtlpStack as u, resolveReactNativeEntry as v, resolveRemixEntry as w, resolveViteEntry as x, supportsInstrumentationClient as y };