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.
package/dist/cli.d.cts ADDED
@@ -0,0 +1,358 @@
1
+ #!/usr/bin/env node
2
+ import { R as Recipe, D as DetectResult, P as PackageManager, b as buildPlan, e as executePlan } from './executor-DKDrkhxM.cjs';
3
+ import { Stack } from 'crumbtrail-design-system';
4
+
5
+ /** Output sink — swappable in tests to capture lines instead of writing stdout. */
6
+ interface Ui {
7
+ out(line?: string): void;
8
+ err(line?: string): void;
9
+ /**
10
+ * Rewrite a transient single-line status in place (a live "Waiting… 12s"
11
+ * ticker). TTY-only; calling with no argument clears the line. Callers MUST
12
+ * clear before the next out()/err() or the lines collide.
13
+ */
14
+ status?(line?: string): void;
15
+ }
16
+ /** One row of a `multiSelect` list. */
17
+ interface MultiSelectItem {
18
+ /** Primary text, e.g. "apps/web". */
19
+ label: string;
20
+ /** Trailing detail, e.g. "next" or "already wired — skipping". */
21
+ hint?: string;
22
+ /** Whether this row starts checked (drives the empty-input default). */
23
+ checked: boolean;
24
+ /**
25
+ * False for rows we can list but cannot wire (no recipe matched). Rendered
26
+ * greyed out and rejected if the user names them explicitly — listing them is
27
+ * how we show the scan wasn't blind to the package.
28
+ */
29
+ selectable: boolean;
30
+ }
31
+ /** Prompts the wizard needs — injectable so tests answer without a TTY. */
32
+ interface Prompter {
33
+ /** Free-text with a default; empty input returns the default. */
34
+ ask(question: string, def?: string): Promise<string>;
35
+ /** Yes/no; empty input returns `def`. */
36
+ confirm(question: string, def?: boolean): Promise<boolean>;
37
+ /** 1-based numeric choice among labels; empty input returns `def` (0-based). */
38
+ select(question: string, labels: string[], def?: number): Promise<number>;
39
+ /**
40
+ * 1-based multi-choice; returns 0-based indices. Empty input takes the
41
+ * checked defaults. Number-list based (no raw mode / arrow keys anywhere in
42
+ * this CLI).
43
+ */
44
+ multiSelect(question: string, items: MultiSelectItem[]): Promise<number[]>;
45
+ }
46
+
47
+ interface LoginOptions {
48
+ base: string;
49
+ ui: Ui;
50
+ noBrowser?: boolean;
51
+ fetchImpl?: typeof fetch;
52
+ /** Injected browser opener (tests). */
53
+ openFn?: (url: string) => boolean | Promise<boolean>;
54
+ env?: NodeJS.ProcessEnv;
55
+ /** Device-poll interval override for tests (ms). */
56
+ pollIntervalMs?: number;
57
+ /** Browser hand-off deadline in ms (default 5 min); overridable in tests. */
58
+ browserDeadlineMs?: number;
59
+ }
60
+ /**
61
+ * Resolve a usable CLI token for `base`: reuse a stored token (validated by a
62
+ * cheap GET /api/projects, and only if it was minted for THIS endpoint), else
63
+ * run the login flow and persist the result. A stored token that 401s is
64
+ * cleared and re-minted.
65
+ */
66
+ declare function ensureToken(opts: LoginOptions): Promise<string>;
67
+
68
+ interface Project {
69
+ id: string;
70
+ name: string;
71
+ }
72
+ interface ProvisionInput {
73
+ base: string;
74
+ token: string;
75
+ recipe: Recipe;
76
+ /**
77
+ * The detected non-JS backend Stack for the `otlp` recipe
78
+ * (`DetectResult.otlpStack`). When set it OVERRIDES the static
79
+ * `RECIPE_REGISTRY[recipe].stack` placeholder on the createService call — this
80
+ * is how the single otlp recipe reports its variable Stack to the service.
81
+ */
82
+ stack?: Stack | null;
83
+ ui: Ui;
84
+ prompter: Prompter;
85
+ /** Skip prompts (non-interactive / --yes). */
86
+ assumeYes: boolean;
87
+ /** --project <id>: skip creation, attach a service to this project. */
88
+ projectId?: string;
89
+ /** Inferred defaults (from detection / package.json / git). */
90
+ defaultProjectName: string;
91
+ defaultServiceName: string;
92
+ fetchImpl?: typeof fetch;
93
+ }
94
+ interface ProvisionResult {
95
+ projectId: string;
96
+ projectName: string;
97
+ serviceId: string;
98
+ serviceName: string;
99
+ apiKey: string;
100
+ }
101
+ interface ResolveProjectInput {
102
+ base: string;
103
+ token: string;
104
+ ui: Ui;
105
+ prompter: Prompter;
106
+ assumeYes: boolean;
107
+ projectId?: string;
108
+ defaultProjectName: string;
109
+ fetchImpl?: typeof fetch;
110
+ }
111
+ /**
112
+ * Resolve the project a service will report to: explicit --project, an
113
+ * interactive pick among existing projects, or a freshly created one.
114
+ *
115
+ * Split out of provisionFlow so the batch installer can resolve ONE project and
116
+ * then mint many services under it.
117
+ */
118
+ declare function resolveProject(input: ResolveProjectInput): Promise<Project>;
119
+ interface ProvisionServiceInput {
120
+ base: string;
121
+ token: string;
122
+ projectId: string;
123
+ recipe: Recipe;
124
+ /** Detected otlp stack; overrides the registry placeholder. */
125
+ stack?: Stack | null;
126
+ serviceName: string;
127
+ ui: Ui;
128
+ fetchImpl?: typeof fetch;
129
+ }
130
+ /**
131
+ * Add one service to an already-resolved project and mint its ingest key.
132
+ * Prompt-free by design: the batch installer names services from detection
133
+ * rather than asking N times.
134
+ */
135
+ declare function provisionService(input: ProvisionServiceInput): Promise<{
136
+ serviceId: string;
137
+ serviceName: string;
138
+ apiKey: string;
139
+ }>;
140
+ /**
141
+ * Resolve a project, add a service to it, and mint the ingest key returned to
142
+ * the wizard for injection + summary. Composed from resolveProject +
143
+ * provisionService so the single-package path keeps its exact behavior
144
+ * (including the interactive "Service name" prompt).
145
+ */
146
+ declare function provisionFlow(input: ProvisionInput): Promise<ProvisionResult>;
147
+
148
+ interface IngestPollConfig {
149
+ /** Delay before the first poll, and the backoff step size. */
150
+ initialDelayMs: number;
151
+ /** Upper bound on the per-attempt delay. */
152
+ maxDelayMs: number;
153
+ /** Give up (→ "timedout") once cumulative waiting reaches this. */
154
+ timeoutMs: number;
155
+ }
156
+
157
+ /**
158
+ * Push a synthetic marker session through real ingest (start → one event → end)
159
+ * with the minted key, emulating crumbtrail-core's HttpTransport wire format
160
+ * (X-Crumbtrail-Auth header, {sessionId, events}). Throws if any leg fails —
161
+ * that proves the key/endpoint/network are wrong before the user wastes time.
162
+ */
163
+ declare function syntheticCheck(base: string, apiKey: string, fetchImpl?: typeof fetch): Promise<void>;
164
+ type RealEventOutcome = "found" | "timedout" | "cancelled";
165
+ interface PollRealEventResult {
166
+ outcome: RealEventOutcome;
167
+ /** Set when outcome is "found" — the session behind the first real event. */
168
+ sessionId?: string;
169
+ }
170
+ interface PollRealEventOptions {
171
+ base: string;
172
+ token: string;
173
+ projectId: string;
174
+ ui: Ui;
175
+ /**
176
+ * Ms epoch captured at wizard entry. Only sessions started at/after this count
177
+ * as "the first real event" — a session from a prior run is ignored.
178
+ */
179
+ wizardStart?: number;
180
+ signal?: AbortSignal;
181
+ config?: IngestPollConfig;
182
+ fetchImpl?: typeof fetch;
183
+ /** Injected delay (tests); defaults to a real, abortable setTimeout. */
184
+ sleepFn?: (ms: number, signal?: AbortSignal) => Promise<void>;
185
+ }
186
+ /**
187
+ * Poll the sessions feed until a real (non-synthetic) session lands, the backoff
188
+ * budget is exhausted, or the caller aborts (Ctrl-C). The timing policy is the
189
+ * ported poll.ts state machine. On a TTY, a live elapsed-time status line keeps
190
+ * a long wait from reading as a hang.
191
+ */
192
+ declare function pollForRealEvent(opts: PollRealEventOptions): Promise<PollRealEventResult>;
193
+ interface PollServicesOptions extends Omit<PollRealEventOptions, "wizardStart"> {
194
+ wizardStart?: number;
195
+ /** The services we just wired and expect events from. */
196
+ serviceIds: string[];
197
+ /** Fired once per service, the first time its event lands. */
198
+ onFound?: (serviceId: string, sessionId: string) => void;
199
+ }
200
+ interface PollServicesResult {
201
+ /** "found" only when EVERY serviceId reported. */
202
+ outcome: RealEventOutcome;
203
+ /** serviceId → sessionId, for however many reported before we stopped. */
204
+ found: Record<string, string>;
205
+ }
206
+ /**
207
+ * Poll once for the whole batch until every wired service has reported, the
208
+ * shared budget is exhausted, or the user aborts. Timeout and cancel both return
209
+ * whatever arrived — stragglers never block the wizard from finishing, because
210
+ * the wiring is already done by the time we get here.
211
+ */
212
+ declare function pollForServices(opts: PollServicesOptions): Promise<PollServicesResult>;
213
+
214
+ type CandidateSource = "workspace" | "scan";
215
+ type CandidateFlag = "ambiguous" | "otlp" | "likely-library" | "already-wired" | "no-recipe";
216
+ interface ServiceCandidate {
217
+ /** Absolute path to the service directory. */
218
+ dir: string;
219
+ /** package.json name, falling back to the directory basename. */
220
+ name: string;
221
+ /** Path relative to the repo root — used for labels and `--only` matching. */
222
+ relDir: string;
223
+ source: CandidateSource;
224
+ /** The real detect() output for this dir, not a synthesized one. */
225
+ detected: DetectResult;
226
+ recipe: Recipe | null;
227
+ flags: CandidateFlag[];
228
+ /** Pre-checked in the multi-select list. */
229
+ defaultChecked: boolean;
230
+ /** False when nothing can be wired here (no recipe matched). */
231
+ selectable: boolean;
232
+ }
233
+ interface DiscoverDeps {
234
+ detect?: (cwd: string) => DetectResult;
235
+ /** True when this dir already depends on a Crumbtrail SDK. */
236
+ alreadyWired?: (dir: string) => boolean;
237
+ readDir?: (dir: string) => string[];
238
+ isDir?: (p: string) => boolean;
239
+ isFile?: (p: string) => boolean;
240
+ }
241
+ /**
242
+ * Every service Crumbtrail can see from the repo root: real workspaces first (by
243
+ * path), then non-JS services found by the conventional-dir scan.
244
+ */
245
+ declare function discoverServices(root: string, rootResult: DetectResult, overrides?: DiscoverDeps): ServiceCandidate[];
246
+
247
+ type Command = "wizard" | "login" | "logout" | "help" | "version";
248
+ interface ParsedArgs {
249
+ command: Command;
250
+ yes: boolean;
251
+ project?: string;
252
+ noBrowser: boolean;
253
+ skipVerify: boolean;
254
+ endpoint?: string;
255
+ /**
256
+ * Monorepo root only: wire exactly these services (repeatable `--only`,
257
+ * matched against a service's package name or path). Also the non-interactive
258
+ * escape hatch — a root run in CI must name its services somehow.
259
+ */
260
+ only?: string[];
261
+ /** Monorepo root only: select every wireable service, no prompt. */
262
+ all: boolean;
263
+ /** Non-flag/subcommand leftover — an unknown token triggers usage help. */
264
+ unknown?: string;
265
+ }
266
+ declare function parseArgs(argv: string[]): ParsedArgs;
267
+ interface InstallSdkInput {
268
+ cwd: string;
269
+ packageManager: PackageManager | null;
270
+ recipe: Recipe;
271
+ base: string;
272
+ ui: Ui;
273
+ /** Injected runner (tests); returns the child exit code. */
274
+ spawnFn?: (cmd: string, args: string[], cwd: string) => number;
275
+ /** Injected fetch for the tarball-manifest fallback (tests); defaults to global. */
276
+ fetchImpl?: typeof fetch;
277
+ }
278
+ interface InstallSdkResult {
279
+ installed: boolean;
280
+ packages: string[];
281
+ note?: string;
282
+ }
283
+ /**
284
+ * Install the SDK with the detected package manager. If the registry install
285
+ * fails (e.g. packages not yet public, or a self-hosted endpoint), fall back to
286
+ * installing the deploy's packed tarballs — discovered via
287
+ * `GET <base>/install/manifest.json` — so a fresh deploy wires up before the
288
+ * packages are on npm. Every recipe's SDK (including react-native and tauri, now
289
+ * packed as optional channels by pack-local.mjs) is resolved by name prefix from
290
+ * that manifest, so the fallback is uniform. Non-fatal either way, since
291
+ * injection only writes import statements.
292
+ */
293
+ declare function installSdk(input: InstallSdkInput): Promise<InstallSdkResult>;
294
+ interface WizardDeps {
295
+ detect: (cwd: string) => DetectResult;
296
+ ensureToken: typeof ensureToken;
297
+ provisionFlow: typeof provisionFlow;
298
+ installSdk: (input: InstallSdkInput) => Promise<InstallSdkResult>;
299
+ buildPlan: typeof buildPlan;
300
+ executePlan: typeof executePlan;
301
+ syntheticCheck: typeof syntheticCheck;
302
+ pollForRealEvent: typeof pollForRealEvent;
303
+ /** Batch path (monorepo root). */
304
+ discoverServices: typeof discoverServices;
305
+ resolveProject: typeof resolveProject;
306
+ provisionService: typeof provisionService;
307
+ pollForServices: typeof pollForServices;
308
+ /** Browser opener for the end-of-wizard dashboard hand-off (stub in tests). */
309
+ openBrowserFn?: (url: string) => Promise<boolean>;
310
+ ui: Ui;
311
+ prompter: Prompter;
312
+ env: NodeJS.ProcessEnv;
313
+ cwd: string;
314
+ isTTY: boolean;
315
+ fetchImpl?: typeof fetch;
316
+ }
317
+ declare function defaultDeps(): WizardDeps;
318
+ declare function runWizard(parsed: ParsedArgs, deps: WizardDeps): Promise<number>;
319
+ type ServiceStatus = "wired" | "guidance" | "skipped-already-wired" | "failed";
320
+ interface ServiceOutcome {
321
+ name: string;
322
+ relDir: string;
323
+ recipe: Recipe;
324
+ status: ServiceStatus;
325
+ serviceId?: string;
326
+ apiKey?: string;
327
+ filesTouched: string[];
328
+ notes: string[];
329
+ error?: string;
330
+ sessionUrl?: string;
331
+ }
332
+ /**
333
+ * Resolve --only/--all into indices, or null when we should prompt.
334
+ * Returns a string on a user error (unknown --only value).
335
+ */
336
+ declare function resolveSelection(parsed: ParsedArgs, candidates: ServiceCandidate[]): {
337
+ indices: number[];
338
+ } | {
339
+ error: string;
340
+ } | null;
341
+ interface BatchContext {
342
+ base: string;
343
+ wizardStart: number;
344
+ root: DetectResult;
345
+ }
346
+ /**
347
+ * The monorepo path: scan → pick → one login → one project → wire each selected
348
+ * service → one shared wait → summary.
349
+ *
350
+ * A failure on one service never sinks the batch (each is try/caught and
351
+ * recorded), because a half-wired repo with a clear report is strictly better
352
+ * than an abort that leaves the user guessing which services made it.
353
+ */
354
+ declare function runBatchWizard(parsed: ParsedArgs, deps: WizardDeps, ctx: BatchContext): Promise<number>;
355
+ declare function runCli(argv: string[], deps?: WizardDeps): Promise<number>;
356
+ declare function isCliEntrypoint(argv1: string | undefined): boolean;
357
+
358
+ export { type Command, type InstallSdkInput, type InstallSdkResult, type ParsedArgs, type ServiceOutcome, type ServiceStatus, type WizardDeps, defaultDeps, installSdk, isCliEntrypoint, parseArgs, resolveSelection, runBatchWizard, runCli, runWizard };