@oxygen-agent/cli 1.310.3 → 1.336.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/browser-login.d.ts +16 -0
  3. package/dist/browser-login.js +131 -1
  4. package/dist/command-manifest.js +26 -10
  5. package/dist/help.js +7 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +2152 -124
  8. package/dist/runtime.js +13 -3
  9. package/dist/skills.js +192 -32
  10. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
  11. package/node_modules/@oxygen/shared/dist/billing.d.ts +52 -29
  12. package/node_modules/@oxygen/shared/dist/billing.js +77 -55
  13. package/node_modules/@oxygen/shared/dist/cli-login-code.d.ts +3 -0
  14. package/node_modules/@oxygen/shared/dist/cli-login-code.js +30 -0
  15. package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
  16. package/node_modules/@oxygen/shared/dist/index.js +5 -0
  17. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +84 -0
  18. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +82 -0
  19. package/node_modules/@oxygen/shared/dist/recipes.d.ts +19 -0
  20. package/node_modules/@oxygen/shared/dist/recipes.js +90 -0
  21. package/node_modules/@oxygen/shared/dist/sequences.d.ts +61 -15
  22. package/node_modules/@oxygen/shared/dist/sequences.js +134 -23
  23. package/node_modules/@oxygen/shared/dist/sql-error.d.ts +25 -0
  24. package/node_modules/@oxygen/shared/dist/sql-error.js +46 -0
  25. package/node_modules/@oxygen/shared/dist/version.d.ts +2 -2
  26. package/node_modules/@oxygen/shared/dist/version.js +13 -11
  27. package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.d.ts +4 -0
  28. package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.js +18 -0
  29. package/node_modules/@oxygen/shared/dist/workflow-status-change.d.ts +61 -0
  30. package/node_modules/@oxygen/shared/dist/workflow-status-change.js +124 -0
  31. package/node_modules/@oxygen/shared/dist/workspace-agents.d.ts +65 -0
  32. package/node_modules/@oxygen/shared/dist/workspace-agents.js +67 -0
  33. package/node_modules/@oxygen/workflows/dist/index.js +86 -2
  34. package/package.json +1 -1
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.310.3
37
+ Version: 1.336.1
@@ -3,5 +3,21 @@ export type BrowserLoginSession = {
3
3
  waitForToken: () => Promise<string>;
4
4
  close: () => Promise<void>;
5
5
  };
6
+ export type RelayLoginSession = BrowserLoginSession & {
7
+ confirmationCode: string;
8
+ };
9
+ /**
10
+ * Relay browser login (device-flow style): register a login request
11
+ * server-side, send the user to /settings/cli to approve it, and poll the
12
+ * claim endpoint until the session token is released. No loopback server is
13
+ * involved, so it works where the browser→localhost POST cannot: browsers
14
+ * enforcing the Local Network Access permission, SSH/WSL/remote terminals,
15
+ * containers. Any failure to CREATE the request (old server without the
16
+ * endpoint, network policy) is the caller's cue to fall back to the legacy
17
+ * loopback flow.
18
+ */
19
+ export declare function createRelayLoginSession(apiUrl: string, options?: {
20
+ fetch?: typeof fetch;
21
+ }): Promise<RelayLoginSession>;
6
22
  export declare function createBrowserLoginSession(apiUrl: string): Promise<BrowserLoginSession>;
7
23
  export declare function openBrowser(url: string): boolean;
@@ -1,11 +1,141 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { randomBytes, timingSafeEqual } from "node:crypto";
3
3
  import { createServer } from "node:http";
4
- import { OxygenError } from "@oxygen/shared";
4
+ import { hostname } from "node:os";
5
+ import { OXYGEN_VERSION, OxygenError, deriveCliLoginConfirmationCode } from "@oxygen/shared";
6
+ import { requestOxygen } from "./http-client.js";
5
7
  import { readErrorMessage } from "./util.js";
6
8
  const CALLBACK_HOST = "127.0.0.1";
7
9
  const CALLBACK_PATH = "/cli/login/callback";
8
10
  const MAX_CALLBACK_BODY_BYTES = 32_000;
