Primeiro Commit v0.beta1

This commit is contained in:
2026-04-29 00:27:50 -03:00
parent 93d4906fb5
commit 8662a153fe
27 changed files with 16303 additions and 0 deletions

File diff suppressed because it is too large Load Diff

256
scripts/lib/auth-shared.mjs Normal file
View File

@@ -0,0 +1,256 @@
import fs from "node:fs/promises";
import path from "node:path";
function domainMatches(hostname, cookieDomain) {
const normalized = String(cookieDomain || "").replace(/^\./, "").toLowerCase();
const host = String(hostname || "").toLowerCase();
if (!normalized || !host) return false;
if (normalized === host) return true;
if (normalized === "localhost") return host === "localhost";
return host.endsWith(`.${normalized}`);
}
export function normalizeSameSite(value) {
const normalized = String(value || "").trim().toLowerCase();
if (normalized === "strict") return "Strict";
if (normalized === "none") return "None";
return "Lax";
}
export function normalizeCookie(cookie) {
if (!cookie?.name || !cookie?.domain) return null;
return {
path: "/",
expires: -1,
httpOnly: false,
secure: false,
sameSite: "Lax",
...cookie,
domain: String(cookie.domain).replace(/^\./, "").toLowerCase(),
path: cookie.path || "/",
expires:
Number.isFinite(cookie.expires) || cookie.expires === -1
? cookie.expires
: Number(cookie.expires) || -1,
httpOnly: Boolean(cookie.httpOnly),
secure: Boolean(cookie.secure),
sameSite: normalizeSameSite(cookie.sameSite),
};
}
function findCookieIndex(cookies, candidate) {
return cookies.findIndex(
(cookie) =>
cookie.name === candidate.name &&
String(cookie.domain || "").replace(/^\./, "").toLowerCase() === candidate.domain &&
(cookie.path || "/") === candidate.path
);
}
export function cookieHeaderFor(urlString, cookies) {
const url = new URL(urlString);
const now = Math.floor(Date.now() / 1000);
return cookies
.map(normalizeCookie)
.filter((cookie) => {
if (!cookie) return false;
if (!domainMatches(url.hostname, cookie.domain)) return false;
if (!url.pathname.startsWith(cookie.path || "/")) return false;
if (cookie.secure && url.protocol !== "https:") return false;
if (cookie.expires && cookie.expires !== -1 && cookie.expires < now) {
return false;
}
return true;
})
.sort((left, right) => (right.path?.length || 0) - (left.path?.length || 0))
.map((cookie) => `${cookie.name}=${cookie.value}`)
.join("; ");
}
export function splitSetCookieHeader(headerValue) {
if (!headerValue) return [];
const parts = [];
let current = "";
let inExpires = false;
for (let index = 0; index < headerValue.length; index += 1) {
const char = headerValue[index];
const next = headerValue.slice(index);
if (!inExpires && next.toLowerCase().startsWith("expires=")) {
inExpires = true;
}
if (char === "," && !inExpires) {
const trimmed = current.trim();
if (trimmed) parts.push(trimmed);
current = "";
continue;
}
current += char;
if (inExpires && char === ";") {
inExpires = false;
}
}
const trimmed = current.trim();
if (trimmed) parts.push(trimmed);
return parts;
}
function defaultCookiePath(url) {
const pathname = url.pathname || "/";
if (!pathname.startsWith("/") || pathname === "/") return "/";
const lastSlash = pathname.lastIndexOf("/");
return lastSlash <= 0 ? "/" : pathname.slice(0, lastSlash);
}
export function mergeCookiesFromHeader(state, urlString, headerValue) {
const url = new URL(urlString);
const parts = splitSetCookieHeader(headerValue);
let changed = false;
for (const part of parts) {
const segments = part.split(";").map((segment) => segment.trim()).filter(Boolean);
const [nameValue, ...attributes] = segments;
const eqIndex = nameValue?.indexOf("=") ?? -1;
if (eqIndex <= 0) continue;
const name = nameValue.slice(0, eqIndex);
const value = nameValue.slice(eqIndex + 1);
const nextCookie = {
name,
value,
domain: url.hostname.toLowerCase(),
path: defaultCookiePath(url),
expires: -1,
httpOnly: false,
secure: url.protocol === "https:",
sameSite: "Lax",
};
for (const attribute of attributes) {
const separator = attribute.indexOf("=");
const rawKey = (separator >= 0 ? attribute.slice(0, separator) : attribute).trim();
const rawValue = separator >= 0 ? attribute.slice(separator + 1).trim() : "";
const key = rawKey.toLowerCase();
if (key === "domain") nextCookie.domain = rawValue.replace(/^\./, "").toLowerCase() || url.hostname.toLowerCase();
if (key === "path") nextCookie.path = rawValue || "/";
if (key === "secure") nextCookie.secure = true;
if (key === "httponly") nextCookie.httpOnly = true;
if (key === "samesite") nextCookie.sameSite = normalizeSameSite(rawValue);
if (key === "expires") {
const seconds = Math.floor(new Date(rawValue).getTime() / 1000);
nextCookie.expires = Number.isFinite(seconds) ? seconds : -1;
}
if (key === "max-age") {
const maxAge = Number(rawValue);
nextCookie.maxAge = maxAge;
nextCookie.expires = Number.isFinite(maxAge)
? Math.floor(Date.now() / 1000) + maxAge
: nextCookie.expires;
}
}
const normalized = normalizeCookie(nextCookie);
if (!normalized) continue;
const existingIndex = findCookieIndex(state.cookies, normalized);
if (normalized.expires === 0 || normalized.maxAge === 0) {
if (existingIndex >= 0) {
state.cookies.splice(existingIndex, 1);
changed = true;
}
continue;
}
if (existingIndex >= 0) {
const previous = JSON.stringify(normalizeCookie(state.cookies[existingIndex]));
state.cookies[existingIndex] = { ...state.cookies[existingIndex], ...normalized };
if (previous !== JSON.stringify(normalizeCookie(state.cookies[existingIndex]))) {
changed = true;
}
} else {
state.cookies.push(normalized);
changed = true;
}
}
return changed;
}
function getHeaderValue(headers, name) {
if (!headers) return null;
if (typeof headers.get === "function") {
return headers.get(name) || headers.get(name.toLowerCase()) || headers.get(name.toUpperCase()) || null;
}
return headers[name] || headers[name.toLowerCase()] || headers[name.toUpperCase()] || null;
}
export function extractClientBin(html, headers = null) {
const fromHeader = getHeaderValue(headers, "x-bitech-svrbin");
if (fromHeader) return fromHeader;
return String(html || "").match(/oracle-jet-bundles\/[^.]+\.([a-z0-9]+)\//i)?.[1] || null;
}
export function extractCsrfToken(value) {
if (!value || typeof value !== "object") return null;
for (const [key, candidate] of Object.entries(value)) {
if (typeof candidate === "string" && key.toLowerCase() === "csrftoken") {
return candidate;
}
const nested = extractCsrfToken(candidate);
if (nested) return nested;
}
return null;
}
export async function readJson(filePath, label) {
let raw;
try {
raw = await fs.readFile(filePath, "utf8");
} catch (error) {
if (error?.code === "ENOENT") {
throw new Error(
`Could not find ${label} at ${filePath}. Run scripts/refresh-auth-direct.mjs first to create the session files.`
);
}
throw error;
}
try {
return JSON.parse(raw);
} catch (error) {
throw new Error(`Could not parse ${label} at ${filePath}: ${error.message}`);
}
}
export async function persistSessionArtifacts(statePath, metaPath, state, meta, { ensureParentDir } = {}) {
if (typeof ensureParentDir === "function") {
await ensureParentDir(statePath);
await ensureParentDir(metaPath);
}
await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
await fs.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}\n`, "utf8");
}
export function replaceObjectContents(target, nextValue) {
for (const key of Object.keys(target)) {
delete target[key];
}
Object.assign(target, nextValue);
return target;
}
export function inferRuntimeHome(statePath, metaPath) {
const stateDir = statePath ? path.dirname(statePath) : null;
const metaDir = metaPath ? path.dirname(metaPath) : null;
if (stateDir && metaDir && stateDir === metaDir) {
return stateDir;
}
return stateDir || metaDir || null;
}
export function isAuthRefreshRequiredError(error) {
const message = String(error?.message || "");
return [
"Refresh auth state first",
"Request redirected",
"Session redirected to Oracle sign-in",
"Oracle DV session restore redirected to Oracle sign-in",
"Request redirected while restoring Oracle DV session",
"Oracle DV session is not valid anymore",
"Could not find auth state",
"Could not find session metadata",
"Session metadata at",
"Expected JSON",
].some((fragment) => message.includes(fragment));
}

View File

@@ -0,0 +1,30 @@
export function parseArgs(argv) {
const args = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith("--")) continue;
const key = token.slice(2);
const next = argv[index + 1];
if (!next || next.startsWith("--")) {
args[key] = "true";
continue;
}
const valueParts = [next];
let valueIndex = index + 2;
while (valueIndex < argv.length && !argv[valueIndex].startsWith("--")) {
valueParts.push(argv[valueIndex]);
valueIndex += 1;
}
args[key] = valueParts.join(" ");
index = valueIndex - 1;
}
return args;
}
export function splitCsv(value, fallback = []) {
if (!value) return fallback;
return value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}

View File

@@ -0,0 +1,340 @@
const ANNUAL_WEEK_START_MONTH_INDEX = 5;
const ANNUAL_WEEK_START_DAY = 1;
const CURRENT_PERIOD_TOKENS = new Set([
"current",
"current-week",
"this-week",
"week-current",
"semana-atual",
"week atual",
]);
const CURRENT_MONTH_TOKENS = new Set([
"current",
"current-month",
"this-month",
"month-current",
"mes-atual",
"mês-atual",
"mes atual",
"mês atual",
]);
const CURRENT_QUARTER_TOKENS = new Set([
"current",
"current-quarter",
"this-quarter",
"quarter-current",
"quarter atual",
"trimestre-atual",
"trimestre atual",
]);
const CURRENT_FISCAL_YEAR_TOKENS = new Set([
"current",
"current-fy",
"this-fy",
"fy-current",
"fy atual",
"ano-fiscal-atual",
"ano fiscal atual",
]);
function quoteLiteral(value) {
return `'${String(value).replace(/'/g, "''")}'`;
}
function lastDayOfMonth(year, monthIndex) {
return new Date(year, monthIndex + 1, 0);
}
function formatIsoDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
function addDays(date, days) {
const next = new Date(date.getTime());
next.setDate(next.getDate() + days);
return next;
}
function isLeapYear(year) {
return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
function isValidIsoCalendarDate(raw) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
if (!match) return false;
const [, yearText, monthText, dayText] = match;
const year = Number(yearText);
const month = Number(monthText);
const day = Number(dayText);
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
return false;
}
if (month < 1 || month > 12) return false;
const daysInMonth = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return day >= 1 && day <= daysInMonth[month - 1];
}
function resolveDateInput(value, fallback) {
const raw = String(value || fallback || "").trim();
if (!raw) {
throw new Error("Date input is required.");
}
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
if (!isValidIsoCalendarDate(raw)) {
throw new Error(`Invalid calendar date "${raw}". Use a real date in YYYY-MM-DD format.`);
}
return { input: raw, isoDate: raw };
}
if (raw.toLowerCase() === "today") {
return { input: raw, isoDate: formatIsoDate(new Date()) };
}
const relativeMatch = /^(?:today)?([+-]\d+)d$/i.exec(raw);
if (relativeMatch) {
const days = Number(relativeMatch[1]);
return { input: raw, isoDate: formatIsoDate(addDays(new Date(), days)) };
}
throw new Error(
`Unsupported date token "${raw}". Use YYYY-MM-DD, today, +7d, -3d, or today+7d.`
);
}
function resolveMonthInput(value) {
const raw = String(value || "").trim();
if (CURRENT_MONTH_TOKENS.has(raw.toLowerCase())) {
const today = new Date();
const first = new Date(today.getFullYear(), today.getMonth(), 1);
const last = lastDayOfMonth(today.getFullYear(), today.getMonth());
return {
input: raw,
fromDate: formatIsoDate(first),
toDate: formatIsoDate(last),
};
}
if (!/^\d{4}-\d{2}$/.test(raw)) {
throw new Error(`Unsupported month token "${raw}". Use YYYY-MM, for example 2026-04.`);
}
const [yearText, monthText] = raw.split("-");
const year = Number(yearText);
const month = Number(monthText);
if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
throw new Error(`Unsupported month token "${raw}". Use YYYY-MM, for example 2026-04.`);
}
const first = new Date(year, month - 1, 1);
const last = lastDayOfMonth(year, month - 1);
return {
input: raw,
fromDate: formatIsoDate(first),
toDate: formatIsoDate(last),
};
}
function fiscalQuarterToDateWindow(fiscalYear, quarter) {
const normalizedFiscalYear = String(fiscalYear || "").trim().toUpperCase();
const normalizedQuarter = String(quarter || "").trim().toUpperCase();
const fiscalMatch = /^FY(\d{2,4})$/.exec(normalizedFiscalYear);
const quarterMatch = /^Q([1-4])$/.exec(normalizedQuarter);
if (!fiscalMatch || !quarterMatch) {
throw new Error(`Unsupported fiscal quarter token "${fiscalYear} ${quarter}". Use FY26-Q4 or Q4 FY26.`);
}
const endYear = fiscalMatch[1].length === 2 ? 2000 + Number(fiscalMatch[1]) : Number(fiscalMatch[1]);
const quarterNumber = Number(quarterMatch[1]);
const startYear = endYear - 1;
const quarterWindows = {
1: { from: new Date(startYear, 5, 1), to: lastDayOfMonth(startYear, 7) },
2: { from: new Date(startYear, 8, 1), to: lastDayOfMonth(startYear, 10) },
3: { from: new Date(startYear, 11, 1), to: lastDayOfMonth(endYear, 1) },
4: { from: new Date(endYear, 2, 1), to: lastDayOfMonth(endYear, 4) },
};
return {
fiscalYear: normalizedFiscalYear,
quarter: `Q${quarterNumber}`,
fromDate: formatIsoDate(quarterWindows[quarterNumber].from),
toDate: formatIsoDate(quarterWindows[quarterNumber].to),
};
}
export function resolveQuarterInput(value) {
const raw = String(value || "").trim();
const normalized = raw.toLowerCase();
if (CURRENT_QUARTER_TOKENS.has(normalized)) {
const today = new Date();
const cycleYear = resolveWeekYearInput("");
const monthIndex = today.getMonth();
let quarter = "Q1";
if (monthIndex >= 5 && monthIndex <= 7) {
quarter = "Q1";
} else if (monthIndex >= 8 && monthIndex <= 10) {
quarter = "Q2";
} else if (monthIndex === 11 || monthIndex <= 1) {
quarter = "Q3";
} else {
quarter = "Q4";
}
const fiscalYear = `FY${String(cycleYear + 1).padStart(2, "0").slice(-2)}`;
return {
input: raw,
...fiscalQuarterToDateWindow(fiscalYear, quarter),
};
}
const compactMatch = /^(FY\d{2,4})[-\s]Q([1-4])$/i.exec(raw);
if (compactMatch) {
return {
input: raw,
...fiscalQuarterToDateWindow(compactMatch[1], `Q${compactMatch[2]}`),
};
}
const reversedMatch = /^Q([1-4])[-\s]?(FY\d{2,4})$/i.exec(raw);
if (reversedMatch) {
return {
input: raw,
...fiscalQuarterToDateWindow(reversedMatch[2], `Q${reversedMatch[1]}`),
};
}
throw new Error(`Unsupported quarter token "${raw}". Use FY26-Q4, Q4 FY26, or current-quarter.`);
}
export function resolveWeekYearInput(value) {
const raw = String(value || "").trim();
if (!raw || CURRENT_FISCAL_YEAR_TOKENS.has(raw.toLowerCase())) {
const today = new Date();
const cycleStart = new Date(today.getFullYear(), ANNUAL_WEEK_START_MONTH_INDEX, ANNUAL_WEEK_START_DAY);
return today >= cycleStart ? today.getFullYear() : today.getFullYear() - 1;
}
if (!/^\d{4}$/.test(raw)) {
throw new Error(`Unsupported week year token "${raw}". Use the cycle start year YYYY, for example 2025.`);
}
return Number(raw);
}
export function resolveAnnualWeekInput(value, weekYear) {
const raw = String(value || "").trim().toLowerCase();
if (CURRENT_PERIOD_TOKENS.has(raw)) {
const year = resolveWeekYearInput(weekYear);
const cycleStart = new Date(year, ANNUAL_WEEK_START_MONTH_INDEX, ANNUAL_WEEK_START_DAY);
const cycleEnd = new Date(year + 1, ANNUAL_WEEK_START_MONTH_INDEX, 0);
const today = new Date();
const boundedToday =
today < cycleStart ? cycleStart : today > cycleEnd ? cycleEnd : today;
const week = Math.floor((boundedToday - cycleStart) / (7 * 24 * 60 * 60 * 1000)) + 1;
return resolveAnnualWeekInput(`w${week}`, year);
}
const match = raw.match(/^(?:w|week\s*)?([1-9]|[1-4]\d|5[0-3])$/);
if (!match) {
throw new Error(`Unsupported week token "${value}". Use 1..53, w1..w53, or week 1..week 53.`);
}
const week = Number(match[1]);
const year = resolveWeekYearInput(weekYear);
const cycleStart = new Date(year, ANNUAL_WEEK_START_MONTH_INDEX, ANNUAL_WEEK_START_DAY);
const cycleEnd = new Date(year + 1, ANNUAL_WEEK_START_MONTH_INDEX, 0);
const weekStart = addDays(cycleStart, (week - 1) * 7);
if (weekStart > cycleEnd) {
throw new Error(`Week ${week} is outside the ${year}-${year + 1} cycle. Use 1..53.`);
}
const provisionalWeekEnd = addDays(weekStart, 6);
const weekEnd = provisionalWeekEnd > cycleEnd ? cycleEnd : provisionalWeekEnd;
return {
input: String(value || "").trim(),
week,
weekLabel: `W${week}`,
year,
fromDate: formatIsoDate(weekStart),
toDate: formatIsoDate(weekEnd),
};
}
export function resolveDateWindow(args, defaults = { from: "today", to: "+7d" }) {
const hasSingleDate = Boolean(args.date);
const hasMonth = Boolean(args.month);
const hasWeek = Boolean(args.week);
const hasQuarter = Boolean(args.quarter);
const hasExplicitWindow = Boolean(args["from-date"] || args["to-date"]);
if (
[hasSingleDate, hasMonth, hasWeek, hasQuarter].filter(Boolean).length > 1 ||
((hasSingleDate || hasMonth || hasWeek || hasQuarter) && hasExplicitWindow)
) {
throw new Error(
"Use either --date, --month, --quarter, --week, or the pair --from-date/--to-date for date-window queries."
);
}
if (hasSingleDate) {
const exact = resolveDateInput(args.date);
return {
fromDateInput: exact.input,
toDateInput: exact.input,
fromDate: exact.isoDate,
toDate: exact.isoDate,
fromDateSql: `DATE ${quoteLiteral(exact.isoDate)}`,
toDateSql: `DATE ${quoteLiteral(exact.isoDate)}`,
preset: "single-date",
};
}
if (hasMonth) {
const month = resolveMonthInput(args.month);
return {
fromDateInput: month.input,
toDateInput: month.input,
fromDate: month.fromDate,
toDate: month.toDate,
fromDateSql: `DATE ${quoteLiteral(month.fromDate)}`,
toDateSql: `DATE ${quoteLiteral(month.toDate)}`,
preset: "calendar-month",
};
}
if (hasQuarter) {
const quarter = resolveQuarterInput(args.quarter);
return {
fromDateInput: quarter.input,
toDateInput: quarter.input,
fromDate: quarter.fromDate,
toDate: quarter.toDate,
fromDateSql: `DATE ${quoteLiteral(quarter.fromDate)}`,
toDateSql: `DATE ${quoteLiteral(quarter.toDate)}`,
preset: "fiscal-quarter",
fiscalYear: quarter.fiscalYear,
fiscalQuarter: quarter.quarter,
};
}
if (hasWeek) {
const week = resolveAnnualWeekInput(args.week, args["week-year"]);
return {
fromDateInput: week.input,
toDateInput: week.input,
fromDate: week.fromDate,
toDate: week.toDate,
fromDateSql: `DATE ${quoteLiteral(week.fromDate)}`,
toDateSql: `DATE ${quoteLiteral(week.toDate)}`,
preset: "annual-week",
week: week.week,
weekLabel: week.weekLabel,
weekYear: week.year,
};
}
const from = resolveDateInput(args["from-date"], defaults.from);
const to = resolveDateInput(args["to-date"], defaults.to);
if (from.isoDate > to.isoDate) {
throw new Error(`Invalid date window: from-date ${from.isoDate} is after to-date ${to.isoDate}.`);
}
return {
fromDateInput: from.input,
toDateInput: to.input,
fromDate: from.isoDate,
toDate: to.isoDate,
fromDateSql: `DATE ${quoteLiteral(from.isoDate)}`,
toDateSql: `DATE ${quoteLiteral(to.isoDate)}`,
preset: "date-window",
};
}
export function hasAnyDateWindowArgs(args) {
return Boolean(
args.date ||
args.month ||
args.quarter ||
args.week ||
args["from-date"] ||
args["to-date"]
);
}

