@oxygen-agent/cli 1.98.7 → 1.109.4

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 and support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.98.7
37
+ Version: 1.109.4
@@ -283,8 +283,8 @@ function normalizeProfileName(value) {
283
283
  exitCode: 1,
284
284
  });
285
285
  }
286
- if (!/^[A-Za-z0-9._-]+$/.test(profile)) {
287
- throw new OxygenError("invalid_profile", "Profile names may only contain letters, numbers, dots, underscores, and dashes.", {
286
+ if (!/^[A-Za-z0-9._@+-]+$/.test(profile)) {
287
+ throw new OxygenError("invalid_profile", "Profile names may only contain letters, numbers, dots, underscores, dashes, plus signs, and @.", {
288
288
  details: { profile },
289
289
  exitCode: 1,
290
290
  });
@@ -5,6 +5,7 @@ type RequestOptions = {
5
5
  formData?: FormData;
6
6
  credentials?: StoredCredentials;
7
7
  requireAuth?: boolean;
8
+ enforceMinimumCliVersion?: boolean;
8
9
  timeoutMs?: number;
9
10
  traceId?: string;
10
11
  fetch?: typeof fetch;
@@ -12,4 +13,7 @@ type RequestOptions = {
12
13
  };
13
14
  export declare function requestOxygen<T>(// skipcq: JS-R1005
14
15
  path: string, options?: RequestOptions): Promise<T>;
16
+ export declare function ensureFreshCliForApiUrl(apiUrl: string, options?: {
17
+ fetch?: typeof fetch;
18
+ }): Promise<void>;
15
19
  export {};
@@ -2,6 +2,9 @@ import { OXYGEN_VERSION, OxygenError } from "@oxygen/shared";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { defaultApiUrl, loadCredentials } from "./credentials.js";
4
4
  const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
5
+ const CLI_COMPATIBILITY_CHECK_TIMEOUT_MS = 5_000;
6
+ const PROD_API_HOSTNAME = "oxygen-agent.com";
7
+ const cliCompatibilityCheckKeys = new Set();
5
8
  export async function requestOxygen(// skipcq: JS-R1005
6
9
  path, options = {}) {
7
10
  const credentials = options.credentials
@@ -12,6 +15,10 @@ path, options = {}) {
12
15
  });
13
16
  }
14
17
  const apiUrl = credentials?.apiUrl ?? defaultApiUrl();
18
+ const fetchImpl = options.fetch ?? fetch;
19
+ if (path !== "/api/health" && shouldCheckCliCompatibility(options)) {
20
+ await ensureFreshCliForApiUrl(apiUrl, { fetch: fetchImpl });
21
+ }
15
22
  const traceId = options.traceId ?? randomUUID();
16
23
  const headers = {
17
24
  Accept: "application/json",
@@ -34,7 +41,6 @@ path, options = {}) {
34
41
  let response;
35
42
  const timeoutMs = resolveRequestTimeoutMs(options.timeoutMs);
36
43
  const timeout = createTimeoutSignal(timeoutMs);
37
- const fetchImpl = options.fetch ?? fetch;
38
44
  const requestInit = {
39
45
  method: options.method ?? "GET",
40
46
  headers,
@@ -70,18 +76,31 @@ path, options = {}) {
70
76
  timeout.cancel();
71
77
  }
72
78
  const envelope = await readEnvelope(response);
73
- const serverVersion = readEnvelopeVersion(envelope);
74
- warnIfCliIsOlderThanApi(serverVersion);
79
+ const compatibility = readEnvelopeCompatibility(envelope);
80
+ assertCliMeetsMinimumApiVersion(compatibility, options);
81
+ warnIfCliIsOlderThanApi(compatibility.version);
75
82
  if (!response.ok || !envelope.ok) {
76
83
  const failure = envelope;
77
84
  const responseTraceId = response.headers.get("x-oxygen-trace-id") ?? traceId;
78
85
  throw new OxygenError(failure.error.code, failure.error.message, {
79
- details: withTraceDetails(failure.error.details, responseTraceId, serverVersion),
86
+ details: withTraceDetails(failure.error.details, responseTraceId, compatibility),
80
87
  exitCode: response.status >= 500 ? 1 : 1,
81
88
  });
82
89
  }
83
90
  return envelope.data;
84
91
  }
92
+ export async function ensureFreshCliForApiUrl(apiUrl, options = {}) {
93
+ const checkKey = `${apiUrl}|${OXYGEN_VERSION}`;
94
+ if (cliCompatibilityCheckKeys.has(checkKey))
95
+ return;
96
+ const compatibility = await readHealthCompatibility(apiUrl, options.fetch ?? fetch);
97
+ if (!compatibility) {
98
+ return;
99
+ }
100
+ assertCliMeetsMinimumApiVersion(compatibility, {});
101
+ warnIfCliIsOlderThanApi(compatibility.version);
102
+ cliCompatibilityCheckKeys.add(checkKey);
103
+ }
85
104
  function resolveSelectedOrganization(credentials, explicit) {
86
105
  const fromOption = explicit?.trim();
87
106
  if (fromOption)
@@ -93,6 +112,38 @@ function resolveSelectedOrganization(credentials, explicit) {
93
112
  ?? credentials?.activeOrganization?.id
94
113
  ?? null;
95
114
  }
115
+ function shouldCheckCliCompatibility(options) {
116
+ if (options.enforceMinimumCliVersion === false)
117
+ return false;
118
+ // Tests and local callers often inject a fake fetch implementation for a
119
+ // single request. Skip the prod health preflight there; the response envelope
120
+ // still carries the compatibility metadata and is checked below.
121
+ if (options.fetch)
122
+ return false;
123
+ return true;
124
+ }
125
+ async function readHealthCompatibility(apiUrl, fetchImpl) {
126
+ if (!isProdApiUrl(apiUrl))
127
+ return null;
128
+ const timeout = createTimeoutSignal(CLI_COMPATIBILITY_CHECK_TIMEOUT_MS);
129
+ try {
130
+ const requestInit = {
131
+ method: "GET",
132
+ headers: { Accept: "application/json" },
133
+ };
134
+ if (timeout.signal)
135
+ requestInit.signal = timeout.signal;
136
+ const response = await fetchImpl(new URL("/api/health", apiUrl), requestInit);
137
+ const envelope = await readEnvelope(response);
138
+ return readEnvelopeCompatibility(envelope);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ finally {
144
+ timeout.cancel();
145
+ }
146
+ }
96
147
  const staleCliWarningVersions = new Set();
97
148
  function warnIfCliIsOlderThanApi(serverVersion) {
98
149
  if (!serverVersion || staleCliWarningVersions.has(serverVersion))
@@ -103,21 +154,56 @@ function warnIfCliIsOlderThanApi(serverVersion) {
103
154
  process.stderr.write(`[oxygen] CLI version ${OXYGEN_VERSION} is older than Oxygen API version ${serverVersion}. `
104
155
  + "Run `oxygen update` before using operational commands.\n");
105
156
  }
106
- function readEnvelopeVersion(envelope) {
157
+ function isProdApiUrl(apiUrl) {
158
+ try {
159
+ return new URL(apiUrl).hostname === PROD_API_HOSTNAME;
160
+ }
161
+ catch {
162
+ return false;
163
+ }
164
+ }
165
+ function assertCliMeetsMinimumApiVersion(compatibility, options) {
166
+ if (options.enforceMinimumCliVersion === false)
167
+ return;
168
+ const minimumCliVersion = compatibility.minimumCliVersion;
169
+ if (!minimumCliVersion || !isVersionGreater(minimumCliVersion, OXYGEN_VERSION))
170
+ return;
171
+ throw new OxygenError("cli_update_required", "This Oxygen API requires a newer CLI. Run `oxygen update` before using this command.", {
172
+ details: {
173
+ client_version: OXYGEN_VERSION,
174
+ ...(compatibility.version ? { server_version: compatibility.version } : {}),
175
+ minimum_cli_version: minimumCliVersion,
176
+ cli_update_command: "oxygen update",
177
+ },
178
+ exitCode: 1,
179
+ });
180
+ }
181
+ function readEnvelopeCompatibility(envelope) {
107
182
  const meta = envelope.meta;
108
183
  if (!meta || typeof meta !== "object" || Array.isArray(meta))
109
- return undefined;
110
- const version = meta.version;
111
- return typeof version === "string" ? version : undefined;
184
+ return {};
185
+ const record = meta;
186
+ const compatibility = {};
187
+ if (typeof record.version === "string")
188
+ compatibility.version = record.version;
189
+ if (typeof record.minimum_cli_version === "string") {
190
+ compatibility.minimumCliVersion = record.minimum_cli_version;
191
+ }
192
+ return compatibility;
112
193
  }
113
- function withTraceDetails(details, traceId, serverVersion) {
194
+ function withTraceDetails(details, traceId, compatibility) {
195
+ const serverVersion = compatibility.version;
114
196
  const fields = {
115
197
  trace_id: traceId,
116
198
  client_version: OXYGEN_VERSION,
117
199
  };
118
200
  if (serverVersion)
119
201
  fields.server_version = serverVersion;
120
- if (serverVersion && isVersionGreater(serverVersion, OXYGEN_VERSION)) {
202
+ if (compatibility.minimumCliVersion)
203
+ fields.minimum_cli_version = compatibility.minimumCliVersion;
204
+ if ((serverVersion && isVersionGreater(serverVersion, OXYGEN_VERSION))
205
+ || (compatibility.minimumCliVersion
206
+ && isVersionGreater(compatibility.minimumCliVersion, OXYGEN_VERSION))) {
121
207
  fields.cli_update_command = "oxygen update";
122
208
  }
123
209
  if (details && typeof details === "object" && !Array.isArray(details)) {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { assertRecipeBundleSafe, assertWorkflowManifest, buildRecipeManifest, co
15
15
  import { isRecipeDefinition } from "@oxygen/recipe-sdk";
16
16
  import { createBrowserLoginSession, openBrowser } from "./browser-login.js";
17
17
  import { clearCredentials, defaultApiUrl, listCredentialProfiles, normalizeApiUrl, pickProfileNameForIdentity, pickProfileNameForUserSession, resolveActiveProfile, saveCredentials, switchCredentialProfile, updateActiveOrganizationForProfile, } from "./credentials.js";
18
- import { requestOxygen } from "./http-client.js";
18
+ import { ensureFreshCliForApiUrl, requestOxygen } from "./http-client.js";
19
19
  import { runLocalCustomHttpColumn } from "./local-custom-http-column.js";
20
20
  import { addSessionOutput, addSessionStatus, getSessionUsage, startSession, updateSessionStep, } from "./session.js";
21
21
  import { doctorAgentSkills, installAgentSkills, listAgentSkills, runAutomaticSkillsInstall, } from "./skills.js";
@@ -325,10 +325,11 @@ export function createProgram() {
325
325
  .option("--json", "Print a JSON envelope.")
326
326
  .action(async (options) => {
327
327
  await handleAsyncAction("status", options, async () => {
328
- const server = await requestOxygen("/api/health", { requireAuth: false });
328
+ const server = await requestOxygen("/api/health", { requireAuth: false, enforceMinimumCliVersion: false });
329
329
  return {
330
330
  client_version: OXYGEN_VERSION,
331
331
  server_version: server.server_version,
332
+ minimum_cli_version: server.minimum_cli_version ?? null,
332
333
  sha: server.sha,
333
334
  region: server.region,
334
335
  in_sync: server.server_version === OXYGEN_VERSION,
@@ -1348,7 +1349,7 @@ export function createProgram() {
1348
1349
  .option("--connection-id <connection_id>", "Optional provider integration connection id.")
1349
1350
  .option("--background", "Create a durable background table action run instead of executing synchronously.")
1350
1351
  .option("--max-credits <n>", "Maximum managed/provider credits to reserve for a background run.")
1351
- .option("--max-concurrency <n>", "Maximum concurrent row items for a background run. Defaults to 50.")
1352
+ .option("--max-concurrency <n>", "Maximum concurrent row items for a background run. Defaults to 250 for AI columns and 50 otherwise.")
1352
1353
  .option("--local", "Run a custom HTTP column in this CLI process so env-var secrets stay local.")
1353
1354
  .option("--local-concurrency <n>", "Maximum concurrent custom HTTP requests for --local. Defaults to 3.")
1354
1355
  .option("--json", "Print a JSON envelope.")
@@ -4389,7 +4390,9 @@ async function resolveActiveProfileWithSource() {
4389
4390
  }
4390
4391
  async function handleWhoamiAction(options) {
4391
4392
  try {
4392
- const identity = await requestOxygen("/api/cli/whoami");
4393
+ const identity = await requestOxygen("/api/cli/whoami", {
4394
+ enforceMinimumCliVersion: false,
4395
+ });
4393
4396
  const context = await resolveActiveProfileWithSource();
4394
4397
  if (context.resolution.exists) {
4395
4398
  const existingCredentials = context.resolution.credentials;
@@ -4777,6 +4780,7 @@ function resolveApiKeyExpiresAt(options) {
4777
4780
  }
4778
4781
  async function login(options) {
4779
4782
  const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
4783
+ await ensureFreshCliForApiUrl(apiUrl);
4780
4784
  const token = readOption(options.token) ?? await promptForToken({
4781
4785
  apiUrl,
4782
4786
  browser: options.browser !== false,
@@ -4821,9 +4825,11 @@ async function applyAuthToken(options) {
4821
4825
  exitCode: 1,
4822
4826
  });
4823
4827
  }
4828
+ const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
4829
+ await ensureFreshCliForApiUrl(apiUrl);
4824
4830
  const credentials = {
4825
4831
  token,
4826
- apiUrl: normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl()),
4832
+ apiUrl,
4827
4833
  };
4828
4834
  const loginIdentity = await resolveLoginIdentity(token, credentials.apiUrl);
4829
4835
  const explicitProfile = readOption(options.profile) ?? readEnvProfileName();
@@ -4875,7 +4881,9 @@ async function resolveLoginIdentity(token, apiUrl) {
4875
4881
  };
4876
4882
  if (token.startsWith("oxy_live_"))
4877
4883
  credentials.authKind = "org_api_key";
4878
- const identity = await requestOxygen("/api/cli/whoami", { credentials });
4884
+ const identity = await requestOxygen("/api/cli/whoami", {
4885
+ credentials,
4886
+ });
4879
4887
  const activeOrganization = storedOrganizationFromOption(identity.organization);
4880
4888
  credentials.authKind = identity.authType === "user_session" ? "user_session" : "org_api_key";
4881
4889
  credentials.activeOrganization = activeOrganization;
@@ -1,4 +1,4 @@
1
- export { OXYGEN_VERSION } from "./version.js";
1
+ export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
2
2
  export * from "./billing.js";
3
3
  export * from "./cell-format.js";
4
4
  export * from "./column-types.js";
@@ -12,6 +12,7 @@ export type JsonValue = string | number | boolean | null | JsonValue[] | {
12
12
  export type CliMeta = {
13
13
  command: string;
14
14
  version: string;
15
+ minimum_cli_version?: string;
15
16
  };
16
17
  export type CliSuccess<T> = {
17
18
  ok: true;
@@ -37,10 +38,10 @@ export declare class OxygenError extends Error {
37
38
  exitCode?: number;
38
39
  });
39
40
  }
40
- export declare function success<T>(command: string, data: T, version?: string): CliSuccess<T>;
41
+ export declare function success<T>(command: string, data: T, version?: string, minimumCliVersion?: string): CliSuccess<T>;
41
42
  export declare function failure(command: string, error: {
42
43
  code: string;
43
44
  message: string;
44
45
  details?: unknown;
45
- }, version?: string): CliFailure;
46
+ }, version?: string, minimumCliVersion?: string): CliFailure;
46
47
  export declare function toFailure(command: string, error: unknown, version?: string): CliFailure;
@@ -1,5 +1,5 @@
1
- import { OXYGEN_VERSION } from "./version.js";
2
- export { OXYGEN_VERSION } from "./version.js";
1
+ import { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
2
+ export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
3
3
  export * from "./billing.js";
4
4
  export * from "./cell-format.js";
5
5
  export * from "./column-types.js";
@@ -19,23 +19,25 @@ export class OxygenError extends Error {
19
19
  this.exitCode = options.exitCode ?? 1;
20
20
  }
21
21
  }
22
- export function success(command, data, version = OXYGEN_VERSION) {
22
+ export function success(command, data, version = OXYGEN_VERSION, minimumCliVersion = OXYGEN_MINIMUM_CLI_VERSION) {
23
23
  return {
24
24
  ok: true,
25
25
  data,
26
26
  meta: {
27
27
  command,
28
- version
28
+ version,
29
+ minimum_cli_version: minimumCliVersion,
29
30
  }
30
31
  };
31
32
  }
32
- export function failure(command, error, version = OXYGEN_VERSION) {
33
+ export function failure(command, error, version = OXYGEN_VERSION, minimumCliVersion = OXYGEN_MINIMUM_CLI_VERSION) {
33
34
  return {
34
35
  ok: false,
35
36
  error,
36
37
  meta: {
37
38
  command,
38
- version
39
+ version,
40
+ minimum_cli_version: minimumCliVersion,
39
41
  }
40
42
  };
41
43
  }
@@ -1 +1,2 @@
1
- export declare const OXYGEN_VERSION = "1.98.7";
1
+ export declare const OXYGEN_VERSION = "1.109.4";
2
+ export declare const OXYGEN_MINIMUM_CLI_VERSION = "1.0.0";
@@ -1 +1,3 @@
1
- export const OXYGEN_VERSION = "1.98.7";
1
+ export const OXYGEN_VERSION = "1.109.4";
2
+ // Bump this only when deployed CLI/API contracts require a newer CLI.
3
+ export const OXYGEN_MINIMUM_CLI_VERSION = "1.0.0";
@@ -414,7 +414,7 @@ export declare const workflowTriggerSchema: {
414
414
  readonly additionalProperties: false;
415
415
  readonly properties: {
416
416
  readonly type: {
417
- readonly enum: readonly ["api", "webhook", "cron"];
417
+ readonly enum: readonly ["api", "webhook", "cron", "event"];
418
418
  };
419
419
  readonly trigger_id: {
420
420
  readonly type: "string";
@@ -570,7 +570,7 @@ export declare function getWorkflowSchema(subject?: "apply" | "call" | "event" |
570
570
  readonly additionalProperties: false;
571
571
  readonly properties: {
572
572
  readonly type: {
573
- readonly enum: readonly ["api", "webhook", "cron"];
573
+ readonly enum: readonly ["api", "webhook", "cron", "event"];
574
574
  };
575
575
  readonly trigger_id: {
576
576
  readonly type: "string";
@@ -711,7 +711,7 @@ export declare function getWorkflowSchema(subject?: "apply" | "call" | "event" |
711
711
  readonly additionalProperties: false;
712
712
  readonly properties: {
713
713
  readonly type: {
714
- readonly enum: readonly ["api", "webhook", "cron"];
714
+ readonly enum: readonly ["api", "webhook", "cron", "event"];
715
715
  };
716
716
  readonly trigger_id: {
717
717
  readonly type: "string";
@@ -734,7 +734,7 @@ export const workflowTriggerSchema = {
734
734
  type: "object",
735
735
  additionalProperties: false,
736
736
  properties: {
737
- type: { enum: ["api", "webhook", "cron"] },
737
+ type: { enum: ["api", "webhook", "cron", "event"] },
738
738
  trigger_id: { type: "string" },
739
739
  trigger_name: { type: ["string", "null"] },
740
740
  secret_required: { type: "boolean" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.98.7",
3
+ "version": "1.109.4",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",