11
+ const RELAY_CREATE_TIMEOUT_MS = 10_000;
12
+ const RELAY_CLAIM_TIMEOUT_MS = 15_000;
13
+ const DEFAULT_RELAY_POLL_INTERVAL_SECONDS = 2;
14
+ const MAX_RELAY_POLL_INTERVAL_SECONDS = 10;
15
+ const MAX_CONSECUTIVE_TRANSIENT_CLAIM_ERRORS = 5;
16
+ // Claim-poll errors that read as infrastructure hiccups rather than a verdict
17
+ // about the login request itself — tolerated a few times before giving up.
18
+ const TRANSIENT_CLAIM_ERROR_CODES = new Set([
19
+ "network_error",
20
+ "network_timeout",
21
+ "invalid_response",
22
+ "unexpected_error",
23
+ ]);
24
+ /**
25
+ * Relay browser login (device-flow style): register a login request
26
+ * server-side, send the user to /settings/cli to approve it, and poll the
27
+ * claim endpoint until the session token is released. No loopback server is
28
+ * involved, so it works where the browser→localhost POST cannot: browsers
29
+ * enforcing the Local Network Access permission, SSH/WSL/remote terminals,
30
+ * containers. Any failure to CREATE the request (old server without the
31
+ * endpoint, network policy) is the caller's cue to fall back to the legacy
32
+ * loopback flow.
33
+ */
34
+ export async function createRelayLoginSession(apiUrl, options = {}) {
35
+ const state = randomBytes(32).toString("base64url");
36
+ // Empty token: pins the host to --api-url/profile resolution while sending no
37
+ // Authorization header — these endpoints are pre-auth by design.
38
+ const credentials = { token: "", apiUrl };
39
+ const requestOptions = options.fetch ? { fetch: options.fetch } : {};
40
+ const created = await requestOxygen("/api/cli/login-requests", {
41
+ method: "POST",
42
+ credentials,
43
+ body: {
44
+ state,
45
+ client: {
46
+ os: process.platform,
47
+ hostname: safeHostname(),
48
+ cli_version: OXYGEN_VERSION,
49
+ },
50
+ },
51
+ timeoutMs: RELAY_CREATE_TIMEOUT_MS,
52
+ ...requestOptions,
53
+ });
54
+ const pollIntervalMs = normalizePollIntervalSeconds(created.interval_seconds) * 1000;
55
+ const redirectParams = new URLSearchParams({
56
+ cli_state: state,
57
+ cli_login_request: "1",
58
+ source: "oxygen_cli",
59
+ });
60
+ const verificationUrl = new URL("/auth/sign-in", apiUrl);
61
+ verificationUrl.searchParams.set("redirect_url", `/settings/cli?${redirectParams.toString()}`);
62
+ verificationUrl.searchParams.set("source", "oxygen_cli");
63
+ let closed = false;
64
+ async function waitForToken() {
65
+ let transientErrors = 0;
66
+ while (!closed) {
67
+ await sleep(pollIntervalMs);
68
+ if (closed)
69
+ break;
70
+ let claim;
71
+ try {
72
+ claim = await requestOxygen("/api/cli/login-requests/claim", {
73
+ method: "POST",
74
+ credentials,
75
+ body: { state },
76
+ timeoutMs: RELAY_CLAIM_TIMEOUT_MS,
77
+ ...requestOptions,
78
+ });
79
+ }
80
+ catch (error) {
81
+ if (error instanceof OxygenError && error.code === "rate_limit_exceeded") {
82
+ await sleep(Math.max(readRetryAfterMs(error), pollIntervalMs));
83
+ continue;
84
+ }
85
+ if (error instanceof OxygenError && TRANSIENT_CLAIM_ERROR_CODES.has(error.code)) {
86
+ transientErrors += 1;
87
+ if (transientErrors >= MAX_CONSECUTIVE_TRANSIENT_CLAIM_ERRORS)
88
+ throw error;
89
+ continue;
90
+ }
91
+ // Typed verdicts (login_request_expired / _not_found / _already_claimed)
92
+ // and anything else surface immediately.
93
+ throw error;
94
+ }
95
+ transientErrors = 0;
96
+ if (claim.status === "ready" && typeof claim.token === "string" && claim.token) {
97
+ return claim.token;
98
+ }
99
+ }
100
+ throw new OxygenError("browser_login_closed", "Browser login closed.", { exitCode: 1 });
101
+ }
102
+ return {
103
+ verificationUrl: verificationUrl.toString(),
104
+ confirmationCode: deriveCliLoginConfirmationCode(state),
105
+ waitForToken,
106
+ close: () => {
107
+ closed = true;
108
+ return Promise.resolve();
109
+ },
110
+ };
111
+ }
112
+ function normalizePollIntervalSeconds(value) {
113
+ if (typeof value !== "number" || !Number.isFinite(value)) {
114
+ return DEFAULT_RELAY_POLL_INTERVAL_SECONDS;
115
+ }
116
+ return Math.min(MAX_RELAY_POLL_INTERVAL_SECONDS, Math.max(1, Math.trunc(value)));
117
+ }
118
+ function readRetryAfterMs(error) {
119
+ const details = error.details;
120
+ if (details && typeof details === "object" && !Array.isArray(details)) {
121
+ const seconds = details.retry_after_seconds;
122
+ if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0) {
123
+ return Math.min(seconds, 60) * 1000;
124
+ }
125
+ }
126
+ return DEFAULT_RELAY_POLL_INTERVAL_SECONDS * 1000;
127
+ }
128
+ function safeHostname() {
129
+ try {
130
+ return hostname();
131
+ }
132
+ catch {
133
+ return undefined;
134
+ }
135
+ }
136
+ function sleep(ms) {
137
+ return new Promise((resolve) => setTimeout(resolve, ms));
138
+ }
9
139
  export async function createBrowserLoginSession(apiUrl) {
10
140
  const state = randomBytes(32).toString("base64url");
11
141
  let settled = false;
@@ -1,15 +1,31 @@
1
1
  import { CLI_EXIT_CODE_TABLE, OXYGEN_VERSION } from "@oxygen/shared";
2
+ // Leaf verbs (or their hyphen-prefix) that name a state mutation, external
3
+ // write, or paid side effect. `mutates` is derived from this set (see
4
+ // toManifestEntry). It is hand-curated and cross-checked against the real leaf
5
+ // commands in `oxygen commands --json`: a verb belongs here only if every leaf
6
+ // command it matches is a genuine write. Verbs that also front a read are
7
+ // deliberately excluded so a read is never mislabeled — e.g. `repair`
8
+ // (→ `db repair-preview`) and `request` (→ `egress dedicated request`, a
9
+ // PREVIEW). Object nouns that front paid *reads* (`find company`, `find email`)
10
+ // stay out too: those are flagged by spends_credits, not mutates. When you add
11
+ // a leaf command, verify its verb here against the self-index before trusting
12
+ // the default.
2
13
  const MUTATING_VERBS = new Set([
3
- "add", "apply", "approve", "archive", "assign", "buy", "cancel", "clear",
4
- "configure", "connect", "create", "delete", "disable", "dispatch", "draft",
5
- "emit", "enable", "enroll", "grant", "import", "insert", "invite", "launch",
6
- "login", "logout", "materialize", "merge", "migrate", "order", "pause",
7
- "publish", "purchase", "push", "record", "register", "remove", "rename",
8
- "reorder", "reply", "rerun", "reset", "resolve", "restore", "resume",
9
- "retry", "retype", "revoke", "rotate", "run", "save", "schedule", "select",
10
- "send", "set", "share", "start", "stop", "subscribe", "sync", "unpublish",
11
- "unshare", "unsubscribe", "update", "upload", "upsert", "use", "withdraw",
12
- "write",
14
+ "accept", "ack", "add", "adopt", "apply", "approve", "archive",
15
+ "assign", "attach", "backfill", "buy", "call", "cancel", "clear",
16
+ "comment", "configure", "connect", "create", "delete", "delist", "disable",
17
+ "disconnect", "dispatch", "draft", "edit", "emit", "enable", "enroll",
18
+ "file", "grant", "harvest", "import", "insert", "invite", "launch",
19
+ "log", "login", "logout", "mark-read", "materialize", "merge", "migrate",
20
+ "move", "note", "onboard", "order", "pause", "pin", "provision",
21
+ "publish", "purchase", "purge", "push", "react", "record", "recover-pending",
22
+ "register", "reject", "relink", "remove", "rename", "reorder", "reply",
23
+ "rerun", "rescan", "resend", "reset", "resolve", "restore", "resume",
24
+ "retry", "retype", "revoke", "rotate", "run", "save", "schedule",
25
+ "seed", "select", "send", "set", "setup", "share", "solve",
26
+ "start", "stop", "subscribe", "sync", "synthesize", "unpublish", "unshare",
27
+ "unsubscribe", "update", "upload", "upsert", "use", "warm-send", "warmup",
28
+ "withdraw", "write",
13
29
  ]);
14
30
  export function buildCommandManifest(program, binaryName) {
15
31
  const commands = [];
package/dist/help.js CHANGED
@@ -40,6 +40,13 @@ const HELP_GROUPS = [
40
40
  "reviews",
41
41
  ],
42
42
  },
43
+ {
44
+ // The built-in specialist agents (Control-layer Workspace Agent), grouped on
45
+ // their own so a fresh agent finds the roster surface next to the runs/approvals
46
+ // lenses it governs.
47
+ heading: "Workspace Agent:",
48
+ commands: ["agent"],
49
+ },
43
50
  {
44
51
  heading: "Automation, billing & admin:",
45
52
  commands: [
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
+ export declare function formatDisabledWorkflowNotices(data: unknown): string[];
2
3
  export declare function createProgram(): Command;