@farmslot/recipe-cli 0.2.0 → 0.3.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +13 -9
  3. package/dist/harness/adapters.d.ts +25 -0
  4. package/dist/harness/adapters.d.ts.map +1 -0
  5. package/dist/harness/adapters.js +87 -0
  6. package/dist/harness/adapters.js.map +1 -0
  7. package/dist/harness/artifact-files.d.ts +11 -0
  8. package/dist/harness/artifact-files.d.ts.map +1 -0
  9. package/dist/harness/artifact-files.js +75 -0
  10. package/dist/harness/artifact-files.js.map +1 -0
  11. package/dist/harness/checkout-lock.d.ts +13 -0
  12. package/dist/harness/checkout-lock.d.ts.map +1 -0
  13. package/dist/harness/checkout-lock.js +211 -0
  14. package/dist/harness/checkout-lock.js.map +1 -0
  15. package/dist/harness/cli-color.d.ts +25 -0
  16. package/dist/harness/cli-color.d.ts.map +1 -0
  17. package/dist/harness/cli-color.js +101 -0
  18. package/dist/harness/cli-color.js.map +1 -0
  19. package/dist/harness/command-journal.d.ts +25 -0
  20. package/dist/harness/command-journal.d.ts.map +1 -0
  21. package/dist/harness/command-journal.js +322 -0
  22. package/dist/harness/command-journal.js.map +1 -0
  23. package/dist/harness/commands/last.d.ts +3 -0
  24. package/dist/harness/commands/last.d.ts.map +1 -0
  25. package/dist/harness/commands/last.js +59 -0
  26. package/dist/harness/commands/last.js.map +1 -0
  27. package/dist/harness/commands/launch.d.ts +2 -0
  28. package/dist/harness/commands/launch.d.ts.map +1 -0
  29. package/dist/harness/commands/launch.js +105 -0
  30. package/dist/harness/commands/launch.js.map +1 -0
  31. package/dist/harness/commands/reload.d.ts +2 -0
  32. package/dist/harness/commands/reload.d.ts.map +1 -0
  33. package/dist/harness/commands/reload.js +24 -0
  34. package/dist/harness/commands/reload.js.map +1 -0
  35. package/dist/harness/commands/stop.d.ts +9 -0
  36. package/dist/harness/commands/stop.d.ts.map +1 -0
  37. package/dist/harness/commands/stop.js +60 -0
  38. package/dist/harness/commands/stop.js.map +1 -0
  39. package/dist/harness/heal-bounds.d.ts +15 -0
  40. package/dist/harness/heal-bounds.d.ts.map +1 -0
  41. package/dist/harness/heal-bounds.js +194 -0
  42. package/dist/harness/heal-bounds.js.map +1 -0
  43. package/dist/harness/host.d.ts +37 -0
  44. package/dist/harness/host.d.ts.map +1 -0
  45. package/dist/harness/host.js +48 -0
  46. package/dist/harness/host.js.map +1 -0
  47. package/dist/harness/index.d.ts +19 -0
  48. package/dist/harness/index.d.ts.map +1 -0
  49. package/dist/harness/index.js +22 -0
  50. package/dist/harness/index.js.map +1 -0
  51. package/dist/harness/json-stream.d.ts +19 -0
  52. package/dist/harness/json-stream.d.ts.map +1 -0
  53. package/dist/harness/json-stream.js +59 -0
  54. package/dist/harness/json-stream.js.map +1 -0
  55. package/dist/harness/leaf-invoke.d.ts +8 -0
  56. package/dist/harness/leaf-invoke.d.ts.map +1 -0
  57. package/dist/harness/leaf-invoke.js +31 -0
  58. package/dist/harness/leaf-invoke.js.map +1 -0
  59. package/dist/harness/library-provenance.d.ts +3 -0
  60. package/dist/harness/library-provenance.d.ts.map +1 -0
  61. package/dist/harness/library-provenance.js +31 -0
  62. package/dist/harness/library-provenance.js.map +1 -0
  63. package/dist/harness/overlay.d.ts +17 -0
  64. package/dist/harness/overlay.d.ts.map +1 -0
  65. package/dist/harness/overlay.js +415 -0
  66. package/dist/harness/overlay.js.map +1 -0
  67. package/dist/harness/parse-args.d.ts +35 -0
  68. package/dist/harness/parse-args.d.ts.map +1 -0
  69. package/dist/harness/parse-args.js +249 -0
  70. package/dist/harness/parse-args.js.map +1 -0
  71. package/dist/harness/paths.d.ts +15 -0
  72. package/dist/harness/paths.d.ts.map +1 -0
  73. package/dist/harness/paths.js +29 -0
  74. package/dist/harness/paths.js.map +1 -0
  75. package/dist/harness/shared.d.ts +43 -0
  76. package/dist/harness/shared.d.ts.map +1 -0
  77. package/dist/harness/shared.js +414 -0
  78. package/dist/harness/shared.js.map +1 -0
  79. package/package.json +9 -2
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ All notable changes to `@farmslot/recipe-cli` are tracked here.
6
6
 
