@oxygen-agent/cli 1.218.5 → 1.224.5

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.218.5
37
+ Version: 1.224.5
package/dist/index.js CHANGED
@@ -3730,6 +3730,12 @@ export function createProgram() {
3730
3730
  // packages/mcp-server/src/tools/observability.ts. The API rejects any
3731
3731
  // other value with invalid_request so a typo fails loudly.
3732
3732
  .option("--status <status>", "Filter by success, error, completed, failed, completed_with_errors, queued, skipped, or blocked.")
3733
+ // Keep --type / --source values in sync with OBSERVABILITY_TYPE_FILTERS /
3734
+ // OBSERVABILITY_SOURCE_FILTERS in apps/web/src/lib/observability.ts and
3735
+ // the MCP tool enums. The API rejects any other value with
3736
+ // invalid_request so a typo fails loudly.
3737
+ .option("--type <type>", "Filter by operation or provider_request.")
3738
+ .option("--source <source>", "Filter by cli, web, mcp, or provider.")
3733
3739
  .option("--trace-id <trace_id>", "Filter by trace id.")
3734
3740
  .option("--run-id <run_id>", "Filter by workspace run id.")
3735
3741
  .option("--limit <n>", "Maximum events to return. Defaults to 50.")
@@ -3738,11 +3744,17 @@ export function createProgram() {
3738
3744
  await handleAsyncAction("observability events", options, () => {
3739
3745
  const params = new URLSearchParams();
3740
3746
  const status = readOption(options.status);
3747
+ const type = readOption(options.type);
3748
+ const source = readOption(options.source);
3741
3749
  const traceId = readOption(options.traceId);
3742
3750
  const runId = readOption(options.runId);
3743
3751
  const limit = readPositiveInt(options.limit);
3744
3752
  if (status)
3745
3753
  params.set("status", status);
3754
+ if (type)
3755
+ params.set("type", type);
3756
+ if (source)
3757
+ params.set("source", source);
3746
3758
  if (traceId)
3747
3759
  params.set("trace_id", traceId);
3748
3760
  if (runId)
@@ -4659,7 +4671,7 @@ export function createProgram() {
4659
4671
  .argument("<id>", "WhatsApp account id, connection id, or Unipile account id.")
4660
4672
  .option("--json", "Print a JSON envelope.")
4661
4673
  .action(async (id, options) => {
4662
- await handleAsyncAction("whatsapp disconnect", options, () => requestOxygen("/api/integrations/whatsapp/disconnect", { method: "POST", body: { id } }));
4674
+ await handleAsyncAction("whatsapp disconnect", options, () => requestOxygen(`/api/cli/whatsapp/accounts/${encodeURIComponent(id)}`, { method: "DELETE" }));
4663
4675
  }))
4664
4676
  .addCommand(new Command("limits")
4665
4677
  .description("View and adjust per-account WhatsApp daily limits and the daily-reset timezone. The warm-up ramp is a non-bypassable floor on the message cap regardless of these values.")
@@ -5078,6 +5090,76 @@ export function createProgram() {
5078
5090
  return requestOxygen("/api/cli/inbox/reply-agent", { method: "POST", body });
5079
5091
  });
5080
5092
  }))));
