| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.ensureAndroidPrerequisites = ensureAndroidPrerequisites; | ||
| const node_fs_1 = __importDefault(require("node:fs")); | ||
| const node_os_1 = __importDefault(require("node:os")); | ||
| const node_path_1 = __importDefault(require("node:path")); | ||
| const node_child_process_1 = require("node:child_process"); | ||
| const paths_1 = require("../utils/paths"); | ||
| function run(cmd, args, options = {}) { | ||
| const result = (0, node_child_process_1.spawnSync)(cmd, args, { | ||
| encoding: "utf-8", | ||
| env: options.env, | ||
| input: options.input, | ||
| stdio: options.inherit ? "inherit" : ["pipe", "pipe", "pipe"], | ||
| }); | ||
| const status = result.status ?? 1; | ||
| const stdout = typeof result.stdout === "string" ? result.stdout : ""; | ||
| const stderr = typeof result.stderr === "string" ? result.stderr : ""; | ||
| return { | ||
| ok: status === 0 && !result.error, | ||
| status, | ||
| stdout, | ||
| stderr, | ||
| error: result.error ? String(result.error.message || result.error) : null, | ||
| }; | ||
| } | ||
| function canExecute(filePath) { | ||
| try { | ||
| node_fs_1.default.accessSync(filePath, node_fs_1.default.constants.X_OK); | ||
| return true; | ||
| } | ||
| catch { | ||
| return false; | ||
| } | ||
| } | ||
| function firstExecutable(candidates) { | ||
| for (const candidate of candidates) { | ||
| if (!candidate) { | ||
| continue; | ||
| } | ||
| const resolved = node_path_1.default.resolve(candidate); | ||
| if (node_fs_1.default.existsSync(resolved) && canExecute(resolved)) { | ||
| return resolved; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function findInPath(binName) { | ||
| const entries = (process.env.PATH ?? "") | ||
| .split(node_path_1.default.delimiter) | ||
| .map((v) => v.trim()) | ||
| .filter(Boolean); | ||
| for (const entry of entries) { | ||
| const candidate = node_path_1.default.join(entry, binName); | ||
| if (node_fs_1.default.existsSync(candidate) && canExecute(candidate)) { | ||
| return candidate; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function collectSdkRoot(config) { | ||
| const configured = config.emulator.androidSdkRoot.trim(); | ||
| if (configured) { | ||
| return { sdkRoot: node_path_1.default.resolve(configured), configUpdated: false }; | ||
| } | ||
| const envRoot = process.env.ANDROID_SDK_ROOT?.trim() || process.env.ANDROID_HOME?.trim() || ""; | ||
| const sdkRoot = envRoot ? node_path_1.default.resolve(envRoot) : node_path_1.default.join(node_os_1.default.homedir(), "Library", "Android", "sdk"); | ||
| config.emulator.androidSdkRoot = sdkRoot; | ||
| return { sdkRoot, configUpdated: true }; | ||
| } | ||
| function detectTools(sdkRoot) { | ||
| const fallbackSdk = node_path_1.default.join(node_os_1.default.homedir(), "Library", "Android", "sdk"); | ||
| const sdkRoots = Array.from(new Set([sdkRoot, fallbackSdk])); | ||
| const adbCandidates = sdkRoots | ||
| .map((root) => node_path_1.default.join(root, "platform-tools", "adb")) | ||
| .concat(["/opt/homebrew/bin/adb", "/usr/local/bin/adb"]); | ||
| const emulatorCandidates = sdkRoots | ||
| .map((root) => node_path_1.default.join(root, "emulator", "emulator")) | ||
| .concat([ | ||
| "/opt/homebrew/share/android-commandlinetools/emulator/emulator", | ||
| "/usr/local/share/android-commandlinetools/emulator/emulator", | ||
| ]); | ||
| const sdkManagerCandidates = sdkRoots | ||
| .map((root) => node_path_1.default.join(root, "cmdline-tools", "latest", "bin", "sdkmanager")) | ||
| .concat([ | ||
| "/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin/sdkmanager", | ||
| "/usr/local/share/android-commandlinetools/cmdline-tools/latest/bin/sdkmanager", | ||
| ]); | ||
| const avdManagerCandidates = sdkRoots | ||
| .map((root) => node_path_1.default.join(root, "cmdline-tools", "latest", "bin", "avdmanager")) | ||
| .concat([ | ||
| "/opt/homebrew/share/android-commandlinetools/cmdline-tools/latest/bin/avdmanager", | ||
| "/usr/local/share/android-commandlinetools/cmdline-tools/latest/bin/avdmanager", | ||
| ]); | ||
| return { | ||
| adb: firstExecutable(adbCandidates) ?? findInPath("adb"), | ||
| emulator: firstExecutable(emulatorCandidates) ?? findInPath("emulator"), | ||
| sdkmanager: firstExecutable(sdkManagerCandidates) ?? findInPath("sdkmanager"), | ||
| avdmanager: firstExecutable(avdManagerCandidates) ?? findInPath("avdmanager"), | ||
| }; | ||
| } | ||
| function missingTools(toolPaths) { | ||
| const required = ["adb", "emulator", "sdkmanager", "avdmanager"]; | ||
| return required.filter((name) => !toolPaths[name]); | ||
| } | ||
| function resolveBrewBinary() { | ||
| return firstExecutable(["/opt/homebrew/bin/brew", "/usr/local/bin/brew"]) ?? findInPath("brew"); | ||
| } | ||
| function extendProcessPathForBrew() { | ||
| const extra = ["/opt/homebrew/bin", "/usr/local/bin"]; | ||
| const entries = (process.env.PATH ?? "") | ||
| .split(node_path_1.default.delimiter) | ||
| .filter(Boolean); | ||
| for (const candidate of extra) { | ||
| if (!entries.includes(candidate) && node_fs_1.default.existsSync(candidate)) { | ||
| entries.unshift(candidate); | ||
| } | ||
| } | ||
| process.env.PATH = entries.join(node_path_1.default.delimiter); | ||
| } | ||
| function installHomebrew(logger) { | ||
| logger("Homebrew not found. Installing Homebrew..."); | ||
| const result = run("/usr/bin/env", [ | ||
| "bash", | ||
| "-lc", | ||
| "NONINTERACTIVE=1 /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"", | ||
| ], { inherit: true }); | ||
| if (!result.ok) { | ||
| throw new Error("Failed to install Homebrew automatically."); | ||
| } | ||
| extendProcessPathForBrew(); | ||
| } | ||
| function installBrewCask(brew, cask, logger) { | ||
| const exists = run(brew, ["list", "--cask", cask]); | ||
| if (exists.ok) { | ||
| logger(`brew cask '${cask}' already installed (skip).`); | ||
| return false; | ||
| } | ||
| logger(`Installing brew cask '${cask}'...`); | ||
| const installed = run(brew, ["install", "--cask", cask], { inherit: true }); | ||
| if (!installed.ok) { | ||
| throw new Error(`brew install --cask ${cask} failed.`); | ||
| } | ||
| return true; | ||
| } | ||
| function acceptSdkLicenses(sdkmanager, sdkRoot, logger) { | ||
| logger("Accepting Android SDK licenses..."); | ||
| const res = run(sdkmanager, [`--sdk_root=${sdkRoot}`, "--licenses"], { | ||
| input: `${"y\n".repeat(200)}`, | ||
| }); | ||
| if (!res.ok) { | ||
| logger("SDK licenses command returned non-zero; continuing."); | ||
| } | ||
| } | ||
| function installSdkPackages(sdkmanager, sdkRoot, logger) { | ||
| logger("Installing Android SDK packages: platform-tools, emulator, platforms;android-34 ..."); | ||
| const result = run(sdkmanager, [`--sdk_root=${sdkRoot}`, "platform-tools", "emulator", "platforms;android-34"], { inherit: true }); | ||
| if (!result.ok) { | ||
| throw new Error("Failed to install required Android SDK packages."); | ||
| } | ||
| } | ||
| function installOneSystemImage(sdkmanager, sdkRoot, logger) { | ||
| const archTag = process.arch === "arm64" ? "arm64-v8a" : "x86_64"; | ||
| const candidates = Array.from(new Set([ | ||
| `system-images;android-34;google_apis_playstore;${archTag}`, | ||
| "system-images;android-34;google_apis_playstore;x86_64", | ||
| "system-images;android-34;google_apis_playstore;arm64-v8a", | ||
| "system-images;android-34;google_apis;x86_64", | ||
| "system-images;android-34;google_apis;arm64-v8a", | ||
| ])); | ||
| for (const pkg of candidates) { | ||
| logger(`Trying system image: ${pkg}`); | ||
| const res = run(sdkmanager, [`--sdk_root=${sdkRoot}`, pkg], { inherit: true }); | ||
| if (res.ok) { | ||
| logger(`System image ready: ${pkg}`); | ||
| return pkg; | ||
| } | ||
| } | ||
| logger("Could not install a system image automatically."); | ||
| return null; | ||
| } | ||
| function listAvdNames(avdmanager, sdkRoot) { | ||
| const env = { | ||
| ...process.env, | ||
| ANDROID_SDK_ROOT: sdkRoot, | ||
| ANDROID_HOME: sdkRoot, | ||
| }; | ||
| const result = run(avdmanager, ["list", "avd"], { env }); | ||
| if (!result.ok) { | ||
| return []; | ||
| } | ||
| const names = []; | ||
| const regex = /^Name:\s*(.+)$/gm; | ||
| let match = regex.exec(result.stdout); | ||
| while (match) { | ||
| names.push(match[1].trim()); | ||
| match = regex.exec(result.stdout); | ||
| } | ||
| return names; | ||
| } | ||
| function createAvd(avdmanager, sdkRoot, avdName, imagePackage, logger) { | ||
| logger(`Creating AVD '${avdName}' with image '${imagePackage}'...`); | ||
| const env = { | ||
| ...process.env, | ||
| ANDROID_SDK_ROOT: sdkRoot, | ||
| ANDROID_HOME: sdkRoot, | ||
| }; | ||
| const result = run(avdmanager, ["create", "avd", "--force", "-n", avdName, "-k", imagePackage], { env, input: "no\n" }); | ||
| if (!result.ok) { | ||
| logger("Failed to create AVD automatically."); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| async function ensureAndroidPrerequisites(config, options = {}) { | ||
| const logger = options.logger ?? (() => { }); | ||
| const autoInstall = options.autoInstall !== false; | ||
| if (process.env.OPENPOCKET_SKIP_ENV_SETUP === "1") { | ||
| const { sdkRoot, configUpdated } = collectSdkRoot(config); | ||
| return { | ||
| skipped: true, | ||
| configUpdated, | ||
| sdkRoot, | ||
| toolPaths: detectTools(sdkRoot), | ||
| installedSteps: [], | ||
| avdCreated: false, | ||
| }; | ||
| } | ||
| const { sdkRoot, configUpdated } = collectSdkRoot(config); | ||
| (0, paths_1.ensureDir)(sdkRoot); | ||
| let tools = detectTools(sdkRoot); | ||
| let missing = missingTools(tools); | ||
| const installedSteps = []; | ||
| let avdCreated = false; | ||
| if (missing.length > 0) { | ||
| if (!autoInstall) { | ||
| throw new Error(`Missing Android prerequisites: ${missing.join(", ")}`); | ||
| } | ||
| if (process.platform !== "darwin") { | ||
| throw new Error(`Missing Android prerequisites on ${process.platform}: ${missing.join(", ")}. Auto-install currently supports macOS only.`); | ||
| } | ||
| let brew = resolveBrewBinary(); | ||
| if (!brew) { | ||
| installHomebrew(logger); | ||
| installedSteps.push("homebrew"); | ||
| brew = resolveBrewBinary(); | ||
| } | ||
| if (!brew) { | ||
| throw new Error("Homebrew was not found after installation attempt."); | ||
| } | ||
| if (installBrewCask(brew, "android-platform-tools", logger)) { | ||
| installedSteps.push("brew:android-platform-tools"); | ||
| } | ||
| if (installBrewCask(brew, "android-commandlinetools", logger)) { | ||
| installedSteps.push("brew:android-commandlinetools"); | ||
| } | ||
| tools = detectTools(sdkRoot); | ||
| missing = missingTools(tools); | ||
| } | ||
| if (missing.length > 0) { | ||
| throw new Error(`Missing Android prerequisites after installation: ${missing.join(", ")}`); | ||
| } | ||
| const sdkmanager = tools.sdkmanager; | ||
| const avdmanager = tools.avdmanager; | ||
| if (!sdkmanager || !avdmanager) { | ||
| throw new Error("sdkmanager or avdmanager is not available."); | ||
| } | ||
| acceptSdkLicenses(sdkmanager, sdkRoot, logger); | ||
| installSdkPackages(sdkmanager, sdkRoot, logger); | ||
| installedSteps.push("sdk:platform-tools,emulator,platforms;android-34"); | ||
| const currentAvds = listAvdNames(avdmanager, sdkRoot); | ||
| if (!currentAvds.includes(config.emulator.avdName)) { | ||
| const image = installOneSystemImage(sdkmanager, sdkRoot, logger); | ||
| if (image) { | ||
| installedSteps.push(`sdk:${image}`); | ||
| avdCreated = createAvd(avdmanager, sdkRoot, config.emulator.avdName, image, logger); | ||
| if (!avdCreated) { | ||
| throw new Error(`Failed to create AVD '${config.emulator.avdName}'.`); | ||
| } | ||
| installedSteps.push(`avd:${config.emulator.avdName}`); | ||
| } | ||
| else if (currentAvds.length === 0) { | ||
| throw new Error("No AVD exists and no installable system image was found."); | ||
| } | ||
| } | ||
| tools = detectTools(sdkRoot); | ||
| return { | ||
| skipped: false, | ||
| configUpdated, | ||
| sdkRoot, | ||
| toolPaths: tools, | ||
| installedSteps, | ||
| avdCreated, | ||
| }; | ||
| } |
+131
-39
@@ -20,2 +20,4 @@ #!/usr/bin/env node | ||
| const cli_shortcut_1 = require("./install/cli-shortcut"); | ||
| const android_prerequisites_1 = require("./environment/android-prerequisites"); | ||
| const DEFAULT_PANEL_RELEASE_URL = "https://github.com/SergioChan/openpocket/releases/latest"; | ||
| function printHelp() { | ||
@@ -25,5 +27,3 @@ // eslint-disable-next-line no-console | ||
| Usage: | ||
| openpocket [--config <path>] init | ||
| openpocket [--config <path>] install-cli | ||
| openpocket [--config <path>] setup | ||
| openpocket [--config <path>] onboard | ||
@@ -44,6 +44,8 @@ openpocket [--config <path>] config-show | ||
| Legacy aliases (deprecated): | ||
| openpocket [--config <path>] init | ||
| openpocket [--config <path>] setup | ||
| Examples: | ||
| openpocket init | ||
| openpocket install-cli | ||
| openpocket setup | ||
| openpocket onboard | ||
| openpocket emulator start | ||
@@ -57,2 +59,51 @@ openpocket agent --model gpt-5.2-codex "Open Chrome and search weather" | ||
| } | ||
| function getPanelReleaseUrl() { | ||
| const fromEnv = process.env.OPENPOCKET_PANEL_RELEASE_URL?.trim(); | ||
| if (fromEnv) { | ||
| return fromEnv; | ||
| } | ||
| try { | ||
| const packageJsonPath = node_path_1.default.resolve(__dirname, "..", "package.json"); | ||
| const pkg = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, "utf-8")); | ||
| if (pkg.homepage?.trim()) { | ||
| return pkg.homepage.includes("/releases") | ||
| ? pkg.homepage | ||
| : `${pkg.homepage.replace(/\/$/, "")}/releases/latest`; | ||
| } | ||
| const repoUrlRaw = typeof pkg.repository === "string" | ||
| ? pkg.repository | ||
| : pkg.repository?.url; | ||
| const repoUrl = repoUrlRaw?.replace(/^git\+/, "").replace(/\.git$/, ""); | ||
| if (repoUrl?.includes("github.com")) { | ||
| const normalized = repoUrl.replace(/^git@github.com:/, "https://github.com/"); | ||
| return `${normalized.replace(/\/$/, "")}/releases/latest`; | ||
| } | ||
| } | ||
| catch { | ||
| // ignore and fallback | ||
| } | ||
| return DEFAULT_PANEL_RELEASE_URL; | ||
| } | ||
| function resolveInstalledPanelApp() { | ||
| const home = process.env.HOME ?? ""; | ||
| const candidates = [ | ||
| "/Applications/OpenPocket Control Panel.app", | ||
| node_path_1.default.join(home, "Applications", "OpenPocket Control Panel.app"), | ||
| "/Applications/OpenPocketMenuBar.app", | ||
| node_path_1.default.join(home, "Applications", "OpenPocketMenuBar.app"), | ||
| ].filter(Boolean); | ||
| for (const appPath of candidates) { | ||
| if (node_fs_1.default.existsSync(appPath)) { | ||
| return appPath; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function openPanelApp(appPath) { | ||
| const result = (0, node_child_process_1.spawnSync)("/usr/bin/open", [appPath], { stdio: "ignore" }); | ||
| return (result.status ?? 1) === 0; | ||
| } | ||
| function openReleasePage(url) { | ||
| (0, node_child_process_1.spawnSync)("/usr/bin/open", [url], { stdio: "ignore" }); | ||
| } | ||
| function takeOption(args, name) { | ||
@@ -155,5 +206,16 @@ const out = []; | ||
| } | ||
| async function runSetupCommand(configPath) { | ||
| async function runBootstrapCommand(configPath) { | ||
| const cfg = (0, config_1.loadConfig)(configPath); | ||
| await (0, android_prerequisites_1.ensureAndroidPrerequisites)(cfg, { | ||
| autoInstall: true, | ||
| logger: (line) => { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`[OpenPocket][env] ${line}`); | ||
| }, | ||
| }); | ||
| (0, config_1.saveConfig)(cfg); | ||
| return cfg; | ||
| } | ||
| async function runOnboardCommand(configPath) { | ||
| const cfg = await runBootstrapCommand(configPath); | ||
| await (0, setup_wizard_1.runSetupWizard)(cfg); | ||
@@ -245,35 +307,53 @@ return 0; | ||
| } | ||
| const installedApp = resolveInstalledPanelApp(); | ||
| if (installedApp) { | ||
| if (!openPanelApp(installedApp)) { | ||
| throw new Error(`Failed to open installed panel app: ${installedApp}`); | ||
| } | ||
| // eslint-disable-next-line no-console | ||
| console.log(`OpenPocket Control Panel opened: ${installedApp}`); | ||
| return 0; | ||
| } | ||
| const panelDir = node_path_1.default.resolve(__dirname, "..", "apps", "openpocket-menubar"); | ||
| const buildScript = node_path_1.default.join(panelDir, "scripts", "build.sh"); | ||
| const runScript = node_path_1.default.join(panelDir, "scripts", "run.sh"); | ||
| if (!node_fs_1.default.existsSync(runScript) || !node_fs_1.default.existsSync(buildScript)) { | ||
| throw new Error(`Menu bar app launcher not found: ${runScript}`); | ||
| const hasBundledSource = node_fs_1.default.existsSync(runScript) && node_fs_1.default.existsSync(buildScript); | ||
| if (hasBundledSource) { | ||
| const buildResult = (0, node_child_process_1.spawnSync)("/usr/bin/env", ["bash", buildScript], { | ||
| stdio: "inherit", | ||
| cwd: panelDir, | ||
| }); | ||
| if (buildResult.error) { | ||
| throw buildResult.error; | ||
| } | ||
| if ((buildResult.status ?? 1) !== 0) { | ||
| return buildResult.status ?? 1; | ||
| } | ||
| const appBinary = node_path_1.default.join(panelDir, ".build", "debug", "OpenPocketMenuBar"); | ||
| if (!node_fs_1.default.existsSync(appBinary)) { | ||
| throw new Error(`Built menu bar app not found: ${appBinary}`); | ||
| } | ||
| const env = { ...process.env }; | ||
| if (configPath?.trim()) { | ||
| env.OPENPOCKET_CONFIG_PATH = node_path_1.default.resolve(configPath.trim()); | ||
| } | ||
| const child = (0, node_child_process_1.spawn)(appBinary, [], { | ||
| cwd: panelDir, | ||
| detached: true, | ||
| stdio: "ignore", | ||
| env, | ||
| }); | ||
| child.unref(); | ||
| // eslint-disable-next-line no-console | ||
| console.log(`OpenPocket Control Panel started (pid=${child.pid ?? "unknown"}).`); | ||
| return 0; | ||
| } | ||
| const buildResult = (0, node_child_process_1.spawnSync)("/usr/bin/env", ["bash", buildScript], { | ||
| stdio: "inherit", | ||
| cwd: panelDir, | ||
| }); | ||
| if (buildResult.error) { | ||
| throw buildResult.error; | ||
| } | ||
| if ((buildResult.status ?? 1) !== 0) { | ||
| return buildResult.status ?? 1; | ||
| } | ||
| const appBinary = node_path_1.default.join(panelDir, ".build", "debug", "OpenPocketMenuBar"); | ||
| if (!node_fs_1.default.existsSync(appBinary)) { | ||
| throw new Error(`Built menu bar app not found: ${appBinary}`); | ||
| } | ||
| const env = { ...process.env }; | ||
| if (configPath?.trim()) { | ||
| env.OPENPOCKET_CONFIG_PATH = node_path_1.default.resolve(configPath.trim()); | ||
| } | ||
| const child = (0, node_child_process_1.spawn)(appBinary, [], { | ||
| cwd: panelDir, | ||
| detached: true, | ||
| stdio: "ignore", | ||
| env, | ||
| }); | ||
| child.unref(); | ||
| const releaseUrl = getPanelReleaseUrl(); | ||
| // eslint-disable-next-line no-console | ||
| console.log(`OpenPocket Control Panel started (pid=${child.pid ?? "unknown"}).`); | ||
| console.log("OpenPocket panel app is not installed on this Mac."); | ||
| // eslint-disable-next-line no-console | ||
| console.log(`Opening download page: ${releaseUrl}`); | ||
| openReleasePage(releaseUrl); | ||
| // eslint-disable-next-line no-console | ||
| console.log("Install the macOS PKG from Releases, then run: openpocket panel start"); | ||
| return 0; | ||
@@ -289,6 +369,13 @@ } | ||
| if (command === "init") { | ||
| const cfg = (0, config_1.loadConfig)(configPath ?? undefined); | ||
| (0, config_1.saveConfig)(cfg); | ||
| // eslint-disable-next-line no-console | ||
| console.log(`OpenPocket initialized.\nConfig: ${cfg.configPath}`); | ||
| console.log("[OpenPocket] `init` is deprecated. Use `openpocket onboard`."); | ||
| const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY); | ||
| if (interactive) { | ||
| return runOnboardCommand(configPath ?? undefined); | ||
| } | ||
| const cfg = await runBootstrapCommand(configPath ?? undefined); | ||
| // eslint-disable-next-line no-console | ||
| console.log(`OpenPocket bootstrap completed.\nConfig: ${cfg.configPath}`); | ||
| // eslint-disable-next-line no-console | ||
| console.log("Run `openpocket onboard` in an interactive terminal to complete consent/model/API key onboarding."); | ||
| return 0; | ||
@@ -323,5 +410,10 @@ } | ||
| } | ||
| if (command === "setup" || command === "onboard") { | ||
| return runSetupCommand(configPath ?? undefined); | ||
| if (command === "setup") { | ||
| // eslint-disable-next-line no-console | ||
| console.log("[OpenPocket] `setup` is deprecated. Use `openpocket onboard`."); | ||
| return runOnboardCommand(configPath ?? undefined); | ||
| } | ||
| if (command === "onboard") { | ||
| return runOnboardCommand(configPath ?? undefined); | ||
| } | ||
| throw new Error(`Unknown command: ${command}`); | ||
@@ -328,0 +420,0 @@ } |
+10
-2
| { | ||
| "name": "openpocket", | ||
| "version": "0.2.0-mvp-ts", | ||
| "version": "0.2.1", | ||
| "description": "OpenPocket Node.js TypeScript runtime", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/SergioChan/openpocket.git" | ||
| }, | ||
| "homepage": "https://github.com/SergioChan/openpocket", | ||
| "main": "dist/cli.js", | ||
@@ -23,2 +28,3 @@ "bin": { | ||
| "docs:build": "vitepress build docs", | ||
| "docs:build:pages": "DOCS_BASE=/openpocket/ vitepress build docs", | ||
| "docs:preview": "vitepress preview docs --host" | ||
@@ -36,6 +42,8 @@ }, | ||
| "@types/node-telegram-bot-api": "^0.64.7", | ||
| "mermaid": "^11.12.3", | ||
| "tsx": "^4.19.2", | ||
| "typescript": "^5.7.2", | ||
| "vitepress": "^1.6.4" | ||
| "vitepress": "^1.6.4", | ||
| "vitepress-plugin-mermaid": "^2.0.17" | ||
| } | ||
| } |
+91
-30
@@ -57,26 +57,45 @@ # OpenPocket | ||
| ### 2. Install and initialize (npm package) | ||
| ### 2. Option A: Use the npm package (no source code required) | ||
| After publishing to npm, use: | ||
| ```bash | ||
| npm install -g openpocket | ||
| openpocket init | ||
| openpocket onboard | ||
| ``` | ||
| ### 3. Install and initialize (local clone, no global install) | ||
| If you use the native macOS panel, install the release package from: | ||
| - [OpenPocket Releases](https://github.com/SergioChan/openpocket/releases) | ||
| Then start the panel: | ||
| ```bash | ||
| cd /Users/sergiochan/Documents/GitHub/phone-use-agent | ||
| openpocket panel start | ||
| ``` | ||
| ### 3. Option B: Use a local source clone (for contributors) | ||
| ```bash | ||
| git clone git@github.com:SergioChan/openpocket.git | ||
| cd openpocket | ||
| npm install | ||
| ./openpocket init | ||
| npm run build | ||
| ./openpocket onboard | ||
| ``` | ||
| `./openpocket` automatically runs `dist/cli.js` when present, and falls back to `tsx src/cli.ts` in dev installs. | ||
| `./openpocket` runs `dist/cli.js` when present and falls back to `tsx src/cli.ts` in local dev installs. | ||
| `openpocket onboard` automatically verifies Android runtime dependencies: | ||
| 1. If local tools are already installed, dependency installation is skipped. | ||
| 2. If tools are missing on macOS, OpenPocket tries automatic installation (Homebrew, Android SDK packages, and default AVD bootstrap). | ||
| You can skip this step in CI/tests with: | ||
| ```bash | ||
| export OPENPOCKET_SKIP_ENV_SETUP=1 | ||
| ``` | ||
| ### 4. Start runtime | ||
| Use `openpocket ...` (npm global install) or `./openpocket ...` (local clone): | ||
| For npm package install: | ||
@@ -88,9 +107,17 @@ ```bash | ||
| ### 5. Command resolution and PATH behavior | ||
| For local source clone: | ||
| - `init` / `setup` / `onboard` do **not** modify your shell config or PATH. | ||
| - Use `openpocket ...` when installed from npm globally. | ||
| - Use `./openpocket ...` when running from a local cloned repository. | ||
| - `openpocket install-cli` is optional and explicit; run it only if you want a user-local launcher under `~/.local/bin/openpocket`. | ||
| ```bash | ||
| ./openpocket emulator start | ||
| ./openpocket gateway start | ||
| ``` | ||
| ### 5. Optional: install a user-local command | ||
| ```bash | ||
| ./openpocket install-cli | ||
| ``` | ||
| This installs `~/.local/bin/openpocket` and updates shell rc files when needed. | ||
| ## Configuration | ||
@@ -119,22 +146,40 @@ | ||
| Use `openpocket ...` if installed via npm global package, or `./openpocket ...` in a local clone. | ||
| Command prefix by install mode: | ||
| - npm package install: use `openpocket ...` | ||
| - local source clone: use `./openpocket ...` (or `openpocket ...` after `install-cli`) | ||
| ```bash | ||
| openpocket --help | ||
| openpocket init | ||
| openpocket install-cli | ||
| openpocket setup | ||
| openpocket onboard | ||
| openpocket config-show | ||
| openpocket emulator start | ||
| openpocket emulator status | ||
| openpocket agent --model gpt-5.2-codex "Open Chrome and search weather" | ||
| openpocket script run --text "echo hello" | ||
| openpocket skills list | ||
| openpocket gateway start | ||
| openpocket panel start | ||
| ./openpocket --help | ||
| ./openpocket install-cli | ||
| ./openpocket onboard | ||
| ./openpocket config-show | ||
| ./openpocket emulator start | ||
| ./openpocket emulator status | ||
| ./openpocket agent --model gpt-5.2-codex "Open Chrome and search weather" | ||
| ./openpocket script run --text "echo hello" | ||
| ./openpocket skills list | ||
| ./openpocket gateway start | ||
| ./openpocket panel start | ||
| ``` | ||
| Legacy aliases still work (deprecated): `openpocket init`, `openpocket setup`. | ||
| `openpocket panel start` on macOS uses this order: | ||
| 1. Open an already-installed panel app from `/Applications` or `~/Applications`. | ||
| 2. If running from a source clone with `apps/openpocket-menubar`, build and launch from source. | ||
| 3. If neither is available (typical npm install), open GitHub Releases and guide PKG installation. | ||
| ## Documentation | ||
| ### Where the frontend is | ||
| The documentation frontend is implemented in this repository: | ||
| - Site source: [`/docs`](./docs) | ||
| - VitePress config: [`/docs/.vitepress/config.mjs`](./docs/.vitepress/config.mjs) | ||
| - Custom homepage: [`/docs/index.md`](./docs/index.md) | ||
| - Custom theme styles: [`/docs/.vitepress/theme/custom.css`](./docs/.vitepress/theme/custom.css) | ||
| ### Documentation Website | ||
@@ -154,2 +199,8 @@ | ||
| - Build for GitHub Pages (project-path base): | ||
| ```bash | ||
| npm run docs:build:pages | ||
| ``` | ||
| - Preview built docs: | ||
@@ -161,2 +212,12 @@ | ||
| ### Deployment options | ||
| - GitHub Pages workflow: [`.github/workflows/deploy-docs-pages.yml`](./.github/workflows/deploy-docs-pages.yml) | ||
| - Vercel config: [`vercel.json`](./vercel.json) | ||
| - Deployment guide: [`/docs/get-started/deploy-docs.md`](./docs/get-started/deploy-docs.md) | ||
| Expected GitHub Pages URL for this repo: | ||
| - `https://sergiochan.github.io/openpocket/` | ||
| ### Docs entry points | ||
@@ -166,4 +227,4 @@ | ||
| - [Documentation Hubs](./docs/hubs.md) | ||
| - [Get Started](./docs/get-started/README.md) | ||
| - [Reference](./docs/reference/README.md) | ||
| - [Get Started](./docs/get-started/index.md) | ||
| - [Reference](./docs/reference/index.md) | ||
| - [Ops Runbook](./docs/ops/runbook.md) | ||
@@ -170,0 +231,0 @@ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
190506
10.36%27
3.85%4688
9.05%1
-50%259
30.81%7
40%26
52.94%