@oxygen-agent/cli 1.1013.27 → 1.1013.59

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.
package/README.md CHANGED
@@ -34,4 +34,4 @@ oxygen update
34
34
 
35
35
  For product documentation, visit https://oxygen-agent.com/docs. For support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.1013.27
37
+ Version: 1.1013.59
@@ -113,6 +113,7 @@ const MUTATING_COMMANDS = new Set([
113
113
  // Creates an approval or Checkout link; a first plan's Checkout can start the
114
114
  // first-month offer window. The plan changes only after an admin confirms.
115
115
  "billing change",
116
+ "billing retain",
116
117
  // --approved records a domain's renewal exit, or with --revoke resumes renewals.
117
118
  "managed-inboxes exit",
118
119
  // A default preview is free; approval restores provider seats and billing.
package/dist/index.js CHANGED
@@ -11652,11 +11652,12 @@ Examples:
11652
11652
  }))
11653
11653
  .addCommand(new Command("cancel")
11654
11654
  .description("Turn off subscription renewal. For a yearly commitment paid monthly, remaining monthly payments continue through commitment_ends_at; the plan stays usable until then. Other plans end at their paid period or trial end with no further subscription renewal charge. This command costs no Oxygen credits. Separately funded external resources keep renewing; preview `workspace infrastructure-exit` to end those too. Prints the end date; undo before then with billing resume.")
11655
+ .option("--comment <text>", "Written cancellation feedback (1–2,000 characters).")
11655
11656
  .option("--json", "Print a JSON envelope.")
11656
11657
  .action(async (options) => {
11657
11658
  await handleAsyncAction("billing cancel", options, () => requestOxygen("/api/cli/billing/cancel", {
11658
11659
  method: "POST",
11659
- body: { action: "cancel" },
11660
+ body: { action: "cancel", ...(options.comment !== undefined ? { comment: options.comment } : {}) },
11660
11661
  }));
11661
11662
  }))
11662
11663
  .addCommand(new Command("resume")
@@ -11667,6 +11668,19 @@ Examples:
11667
11668
  method: "POST",
11668
11669
  body: { action: "resume" },
11669
11670
  }));
11671
+ }))
11672
+ .addCommand(new Command("retention-offer")
11673
+ .description("Preview a one-month discount before cancelling your monthly subscription. Reports eligibility, the next invoice price and normal price. Reads only; no discount is applied.")
11674
+ .option("--json", "Print a JSON envelope.")
11675
+ .action(async (options) => {
11676
+ await handleAsyncAction("billing retention-offer", options, () => requestOxygen("/api/cli/billing/cancel", { method: "POST", body: { action: "offer" } }));
11677
+ }))
11678
+ .addCommand(new Command("retain")
11679
+ .description("Stay subscribed with the one-month retention discount from billing retention-offer. Restores renewal if cancellation was scheduled. No charge today; the normal price returns afterwards.")
11680
+ .requiredOption("--offer <id>", "The offer_id from the retention offer preview.")
11681
+ .option("--json", "Print a JSON envelope.")
11682
+ .action(async (options) => {
11683
+ await handleAsyncAction("billing retain", options, () => requestOxygen("/api/cli/billing/cancel", { method: "POST", body: { action: "retain", offer_id: options.offer } }));
11670
11684
  }))