7
7
  - Active-development baseline; add user-facing changes here before release or package publication.
8
8
 
9
+ ## 0.3.0 - 2026-10-04
10
+
11
+ - Add the lifecycle commands to `@farmslot/recipe-cli/harness`, moved from mm-harness and driven through `@farmslot/adapter-sdk` adapters the host registers (`configureHarnessAdapters`): `handleLaunch`, `handleReload`, `handleStop` (with a `companions` hook for host processes), `handleLast`, and `handleHarness` for `install`/`verify`/`cleanup` (with `usage` and `install`-variant hooks). They come with their shared modules: adapter detection and resolution (`detectAdapter`, `adapterForPlatform`, `resolveAdapter`, `assertAdapter`), option parsing (`parseArgs`, `parseFlags`, `CliError`), leaf spawning (`spawnScript`, `spawnScriptStreaming`, `spawnInherit`), teaching errors (`usageOut`, `checkoutBusyOut`, `EXIT`) and bounded healing (`parseHeal`, `ensureOverlay`, `classifyFailure`, `checkHealBounds`). Platform policy comes from the adapters (`detect`, `targets`, `flags`, `failurePatterns`), and names from the host (new `product` field, required).
12
+ - Add `@farmslot/recipe-cli/harness`, the generic support a harness CLI runs its commands on: the host identity (`configureHarnessHost`: name, env prefix, package, executable, journaled commands), runtime paths (`recipeRuntimeDir`, `recipeRuntimePath`, `recipeHarnessRoot`, `recipeHarnessPath`, `harnessExecutable`), the resumability journal (`withCommandJournal`, `readCommandJournal`, argument redaction), the checkout lock (`acquireCheckoutLock`, `trackCheckoutChild`), `JsonStreamWriter`, colour helpers, contained artifact writes, Git library provenance and shell-leaf invocation. Moved from mm-harness; a product preset keeps its own env names (`MM_HARNESS_*`) through the host identity.
13
+ - Publish with protocol 0.34.0, recipe-runner 0.23.0, agent-runtime 0.17.0 and adapter-sdk 0.2.0 (agent-runtime and adapter-sdk are new dependencies of the harness support).
14
+
9
15
  ## 0.2.0 - 2026-10-04
10
16
 
11
17
  - Depend on `@farmslot/recipe-runner` (renamed from `@farmslot/recipe-harness`).
package/README.md CHANGED
@@ -29,15 +29,17 @@ Docs: https://farmslot.io/docs/reference/recipe-discovery
29
29
 
30
30
  ## Source layout
31
31
 
32
- | path | owns |
33
- | --------------------------------------------------- | --------------------------------------------------------------------------- |
34
- | `src/cli.ts` | the `farmslot-recipe` program: the runner's `run`/`validate` plus discovery |
35
- | `src/commands.ts` | discovery command registration, JSON envelopes and errors |
36
- | `src/libraries.ts` | library resolution: precedence, overrides, digests, `requires` checks |
37
- | `src/discovery-index.ts` | the recipe and action index for one platform view |
38
- | `src/composition.ts` | composition, callers and `explain` |
39
- | `src/search.ts`, `src/template.ts`, `src/render.ts` | search ranking, skeletons, text output |
40
- | `src/types.ts` | the documented `--json` schema |
32
+ | path | owns |
33
+ | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `src/cli.ts` | the `farmslot-recipe` program: the runner's `run`/`validate` plus discovery |
35
+ | `src/commands.ts` | discovery command registration, JSON envelopes and errors |
36
+ | `src/libraries.ts` | library resolution: precedence, overrides, digests, `requires` checks |
37
+ | `src/discovery-index.ts` | the recipe and action index for one platform view |
38
+ | `src/composition.ts` | composition, callers and `explain` |
39
+ | `src/search.ts`, `src/template.ts`, `src/render.ts` | search ranking, skeletons, text output |
40
+ | `src/types.ts` | the documented `--json` schema |
41
+ | `src/harness/` | generic harness support (`@farmslot/recipe-cli/harness`): host identity, adapter registry, runtime paths, journal, checkout lock, JSON stream, colour, option parsing, leaf spawning, bounded healing |
42
+ | `src/harness/commands/` | lifecycle commands a host CLI dispatches to: `launch`, `reload`, `stop`, `last`; `overlay.ts` is `install`/`verify`/`cleanup` |
41
43
 
42
44
  ## Maintenance rules
43
45
 
