@oxygen-agent/cli 1.1011.687 → 1.1011.767

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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/column-run-notices.js +2 -0
  3. package/dist/help.d.ts +2 -0
  4. package/dist/help.js +5 -0
  5. package/dist/index.js +139 -91
  6. package/dist/workflow-run-list-flags.d.ts +3 -0
  7. package/dist/workflow-run-list-flags.js +21 -0
  8. package/node_modules/@oxygen/shared/dist/capability-discovery.js +42 -13
  9. package/node_modules/@oxygen/shared/dist/contracts/crm-enrichment-read.js +1 -1
  10. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +1 -0
  11. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +1 -0
  12. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +8 -6
  13. package/node_modules/@oxygen/shared/dist/contracts/schema.d.ts +8 -0
  14. package/node_modules/@oxygen/shared/dist/contracts/schema.js +6 -0
  15. package/node_modules/@oxygen/shared/dist/contracts/sequence-analytics-read.d.ts +38 -0
  16. package/node_modules/@oxygen/shared/dist/contracts/sequence-analytics-read.js +22 -0
  17. package/node_modules/@oxygen/shared/dist/contracts/table-list.d.ts +31 -0
  18. package/node_modules/@oxygen/shared/dist/contracts/table-list.js +22 -0
  19. package/node_modules/@oxygen/shared/dist/contracts/table-operations.d.ts +58 -0
  20. package/node_modules/@oxygen/shared/dist/contracts/table-operations.js +30 -0
  21. package/node_modules/@oxygen/shared/dist/contracts/workflow-run-list.d.ts +79 -0
  22. package/node_modules/@oxygen/shared/dist/contracts/workflow-run-list.js +38 -0
  23. package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +2 -2
  24. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +2 -2
  25. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +2 -2
  26. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +3 -3
  27. package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +3 -3
  28. package/node_modules/@oxygen/shared/dist/hosted-ai.js +24 -0
  29. package/node_modules/@oxygen/shared/dist/recipes.d.ts +1 -1
  30. package/node_modules/@oxygen/shared/dist/recipes.js +4 -9
  31. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  32. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  33. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +1 -0
  34. package/node_modules/@oxygen/workflows/dist/graph/expression.js +15 -0
  35. package/node_modules/@oxygen/workflows/dist/graph/lint.js +8 -2
  36. package/package.json +1 -1