11671
11685
  .addCommand(new Command("topup")
11672
11686
  .description("Buy a custom amount of on-demand credits. Run without an amount to see the current price per 100 credits (`usd_per_100_credits`), the allowed range and the packs; checkout happens in Stripe, the price is shown before you pay, and purchased credits never expire. Plan credits cost less. `usd_per_1k_credits` is a deprecated name for the same per-100 price, not a price per 1,000 credits.")
@@ -4,7 +4,7 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
4
4
  .command("ugc")
5
5
  .description("Manage UGC programs and independent creator participations: voice, posts, approvals, sponsorship and bounded amplification. Requires UGC availability. Start with memberships list in a creator workspace. See the oxygen-ugc skill; JSON reads return web_url for inspection.");
6
6
  const groupDescriptions = {
7
- programs: "Programs you operate or joined; discover public programs from other brands and apply to them. Lifecycle: create, update, duplicate (settings), archive (stops future work, keeps shared history, reversible with restore) and delete (permanent; only while no creator has ever joined).",
7
+ programs: "Programs you operate or joined; discover public programs from other brands and apply to or join them. Lifecycle: create, update, duplicate (settings), archive (stops future work, keeps shared history, reversible with restore) and delete (permanent; only while no creator has ever joined).",
8
8
  creators: "Creator participations: invite, set one creator's own posting deal, revoke an invite, accept in the creator workspace, connect a sender, change approval authority, or revoke.",
9
9
  memberships: "Active programs this creator workspace has joined, each with your deal (posting_target), this period's progress and your published posts. Your own numbers only. Read these before choosing a program for a personal post.",
10
10
  applications: "Applications to public programs: the ones you sent as a creator, or the ones your program received as a brand.",
@@ -83,9 +83,15 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
83
83
  .action((options) => handle("ugc creators invitation", options, () => requestOxygen(`/api/cli/ugc?${new URLSearchParams({ view: "invitation", token: String(options.token) })}`)));
84
84
  group("programs")
85
85
  .command("discover")
86
- .description("Public programs from every brand on Oxygen that you can apply to, with each brand's deal and brief excerpt, and whether you already joined or applied. Free, read-only.")
86
+ .description("Public programs from every brand on Oxygen that you can apply to, with each brand's deal, brief excerpt and join_policy (automatic: you join at once; approval: the brand decides), and whether you already joined or applied. Free, read-only.")
87
+ .option("--program-id <id>", "Only this program, e.g. the id in a join link (…/ugc?join=<id>).")
87
88
  .option("--json", "Print a JSON envelope.")
88
- .action((options) => handle("ugc programs discover", options, () => requestOxygen("/api/cli/ugc?view=discover")));
89
+ .action((options) => handle("ugc programs discover", options, () => {
90
+ const query = new URLSearchParams({ view: "discover" });
91
+ if (options.programId)
92
+ query.set("program_id", String(options.programId));
93
+ return requestOxygen(`/api/cli/ugc?${query}`);
94
+ }));
89
95
  group("applications")
90
96
  .command("list")
91
97
  .description("Applications to public programs. Without --program-id: the ones this creator workspace sent, with status. With --program-id: the ones your program received (brand), pending first. Read-only.")
@@ -336,7 +342,13 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
336
342
  {
337
343
  name: "visibility",
338
344
  kind: "string",
339
- description: "private (default: invitation only) or public (listed in every creator's Discover, where creators apply and you approve).",
345
+ description: "private (default: invitation only) or public (listed in every creator's Discover, where creators apply).",
346
+ required: false,
347
+ },
348
+ {
349
+ name: "join_policy",
350
+ kind: "string",
351
+ description: "Public programs only: approval (default: you approve each application) or automatic (creators join at once, from Discover or the program's join_url). automatic needs --visibility public.",
340
352
  required: false,
341
353
  },
342
354
  ],
@@ -397,7 +409,13 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
397
409
  {
398
410
  name: "visibility",
399
411
  kind: "string",
400
- description: "private (default: invitation only) or public (listed in every creator's Discover, where creators apply and you approve).",
412
+ description: "private (invitation only; also turns automatic join off) or public (listed in every creator's Discover, where creators apply).",
413
+ required: false,
414
+ },
415
+ {
416
+ name: "join_policy",
417
+ kind: "string",
418
+ description: "approval (you approve each application) or automatic (creators join at once, from Discover or the program's join_url). automatic needs a public program. programs get shows join_policy and join_url.",
401
419
  required: false,
402
420
  },
403
421
  {
@@ -544,10 +562,10 @@ export function registerUgcCommands(program, { request: requestOxygen, handle })
544
562
  {
545
563
  action: "application_create",
546
564
  command: "programs apply",
547
- description: "Apply to a public program from programs discover, with a short note to the brand. The brand approves or declines; approval makes you a creator in the program and emails you. Free: 0 credits.",
565
+ description: "Join or apply to a public program from programs discover or a join link: with join_policy automatic you join at once (joined: true, with your participation); otherwise the brand approves or declines (joined: false), and approval makes you a creator and emails you. Free: 0 credits.",
548
566
  fields: [
549
- { name: "program_id", kind: "string", description: "Public program ID from programs discover.", required: true },
550
- { name: "message", kind: "string", description: "Your note to the brand (up to 600 characters).", required: true },
567
+ { name: "program_id", kind: "string", description: "Public program ID from programs discover or a join link (…/ugc?join=<id>).", required: true },
568
+ { name: "message", kind: "string", description: "Your note to the brand (up to 600 characters). Required unless the program's join_policy is automatic.", required: false },
551
569
  { name: "linkedin_url", kind: "string", description: "Your LinkedIn profile URL; passing it also saves it to your profile. Omit to use the one on your profile (ugc profile get); if neither exists, ask the person rather than guess.", required: false },
552
570
  ],
553
571
  },
@@ -102,7 +102,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
102
102
  gatewayCommands: ["billing balance", "billing usage", "billing audit", "billing estimate", "budget list", "billing invoices", "pricing customer-credit-prices"],
103
103
  skills: ["oxygen-gtm", "oxygen-diagnostics"],
104
104
  endpointSections: ["billing", "budget", "limits", "pricing"],
105
- intentTerms: ["billing", "credits", "credit balance", "customer credit prices", "credit price catalog", "usage", "spend", "cost", "cost estimate", "monthly cost", "budget", "subscription", "invoice", "vat", "vat number", "tax id", "cancel plan", "change plan", "upgrade", "downgrade", "subscription plans", "annual plan", "yearly commitment", "yearly saving", "annual discount"],
105
+ intentTerms: ["billing", "credits", "credit balance", "customer credit prices", "credit price catalog", "usage", "spend", "cost", "cost estimate", "monthly cost", "budget", "subscription", "invoice", "vat", "vat number", "tax id", "cancel plan", "change plan", "upgrade", "downgrade", "subscription plans", "annual plan", "yearly commitment", "yearly saving", "annual discount", "retention offer", "cancellation reason", "winback discount"],
106
106
  },
107
107
  {
108
108
  id: "support-and-feedback",
@@ -1043,7 +1043,7 @@ function routeCardFor(card, query) {
1043
1043
  card = { ...card, posture: "read_only", execution: providerRead.execution };
1044
1044
  const spendRead = card.id === "billing-and-budgets" && !providerRead ? spendBreakdownRecommendation(query) : null;
1045
1045
  if (spendRead)
1046
- card = { ...card, posture: "read_only", execution: spendRead.execution };
1046
+ card = { ...card, posture: "mixed", execution: spendRead.execution };
1047
1047
  const blockRead = card.primitive === "sequences" ? sendingBlockReadRecommendation(query) : null;
1048
1048
  if (blockRead)
1049
1049
  card = { ...card, posture: "read_only", execution: blockRead.execution };
@@ -1512,10 +1512,24 @@ function tableRowRemovalRecommendation(query) {
1512
1512
  }
1513
1513
  /** Reads of grid values/state are separate from executing or resolving actions. */
1514
1514
  function tableReadRecommendation(query) {
1515
- if (!/\btables?\b/.test(query) || isFunctionIntent(query) || isSecondPartyDecisionIntent(query)
1515
+ if (isFunctionIntent(query) || isSecondPartyDecisionIntent(query)
1516
1516
  || /\b(workflows?|agents?|actions?|items?|retry|resolve|effects?|imports?|webhooks?|feeds?|enrich\w*|create|add|insert|upsert|update|delete|remove|rename|hide|archive|columns? run)\b/.test(query)
1517
1517
  || /\b(run|execute|start|cancel)\s+(?:(?:the|my|these)\s+)?(?:columns?|table|enrichment)\b/.test(query))
1518
1518
  return null;
1519
+ // An explicit order over existing labels reads rows; it does not classify
1520
+ // them or create a rank column. The column-only wording also names Tables.
1521
+ if (/\b(tables?|rows?|columns?)\b/.test(query)
1522
+ && /\b(sort(?:s|ed|ing)?|order(?:s|ed|ing)?|rank(?:s|ed|ing)?)\b/.test(query)
1523
+ && /\b(custom|explicit)\b/.test(query)
1524
+ && /\b(labels?|values?|priority|select|options?)\b/.test(query)
1525
+ && !/\b(classif\w*|categori[sz]\w*|tag(?:ging)?|assign|set|change|edit|reorder\w*|mov(?:e|es|ed|ing)|sav(?:e|es|ed|ing)|configur(?:e|es|ed|ing)|calculat(?:e|es|ed|ing)|scor(?:e|es|ed|ing)|enroll\w*|send\w*|sequences?|campaigns?)\b/.test(query)
1526
+ && !/\b(do not|don't|avoid|without)\s+(sort|order|rank)\w*\b/.test(query))
1527
+ return {
1528
+ tools: ["oxygen_tables_query", "oxygen_tables_describe"], commands: ["tables query", "tables describe"],
1529
+ execution: "Raw tables query supports sorts[].valueOrder (alias value_order) for an explicit label order. Inspect the Table's column keys and query schema first. Values are trimmed/lowercased and deduplicated; asc puts listed values first and unlisted/null last, desc reverses. An empty list uses ordinary sorting. Keep the same sorts when following its cursor. format=grid rejects sorts; rows-page and saved-view sort formats have separate schemas. This read does not classify rows or run columns.",
1530
+ };
1531
+ if (!/\btables?\b/.test(query))
1532
+ return null;
1519
1533
  if (/\bcells?\b/.test(query) && /\b(history|changes?|provenance)\b/.test(query))
1520
1534
  return {
1521
1535
  tools: ["oxygen_tables_cell_history", "oxygen_tables_cell_inspect"], commands: ["cells history", "cells inspect"],
@@ -1763,7 +1777,7 @@ function spendBreakdownRecommendation(query) {
1763
1777
  return null;
1764
1778
  return {
1765
1779
  tools: ["oxygen_billing_audit", "oxygen_billing_usage"], commands: ["billing audit", "billing usage"],
1766
- execution: "Read where managed credits went with oxygen_billing_audit: spend_by_category ranks each area's credits and share of managed_credits_spent, largest first, over the whole window; group_by provider, tool, model, run or workspace narrows it. Spent is reserved minus released in the window; it differs from captured (oxygen_billing_usage's managed_credits_used) by holds still open and holds settling across the window edge, so do not attribute the whole gap to open holds. External MCP uses oxygen_capabilities_call; built-in agents invoke the native capability. Reads cost 0 credits.",
1780
+ execution: "Read where managed credits went with oxygen_billing_audit: spend_by_category ranks each area's credits and share of managed_credits_spent, largest first, over the whole window; group_by provider, tool, model, run or workspace narrows it. Spent is reserved minus released in the window; it differs from captured (oxygen_billing_usage's managed_credits_used) by holds still open and holds settling across the window edge, so do not attribute the whole gap to open holds. External MCP uses oxygen_capabilities_call; built-in agents invoke the native capability. Reports cost 0 credits; billing audit may apply due plan grants or credit expiry.",
1767
1781
  };
1768
1782
  }
1769
1783
  function sendingBlockReadRecommendation(query) {
@@ -1789,10 +1803,16 @@ function isSpintaxIntent(query) {
1789
1803
  return /\b(wording|copy|message|draft|subject(?: line)?)\b.{0,45}\b(variations?|variants?|paraphrase|rewrite)\b/.test(query)
1790
1804
  || /\b(paraphrase|rewrite|variations?)\b.{0,45}\b(wording|copy|message|draft|subject(?: line)?)\b/.test(query);
1791
1805
  }
1806
+ function isSubscriptionRetentionIntent(query) {
1807
+ return /\b(retention offer|win[ -]?back discount|cancellation (?:reason|feedback)|written cancellation)\b/.test(query)
1808
+ || /\b(stay|cancel|cancelling)\b/.test(query) && /\b(discount|next month|subscription reason)\b/.test(query);
1809
+ }
1792
1810
  function explicitCapabilityIntent(query) {
1793
1811
  // Importing an AI answer is GEO evidence, rather than a Table file import.
1794
1812
  if (/\b(?:geo|aeo|ai (?:(?:search|answer) )?visibility|consumer ai answers?|answer engine optimization|generative engine optimization)\b/.test(query))
1795
1813
  return ROUTE_BY_ID.get("geo") ?? null;
1814
+ if (isSubscriptionRetentionIntent(query))
1815
+ return ROUTE_BY_ID.get("billing-and-budgets") ?? null;
1796
1816
  if (isConnectionPriceIntent(query))
1797
1817
  return ROUTE_BY_ID.get("billing-and-budgets") ?? null;
1798
1818
  if (sentLinkedInInvitationReadRecommendation(query))
@@ -2142,6 +2162,9 @@ function recommendationsFor(card, query) {
2142
2162
  const spendRead = card.id === "billing-and-budgets" ? spendBreakdownRecommendation(query) : null;
2143
2163
  if (spendRead)
2144
2164
  return spendRead;
2165
+ if (card.id === "billing-and-budgets" && isSubscriptionRetentionIntent(query)) {
2166
+ return { tools: ["oxygen_billing_cancel"], commands: ["billing retention-offer", "billing cancel", "billing retain"] };
2167
+ }
2145
2168
  if (card.id === "billing-and-budgets" && isConnectionPriceIntent(query)) {
2146
2169
  return {
2147
2170
  tools: ["oxygen_billing_commitments"],
@@ -8,6 +8,13 @@ export type UgcPostingTarget = {
8
8
  };
9
9
  export declare const UGC_POSTING_TARGET_MAX = 100;
10
10
  export declare const UGC_POSTING_TARGET_PERIODS: readonly ["week", "month", "total"];
11
+ /**
12
+ * How creators get into a public program (Tim, 2026-10-09): the brand approves
13
+ * each application, or an application joins at once. Private programs are
14
+ * joined by invitation only, so they are always `approval`.
15
+ */
16
+ export declare const UGC_JOIN_POLICIES: readonly ["approval", "automatic"];
17
+ export type UgcJoinPolicy = (typeof UGC_JOIN_POLICIES)[number];
11
18
  /**
12
19
  * The operator-written creator brief: one page, not a document (Philipp, 2026-09-23;
13
20
  * raised from 1,200 on 2026-09-29, when a natural founder brief ran 1,298 and blind
@@ -44,6 +51,8 @@ export type UgcProgram = {
44
51
  /** Posts each creator is expected to publish per period; both null when unset. */
45
52
  postingTargetCount?: number | null;
46
53
  postingTargetPeriod?: UgcPostingTargetPeriod | null;
54
+ /** How creators join while the program is public; older rows read as approval. */
55
+ joinPolicy?: UgcJoinPolicy;
47
56
  status: "active" | "archived";
48
57
  createdAt: Date;
49
58
  updatedAt: Date;
@@ -1,5 +1,11 @@
1
1
  export const UGC_POSTING_TARGET_MAX = 100;
2
2
  export const UGC_POSTING_TARGET_PERIODS = ["week", "month", "total"];
3
+ /**
4
+ * How creators get into a public program (Tim, 2026-10-09): the brand approves
5
+ * each application, or an application joins at once. Private programs are
6
+ * joined by invitation only, so they are always `approval`.
7
+ */
8
+ export const UGC_JOIN_POLICIES = ["approval", "automatic"];
3
9
  /**
4
10
  * The operator-written creator brief: one page, not a document (Philipp, 2026-09-23;
5
11
  * raised from 1,200 on 2026-09-29, when a natural founder brief ran 1,298 and blind
@@ -1 +1 @@
1
- export declare const OXYGEN_BUILD_VERSION = "1.1013.27";
1
+ export declare const OXYGEN_BUILD_VERSION = "1.1013.59";
@@ -1,2 +1,2 @@
1
1
  // GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
2
- export const OXYGEN_BUILD_VERSION = "1.1013.27";
2
+ export const OXYGEN_BUILD_VERSION = "1.1013.59";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.1013.27",
3
+ "version": "1.1013.59",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",