@mearl/setup
Advanced tools
+4
-4
| #!/usr/bin/env node | ||
| import { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin } from './agentBay.js'; | ||
| import { type MearlLifecycleOperation } from './lifecycle.js'; | ||
| import { runMearlLifecycle, type MearlLifecycleOperation } from './lifecycle.js'; | ||
| import type { MearlProfile } from './profiles.js'; | ||
| import { createQoderPairingInfo } from './qoder.js'; | ||
| export declare const SETUP_VERSION: string; | ||
@@ -10,4 +9,5 @@ interface ParsedArgs { | ||
| agentBayReadStdin: boolean; | ||
| cloudMode?: 'websocket' | 'qoder'; | ||
| help: boolean; | ||
| operation?: MearlLifecycleOperation | 'agentbay' | 'qoder'; | ||
| operation?: MearlLifecycleOperation | 'agentbay'; | ||
| profiles?: MearlProfile[]; | ||
@@ -24,3 +24,3 @@ skipSkill: boolean; | ||
| readAgentBayKeyStdin?: typeof readAgentBayApiKeyStdin; | ||
| createQoderPairing?: typeof createQoderPairingInfo; | ||
| runLifecycle?: typeof runMearlLifecycle; | ||
| } | ||
@@ -27,0 +27,0 @@ export declare function parseSetupArgs(argv: string[]): ParsedArgs; |
+18
-34
@@ -5,3 +5,2 @@ #!/usr/bin/env node | ||
| import { runMearlLifecycle } from './lifecycle.js'; | ||
| import { createQoderPairingInfo, formatQoderPairingOutput } from './qoder.js'; | ||
| import { isMearlReleaseLine } from './version.js'; | ||
@@ -57,16 +56,2 @@ const require = createRequire(import.meta.url); | ||
| } | ||
| if (operation === 'qoder') { | ||
| const options = argv.slice(1); | ||
| const allowed = new Set(['--yes', '-y', '--non-interactive']); | ||
| if (options.some(option => !allowed.has(option))) { | ||
| throw new Error('Usage: npx @mearl/setup qoder [--yes]'); | ||
| } | ||
| return { | ||
| agentBayReadStdin: false, | ||
| help: false, | ||
| operation, | ||
| skipSkill: false, | ||
| yes: options.length > 0, | ||
| }; | ||
| } | ||
| if (operation !== 'install' && operation !== 'update' && operation !== 'uninstall') { | ||
@@ -79,2 +64,3 @@ throw new Error(`Unknown operation: ${operation}`); | ||
| let yes = false; | ||
| let cloudMode; | ||
| for (let index = optionStart; index < argv.length; index += 1) { | ||
@@ -99,5 +85,12 @@ const arg = argv[index]; | ||
| } | ||
| if (arg === '--cloud' || arg === '--qoder-cloud') { | ||
| const requestedMode = arg === '--qoder-cloud' ? 'qoder' : 'websocket'; | ||
| if (cloudMode && cloudMode !== requestedMode) { | ||
| throw new Error('--cloud and --qoder-cloud cannot be used together'); | ||
| } | ||
| cloudMode = requestedMode; | ||
| } | ||
| const candidate = arg === '--local' | ||
| ? 'local' | ||
| : arg === '--cloud' | ||
| : arg === '--cloud' || arg === '--qoder-cloud' | ||
| ? 'cloud' | ||
@@ -119,2 +112,3 @@ : arg === '--mcp' | ||
| operation, | ||
| ...(cloudMode ? { cloudMode } : {}), | ||
| ...(profiles.length > 0 ? { profiles } : {}), | ||
@@ -130,6 +124,5 @@ skipSkill, | ||
| Usage: | ||
| npx @mearl/setup [install] [--local] [--cloud] [--mcp] [--yes] | ||
| npx @mearl/setup <update|uninstall> [--local] [--cloud] [--mcp] [--yes] | ||
| npx @mearl/setup [install] [--local] [--cloud|--qoder-cloud] [--mcp] [--yes] | ||
| npx @mearl/setup <update|uninstall> [--local] [--cloud|--qoder-cloud] [--mcp] [--yes] | ||
| npx @mearl/setup agentbay [--stdin | --key-file <path>] | ||
| npx @mearl/setup qoder [--yes] | ||
@@ -141,7 +134,8 @@ Behavior: | ||
| agentbay Save or update AGENTBAY_KEY without changing other Mearl settings | ||
| qoder Create a Qoder client-side tool pairing request for troubleshooting | ||
| Options: | ||
| --local Manage @mearl/native-host and @mearl/client | ||
| --cloud Manage @mearl/cloud-server and the unified @mearl/client | ||
| --cloud Manage cloud packages in WebSocket server mode | ||
| --qoder-cloud | ||
| Manage the same cloud packages in Qoder pairing mode | ||
| --mcp Manage @mearl/mcp-server and its configuration | ||
@@ -168,3 +162,3 @@ --stdin Read the AgentBay API key from standard input | ||
| npx @mearl/setup agentbay | ||
| npx @mearl/setup qoder --yes | ||
| npx @mearl/setup --qoder-cloud --yes | ||
| npx @mearl/setup update --local --target-line 2.3.x --skip-skill | ||
@@ -220,13 +214,2 @@ npx @mearl/setup uninstall --local`; | ||
| } | ||
| if (parsed.operation === 'qoder') { | ||
| try { | ||
| const pairing = (dependencies.createQoderPairing ?? createQoderPairingInfo)(); | ||
| console.log(formatQoderPairingOutput(pairing)); | ||
| return 0; | ||
| } | ||
| catch (error) { | ||
| console.error(error instanceof Error ? error.message : String(error)); | ||
| return 1; | ||
| } | ||
| } | ||
| if (!interactiveTerminal && !parsed.yes && parsed.operation !== 'update') { | ||
@@ -237,5 +220,6 @@ console.error('Non-interactive input detected. Re-run with --yes or --non-interactive.'); | ||
| try { | ||
| const ok = await runMearlLifecycle({ | ||
| const ok = await (dependencies.runLifecycle ?? runMearlLifecycle)({ | ||
| operation: parsed.operation, | ||
| profiles: parsed.profiles, | ||
| cloudMode: parsed.cloudMode, | ||
| skipSkill: parsed.skipSkill, | ||
@@ -242,0 +226,0 @@ targetLine: parsed.targetLine, |
+0
-2
| export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js'; | ||
| export { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js'; | ||
| export type { AgentBayConfigurationOptions, AgentBayConfigurationResult, AgentBaySecretInput, AgentBaySecretOutput, } from './agentBay.js'; | ||
| export { createQoderPairingInfo, formatQoderPairingOutput } from './qoder.js'; | ||
| export type { QoderPairingInfo, QoderPairingOptions } from './qoder.js'; | ||
| export type { DetectedGlobalPackage, GlobalPackageCommand, GlobalPackageManager, PackageManagerInstallation, PackageOperation, PackageDetectionOptions, } from './packageManager.js'; | ||
@@ -7,0 +5,0 @@ export { runMearlLifecycle } from './lifecycle.js'; |
+0
-1
| export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js'; | ||
| export { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js'; | ||
| export { createQoderPairingInfo, formatQoderPairingOutput } from './qoder.js'; | ||
| export { runMearlLifecycle } from './lifecycle.js'; | ||
@@ -5,0 +4,0 @@ export { MEARL_PROFILES, detectMearlProfiles } from './profiles.js'; |
@@ -9,2 +9,3 @@ import { runGlobalMearlSkillOperation } from './aliSkills.js'; | ||
| operation: MearlLifecycleOperation; | ||
| cloudMode?: 'websocket' | 'qoder'; | ||
| /** @deprecated Use profiles when selecting one or more composable environments. */ | ||
@@ -11,0 +12,0 @@ profile?: MearlProfile; |
+12
-7
@@ -59,5 +59,6 @@ import fs from 'node:fs'; | ||
| } | ||
| async function runHook(profile, operation, detected, runner, existsSync, env, writeLine, yes) { | ||
| async function runHook(profile, operation, detected, runner, existsSync, env, writeLine, yes, cloudMode) { | ||
| let executable; | ||
| let args; | ||
| let hookEnv = env; | ||
| if (profile === 'local' && operation !== 'uninstall') { | ||
@@ -85,2 +86,5 @@ executable = detected.executable; | ||
| } | ||
| const requestedMode = operation === 'uninstall' ? 'websocket' : cloudMode; | ||
| const commandEnv = requestedMode ? { ...env, MEARL_CLOUD_MODE: requestedMode } : env; | ||
| hookEnv = commandEnv; | ||
| if (operation === 'uninstall') { | ||
@@ -90,3 +94,3 @@ args = ['stop']; | ||
| else { | ||
| const status = await runLifecycleCommand(runner, executable, ['status'], env, 'Hook: cloud status', writeLine, true); | ||
| const status = await runLifecycleCommand(runner, executable, ['status'], commandEnv, 'Hook: cloud status', writeLine, true); | ||
| if (!status) | ||
@@ -99,7 +103,8 @@ return false; | ||
| const running = /\[CloudServer\]\s+Status:\s+running\b/.test(status.stdout); | ||
| if (operation === 'update' && !running) { | ||
| const qoderMode = cloudMode === 'qoder' || /\[CloudServer\]\s+Mode:\s+qoder\b/.test(status.stdout); | ||
| if (operation === 'update' && !running && !qoderMode) { | ||
| writeLine('Hook: cloud update skipped because cloud-server is not running.'); | ||
| return true; | ||
| } | ||
| args = [running ? 'restart' : 'start']; | ||
| args = [running && !qoderMode ? 'restart' : 'start']; | ||
| } | ||
@@ -114,3 +119,3 @@ } | ||
| } | ||
| const result = await runLifecycleCommand(runner, executable, args, env, `Hook: ${profile} ${operation}`, writeLine); | ||
| const result = await runLifecycleCommand(runner, executable, args, hookEnv, `Hook: ${profile} ${operation}`, writeLine); | ||
| if (!result) | ||
@@ -241,3 +246,3 @@ return false; | ||
| if (options.operation === 'uninstall' && | ||
| !(await runHook(profile, options.operation, current, runner, existsSync, env, writeLine, options.yes === true))) { | ||
| !(await runHook(profile, options.operation, current, runner, existsSync, env, writeLine, options.yes === true, options.cloudMode))) { | ||
| packagesOk = false; | ||
@@ -262,3 +267,3 @@ continue; | ||
| if (!refreshed || | ||
| !(await runHook(profile, options.operation, refreshed, runner, existsSync, env, writeLine, options.yes === true))) { | ||
| !(await runHook(profile, options.operation, refreshed, runner, existsSync, env, writeLine, options.yes === true, options.cloudMode))) { | ||
| packagesOk = false; | ||
@@ -265,0 +270,0 @@ } |
+1
-5
| { | ||
| "name": "@mearl/setup", | ||
| "version": "2.7.1", | ||
| "version": "2.7.2", | ||
| "description": "One-shot environment setup and configuration for Mearl", | ||
@@ -33,6 +33,2 @@ "type": "module", | ||
| }, | ||
| "./qoder": { | ||
| "types": "./dist/qoder.d.ts", | ||
| "import": "./dist/qoder.js" | ||
| }, | ||
| "./version": { | ||
@@ -39,0 +35,0 @@ "types": "./dist/version.d.ts", |
+11
-8
@@ -6,4 +6,4 @@ # @mearl/setup | ||
| ```bash | ||
| npx @mearl/setup [install] [--local] [--cloud] [--mcp] [--yes] | ||
| npx @mearl/setup <update|uninstall> [--local] [--cloud] [--mcp] [--yes] | ||
| npx @mearl/setup [install] [--local] [--cloud|--qoder-cloud] [--mcp] [--yes] | ||
| npx @mearl/setup <update|uninstall> [--local] [--cloud|--qoder-cloud] [--mcp] [--yes] | ||
| ``` | ||
@@ -21,5 +21,6 @@ | ||
| Qoder Cloud Agent 仅在常规云连接失败、Mearl Skill 进入 Qoder 异常接管分支时使用 | ||
| `npx @mearl/setup qoder --yes` 生成一次性配对指令。它不安装额外隧道,也不会把 Qoder | ||
| PAT 写入配对 URL;正常安装和连接流程无需执行该命令。 | ||
| 在 Qoder Cloud Agent 环境中,使用 `npx @mearl/setup --qoder-cloud --yes` 选择 Qoder | ||
| 配对模式。它与 `--cloud` 安装同一套 cloud-server、client 和 Skill,只把 cloud-server | ||
| 模式记录为 Qoder;`mearl-cloud-server` 的命令交互保持不变,`start` 和 `restart` 输出新的 | ||
| 配对指令后退出,不监听端口或保留守护进程。配对 URL 不包含 Qoder PAT。 | ||
@@ -35,6 +36,8 @@ ## 行为 | ||
| 多个已安装环境。 | ||
| - `--local`、`--cloud` 与 `--mcp` 可以组合,显式传入时只处理选中的环境。 | ||
| - `--local`、`--cloud` 与 `--mcp` 可以组合,显式传入时只处理选中的环境; | ||
| `--qoder-cloud` 选择相同的 cloud 环境并记录 Qoder 配对模式,不能与 `--cloud` 同时使用。 | ||
| - 已有环境沿用其原包管理器和全局安装前缀;首次安装沿用启动 setup 的包管理器。 | ||
| - cloud 环境统一管理 `@mearl/cloud-server` 与 `@mearl/client`:首次安装后启动 | ||
| Server,更新后仅在 Server 原本运行时重启,卸载前停止。 | ||
| - cloud 环境统一管理 `@mearl/cloud-server` 与 `@mearl/client`:WebSocket 模式首次安装后 | ||
| 启动 Server,更新后仅在 Server 原本运行时重启;Qoder 模式由相同的 `start` 命令生成 | ||
| 新配对但不起服务;卸载前均调用 `stop`。 | ||
| - local/cloud 共用一份 `@mearl/client`;组合安装只处理一次,卸载单个环境时若另一个环境仍在使用则保留。 | ||
@@ -41,0 +44,0 @@ - 从 2.5.x 更新 cloud 环境时会先卸载旧 `@mearl/cloud-client`,避免它遗留的 `mearl` bin 与统一 client 冲突。 |
| export interface QoderPairingInfo { | ||
| pairingCode: string; | ||
| createdAt: number; | ||
| connectorUrl: string; | ||
| toolInput: { | ||
| action: 'pair'; | ||
| payload: { | ||
| code: string; | ||
| }; | ||
| }; | ||
| } | ||
| export interface QoderPairingOptions { | ||
| now?: () => number; | ||
| createCode?: () => string; | ||
| } | ||
| /** Create a short-lived correlation handle without embedding Qoder credentials in the URL. */ | ||
| export declare function createQoderPairingInfo(options?: QoderPairingOptions): QoderPairingInfo; | ||
| export declare function formatQoderPairingOutput(info: QoderPairingInfo): string; |
| import { randomBytes } from 'node:crypto'; | ||
| /** Create a short-lived correlation handle without embedding Qoder credentials in the URL. */ | ||
| export function createQoderPairingInfo(options = {}) { | ||
| const pairingCode = (options.createCode ?? (() => randomBytes(24).toString('base64url')))(); | ||
| if (!/^[A-Za-z0-9_-]{16,128}$/.test(pairingCode)) { | ||
| throw new Error('Qoder pairing code must be 16-128 URL-safe characters'); | ||
| } | ||
| const createdAt = (options.now ?? Date.now)(); | ||
| if (!Number.isSafeInteger(createdAt) || | ||
| createdAt < 1_000_000_000_000 || | ||
| createdAt > 9_999_999_999_999) { | ||
| throw new Error('Qoder pairing timestamp must be a 13-digit epoch millisecond value'); | ||
| } | ||
| return { | ||
| pairingCode, | ||
| createdAt, | ||
| connectorUrl: `qoder://pair/${pairingCode}?created=${createdAt}`, | ||
| toolInput: { action: 'pair', payload: { code: pairingCode } }, | ||
| }; | ||
| } | ||
| export function formatQoderPairingOutput(info) { | ||
| const connectorCommand = `npx @mearl/cloud-connector "${info.connectorUrl}"`; | ||
| return `[Qoder] Mearl local-browser pairing is ready. | ||
| Show the user this command and ask them to run it on the computer that owns the browser: | ||
| ${connectorCommand} | ||
| Then immediately invoke the Qoder client-side custom tool named "mearl" with this input: | ||
| ${JSON.stringify(info.toolInput)} | ||
| The connector resolves QODER_PERSONAL_ACCESS_TOKEN from the local environment, ~/.mearl/.env, or supported local credential storage. Never add the PAT to the pairing URL.`; | ||
| } |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
104
2.97%78508
-2.53%24
-7.69%1663
-3.48%