352 lines
12 KiB
JavaScript
352 lines
12 KiB
JavaScript
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,
|
|
};
|
|
}
|