@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.
- package/README.md +1 -1
- package/dist/browser-login.d.ts +16 -0
- package/dist/browser-login.js +131 -1
- package/dist/command-manifest.js +26 -10
- package/dist/help.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2152 -124
- package/dist/runtime.js +13 -3
- package/dist/skills.js +192 -32
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +52 -29
- package/node_modules/@oxygen/shared/dist/billing.js +77 -55
- package/node_modules/@oxygen/shared/dist/cli-login-code.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/cli-login-code.js +30 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/index.js +5 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +84 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +82 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +90 -0
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +61 -15
- package/node_modules/@oxygen/shared/dist/sequences.js +134 -23
- package/node_modules/@oxygen/shared/dist/sql-error.d.ts +25 -0
- package/node_modules/@oxygen/shared/dist/sql-error.js +46 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/version.js +13 -11
- package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/workflow-mcp-tools.js +18 -0
- package/node_modules/@oxygen/shared/dist/workflow-status-change.d.ts +61 -0
- package/node_modules/@oxygen/shared/dist/workflow-status-change.js +124 -0
- package/node_modules/@oxygen/shared/dist/workspace-agents.d.ts +65 -0
- package/node_modules/@oxygen/shared/dist/workspace-agents.js +67 -0
- package/node_modules/@oxygen/workflows/dist/index.js +86 -2
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/browser-login.d.ts
CHANGED
|
@@ -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;
|
package/dist/browser-login.js
CHANGED
|
@@ -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 {
|
|
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;
|
package/dist/command-manifest.js
CHANGED
|
@@ -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
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
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