@open-agent-toolkit/cli 0.2.5 → 0.2.7

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 (104) hide show
  1. package/assets/docs/cli-utilities/configuration.md +40 -0
  2. package/assets/docs/cli-utilities/tool-packs.md +31 -0
  3. package/assets/docs/workflows/projects/artifacts.md +27 -0
  4. package/assets/docs/workflows/skills/explainer-kit.md +125 -0
  5. package/assets/docs/workflows/skills/index.md +4 -0
  6. package/assets/migration/pjm-restructure.md +12 -0
  7. package/assets/public-package-versions.json +4 -4
  8. package/assets/skills/explainer-kit/SKILL.md +95 -0
  9. package/assets/skills/explainer-kit/examples/project-explainer/content.md +31 -0
  10. package/assets/skills/explainer-kit/examples/project-explainer/fact-base.md +22 -0
  11. package/assets/skills/explainer-kit/examples/project-recap/content.md +34 -0
  12. package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +22 -0
  13. package/assets/skills/explainer-kit/examples/theme-bundle.json +87 -0
  14. package/assets/skills/explainer-kit/palettes/ember.json +37 -0
  15. package/assets/skills/explainer-kit/palettes/forest.json +37 -0
  16. package/assets/skills/explainer-kit/palettes/neutral.json +37 -0
  17. package/assets/skills/explainer-kit/palettes/ocean.json +37 -0
  18. package/assets/skills/explainer-kit/palettes/violet.json +37 -0
  19. package/assets/skills/explainer-kit/profiles/clean.json +42 -0
  20. package/assets/skills/explainer-kit/profiles/editorial.json +42 -0
  21. package/assets/skills/explainer-kit/profiles/technical.json +39 -0
  22. package/assets/skills/explainer-kit/recipes/engineer-tour.json +33 -0
  23. package/assets/skills/explainer-kit/recipes/project-explainer.json +33 -0
  24. package/assets/skills/explainer-kit/recipes/project-recap.json +34 -0
  25. package/assets/skills/explainer-kit/references/contracts.md +101 -0
  26. package/assets/skills/explainer-kit/references/destination-contract.md +94 -0
  27. package/assets/skills/explainer-kit/references/extension-contract.md +71 -0
  28. package/assets/skills/explainer-kit/references/fact-base-contract.md +119 -0
  29. package/assets/skills/explainer-kit/schemas/build-record.schema.json +92 -0
  30. package/assets/skills/explainer-kit/schemas/durability-evidence.schema.json +53 -0
  31. package/assets/skills/explainer-kit/schemas/fact-base.schema.json +129 -0
  32. package/assets/skills/explainer-kit/schemas/manifest.schema.json +193 -0
  33. package/assets/skills/explainer-kit/schemas/publish-receipt.schema.json +97 -0
  34. package/assets/skills/explainer-kit/schemas/publish-request.schema.json +32 -0
  35. package/assets/skills/explainer-kit/schemas/run-request.schema.json +132 -0
  36. package/assets/skills/explainer-kit/schemas/theme.schema.json +217 -0
  37. package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +180 -0
  38. package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +680 -0
  39. package/assets/skills/explainer-kit/scripts/lib/durability.mjs +538 -0
  40. package/assets/skills/explainer-kit/scripts/lib/fact-base.mjs +494 -0
  41. package/assets/skills/explainer-kit/scripts/lib/fs-safe.mjs +218 -0
  42. package/assets/skills/explainer-kit/scripts/lib/qa.mjs +512 -0
  43. package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +289 -0
  44. package/assets/skills/explainer-kit/scripts/lib/records.mjs +231 -0
  45. package/assets/skills/explainer-kit/scripts/lib/render.mjs +485 -0
  46. package/assets/skills/explainer-kit/scripts/lib/s3-static.mjs +587 -0
  47. package/assets/skills/explainer-kit/scripts/lib/safe-paths.mjs +96 -0
  48. package/assets/skills/explainer-kit/scripts/lib/theme.mjs +332 -0
  49. package/assets/skills/explainer-kit/scripts/publish.mjs +76 -0
  50. package/assets/skills/explainer-kit/scripts/record-durability.mjs +64 -0
  51. package/assets/skills/explainer-kit/scripts/render-qa.mjs +273 -0
  52. package/assets/skills/explainer-kit/scripts/run.mjs +778 -0
  53. package/assets/skills/explainer-kit/scripts/validate.mjs +59 -0
  54. package/assets/skills/explainer-kit/templates/deck-shell.html +233 -0
  55. package/assets/skills/explainer-kit/templates/diagram-shell.html +219 -0
  56. package/assets/skills/explainer-kit/templates/engineer-tour.html +273 -0
  57. package/assets/skills/explainer-kit/templates/house-style.html +218 -0
  58. package/assets/skills/oat-explainer-kit/SKILL.md +92 -0
  59. package/assets/skills/oat-explainer-kit/references/config-contract.md +67 -0
  60. package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +143 -0
  61. package/assets/skills/oat-explainer-kit/references/migration.md +143 -0
  62. package/assets/skills/oat-explainer-kit/scripts/bind-project-sources.mjs +103 -0
  63. package/assets/skills/oat-explainer-kit/scripts/check-core.mjs +127 -0
  64. package/assets/skills/oat-explainer-kit/scripts/finalize-tracked-run.mjs +392 -0
  65. package/assets/skills/oat-explainer-kit/scripts/persist-intent.mjs +130 -0
  66. package/assets/skills/oat-explainer-kit/scripts/resolve-config.mjs +293 -0
  67. package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +209 -0
  68. package/assets/skills/oat-explainer-kit/scripts/resolve-paths.mjs +166 -0
  69. package/assets/skills/oat-explainer-kit/scripts/run.mjs +284 -0
  70. package/assets/skills/oat-project-autonomous/SKILL.md +22 -1
  71. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +5 -1
  72. package/assets/skills/oat-project-complete/SKILL.md +166 -28
  73. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +5 -1
  74. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  75. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +17 -0
  76. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +5 -1
  77. package/assets/skills/oat-project-plan/SKILL.md +30 -1
  78. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +5 -1
  79. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +5 -1
  80. package/assets/skills/oat-project-summary/SKILL.md +20 -1
  81. package/assets/skills/oat-wave-execute/SKILL.md +1 -1
  82. package/dist/commands/config/index.d.ts.map +1 -1
  83. package/dist/commands/config/index.js +232 -1
  84. package/dist/commands/init/tools/shared/skill-manifest.d.ts +2 -2
  85. package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
  86. package/dist/commands/init/tools/shared/skill-manifest.js +2 -0
  87. package/dist/commands/project/archive/archive-utils.d.ts +12 -0
  88. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  89. package/dist/commands/project/archive/archive-utils.js +320 -8
  90. package/dist/commands/project/archive/index.d.ts.map +1 -1
  91. package/dist/commands/project/archive/index.js +1 -0
  92. package/dist/commands/project/archive/push-runner.d.ts +1 -0
  93. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  94. package/dist/commands/project/archive/push-runner.js +9 -2
  95. package/dist/config/oat-config.d.ts +26 -0
  96. package/dist/config/oat-config.d.ts.map +1 -1
  97. package/dist/config/oat-config.js +88 -0
  98. package/dist/config/resolve.d.ts.map +1 -1
  99. package/dist/config/resolve.js +18 -0
  100. package/dist/validation/project-state.d.ts +3 -0
  101. package/dist/validation/project-state.d.ts.map +1 -1
  102. package/dist/validation/project-state.js +91 -0
  103. package/package.json +2 -2
  104. /package/assets/skills/{oat-wave-execute/assets/program-recap.recipe.json → explainer-kit/recipes/program-recap.json} +0 -0
