@oxygen-agent/cli 1.164.30 → 1.177.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/index.js +1195 -507
- package/dist/run-wait.d.ts +23 -0
- package/dist/run-wait.js +57 -0
- package/node_modules/@oxygen/shared/dist/cell-format.d.ts +2 -14
- package/node_modules/@oxygen/shared/dist/cell-format.js +3 -10
- package/node_modules/@oxygen/shared/dist/cli-envelope.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/cli-result.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +52 -0
- package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +1 -1
- package/node_modules/@oxygen/shared/dist/file-import.js +1 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -39
- package/node_modules/@oxygen/shared/dist/index.js +2 -44
- package/node_modules/@oxygen/shared/dist/log.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/log.js +8 -3
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +0 -3
- package/node_modules/@oxygen/shared/dist/object-storage.js +1 -24
- package/node_modules/@oxygen/shared/dist/search-vocab.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/search-vocab.js +151 -0
- package/node_modules/@oxygen/shared/dist/select-options.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/select-options.js +121 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +1 -1
- package/node_modules/@oxygen/shared/dist/sql-error.d.ts +0 -6
- package/node_modules/@oxygen/shared/dist/sql-error.js +67 -58
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/telemetry.js +23 -18
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/dist/worker-failures-queue.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/worker-failures-queue.js +56 -0
- package/package.json +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CliRunWaitConfig {
|
|
2
|
+
/** Run id echoed into the timeout error details. */
|
|
3
|
+
runId: string;
|
|
4
|
+
/** Raw --timeout-seconds flag; falls back to defaultTimeoutSeconds when unset. */
|
|
5
|
+
requestedTimeoutSeconds?: string | undefined;
|
|
6
|
+
/** Raw --interval-seconds flag; falls back to defaultIntervalSeconds when unset. */
|
|
7
|
+
requestedIntervalSeconds?: string | undefined;
|
|
8
|
+
defaultTimeoutSeconds: number;
|
|
9
|
+
defaultIntervalSeconds: number;
|
|
10
|
+
/** Fetches the latest run snapshot to inspect this poll. */
|
|
11
|
+
fetchRun: () => Promise<Record<string, unknown>>;
|
|
12
|
+
/** True when the run status is terminal for this resource. */
|
|
13
|
+
isTerminal: (status: string | null) => boolean;
|
|
14
|
+
/** Builds the response once a terminal status is observed. */
|
|
15
|
+
shapeTerminal: (run: Record<string, unknown>, status: string | null, polls: number, elapsedMs: number) => Record<string, unknown>;
|
|
16
|
+
/** OxygenError shape thrown when the deadline passes before a terminal status. */
|
|
17
|
+
timeoutCode: string;
|
|
18
|
+
timeoutMessage: string;
|
|
19
|
+
timeoutDetailIdKey: string;
|
|
20
|
+
/** Optional extra timeout details (e.g. workflow queued-no-worker guidance). */
|
|
21
|
+
timeoutExtraDetails?: (latestRun: Record<string, unknown> | null, status: string | null) => Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export declare function waitForCliRun(config: CliRunWaitConfig): Promise<Record<string, unknown>>;
|
package/dist/run-wait.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { OxygenError } from "@oxygen/shared";
|
|
2
|
+
export async function waitForCliRun(config) {
|
|
3
|
+
const timeoutSeconds = readPositiveInt(config.requestedTimeoutSeconds)
|
|
4
|
+
?? config.defaultTimeoutSeconds;
|
|
5
|
+
const intervalSeconds = readPositiveInt(config.requestedIntervalSeconds)
|
|
6
|
+
?? config.defaultIntervalSeconds;
|
|
7
|
+
const startedAt = Date.now();
|
|
8
|
+
const deadline = startedAt + timeoutSeconds * 1000;
|
|
9
|
+
let polls = 0;
|
|
10
|
+
while (true) {
|
|
11
|
+
polls += 1;
|
|
12
|
+
const latestRun = await config.fetchRun();
|
|
13
|
+
const status = readRecordString(latestRun, "status");
|
|
14
|
+
if (config.isTerminal(status)) {
|
|
15
|
+
return config.shapeTerminal(latestRun, status, polls, Date.now() - startedAt);
|
|
16
|
+
}
|
|
17
|
+
const remainingMs = deadline - Date.now();
|
|
18
|
+
if (remainingMs <= 0) {
|
|
19
|
+
throw new OxygenError(config.timeoutCode, config.timeoutMessage, {
|
|
20
|
+
details: {
|
|
21
|
+
[config.timeoutDetailIdKey]: config.runId,
|
|
22
|
+
status: status ?? null,
|
|
23
|
+
timeout_seconds: timeoutSeconds,
|
|
24
|
+
polls,
|
|
25
|
+
...(config.timeoutExtraDetails?.(latestRun, status) ?? {}),
|
|
26
|
+
},
|
|
27
|
+
exitCode: 1,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
await sleep(Math.min(intervalSeconds * 1000, remainingMs));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Local copies of index.ts's tiny readers keep this helper free of an
|
|
34
|
+
// index.ts <-> run-wait.ts import cycle (the MCP run-wait.ts module likewise
|
|
35
|
+
// defines its own sleep rather than importing from a tool file).
|
|
36
|
+
function readPositiveInt(value) {
|
|
37
|
+
const trimmed = value?.trim();
|
|
38
|
+
if (!trimmed)
|
|
39
|
+
return undefined;
|
|
40
|
+
const parsed = Number(trimmed);
|
|
41
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
42
|
+
throw new OxygenError("invalid_number", "Expected a positive integer.", {
|
|
43
|
+
details: { value },
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
function readRecordString(value, key) {
|
|
50
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51
|
+
return null;
|
|
52
|
+
const entry = value[key];
|
|
53
|
+
return typeof entry === "string" ? entry : null;
|
|
54
|
+
}
|
|
55
|
+
function sleep(ms) {
|
|
56
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* and provider adapters, so a `text` column whose values parse cleanly as
|
|
12
12
|
* numbers still gets thousands grouping at display time.
|
|
13
13
|
* 3. Refuse to "rescue" things that aren't actually numbers (IP addresses,
|
|
14
|
-
* version strings, phone numbers, URLs)
|
|
14
|
+
* version strings, phone numbers, URLs): {@link rescueNumericText} returns
|
|
15
|
+
* null for those.
|
|
15
16
|
*/
|
|
16
17
|
/** Surfaces have different escaping/length budgets, but the canonical string is shared. */
|
|
17
18
|
export type CellFormatSurface = "cli" | "mcp" | "web";
|
|
@@ -38,19 +39,6 @@ export type CellFormatOptions = {
|
|
|
38
39
|
* escaping, ellipsis budgets) belong outside this function.
|
|
39
40
|
*/
|
|
40
41
|
export declare function formatCellForDisplay(value: unknown, column: CellColumnLike | null | undefined, options: CellFormatOptions): string;
|
|
41
|
-
/**
|
|
42
|
-
* Best-effort parse of a string that looks numeric (with or without thousands
|
|
43
|
-
* grouping, with or without dotted IP-style separators) into a finite number.
|
|
44
|
-
* Returns `null` when the string is clearly *not* a single number — IP
|
|
45
|
-
* addresses, version strings, phone numbers, anything alphabetic.
|
|
46
|
-
*/
|
|
47
|
-
export declare function rescueNumericText(raw: string): number | null;
|
|
48
|
-
/**
|
|
49
|
-
* True when a string is unambiguously numeric (integer, decimal, grouped, or
|
|
50
|
-
* a mangled dotted form we can recover). Exported for callers that want to
|
|
51
|
-
* highlight rescue cases without re-running the formatter.
|
|
52
|
-
*/
|
|
53
|
-
export declare function looksLikeNumericText(value: string): boolean;
|
|
54
42
|
/**
|
|
55
43
|
* Decide whether the same value-level + column-level guards used inside
|
|
56
44
|
* `formatCellForDisplay`'s text path should rescue this string. Exposed so
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* and provider adapters, so a `text` column whose values parse cleanly as
|
|
12
12
|
* numbers still gets thousands grouping at display time.
|
|
13
13
|
* 3. Refuse to "rescue" things that aren't actually numbers (IP addresses,
|
|
14
|
-
* version strings, phone numbers, URLs)
|
|
14
|
+
* version strings, phone numbers, URLs): {@link rescueNumericText} returns
|
|
15
|
+
* null for those.
|
|
15
16
|
*/
|
|
16
17
|
const DEFAULT_LOCALE = "en-US";
|
|
17
18
|
const INTEGER_RE = /^-?\d{1,15}$/;
|
|
@@ -99,7 +100,7 @@ export function formatCellForDisplay(value, column, options) {
|
|
|
99
100
|
* Returns `null` when the string is clearly *not* a single number — IP
|
|
100
101
|
* addresses, version strings, phone numbers, anything alphabetic.
|
|
101
102
|
*/
|
|
102
|
-
|
|
103
|
+
function rescueNumericText(raw) {
|
|
103
104
|
const trimmed = raw.trim();
|
|
104
105
|
if (!trimmed)
|
|
105
106
|
return null;
|
|
@@ -168,14 +169,6 @@ function decodeDottedNumeric(raw) {
|
|
|
168
169
|
const n = Number(normalized || "0");
|
|
169
170
|
return Number.isFinite(n) ? sign * n : null;
|
|
170
171
|
}
|
|
171
|
-
/**
|
|
172
|
-
* True when a string is unambiguously numeric (integer, decimal, grouped, or
|
|
173
|
-
* a mangled dotted form we can recover). Exported for callers that want to
|
|
174
|
-
* highlight rescue cases without re-running the formatter.
|
|
175
|
-
*/
|
|
176
|
-
export function looksLikeNumericText(value) {
|
|
177
|
-
return rescueNumericText(value) !== null;
|
|
178
|
-
}
|
|
179
172
|
function readDataType(column) {
|
|
180
173
|
const raw = column?.dataType ?? column?.data_type ?? null;
|
|
181
174
|
if (!raw)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export type CliMeta = {
|
|
5
|
+
command: string;
|
|
6
|
+
version: string;
|
|
7
|
+
minimum_cli_version?: string;
|
|
8
|
+
};
|
|
9
|
+
export type CliSuccess<T> = {
|
|
10
|
+
ok: true;
|
|
11
|
+
data: T;
|
|
12
|
+
meta: CliMeta;
|
|
13
|
+
};
|
|
14
|
+
export type CliFailure = {
|
|
15
|
+
ok: false;
|
|
16
|
+
error: {
|
|
17
|
+
code: string;
|
|
18
|
+
message: string;
|
|
19
|
+
details?: unknown;
|
|
20
|
+
};
|
|
21
|
+
meta: CliMeta;
|
|
22
|
+
};
|
|
23
|
+
export type CliResult<T> = CliSuccess<T> | CliFailure;
|
|
24
|
+
export declare class OxygenError extends Error {
|
|
25
|
+
readonly code: string;
|
|
26
|
+
readonly details?: unknown;
|
|
27
|
+
readonly exitCode: number;
|
|
28
|
+
constructor(code: string, message: string, options?: {
|
|
29
|
+
details?: unknown;
|
|
30
|
+
exitCode?: number;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export declare function success<T>(command: string, data: T, version?: string, minimumCliVersion?: string): CliSuccess<T>;
|
|
34
|
+
export declare function failure(command: string, error: {
|
|
35
|
+
code: string;
|
|
36
|
+
message: string;
|
|
37
|
+
details?: unknown;
|
|
38
|
+
}, version?: string, minimumCliVersion?: string): CliFailure;
|
|
39
|
+
export declare function toFailure(command: string, error: unknown, version?: string): CliFailure;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Core CLI-result envelope + error primitives, owned by a dependency-free leaf
|
|
2
|
+
// module so the shared package's other leaf modules (sequences, file-import,
|
|
3
|
+
// object-storage, cli-envelope, ...) can depend on `OxygenError` / `CliResult`
|
|
4
|
+
// without importing the root barrel (`./index.js`). The barrel re-exports
|
|
5
|
+
// everything here, so public imports from `@oxygen/shared` are unchanged; this
|
|
6
|
+
// only removes an internal runtime cycle (barrel → leaf → barrel) and stops
|
|
7
|
+
// subpath modules pulling the whole barrel for one class. Depends only on
|
|
8
|
+
// `./version.js` (itself a leaf).
|
|
9
|
+
import { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
|
|
10
|
+
export class OxygenError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
details;
|
|
13
|
+
exitCode;
|
|
14
|
+
constructor(code, message, options = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "OxygenError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.details = options.details;
|
|
19
|
+
this.exitCode = options.exitCode ?? 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function success(command, data, version = OXYGEN_VERSION, minimumCliVersion = OXYGEN_MINIMUM_CLI_VERSION) {
|
|
23
|
+
return {
|
|
24
|
+
ok: true,
|
|
25
|
+
data,
|
|
26
|
+
meta: {
|
|
27
|
+
command,
|
|
28
|
+
version,
|
|
29
|
+
minimum_cli_version: minimumCliVersion,
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function failure(command, error, version = OXYGEN_VERSION, minimumCliVersion = OXYGEN_MINIMUM_CLI_VERSION) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
error,
|
|
37
|
+
meta: {
|
|
38
|
+
command,
|
|
39
|
+
version,
|
|
40
|
+
minimum_cli_version: minimumCliVersion,
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function toFailure(command, error, version = OXYGEN_VERSION) {
|
|
45
|
+
if (error instanceof OxygenError) {
|
|
46
|
+
return failure(command, { code: error.code, message: error.message, details: error.details }, version);
|
|
47
|
+
}
|
|
48
|
+
if (error instanceof Error) {
|
|
49
|
+
return failure(command, { code: "unexpected_error", message: error.message }, version);
|
|
50
|
+
}
|
|
51
|
+
return failure(command, { code: "unexpected_error", message: "An unexpected error occurred." }, version);
|
|
52
|
+
}
|
|
@@ -11,4 +11,3 @@ export declare function buildCreditGuidance(input: {
|
|
|
11
11
|
availableCredits: number | null | undefined;
|
|
12
12
|
headroomMultiplier?: number;
|
|
13
13
|
}): CreditGuidance;
|
|
14
|
-
export declare function recommendedCreditCeiling(estimatedCredits: number | null | undefined, headroomMultiplier?: number): number | null;
|
|
@@ -38,7 +38,7 @@ export function buildCreditGuidance(input) {
|
|
|
38
38
|
credit_guidance: "Credits are tight for the recommended ceiling; ask before running, reduce scope, or top up credits.",
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
function recommendedCreditCeiling(estimatedCredits, headroomMultiplier = DEFAULT_HEADROOM_MULTIPLIER) {
|
|
42
42
|
const normalized = normalizeCreditValue(estimatedCredits);
|
|
43
43
|
if (normalized === null)
|
|
44
44
|
return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extname } from "node:path";
|
|
2
2
|
import readXlsxFile from "read-excel-file/node";
|
|
3
3
|
import { inferImportColumnDataType, parseDateValueToIso, } from "./column-types.js";
|
|
4
|
-
import { OxygenError } from "./
|
|
4
|
+
import { OxygenError } from "./cli-result.js";
|
|
5
5
|
const MAX_IDENTIFIER_LENGTH = 63;
|
|
6
6
|
export const MAX_BUFFERED_IMPORT_PARSE_BYTES = 10 * 1024 * 1024;
|
|
7
7
|
export function inferRowsFileFormat(path) {
|
|
@@ -3,6 +3,7 @@ export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPaus
|
|
|
3
3
|
export * from "./billing.js";
|
|
4
4
|
export * from "./cell-format.js";
|
|
5
5
|
export * from "./cli-envelope.js";
|
|
6
|
+
export * from "./cli-result.js";
|
|
6
7
|
export * from "./column-types.js";
|
|
7
8
|
export * from "./credit-guidance.js";
|
|
8
9
|
export * from "./linkedin-sequences.js";
|
|
@@ -12,46 +13,8 @@ export * from "./provider-request-outcomes.js";
|
|
|
12
13
|
export * from "./signup-lead-deliveries.js";
|
|
13
14
|
export * from "./sql-error.js";
|
|
14
15
|
export * from "./telemetry.js";
|
|
16
|
+
export * from "./worker-failures-queue.js";
|
|
15
17
|
export declare const MAX_ROW_LOOP_WRITE_ROWS = 500;
|
|
16
|
-
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
17
|
-
[key: string]: JsonValue;
|
|
18
|
-
};
|
|
19
|
-
export type CliMeta = {
|
|
20
|
-
command: string;
|
|
21
|
-
version: string;
|
|
22
|
-
minimum_cli_version?: string;
|
|
23
|
-
};
|
|
24
|
-
export type CliSuccess<T> = {
|
|
25
|
-
ok: true;
|
|
26
|
-
data: T;
|
|
27
|
-
meta: CliMeta;
|
|
28
|
-
};
|
|
29
|
-
export type CliFailure = {
|
|
30
|
-
ok: false;
|
|
31
|
-
error: {
|
|
32
|
-
code: string;
|
|
33
|
-
message: string;
|
|
34
|
-
details?: unknown;
|
|
35
|
-
};
|
|
36
|
-
meta: CliMeta;
|
|
37
|
-
};
|
|
38
|
-
export type CliResult<T> = CliSuccess<T> | CliFailure;
|
|
39
|
-
export declare class OxygenError extends Error {
|
|
40
|
-
readonly code: string;
|
|
41
|
-
readonly details?: unknown;
|
|
42
|
-
readonly exitCode: number;
|
|
43
|
-
constructor(code: string, message: string, options?: {
|
|
44
|
-
details?: unknown;
|
|
45
|
-
exitCode?: number;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
export declare function success<T>(command: string, data: T, version?: string, minimumCliVersion?: string): CliSuccess<T>;
|
|
49
|
-
export declare function failure(command: string, error: {
|
|
50
|
-
code: string;
|
|
51
|
-
message: string;
|
|
52
|
-
details?: unknown;
|
|
53
|
-
}, version?: string, minimumCliVersion?: string): CliFailure;
|
|
54
|
-
export declare function toFailure(command: string, error: unknown, version?: string): CliFailure;
|
|
55
18
|
export type SemanticVersion = {
|
|
56
19
|
major: number;
|
|
57
20
|
minor: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
|
|
2
1
|
export { OXYGEN_MINIMUM_CLI_VERSION, OXYGEN_VERSION } from "./version.js";
|
|
3
2
|
export { WORKFLOW_TRIGGER_AUTO_PAUSE_METADATA_KEYS, clearWorkflowTriggerAutoPauseMetadata, } from "./workflow-trigger-metadata.js";
|
|
4
3
|
export * from "./billing.js";
|
|
5
4
|
export * from "./cell-format.js";
|
|
6
5
|
export * from "./cli-envelope.js";
|
|
6
|
+
export * from "./cli-result.js";
|
|
7
7
|
export * from "./column-types.js";
|
|
8
8
|
export * from "./credit-guidance.js";
|
|
9
9
|
export * from "./linkedin-sequences.js";
|
|
@@ -13,55 +13,13 @@ export * from "./provider-request-outcomes.js";
|
|
|
13
13
|
export * from "./signup-lead-deliveries.js";
|
|
14
14
|
export * from "./sql-error.js";
|
|
15
15
|
export * from "./telemetry.js";
|
|
16
|
+
export * from "./worker-failures-queue.js";
|
|
16
17
|
// Maximum rows a single row-loop write (insert/upsert/preview) may process. The
|
|
17
18
|
// row-loop engine issues one DB round-trip per row, so a 500-row write already
|
|
18
19
|
// approaches request timeouts (~50s observed in prod); larger batches must use
|
|
19
20
|
// the COPY-based bulk engine. Tenant-db enforces this and the CLI/API row caps
|
|
20
21
|
// reference it so they never advertise a batch the row-loop will reject.
|
|
21
22
|
export const MAX_ROW_LOOP_WRITE_ROWS = 500;
|
|
22
|
-
export class OxygenError extends Error {
|
|
23
|
-
code;
|
|
24
|
-
details;
|
|
25
|
-
exitCode;
|
|
26
|
-
constructor(code, message, options = {}) {
|
|
27
|
-
super(message);
|
|
28
|
-
this.name = "OxygenError";
|
|
29
|
-
this.code = code;
|
|
30
|
-
this.details = options.details;
|
|
31
|
-
this.exitCode = options.exitCode ?? 1;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export function success(command, data, version = OXYGEN_VERSION, minimumCliVersion = OXYGEN_MINIMUM_CLI_VERSION) {
|
|
35
|
-
return {
|
|
36
|
-
ok: true,
|
|
37
|
-
data,
|
|
38
|
-
meta: {
|
|
39
|
-
command,
|
|
40
|
-
version,
|
|
41
|
-
minimum_cli_version: minimumCliVersion,
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export function failure(command, error, version = OXYGEN_VERSION, minimumCliVersion = OXYGEN_MINIMUM_CLI_VERSION) {
|
|
46
|
-
return {
|
|
47
|
-
ok: false,
|
|
48
|
-
error,
|
|
49
|
-
meta: {
|
|
50
|
-
command,
|
|
51
|
-
version,
|
|
52
|
-
minimum_cli_version: minimumCliVersion,
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
export function toFailure(command, error, version = OXYGEN_VERSION) {
|
|
57
|
-
if (error instanceof OxygenError) {
|
|
58
|
-
return failure(command, { code: error.code, message: error.message, details: error.details }, version);
|
|
59
|
-
}
|
|
60
|
-
if (error instanceof Error) {
|
|
61
|
-
return failure(command, { code: "unexpected_error", message: error.message }, version);
|
|
62
|
-
}
|
|
63
|
-
return failure(command, { code: "unexpected_error", message: "An unexpected error occurred." }, version);
|
|
64
|
-
}
|
|
65
23
|
/**
|
|
66
24
|
* Parse a three-segment semantic version (e.g. `1.142.17`). Pre-release and
|
|
67
25
|
* build metadata suffixes (`-rc.1`, `+build`) are tolerated but ignored.
|
|
@@ -15,7 +15,6 @@ export type LogContext = {
|
|
|
15
15
|
surface?: "mcp" | "cli" | "web" | "worker" | undefined;
|
|
16
16
|
};
|
|
17
17
|
export declare function withLogContext<T>(ctx: LogContext, fn: () => T): T;
|
|
18
|
-
export declare function getLogContext(): LogContext;
|
|
19
18
|
export declare function log(level: LogLevel, msg: string, fields?: Record<string, unknown>): void;
|
|
20
19
|
export declare function errorId(err: unknown): string;
|
|
21
20
|
export declare function errorFields(err: unknown): Record<string, unknown>;
|
|
@@ -7,7 +7,7 @@ export function withLogContext(ctx, fn) {
|
|
|
7
7
|
const merged = { ...(store.getStore() ?? {}), ...ctx };
|
|
8
8
|
return store.run(merged, fn);
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
function getLogContext() {
|
|
11
11
|
return store.getStore() ?? {};
|
|
12
12
|
}
|
|
13
13
|
export function log(level, msg, fields) {
|
|
@@ -68,9 +68,14 @@ export function errorFields(err) {
|
|
|
68
68
|
error_id: errorId(err),
|
|
69
69
|
error_name: err.name,
|
|
70
70
|
// Strip drizzle's `\nparams: <values>` tail so SQL parameter values never
|
|
71
|
-
// reach logs (OXY-46)
|
|
71
|
+
// reach logs (OXY-46).
|
|
72
72
|
error_message: redactSqlParameters(err.message),
|
|
73
|
-
error_stack
|
|
73
|
+
// Deliberately NOT emitting `error_stack`: raw stack frames in
|
|
74
|
+
// Axiom-bound log fields leak absolute file paths and any credential
|
|
75
|
+
// dumped into a frame, and they tripped the prod/preview log-hygiene
|
|
76
|
+
// sweep (OXY-166). `error_id` already hashes name + message + first frame,
|
|
77
|
+
// so same-origin errors still group for triage without shipping the
|
|
78
|
+
// frames themselves; reproduce locally or follow trace_id for the stack.
|
|
74
79
|
};
|
|
75
80
|
}
|
|
76
81
|
return { error_id: "non_error", error_message: redactSqlParameters(String(err)) };
|
|
@@ -18,9 +18,6 @@ export declare function presignImportUpload(input: {
|
|
|
18
18
|
contentType?: string | null;
|
|
19
19
|
contentLength: number;
|
|
20
20
|
}): Promise<PresignedImportUpload>;
|
|
21
|
-
export declare function downloadImportObject(input: {
|
|
22
|
-
storageKey: string;
|
|
23
|
-
}): Promise<Buffer>;
|
|
24
21
|
export declare function getImportObjectMetadata(input: {
|
|
25
22
|
storageKey: string;
|
|
26
23
|
}): Promise<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
3
3
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4
|
-
import { OxygenError } from "./
|
|
4
|
+
import { OxygenError } from "./cli-result.js";
|
|
5
5
|
// S3-compatible object storage for large CSV/file imports. The CLI uploads the
|
|
6
6
|
// raw file straight to the bucket via a presigned PUT URL (bypassing Vercel's
|
|
7
7
|
// ~4.5MB request-body limit), then the Fly worker downloads it and COPY-loads
|
|
@@ -80,22 +80,6 @@ export async function presignImportUpload(input) {
|
|
|
80
80
|
expiresInSeconds: PRESIGN_EXPIRY_SECONDS,
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
export async function downloadImportObject(input) {
|
|
84
|
-
const { client, config } = resolveClient();
|
|
85
|
-
const result = await client.send(new GetObjectCommand({ Bucket: config.bucket, Key: input.storageKey }));
|
|
86
|
-
const body = result.Body;
|
|
87
|
-
if (!body) {
|
|
88
|
-
throw new OxygenError("import_object_missing", "Import object had no body.", {
|
|
89
|
-
details: { storage_key: input.storageKey },
|
|
90
|
-
exitCode: 1,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
const transform = body.transformToByteArray;
|
|
94
|
-
if (typeof transform === "function") {
|
|
95
|
-
return Buffer.from(await transform.call(body));
|
|
96
|
-
}
|
|
97
|
-
return streamToBuffer(body);
|
|
98
|
-
}
|
|
99
83
|
export async function getImportObjectMetadata(input) {
|
|
100
84
|
const { client, config } = resolveClient();
|
|
101
85
|
const result = await client.send(new HeadObjectCommand({ Bucket: config.bucket, Key: input.storageKey }));
|
|
@@ -125,10 +109,3 @@ function sanitizeFileName(fileName) {
|
|
|
125
109
|
const base = fileName.split(/[\\/]/).pop() ?? "";
|
|
126
110
|
return base.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120);
|
|
127
111
|
}
|
|
128
|
-
async function streamToBuffer(stream) {
|
|
129
|
-
const chunks = [];
|
|
130
|
-
for await (const chunk of stream) {
|
|
131
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
|
|
132
|
-
}
|
|
133
|
-
return Buffer.concat(chunks);
|
|
134
|
-
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function normalizeText(value: string): string;
|
|
2
|
+
export declare function escapeRegExp(value: string): string;
|
|
3
|
+
export declare function extractUrls(value: string): string[];
|
|
4
|
+
export declare function extractDomains(value: string): string[];
|
|
5
|
+
export declare function parseMoney(amount: string, unit: string | undefined): number;
|
|
6
|
+
export declare function regionDisplayName(code: string): string | null;
|
|
7
|
+
export declare const COUNTRY_NAME_TO_ISO: Map<string, string>;
|
|
8
|
+
export declare const COUNTRY_ALIAS_TO_ISO: Record<string, string>;
|
|
9
|
+
export declare const CITY_TO_ISO: Record<string, string>;
|
|
10
|
+
export declare const KNOWN_TECHNOLOGIES: readonly ["salesforce", "hubspot", "shopify", "stripe", "aws", "azure", "gcp", "google cloud", "snowflake", "databricks", "postgres", "postgresql", "mysql", "mongodb", "redis", "kafka", "kubernetes", "docker", "react", "nextjs", "next.js", "vue", "angular", "node", "python", "ruby on rails", "django", "segment", "amplitude", "mixpanel", "intercom", "zendesk", "marketo", "pardot", "outreach", "salesloft", "gong", "clari", "netsuite", "workday", "sap", "oracle", "twilio", "klaviyo", "webflow", "wordpress", "magento", "bigcommerce", "woocommerce"];
|
|
11
|
+
export declare const INDUSTRY_TERMS: readonly ["saas", "b2b saas", "fintech", "healthtech", "healthcare", "edtech", "martech", "adtech", "proptech", "insurtech", "legaltech", "hrtech", "regtech", "biotech", "medtech", "cleantech", "climate tech", "cybersecurity", "security", "devtools", "developer tools", "e-commerce", "ecommerce", "logistics", "supply chain", "manufacturing", "construction", "real estate", "hospitality", "retail", "gaming", "media", "telecom", "automotive", "aerospace", "agritech", "foodtech", "ai", "artificial intelligence", "machine learning", "data analytics", "consulting", "staffing", "recruiting"];
|
|
12
|
+
export declare const EMPLOYEE_SEGMENTS: Record<string, {
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
}>;
|
|
16
|
+
export declare const KEYWORD_STOPWORDS: Set<string>;
|
|
17
|
+
export declare const DEPARTMENT_TERMS: Record<string, string>;
|
|
18
|
+
export declare const SENIORITY_TERMS: Record<string, string>;
|