@@ -574,26 +574,17 @@ export function inferCapabilityRoute(query) {
574
574
  // Its source nouns otherwise select a one-shot post search or follower order.
575
575
  if (isLinkedInAudienceCampaignIntent(query)) {
576
576
  const recipes = ROUTE_BY_ID.get("recipes");
577
- const slug = query.trim().toLowerCase();
578
- const namedPlay = AUDIENCE_PLAY_SLUGS.includes(slug) ? slug : null;
579
577
  if (recipes)
580
578
  return {
581
579
  card: {
582
580
  ...recipes,
583
- execution: "Pick the matching audience Recipe (own, competitor, creator or keyword post engagers, company-page followers or own followers), resolve current company/ICP context, then read that Recipe and the oxygen-signals file references/audience-campaigns.md. Preserve the exact source, attribution, qualification rule/version and object IDs. Prepare a bounded native audience campaign for email, LinkedIn DM/connection requests or WhatsApp. Keep capture, standing qualification, router and Sequence disabled until their own approval and credit scope; the shared guide owns execution.",
581
+ execution: "Read the inbound-led-outbound Recipe and pick its source (your posts, a competitor's or creator's posts, a topic, your profile viewers, or your followers and connections), resolve current company/ICP context, then read the oxygen-signals file references/audience-campaigns.md. Preserve the exact source, attribution, qualification rule/version and object IDs. Prepare a bounded native audience campaign for email, LinkedIn DM/connection requests or WhatsApp. Keep capture, standing qualification, router and Sequence disabled until their own approval and credit scope; the shared guide owns execution.",
584
582
  skills: ["oxygen-recipes", "oxygen-signals"],
585
583
  },
586
584
  recommendedTools: ["oxygen_recipes_list", "oxygen_recipes_get", "oxygen_context_resolve"],
587
585
  recommendedCommands: ["recipes list", "recipes show", "context resolve"],
588
- ...(namedPlay
589
- ? {
590
- exactCommand: `oxygen recipes show ${namedPlay} --json`,
591
- exactMcpCall: { tool: "oxygen_recipes_get", arguments: { slug: namedPlay } },
592
- }
593
- : {
594
- exactCommand: 'oxygen recipes list "linkedin audience" --view summary --json',
595
- exactMcpCall: { tool: "oxygen_recipes_list", arguments: { query: "linkedin audience", view: "summary" } },
596
- }),
586
+ exactCommand: `oxygen recipes show ${AUDIENCE_PLAY_SLUG} --json`,
587
+ exactMcpCall: { tool: "oxygen_recipes_get", arguments: { slug: AUDIENCE_PLAY_SLUG } },
597
588
  };
598
589
  }
599
590
  const explicit = explicitCapabilityIntent(normalized);
@@ -817,8 +808,11 @@ export function serializeCapabilityRoute(route, surfaceOrOptions = "cli", option
817
808
  function normalizeIntent(query) {
818
809
  return query.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
819
810
  }
820
- /** The six LinkedIn audience-campaign Recipes, by catalog slug. */
811
+ /** The one LinkedIn audience-campaign Recipe since the 2026-10-04 consolidation. */
812
+ const AUDIENCE_PLAY_SLUG = "inbound-led-outbound";
813
+ /** The six audience Recipes it absorbed or replaced; a bare old slug still names the play. */
821
814
  const AUDIENCE_PLAY_SLUGS = [
815
+ AUDIENCE_PLAY_SLUG,
822
816
  "own-post-engagers",
823
817
  "competitor-post-engagers",
824
818
  "creator-post-engagers",
@@ -952,8 +946,39 @@ const UGC_ROUTE_VIEW = {
952
946
  posture: "mixed",
953
947
  skills: ["oxygen-ugc", "oxygen-publishing"],
954
948
  };
949
+ /** Saved CRM preset configuration is a Records read, not an enrichment run. */
950
+ function isCrmEnrichmentConfigurationRead(query) {
951
+ if (!/\bcrm\b/.test(query) || !/\benrich\w*\b/.test(query))
952
+ return false;
953
+ // Remove only local negated execution restrictions. A later positive verb
954
+ // ("read, do not change this, then enable...") still rejects this read view.
955
+ const intent = query.replace(/\bnot a provider run\b/g, "")
956
+ .replace(/\b(?:do not|don't|without)\s+(?:any\s+)?(?:chang(?:e|ing)|runn?ing|run|enabl(?:e|ing)|disabl(?:e|ing)|configur(?:e|ing)|send(?:ing)?|sync(?:ing)?)\b/g, "");
957
+ if (/\b(tables?|columns?|workflows?|agents?|functions?|callables?|providers?|hubspot|sync|deals?|custom|fields?|values?|history|failed|items?)\b/.test(intent))
958
+ return false;
959
+ if (/\b(enable|disable|set|configure|change|run|running|backfill|send|create|start|execute|apply)\b/.test(intent))
960
+ return false;
961
+ return /\b(configur(?:ation|ed)|settings?|presets?|auto(?:matic)?|armed|enabled|backlog)\b/.test(intent)
962
+ && /\b(read|inspect|list|show|view|what|which|how many|current|existing|already)\b/.test(intent);
963
+ }
964
+ function crmEnrichmentReadRecommendation(query) {
965
+ const omitCounts = /\b(?:skip|omit|leave)\b.{0,45}\bcounts?\b|\bwithout\b.{0,45}\b(?:counts?|scanning historical records)\b/.test(query);
966
+ return {
967
+ tools: ["oxygen_crm_enrichment"], commands: ["crm enrichment list"],
968
+ exactCommand: `oxygen crm enrichment list${omitCounts ? " --no-backfill" : ""} --projection summary --json`,
969
+ exactMcpCall: { tool: "oxygen_crm_enrichment", arguments: { action: "list", ...(omitCounts ? { backfill: false } : {}), projection: "summary" } },
970
+ };
971
+ }
972
+ const CRM_ENRICHMENT_READ_VIEW = {
973
+ owns: "Saved automatic-enrichment presets and batch caps for native CRM companies/people, backed by Tables auto-run.",
974
+ notFor: "Running enrichment, provider catalog/sourcing, linked working Tables, external CRM sync, or changing the standing grant.",
975
+ execution: "Read the saved configuration with action=list, free through oxygen_capabilities_call: this tool also writes, so its paid/write annotations and built-in approval policy remain. Omit object and use backfill=false to skip historical counts; object reads ignore it. summary is compact; preset_count counts catalog entries, trigger_default is the current plan-default cap, and backfill:null means no count was produced. set/enable/disable change standing auto-run; action=backfill previews for free, while paid live batches require native approval and a ceiling.",
976
+ posture: "mixed",
977
+ };
955
978
  /** The card a route reports: Publishing's own, or its UGC view for a creator-program ask. */
956
979
  function routeCardFor(card, query) {
980
+ if (card.id === "records" && isCrmEnrichmentConfigurationRead(query))
981
+ return { ...card, ...CRM_ENRICHMENT_READ_VIEW };
957
982
  if (card.primitive === "publishing" && isUgcIntent(query))
958
983
  return { ...card, ...UGC_ROUTE_VIEW };
959
984
  const contactFind = card.id === "sourcing-and-provider-tools" ? oneShotContactFindingRecommendation(query) : null;
@@ -1540,6 +1565,8 @@ function oneShotContactFindingRecommendation(query) {
1540
1565
  function explicitCapabilityIntent(query) {
1541
1566
  if (sentLinkedInInvitationReadRecommendation(query))
1542
1567
  return ROUTE_BY_ID.get("connected-linkedin") ?? null;
1568
+ if (isCrmEnrichmentConfigurationRead(query))
1569
+ return ROUTE_BY_ID.get("records") ?? null;
1543
1570
  if (databaseRecommendation(query))
1544
1571
  return ROUTE_BY_ID.get("platform-operations") ?? null;
1545
1572
  if (oneShotContactFindingRecommendation(query))
@@ -1850,6 +1877,8 @@ function recommendationsFor(card, query) {
1850
1877
  const sent = card.id === "connected-linkedin" ? sentLinkedInInvitationReadRecommendation(query) : null;
1851
1878
  if (sent)
1852
1879
  return sent;
1880
+ if (card.id === "records" && isCrmEnrichmentConfigurationRead(query))
1881
+ return crmEnrichmentReadRecommendation(query);
1853
1882
  const database = card.id === "platform-operations" ? databaseRecommendation(query) : null;
1854
1883
  if (database)
1855
1884
  return database;
@@ -1,7 +1,7 @@
1
1
  import { bool, enumOf, int, obj, str } from "./schema.js";
2
2
  /** Read selectors only; action=backfill is a separate governed operation. */
3
3
  export const CRM_ENRICHMENT_READ_CONTRACT = obj({
4
- object: str({ minLength: 1, maxLength: 120, description: "CRM object slug (companies, people, deals)." }),
4
+ object: str({ minLength: 1, maxLength: 120, description: "CRM object: companies or people." }),
5
5
  backfill: bool({ description: "list without object: include stored historical backlog counts (default true). false skips the scans; object reads ignore it. Never runs enrichment." }),
6
6
  projection: enumOf(["summary", "full", "value"], { description: "summary: standing configuration without long arrays. full: legacy data. value: lossless JSON of path, continued with next_cursor. Omit for legacy output." }),
7
7
  path: str({ maxLength: 1024, description: "value: JSON Pointer into the selected native result; empty selects everything. Examples /objects/0/auto_run or /presets." }),
@@ -1,6 +1,7 @@
1
1
  export declare const CRM_NOTE_BODY_MAX_CHARS = 20000;
2
2
  export declare const CRM_NOTE_TITLE_MAX_CHARS = 300;
3
3
  export declare const CRM_FILE_LABEL_MAX_CHARS = 300;
4
+ export declare const CRM_FILE_ATTACH_DESCRIPTION = "Attach inline base64 bytes to a CRM record; each call creates a new file and attachment, including identical retries. If a response is lost, inspect the record's files before deciding to retry. Entire request must fit 2 MB; larger originals use upload prepare/complete. No credits.";
4
5
  export declare const CRM_NOTE_EDIT_CONTRACT: import("./schema.js").ContractObject<{
5
6
  readonly body: import("./schema.js").StringProperty;
6
7
  readonly title: {
@@ -2,6 +2,7 @@ import { bool, enumOf, int, nullable, obj, str } from "./schema.js";
2
2
  export const CRM_NOTE_BODY_MAX_CHARS = 20_000;
3
3
  export const CRM_NOTE_TITLE_MAX_CHARS = 300;
4
4
  export const CRM_FILE_LABEL_MAX_CHARS = 300;
5
+ export const CRM_FILE_ATTACH_DESCRIPTION = "Attach inline base64 bytes to a CRM record; each call creates a new file and attachment, including identical retries. If a response is lost, inspect the record's files before deciding to retry. Entire request must fit 2 MB; larger originals use upload prepare/complete. No credits.";
5
6
  export const CRM_NOTE_EDIT_CONTRACT = obj({
6
7
  body: str({ minLength: 1, maxLength: CRM_NOTE_BODY_MAX_CHARS, description: "Replacement Markdown text." }),
7
8
  title: nullable(str({ maxLength: CRM_NOTE_TITLE_MAX_CHARS, description: "Replacement title; null or empty clears." })),
@@ -2,8 +2,10 @@
2
2
  import { arr, bool, enumOf, int, nullable, num, obj, str } from "./schema.js";
3
3
  import { DATABASE_ATTACH_CONTRACT, DATABASE_MIGRATE_CONTRACT, DATABASE_QUERY_CONTRACT } from "./database.js";
4
4
  import { WHATSAPP_DRAFTS_READ_CONTRACT } from "./whatsapp-reads.js";
5
+ import { TABLE_FAVORITE_CONTRACT, TABLE_FAVORITES_LIST_CONTRACT, TABLE_ITEM_RESOLVE_CONTRACT, TABLE_LINK_SUGGEST_CONTRACT, TABLE_RECOVER_PENDING_CONTRACT } from "./table-operations.js";
5
6
  import { BILLING_OWNER_READ_CONTRACT } from "./billing-owner-read.js";
6
7
  import { COPILOT_SESSIONS_LIST_CONTRACT } from "./copilot-sessions.js";
8
+ import { CRM_FILE_ATTACH_CONTRACT, CRM_FILE_ATTACH_DESCRIPTION } from "./crm-support.js";
7
9
  const text = (description) => str(description === undefined ? {} : { description });
8
10
  const list = () => arr(str());
9
11
  const json = { type: "object", additionalProperties: true };
@@ -42,7 +44,7 @@ export const CUSTOMER_OPERATIONS = [
42
44
  operation("context.canonical.get", "/api/cli/context/canonical/get", "POST", "context canonical get", "oxygen_context_canonical_get", "knowledge", "Read the pinned active context asset for a type; present=false means none is set.", { type: text() }, ["type"], { mutates: false }),
43
45
  operation("crm.notes.update", "/api/cli/crm/notes/{note_id}", "PATCH", "crm notes update", "oxygen_crm_note_update", "crm", "Edit a CRM note's body, title or pinned state; an empty title clears it.", { body: text(), title: nullable(text()), pinned: bool() }, [], mcpOnly),
44
46
  operation("crm.notes.delete", "/api/cli/crm/notes/{note_id}", "DELETE", "crm notes delete", "oxygen_crm_note_delete", "crm", "Delete one CRM note in this workspace.", {}, [], mcpOnly),
45
- operation("crm.files.attach_inline", "/api/cli/crm/objects/{object}/records/{row_id}/files", "POST", "crm files attach-inline", "oxygen_crm_file_attach", "crm", "Attach base64 file bytes to one CRM record. For larger files use prepare-upload and complete-upload.", { name: text(), content_base64: text(), label: text() }, ["name", "content_base64"]),
47
+ operation("crm.files.attach_inline", "/api/cli/crm/objects/{object}/records/{row_id}/files", "POST", "crm files attach-inline", "oxygen_crm_file_attach", "crm", CRM_FILE_ATTACH_DESCRIPTION, CRM_FILE_ATTACH_CONTRACT.properties, [...(CRM_FILE_ATTACH_CONTRACT.required ?? [])]),
46
48
  operation("crm.files.prepare", "/api/cli/crm/objects/{object}/records/{row_id}/files/upload-url", "POST", "crm files prepare-upload", "oxygen_crm_file_prepare_upload", "crm", "Reserve a CRM file upload by exact name, length and SHA-256; returns a scoped upload URL.", { name: text(), byte_length: int({ minimum: 0 }), sha256: text() }, ["name", "byte_length", "sha256"]),
47
49
  operation("crm.files.complete", "/api/cli/crm/objects/{object}/records/{row_id}/files/{file_id}/uploaded", "POST", "crm files complete-upload", "oxygen_crm_file_complete_upload", "crm", "Verify a reserved CRM upload and attach it to its record; mismatched bytes remain pending.", { label: text() }),
48
50
  operation("crm.files.delete", "/api/cli/crm/files/{attachment_id}", "DELETE", "crm files detach", "oxygen_crm_file_delete", "crm", "Detach a CRM attachment by attachment_id, preserving the stored file.", {}, [], mcpOnly),
@@ -76,17 +78,17 @@ export const CUSTOMER_OPERATIONS = [
76
78
  operation("settings.beta.get", "/api/cli/settings/beta-features", "GET", "settings beta-features get", "oxygen_settings_beta_features_get", "workspace", "Read this workspace's beta-navigation opt-in."),
77
79
  operation("settings.beta.set", "/api/cli/settings/beta-features", "POST", "settings beta-features set", "oxygen_settings_beta_features_set", "workspace", "Enable or disable beta navigation for this workspace; admin or owner only.", { enabled: bool() }, ["enabled"]),
78
80
  operation("spintax.generate", "/api/cli/spintax", "POST", "spintax generate", "oxygen_spintax_generate", "sequences", "Generate spintax for outreach text. Costs exactly 3 credits; failed generation refunds the reservation.", { text: str({ minLength: 1, maxLength: 4000 }), section: str({ maxLength: 80 }) }, ["text"], { spends: true }),
79
- operation("table_action_items.resolve", "/api/cli/table-action-items/{item_id}/resolve", "POST", "table-actions resolve", "oxygen_table_action_item_resolve", "tables", "Record a verified uncertain Table action outcome; never repeats the external action.", { outcome: enumOf(["applied", "not_applied"]), note: text(), value: { oneOf: [text(), num(), bool(), json, rows(), { type: "null" }] } }, ["outcome"]),
81
+ operation("table_action_items.resolve", "/api/cli/table-action-items/{item_id}/resolve", "POST", "table-actions resolve", "oxygen_table_action_item_resolve", "tables", "Record a human-verified uncertain Table action outcome without repeating it. Use cells resolve for row/column coordinates.", TABLE_ITEM_RESOLVE_CONTRACT.properties, ["outcome"]),
80
82
  operation("table_ingestions.append", "/api/cli/table-ingestion-runs/{ingestion_run_id}/items", "POST", "table-ingestions append", "oxygen_table_ingestions_append_items", "runs", "Append bounded ingestion items to an existing run; preserves the run's approved scope.", { items: rows(), max_attempts: int({ minimum: 1 }) }, ["items"], cliOnly),
81
- operation("tables.favorites.get", "/api/cli/tables/favorite", "GET", "tables favorites", "oxygen_tables_favorites", "tables", "Read this member's table and folder favorites in the active workspace. A per-member preference; free (0 credits)."),
82
- operation("tables.favorite.set", "/api/cli/tables/favorite", "POST", "tables favorite", "oxygen_tables_favorite", "tables", "Star or unstar exactly one table or folder in the active workspace. Free (0 credits); reads back the saved per-member preference.", { table: text("Table id or slug. Pass exactly one of table or folder."), folder: text("Folder id or slug. Pass exactly one of table or folder."), favorite: bool({ description: "true stars; false unstars. Free (0 credits); changes only this member's preference." }) }, ["favorite"]),
83
+ operation("tables.favorites.get", "/api/cli/tables/favorite", "GET", "tables favorites", "oxygen_tables_favorites", "tables", "Page Table/folder stars (per-member preference; free (0 credits)); use next_cursor. Tool-catalog favorites are separate.", TABLE_FAVORITES_LIST_CONTRACT.properties, [], { generated: { cli: true, mcp: false } }),
84
+ operation("tables.favorite.set", "/api/cli/tables/favorite", "POST", "tables favorite", "oxygen_tables_favorite", "tables", "Star/unstar exactly one table or folder; read back saved per-member preference. Free (0 credits). Tool-catalog favorites are separate.", TABLE_FAVORITE_CONTRACT.properties, ["favorite"]),
83
85
  operation("tables.import.csv", "/api/cli/tables/import-csv", "POST", "tables import-csv", "oxygen_tables_import_csv", "tables", "Import inline CSV into a hosted Table through its durable ingestion run. Use staged upload for large files.", { csv_text: text(), csv_base64: text(), table: text(), table_name: text(), project: text(), filename: text(), upsert_key: text(), batch_size: int({ minimum: 1, maximum: 5000 }) }, [], cliOnly),
84
86
  operation("tables.import.prepare", "/api/cli/tables/import-url", "POST", "tables import-upload", "oxygen_tables_import_upload", "tables", "Prepare a scoped storage upload for a hosted Table import; uploads and imports are separate, inspectable steps.", { file_name: text(), content_type: text(), byte_length: int({ minimum: 1 }), trace_id: text(), format: enumOf(["csv", "json", "xlsx"]) }, ["file_name", "byte_length"]),
85
87
  operation("tables.import.preview", "/api/cli/tables/import-preview", "POST", "tables import-preview", "oxygen_tables_import_preview", "tables", "Preview a staged file's headers, rows and existing-table mapping; writes no imported rows.", { storage_key: text(), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), byte_length: int({ minimum: 1 }), table: text(), upsert_key: text() }, ["storage_key", "byte_length"], { mutates: false }),
86
88
  operation("tables.import.staged", "/api/cli/tables/import-staged", "POST", "tables import-staged", "oxygen_tables_import_staged", "tables", "Start a durable hosted import from this workspace's staged file; choose an existing table or create_name.", { table: text(), create_name: text(), project: text(), storage_key: text(), storage_bucket: text(), storage_provider: enumOf(["s3"]), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), sha256: text(), upsert_key: text(), source_key_map: json, byte_length: int({ minimum: 1 }), row_count: int({ minimum: 1 }), max_concurrency: int({ minimum: 1, maximum: 1000 }), source: enumOf(["cli", "mcp", "web"]), batch_size: int({ minimum: 1, maximum: 5000 }) }, ["storage_key", "byte_length"]),
87
89
  operation("tables.import.file", "/api/cli/tables/import-file", "POST", "tables import-file", "oxygen_tables_import_file", "tables", "Import small base64 file bytes through the shared multipart import API; use staged upload for large files.", { content_base64: text(), file_name: text(), content_type: text(), table: text(), table_name: text(), project: text(), format: enumOf(["csv", "json", "xlsx"]), sheet: text(), upsert_key: text(), source_key_map: json, source_hash: text(), batch_size: int({ minimum: 1, maximum: 5000 }), max_concurrency: int({ minimum: 1 }) }, ["content_base64", "file_name"], { multipart: { contentField: "content_base64", nameField: "file_name", typeField: "content_type", fileField: "file" } }),
88
- operation("tables.link.suggest", "/api/cli/tables/link/suggest", "POST", "tables link-suggest", "oxygen_tables_link_suggest", "tables", "Preview which columns can link two Tables and how many rows match; creates no relation.", { table: text(), to: text(), on: text(), scan_limit: int({ minimum: 1 }) }, ["table", "to"], { mutates: false }),
89
- operation("tables.recover_pending", "/api/cli/tables/recover-pending", "POST", "tables recover-pending", "oxygen_tables_recover_pending", "tables", "Recover bounded pending cells in one Table through the platform's native recovery path.", { table: text(), max_items: int({ minimum: 1 }) }, ["table"], mcpOnly),
90
+ operation("tables.link.suggest", "/api/cli/tables/link/suggest", "POST", "tables link-suggest", "oxygen_tables_link_suggest", "tables", "Plan column links and matching row counts between Tables; creates no relation. Apply with tables link bulk.", TABLE_LINK_SUGGEST_CONTRACT.properties, ["table", "to"], { mutates: false }),
91
+ operation("tables.recover_pending", "/api/cli/tables/recover-pending", "POST", "tables recover-pending", "oxygen_tables_recover_pending", "tables", "Poll pending BetterContact requests and save recovered phones; no new orders. Does not resume imports.", TABLE_RECOVER_PENDING_CONTRACT.properties, ["table"], { ...mcpOnly, annotations: { openWorldHint: true } }),
90
92
  operation("tables.webhooks.create", "/api/cli/tables/webhooks", "POST", "tables webhook create", "oxygen_table_webhook_create", "tables", "Create an inbound Table webhook or platform LinkedIn feed. Optional auto_run is standing permission with explicit or plan-default delivery ceilings.", { table: text(), name: text(), mode: enumOf(["insert", "upsert"]), upsert_key: text(), items_path: text(), event_id_path: text(), event_type_path: text(), occurred_at_path: text(), auth_mode: enumOf(["none", "secret"]), feed_kind: enumOf(["generic", "unipile"]), unipile_account_id: text(), event_types: list(), mapping: text(), auto_run: json }, ["table"], mcpOnly),
91
93
  operation("tables.webhooks.update", "/api/cli/tables/webhooks", "PATCH", "tables webhook update", "oxygen_table_webhook_update", "tables", "Edit a Table webhook's name, state or standing auto_run; auth_mode=secret rotates its secret and returns it once.", { endpoint_id: text(), name: text(), status: enumOf(["active", "disabled"]), auth_mode: enumOf(["none", "secret"]), auto_run: json, clear_auto_run: bool() }, ["endpoint_id"], mcpOnly),
92
94
  operation("whatsapp.draft", "/api/cli/whatsapp/draft", "POST", "whatsapp draft", "oxygen_whatsapp_draft", "inbox", "Generate a WhatsApp reply draft in the workspace voice, or approve a reviewed draft for an existing chat. Draft generation uses managed AI credits.", { conversation_id: text(), goal: text(), row_id: text(), object: text(), approved: bool(), draft_id: text(), text: text() }, ["conversation_id"], { ...cliOnly, spends: true }),
@@ -55,6 +55,9 @@ export interface OneOfProperty {
55
55
  description?: string;
56
56
  }
57
57
  export type JsonProperty = {
58
+ type?: never;
59
+ description?: string;
60
+ } | {
58
61
  $ref: string;
59
62
  } | StringProperty | NumberProperty | BooleanProperty | NullProperty | ObjectProperty | ArrayProperty | OneOfProperty;
60
63
  /** The top level of a contract: a closed object. */
@@ -88,6 +91,11 @@ export declare function num(options?: NumberOptions): {
88
91
  "x-clamp"?: true;
89
92
  };
90
93
  export declare function bool(options?: Described): BooleanProperty;
94
+ /** Any JSON value, including null; used for native cell-result values. */
95
+ export declare function jsonValue(options?: Described): {
96
+ type?: never;
97
+ description?: string;
98
+ };
91
99
  export declare function str(options?: Described & {
92
100
  minLength?: number;
93
101
  maxLength?: number;
@@ -31,6 +31,10 @@ export function num(options) {
31
31
  export function bool(options = {}) {
32
32
  return described({ type: "boolean" }, options.description);
33
33
  }
34
+ /** Any JSON value, including null; used for native cell-result values. */
35
+ export function jsonValue(options = {}) {
36
+ return { ...options };
37
+ }
34
38
  export function str(options = {}) {
35
39
  const { description, ...rest } = options;
36
40
  return described({ type: "string", ...rest }, description);
@@ -172,6 +176,8 @@ function walkValue(schema, value, path, walk) {
172
176
  walk.issues.push({ path, message: `${path} does not match any accepted shape.` });
173
177
  return value;
174
178
  }
179
+ if (schema.type === undefined)
180
+ return value;
175
181
  if (schema.type === "null") {
176
182
  if (value !== null)
177
183
  walk.issues.push({ path, message: `${path} must be null.` });
@@ -0,0 +1,38 @@
1
+ export declare const SEQUENCE_ANALYTICS_READ_CONTRACT: import("./schema.js").ContractObject<{
2
+ readonly projection: {
3
+ type: "string";
4
+ enum: readonly ("full" | "summary" | "value")[];
5
+ description?: string;
6
+ };
7
+ readonly path: import("./schema.js").StringProperty;
8
+ readonly value_cursor: import("./schema.js").StringProperty;
9
+ readonly max_chars: {
10
+ type: "integer";
11
+ description?: string;
12
+ minimum?: number;
13
+ maximum?: number;
14
+ "x-clamp"?: true;
15
+ };
16
+ readonly range: {
17
+ type: "string";
18
+ enum: readonly ("14d" | "180d" | "28d" | "30d" | "365d" | "7d" | "90d" | "all")[];
19
+ description?: string;
20
+ };
21
+ readonly from: import("./schema.js").StringProperty;
22
+ readonly to: import("./schema.js").StringProperty;
23
+ readonly sequence: import("./schema.js").StringProperty;
24
+ readonly campaigns: import("./schema.js").StringProperty;
25
+ readonly senders: import("./schema.js").StringProperty;
26
+ }, never>;
27
+ /** Preserve shipped query precedence, repeated/CSV senders, and native range
28
+ * coercion. Only opt-in representation controls are separately validated. */
29
+ export declare function sequenceAnalyticsSelection(params: URLSearchParams): {
30
+ range: {
31
+ preset: string | null;
32
+ from: string | null;
33
+ to: string | null;
34
+ };
35
+ sequenceId: string | null;
36
+ sequenceIds: string[];
37
+ senders: string[];
38
+ };
@@ -0,0 +1,22 @@
1
+ import { enumOf, obj, str } from "./schema.js";
2
+ import { STORED_READ_WINDOW_FIELDS } from "./stored-read-window.js";
3
+ export const SEQUENCE_ANALYTICS_READ_CONTRACT = obj({
4
+ range: enumOf(["7d", "14d", "28d", "30d", "90d", "180d", "365d", "all"], { description: "Default 14d. all starts at oldest selected campaign, at most 1830 days; daily<=92 days, weekly<=550, else monthly." }),
5
+ from: str({ description: "Custom start date (YYYY-MM-DD)." }),
6
+ to: str({ description: "Custom end date (YYYY-MM-DD)." }),
7
+ sequence: str({ description: "Exact campaign id/slug, including outside latest 500. Single scope ignores campaigns; includes sender/message performance." }),
8
+ campaigns: str({ description: "CSV ids/slugs: org totals, series, email attribution and operational summary only. Roster/fleet/per-campaign stats retain latest 500; ignored with sequence." }),
9
+ senders: str({ description: "CSV analytics.senders keys or sender/profile ids (oxygen_senders_profiles_list). Narrows counts/funnels/series/attribution/drilldowns, not roster or operational health/work." }),
10
+ ...STORED_READ_WINDOW_FIELDS,
11
+ });
12
+ /** Preserve shipped query precedence, repeated/CSV senders, and native range
13
+ * coercion. Only opt-in representation controls are separately validated. */
14
+ export function sequenceAnalyticsSelection(params) {
15
+ const csv = (values) => values.flatMap(value => value.split(",")).map(value => value.trim()).filter(value => value.length > 0);
16
+ return {
17
+ range: { preset: params.get("range"), from: params.get("from"), to: params.get("to") },
18
+ sequenceId: params.get("sequence") ?? params.get("sequence_id") ?? null,
19
+ sequenceIds: csv([params.get("campaigns") ?? params.get("sequences") ?? ""]),
20
+ senders: csv(params.getAll("senders")),
21
+ };
22
+ }
@@ -0,0 +1,31 @@
1
+ /** Bounded table discovery, shared by the HTTP query, CLI flags and MCP. */
2
+ import { type Infer } from "./schema.js";
3
+ export declare const TABLE_LIST_QUERY_CONTRACT: import("./schema.js").ContractObject<{
4
+ readonly project: import("./schema.js").StringProperty;
5
+ readonly tag: import("./schema.js").StringProperty;
6
+ readonly include_archived: import("./schema.js").BooleanProperty;
7
+ readonly limit: {
8
+ type: "integer";
9
+ description?: string;
10
+ minimum?: number;
11
+ maximum?: number;
12
+ "x-clamp"?: true;
13
+ };
14
+ readonly cursor: import("./schema.js").StringProperty;
15
+ readonly query: import("./schema.js").StringProperty;
16
+ readonly projection: {
17
+ type: "string";
18
+ enum: readonly ("full" | "summary")[];
19
+ description?: string;
20
+ };
21
+ readonly max_chars: {
22
+ type: "integer";
23
+ description?: string;
24
+ minimum?: number;
25
+ maximum?: number;
26
+ "x-clamp"?: true;
27
+ };
28
+ }, never>;
29
+ export type TableListQuery = Infer<typeof TABLE_LIST_QUERY_CONTRACT>;
30
+ /** Unknown query keys are ignored for compatibility; only declared keys travel. */
31
+ export declare function tableListQueryParams(query: TableListQuery): URLSearchParams;
@@ -0,0 +1,22 @@
1
+ /** Bounded table discovery, shared by the HTTP query, CLI flags and MCP. */
2
+ import { bool, enumOf, int, obj, str } from "./schema.js";
3
+ export const TABLE_LIST_QUERY_CONTRACT = obj({
4
+ project: str({ minLength: 1, maxLength: 200 }),
5
+ tag: str(),
6
+ include_archived: bool(),
7
+ limit: int({ minimum: 1, maximum: 1000, clamp: true }),
8
+ cursor: str({ maxLength: 2000 }),
9
+ query: str({ maxLength: 200, description: "Name/slug substring." }),
10
+ projection: enumOf(["full", "summary"]),
11
+ max_chars: int({ minimum: 2000, maximum: 40000 }),
12
+ });
13
+ /** Unknown query keys are ignored for compatibility; only declared keys travel. */
14
+ export function tableListQueryParams(query) {
15
+ const params = new URLSearchParams();
16
+ for (const key of Object.keys(TABLE_LIST_QUERY_CONTRACT.properties)) {
17
+ const value = query[key];
18
+ if (value !== undefined)
19
+ params.set(key, String(value));
20
+ }
21
+ return params;
22
+ }
@@ -0,0 +1,58 @@
1
+ export declare const MAX_TABLE_RECOVERY_ITEMS = 1000;
2
+ export declare const TABLE_FAVORITES_LIST_CONTRACT: import("./schema.js").ContractObject<{
3
+ readonly limit: {
4
+ type: "integer";
5
+ description?: string;
6
+ minimum?: number;
7
+ maximum?: number;
8
+ "x-clamp"?: true;
9
+ };
10
+ readonly cursor: import("./schema.js").StringProperty;
11
+ readonly max_chars: {
12
+ type: "integer";
13
+ description?: string;
14
+ minimum?: number;
15
+ maximum?: number;
16
+ "x-clamp"?: true;
17
+ };
18
+ }, never>;
19
+ export declare const TABLE_FAVORITE_CONTRACT: import("./schema.js").ContractObject<{
20
+ readonly table: import("./schema.js").StringProperty;
21
+ readonly folder: import("./schema.js").StringProperty;
22
+ readonly favorite: import("./schema.js").BooleanProperty;
23
+ }, "favorite">;
24
+ export declare const TABLE_LINK_SUGGEST_CONTRACT: import("./schema.js").ContractObject<{
25
+ readonly table: import("./schema.js").StringProperty;
26
+ readonly to: import("./schema.js").StringProperty;
27
+ readonly on: import("./schema.js").StringProperty;
28
+ readonly scan_limit: {
29
+ type: "number";
30
+ description?: string;
31
+ minimum?: number;
32
+ maximum?: number;
33
+ "x-clamp"?: true;
34
+ };
35
+ }, "table" | "to">;
36
+ export declare const TABLE_EFFECT_OUTCOMES: readonly ["applied", "not_applied"];
37
+ export declare const TABLE_ITEM_RESOLVE_CONTRACT: import("./schema.js").ContractObject<{
38
+ readonly outcome: {
39
+ type: "string";
40
+ enum: readonly ("applied" | "not_applied")[];
41
+ description?: string;
42
+ };
43
+ readonly note: import("./schema.js").StringProperty;
44
+ readonly value: {
45
+ type?: never;
46
+ description?: string;
47
+ };
48
+ }, "outcome">;
49
+ export declare const TABLE_RECOVER_PENDING_CONTRACT: import("./schema.js").ContractObject<{
50
+ readonly table: import("./schema.js").StringProperty;
51
+ readonly max_items: {
52
+ type: "integer";
53
+ description?: string;
54
+ minimum?: number;
55
+ maximum?: number;
56
+ "x-clamp"?: true;
57
+ };
58
+ }, "table">;
@@ -0,0 +1,30 @@
1
+ /** Customer Table operations; the native routes retain identity and effect guards. */
2
+ import { bool, enumOf, int, jsonValue, num, obj, str } from "./schema.js";
3
+ const table = str({ minLength: 1, description: "Table id, slug, or Oxygen table URL." });
4
+ export const MAX_TABLE_RECOVERY_ITEMS = 1000;
5
+ export const TABLE_FAVORITES_LIST_CONTRACT = obj({
6
+ limit: int({ minimum: 1, maximum: 200, description: "Page size; MCP default 20." }),
7
+ cursor: str({ maxLength: 512, description: "Previous next_cursor; restart if stars changed." }),
8
+ max_chars: int({ minimum: 500, maximum: 10_000, description: "Complete response cap in characters." }),
9
+ });
10
+ export const TABLE_FAVORITE_CONTRACT = obj({
11
+ table: str({ minLength: 1, description: "Table id, slug, or Oxygen table URL. Pass exactly one of table or folder." }),
12
+ folder: str({ minLength: 1, description: "Folder id or slug. Pass exactly one of table or folder." }),
13
+ favorite: bool({ description: "true stars; false unstars." }),
14
+ }, { required: ["favorite"] });
15
+ export const TABLE_LINK_SUGGEST_CONTRACT = obj({
16
+ table,
17
+ to: table,
18
+ on: str({ description: "Optional join column key or id." }),
19
+ scan_limit: num({ description: "Native planner scan limit; bounded server-side." }),
20
+ }, { required: ["table", "to"] });
21
+ export const TABLE_EFFECT_OUTCOMES = ["applied", "not_applied"];
22
+ export const TABLE_ITEM_RESOLVE_CONTRACT = obj({
23
+ outcome: enumOf(TABLE_EFFECT_OUTCOMES),
24
+ note: str(),
25
+ value: jsonValue({ description: "Verified cell result; any JSON, including null." }),
26
+ }, { required: ["outcome"] });
27
+ export const TABLE_RECOVER_PENDING_CONTRACT = obj({
28
+ table: str({ minLength: 1, description: "Table id or slug." }),
29
+ max_items: int({ minimum: 1, maximum: MAX_TABLE_RECOVERY_ITEMS, description: "Pending requests to poll; default 1000." }),
30
+ }, { required: ["table"] });
@@ -0,0 +1,79 @@
1
+ export declare const WORKFLOW_RUN_STATUS_VALUES: readonly ["queued", "running", "waiting", "awaiting_approval", "completed", "failed", "canceling", "canceled"];
2
+ export declare const WORKFLOW_RUN_MODE_VALUES: readonly ["dry_run", "live", "smoke_test"];
3
+ export declare const WORKFLOW_RUN_LIST_CONTRACT: import("./schema.js").ContractObject<{
4
+ readonly projection: {
5
+ type: "string";
6
+ enum: readonly ("full" | "summary" | "value")[];
7
+ description?: string;
8
+ };
9
+ readonly path: import("./schema.js").StringProperty;
10
+ readonly value_cursor: import("./schema.js").StringProperty;
11
+ readonly max_chars: {
12
+ type: "integer";
13
+ description?: string;
14
+ minimum?: number;
15
+ maximum?: number;
16
+ "x-clamp"?: true;
17
+ };
18
+ readonly workflow: import("./schema.js").StringProperty;
19
+ readonly workflow_id: import("./schema.js").StringProperty;
20
+ readonly status: import("./schema.js").StringProperty;
21
+ readonly mode: import("./schema.js").StringProperty;
22
+ readonly cursor: import("./schema.js").StringProperty;
23
+ readonly limit: {
24
+ type: "integer";
25
+ description?: string;
26
+ minimum?: number;
27
+ maximum?: number;
28
+ "x-clamp"?: true;
29
+ };
30
+ readonly run_id: import("./schema.js").StringProperty;
31
+ }, never>;
32
+ export declare const WORKFLOW_RUN_LIST_MCP_CONTRACT: import("./schema.js").ContractObject<{
33
+ readonly projection: {
34
+ type: "string";
35
+ enum: readonly ("full" | "summary" | "value")[];
36
+ description?: string;
37
+ };
38
+ readonly path: import("./schema.js").StringProperty;
39
+ readonly value_cursor: import("./schema.js").StringProperty;
40
+ readonly max_chars: {
41
+ type: "integer";
42
+ description?: string;
43
+ minimum?: number;
44
+ maximum?: number;
45
+ "x-clamp"?: true;
46
+ };
47
+ readonly workflow: import("./schema.js").StringProperty;
48
+ readonly workflow_id: import("./schema.js").StringProperty;
49
+ readonly cursor: import("./schema.js").StringProperty;
50
+ readonly limit: {
51
+ type: "integer";
52
+ description?: string;
53
+ minimum?: number;
54
+ maximum?: number;
55
+ "x-clamp"?: true;
56
+ };
57
+ readonly run_id: import("./schema.js").StringProperty;
58
+ readonly status: {
59
+ type: "string";
60
+ enum: readonly ("awaiting_approval" | "canceled" | "canceling" | "completed" | "failed" | "queued" | "running" | "waiting")[];
61
+ description?: string;
62
+ };
63
+ readonly mode: {
64
+ type: "string";
65
+ enum: readonly ("dry_run" | "live" | "smoke_test")[];
66
+ description?: string;
67
+ };
68
+ }, never>;
69
+ /** The native GET keeps raw strings, canonical alias precedence and absent
70
+ * values. CLI legacy trimming is an explicit adapter above this contract. */
71
+ export declare function workflowRunListQueryParams(args: Record<string, unknown>): URLSearchParams;
72
+ /** Exact native HTTP scalar selection, including blank canonical precedence. */
73
+ export declare function readWorkflowRunListSelectors(params: URLSearchParams): {
74
+ workflowIdOrSlug: string | null;
75
+ status: string | null;
76
+ mode: string | null;
77
+ cursor: string | null;
78
+ runId: string | null;
79
+ };
@@ -0,0 +1,38 @@
1
+ import { enumOf, int, obj, str } from "./schema.js";
2
+ import { STORED_READ_WINDOW_FIELDS } from "./stored-read-window.js";
3
+ export const WORKFLOW_RUN_STATUS_VALUES = ["queued", "running", "waiting", "awaiting_approval", "completed", "failed", "canceling", "canceled"];
4
+ export const WORKFLOW_RUN_MODE_VALUES = ["dry_run", "live", "smoke_test"];
5
+ export const WORKFLOW_RUN_LIST_CONTRACT = obj({
6
+ workflow: str({ minLength: 1, maxLength: 400, description: "Workflow id, slug, exact name, or copied URL." }),
7
+ workflow_id: str({ minLength: 1, maxLength: 400, description: "Canonical workflow ref; takes precedence over workflow on HTTP/MCP." }),
8
+ status: str({ description: "Stored status filter: queued, running, waiting, awaiting_approval, completed, failed, canceling, canceled. Native validation trims and ignores case." }),
9
+ mode: str({ description: "Stored execution mode filter: dry_run, live or smoke_test. Never runs a workflow." }),
10
+ cursor: str({ description: "Previous nextCursor, echoed unchanged with the same filters." }),
11
+ limit: int({ minimum: 1, maximum: 500, description: "Runs per page; native default 50." }),
12
+ run_id: str({ minLength: 1, maxLength: 80, description: "Exact stored run UUID within the same workflow/status/mode/cursor filters; opt-in windows only." }),
13
+ ...STORED_READ_WINDOW_FIELDS,
14
+ });
15
+ export const WORKFLOW_RUN_LIST_MCP_CONTRACT = obj({
16
+ ...WORKFLOW_RUN_LIST_CONTRACT.properties,
17
+ status: enumOf(WORKFLOW_RUN_STATUS_VALUES, { description: "Stored run status filter." }),
18
+ mode: enumOf(WORKFLOW_RUN_MODE_VALUES, { description: "Stored execution mode filter; executes nothing." }),
19
+ });
20
+ /** The native GET keeps raw strings, canonical alias precedence and absent
21
+ * values. CLI legacy trimming is an explicit adapter above this contract. */
22
+ export function workflowRunListQueryParams(args) {
23
+ const pairs = [];
24
+ for (const key of Object.keys(WORKFLOW_RUN_LIST_CONTRACT.properties)) {
25
+ const value = args[key];
26
+ if (value !== undefined && value !== null)
27
+ pairs.push([key, String(value)]);
28
+ }
29
+ return new URLSearchParams(pairs);
30
+ }
31
+ /** Exact native HTTP scalar selection, including blank canonical precedence. */
32
+ export function readWorkflowRunListSelectors(params) {
33
+ return {
34
+ workflowIdOrSlug: params.get("workflow_id") ?? params.get("workflow"),
35
+ status: params.get("status"), mode: params.get("mode"),
36
+ cursor: params.get("cursor"), runId: params.get("run_id"),
37
+ };
38
+ }
@@ -12,8 +12,8 @@ export declare const COPILOT_JOURNEYS: readonly [{
12
12
  readonly slug: "inbound-led-outbound";
13
13
  readonly title: "Inbound-led outbound";
14
14
  readonly description: "Turn the people who engage with your LinkedIn posts into a capped, evidence-gated warm sequence with replies routed to your CRM.";
15
- readonly recipeSlugs: readonly ["inbound-led-outbound", "linkedin-network-first-motion", "founder-posting-system", "reply-to-meeting"];
16
- readonly seedMessage: "I chose the inbound-led-outbound recipe. Help me set up inbound-led outbound for my LinkedIn posts: watch who engages with them every day, assess each person against my ICP, hand me the strong fits, put the possible fits into a draft LinkedIn sequence on a schedule, move replies into my CRM, and write what we learn back into my knowledge wiki. Inspect the workspace first, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.";
15
+ readonly recipeSlugs: readonly ["inbound-led-outbound", "founder-posting-system", "reply-to-meeting"];
16
+ readonly seedMessage: "I chose the inbound-led-outbound recipe. Help me turn the people paying attention to me on LinkedIn into conversations: capture a small sample, score each person against my ideal customer, show me the good fits and draft a LinkedIn message I approve. Inspect the workspace first and resume from what exists. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.";
17
17
  }, {
18
18
  readonly slug: "email-infra-setup";
19
19
  readonly title: "Outbound infrastructure";
@@ -2,8 +2,8 @@ export declare const COPILOT_JOURNEYS_GENERATED: readonly [{
2
2
  readonly slug: "inbound-led-outbound";
3
3
  readonly title: "Inbound-led outbound";
4
4
  readonly description: "Turn the people who engage with your LinkedIn posts into a capped, evidence-gated warm sequence with replies routed to your CRM.";
5
- readonly recipeSlugs: readonly ["inbound-led-outbound", "linkedin-network-first-motion", "founder-posting-system", "reply-to-meeting"];
6
- readonly seedMessage: "I chose the inbound-led-outbound recipe. Help me set up inbound-led outbound for my LinkedIn posts: watch who engages with them every day, assess each person against my ICP, hand me the strong fits, put the possible fits into a draft LinkedIn sequence on a schedule, move replies into my CRM, and write what we learn back into my knowledge wiki. Inspect the workspace first, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.";
5
+ readonly recipeSlugs: readonly ["inbound-led-outbound", "founder-posting-system", "reply-to-meeting"];
6
+ readonly seedMessage: "I chose the inbound-led-outbound recipe. Help me turn the people paying attention to me on LinkedIn into conversations: capture a small sample, score each person against my ideal customer, show me the good fits and draft a LinkedIn message I approve. Inspect the workspace first and resume from what exists. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.";
7
7
  }, {
8
8
  readonly slug: "email-infra-setup";
9
9
  readonly title: "Outbound infrastructure";
@@ -6,8 +6,8 @@ export const COPILOT_JOURNEYS_GENERATED = [
6
6
  slug: "inbound-led-outbound",
7
7
  title: "Inbound-led outbound",
8
8
  description: "Turn the people who engage with your LinkedIn posts into a capped, evidence-gated warm sequence with replies routed to your CRM.",
9
- recipeSlugs: ["inbound-led-outbound", "linkedin-network-first-motion", "founder-posting-system", "reply-to-meeting"],
10
- seedMessage: "I chose the inbound-led-outbound recipe. Help me set up inbound-led outbound for my LinkedIn posts: watch who engages with them every day, assess each person against my ICP, hand me the strong fits, put the possible fits into a draft LinkedIn sequence on a schedule, move replies into my CRM, and write what we learn back into my knowledge wiki. Inspect the workspace first, follow the recipe, and resume from durable evidence instead of starting over. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.",
9
+ recipeSlugs: ["inbound-led-outbound", "founder-posting-system", "reply-to-meeting"],
10
+ seedMessage: "I chose the inbound-led-outbound recipe. Help me turn the people paying attention to me on LinkedIn into conversations: capture a small sample, score each person against my ideal customer, show me the good fits and draft a LinkedIn message I approve. Inspect the workspace first and resume from what exists. Do not connect an account, enroll anyone, send anything, or spend credits without asking me first.",
11
11
  },
12
12
  {
13
13
  slug: "email-infra-setup",