@@ -0,0 +1,332 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ import { canonicalHash, validateContract } from './contracts.mjs';
4
+
5
+ const PALETTE_NAMES = new Set([
6
+ 'neutral',
7
+ 'ocean',
8
+ 'ember',
9
+ 'forest',
10
+ 'violet',
11
+ ]);
12
+ const PROFILE_NAMES = new Set(['clean', 'editorial', 'technical']);
13
+ const RENDER_STRATEGIES = new Set(['default-only', 'user-switchable']);
14
+ const DEFAULT_MODES = new Set(['light', 'dark']);
15
+ const MODE_ROLES = ['surface', 'ink', 'accent', 'status', 'diagramSeries'];
16
+
17
+ export async function resolveTheme(selection = {}) {
18
+ assertSelection(selection);
19
+
20
+ const renderStrategy = selection.renderStrategy ?? 'default-only';
21
+ const warnings = [];
22
+ let theme;
23
+
24
+ if (selection.suppliedBundlePath !== undefined) {
25
+ theme = await resolveSuppliedBundle(selection.suppliedBundlePath);
26
+ if (
27
+ ['palette', 'visualProfile', 'artDirection', 'defaultMode'].some(
28
+ (key) => selection[key] !== undefined,
29
+ )
30
+ ) {
31
+ warnings.push(
32
+ 'Supplied bundle wins over palette, visual profile, art direction, and default mode selections.',
33
+ );
34
+ }
35
+ } else {
36
+ theme = await resolveNamedTheme(selection);
37
+ }
38
+
39
+ assertResolvedTheme(theme);
40
+ return {
41
+ theme,
42
+ renderStrategy,
43
+ presentationModes:
44
+ renderStrategy === 'user-switchable'
45
+ ? [theme.defaultMode, oppositeMode(theme.defaultMode)]
46
+ : [theme.defaultMode],
47
+ warnings,
48
+ };
49
+ }
50
+
51
+ async function resolveNamedTheme(selection) {
52
+ const paletteName = selection.palette ?? 'neutral';
53
+ const profileName = selection.visualProfile ?? 'clean';
54
+ assertNamedSelection(paletteName, PALETTE_NAMES, 'palette');
55
+ assertNamedSelection(profileName, PROFILE_NAMES, 'visual profile');
56
+
57
+ const [palette, profile] = await Promise.all([
58
+ loadBundledJson('palettes', paletteName),
59
+ loadBundledJson('profiles', profileName),
60
+ ]);
61
+ assertPalette(palette, paletteName);
62
+ assertProfile(profile, profileName);
63
+
64
+ const theme = {
65
+ schemaVersion: 'explainer-kit.theme/v1',
66
+ name: `${paletteName}-${profileName}`,
67
+ defaultMode: selection.defaultMode ?? 'light',
68
+ modes: structuredClone(palette.modes),
69
+ provenance: {
70
+ palette: paletteName,
71
+ visualProfile: profileName,
72
+ derived: false,
73
+ },
74
+ typography: structuredClone(profile.typography),
75
+ spacing: structuredClone(profile.spacing),
76
+ geometry: structuredClone(profile.geometry),
77
+ elevation: structuredClone(profile.elevation),
78
+ density: profile.density,
79
+ motion: structuredClone(profile.motion),
80
+ diagrams: structuredClone(profile.diagrams),
81
+ };
82
+
83
+ if (selection.artDirection !== undefined) {
84
+ applyArtDirection(theme, selection.artDirection);
85
+ }
86
+ theme.bundleHash = canonicalHash(theme);
87
+ return theme;
88
+ }
89
+
90
+ async function resolveSuppliedBundle(suppliedBundlePath) {
91
+ if (
92
+ typeof suppliedBundlePath !== 'string' ||
93
+ suppliedBundlePath.length === 0
94
+ ) {
95
+ throw new TypeError('Supplied bundle path must be a non-empty string.');
96
+ }
97
+
98
+ let source;
99
+ try {
100
+ source = JSON.parse(await readFile(suppliedBundlePath, 'utf8'));
101
+ } catch (error) {
102
+ throw new Error('Unable to read the supplied theme bundle.', {
103
+ cause: error,
104
+ });
105
+ }
106
+
107
+ assertResolvedTheme(source);
108
+ const theme = structuredClone(source);
109
+ theme.provenance.suppliedBundleHash = canonicalHash(source);
110
+ theme.bundleHash = canonicalHash(withoutBundleHash(theme));
111
+ return theme;
112
+ }
113
+
114
+ function applyArtDirection(theme, instruction) {
115
+ if (typeof instruction !== 'string' || instruction.trim().length === 0) {
116
+ throw new TypeError('Art direction must be a non-empty string.');
117
+ }
118
+
119
+ const instructionHash = canonicalHash(instruction.normalize('NFKC').trim());
120
+ const variant = Number.parseInt(instructionHash.slice(-2), 16) % 3;
121
+ const spacingFactor = [0.9, 1.05, 1.15][variant];
122
+ const radiusFactor = [0.8, 1.1, 1.25][variant];
123
+
124
+ theme.name = `${theme.name}-derived`;
125
+ theme.provenance.derived = true;
126
+ theme.provenance.instructionHash = instructionHash;
127
+ theme.spacing.unit = round(theme.spacing.unit * spacingFactor);
128
+ theme.spacing.scale = mapNumbers(theme.spacing.scale, spacingFactor);
129
+ theme.geometry.radius = mapNumbers(theme.geometry.radius, radiusFactor);
130
+ theme.diagrams.nodeGap = round(theme.diagrams.nodeGap * spacingFactor);
131
+ }
132
+
133
+ function assertSelection(selection) {
134
+ if (!isObject(selection)) {
135
+ throw new TypeError('Theme selection must be an object.');
136
+ }
137
+ const allowed = new Set([
138
+ 'palette',
139
+ 'visualProfile',
140
+ 'suppliedBundlePath',
141
+ 'artDirection',
142
+ 'defaultMode',
143
+ 'renderStrategy',
144
+ ]);
145
+ const unknown = Object.keys(selection).filter((key) => !allowed.has(key));
146
+ if (unknown.length > 0) {
147
+ throw new Error(`Unknown theme selection field: ${unknown[0]}.`);
148
+ }
149
+ if (
150
+ selection.renderStrategy !== undefined &&
151
+ !RENDER_STRATEGIES.has(selection.renderStrategy)
152
+ ) {
153
+ throw new Error('Unsupported theme render strategy.');
154
+ }
155
+ if (
156
+ selection.defaultMode !== undefined &&
157
+ !DEFAULT_MODES.has(selection.defaultMode)
158
+ ) {
159
+ throw new Error('Unsupported theme default mode.');
160
+ }
161
+ }
162
+
163
+ function assertNamedSelection(value, allowed, label) {
164
+ if (typeof value !== 'string' || !allowed.has(value)) {
165
+ throw new Error(`Unknown ${label}: ${String(value)}.`);
166
+ }
167
+ }
168
+
169
+ async function loadBundledJson(directory, name) {
170
+ return JSON.parse(
171
+ await readFile(
172
+ new URL(`../../${directory}/${name}.json`, import.meta.url),
173
+ 'utf8',
174
+ ),
175
+ );
176
+ }
177
+
178
+ function assertPalette(palette, expectedName) {
179
+ if (
180
+ !isObject(palette) ||
181
+ palette.name !== expectedName ||
182
+ !hasExactKeys(palette, ['name', 'modes']) ||
183
+ !isObject(palette.modes) ||
184
+ !hasExactKeys(palette.modes, ['light', 'dark'])
185
+ ) {
186
+ throw new Error(`Bundled palette ${expectedName} has an invalid shape.`);
187
+ }
188
+ for (const mode of Object.values(palette.modes)) {
189
+ assertMode(mode);
190
+ }
191
+ }
192
+
193
+ function assertMode(mode) {
194
+ if (!isObject(mode) || !hasExactKeys(mode, MODE_ROLES)) {
195
+ throw new Error('Theme mode must contain the closed semantic color roles.');
196
+ }
197
+ assertExactColorObject(mode.surface, ['canvas', 'panel', 'elevated']);
198
+ assertExactColorObject(mode.ink, ['primary', 'muted', 'inverse']);
199
+ assertExactColorObject(mode.accent, ['primary', 'secondary']);
200
+ assertExactColorObject(mode.status, ['success', 'warning', 'danger', 'info']);
201
+ if (
202
+ !Array.isArray(mode.diagramSeries) ||
203
+ mode.diagramSeries.length === 0 ||
204
+ mode.diagramSeries.some((color) => !isNormalizedColor(color)) ||
205
+ new Set(mode.diagramSeries).size !== mode.diagramSeries.length
206
+ ) {
207
+ throw new Error(
208
+ 'Theme diagram series must contain unique normalized colors.',
209
+ );
210
+ }
211
+ assertAaPairs(mode);
212
+ }
213
+
214
+ function assertProfile(profile, expectedName) {
215
+ const expectedKeys = [
216
+ 'name',
217
+ 'typography',
218
+ 'spacing',
219
+ 'geometry',
220
+ 'elevation',
221
+ 'density',
222
+ 'motion',
223
+ 'diagrams',
224
+ ];
225
+ if (
226
+ !isObject(profile) ||
227
+ profile.name !== expectedName ||
228
+ !hasExactKeys(profile, expectedKeys)
229
+ ) {
230
+ throw new Error(
231
+ `Bundled visual profile ${expectedName} has an invalid shape.`,
232
+ );
233
+ }
234
+ }
235
+
236
+ function assertResolvedTheme(theme) {
237
+ const result = validateContract('theme', theme);
238
+ if (!result.valid) {
239
+ throw new Error(
240
+ `Invalid resolved theme: ${result.errors
241
+ .map(({ path, message }) => `${path}: ${message}`)
242
+ .join('; ')}`,
243
+ );
244
+ }
245
+ if (theme.bundleHash !== canonicalHash(withoutBundleHash(theme))) {
246
+ throw new Error(
247
+ 'Resolved theme bundleHash does not match canonical content.',
248
+ );
249
+ }
250
+ for (const mode of Object.values(theme.modes)) {
251
+ assertMode(mode);
252
+ }
253
+ }
254
+
255
+ function assertExactColorObject(value, keys) {
256
+ if (
257
+ !isObject(value) ||
258
+ !hasExactKeys(value, keys) ||
259
+ Object.values(value).some((color) => !isNormalizedColor(color))
260
+ ) {
261
+ throw new Error('Theme color role is incomplete or invalid.');
262
+ }
263
+ }
264
+
265
+ function assertAaPairs(mode) {
266
+ for (const surface of Object.values(mode.surface)) {
267
+ assertContrast(mode.ink.primary, surface);
268
+ assertContrast(mode.ink.muted, surface);
269
+ }
270
+ for (const background of [
271
+ ...Object.values(mode.accent),
272
+ ...Object.values(mode.status),
273
+ ]) {
274
+ assertContrast(mode.ink.inverse, background);
275
+ }
276
+ }
277
+
278
+ function assertContrast(foreground, background) {
279
+ const lighter = Math.max(luminance(foreground), luminance(background));
280
+ const darker = Math.min(luminance(foreground), luminance(background));
281
+ if ((lighter + 0.05) / (darker + 0.05) < 4.5) {
282
+ throw new Error(
283
+ 'Theme text/background pair does not meet WCAG AA contrast.',
284
+ );
285
+ }
286
+ }
287
+
288
+ function luminance(hex) {
289
+ const channels = hex
290
+ .slice(1)
291
+ .match(/.{2}/g)
292
+ .map((value) => Number.parseInt(value, 16) / 255)
293
+ .map((value) =>
294
+ value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4,
295
+ );
296
+ return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
297
+ }
298
+
299
+ function withoutBundleHash(theme) {
300
+ const identity = structuredClone(theme);
301
+ delete identity.bundleHash;
302
+ return identity;
303
+ }
304
+
305
+ function mapNumbers(values, factor) {
306
+ return Object.fromEntries(
307
+ Object.entries(values).map(([key, value]) => [key, round(value * factor)]),
308
+ );
309
+ }
310
+
311
+ function round(value) {
312
+ return Math.round(value * 100) / 100;
313
+ }
314
+
315
+ function oppositeMode(mode) {
316
+ return mode === 'light' ? 'dark' : 'light';
317
+ }
318
+
319
+ function hasExactKeys(value, keys) {
320
+ return (
321
+ Object.keys(value).length === keys.length &&
322
+ keys.every((key) => Object.hasOwn(value, key))
323
+ );
324
+ }
325
+
326
+ function isNormalizedColor(value) {
327
+ return typeof value === 'string' && /^#[0-9a-f]{6}$/.test(value);
328
+ }
329
+
330
+ function isObject(value) {
331
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
332
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from 'node:fs/promises';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ import { publishS3Static } from './lib/s3-static.mjs';
7
+
8
+ export async function runPublishCli(argv, dependencies = {}) {
9
+ const log = dependencies.log ?? console.log;
10
+ try {
11
+ const options = parseArguments(argv);
12
+ if (!options.confirmPublish) {
13
+ throw Object.assign(
14
+ new Error(
15
+ 'Publishing requires --confirm-publish after human approval.',
16
+ ),
17
+ { code: 'E_PUBLISH_APPROVAL' },
18
+ );
19
+ }
20
+ const request = JSON.parse(await readFile(options.request, 'utf8'));
21
+ const receipt = await publishS3Static(request, {
22
+ approved: true,
23
+ receiptPath: options.receipt,
24
+ ...dependencies.connector,
25
+ });
26
+ log(JSON.stringify({ ok: true, receiptPath: options.receipt, receipt }));
27
+ return 0;
28
+ } catch (caught) {
29
+ log(
30
+ JSON.stringify({
31
+ ok: false,
32
+ error: {
33
+ code: caught?.code ?? 'E_PUBLISH',
34
+ message: caught instanceof Error ? caught.message : String(caught),
35
+ },
36
+ }),
37
+ );
38
+ return 1;
39
+ }
40
+ }
41
+
42
+ function parseArguments(argv) {
43
+ const options = {
44
+ request: null,
45
+ receipt: null,
46
+ confirmPublish: false,
47
+ };
48
+ for (let index = 0; index < argv.length; index += 1) {
49
+ const argument = argv[index];
50
+ if (argument === '--confirm-publish') {
51
+ options.confirmPublish = true;
52
+ continue;
53
+ }
54
+ if (argument === '--request' || argument === '--receipt') {
55
+ const value = argv[index + 1];
56
+ if (!value || value.startsWith('--')) {
57
+ throw new Error(`${argument} requires a path.`);
58
+ }
59
+ options[argument.slice(2)] = value;
60
+ index += 1;
61
+ continue;
62
+ }
63
+ throw new Error(`Unknown argument: ${argument}`);
64
+ }
65
+ if (!options.request || !options.receipt) {
66
+ throw new Error('--request and --receipt are required.');
67
+ }
68
+ return options;
69
+ }
70
+
71
+ if (
72
+ process.argv[1] &&
73
+ import.meta.url === pathToFileURL(process.argv[1]).href
74
+ ) {
75
+ process.exitCode = await runPublishCli(process.argv.slice(2));
76
+ }
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from 'node:fs/promises';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ import { recordDurability } from './lib/durability.mjs';
7
+
8
+ export async function runRecordDurabilityCli(
9
+ argv = process.argv.slice(2),
10
+ io = console,
11
+ ) {
12
+ const [requestPath] = argv;
13
+ if (!requestPath || argv.length !== 1) {
14
+ io.log(
15
+ JSON.stringify(
16
+ {
17
+ durable: false,
18
+ outcome: 'built-not-durable',
19
+ errors: [
20
+ {
21
+ code: 'usage',
22
+ message: 'Usage: record-durability.mjs <request-json-path>',
23
+ },
24
+ ],
25
+ },
26
+ null,
27
+ 2,
28
+ ),
29
+ );
30
+ return 1;
31
+ }
32
+
33
+ try {
34
+ const request = JSON.parse(await readFile(requestPath, 'utf8'));
35
+ const result = await recordDurability(request);
36
+ io.log(JSON.stringify(result, null, 2));
37
+ return result.durable ? 0 : 1;
38
+ } catch (error) {
39
+ io.log(
40
+ JSON.stringify(
41
+ {
42
+ durable: false,
43
+ outcome: 'built-not-durable',
44
+ errors: [
45
+ {
46
+ code: 'input',
47
+ message: error instanceof Error ? error.message : String(error),
48
+ },
49
+ ],
50
+ },
51
+ null,
52
+ 2,
53
+ ),
54
+ );
55
+ return 1;
56
+ }
57
+ }
58
+
59
+ if (
60
+ process.argv[1] &&
61
+ pathToFileURL(process.argv[1]).href === import.meta.url
62
+ ) {
63
+ process.exitCode = await runRecordDurabilityCli();
64
+ }