5093
+ program.addCommand(new Command("messages")
5094
+ .description("Cross-channel message corpus: every individual email + LinkedIn + WhatsApp message as one searchable stream (Postgres full-text search over bodies, keyset paginated by recency), plus reply-rate/campaign analytics. Message-level, unlike inbox (conversation-level triage).")
5095
+ .addCommand(new Command("query")
5096
+ .description("Query messages across channels newest first, or relevance-ranked when -q is set. --channel all merges email + LinkedIn + WhatsApp (narrow with --channels); a campaign filter (--sequence-id) restricts to email. Filter by direction, account, contact, status, sentiment, and date range.")
5097
+ .option("-q, --query <text>", "Full-text search over message bodies (relevance-ranked).")
5098
+ .option("--channel <channel>", "Channel: all (merged, default), email, linkedin, or whatsapp.")
5099
+ .option("--channels <list>", "channel=all only: comma-separated channels to include (email,linkedin,whatsapp). Empty = all three.")
5100
+ .option("--direction <direction>", "Filter by direction: inbound or outbound.")
5101
+ .option("--account <id>", "Mailbox id (email) or sender account id (DM) to filter to.")
5102
+ .option("--contact <text>", "Filter by counterpart name or handle (address / provider id).")
5103
+ .option("--sequence-id <id>", "Email only: campaign (sequence) id (restricts the result to email).")
5104
+ .option("--status <keys>", "Comma-separated conversation status keys (e.g. interested,meeting_booked).")
5105
+ .option("--sentiment <sentiment>", "Filter by sentiment: positive, neutral, or negative.")
5106
+ .option("--since <iso>", "Only messages sent at or after this ISO timestamp.")
5107
+ .option("--until <iso>", "Only messages sent before this ISO timestamp.")
5108
+ .option("--limit <n>", "Maximum messages to return (1-200). Defaults to 50.")
5109
+ .option("--cursor <c>", "Pagination cursor from a previous page's next_cursor (recency path only).")
5110
+ .option("--json", "Print a JSON envelope.")
5111
+ .action(async (options) => {
5112
+ await handleAsyncAction("messages query", options, () => {
5113
+ const params = new URLSearchParams();
5114
+ const query = readOption(options.query);
5115
+ if (query)
5116
+ params.set("q", query);
5117
+ for (const [flag, key] of [
5118
+ ["channel", "channel"],
5119
+ ["channels", "channels"],
5120
+ ["direction", "direction"],
5121
+ ["account", "account"],
5122
+ ["contact", "contact"],
5123
+ ["sequenceId", "sequence_id"],
5124
+ ["status", "status"],
5125
+ ["sentiment", "sentiment"],
5126
+ ["since", "since"],
5127
+ ["until", "until"],
5128
+ ["limit", "limit"],
5129
+ ["cursor", "cursor"],
5130
+ ]) {
5131
+ const value = readOption(options[flag]);
5132
+ if (value)
5133
+ params.set(key, value);
5134
+ }
5135
+ const suffix = params.toString();
5136
+ return requestOxygen(`/api/cli/messages${suffix ? `?${suffix}` : ""}`);
5137
+ });
5138
+ }))
5139
+ .addCommand(new Command("stats")
5140
+ .description("Cross-channel campaign analytics: outbound/inbound totals, heuristic reply rate by channel + campaign, status/sentiment breakdowns, response-time percentiles, top counterpart domains, and winning openers. Scope with --sequence-id, --channel, and a date range.")
5141
+ .option("--sequence-id <ids>", "Comma-separated campaign (sequence) ids to scope to.")
5142
+ .option("--channel <channel>", "Channel: all (default), email, linkedin, or whatsapp.")
5143
+ .option("--since <iso>", "Only messages sent at or after this ISO timestamp.")
5144
+ .option("--until <iso>", "Only messages sent before this ISO timestamp.")
5145
+ .option("--json", "Print a JSON envelope.")
5146
+ .action(async (options) => {
5147
+ await handleAsyncAction("messages stats", options, () => {
5148
+ const params = new URLSearchParams();
5149
+ for (const [flag, key] of [
5150
+ ["sequenceId", "sequence_id"],
5151
+ ["channel", "channel"],
5152
+ ["since", "since"],
5153
+ ["until", "until"],
5154
+ ]) {
5155
+ const value = readOption(options[flag]);
5156
+ if (value)
5157
+ params.set(key, value);
5158
+ }
5159
+ const suffix = params.toString();
5160
+ return requestOxygen(`/api/cli/messages/stats${suffix ? `?${suffix}` : ""}`);
5161
+ });
5162
+ })));
5081
5163
  program.addCommand(new Command("sequences")
5082
5164
  .description("Multichannel outreach sequences: one enrollment per lead spans LinkedIn + email over a journey. LinkedIn steps dispatch natively (rate-limited, credit-capped); email steps send natively or place/move/stop the lead in a bound Instantly campaign (BYOK). Cross-channel reply-stop is intrinsic. A LinkedIn-only sequence behaves exactly like the original sequencer.")
5083
5165
  .addCommand(new Command("list")
@@ -7273,10 +7355,20 @@ async function prepareImportTarget(table, options, parsedRows) {
7273
7355
  async function resolveExistingTableImportKeyMapping(table, rows, upsertKey) {
7274
7356
  const describe = await requestOxygen("/api/cli/tables/describe", { method: "POST", body: { table } });
7275
7357
  const resolver = buildExistingTableColumnResolver(describe.columns ?? []);
7276
- const sourceKeyMap = {};
7358
+ const resolvedByHeader = new Map();
7359
+ const targetCounts = new Map();
7277
7360
  for (const sourceKey of inferImportColumnLabels(rows)) {
7278
7361
  const resolved = resolveImportSourceKey(sourceKey, resolver);
7279
- if (resolved && resolved !== sourceKey)
7362
+ if (!resolved)
7363
+ continue;
7364
+ resolvedByHeader.set(sourceKey, resolved);
7365
+ targetCounts.set(resolved, (targetCounts.get(resolved) ?? 0) + 1);
7366
+ }
7367
+ const sourceKeyMap = {};
7368
+ for (const [sourceKey, resolved] of resolvedByHeader) {
7369
+ if ((targetCounts.get(resolved) ?? 0) > 1)
7370
+ continue;
7371
+ if (resolved !== sourceKey)
7280
7372
  sourceKeyMap[sourceKey] = resolved;
7281
7373
  }
7282
7374
  return {
@@ -1,2 +1,2 @@
1
- export declare const OXYGEN_VERSION = "1.218.5";
1
+ export declare const OXYGEN_VERSION = "1.224.5";
2
2
  export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.181.0";
@@ -1,4 +1,4 @@
1
- export const OXYGEN_VERSION = "1.218.5";
1
+ export const OXYGEN_VERSION = "1.224.5";
2
2
  // Bump this only when deployed CLI/API contracts require a newer CLI.
3
3
  // 1.181.0: paid table action runs and background columns run require
4
4
  // approved=true in addition to max_credits; older CLIs cannot send the flag.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.218.5",
3
+ "version": "1.224.5",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",