@@ -45,6 +47,8 @@ Docs: https://farmslot.io/docs/reference/recipe-discovery
45
47
  - Library loading, recipe identity and precedence belong to `@farmslot/recipe-runner`; this package reads them and must not re-implement them.
46
48
  - `--json` envelopes are a contract: add fields freely, but bump `DISCOVERY_SCHEMA_VERSION` before removing or renaming one.
47
49
  - Discovery never runs actions or loads adapter code.
50
+ - Harness commands never compare adapter ids: platform behaviour comes from the registered `@farmslot/adapter-sdk` adapters, and a new behaviour is a new adapter member.
51
+ - `src/harness/` never hardcodes a product: names and env variables come from the host identity (`configureHarnessHost`), so a preset such as `mm-harness` keeps its own spelling. Runtime paths are shared by every host (`temp/recipe/runtime`, `temp/recipe/harness`; `RECIPE_RUNTIME_DIR` and `RECIPE_HARNESS_ROOT` override them).
48
52
 
49
53
  ## Local quality
50
54
 
@@ -0,0 +1,25 @@
1
+ import { type AdapterRegistry, type PlatformAdapter } from '@farmslot/adapter-sdk';
2
+ /** Set the registry the generic commands use. Call once, before any command runs. */
3
+ export declare function configureHarnessAdapters(next: AdapterRegistry): void;
4
+ export declare function harnessAdapters(): AdapterRegistry;
5
+ export declare function harnessAdapter(id: string): PlatformAdapter;
6
+ /**
7
+ * The adapter whose checkout `target` is, or undefined. Any adapter's remote
8
+ * match beats any adapter's file match; within a pass, registration order decides.
9
+ */
10
+ export declare function detectAdapter(target: string): string | undefined;
11
+ /** The adapter a `--platform` value or positional target names: its own id, or one of its `targets`. */
12
+ export declare function adapterForPlatform(value: string | undefined): string | undefined;
13
+ /** Whether `value` is one of an adapter's positional platform targets. */
14
+ export declare function isPlatformTarget(value: string | undefined): value is string;
15
+ /** Both escapes from a failed detection: where to run, and how to force it. */
16
+ export declare function adapterDetectNext(): string;
17
+ export declare function undetectedAdapterMessage(target: string): string;
18
+ export declare function assertAdaptersRegistered(): void;
19
+ /** Throws unless `adapter` is a registered id. An empty registry is an internal error. */
20
+ export declare function assertAdapter(adapter: unknown): asserts adapter is string;
21
+ /** Extra dev-server port environment names every registered adapter reads. */
22
+ export declare function adapterPortEnv(): string[];
23
+ /** Boolean flags every registered adapter adds, for `launch` or for the other commands. */
24
+ export declare function adapterFlags(kind: 'launch' | 'commands'): string[];
25
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../src/harness/adapters.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAM/B,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAEpE;AAED,wBAAgB,eAAe,IAAI,eAAe,CAEjD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,CAE1D;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoBhE;AAED,wGAAwG;AACxG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGhF;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAE3E;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAM/C;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAQzE;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,MAAM,EAAE,CAElE"}
@@ -0,0 +1,87 @@
1
+ // The host's platform adapters: a product preset registers its adapters here,
2
+ // and every generic command resolves platform behaviour through them. No
3
+ // command compares adapter ids.
4
+ import { execFileSync } from 'node:child_process';
5
+ import { createAdapterRegistry, } from '@farmslot/adapter-sdk';
6
+ import { harnessHost } from './host.js';
7
+ let registry = createAdapterRegistry();
8
+ /** Set the registry the generic commands use. Call once, before any command runs. */
9
+ export function configureHarnessAdapters(next) {
10
+ registry = next;
11
+ }
12
+ export function harnessAdapters() {
13
+ return registry;
14
+ }
15
+ export function harnessAdapter(id) {
16
+ return registry.get(id);
17
+ }
18
+ function registered() {
19
+ return registry.list().map((id) => registry.get(id));
20
+ }
21
+ /**
22
+ * The adapter whose checkout `target` is, or undefined. Any adapter's remote
23
+ * match beats any adapter's file match; within a pass, registration order decides.
24
+ */
25
+ export function detectAdapter(target) {
26
+ // An empty registry would detect nothing for every checkout; that is a host
27
+ // wiring error, not an unknown checkout.
28
+ assertAdaptersRegistered();
29
+ let remote = '';
30
+ try {
31
+ remote = execFileSync('git', ['-C', target, 'config', '--get', 'remote.origin.url'], {
32
+ encoding: 'utf8',
33
+ stdio: ['ignore', 'pipe', 'ignore'],
34
+ }).trim();
35
+ }
36
+ catch {
37
+ // Not a Git checkout, or no origin: only the file checks apply.
38
+ remote = '';
39
+ }
40
+ const adapters = registered();
41
+ if (remote) {
42
+ const byRemote = adapters.find((adapter) => adapter.detect?.remote?.(remote));
43
+ if (byRemote)
44
+ return byRemote.id;
45
+ }
46
+ return adapters.find((adapter) => adapter.detect?.files?.(target))?.id;
47
+ }
48
+ /** The adapter a `--platform` value or positional target names: its own id, or one of its `targets`. */
49
+ export function adapterForPlatform(value) {
50
+ if (value === undefined)
51
+ return undefined;
52
+ return registered().find((adapter) => adapter.targets?.includes(value))?.id ?? value;
53
+ }
54
+ /** Whether `value` is one of an adapter's positional platform targets. */
55
+ export function isPlatformTarget(value) {
56
+ return value !== undefined && registered().some((adapter) => adapter.targets?.includes(value));
57
+ }
58
+ /** Both escapes from a failed detection: where to run, and how to force it. */
59
+ export function adapterDetectNext() {
60
+ return `cd into a ${harnessHost().product} checkout or pass --target <path>, or force it with --adapter <${registry.list().join('|')}>`;
61
+ }
62
+ export function undetectedAdapterMessage(target) {
63
+ return `could not detect the ${harnessHost().product} repo type for ${target}`;
64
+ }
65
+ export function assertAdaptersRegistered() {
66
+ if (registry.list().length === 0) {
67
+ throw new Error('internal: no adapters are registered yet; the host registers its adapters before resolving one');
68
+ }
69
+ }
70
+ /** Throws unless `adapter` is a registered id. An empty registry is an internal error. */
71
+ export function assertAdapter(adapter) {
72
+ assertAdaptersRegistered();
73
+ const ids = registry.list();
74
+ if (typeof adapter !== 'string' || !registry.has(adapter)) {
75
+ const choices = ids.length > 1 ? `${ids.slice(0, -1).join(', ')}, or ${ids.at(-1)}` : ids.join('');
76
+ throw new Error(`Adapter must be ${choices}.`);
77
+ }
78
+ }
79
+ /** Extra dev-server port environment names every registered adapter reads. */
80
+ export function adapterPortEnv() {
81
+ return [...new Set(registered().flatMap((adapter) => [...(adapter.devServer.portEnv ?? [])]))];
82
+ }
83
+ /** Boolean flags every registered adapter adds, for `launch` or for the other commands. */
84
+ export function adapterFlags(kind) {
85
+ return registered().flatMap((adapter) => [...(adapter.flags?.[kind] ?? [])]);
86
+ }
87
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/harness/adapters.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface ArtifactManifestEntry {
2
+ category?: string;
3
+ label: string;
4
+ path: string;
5
+ type: string;
6
+ metadata?: Record<string, unknown>;
7
+ }
8
+ export declare function writeContainedArtifact(artifactsDir: string, relativePath: string, value: string | Uint8Array, label: string): Promise<void>;
9
+ export declare function readContainedJsonArtifact<T>(artifactsDir: string, relativePath: string, maxBytes: number, label: string): Promise<T>;
10
+ export declare function indexArtifactManifest(manifestPath: string, entries: readonly ArtifactManifestEntry[]): Promise<void>;
11
+ //# sourceMappingURL=artifact-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-files.d.ts","sourceRoot":"","sources":["../../src/harness/artifact-files.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GAAG,UAAU,EAC1B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,SAAS,qBAAqB,EAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAaf"}
@@ -0,0 +1,75 @@
1
+ import { constants as fsConstants } from 'node:fs';
2
+ import { lstat, mkdir, open, readFile, realpath, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ export async function writeContainedArtifact(artifactsDir, relativePath, value, label) {
5
+ const file = resolveContainedArtifact(artifactsDir, relativePath, label);
6
+ await mkdir(path.dirname(file), { recursive: true });
7
+ await assertContainedParent(artifactsDir, file, label);
8
+ await assertNotSymlink(file, label);
9
+ const handle = await open(file, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_TRUNC | fsConstants.O_NOFOLLOW, 0o600);
10
+ try {
11
+ await handle.writeFile(value);
12
+ }
13
+ finally {
14
+ await handle.close();
15
+ }
16
+ }
17
+ export async function readContainedJsonArtifact(artifactsDir, relativePath, maxBytes, label) {
18
+ const file = resolveContainedArtifact(artifactsDir, relativePath, label);
19
+ await assertContainedParent(artifactsDir, file, label);
20
+ await assertNotSymlink(file, label);
21
+ const handle = await open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
22
+ try {
23
+ const info = await handle.stat();
24
+ if (!info.isFile() || info.size > maxBytes) {
25
+ throw new Error(`${label} is not a bounded regular file.`);
26
+ }
27
+ return JSON.parse(await handle.readFile('utf8'));
28
+ }
29
+ finally {
30
+ await handle.close();
31
+ }
32
+ }
33
+ export async function indexArtifactManifest(manifestPath, entries) {
34
+ const parsed = JSON.parse(await readFile(manifestPath, 'utf8'));
35
+ const manifest = asRecord(parsed);
36
+ const replacementPaths = new Set(entries.map((entry) => entry.path));
37
+ const current = Array.isArray(manifest.artifacts)
38
+ ? manifest.artifacts.filter((artifact) => !replacementPaths.has(String(asRecord(artifact).path ?? '')))
39
+ : [];
40
+ await writeFile(manifestPath, `${JSON.stringify({ ...manifest, artifacts: [...current, ...entries] }, null, 2)}\n`);
41
+ }
42
+ function resolveContainedArtifact(artifactsDir, relativePath, label) {
43
+ const root = path.resolve(artifactsDir);
44
+ const file = path.resolve(root, relativePath);
45
+ const relative = path.relative(root, file);
46
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
47
+ throw new Error(`${label} path must stay inside artifactsDir.`);
48
+ }
49
+ return file;
50
+ }
51
+ async function assertContainedParent(artifactsDir, file, label) {
52
+ const root = await realpath(artifactsDir);
53
+ const parent = await realpath(path.dirname(file));
54
+ const relative = path.relative(root, parent);
55
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
56
+ throw new Error(`${label} parent must stay inside artifactsDir.`);
57
+ }
58
+ }
59
+ async function assertNotSymlink(file, label) {
60
+ try {
61
+ if ((await lstat(file)).isSymbolicLink()) {
62
+ throw new Error(`${label} must not be a symbolic link.`);
63
+ }
64
+ }
65
+ catch (error) {
66
+ if (error.code !== 'ENOENT')
67
+ throw error;
68
+ }
69
+ }
70
+ function asRecord(value) {
71
+ return value && typeof value === 'object' && !Array.isArray(value)
72
+ ? value
73
+ : {};
74
+ }
75
+ //# sourceMappingURL=artifact-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-files.js","sourceRoot":"","sources":["../../src/harness/artifact-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface CheckoutLock {
2
+ path: string;
3
+ release: () => void;
4
+ }
5
+ export interface CheckoutLockFailure {
6
+ path: string;
7
+ owner: Record<string, unknown> | null;
8
+ message: string;
9
+ }
10
+ export declare function acquireCheckoutLock(target: string, operation: string): CheckoutLock | CheckoutLockFailure;
11
+ /** Each nested launcher records its child independently under the inherited lock. */
12
+ export declare function trackCheckoutChild(target: string, pid: number, ownsProcessGroup?: boolean): () => void;
13
+ //# sourceMappingURL=checkout-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-lock.d.ts","sourceRoot":"","sources":["../../src/harness/checkout-lock.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,YAAY,GAAG,mBAAmB,CA8EpC;AA+FD,qFAAqF;AACrF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,gBAAgB,UAAQ,GACvB,MAAM,IAAI,CAiCZ"}
@@ -0,0 +1,211 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { ensureRuntimeDirectory, processIdentity } from '@farmslot/recipe-runner/runtime/operation';
5
+ import { hostEnvName } from './host.js';
6
+ import { recipeRuntimeDir } from './paths.js';
7
+ const lockTokenEnv = () => hostEnvName('CHECKOUT_LOCK_TOKEN');
8
+ export function acquireCheckoutLock(target, operation) {
9
+ const repoRoot = fs.realpathSync(target);
10
+ const lockPath = path.join(repoRoot, recipeRuntimeDir(), 'sandbox.lock');
11
+ ensureRuntimeDirectory(path.dirname(lockPath));
12
+ const tokenEnv = lockTokenEnv();
13
+ const inheritedToken = process.env[tokenEnv];
14
+ for (let attempt = 0; attempt < 4; attempt += 1) {
15
+ try {
16
+ const identity = processIdentity(process.pid);
17
+ if (!identity)
18
+ throw new Error('Cannot identify checkout lock owner process');
19
+ const fd = fs.openSync(lockPath, 'wx', 0o600);
20
+ fs.fchmodSync(fd, 0o600);
21
+ const token = randomUUID();
22
+ const owner = {
23
+ schemaVersion: 1,
24
+ pid: process.pid,
25
+ processStartedAt: identity,
26
+ operation,
27
+ repoRoot,
28
+ token,
29
+ startedAt: new Date().toISOString(),
30
+ };
31
+ fs.writeFileSync(fd, `${JSON.stringify(owner, null, 2)}\n`);
32
+ fs.closeSync(fd);
33
+ process.env[tokenEnv] = token;
34
+ return {
35
+ path: lockPath,
36
+ release: () => {
37
+ const current = readOwner(lockPath);
38
+ if (current?.pid === process.pid && current.token === token && !childrenAlive(current)) {
39
+ fs.rmSync(lockPath, { force: true });
40
+ fs.rmSync(path.join(path.dirname(lockPath), 'lock-members', token), {
41
+ recursive: true,
42
+ force: true,
43
+ });
44
+ }
45
+ if (process.env[tokenEnv] === token) {
46
+ if (inheritedToken)
47
+ process.env[tokenEnv] = inheritedToken;
48
+ else
49
+ delete process.env[tokenEnv];
50
+ }
51
+ },
52
+ };
53
+ }
54
+ catch (error) {
55
+ if (error.code !== 'EEXIST')
56
+ throw error;
57
+ const owner = readOwner(lockPath);
58
+ if (inheritedToken &&
59
+ owner?.token === inheritedToken &&
60
+ owner.repoRoot === repoRoot &&
61
+ typeof owner.pid === 'number' &&
62
+ ownerAlive(owner)) {
63
+ trackCheckoutChild(repoRoot, process.pid);
64
+ return { path: lockPath, release: () => undefined };
65
+ }
66
+ if (owner && typeof owner.pid === 'number' && ownerAlive(owner)) {
67
+ return {
68
+ path: lockPath,
69
+ owner,
70
+ message: `checkout sandbox is busy with ${String(owner.operation ?? 'another operation')} (pid ${owner.pid}).`,
71
+ };
72
+ }
73
+ if (!owner || childrenAlive(owner)) {
74
+ return {
75
+ path: lockPath,
76
+ owner,
77
+ message: `checkout sandbox owner is unreadable or has surviving child processes. Inspect ${lockPath} and its lock-members directory; remove the lock only after verifying all recorded processes have exited.`,
78
+ };
79
+ }
80
+ claimStaleLock(lockPath, owner);
81
+ }
82
+ }
83
+ return {
84
+ path: lockPath,
85
+ owner: readOwner(lockPath),
86
+ message: 'checkout sandbox lock could not be acquired.',
87
+ };
88
+ }
89
+ function claimStaleLock(lockPath, expectedOwner) {
90
+ const claimedPath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
91
+ try {
92
+ fs.renameSync(lockPath, claimedPath);
93
+ }
94
+ catch (error) {
95
+ if (error.code === 'ENOENT')
96
+ return;
97
+ throw error;
98
+ }
99
+ try {
100
+ const claimedOwner = readOwner(claimedPath);
101
+ if (!sameOwner(claimedOwner, expectedOwner) ||
102
+ (claimedOwner &&
103
+ typeof claimedOwner.pid === 'number' &&
104
+ (ownerAlive(claimedOwner) || childrenAlive(claimedOwner)))) {
105
+ try {
106
+ // Restore only when nobody else acquired the now-empty canonical path.
107
+ // linkSync is atomic and never overwrites a newer owner's lock.
108
+ fs.linkSync(claimedPath, lockPath);
109
+ }
110
+ catch (error) {
111
+ if (error.code !== 'EEXIST')
112
+ throw error;
113
+ }
114
+ }
115
+ else if (typeof claimedOwner?.token === 'string' &&
116
+ /^[a-f0-9-]{36}$/.test(claimedOwner.token)) {
117
+ fs.rmSync(path.join(path.dirname(lockPath), 'lock-members', claimedOwner.token), {
118
+ recursive: true,
119
+ force: true,
120
+ });
121
+ }
122
+ }
123
+ finally {
124
+ fs.rmSync(claimedPath, { force: true });
125
+ }
126
+ }
127
+ function sameOwner(left, right) {
128
+ if (!left || !right)
129
+ return left === right;
130
+ return (left.pid === right.pid &&
131
+ left.token === right.token &&
132
+ left.startedAt === right.startedAt &&
133
+ left.repoRoot === right.repoRoot);
134
+ }
135
+ function readOwner(lockPath) {
136
+ try {
137
+ const value = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
138
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ function ownerAlive(owner) {
145
+ if (typeof owner.pid !== 'number')
146
+ return true;
147
+ if (typeof owner.processStartedAt === 'string')
148
+ return processIdentity(owner.pid) === owner.processStartedAt;
149
+ return processAlive(owner.pid);
150
+ }
151
+ function childrenAlive(owner) {
152
+ if (typeof owner.repoRoot !== 'string' ||
153
+ typeof owner.token !== 'string' ||
154
+ !/^[a-f0-9-]{36}$/.test(owner.token))
155
+ return false;
156
+ const directory = path.join(owner.repoRoot, recipeRuntimeDir(), 'lock-members', owner.token);
157
+ if (!fs.existsSync(directory))
158
+ return false;
159
+ return fs
160
+ .readdirSync(directory)
161
+ .filter((name) => name.endsWith('.json'))
162
+ .some((name) => {
163
+ const child = readOwner(path.join(directory, name));
164
+ if (child && typeof child.processGroupId === 'number' && child.processGroupId > 0)
165
+ return processAlive(-child.processGroupId);
166
+ // An incomplete membership write is uncertain ownership, never permission to reclaim.
167
+ return (!child ||
168
+ typeof child.pid !== 'number' ||
169
+ typeof child.processStartedAt !== 'string' ||
170
+ (child.pid !== owner.pid && processIdentity(child.pid) === child.processStartedAt));
171
+ });
172
+ }
173
+ /** Each nested launcher records its child independently under the inherited lock. */
174
+ export function trackCheckoutChild(target, pid, ownsProcessGroup = false) {
175
+ const repoRoot = fs.realpathSync(target);
176
+ const lockPath = path.join(repoRoot, recipeRuntimeDir(), 'sandbox.lock');
177
+ const owner = readOwner(lockPath);
178
+ if (!owner ||
179
+ owner.token !== process.env[lockTokenEnv()] ||
180
+ typeof owner.token !== 'string' ||
181
+ !/^[a-f0-9-]{36}$/.test(owner.token))
182
+ return () => undefined;
183
+ const identity = processIdentity(pid);
184
+ if (!identity && !ownsProcessGroup)
185
+ return () => undefined;
186
+ const directory = path.join(repoRoot, recipeRuntimeDir(), 'lock-members', owner.token);
187
+ ensureRuntimeDirectory(directory);
188
+ const file = path.join(directory, `${pid}.json`);
189
+ const previous = readOwner(file);
190
+ const group = ownsProcessGroup ||
191
+ (previous?.processStartedAt === identity && previous?.processGroupId === pid);
192
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
193
+ fs.writeFileSync(temporary, JSON.stringify({ pid, processStartedAt: identity, ...(group ? { processGroupId: pid } : {}) }), { mode: 0o600 });
194
+ fs.chmodSync(temporary, 0o600);
195
+ fs.renameSync(temporary, file);
196
+ return () => {
197
+ const current = readOwner(file);
198
+ if (current?.pid === pid && current.processStartedAt === identity)
199
+ fs.rmSync(file, { force: true });
200
+ };
201
+ }
202
+ function processAlive(pid) {
203
+ try {
204
+ process.kill(pid, 0);
205
+ return true;
206
+ }
207
+ catch (error) {
208
+ return error.code === 'EPERM';
209
+ }
210
+ }
211
+ //# sourceMappingURL=checkout-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-lock.js","sourceRoot":"","sources":["../../src/harness/checkout-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAEpG,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,YAAY,GAAG,GAAW,EAAE,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;AAatE,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,SAAiB;IAEjB,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,cAAc,CAAC,CAAC;IACzE,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAE7C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC9C,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;YAC9E,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC9C,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACzB,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG;gBACZ,aAAa,EAAE,CAAC;gBAChB,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,gBAAgB,EAAE,QAAQ;gBAC1B,SAAS;gBACT,QAAQ;gBACR,KAAK;gBACL,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YACF,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5D,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;YAC9B,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,GAAG,EAAE;oBACZ,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;oBACpC,IAAI,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;wBACvF,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;wBACrC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,EAAE;4BAClE,SAAS,EAAE,IAAI;4BACf,KAAK,EAAE,IAAI;yBACZ,CAAC,CAAC;oBACL,CAAC;oBACD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;wBACpC,IAAI,cAAc;4BAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC;;4BACtD,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;YACpE,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YAClC,IACE,cAAc;gBACd,KAAK,EAAE,KAAK,KAAK,cAAc;gBAC/B,KAAK,CAAC,QAAQ,KAAK,QAAQ;gBAC3B,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;gBAC7B,UAAU,CAAC,KAAK,CAAC,EACjB,CAAC;gBACD,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC1C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;YACtD,CAAC;YACD,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChE,OAAO;oBACL,IAAI,EAAE,QAAQ;oBACd,KAAK;oBACL,OAAO,EAAE,iCAAiC,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,mBAAmB,CAAC,SAAS,KAAK,CAAC,GAAG,IAAI;iBAC/G,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,KAAK,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACnC,OAAO;oBACL,IAAI,EAAE,QAAQ;oBACd,KAAK;oBACL,OAAO,EAAE,kFAAkF,QAAQ,2GAA2G;iBAC/M,CAAC;YACJ,CAAC;YACD,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC;QAC1B,OAAO,EAAE,8CAA8C;KACxD,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,aAA6C;IACrF,MAAM,WAAW,GAAG,GAAG,QAAQ,UAAU,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,EAAE,CAAC;IACvE,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QAC/D,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QAC5C,IACE,CAAC,SAAS,CAAC,YAAY,EAAE,aAAa,CAAC;YACvC,CAAC,YAAY;gBACX,OAAO,YAAY,CAAC,GAAG,KAAK,QAAQ;gBACpC,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,EAC5D,CAAC;YACD,IAAI,CAAC;gBACH,uEAAuE;gBACvE,gEAAgE;gBAChE,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;YACrC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,KAAK,CAAC;YACtE,CAAC;QACH,CAAC;aAAM,IACL,OAAO,YAAY,EAAE,KAAK,KAAK,QAAQ;YACvC,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAC1C,CAAC;YACD,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE;gBAC/E,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE,IAAI;aACZ,CAAC,CAAC;QACL,CAAC;IACH,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,IAAoC,EACpC,KAAqC;IAErC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,KAAK,KAAK,CAAC;IAC3C,OAAO,CACL,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG;QACtB,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK;QAC1B,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;QAClC,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CACjC,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB;IACjC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAA8B;IAChD,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,OAAO,KAAK,CAAC,gBAAgB,KAAK,QAAQ;QAC5C,OAAO,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,gBAAgB,CAAC;IAC/D,OAAO,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,aAAa,CAAC,KAA8B;IACnD,IACE,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAClC,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ;QAC/B,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAEpC,OAAO,KAAK,CAAC;IACf,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC7F,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,EAAE;SACN,WAAW,CAAC,SAAS,CAAC;SACtB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;SACxC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;QACpD,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,cAAc,KAAK,QAAQ,IAAI,KAAK,CAAC,cAAc,GAAG,CAAC;YAC/E,OAAO,YAAY,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC7C,sFAAsF;QACtF,OAAO,CACL,CAAC,KAAK;YACN,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;YAC7B,OAAO,KAAK,CAAC,gBAAgB,KAAK,QAAQ;YAC1C,CAAC,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,gBAAgB,CAAC,CACnF,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,GAAW,EACX,gBAAgB,GAAG,KAAK;IAExB,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,cAAc,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,IACE,CAAC,KAAK;QACN,KAAK,CAAC,KAAK,KAAK,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ;QAC/B,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;QAEpC,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IACzB,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,IAAI,CAAC,gBAAgB;QAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACvF,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,KAAK,GACT,gBAAgB;QAChB,CAAC,QAAQ,EAAE,gBAAgB,KAAK,QAAQ,IAAI,QAAQ,EAAE,cAAc,KAAK,GAAG,CAAC,CAAC;IAChF,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,MAAM,CAAC;IAC/D,EAAE,CAAC,aAAa,CACd,SAAS,EACT,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAC9F,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;IACF,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/B,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/B,OAAO,GAAG,EAAE;QACV,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,OAAO,EAAE,GAAG,KAAK,GAAG,IAAI,OAAO,CAAC,gBAAgB,KAAK,QAAQ;YAC/D,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAC;IAC3D,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * cli-color.ts — shared ANSI styling for recipe CLI human output.
3
+ *
4
+ * Honors NO_COLOR, RECIPE_NO_COLOR, FORCE_COLOR, RECIPE_COLOR, and TTY detection.
5
+ */
6
+ export declare function colorEnabled(stream?: NodeJS.WriteStream): boolean;
7
+ export declare function stripAnsi(text: string): string;
8
+ export declare function color(style: string, text: string, { stream }?: {
9
+ stream?: NodeJS.WriteStream;
10
+ }): string;
11
+ export declare function colorKv(label: string, value: string, valueStyle?: string, { stream }?: {
12
+ stream?: NodeJS.WriteStream;
13
+ }): string;
14
+ export declare function colorStatusWord(word: string, { stream }?: {
15
+ stream?: NodeJS.WriteStream;
16
+ }): string;
17
+ export declare function colorHumanMessage(message: string, { stream }?: {
18
+ stream?: NodeJS.WriteStream;
19
+ }): string;
20
+ export declare function classifyLogEvent(event: string): string;
21
+ export declare function colorLogEvent(event: string, { latest, stream, }?: {
22
+ latest?: boolean;
23
+ stream?: NodeJS.WriteStream;
24
+ }): string;
25
+ //# sourceMappingURL=cli-color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-color.d.ts","sourceRoot":"","sources":["../../src/harness/cli-color.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiCH,wBAAgB,YAAY,CAAC,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,OAAO,CAOjF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,KAAK,CACnB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,EAAE,MAAuB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAA;CAAO,GAChE,MAAM,CAMR;AAED,wBAAgB,OAAO,CACrB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,SAAS,EACnB,EAAE,MAAuB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAA;CAAO,GAChE,MAAM,CAER;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,EAAE,MAAuB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAA;CAAO,GAChE,MAAM,CAgBR;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,EAAE,MAAuB,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAA;CAAO,GAChE,MAAM,CAoBR;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBtD;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,EACE,MAAc,EACd,MAAuB,GACxB,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAA;CAAO,GACxD,MAAM,CAIR"}