View File

@@ -0,0 +1,200 @@
import fs from "node:fs/promises";
import {
ensureParentDir,
resolveOutputFilePath,
} from "./runtime-paths.mjs";
const NULL_SENTINEL = "__CODEX_NULL__";
const MONETARY_FIELD_KEYS = new Set(["bookingValue", "opportunityValue", "workloadAmount", "pipelineAmount"]);
const THOUSANDS_DISPLAY_KEYS = new Set([
...MONETARY_FIELD_KEYS,
"totalBookingValue",
"totalOpportunityValue",
"totalWorkloadAmount",
"totalPipelineAmount",
]);
const THOUSANDS_DISPLAY_SOURCE_FIELDS = new Set(MONETARY_FIELD_KEYS);
const ORACLE_MONTHS = {
JAN: "01",
FEB: "02",
MAR: "03",
APR: "04",
MAY: "05",
JUN: "06",
JUL: "07",
AUG: "08",
SEP: "09",
OCT: "10",
NOV: "11",
DEC: "12",
};
export function buildWarnings({ requestedLimit, recordsCount, customSql, customSqlColumnsResolved = false }) {
const warnings = [];
if (requestedLimit > 0 && recordsCount >= requestedLimit) {
warnings.push(
`The query returned ${recordsCount} rows, which matches the requested limit (${requestedLimit}). Results may be truncated; rerun with a higher --limit if you need a complete extract.`
);
}
if (customSql && !customSqlColumnsResolved) {
warnings.push("Custom SQL records are returned as raw row arrays because the Oracle DV response does not guarantee stable field aliases.");
}
return warnings;
}
export function parseNumericValue(value) {
if (typeof value === "number") return Number.isFinite(value) ? value : 0;
const normalized = String(value ?? "")
.replace(/,/g, "")
.trim();
if (!normalized) return 0;
const parsed = Number(normalized);
return Number.isFinite(parsed) ? parsed : 0;
}
function isLikelyMonetaryFieldName(fieldName) {
return /booking|workload|pipeline|opportunityvalue/i.test(String(fieldName || ""));
}
function formatThousandsDisplayValue(value) {
const thousands = Math.trunc(parseNumericValue(value) / 1000);
const grouped = String(thousands).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
return `${grouped}k`;
}
function formatDisplaySection(node, displayKeys = THOUSANDS_DISPLAY_KEYS) {
if (Array.isArray(node)) {
return node.map((item) => formatDisplaySection(item, displayKeys));
}
if (!node || typeof node !== "object") {
return node;
}
return Object.fromEntries(
Object.entries(node).map(([key, value]) => {
if ((displayKeys.has(key) || isLikelyMonetaryFieldName(key)) && value !== null && value !== undefined && value !== "") {
return [key, formatThousandsDisplayValue(value)];
}
return [key, formatDisplaySection(value, displayKeys)];
})
);
}
function collectThousandsDisplayKeys(payload) {
const displayKeys = new Set(THOUSANDS_DISPLAY_KEYS);
for (const columnName of payload?.columns || []) {
if (MONETARY_FIELD_KEYS.has(columnName) || isLikelyMonetaryFieldName(columnName)) {
displayKeys.add(columnName);
}
}
const querySpec = payload?.request?.query;
if (querySpec && typeof querySpec === "object") {
for (const item of [...(querySpec.select || []), ...(querySpec.measure || [])]) {
if (!item || typeof item !== "object") continue;
if (!THOUSANDS_DISPLAY_SOURCE_FIELDS.has(item.fieldName)) continue;
if (item.outputName) displayKeys.add(item.outputName);
}
}
for (const inferredColumn of payload?.request?.inferredColumns || []) {
if (MONETARY_FIELD_KEYS.has(inferredColumn) || isLikelyMonetaryFieldName(inferredColumn)) {
displayKeys.add(inferredColumn);
}
}
return displayKeys;
}
function rowsPulledFromApi(result) {
return Array.isArray(result?.aResults) ? result.aResults.length : 0;
}
function buildApiQueryMetric({
label,
result = null,
rowsReturned = null,
source = null,
countedInTotal = true,
} = {}) {
const resolvedRowsReturned = Number.isFinite(rowsReturned) ? rowsReturned : rowsPulledFromApi(result);
return {
label: label || "query",
rowsPulledFromApi: resolvedRowsReturned,
source: source || null,
countedInTotal: countedInTotal !== false,
};
}
export function buildApiMetrics(entries = []) {
const queries = entries
.filter(Boolean)
.map((entry) =>
buildApiQueryMetric({
label: entry.label,
result: entry.result,
rowsReturned: entry.rowsReturned,
source: entry.source,
countedInTotal: entry.countedInTotal,
})
);
const totalRowsPulledFromApi = queries.reduce(
(sum, query) => sum + (query.countedInTotal ? query.rowsPulledFromApi : 0),
0
);
return {
totalRowsPulledFromApi,
queries,
explanation: `Raw rows pulled from API: ${totalRowsPulledFromApi}.`,
};
}
export function preparePayloadForOutput(payload) {
const displayKeys = collectThousandsDisplayKeys(payload);
return {
...payload,
apiMetrics: payload?.apiMetrics || buildApiMetrics(),
...(Object.prototype.hasOwnProperty.call(payload, "records")
? { records: formatDisplaySection(payload.records, displayKeys) }
: {}),
...(payload.summary && typeof payload.summary === "object"
? { summary: formatDisplaySection(payload.summary, displayKeys) }
: {}),
};
}
function normalizeOracleDateString(value) {
if (typeof value !== "string") return value;
const match = /^(\d{2})-([A-Z]{3})-(\d{2}|\d{4})$/.exec(value.trim().toUpperCase());
if (!match) return value;
const [, day, monthToken, yearToken] = match;
const month = ORACLE_MONTHS[monthToken];
if (!month) return value;
const year =
yearToken.length === 4
? yearToken
: Number(yearToken) >= 70
? `19${yearToken}`
: `20${yearToken}`;
return `${year}-${month}-${day}`;
}
export function normalizeCell(value) {
if (value === NULL_SENTINEL) return null;
return normalizeOracleDateString(value ?? null);
}
export function mapRows(fields, rows) {
return rows.map((row) =>
Object.fromEntries(fields.map((field, index) => [field, normalizeCell(row[index])]))
);
}
export function emitJson(args, payload, runtimeHome) {
const json = JSON.stringify(preparePayloadForOutput(payload), null, 2);
const outputFilePath = resolveOutputFilePath(args["output-file"], runtimeHome);
if (outputFilePath) {
return ensureParentDir(outputFilePath)
.then(() => fs.writeFile(outputFilePath, `${json}\n`, "utf8"))
.then(() => json);
}
return Promise.resolve(json);
}

