@oxygen-agent/cli 1.64.5 → 1.99.1

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.
@@ -1,7 +1,10 @@
1
1
  export declare const WORKFLOW_MANIFEST_VERSION = 1;
2
2
  export declare const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
3
3
  export declare const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
4
+ export declare const BLUEPRINT_VERSION = 1;
5
+ export declare const BLUEPRINT_COMPILER_VERSION = "oxygen-blueprints-v1";
4
6
  export declare const MAX_RECIPE_BUNDLE_BYTES = 2000000;
7
+ export declare const MAX_BLUEPRINT_BYTES = 4000000;
5
8
  export declare const DEFAULT_WORKFLOW_CRON_TIMEZONE = "UTC";
6
9
  export type WorkflowMode = "dry_run" | "live" | "smoke_test";
7
10
  export type WorkflowTriggerType = "api" | "webhook" | "cron" | "event";
@@ -100,6 +103,72 @@ export type RecipeManifest = {
100
103
  created_at: string;
101
104
  };
102
105
  export type AnyWorkflowManifest = WorkflowManifest | RecipeManifest;
106
+ export type BlueprintColumnInput = {
107
+ key?: string;
108
+ label: string;
109
+ dataType?: string;
110
+ kind?: string;
111
+ semanticType?: string | null;
112
+ definition?: Record<string, unknown>;
113
+ };
114
+ export type BlueprintTable = {
115
+ ref: string;
116
+ name: string;
117
+ description?: string;
118
+ columns: BlueprintColumnInput[];
119
+ metadata?: Record<string, unknown>;
120
+ };
121
+ export type BlueprintColumnGraft = {
122
+ table_ref: string;
123
+ column: BlueprintColumnInput;
124
+ };
125
+ export type BlueprintPromptKind = "ai_column_system" | "scoring_rubric" | "other";
126
+ export type BlueprintPromptTemplate = {
127
+ slug: string;
128
+ name: string;
129
+ description?: string | null;
130
+ kind: BlueprintPromptKind;
131
+ body: string;
132
+ };
133
+ export type BlueprintIntegrationRequirement = {
134
+ kind: string;
135
+ purpose?: string;
136
+ required?: boolean;
137
+ };
138
+ export type BlueprintByokRequirement = {
139
+ kind: "openai" | "anthropic" | "google" | "openrouter" | string;
140
+ required?: boolean;
141
+ };
142
+ export type BlueprintRequires = {
143
+ integrations?: BlueprintIntegrationRequirement[];
144
+ context_keys?: string[];
145
+ byok?: BlueprintByokRequirement[];
146
+ };
147
+ export type BlueprintWorkflow = {
148
+ manifest: AnyWorkflowManifest;
149
+ table_refs?: Record<string, string>;
150
+ prompt_template_slugs?: string[];
151
+ };
152
+ export type Blueprint = {
153
+ blueprint_version: typeof BLUEPRINT_VERSION;
154
+ id: string;
155
+ name: string;
156
+ summary: string;
157
+ tags: string[];
158
+ audience?: string[];
159
+ requires: BlueprintRequires;
160
+ input_schema?: JsonSchema;
161
+ tables: BlueprintTable[];
162
+ column_grafts?: BlueprintColumnGraft[];
163
+ prompt_templates: BlueprintPromptTemplate[];
164
+ workflows: BlueprintWorkflow[];
165
+ exported_at: string;
166
+ exported_from?: {
167
+ oxygen_version?: string;
168
+ };
169
+ source_hash: string;
170
+ compiler_version: typeof BLUEPRINT_COMPILER_VERSION;
171
+ };
103
172
  export type WorkflowApplyInput = {
104
173
  manifest: WorkflowManifest;
105
174
  };
@@ -229,6 +298,28 @@ export declare function lintRecipeManifest(// skipcq: JS-R1005
229
298
  value: unknown, options?: WorkflowManifestValidationOptions): WorkflowLintResult;
230
299
  export declare function assertRecipeManifest(value: unknown, options?: WorkflowManifestValidationOptions): asserts value is RecipeManifest;
