ingestron 0.12.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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,588 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import * as prompts from "@clack/prompts";
4
+ import { resolve, basename, dirname } from "node:path";
5
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
6
+ import { parseDocument } from "yaml";
7
+ import { execute, executeAsync, } from "@ingestron/core";
8
+ import { fence } from "@ingestron/core/adapter";
9
+ import { Configuration } from "@ingestron/core/adapter";
10
+ import { check, Problem } from "@ingestron/core/adapter";
11
+ import { version } from "../version.js";
12
+ import { terminal, exitCode } from "./output.js";
13
+ import { decorate, banner, backgroundOperation } from "./presentation.js";
14
+ import { workflowCommands } from "./workflow-commands.js";
15
+ const app = new Command()
16
+ .name("ingestron")
17
+ .description("Configure flows once. Generate reviewable native data engineering assets offline.")
18
+ .version(version)
19
+ .option("--project <directory>", "Project root", ".")
20
+ .option("-e, --environment <name>", "Selected environment", "dev")
21
+ .option("--env <name>", "Alias for --environment")
22
+ .option("--json", "Versioned JSON output; progress goes to stderr")
23
+ .option("--plain", "Plain noninteractive output; no styling or animation")
24
+ .option("--no-color", "Disable terminal colour and decoration")
25
+ .option("--no-progress", "Disable progress animation")
26
+ .option("--verbose", "Show full result details in human-readable output")
27
+ .option("--no-input", "Never ask interactive questions")
28
+ .option("--dry-run", "Preview local file changes without applying");
29
+ const opts = () => app.opts();
30
+ const presentation = () => ({
31
+ tty: !!process.stdout.isTTY,
32
+ plain: opts().plain,
33
+ colour: opts().color,
34
+ progress: opts().progress,
35
+ json: !!opts().json,
36
+ });
37
+ const context = (root) => ({
38
+ root: root ?? resolve(opts().project),
39
+ environment: opts().env ?? opts().environment,
40
+ allowWrite: true,
41
+ allowNetwork: true,
42
+ allowExecute: true,
43
+ });
44
+ function print(result, args) {
45
+ process.exitCode = exitCode(result);
46
+ if (opts().json || process.argv.includes("--json"))
47
+ process.stdout.write(JSON.stringify(result) + "\n");
48
+ else
49
+ (result.ok ? process.stdout : process.stderr).write(decorate(terminal(result, !!opts().verbose, args), result.ok, presentation()) + "\n");
50
+ }
51
+ // Commander failures must obey the same JSON contract as operation failures.
52
+ // Help/version remain successful envelopes in JSON mode; no progress goes to stdout.
53
+ let helpText = "";
54
+ app.exitOverride();
55
+ app.configureOutput({
56
+ writeOut: (text) => {
57
+ if (process.argv.includes("--json"))
58
+ helpText += text;
59
+ else
60
+ process.stdout.write(text.startsWith("Usage: ingestron [")
61
+ ? banner(text, presentation())
62
+ : text);
63
+ },
64
+ writeErr: (text) => {
65
+ if (text.startsWith("Usage:")) {
66
+ if (process.argv.includes("--json"))
67
+ helpText += text;
68
+ else
69
+ process.stdout.write(text.startsWith("Usage: ingestron [")
70
+ ? banner(text, presentation())
71
+ : text);
72
+ }
73
+ },
74
+ outputError: () => { },
75
+ });
76
+ const run = async (name, args = {}, ctx = context()) => {
77
+ const result = ["build", "plan", "generate", "packages_install"].includes(name)
78
+ ? await backgroundOperation(ctx, name, args, `${name === "packages_install" ? "Installing plugin" : "Preparing native assets"}`, presentation())
79
+ : await executeAsync(ctx, name, args);
80
+ print(result, args);
81
+ };
82
+ function author(name, args, ctx = context()) {
83
+ const result = execute(ctx, name, args);
84
+ return print(!result.ok || opts().dryRun
85
+ ? result
86
+ : execute(ctx, "apply", { proposal: result.result }));
87
+ }
88
+ function readData(file) {
89
+ const doc = parseDocument(new Configuration(context().root).text(file), {
90
+ uniqueKeys: true,
91
+ });
92
+ check(!doc.errors.length, "YAML", doc.errors[0]?.message ?? "Invalid YAML");
93
+ return doc.toJS();
94
+ }
95
+ function save(file, value) {
96
+ const absolute = fence(resolve(opts().project), file);
97
+ mkdirSync(dirname(absolute), { recursive: true });
98
+ writeFileSync(absolute, JSON.stringify(value, null, 2) + "\n");
99
+ }
100
+ const canPrompt = () => !!process.stdin.isTTY &&
101
+ opts().input !== false &&
102
+ !opts().json &&
103
+ !opts().plain &&
104
+ process.env.TERM !== "dumb" &&
105
+ !process.env.CI;
106
+ async function answer(value) {
107
+ const result = await value;
108
+ if (prompts.isCancel(result)) {
109
+ process.exitCode = 130;
110
+ throw new Problem("CANCELLED", "Cancelled");
111
+ }
112
+ return result;
113
+ }
114
+ app
115
+ .command("init [directory]")
116
+ .description("Create a project; install a provider when ready to build")
117
+ .option("--id <id>", "Project identifier")
118
+ .option("--provider <reference>", "Installed repository or exact reference, e.g. owner/provider@1.0.0")
119
+ .option("--environments <names>", "Comma-separated environments (default: dev,test,prod; --env selects one)")
120
+ .action(async (directory = ".", command) => {
121
+ const root = resolve(opts().project, directory);
122
+ const id = command.id ??
123
+ (canPrompt()
124
+ ? await answer(prompts.text({
125
+ message: "Project identifier",
126
+ initialValue: basename(root),
127
+ }))
128
+ : basename(root));
129
+ author("initialise", {
130
+ id,
131
+ provider: command.provider,
132
+ environments: (command.environments ??
133
+ (opts().env !== undefined ||
134
+ app.getOptionValueSource("environment") === "cli"
135
+ ? context().environment
136
+ : "dev,test,prod"))
137
+ .split(",")
138
+ .map((name) => name.trim()),
139
+ }, context(root));
140
+ });
141
+ const flow = app
142
+ .command("flow")
143
+ .description("Create and inspect ingestion/transformation flows");
144
+ flow
145
+ .command("add <id>")
146
+ .option("--kind <kind>", "ingestion or transformation", "ingestion")
147
+ .option("--contract <id>", "Contract ID or project-relative file")
148
+ .option("--source <id>", "Named source; otherwise inferred from contract provenance")
149
+ .option("--standard <name>", "Provider ingestion standard, e.g. snapshot-with-history@v1")
150
+ .option("--provider <configuration>", "Provider configuration for this flow")
151
+ .option("--source-kind <kind>", "Provider source kind")
152
+ .option("--format <format>", "Provider-supported file format (for example csv, json, parquet)")
153
+ .option("--source-binding <id>", "Existing or new environment source binding")
154
+ .option("--input-dataset <id>", "Published source dataset for a native SQL transformation")
155
+ .action(async (id, command) => {
156
+ let standard = command.standard;
157
+ if (command.kind === "ingestion" && !standard && canPrompt())
158
+ standard = await answer(prompts.text({
159
+ message: "Standard from the installed provider (name@version)",
160
+ validate: (value) => value?.trim() ? undefined : "Enter a provider standard",
161
+ }));
162
+ author("flow_create", {
163
+ ...(command.contract ? { contract: command.contract } : {}),
164
+ ...(command.source ? { source: command.source } : {}),
165
+ id,
166
+ kind: command.kind,
167
+ ...(standard ? { standard } : {}),
168
+ ...(command.provider ? { provider: command.provider } : {}),
169
+ ...(command.sourceKind ? { sourceKind: command.sourceKind } : {}),
170
+ ...(command.format ? { format: command.format } : {}),
171
+ ...(command.inputDataset ? { inputDataset: command.inputDataset } : {}),
172
+ ...(command.sourceBinding
173
+ ? { sourceBinding: command.sourceBinding }
174
+ : {}),
175
+ });
176
+ });
177
+ flow
178
+ .command("configure <id>")
179
+ .requiredOption("--patch <file>")
180
+ .action((id, command) => author("flow_configure", { flow: id, patch: readData(command.patch) }));
181
+ app
182
+ .command("step")
183
+ .command("add <id>")
184
+ .requiredOption("--flow <id>")
185
+ .requiredOption("--uses <activity>")
186
+ .option("--after <id>")
187
+ .option("--with <file>")
188
+ .action((id, command) => author("step_add", {
189
+ flow: command.flow,
190
+ step: {
191
+ id,
192
+ uses: command.uses,
193
+ with: command.with ? readData(command.with) : {},
194
+ },
195
+ ...(command.after ? { after: command.after } : {}),
196
+ }));
197
+ flow
198
+ .command("list")
199
+ .description("List configured flows, table names and published outputs")
200
+ .action(() => run("inspect"));
201
+ const table = app
202
+ .command("table")
203
+ .description("Declare ingestion tables once per flow");
204
+ table
205
+ .command("add <id>")
206
+ .requiredOption("--flow <id>", "Owning ingestion flow")
207
+ .requiredOption("--contract <file>", "Source ODCS file inside the project")
208
+ .action((id, command) => author("table_add", {
209
+ id,
210
+ flow: command.flow,
211
+ contract: command.contract,
212
+ }));
213
+ table
214
+ .command("configure <id>")
215
+ .requiredOption("--flow <id>", "Owning flow")
216
+ .requiredOption("--patch <file>", "YAML/JSON override patch")
217
+ .action((id, command) => author("table_configure", {
218
+ id,
219
+ flow: command.flow,
220
+ patch: readData(command.patch),
221
+ }));
222
+ table
223
+ .command("list")
224
+ .description("List configured table names grouped by flow")
225
+ .action(() => run("tables"));
226
+ const dataset = app.command("dataset");
227
+ dataset.command("list").action(() => run("datasets"));
228
+ dataset
229
+ .command("add <id>")
230
+ .requiredOption("--flow <id>", "Transformation flow")
231
+ .requiredOption("--contract <file>", "Output ODCS file")
232
+ .requiredOption("--from <port>", "Producing port, e.g. steps.ready.outputs.table")
233
+ .action((id, command) => author("dataset_add", {
234
+ id,
235
+ flow: command.flow,
236
+ contract: command.contract,
237
+ from: command.from,
238
+ }));
239
+ const config = app
240
+ .command("config")
241
+ .description("Configure existing resources and non-secret values");
242
+ config
243
+ .command("set <path>")
244
+ .requiredOption("--value <value>", "YAML scalar or mapping")
245
+ .action((path, command) => author("config_set", { path, value: parseDocument(command.value).toJS() }));
246
+ config
247
+ .command("explain <path>")
248
+ .action((path) => run("config_explain", { path }));
249
+ config.command("fill").action(async () => {
250
+ const result = execute(context(), "resolve", {});
251
+ if (!result.ok) {
252
+ print(result);
253
+ return;
254
+ }
255
+ check(canPrompt(), "INPUT", "config fill requires an interactive terminal; use config set or doctor with --no-input/--json");
256
+ const profile = result.result.project.environments[context().environment];
257
+ const fields = [];
258
+ const walk = (value, path) => {
259
+ if (value && typeof value === "object" && value.$env)
260
+ fields.push({ path, variable: value.$env });
261
+ else if (value && typeof value === "object")
262
+ for (const [k, child] of Object.entries(value))
263
+ walk(child, path ? path + "." + k : k);
264
+ };
265
+ walk(profile.bindings, "bindings");
266
+ walk(profile.values, "values");
267
+ for (const field of fields) {
268
+ const value = await answer(prompts.text({
269
+ message: `${field.path} (${field.variable})`,
270
+ placeholder: "Existing resource value; leave blank to keep pending",
271
+ }));
272
+ if (value)
273
+ author("config_set", { path: field.path, value });
274
+ }
275
+ });
276
+ const environments = app.command("environments");
277
+ environments.command("list").action(() => run("environments"));
278
+ environments
279
+ .command("add <name>")
280
+ .description("Copy an existing environment for review and configuration")
281
+ .requiredOption("--from <environment>", "Existing environment to copy")
282
+ .action((name, command) => author("environment_add", { name, from: command.from }));
283
+ for (const kind of ["activities", "standards", "providers"]) {
284
+ const group = app
285
+ .command(kind)
286
+ .description(`Inspect current ${kind} and their supported capabilities`);
287
+ group.command("list").action(() => run("catalogue", { kind }));
288
+ group.command("explain <id>").action((id) => run("catalogue", { kind, id }));
289
+ }
290
+ const connections = app
291
+ .command("connections")
292
+ .description("Validate and prepare project-defined connector flows without source access");
293
+ connections
294
+ .command("validate <flow>")
295
+ .action((flow) => run("connection_prepare", { flow, validateOnly: true }));
296
+ connections
297
+ .command("prepare <flow>")
298
+ .requiredOption("--out <file>", "New provider result JSON file")
299
+ .action((flow, command) => {
300
+ const result = execute(context(), "connection_prepare", { flow });
301
+ if (result.ok) {
302
+ const file = fence(context().root, command.out);
303
+ mkdirSync(dirname(file), { recursive: true });
304
+ writeFileSync(file, JSON.stringify(result.result, null, 2) + "\n", {
305
+ flag: "wx",
306
+ });
307
+ }
308
+ print(result);
309
+ });
310
+ connections
311
+ .command("contracts <flow>")
312
+ .requiredOption("--review <file>", "Approved generated review")
313
+ .requiredOption("--out <file>", "New provider result file")
314
+ .action((flow, command) => {
315
+ const result = execute(context(), "connection_prepare", {
316
+ flow,
317
+ review: command.review,
318
+ });
319
+ if (result.ok) {
320
+ const file = fence(context().root, command.out);
321
+ mkdirSync(dirname(file), { recursive: true });
322
+ writeFileSync(file, JSON.stringify(result.result, null, 2) + "\n", {
323
+ flag: "wx",
324
+ });
325
+ }
326
+ print(result);
327
+ });
328
+ const providerCommands = app.commands.find((command) => command.name() === "providers");
329
+ for (const update of [false, true])
330
+ providerCommands
331
+ .command(`${update ? "update" : "install"} <reference>`)
332
+ .description("Install a provider version into the project cache; select it in project.yaml")
333
+ .option("--from-git <directory>", "Local Git repository")
334
+ .option("--frozen", "Require an existing immutable lock")
335
+ .action((reference, command) => run("packages_install", {
336
+ reference,
337
+ update,
338
+ frozen: !!command.frozen,
339
+ ...(command.fromGit ? { fromGit: command.fromGit } : {}),
340
+ }));
341
+ providerCommands
342
+ .command("commands <configuration>")
343
+ .description("List provider-owned commands and input schemas without executing plugin code")
344
+ .action((configuration) => run("provider_commands", { configuration }));
345
+ app
346
+ .command("provider <configuration> <command...>")
347
+ .description("Run an installed provider's offline command; inspect providers commands first")
348
+ .option("--input <file>", "Project-relative JSON input file")
349
+ .option("--out <file>", "Save the provider result JSON to a new project file")
350
+ .action((configuration, words, command) => providerAction("provider_command", { configuration, command: words.join(" ") }, command));
351
+ function providerAction(operation, args, command) {
352
+ const input = command.input
353
+ ? JSON.parse(readFileSync(new Configuration(context().root).path(command.input), "utf8"))
354
+ : {};
355
+ const result = execute(context(), operation, { ...args, input });
356
+ if (result.ok && command.out) {
357
+ const file = fence(context().root, command.out);
358
+ mkdirSync(dirname(file), { recursive: true });
359
+ writeFileSync(file, JSON.stringify(result.result, null, 2) + "\n", {
360
+ flag: "wx",
361
+ });
362
+ if (!opts().json && !opts().verbose) {
363
+ process.stdout.write(`Saved provider result to ${command.out}\n`);
364
+ return;
365
+ }
366
+ }
367
+ print(result);
368
+ }
369
+ providerCommands
370
+ .command("export")
371
+ .description("Review/export files returned by an offline provider; no script execution")
372
+ .requiredOption("--result <file>", "Saved provider result JSON")
373
+ .requiredOption("--out <directory>", "New artifact directory")
374
+ .action((command) => author("provider_export", { result: command.result, out: command.out }));
375
+ providerCommands
376
+ .command("migrate")
377
+ .requiredOption("--from <package>", "Project package key")
378
+ .requiredOption("--to <reference>", "Installed exact provider reference")
379
+ .action((command) => author("provider_migrate", { package: command.from, to: command.to }));
380
+ const packages = app.command("packages");
381
+ packages.command("list").action(() => run("packages_list"));
382
+ for (const update of [false, true])
383
+ packages
384
+ .command(`${update ? "update" : "install"} <reference>`)
385
+ .option("--from-git <directory>", "Explicit local Git source for offline installation")
386
+ .option("--frozen", "Require existing immutable lock entry")
387
+ .action((reference, command) => run("packages_install", {
388
+ reference,
389
+ update,
390
+ ...(command.fromGit ? { fromGit: command.fromGit } : {}),
391
+ frozen: !!command.frozen,
392
+ }));
393
+ app
394
+ .command("validate")
395
+ .option("--mode <mode>", "draft or strict", "strict")
396
+ .option("--flow <id>")
397
+ .option("--table <id>")
398
+ .option("--step <id>")
399
+ .action((command) => run("validate", {
400
+ mode: command.mode,
401
+ ...(command.flow ? { flow: command.flow } : {}),
402
+ ...(command.table ? { table: command.table } : {}),
403
+ ...(command.step ? { step: command.step } : {}),
404
+ }));
405
+ app
406
+ .command("doctor")
407
+ .description("Check local configuration and missing inputs; no platform connection")
408
+ .action(() => run("doctor"));
409
+ app.command("resolve").action(() => run("resolve"));
410
+ app
411
+ .command("plan")
412
+ .option("--flow <id>")
413
+ .option("--table <id>")
414
+ .option("--step <id>")
415
+ .option("--out <file>", "Save a digest-bound JSON plan")
416
+ .action((command) => {
417
+ const result = execute(context(), "plan", {
418
+ ...(command.flow ? { flow: command.flow } : {}),
419
+ ...(command.table ? { table: command.table } : {}),
420
+ ...(command.step ? { step: command.step } : {}),
421
+ });
422
+ if (result.ok && command.out)
423
+ save(command.out, result.result);
424
+ print(result);
425
+ });
426
+ app
427
+ .command("diff")
428
+ .description("Compare local plans; no remote deployment drift lookup")
429
+ .requiredOption("--plan <file>", "Previously reviewed plan")
430
+ .action((command) => run("diff", { plan: command.plan }));
431
+ app
432
+ .command("generate")
433
+ .requiredOption("--plan <file>", "Saved plan JSON")
434
+ .requiredOption("--out <directory>", "Owned output directory inside project")
435
+ .option("--ownership <mode>", "managed (regenerate safely) or team (manual handover)", "managed")
436
+ .action((command) => run("generate", {
437
+ plan: command.plan,
438
+ out: command.out,
439
+ ownership: command.ownership,
440
+ }));
441
+ app
442
+ .command("validate-output <directory>")
443
+ .description("Check local output ownership and syntax; no target validation")
444
+ .action((directory) => run("validate_output", { directory }));
445
+ app.command("schema <name>").action((name) => run("schema", { name }));
446
+ const contract = app.command("contract");
447
+ const activity = app.command("activity");
448
+ activity
449
+ .command("validate <file>")
450
+ .description("Validate an activity manifest and inventory source files; no rendering or execution")
451
+ .action((file) => run("activity_inspect", { path: file }));
452
+ contract
453
+ .command("validate <file>")
454
+ .action((file) => run("contract_validate", { path: file }));
455
+ app
456
+ .command("mcp")
457
+ .command("serve")
458
+ .option("--allow-write", "Allow explicit reviewed local changes")
459
+ .option("--allow-execute", "Allow explicit customer-side source execution; requires --allow-write")
460
+ .option("--allow-network", "Allow GitHub package version lookup and fetching; no platform access")
461
+ .action(async (command) => {
462
+ const { serve } = await import("../mcp/index.js");
463
+ await serve(resolve(opts().project), !!command.allowWrite, context().environment, !!command.allowNetwork, !!command.allowExecute);
464
+ });
465
+ workflowCommands(app, {
466
+ context,
467
+ run,
468
+ author,
469
+ print,
470
+ readData,
471
+ canPrompt,
472
+ answer,
473
+ perform: (operation, args, label) => backgroundOperation(context(), operation, args, label, presentation()),
474
+ });
475
+ app.hook("preAction", (_root, command) => {
476
+ if (!opts().dryRun)
477
+ return;
478
+ const parts = [];
479
+ for (let current = command; current && current !== app; current = current.parent)
480
+ parts.unshift(current.name());
481
+ check(new Set([
482
+ "init",
483
+ "source add",
484
+ "source configure",
485
+ "source prepare",
486
+ "source import",
487
+ "plugin configure",
488
+ "plugin migrate",
489
+ "config environment add",
490
+ "flow table add",
491
+ "flow table configure",
492
+ "flow step add",
493
+ "flow output add",
494
+ "advanced export",
495
+ "flow add",
496
+ "flow configure",
497
+ "step add",
498
+ "table add",
499
+ "table configure",
500
+ "dataset add",
501
+ "config set",
502
+ "config fill",
503
+ "environments add",
504
+ "providers migrate",
505
+ "providers export",
506
+ "contract draft",
507
+ "plugin scaffold",
508
+ "plugin pack-add",
509
+ ]).has(parts.join(" ")), "OPTION", "--dry-run is supported only by project authoring commands; this command has not run");
510
+ });
511
+ async function parseCommands() {
512
+ const parsed = app.parseOptions(process.argv.slice(2));
513
+ const requested = [...parsed.operands, ...parsed.unknown].find((arg) => !arg.startsWith("-"));
514
+ const known = new Set(app.commands.flatMap((command) => [command.name(), ...command.aliases()]));
515
+ if (existsSync(resolve(context().root, "project.yaml"))) {
516
+ const catalogue = execute(context(), "plugin_commands", {});
517
+ if (!catalogue.ok) {
518
+ if (requested && !known.has(requested)) {
519
+ print(catalogue);
520
+ return;
521
+ }
522
+ }
523
+ else {
524
+ for (const binding of catalogue.result.bindings) {
525
+ check(!known.has(binding.namespace), "COMMAND_NAMESPACE", `Plugin namespace conflicts with core command ${binding.namespace}`);
526
+ let namespace = app.commands.find((command) => command.name() === binding.namespace);
527
+ if (namespace)
528
+ continue; // Same locked package, multiple selectable configurations.
529
+ namespace = app
530
+ .command(binding.namespace)
531
+ .description(`Installed ${binding.provider} offline operations`);
532
+ for (const definition of binding.commands) {
533
+ const words = definition.name.split(" ");
534
+ let parent = namespace;
535
+ for (const word of words.slice(0, -1)) {
536
+ let group = parent.commands.find((command) => command.name() === word);
537
+ if (!group)
538
+ group = parent.command(word).description(`${word} operations`);
539
+ parent = group;
540
+ }
541
+ parent
542
+ .command(words.at(-1))
543
+ .description(definition.description)
544
+ .option("--target <configuration>", "Provider configuration; required when multiple bindings match")
545
+ .option("--input <file>", "Project-relative JSON input file")
546
+ .option("--out <file>", "Save result JSON to a new project file")
547
+ .addHelpText("after", `\nInput schema:\n${JSON.stringify(definition.inputSchema, null, 2)}\n${(definition.examples ?? []).join("\n")}`)
548
+ .action((options) => providerAction("plugin_command", {
549
+ namespace: binding.namespace,
550
+ command: definition.name,
551
+ ...(options.target ? { target: options.target } : {}),
552
+ }, options));
553
+ }
554
+ }
555
+ }
556
+ }
557
+ await app.parseAsync();
558
+ }
559
+ await parseCommands().catch((error) => {
560
+ if (error.code === "commander.helpDisplayed" ||
561
+ error.code === "commander.version" ||
562
+ error.code === "commander.help") {
563
+ process.exitCode = 0;
564
+ if (process.argv.includes("--json"))
565
+ print({
566
+ apiVersion: "ingestron.operation/v1",
567
+ operation: error.code === "commander.version" ? "version" : "help",
568
+ ok: true,
569
+ result: { text: helpText },
570
+ diagnostics: [],
571
+ });
572
+ return;
573
+ }
574
+ print({
575
+ apiVersion: "ingestron.operation/v1",
576
+ operation: "command",
577
+ ok: false,
578
+ diagnostics: [
579
+ {
580
+ code: error instanceof Problem ? error.code : "COMMAND",
581
+ message: error instanceof Error
582
+ ? error.message.replace(/^error: /, "")
583
+ : "Command failed",
584
+ ...(error instanceof Problem && error.hint ? { hint: error.hint } : {}),
585
+ },
586
+ ],
587
+ });
588
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /** Keep package-network work off the presentation thread so progress reflects
2
+ * actual pending work. This worker uses the same JSON operations as MCP/Web. */
3
+ import { parentPort, workerData } from "node:worker_threads";
4
+ import { execute } from "@ingestron/core";
5
+ parentPort.postMessage(execute(workerData.context, workerData.operation, workerData.args));
@@ -0,0 +1,3 @@
1
+ import type { Result } from "@ingestron/core";
2
+ export declare function exitCode(result: Result): number;
3
+ export declare function terminal(result: Result, verbose?: boolean, args?: any): string;