View File

@@ -0,0 +1,351 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
export const SKILL_SLUG = "knowledge-one-view-cli";
export const LEGACY_SKILL_SLUG = "oracle-sales-intelligence-direct";
export const DISPLAY_RUNTIME_HOME = "<workspace>/knowledge-one-view-cli/runtime";
export const DISPLAY_STATE_PATH = "<workspace>/knowledge-one-view-cli/runtime/auth-state.json";
export const DISPLAY_META_PATH = "<workspace>/knowledge-one-view-cli/runtime/session-meta.json";
export const DISPLAY_PROFILE_DIR = "<workspace>/knowledge-one-view-cli/runtime/chrome-profile";
export const DISPLAY_CHROME_PATH = "auto-detected by platform";
export const LEGACY_DISPLAY_HOME = `~/.codex/${LEGACY_SKILL_SLUG}`;
const helperDir = path.dirname(fileURLToPath(import.meta.url));
export const SKILL_HOME = path.resolve(helperDir, "..", "..");
export const CODEX_HOME = path.join(os.homedir(), ".codex");
export const LEGACY_HOME = path.join(os.homedir(), ".codex", LEGACY_SKILL_SLUG);
export const LEGACY_STATE_PATH = path.join(LEGACY_HOME, "auth-state.json");
export const LEGACY_META_PATH = path.join(LEGACY_HOME, "session-meta.json");
export const LEGACY_PROFILE_DIR = path.join(LEGACY_HOME, "chrome-profile");
function pathExists(targetPath) {
return Boolean(targetPath) && fs.existsSync(targetPath);
}
function normalizeDir(targetPath) {
return path.resolve(targetPath);
}
function normalizeWorkspaceRuntimeHomeCandidate(targetPath) {
const resolved = normalizeDir(targetPath);
const runtimeName = path.basename(resolved).toLowerCase();
if (runtimeName === "runtime") {
return resolved;
}
if (!runtimeName.startsWith("runtime-")) {
return resolved;
}
const parentDir = path.dirname(resolved);
if (path.basename(parentDir) !== SKILL_SLUG) {
return resolved;
}
// Keep one canonical runtime path in the workspace skill folder.
return path.join(parentDir, "runtime");
}
function looksLikeTemporaryFileName(fileName) {
const lower = String(fileName || "").toLowerCase();
return (
lower.startsWith("tmp-") ||
lower.startsWith("temp-") ||
lower.startsWith("scratch-") ||
lower.startsWith("intermediate-") ||
lower.startsWith("render-output-") ||
lower.includes(".tmp.") ||
lower.endsWith(".tmp") ||
lower.endsWith(".temp")
);
}
function isSameOrInside(targetPath, parentPath) {
const relative = path.relative(parentPath, targetPath);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
function looksLikeInstalledSkillHome(targetPath) {
return ["SKILL.md", "scripts", "docs", "references", "tests"].some((name) =>
pathExists(path.join(targetPath, name))
);
}
function looksLikeWorkspaceSkillHome(targetPath) {
if (path.basename(targetPath) !== SKILL_SLUG) return false;
if (looksLikeInstalledSkillHome(targetPath)) return false;
return true;
}
function candidateChromePaths() {
if (process.platform === "win32") {
return [
process.env.ORACLE_SI_CHROME_PATH,
process.env.LOCALAPPDATA
? path.join(process.env.LOCALAPPDATA, "Google", "Chrome", "Application", "chrome.exe")
: null,
process.env["PROGRAMFILES"]
? path.join(process.env["PROGRAMFILES"], "Google", "Chrome", "Application", "chrome.exe")
: null,
process.env["PROGRAMFILES(X86)"]
? path.join(process.env["PROGRAMFILES(X86)"], "Google", "Chrome", "Application", "chrome.exe")
: null,
].filter(Boolean);
}
if (process.platform === "darwin") {
return [
process.env.ORACLE_SI_CHROME_PATH,
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
path.join(os.homedir(), "Applications", "Google Chrome.app", "Contents", "MacOS", "Google Chrome"),
].filter(Boolean);
}
return [
process.env.ORACLE_SI_CHROME_PATH,
"/usr/bin/google-chrome",
"/usr/bin/chromium",
"/usr/bin/chromium-browser",
].filter(Boolean);
}
export function resolveWorkspaceRoot(candidatePath = process.cwd()) {
const cwd = normalizeDir(candidatePath);
const codexHome = normalizeDir(CODEX_HOME);
if (isSameOrInside(cwd, codexHome)) {
throw new Error(
`Cannot infer a workspace root from ${cwd}. Run this command from the target workspace or pass --runtime-home (or ORACLE_SI_RUNTIME_HOME) explicitly. Refusing to create runtime files inside ${codexHome}.`
);
}
if (looksLikeWorkspaceSkillHome(cwd)) {
return path.dirname(cwd);
}
return cwd;
}
export function buildRuntimeHomeFromWorkspace(workspaceRoot) {
return path.join(normalizeDir(workspaceRoot), SKILL_SLUG, "runtime");
}
export function buildLegacyRuntimeHomeFromWorkspace(workspaceRoot) {
return path.join(normalizeDir(workspaceRoot), LEGACY_SKILL_SLUG, "runtime");
}
export function resolveWorkspaceSkillHome(runtimeHome) {
return path.dirname(resolveRuntimeHome(runtimeHome));
}
export function resolveLegacyWorkspaceSkillHome(runtimeHome) {
return path.dirname(resolveLegacyRuntimeHomeCandidate(runtimeHome));
}
export function resolveRuntimeHome(explicitPath) {
if (explicitPath) return normalizeWorkspaceRuntimeHomeCandidate(explicitPath);
if (process.env.ORACLE_SI_RUNTIME_HOME) {
return normalizeWorkspaceRuntimeHomeCandidate(process.env.ORACLE_SI_RUNTIME_HOME);
}
return buildRuntimeHomeFromWorkspace(resolveWorkspaceRoot());
}
export function resolveLegacyRuntimeHomeCandidate(runtimeHome) {
const resolvedRuntimeHome = resolveRuntimeHome(runtimeHome);
const workspaceRoot = path.dirname(path.dirname(resolvedRuntimeHome));
return buildLegacyRuntimeHomeFromWorkspace(workspaceRoot);
}
export function resolveOutputDir(runtimeHome) {
return path.join(resolveWorkspaceSkillHome(runtimeHome), "output");
}
export function resolveOutputFilePath(explicitPath, runtimeHome) {
if (!explicitPath) return null;
if (path.isAbsolute(explicitPath)) return path.resolve(explicitPath);
const workspaceSkillHome = resolveWorkspaceSkillHome(runtimeHome);
if (path.dirname(explicitPath) === ".") {
// Keep user-facing/final outputs in output/, but route temporary/intermediate
// file names directly into tmp/ to avoid polluting output/.
const fileName = path.basename(explicitPath);
if (looksLikeTemporaryFileName(fileName)) {
return path.join(resolveTmpDir(runtimeHome), fileName);
}
return path.join(resolveOutputDir(runtimeHome), explicitPath);
}
return path.join(workspaceSkillHome, explicitPath);
}
export function resolveTmpDir(runtimeHome) {
return path.join(resolveWorkspaceSkillHome(runtimeHome), "tmp");
}
export function resolveTestRunsDir(runtimeHome) {
return path.join(resolveOutputDir(runtimeHome), "test-runs");
}
export function resolveTmpTestInputsDir(runtimeHome) {
return resolveTmpDir(runtimeHome);
}
export function resolveTmpRenderedSqlDir(runtimeHome) {
return resolveTmpDir(runtimeHome);
}
export function resolveTmpScratchDir(runtimeHome) {
return resolveTmpDir(runtimeHome);
}
export function resolveStateWritePath(options = {}) {
if (options.explicitPath) return path.resolve(options.explicitPath);
if (process.env.ORACLE_SI_STATE_PATH) return path.resolve(process.env.ORACLE_SI_STATE_PATH);
return path.join(resolveRuntimeHome(options.runtimeHome), "auth-state.json");
}
export function resolveMetaWritePath(options = {}) {
if (options.explicitPath) return path.resolve(options.explicitPath);
if (process.env.ORACLE_SI_META_PATH) return path.resolve(process.env.ORACLE_SI_META_PATH);
return path.join(resolveRuntimeHome(options.runtimeHome), "session-meta.json");
}
export function resolveProfileDir(options = {}) {
if (options.explicitPath) return path.resolve(options.explicitPath);
if (process.env.ORACLE_SI_PROFILE_DIR) return path.resolve(process.env.ORACLE_SI_PROFILE_DIR);
return path.join(resolveRuntimeHome(options.runtimeHome), "chrome-profile");
}
export function resolveStateReadPath(options = {}) {
const preferred = resolveStateWritePath(options);
if (pathExists(preferred)) return preferred;
const legacyWorkspacePath = path.join(
resolveLegacyRuntimeHomeCandidate(options.runtimeHome),
"auth-state.json"
);
if (pathExists(legacyWorkspacePath)) return legacyWorkspacePath;
return pathExists(LEGACY_STATE_PATH) ? LEGACY_STATE_PATH : preferred;
}
export function resolveMetaReadPath(options = {}) {
const preferred = resolveMetaWritePath(options);
if (pathExists(preferred)) return preferred;
const legacyWorkspacePath = path.join(
resolveLegacyRuntimeHomeCandidate(options.runtimeHome),
"session-meta.json"
);
if (pathExists(legacyWorkspacePath)) return legacyWorkspacePath;
return pathExists(LEGACY_META_PATH) ? LEGACY_META_PATH : preferred;
}
export function resolveChromePath(explicitPath) {
if (explicitPath) return path.resolve(explicitPath);
if (process.env.ORACLE_SI_CHROME_PATH) return path.resolve(process.env.ORACLE_SI_CHROME_PATH);
for (const candidate of candidateChromePaths()) {
if (pathExists(candidate)) {
return candidate;
}
}
return candidateChromePaths()[0] || "google-chrome";
}
export async function ensureDir(dirPath) {
await fs.promises.mkdir(dirPath, { recursive: true });
}
export async function ensureParentDir(filePath) {
await ensureDir(path.dirname(filePath));
}
async function migrateLegacyTmpSubdirs(tmpDir) {
const legacySubdirs = ["test-inputs", "rendered-sql", "scratch"];
const moveFileToTmpRoot = async (sourcePath) => {
const ext = path.extname(sourcePath);
const base = path.basename(sourcePath, ext);
let candidateName = path.basename(sourcePath);
let targetPath = path.join(tmpDir, candidateName);
let suffix = 1;
while (pathExists(targetPath)) {
candidateName = `${base}-migrated-${suffix}${ext}`;
targetPath = path.join(tmpDir, candidateName);
suffix += 1;
}
await fs.promises.rename(sourcePath, targetPath);
};
const walkAndMoveFiles = async (currentDir) => {
const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const absolutePath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
await walkAndMoveFiles(absolutePath);
continue;
}
await moveFileToTmpRoot(absolutePath);
}
};
for (const subdirName of legacySubdirs) {
const legacyDir = path.join(tmpDir, subdirName);
if (!pathExists(legacyDir)) continue;
await walkAndMoveFiles(legacyDir);
await fs.promises.rm(legacyDir, { recursive: true, force: true });
}
}
async function migrateTemporaryOutputFiles(outputDir, tmpDir) {
if (!pathExists(outputDir)) return;
const entries = await fs.promises.readdir(outputDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isFile()) continue;
if (!looksLikeTemporaryFileName(entry.name)) continue;
const sourcePath = path.join(outputDir, entry.name);
let candidateName = entry.name;
let targetPath = path.join(tmpDir, candidateName);
let suffix = 1;
const ext = path.extname(entry.name);
const base = path.basename(entry.name, ext);
while (pathExists(targetPath)) {
candidateName = `${base}-migrated-${suffix}${ext}`;
targetPath = path.join(tmpDir, candidateName);
suffix += 1;
}
await fs.promises.rename(sourcePath, targetPath);
}
}
export async function ensureRuntimeTree(runtimeHome) {
const resolvedRuntimeHome = resolveRuntimeHome(runtimeHome);
const workspaceSkillHome = resolveWorkspaceSkillHome(resolvedRuntimeHome);
const outputDir = resolveOutputDir(resolvedRuntimeHome);
const testRunsDir = resolveTestRunsDir(resolvedRuntimeHome);
const tmpDir = resolveTmpDir(resolvedRuntimeHome);
const tmpTestInputsDir = resolveTmpTestInputsDir(resolvedRuntimeHome);
const tmpRenderedSqlDir = resolveTmpRenderedSqlDir(resolvedRuntimeHome);
const tmpScratchDir = resolveTmpScratchDir(resolvedRuntimeHome);
const profileDir = resolveProfileDir({ runtimeHome: resolvedRuntimeHome });
await ensureDir(workspaceSkillHome);
await ensureDir(resolvedRuntimeHome);
await ensureDir(outputDir);
await ensureDir(testRunsDir);
await ensureDir(tmpDir);
await migrateLegacyTmpSubdirs(tmpDir);
await migrateTemporaryOutputFiles(outputDir, tmpDir);
await ensureDir(tmpTestInputsDir);
await ensureDir(tmpRenderedSqlDir);
await ensureDir(tmpScratchDir);
await ensureDir(profileDir);
return {
workspaceSkillHome,
runtimeHome: resolvedRuntimeHome,
outputDir,
testRunsDir,
tmpDir,
tmpTestInputsDir,
tmpRenderedSqlDir,
tmpScratchDir,
profileDir,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { fileURLToPath, pathToFileURL } from "node:url";
import {
DISPLAY_CHROME_PATH,
DISPLAY_META_PATH,
DISPLAY_RUNTIME_HOME,
DISPLAY_PROFILE_DIR,
DISPLAY_STATE_PATH,
ensureDir,
ensureParentDir,
ensureRuntimeTree,
resolveChromePath,
resolveMetaWritePath,
resolveProfileDir,
resolveRuntimeHome,
resolveStateWritePath,
} from "./lib/runtime-paths.mjs";
import { parseArgs } from "./lib/cli-shared.mjs";
import { extractClientBin } from "./lib/auth-shared.mjs";
const DEFAULT_URL =
"https://salesintelligence-dv.oracle.com/ui/dv/ui/project.jsp?pageid=visualAnalyzer&reportmode=presentation&reportpath=%2F%40Catalog%2Fshared%2FTeam%20Sharing%20Area%2FLAD%2FLAD%20Sales%20Operations%2FLAD%20Production%2FLAD%20Knowledge%20One%20View";
const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
const skillHome = path.resolve(scriptsDir, "..");
const skillsHome = path.resolve(skillHome, "..");
function usage() {
return `Refresh Oracle Sales Intelligence auth by opening Chrome.
This is an optional development and troubleshooting helper. Daily use should prefer scripts/refresh-auth-direct.mjs.
Usage:
node scripts/refresh-auth-state.mjs [options]
Options:
--runtime-home <dir> Override workspace runtime root (default: ${DISPLAY_RUNTIME_HOME})
--state-path <file> Override auth-state.json output path (default: ${DISPLAY_STATE_PATH})
--meta-path <file> Override session-meta.json output path (default: ${DISPLAY_META_PATH})
--profile-dir <dir> Override Chrome profile directory (default: ${DISPLAY_PROFILE_DIR})
--chrome-path <file> Override Chrome executable path (default: ${DISPLAY_CHROME_PATH})
--url <url> Target Oracle DV workbook URL
--timeout-ms <ms> Wait time for authenticated Oracle DV page (default: 600000)
--help Show this help
`;
}
async function pathExists(targetPath) {
try {
await fs.access(targetPath);
return true;
} catch {
return false;
}
}
function uniquePaths(values) {
const seen = new Set();
const result = [];
for (const value of values.filter(Boolean)) {
const resolved = path.resolve(value);
if (seen.has(resolved)) continue;
seen.add(resolved);
result.push(resolved);
}
return result;
}
function bundledRuntimePackageCandidates(packageName) {
const execDir = path.dirname(process.execPath || "");
const envNodeModules = [
process.env.CODEX_NODE_MODULES,
process.env.CODEX_BUNDLED_NODE_MODULES,
process.env.NODE_PATH,
].filter(Boolean);
const packageJsonCandidates = [];
for (const nodeModulesPath of envNodeModules) {
for (const basePath of String(nodeModulesPath).split(path.delimiter)) {
if (!basePath) continue;
packageJsonCandidates.push(path.join(basePath, packageName, "package.json"));
}
}
if (process.env.CODEX_PRIMARY_RUNTIME_HOME) {
packageJsonCandidates.push(
path.join(
process.env.CODEX_PRIMARY_RUNTIME_HOME,
"dependencies",
"node",
"node_modules",
packageName,
"package.json",
),
);
}
packageJsonCandidates.push(
path.join(execDir, "node_modules", packageName, "package.json"),
path.join(execDir, "..", "node_modules", packageName, "package.json"),
path.join(execDir, "..", "..", "node_modules", packageName, "package.json"),
path.join(
os.homedir(),
".cache",
"codex-runtimes",
"codex-primary-runtime",
"dependencies",
"node",
"node_modules",
packageName,
"package.json",
),
);
return uniquePaths(packageJsonCandidates);
}
async function loadPlaywright() {
const packageNames = ["playwright", "playwright-core"];
for (const packageName of packageNames) {
try {
return {
runtime: await import(packageName),
sourceLabel: `local-module:${packageName}`,
};
} catch {}
}
const sharedSkillPackageJson = path.join(skillsHome, "playwright", "package.json");
if (await pathExists(sharedSkillPackageJson)) {
const requireFromSharedSkill = createRequire(sharedSkillPackageJson);
for (const packageName of packageNames) {
try {
return {
runtime: requireFromSharedSkill(packageName),
sourceLabel: `shared-skill:${packageName}`,
};
} catch {}
}
}
for (const packageName of packageNames) {
for (const packageJsonPath of bundledRuntimePackageCandidates(packageName)) {
if (!(await pathExists(packageJsonPath))) continue;
try {
const requireFromBundledRuntime = createRequire(packageJsonPath);
return {
runtime: requireFromBundledRuntime(packageName),
sourceLabel: `bundled-runtime:${packageJsonPath}`,
};
} catch {}
}
}
throw new Error(
[
"Could not load Playwright for the browser auth helper.",
"Tried local module resolution, ~/.codex/skills/playwright, and bundled Codex runtime locations.",
"Install a shared Playwright runtime under ~/.codex/skills/playwright or run this helper from a Codex environment with bundled node_modules.",
].join(" "),
);
}
async function ensureChromeExecutable(chromePath) {
try {
await fs.access(chromePath);
} catch {
throw new Error(
`Could not find a Chrome executable at ${chromePath}. Pass --chrome-path or set ORACLE_SI_CHROME_PATH to a valid Chrome installation.`
);
}
}
export async function main(argv = process.argv.slice(2)) {
const args = parseArgs(argv);
if (args.help === "true") {
console.log(usage());
return;
}
const runtimeHome = resolveRuntimeHome(args["runtime-home"]);
await ensureRuntimeTree(runtimeHome);
const statePath = resolveStateWritePath({ explicitPath: args["state-path"], runtimeHome });
const metaPath = resolveMetaWritePath({ explicitPath: args["meta-path"], runtimeHome });
const profileDir = resolveProfileDir({ explicitPath: args["profile-dir"], runtimeHome });
const chromePath = resolveChromePath(args["chrome-path"]);
const targetUrl = args.url || process.env.ORACLE_SI_TARGET_URL || DEFAULT_URL;
const timeoutMs = Number(args["timeout-ms"] || 10 * 60 * 1000);
await ensureChromeExecutable(chromePath);
const { runtime: playwrightRuntime, sourceLabel } = await loadPlaywright();
const { chromium } = playwrightRuntime;
await ensureParentDir(statePath);
await ensureParentDir(metaPath);
await ensureDir(profileDir);
console.log(`Opening Chrome profile at ${profileDir}`);
console.log(`Using browser automation runtime: ${sourceLabel}`);
console.log("Complete Oracle login in the opened window if prompted.");
const context = await chromium.launchPersistentContext(profileDir, {
executablePath: chromePath,
headless: false,
ignoreHTTPSErrors: true,
viewport: { width: 1600, height: 900 },
});
let headerMeta = null;
const requestListener = (request) => {
if (headerMeta) return;
if (!request.url().includes("/ui/dv/ui/api/")) return;
const headers = request.headers();
const csrfToken = headers["x-csrf-token"] || null;
const clientBin = headers["x-bitech-clientbin"] || null;
if (!csrfToken || !clientBin) return;
headerMeta = {
xCsrfToken: csrfToken,
xBitechClientbin: clientBin,
referer: headers.referer || targetUrl,
targetUrl,
capturedAt: new Date().toISOString(),
};
};
context.on("request", requestListener);
try {
const page = context.pages()[0] ?? (await context.newPage());
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 120000 });
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
await page.waitForTimeout(2000);
const url = page.url();
const title = await page.title().catch(() => "");
console.log(`Waiting for Oracle Analytics session: ${url}`);
const authenticated =
url.startsWith("https://salesintelligence-dv.oracle.com/") &&
!url.includes("signon.oracle.com") &&
!url.includes("signon-int.oracle.com") &&
/Oracle Analytics|LAD Knowledge One View/i.test(title || url);
if (!authenticated) continue;
const html = await page.content();
const fallbackClientBin = extractClientBin(html);
if (!headerMeta) {
await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {});
await page.waitForTimeout(4000);
}
if (!headerMeta?.xBitechClientbin && fallbackClientBin) {
headerMeta = {
...(headerMeta || {}),
xCsrfToken: headerMeta?.xCsrfToken || null,
xBitechClientbin: fallbackClientBin,
referer: headerMeta?.referer || targetUrl,
targetUrl,
capturedAt: new Date().toISOString(),
};
}
if (!headerMeta?.xCsrfToken || !headerMeta?.xBitechClientbin) {
continue;
}
const state = await context.storageState();
await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
await fs.writeFile(metaPath, `${JSON.stringify(headerMeta, null, 2)}\n`, "utf8");
console.log(`Saved auth state to ${statePath}`);
console.log(`Saved session metadata to ${metaPath}`);
return;
}
throw new Error(`Timed out waiting for an authenticated Oracle DV page after ${timeoutMs}ms.`);
} finally {
context.off("request", requestListener);
await context.close().catch(() => {});
}
}
const entryUrl = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
if (import.meta.url === entryUrl) {
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import { execFileSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const fetchScriptPath = path.join(scriptDir, "fetch-sales-intelligence.mjs");
function printUsage() {
console.log(`Usage:
node "${path.resolve(process.argv[1] || "run-sales-intelligence-sql.mjs")}" --sql-file <file> [fetch args...]
node "${path.resolve(process.argv[1] || "run-sales-intelligence-sql.mjs")}" --stdin [fetch args...]
Examples:
node "${path.resolve(process.argv[1] || "run-sales-intelligence-sql.mjs")}" --sql-file query.sql --limit 200
Get-Content query.sql | node "${path.resolve(process.argv[1] || "run-sales-intelligence-sql.mjs")}" --stdin --render-sql
Notes:
- This helper preserves complex logical SQL that is awkward to pass through PowerShell quoting.
- All flags other than --sql-file / --stdin / --help are passed through to fetch-sales-intelligence.mjs.
`);
}
async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
return Buffer.concat(chunks).toString("utf8");
}
const args = process.argv.slice(2);
let sqlFilePath = null;
let useStdin = false;
const passthroughArgs = [];
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (arg === "--help") {
printUsage();
process.exit(0);
}
if (arg === "--sql-file") {
sqlFilePath = args[index + 1];
index += 1;
if (!sqlFilePath) {
console.error("Missing value for --sql-file");
process.exit(1);
}
continue;
}
if (arg === "--stdin") {
useStdin = true;
continue;
}
passthroughArgs.push(arg);
}
if (sqlFilePath && useStdin) {
console.error("Use either --sql-file or --stdin, not both.");
process.exit(1);
}
if (!sqlFilePath && !useStdin) {
console.error("One of --sql-file or --stdin is required.");
printUsage();
process.exit(1);
}
const sql = useStdin ? await readStdin() : await fs.readFile(path.resolve(sqlFilePath), "utf8");
if (!sql.trim()) {
console.error("SQL input is empty.");
process.exit(1);
}
try {
const stdout = execFileSync(process.execPath, [fetchScriptPath, "--sql", sql, ...passthroughArgs], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
process.stdout.write(stdout);
} catch (error) {
if (typeof error.stdout === "string" && error.stdout.length > 0) {
process.stdout.write(error.stdout);
}
if (typeof error.stderr === "string" && error.stderr.length > 0) {
process.stderr.write(error.stderr);
}
process.exit(typeof error.status === "number" ? error.status : 1);
}