@oxygen-agent/cli 1.45.4 → 1.50.37
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 +13 -1
- package/dist/browser-login.d.ts +0 -1
- package/dist/browser-login.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +114 -44
- package/dist/http-client.d.ts +1 -2
- package/dist/http-client.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +436 -87
- package/dist/local-custom-http-column.d.ts +0 -1
- package/dist/local-custom-http-column.js +1 -2
- package/dist/session.d.ts +0 -1
- package/dist/session.js +0 -1
- package/dist/update.d.ts +31 -0
- package/dist/update.js +116 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +0 -1
- package/node_modules/@oxygen/recipe-sdk/dist/index.js +0 -1
- package/node_modules/@oxygen/shared/dist/billing.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/billing.js +2 -2
- package/node_modules/@oxygen/shared/dist/cell-format.d.ts +60 -0
- package/node_modules/@oxygen/shared/dist/cell-format.js +278 -0
- package/node_modules/@oxygen/shared/dist/column-types.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/column-types.js +3 -3
- package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.js +0 -1
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/file-import.js +1 -2
- package/node_modules/@oxygen/shared/dist/index.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/index.js +1 -1
- package/node_modules/@oxygen/shared/dist/log.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/log.js +14 -6
- package/node_modules/@oxygen/shared/dist/redaction.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/redaction.js +0 -1
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/telemetry.js +0 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -2
- package/node_modules/@oxygen/shared/dist/version.js +1 -2
- package/node_modules/@oxygen/workflows/dist/index.d.ts +145 -144
- package/node_modules/@oxygen/workflows/dist/index.js +30 -27
- package/package.json +6 -1
- package/dist/browser-login.d.ts.map +0 -1
- package/dist/browser-login.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/http-client.d.ts.map +0 -1
- package/dist/http-client.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/local-custom-http-column.d.ts.map +0 -1
- package/dist/local-custom-http-column.js.map +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js.map +0 -1
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts.map +0 -1
- package/node_modules/@oxygen/recipe-sdk/dist/index.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/billing.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/billing.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/column-types.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/column-types.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/credit-guidance.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/file-import.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/file-import.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/index.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/log.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/log.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/redaction.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/redaction.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/telemetry.js.map +0 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts.map +0 -1
- package/node_modules/@oxygen/shared/dist/version.js.map +0 -1
- package/node_modules/@oxygen/workflows/dist/index.d.ts.map +0 -1
- package/node_modules/@oxygen/workflows/dist/index.js.map +0 -1
|
@@ -387,7 +387,7 @@ async function readLocalHttpResponseBody(response) {
|
|
|
387
387
|
if (!text)
|
|
388
388
|
return null;
|
|
389
389
|
const contentType = response.headers.get("content-type") ?? "";
|
|
390
|
-
const looksJson = contentType.toLowerCase().includes("json") || /^[\s\n\r]*[\[{]/.test(text);
|
|
390
|
+
const looksJson = contentType.toLowerCase().includes("json") || /^[\s\n\r]*[\[{]/.test(text); // skipcq: JS-0097
|
|
391
391
|
if (!looksJson)
|
|
392
392
|
return text;
|
|
393
393
|
try {
|
|
@@ -568,4 +568,3 @@ function isNonEmptyLocalValue(value) {
|
|
|
568
568
|
function isRecord(value) {
|
|
569
569
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
570
570
|
}
|
|
571
|
-
//# sourceMappingURL=local-custom-http-column.js.map
|
package/dist/session.d.ts
CHANGED
package/dist/session.js
CHANGED
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
type SpawnResult = ReturnType<typeof spawnSync>;
|
|
3
|
+
type SpawnSyncLike = (command: string, args: string[], options: {
|
|
4
|
+
encoding: BufferEncoding;
|
|
5
|
+
env: NodeJS.ProcessEnv;
|
|
6
|
+
stdio: "inherit" | ["ignore", "pipe", "pipe"];
|
|
7
|
+
windowsHide: boolean;
|
|
8
|
+
windowsVerbatimArguments?: boolean;
|
|
9
|
+
}) => SpawnResult;
|
|
10
|
+
export type UpdateOptions = {
|
|
11
|
+
json?: boolean;
|
|
12
|
+
package?: string;
|
|
13
|
+
dryRun?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export type UpdateResult = {
|
|
16
|
+
current_version: string;
|
|
17
|
+
package: string;
|
|
18
|
+
command: string;
|
|
19
|
+
dry_run: boolean;
|
|
20
|
+
updated: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare function updateCli(options: UpdateOptions, runtime?: {
|
|
23
|
+
env?: NodeJS.ProcessEnv;
|
|
24
|
+
platform?: NodeJS.Platform;
|
|
25
|
+
modulePath?: string;
|
|
26
|
+
spawnSync?: SpawnSyncLike;
|
|
27
|
+
}): UpdateResult;
|
|
28
|
+
export declare function detectCliInstallPrefix(modulePath?: string): string | null;
|
|
29
|
+
export declare function detectCliInstallPrefixFromPath(modulePath: string): string | null;
|
|
30
|
+
export declare function resolveNpmExecutables(platform?: NodeJS.Platform): string[];
|
|
31
|
+
export {};
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { OXYGEN_VERSION, OxygenError } from "@oxygen/shared";
|
|
4
|
+
const DEFAULT_CLI_PACKAGE_SPEC = "@oxygen-agent/cli@latest";
|
|
5
|
+
export function updateCli(options, runtime = {}) {
|
|
6
|
+
const env = runtime.env ?? process.env;
|
|
7
|
+
const platform = runtime.platform ?? process.platform;
|
|
8
|
+
const spawn = runtime.spawnSync ?? spawnSync;
|
|
9
|
+
const packageSpec = readOption(options.package) ?? DEFAULT_CLI_PACKAGE_SPEC;
|
|
10
|
+
const prefix = detectCliInstallPrefix(runtime.modulePath);
|
|
11
|
+
const args = prefix
|
|
12
|
+
? ["install", "-g", "--prefix", prefix, packageSpec]
|
|
13
|
+
: ["install", "-g", packageSpec];
|
|
14
|
+
// The command string is for human display (printed in dry-run / success
|
|
15
|
+
// output). Quote args that contain shell-significant characters so a
|
|
16
|
+
// copy-paste of the line works on install prefixes with spaces.
|
|
17
|
+
const command = ["npm", ...args].map(quoteForDisplay).join(" ");
|
|
18
|
+
if (options.dryRun) {
|
|
19
|
+
return {
|
|
20
|
+
current_version: OXYGEN_VERSION,
|
|
21
|
+
package: packageSpec,
|
|
22
|
+
command,
|
|
23
|
+
dry_run: true,
|
|
24
|
+
updated: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const attemptedExecutables = [];
|
|
28
|
+
let lastResult = null;
|
|
29
|
+
for (const invocation of resolveNpmInvocations(args, platform, env)) {
|
|
30
|
+
attemptedExecutables.push(invocation.label);
|
|
31
|
+
const result = spawn(invocation.executable, invocation.args, {
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
env,
|
|
34
|
+
stdio: options.json ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
35
|
+
windowsHide: true,
|
|
36
|
+
// We pre-quote args for `cmd /d /s /c`; Node must not re-escape them
|
|
37
|
+
// or the install prefix path ends up with literal `"` characters
|
|
38
|
+
// baked in (see scripts/ci/cli-package-smoke.mjs for the same fix).
|
|
39
|
+
...(platform === "win32" ? { windowsVerbatimArguments: true } : {}),
|
|
40
|
+
});
|
|
41
|
+
lastResult = result;
|
|
42
|
+
if (result.error && isMissingExecutableError(result.error))
|
|
43
|
+
continue;
|
|
44
|
+
if (!result.error && result.status === 0) {
|
|
45
|
+
return {
|
|
46
|
+
current_version: OXYGEN_VERSION,
|
|
47
|
+
package: packageSpec,
|
|
48
|
+
command,
|
|
49
|
+
dry_run: false,
|
|
50
|
+
updated: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
const result = lastResult;
|
|
56
|
+
throw new OxygenError("cli_update_failed", "Unable to update the Oxygen CLI.", {
|
|
57
|
+
details: {
|
|
58
|
+
command,
|
|
59
|
+
package: packageSpec,
|
|
60
|
+
exit_code: result?.status ?? null,
|
|
61
|
+
reason: result?.error instanceof Error ? result.error.message : null,
|
|
62
|
+
stderr: typeof result?.stderr === "string" && result.stderr.trim() ? result.stderr.trim().slice(0, 4000) : null,
|
|
63
|
+
attempted_executables: attemptedExecutables,
|
|
64
|
+
},
|
|
65
|
+
exitCode: 1,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export function detectCliInstallPrefix(modulePath = fileURLToPath(import.meta.url)) {
|
|
69
|
+
return detectCliInstallPrefixFromPath(modulePath);
|
|
70
|
+
}
|
|
71
|
+
export function detectCliInstallPrefixFromPath(modulePath) {
|
|
72
|
+
const normalizedPath = modulePath.replace(/\\/g, "/");
|
|
73
|
+
const markers = [
|
|
74
|
+
"/node_modules/@oxygen-agent/cli/dist/",
|
|
75
|
+
"/node_modules/@oxygen/cli/dist/",
|
|
76
|
+
];
|
|
77
|
+
for (const marker of markers) {
|
|
78
|
+
const markerIndex = normalizedPath.lastIndexOf(marker);
|
|
79
|
+
if (markerIndex === -1)
|
|
80
|
+
continue;
|
|
81
|
+
const packageParent = normalizedPath.slice(0, markerIndex);
|
|
82
|
+
if (packageParent.endsWith("/lib"))
|
|
83
|
+
return packageParent.slice(0, -"/lib".length);
|
|
84
|
+
return packageParent;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
export function resolveNpmExecutables(platform = process.platform) {
|
|
89
|
+
return platform === "win32" ? ["cmd.exe"] : ["npm"];
|
|
90
|
+
}
|
|
91
|
+
function readOption(value) {
|
|
92
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
93
|
+
}
|
|
94
|
+
function isMissingExecutableError(error) {
|
|
95
|
+
return error.code === "ENOENT";
|
|
96
|
+
}
|
|
97
|
+
function resolveNpmInvocations(args, platform, env) {
|
|
98
|
+
if (platform !== "win32")
|
|
99
|
+
return [{ executable: "npm", args, label: "npm" }];
|
|
100
|
+
const shell = env.ComSpec?.trim() || "cmd.exe";
|
|
101
|
+
return [{
|
|
102
|
+
executable: shell,
|
|
103
|
+
args: ["/d", "/s", "/c", ["npm", ...args].map(quoteWindowsCmdArg).join(" ")],
|
|
104
|
+
label: shell,
|
|
105
|
+
}];
|
|
106
|
+
}
|
|
107
|
+
function quoteWindowsCmdArg(value) {
|
|
108
|
+
if (/^[A-Za-z0-9_@+=:,./\\-]+$/.test(value))
|
|
109
|
+
return value;
|
|
110
|
+
return `"${value.replace(/(["^&|<>()%])/g, "^$1")}"`;
|
|
111
|
+
}
|
|
112
|
+
function quoteForDisplay(value) {
|
|
113
|
+
if (/^[A-Za-z0-9_@+=:,./-]+$/.test(value))
|
|
114
|
+
return value;
|
|
115
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
116
|
+
}
|
|
@@ -133,4 +133,3 @@ export type DefineDurableRecipeInput = DefineRecipeInput & {
|
|
|
133
133
|
};
|
|
134
134
|
export declare function defineRecipe(input: DefineRecipeInput): RecipeDefinition;
|
|
135
135
|
export declare function isRecipeDefinition(value: unknown): value is RecipeDefinition;
|
|
136
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -108,7 +108,8 @@ export declare const ACTIVE_SUBSCRIPTION_STATUSES: readonly ["active", "trialing
|
|
|
108
108
|
export type ActiveSubscriptionStatus = (typeof ACTIVE_SUBSCRIPTION_STATUSES)[number];
|
|
109
109
|
export type ResolvedPricingPlan = PricingPlanDefinition;
|
|
110
110
|
export declare function resolveBasePricingPlan(tier: string | null | undefined): ResolvedPricingPlan | null;
|
|
111
|
-
export declare function applyPricingPlanMetadataOverrides(
|
|
111
|
+
export declare function applyPricingPlanMetadataOverrides(// skipcq: JS-R1005
|
|
112
|
+
plan: PricingPlanDefinition, metadata: PricingPlanMetadata): PricingPlanDefinition;
|
|
112
113
|
export declare function isSelfServePlanTier(value: string): value is SelfServePlanTier;
|
|
113
114
|
export declare function isBillingCurrency(value: string): value is BillingCurrency;
|
|
114
115
|
export declare function normalizeBillingCurrency(value: string | null | undefined): BillingCurrency;
|
|
@@ -121,4 +122,3 @@ export declare function evaluateWeeklyQuota(usedCredits: number, requestedCredit
|
|
|
121
122
|
projectedRemainingCredits: number;
|
|
122
123
|
exceedsLimit: boolean;
|
|
123
124
|
};
|
|
124
|
-
//# sourceMappingURL=billing.d.ts.map
|
|
@@ -208,7 +208,8 @@ export function resolveBasePricingPlan(tier) {
|
|
|
208
208
|
const legacy = LEGACY_PRICING_PLAN_OVERRIDES[tier];
|
|
209
209
|
return legacy ?? null;
|
|
210
210
|
}
|
|
211
|
-
export function applyPricingPlanMetadataOverrides(
|
|
211
|
+
export function applyPricingPlanMetadataOverrides(// skipcq: JS-R1005
|
|
212
|
+
plan, metadata) {
|
|
212
213
|
const record = readRecord(metadata);
|
|
213
214
|
if (!record)
|
|
214
215
|
return plan;
|
|
@@ -293,4 +294,3 @@ export function evaluateWeeklyQuota(usedCredits, requestedCredits, weeklyCredits
|
|
|
293
294
|
exceedsLimit: projectedCredits > weeklyCreditsLimit,
|
|
294
295
|
};
|
|
295
296
|
}
|
|
296
|
-
//# sourceMappingURL=billing.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cell display formatter for CLI, MCP widgets, and the web app.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for how a stored cell value should look to a human.
|
|
5
|
+
* Storage stays untouched — this only decides what to render.
|
|
6
|
+
*
|
|
7
|
+
* Three responsibilities:
|
|
8
|
+
* 1. Format known-typed values consistently (numeric → 1,154; timestamp → ISO/short).
|
|
9
|
+
* 2. Rescue numeric-looking values that landed in a `text` column. Headcount
|
|
10
|
+
* and similar fields routinely arrive as text from CSV imports, AI columns,
|
|
11
|
+
* and provider adapters, so a `text` column whose values parse cleanly as
|
|
12
|
+
* numbers still gets thousands grouping at display time.
|
|
13
|
+
* 3. Refuse to "rescue" things that aren't actually numbers (IP addresses,
|
|
14
|
+
* version strings, phone numbers, URLs), via {@link looksLikeNumericText}.
|
|
15
|
+
*/
|
|
16
|
+
/** Surfaces have different escaping/length budgets, but the canonical string is shared. */
|
|
17
|
+
export type CellFormatSurface = "cli" | "mcp" | "web";
|
|
18
|
+
export type CellColumnLike = {
|
|
19
|
+
dataType?: string | null;
|
|
20
|
+
data_type?: string | null;
|
|
21
|
+
kind?: string | null;
|
|
22
|
+
label?: string | null;
|
|
23
|
+
key?: string | null;
|
|
24
|
+
semanticType?: string | null;
|
|
25
|
+
semantic_type?: string | null;
|
|
26
|
+
};
|
|
27
|
+
export type CellFormatOptions = {
|
|
28
|
+
surface: CellFormatSurface;
|
|
29
|
+
locale?: string;
|
|
30
|
+
/** When the value is rescued from a text column, callers can show a hint. */
|
|
31
|
+
onRescued?: (parsed: number) => void;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Canonical display string for a single cell.
|
|
35
|
+
*
|
|
36
|
+
* Returns `""` for null/undefined/empty so callers can treat the absence of
|
|
37
|
+
* a value as the absence of a string. Surface-specific concerns (HTML
|
|
38
|
+
* escaping, ellipsis budgets) belong outside this function.
|
|
39
|
+
*/
|
|
40
|
+
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
|
+
/**
|
|
55
|
+
* Decide whether the same value-level + column-level guards used inside
|
|
56
|
+
* `formatCellForDisplay`'s text path should rescue this string. Exposed so
|
|
57
|
+
* non-formatter callers (web grid, etc.) can stay in lockstep without
|
|
58
|
+
* duplicating the heuristic.
|
|
59
|
+
*/
|
|
60
|
+
export declare function tryRescueTextCell(value: string, column: CellColumnLike | null | undefined): number | null;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared cell display formatter for CLI, MCP widgets, and the web app.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for how a stored cell value should look to a human.
|
|
5
|
+
* Storage stays untouched — this only decides what to render.
|
|
6
|
+
*
|
|
7
|
+
* Three responsibilities:
|
|
8
|
+
* 1. Format known-typed values consistently (numeric → 1,154; timestamp → ISO/short).
|
|
9
|
+
* 2. Rescue numeric-looking values that landed in a `text` column. Headcount
|
|
10
|
+
* and similar fields routinely arrive as text from CSV imports, AI columns,
|
|
11
|
+
* and provider adapters, so a `text` column whose values parse cleanly as
|
|
12
|
+
* numbers still gets thousands grouping at display time.
|
|
13
|
+
* 3. Refuse to "rescue" things that aren't actually numbers (IP addresses,
|
|
14
|
+
* version strings, phone numbers, URLs), via {@link looksLikeNumericText}.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_LOCALE = "en-US";
|
|
17
|
+
const INTEGER_RE = /^-?\d{1,15}$/;
|
|
18
|
+
const DECIMAL_RE = /^-?\d{1,15}(?:\.\d{1,9})?$/;
|
|
19
|
+
const GROUPED_RE = /^-?\d{1,3}(?:,\d{3})+(?:\.\d+)?$/;
|
|
20
|
+
// Matches strings that contain only digits and dots with at least two dots
|
|
21
|
+
// (e.g. "0.0.1.154", "192.168.1.1"). Used to *detect*, not classify; the
|
|
22
|
+
// numeric-rescue path then decides whether the parts can be safely joined.
|
|
23
|
+
const DOTTED_NUMERIC_RE = /^[+-]?\d+(?:\.\d+){2,}$/;
|
|
24
|
+
const ENRICHMENT_VALUE_KEYS = ["value", "text", "result"];
|
|
25
|
+
// Column kinds whose cell shape is a wrapper object emitted by an enrichment,
|
|
26
|
+
// tool, AI, action, or formula run — for these we unwrap the carried value.
|
|
27
|
+
// Manual/source columns can also legitimately hold `{value: …, source: …}`
|
|
28
|
+
// JSON data the user typed themselves; we leave those untouched.
|
|
29
|
+
const WRAPPER_COLUMN_KINDS = new Set([
|
|
30
|
+
"enrichment",
|
|
31
|
+
"tool",
|
|
32
|
+
"tool_enrichment",
|
|
33
|
+
"ai",
|
|
34
|
+
"action",
|
|
35
|
+
"formula",
|
|
36
|
+
"relation",
|
|
37
|
+
]);
|
|
38
|
+
// Digit-only strings of this length or longer are far more likely to be
|
|
39
|
+
// identifiers (US phone = 10, SSN/EIN/DUNS = 9, account numbers) than
|
|
40
|
+
// headcount/revenue/etc. Headcount and similar fit comfortably under this.
|
|
41
|
+
const IDENTIFIER_DIGIT_LENGTH = 10;
|
|
42
|
+
// Column hints that strongly suggest the column holds identifiers (where
|
|
43
|
+
// applying thousands grouping or stripping leading zeros would corrupt the
|
|
44
|
+
// displayed value). Matched against `label`, `key`, and `semantic*` fields.
|
|
45
|
+
// We use explicit non-alphanumeric boundaries rather than `\b` because `_` is
|
|
46
|
+
// a JS word character, so `\bphone\b` would not match a `phone_number` key.
|
|
47
|
+
const IDENTIFIER_COLUMN_RE = /(?:^|[^a-z0-9])(phone|mobile|tel(?:ephone)?|fax|zip|postal|postcode|ssn|ein|tin|vat|gst|iban|swift|duns|sku|upc|ean|isbn|imei|customer[_ -]?(?:no|number|id)|account[_ -]?(?:no|number|id))(?:$|[^a-z0-9])/i;
|
|
48
|
+
/**
|
|
49
|
+
* Canonical display string for a single cell.
|
|
50
|
+
*
|
|
51
|
+
* Returns `""` for null/undefined/empty so callers can treat the absence of
|
|
52
|
+
* a value as the absence of a string. Surface-specific concerns (HTML
|
|
53
|
+
* escaping, ellipsis budgets) belong outside this function.
|
|
54
|
+
*/
|
|
55
|
+
export function formatCellForDisplay(value, column, options) {
|
|
56
|
+
if (value === null || value === undefined || value === "")
|
|
57
|
+
return "";
|
|
58
|
+
const dataType = readDataType(column);
|
|
59
|
+
const locale = options.locale ?? DEFAULT_LOCALE;
|
|
60
|
+
if (dataType === "boolean")
|
|
61
|
+
return formatBoolean(value, options.surface);
|
|
62
|
+
if (dataType === "timestamptz")
|
|
63
|
+
return formatTimestamp(value, options.surface);
|
|
64
|
+
if (dataType === "numeric")
|
|
65
|
+
return formatNumeric(value, locale, options.surface);
|
|
66
|
+
if (isEnrichmentPayload(value) && columnHasWrapperKind(column)) {
|
|
67
|
+
const wrapped = readEnrichmentValue(value);
|
|
68
|
+
if (wrapped !== undefined) {
|
|
69
|
+
return formatCellForDisplay(wrapped, column, options);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (typeof value === "number") {
|
|
73
|
+
return Number.isFinite(value) ? formatNumberValue(value, locale) : String(value);
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
const rescue = rescueNumericText(value);
|
|
77
|
+
if (rescue !== null
|
|
78
|
+
&& shouldRescueColumn(column)
|
|
79
|
+
&& !looksLikeBareIdentifierDigits(value.trim())) {
|
|
80
|
+
options.onRescued?.(rescue);
|
|
81
|
+
return formatNumberValue(rescue, locale);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === "object") {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(value);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return String(value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return String(value);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Best-effort parse of a string that looks numeric (with or without thousands
|
|
97
|
+
* grouping, with or without dotted IP-style separators) into a finite number.
|
|
98
|
+
* Returns `null` when the string is clearly *not* a single number — IP
|
|
99
|
+
* addresses, version strings, phone numbers, anything alphabetic.
|
|
100
|
+
*/
|
|
101
|
+
export function rescueNumericText(raw) {
|
|
102
|
+
const trimmed = raw.trim();
|
|
103
|
+
if (!trimmed)
|
|
104
|
+
return null;
|
|
105
|
+
if (INTEGER_RE.test(trimmed)) {
|
|
106
|
+
const n = Number(trimmed);
|
|
107
|
+
return Number.isFinite(n) ? n : null;
|
|
108
|
+
}
|
|
109
|
+
if (DECIMAL_RE.test(trimmed)) {
|
|
110
|
+
const n = Number(trimmed);
|
|
111
|
+
return Number.isFinite(n) ? n : null;
|
|
112
|
+
}
|
|
113
|
+
if (GROUPED_RE.test(trimmed)) {
|
|
114
|
+
const n = Number(trimmed.replace(/,/g, ""));
|
|
115
|
+
return Number.isFinite(n) ? n : null;
|
|
116
|
+
}
|
|
117
|
+
if (DOTTED_NUMERIC_RE.test(trimmed)) {
|
|
118
|
+
return decodeDottedNumeric(trimmed);
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* True when a digit-only string is shaped like an identifier — phone number,
|
|
124
|
+
* ZIP code with a preserved leading zero, SSN/EIN/DUNS, account or customer
|
|
125
|
+
* number. We refuse to rescue these even in numeric-looking text columns,
|
|
126
|
+
* because thousands-grouping (4155550123 → "4,155,550,123") or leading-zero
|
|
127
|
+
* stripping ("02101" → "2101") corrupts the displayed value.
|
|
128
|
+
*/
|
|
129
|
+
function looksLikeBareIdentifierDigits(trimmed) {
|
|
130
|
+
if (!/^\d+$/.test(trimmed))
|
|
131
|
+
return false;
|
|
132
|
+
if (trimmed.startsWith("0"))
|
|
133
|
+
return true;
|
|
134
|
+
return trimmed.length >= IDENTIFIER_DIGIT_LENGTH;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Decide whether a dotted-numeric string like `0.0.1.154` is a mangled integer
|
|
138
|
+
* (return 1154) or a real address/version (return null).
|
|
139
|
+
*
|
|
140
|
+
* Signature of a mangled number: ≥2 leading parts that are literally "0".
|
|
141
|
+
* That excludes real IPv4 ("192.168.1.1"), semver ("1.2.3"), and partial IPs
|
|
142
|
+
* ("10.0.0.1"), all of which have at most one leading zero group.
|
|
143
|
+
*
|
|
144
|
+
* Real headcount/revenue/employee values almost never start with multiple
|
|
145
|
+
* leading zeros — so this guard is conservative on the "don't break real IPs"
|
|
146
|
+
* side, while still recovering the screenshot's `0.0.1.154` shape.
|
|
147
|
+
*/
|
|
148
|
+
function decodeDottedNumeric(raw) {
|
|
149
|
+
const sign = raw.startsWith("-") ? -1 : 1;
|
|
150
|
+
const body = raw.replace(/^[+-]/, "");
|
|
151
|
+
const parts = body.split(".");
|
|
152
|
+
if (parts.length < 3)
|
|
153
|
+
return null;
|
|
154
|
+
let leadingZeros = 0;
|
|
155
|
+
for (const part of parts) {
|
|
156
|
+
if (part === "0")
|
|
157
|
+
leadingZeros += 1;
|
|
158
|
+
else
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (leadingZeros < 2)
|
|
162
|
+
return null;
|
|
163
|
+
const concatenated = parts.join("");
|
|
164
|
+
if (!/^\d+$/.test(concatenated))
|
|
165
|
+
return null;
|
|
166
|
+
const normalized = concatenated.replace(/^0+(?=\d)/, "");
|
|
167
|
+
const n = Number(normalized || "0");
|
|
168
|
+
return Number.isFinite(n) ? sign * n : null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* True when a string is unambiguously numeric (integer, decimal, grouped, or
|
|
172
|
+
* a mangled dotted form we can recover). Exported for callers that want to
|
|
173
|
+
* highlight rescue cases without re-running the formatter.
|
|
174
|
+
*/
|
|
175
|
+
export function looksLikeNumericText(value) {
|
|
176
|
+
return rescueNumericText(value) !== null;
|
|
177
|
+
}
|
|
178
|
+
function readDataType(column) {
|
|
179
|
+
const raw = column?.dataType ?? column?.data_type ?? null;
|
|
180
|
+
if (!raw)
|
|
181
|
+
return null;
|
|
182
|
+
if (raw === "text" || raw === "numeric" || raw === "boolean" || raw === "timestamptz") {
|
|
183
|
+
return raw;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Rescue only applies to columns that are reasonable to interpret numerically.
|
|
189
|
+
* Allowlist `text` and `numeric` explicitly — JSONB / unknown data types fall
|
|
190
|
+
* through (a stored string like `"123456789"` in a payload column shouldn't
|
|
191
|
+
* be silently regrouped). Also skip identifier-shaped columns (phone, ZIP,
|
|
192
|
+
* DUNS, etc.) where formatting would corrupt the displayed value, and skip
|
|
193
|
+
* when the column has no metadata at all.
|
|
194
|
+
*/
|
|
195
|
+
function shouldRescueColumn(column) {
|
|
196
|
+
if (!column)
|
|
197
|
+
return false;
|
|
198
|
+
const dataType = readDataType(column);
|
|
199
|
+
if (dataType !== "text" && dataType !== "numeric")
|
|
200
|
+
return false;
|
|
201
|
+
if (columnLooksLikeIdentifier(column))
|
|
202
|
+
return false;
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Decide whether the same value-level + column-level guards used inside
|
|
207
|
+
* `formatCellForDisplay`'s text path should rescue this string. Exposed so
|
|
208
|
+
* non-formatter callers (web grid, etc.) can stay in lockstep without
|
|
209
|
+
* duplicating the heuristic.
|
|
210
|
+
*/
|
|
211
|
+
export function tryRescueTextCell(value, column) {
|
|
212
|
+
const trimmed = value.trim();
|
|
213
|
+
if (!trimmed)
|
|
214
|
+
return null;
|
|
215
|
+
if (!shouldRescueColumn(column))
|
|
216
|
+
return null;
|
|
217
|
+
if (looksLikeBareIdentifierDigits(trimmed))
|
|
218
|
+
return null;
|
|
219
|
+
return rescueNumericText(trimmed);
|
|
220
|
+
}
|
|
221
|
+
function columnHasWrapperKind(column) {
|
|
222
|
+
const kind = column?.kind;
|
|
223
|
+
return typeof kind === "string" && WRAPPER_COLUMN_KINDS.has(kind);
|
|
224
|
+
}
|
|
225
|
+
function columnLooksLikeIdentifier(column) {
|
|
226
|
+
const hints = [column.label, column.key, column.semanticType, column.semantic_type];
|
|
227
|
+
for (const hint of hints) {
|
|
228
|
+
if (typeof hint === "string" && IDENTIFIER_COLUMN_RE.test(hint))
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
function formatNumberValue(value, locale) {
|
|
234
|
+
// `Intl.NumberFormat` defaults to `maximumFractionDigits: 3` which silently
|
|
235
|
+
// truncates real precision on financial / scientific values. Lift the cap
|
|
236
|
+
// so cells render exactly the digits the user stored.
|
|
237
|
+
return new Intl.NumberFormat(locale, { maximumFractionDigits: 20 }).format(value);
|
|
238
|
+
}
|
|
239
|
+
function formatNumeric(value, locale, _surface) {
|
|
240
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
241
|
+
return formatNumberValue(value, locale);
|
|
242
|
+
}
|
|
243
|
+
if (typeof value === "string") {
|
|
244
|
+
const rescue = rescueNumericText(value);
|
|
245
|
+
if (rescue !== null)
|
|
246
|
+
return formatNumberValue(rescue, locale);
|
|
247
|
+
return value;
|
|
248
|
+
}
|
|
249
|
+
return String(value);
|
|
250
|
+
}
|
|
251
|
+
function formatBoolean(value, surface) {
|
|
252
|
+
const truthy = value === true || value === "true" || value === 1 || value === "1";
|
|
253
|
+
if (surface === "cli")
|
|
254
|
+
return truthy ? "true" : "false";
|
|
255
|
+
return truthy ? "Yes" : "No";
|
|
256
|
+
}
|
|
257
|
+
function formatTimestamp(value, surface) {
|
|
258
|
+
if (value instanceof Date) {
|
|
259
|
+
return surface === "web" ? value.toISOString().slice(0, 10) : value.toISOString();
|
|
260
|
+
}
|
|
261
|
+
if (typeof value !== "string")
|
|
262
|
+
return String(value);
|
|
263
|
+
const trimmed = value.trim();
|
|
264
|
+
const date = new Date(trimmed);
|
|
265
|
+
if (!Number.isFinite(date.getTime()))
|
|
266
|
+
return trimmed;
|
|
267
|
+
return surface === "web" ? date.toISOString().slice(0, 10) : date.toISOString();
|
|
268
|
+
}
|
|
269
|
+
function isEnrichmentPayload(value) {
|
|
270
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
271
|
+
}
|
|
272
|
+
function readEnrichmentValue(payload) {
|
|
273
|
+
for (const key of ENRICHMENT_VALUE_KEYS) {
|
|
274
|
+
if (key in payload)
|
|
275
|
+
return payload[key];
|
|
276
|
+
}
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
@@ -51,5 +51,5 @@ export type CoercionResult = {
|
|
|
51
51
|
* Used by column retype: every value must coerce or the retype is rejected so
|
|
52
52
|
* the table is never left half-converted.
|
|
53
53
|
*/
|
|
54
|
-
export declare function coerceValueForDataType(
|
|
55
|
-
|
|
54
|
+
export declare function coerceValueForDataType(// skipcq: JS-0045
|
|
55
|
+
value: unknown, dataType: RetypeableDataType): CoercionResult;
|
|
@@ -127,11 +127,12 @@ function isExcelSerialValue(value) {
|
|
|
127
127
|
* Used by column retype: every value must coerce or the retype is rejected so
|
|
128
128
|
* the table is never left half-converted.
|
|
129
129
|
*/
|
|
130
|
-
export function coerceValueForDataType(
|
|
130
|
+
export function coerceValueForDataType(// skipcq: JS-0045
|
|
131
|
+
value, dataType) {
|
|
131
132
|
if (value === null || value === undefined || value === "") {
|
|
132
133
|
return { ok: true, value: null };
|
|
133
134
|
}
|
|
134
|
-
switch (dataType) {
|
|
135
|
+
switch (dataType) { // skipcq: JS-0047
|
|
135
136
|
case "text":
|
|
136
137
|
return { ok: true, value: typeof value === "string" ? value : String(value) };
|
|
137
138
|
case "timestamptz": {
|
|
@@ -158,4 +159,3 @@ export function coerceValueForDataType(value, dataType) {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
|
-
//# sourceMappingURL=column-types.js.map
|
|
@@ -12,4 +12,3 @@ export declare function buildCreditGuidance(input: {
|
|
|
12
12
|
headroomMultiplier?: number;
|
|
13
13
|
}): CreditGuidance;
|
|
14
14
|
export declare function recommendedCreditCeiling(estimatedCredits: number | null | undefined, headroomMultiplier?: number): number | null;
|
|
15
|
-
//# sourceMappingURL=credit-guidance.d.ts.map
|
|
@@ -19,4 +19,3 @@ export declare function parseRowsText(text: string, format: Exclude<RowsFileForm
|
|
|
19
19
|
export declare function inferImportColumnLabels(rows: Record<string, unknown>[]): string[];
|
|
20
20
|
export declare function normalizeRowsForNewTable(rows: Record<string, unknown>[]): NewTableImportRows;
|
|
21
21
|
export declare function normalizeImportColumnKey(value: string): string;
|
|
22
|
-
//# sourceMappingURL=file-import.d.ts.map
|
|
@@ -92,7 +92,7 @@ export function normalizeRowsForNewTable(rows) {
|
|
|
92
92
|
export function normalizeImportColumnKey(value) {
|
|
93
93
|
const normalized = value
|
|
94
94
|
.normalize("NFKD")
|
|
95
|
-
.replace(/[\u0300-\u036f]/g, "")
|
|
95
|
+
.replace(/[\u0300-\u036f]/g, "") // skipcq: JS-0117
|
|
96
96
|
.toLowerCase()
|
|
97
97
|
.replace(/[^a-z0-9]+/g, "_")
|
|
98
98
|
.replace(/^_+|_+$/g, "")
|
|
@@ -229,4 +229,3 @@ function makeUniqueImportKey(label, taken) {
|
|
|
229
229
|
exitCode: 1,
|
|
230
230
|
});
|
|
231
231
|
}
|
|
232
|
-
//# sourceMappingURL=file-import.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { OXYGEN_VERSION } from "./version.js";
|
|
2
2
|
export * from "./billing.js";
|
|
3
|
+
export * from "./cell-format.js";
|
|
3
4
|
export * from "./column-types.js";
|
|
4
5
|
export * from "./credit-guidance.js";
|
|
5
6
|
export * from "./log.js";
|
|
@@ -42,4 +43,3 @@ export declare function failure(command: string, error: {
|
|
|
42
43
|
details?: unknown;
|
|
43
44
|
}, version?: string): CliFailure;
|
|
44
45
|
export declare function toFailure(command: string, error: unknown, version?: string): CliFailure;
|
|
45
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OXYGEN_VERSION } from "./version.js";
|
|
2
2
|
export { OXYGEN_VERSION } from "./version.js";
|
|
3
3
|
export * from "./billing.js";
|
|
4
|
+
export * from "./cell-format.js";
|
|
4
5
|
export * from "./column-types.js";
|
|
5
6
|
export * from "./credit-guidance.js";
|
|
6
7
|
export * from "./log.js";
|
|
@@ -46,4 +47,3 @@ export function toFailure(command, error, version = OXYGEN_VERSION) {
|
|
|
46
47
|
}
|
|
47
48
|
return failure(command, { code: "unexpected_error", message: "An unexpected error occurred." }, version);
|
|
48
49
|
}
|
|
49
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -19,4 +19,3 @@ export declare function getLogContext(): LogContext;
|
|
|
19
19
|
export declare function log(level: LogLevel, msg: string, fields?: Record<string, unknown>): void;
|
|
20
20
|
export declare function errorId(err: unknown): string;
|
|
21
21
|
export declare function errorFields(err: unknown): Record<string, unknown>;
|
|
22
|
-
//# sourceMappingURL=log.d.ts.map
|