@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.
Files changed (74) hide show
  1. package/README.md +13 -1
  2. package/dist/browser-login.d.ts +0 -1
  3. package/dist/browser-login.js +0 -1
  4. package/dist/credentials.d.ts +0 -1
  5. package/dist/credentials.js +114 -44
  6. package/dist/http-client.d.ts +1 -2
  7. package/dist/http-client.js +0 -1
  8. package/dist/index.d.ts +0 -1
  9. package/dist/index.js +436 -87
  10. package/dist/local-custom-http-column.d.ts +0 -1
  11. package/dist/local-custom-http-column.js +1 -2
  12. package/dist/session.d.ts +0 -1
  13. package/dist/session.js +0 -1
  14. package/dist/update.d.ts +31 -0
  15. package/dist/update.js +116 -0
  16. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +0 -1
  17. package/node_modules/@oxygen/recipe-sdk/dist/index.js +0 -1
  18. package/node_modules/@oxygen/shared/dist/billing.d.ts +2 -2
  19. package/node_modules/@oxygen/shared/dist/billing.js +2 -2
  20. package/node_modules/@oxygen/shared/dist/cell-format.d.ts +60 -0
  21. package/node_modules/@oxygen/shared/dist/cell-format.js +278 -0
  22. package/node_modules/@oxygen/shared/dist/column-types.d.ts +2 -2
  23. package/node_modules/@oxygen/shared/dist/column-types.js +3 -3
  24. package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts +0 -1
  25. package/node_modules/@oxygen/shared/dist/credit-guidance.js +0 -1
  26. package/node_modules/@oxygen/shared/dist/file-import.d.ts +0 -1
  27. package/node_modules/@oxygen/shared/dist/file-import.js +1 -2
  28. package/node_modules/@oxygen/shared/dist/index.d.ts +1 -1
  29. package/node_modules/@oxygen/shared/dist/index.js +1 -1
  30. package/node_modules/@oxygen/shared/dist/log.d.ts +0 -1
  31. package/node_modules/@oxygen/shared/dist/log.js +14 -6
  32. package/node_modules/@oxygen/shared/dist/redaction.d.ts +0 -1
  33. package/node_modules/@oxygen/shared/dist/redaction.js +0 -1
  34. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +0 -1
  35. package/node_modules/@oxygen/shared/dist/telemetry.js +0 -1
  36. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -2
  37. package/node_modules/@oxygen/shared/dist/version.js +1 -2
  38. package/node_modules/@oxygen/workflows/dist/index.d.ts +145 -144
  39. package/node_modules/@oxygen/workflows/dist/index.js +30 -27
  40. package/package.json +6 -1
  41. package/dist/browser-login.d.ts.map +0 -1
  42. package/dist/browser-login.js.map +0 -1
  43. package/dist/credentials.d.ts.map +0 -1
  44. package/dist/credentials.js.map +0 -1
  45. package/dist/http-client.d.ts.map +0 -1
  46. package/dist/http-client.js.map +0 -1
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js.map +0 -1
  49. package/dist/local-custom-http-column.d.ts.map +0 -1
  50. package/dist/local-custom-http-column.js.map +0 -1
  51. package/dist/session.d.ts.map +0 -1
  52. package/dist/session.js.map +0 -1
  53. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts.map +0 -1
  54. package/node_modules/@oxygen/recipe-sdk/dist/index.js.map +0 -1
  55. package/node_modules/@oxygen/shared/dist/billing.d.ts.map +0 -1
  56. package/node_modules/@oxygen/shared/dist/billing.js.map +0 -1
  57. package/node_modules/@oxygen/shared/dist/column-types.d.ts.map +0 -1
  58. package/node_modules/@oxygen/shared/dist/column-types.js.map +0 -1
  59. package/node_modules/@oxygen/shared/dist/credit-guidance.d.ts.map +0 -1
  60. package/node_modules/@oxygen/shared/dist/credit-guidance.js.map +0 -1
  61. package/node_modules/@oxygen/shared/dist/file-import.d.ts.map +0 -1
  62. package/node_modules/@oxygen/shared/dist/file-import.js.map +0 -1
  63. package/node_modules/@oxygen/shared/dist/index.d.ts.map +0 -1
  64. package/node_modules/@oxygen/shared/dist/index.js.map +0 -1
  65. package/node_modules/@oxygen/shared/dist/log.d.ts.map +0 -1
  66. package/node_modules/@oxygen/shared/dist/log.js.map +0 -1
  67. package/node_modules/@oxygen/shared/dist/redaction.d.ts.map +0 -1
  68. package/node_modules/@oxygen/shared/dist/redaction.js.map +0 -1
  69. package/node_modules/@oxygen/shared/dist/telemetry.d.ts.map +0 -1
  70. package/node_modules/@oxygen/shared/dist/telemetry.js.map +0 -1
  71. package/node_modules/@oxygen/shared/dist/version.d.ts.map +0 -1
  72. package/node_modules/@oxygen/shared/dist/version.js.map +0 -1
  73. package/node_modules/@oxygen/workflows/dist/index.d.ts.map +0 -1
  74. package/node_modules/@oxygen/workflows/dist/index.js.map +0 -1
@@ -6,4 +6,3 @@ type LocalColumnRunOptions = {
6
6
  };
7
7
  export declare function runLocalCustomHttpColumn(table: string, columnKeyOrId: string, options: LocalColumnRunOptions): Promise<Record<string, unknown>>;
8
8
  export {};
9
- //# sourceMappingURL=local-custom-http-column.d.ts.map
@@ -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
@@ -54,4 +54,3 @@ export declare function addSessionOutput(input: {
54
54
  export declare function getSessionUsage(input: {
55
55
  sessionId?: string | null;
56
56
  }): Record<string, unknown>;
57
- //# sourceMappingURL=session.d.ts.map
package/dist/session.js CHANGED
@@ -191,4 +191,3 @@ function isSession(value) {
191
191
  && Array.isArray(record.statuses)
192
192
  && Array.isArray(record.outputs);
193
193
  }
194
- //# sourceMappingURL=session.js.map
@@ -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
@@ -40,4 +40,3 @@ export function isRecipeDefinition(value) {
40
40
  && typeof value === "object"
41
41
  && value.__oxygen_recipe === true;
42
42
  }
43
- //# sourceMappingURL=index.js.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(plan: PricingPlanDefinition, metadata: PricingPlanMetadata): PricingPlanDefinition;
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(plan, metadata) {
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(value: unknown, dataType: RetypeableDataType): CoercionResult;
55
- //# sourceMappingURL=column-types.d.ts.map
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(value, dataType) {
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
@@ -57,4 +57,3 @@ function normalizeCreditValue(value) {
57
57
  function roundCreditValue(value) {
58
58
  return Math.round(value * 1000) / 1000;
59
59
  }
60
- //# sourceMappingURL=credit-guidance.js.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