231
300
  export declare function assertWorkflowManifest(value: unknown, options?: WorkflowManifestValidationOptions): asserts value is WorkflowManifest;
301
+ export declare function isBlueprint(value: unknown): value is Blueprint;
302
+ export declare function lintBlueprint(// skipcq: JS-R1005
303
+ value: unknown, options?: WorkflowManifestValidationOptions): WorkflowLintResult;
304
+ export declare function assertBlueprint(value: unknown, options?: WorkflowManifestValidationOptions): asserts value is Blueprint;
305
+ export declare function buildBlueprint(input: {
306
+ id: string;
307
+ name: string;
308
+ summary?: string;
309
+ tags?: string[];
310
+ audience?: string[];
311
+ requires?: BlueprintRequires;
312
+ inputSchema?: JsonSchema;
313
+ tables: BlueprintTable[];
314
+ columnGrafts?: BlueprintColumnGraft[];
315
+ promptTemplates?: BlueprintPromptTemplate[];
316
+ workflows: BlueprintWorkflow[];
317
+ exportedFrom?: {
318
+ oxygen_version?: string;
319
+ };
320
+ exportedAt?: Date;
321
+ sourceHash?: string;
322
+ }): Blueprint;
232
323
  export declare function validateJsonSchemaValue(value: unknown, schema: JsonSchema | undefined, path?: string): JsonSchemaValidationIssue[];
233
324
  export declare function assertRecipeBundleSafe(bundle: string): void;
234
325
  export declare function lintRecipeBundleSafety(bundle: string, path?: string): WorkflowLintIssue[];
@@ -3,7 +3,10 @@ import * as vm from "node:vm"; // skipcq: JS-C1003
3
3
  export const WORKFLOW_MANIFEST_VERSION = 1;
4
4
  export const WORKFLOW_COMPILER_VERSION = "oxygen-workflows-v1";
5
5
  export const DURABLE_RECIPE_COMPILER_VERSION = "oxygen-recipes-v2";
6
+ export const BLUEPRINT_VERSION = 1;
7
+ export const BLUEPRINT_COMPILER_VERSION = "oxygen-blueprints-v1";
6
8
  export const MAX_RECIPE_BUNDLE_BYTES = 2_000_000;
9
+ export const MAX_BLUEPRINT_BYTES = 4_000_000;
7
10
  export const DEFAULT_WORKFLOW_CRON_TIMEZONE = "UTC";
8
11
  // Compatibility and determinism lint only. The Vercel sandbox process,
9
12
  // denied network policy, and runtime global guards are the security boundary.
@@ -380,6 +383,234 @@ export function assertWorkflowManifest(value, options = {}) {
380
383
  const first = result.issues[0];
381
384
  throw new Error(first ? `${first.code}: ${first.message}` : "Invalid workflow manifest.");
382
385
  }
386
+ export function isBlueprint(value) {
387
+ return isRecord(value)
388
+ && value.blueprint_version === BLUEPRINT_VERSION
389
+ && value.compiler_version === BLUEPRINT_COMPILER_VERSION;
390
+ }
391
+ const BLUEPRINT_REF_PATTERN = /^[a-z][a-z0-9_-]{0,79}$/;
392
+ const BLUEPRINT_PROMPT_SLUG_PATTERN = /^[a-z][a-z0-9_-]{0,119}$/;
393
+ export function lintBlueprint(// skipcq: JS-R1005
394
+ value, options = {}) {
395
+ const issues = [];
396
+ const add = (path, code, message) => issues.push({ path, code, message });
397
+ if (!isRecord(value)) {
398
+ add("$", "invalid_blueprint", "Blueprint must be an object.");
399
+ return { ok: false, issues };
400
+ }
401
+ if (value.blueprint_version !== BLUEPRINT_VERSION) {
402
+ add("$.blueprint_version", "invalid_blueprint_version", "Blueprint version must be 1.");
403
+ }
404
+ if (value.compiler_version !== BLUEPRINT_COMPILER_VERSION) {
405
+ add("$.compiler_version", "invalid_compiler_version", `Blueprint compiler_version must be '${BLUEPRINT_COMPILER_VERSION}'.`);
406
+ }
407
+ if (!isNonEmptyString(value.id))
408
+ add("$.id", "invalid_blueprint_id", "Blueprint id is required.");
409
+ if (!isNonEmptyString(value.name))
410
+ add("$.name", "invalid_blueprint_name", "Blueprint name is required.");
411
+ if (typeof value.summary !== "string")
412
+ add("$.summary", "invalid_summary", "Blueprint summary must be a string.");
413
+ if (!Array.isArray(value.tags)) {
414
+ add("$.tags", "invalid_tags", "Blueprint tags must be an array of strings.");
415
+ }
416
+ if (!isNonEmptyString(value.exported_at)) {
417
+ add("$.exported_at", "invalid_exported_at", "Blueprint exported_at must be an ISO timestamp.");
418
+ }
419
+ if (!isNonEmptyString(value.source_hash)) {
420
+ add("$.source_hash", "missing_source_hash", "Blueprint source_hash is required.");
421
+ }
422
+ const tableRefs = new Set();
423
+ if (!Array.isArray(value.tables)) {
424
+ add("$.tables", "invalid_tables", "Blueprint tables must be an array.");
425
+ }
426
+ else {
427
+ value.tables.forEach((table, index) => {
428
+ const path = `$.tables.${index}`;
429
+ if (!isRecord(table)) {
430
+ add(path, "invalid_table", "Blueprint table must be an object.");
431
+ return;
432
+ }
433
+ if (!isNonEmptyString(table.ref) || !BLUEPRINT_REF_PATTERN.test(table.ref)) {
434
+ add(`${path}.ref`, "invalid_table_ref", "Blueprint table ref must be lowercase kebab/snake_case (a-z0-9_-).");
435
+ }
436
+ else {
437
+ if (tableRefs.has(table.ref)) {
438
+ add(`${path}.ref`, "duplicate_table_ref", `Blueprint table ref '${table.ref}' is duplicated.`);
439
+ }
440
+ tableRefs.add(table.ref);
441
+ }
442
+ if (!isNonEmptyString(table.name))
443
+ add(`${path}.name`, "invalid_table_name", "Blueprint table name is required.");
444
+ if (!Array.isArray(table.columns) || table.columns.length === 0) {
445
+ add(`${path}.columns`, "invalid_table_columns", "Blueprint table must declare at least one column.");
446
+ }
447
+ else {
448
+ table.columns.forEach((column, columnIndex) => {
449
+ const columnPath = `${path}.columns.${columnIndex}`;
450
+ if (!isRecord(column)) {
451
+ add(columnPath, "invalid_column", "Blueprint column must be an object.");
452
+ return;
453
+ }
454
+ if (!isNonEmptyString(column.label)) {
455
+ add(`${columnPath}.label`, "invalid_column_label", "Blueprint column label is required.");
456
+ }
457
+ });
458
+ }
459
+ });
460
+ }
461
+ if (value.column_grafts !== undefined) {
462
+ if (!Array.isArray(value.column_grafts)) {
463
+ add("$.column_grafts", "invalid_column_grafts", "Blueprint column_grafts must be an array.");
464
+ }
465
+ else {
466
+ value.column_grafts.forEach((graft, index) => {
467
+ const path = `$.column_grafts.${index}`;
468
+ if (!isRecord(graft)) {
469
+ add(path, "invalid_column_graft", "Column graft must be an object.");
470
+ return;
471
+ }
472
+ if (!isNonEmptyString(graft.table_ref) || !tableRefs.has(graft.table_ref)) {
473
+ add(`${path}.table_ref`, "invalid_graft_ref", "Column graft table_ref must match a declared blueprint table ref.");
474
+ }
475
+ if (!isRecord(graft.column) || !isNonEmptyString(graft.column.label)) {
476
+ add(`${path}.column`, "invalid_graft_column", "Column graft column must be an object with a label.");
477
+ }
478
+ });
479
+ }
480
+ }
481
+ const promptSlugs = new Set();
482
+ if (!Array.isArray(value.prompt_templates)) {
483
+ add("$.prompt_templates", "invalid_prompt_templates", "Blueprint prompt_templates must be an array.");
484
+ }
485
+ else {
486
+ value.prompt_templates.forEach((prompt, index) => {
487
+ const path = `$.prompt_templates.${index}`;
488
+ if (!isRecord(prompt)) {
489
+ add(path, "invalid_prompt", "Blueprint prompt must be an object.");
490
+ return;
491
+ }
492
+ if (!isNonEmptyString(prompt.slug) || !BLUEPRINT_PROMPT_SLUG_PATTERN.test(prompt.slug)) {
493
+ add(`${path}.slug`, "invalid_prompt_slug", "Prompt slug must be lowercase kebab/snake_case (a-z0-9_-).");
494
+ }
495
+ else {
496
+ if (promptSlugs.has(prompt.slug)) {
497
+ add(`${path}.slug`, "duplicate_prompt_slug", `Prompt slug '${prompt.slug}' is duplicated.`);
498
+ }
499
+ promptSlugs.add(prompt.slug);
500
+ }
501
+ if (!isNonEmptyString(prompt.name))
502
+ add(`${path}.name`, "invalid_prompt_name", "Prompt name is required.");
503
+ if (!isNonEmptyString(prompt.body))
504
+ add(`${path}.body`, "invalid_prompt_body", "Prompt body is required.");
505
+ if (prompt.kind !== "ai_column_system" && prompt.kind !== "scoring_rubric" && prompt.kind !== "other") {
506
+ add(`${path}.kind`, "invalid_prompt_kind", "Prompt kind must be ai_column_system, scoring_rubric, or other.");
507
+ }
508
+ });
509
+ }
510
+ if (!Array.isArray(value.workflows)) {
511
+ add("$.workflows", "invalid_workflows", "Blueprint workflows must be an array.");
512
+ }
513
+ else if (value.workflows.length === 0) {
514
+ add("$.workflows", "missing_workflows", "Blueprint must include at least one workflow.");
515
+ }
516
+ else {
517
+ value.workflows.forEach((entry, index) => {
518
+ const path = `$.workflows.${index}`;
519
+ if (!isRecord(entry)) {
520
+ add(path, "invalid_workflow_entry", "Workflow entry must be an object.");
521
+ return;
522
+ }
523
+ const manifest = entry.manifest;
524
+ const manifestResult = lintWorkflowManifest(manifest, options);
525
+ if (!manifestResult.ok) {
526
+ manifestResult.issues.forEach((issue) => {
527
+ add(`${path}.manifest${issue.path.replace(/^\$/, "")}`, issue.code, issue.message);
528
+ });
529
+ }
530
+ if (entry.table_refs !== undefined && !isRecord(entry.table_refs)) {
531
+ add(`${path}.table_refs`, "invalid_workflow_table_refs", "Workflow table_refs must be an object map.");
532
+ }
533
+ else if (isRecord(entry.table_refs)) {
534
+ for (const refName of Object.keys(entry.table_refs)) {
535
+ if (!tableRefs.has(refName)) {
536
+ add(`${path}.table_refs.${refName}`, "unknown_workflow_table_ref", `Workflow references table ref '${refName}' which is not declared on this blueprint.`);
537
+ }
538
+ }
539
+ }
540
+ if (entry.prompt_template_slugs !== undefined) {
541
+ if (!Array.isArray(entry.prompt_template_slugs)) {
542
+ add(`${path}.prompt_template_slugs`, "invalid_prompt_slug_list", "Workflow prompt_template_slugs must be an array of strings.");
543
+ }
544
+ else {
545
+ entry.prompt_template_slugs.forEach((slug, slugIndex) => {
546
+ if (!isNonEmptyString(slug)) {
547
+ add(`${path}.prompt_template_slugs.${slugIndex}`, "invalid_prompt_slug", "Workflow prompt slug references must be non-empty strings.");
548
+ }
549
+ else if (!promptSlugs.has(slug)) {
550
+ add(`${path}.prompt_template_slugs.${slugIndex}`, "unknown_prompt_slug", `Workflow references prompt slug '${slug}' which is not declared on this blueprint.`);
551
+ }
552
+ });
553
+ }
554
+ }
555
+ });
556
+ }
557
+ const requires = value.requires;
558
+ if (!isRecord(requires)) {
559
+ add("$.requires", "invalid_requires", "Blueprint requires must be an object.");
560
+ }
561
+ else {
562
+ if (requires.integrations !== undefined) {
563
+ if (!Array.isArray(requires.integrations)) {
564
+ add("$.requires.integrations", "invalid_requires_integrations", "Blueprint requires.integrations must be an array.");
565
+ }
566
+ else {
567
+ requires.integrations.forEach((entry, index) => {
568
+ const path = `$.requires.integrations.${index}`;
569
+ if (!isRecord(entry)) {
570
+ add(path, "invalid_integration", "Integration requirement must be an object.");
571
+ return;
572
+ }
573
+ if (!isNonEmptyString(entry.kind)) {
574
+ add(`${path}.kind`, "invalid_integration_kind", "Integration requirement kind is required (e.g. 'instantly', 'apollo').");
575
+ }
576
+ });
577
+ }
578
+ }
579
+ if (requires.context_keys !== undefined && !Array.isArray(requires.context_keys)) {
580
+ add("$.requires.context_keys", "invalid_context_keys", "Blueprint requires.context_keys must be an array of strings.");
581
+ }
582
+ }
583
+ return { ok: issues.length === 0, issues };
584
+ }
585
+ export function assertBlueprint(value, options = {}) {
586
+ const result = lintBlueprint(value, options);
587
+ if (result.ok)
588
+ return;
589
+ const first = result.issues[0];
590
+ throw new Error(first ? `${first.code}: ${first.message}` : "Invalid blueprint.");
591
+ }
592
+ export function buildBlueprint(input) {
593
+ const draft = {
594
+ blueprint_version: BLUEPRINT_VERSION,
595
+ id: input.id,
596
+ name: input.name,
597
+ summary: input.summary ?? "",
598
+ tags: Array.isArray(input.tags) ? Array.from(new Set(input.tags.filter(Boolean))) : [],
599
+ ...(input.audience ? { audience: input.audience } : {}),
600
+ requires: input.requires ?? {},
601
+ ...(input.inputSchema ? { input_schema: input.inputSchema } : {}),
602
+ tables: input.tables,
603
+ ...(input.columnGrafts && input.columnGrafts.length > 0 ? { column_grafts: input.columnGrafts } : {}),
604
+ prompt_templates: input.promptTemplates ?? [],
605
+ workflows: input.workflows,
606
+ exported_at: (input.exportedAt ?? new Date()).toISOString(),
607
+ ...(input.exportedFrom ? { exported_from: input.exportedFrom } : {}),
608
+ compiler_version: BLUEPRINT_COMPILER_VERSION,
609
+ };
610
+ const sourceHash = input.sourceHash
611
+ ?? hashWorkflowSource(JSON.stringify(draft, workflowJsonReplacer));
612
+ return { ...draft, source_hash: sourceHash };
613
+ }
383
614
  export function validateJsonSchemaValue(value, schema, path = "$") {
384
615
  if (!schema || typeof schema !== "object" || Array.isArray(schema))
385
616
  return [];
@@ -808,7 +1039,7 @@ function validateRecipeBundleSafety(source, path, add) {
808
1039
  }
809
1040
  }
810
1041
  function isWorkflowToolRejected(toolId) {
811
- return toolId === "run_javascript" || toolId === "custom_http" || toolId.includes("custom_http");
1042
+ return toolId === "run_javascript" || toolId === "custom_http";
812
1043
  }
813
1044
  const CRON_FIELD_LIMITS = [
814
1045
  { name: "minute", min: 0, max: 59 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.64.5",
3
+ "version": "1.99.1",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",