oh-my-opencode 5.1.29 → 5.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +2 -0
- package/README.ko.md +2 -0
- package/README.md +2 -0
- package/README.ru.md +2 -0
- package/README.zh-cn.md +2 -0
- package/dist/cli/cli-program.d.ts +1 -20
- package/dist/cli/index.js +950 -51
- package/dist/cli/install-command-options.d.ts +20 -0
- package/dist/cli/service-auth/browser.d.ts +3 -0
- package/dist/cli/service-auth/cli-keystore.fixture.d.ts +7 -0
- package/dist/cli/service-auth/commands.d.ts +19 -0
- package/dist/cli/service-auth/device-flow.d.ts +15 -0
- package/dist/cli/service-auth/device-keys.d.ts +10 -0
- package/dist/cli/service-auth/device-name.d.ts +3 -0
- package/dist/cli/service-auth/keystore.d.ts +20 -0
- package/dist/cli/service-auth/loopback.d.ts +11 -0
- package/dist/cli/service-auth/protocol.d.ts +86 -0
- package/dist/cli/service-auth/refresh-child.fixture.d.ts +1 -0
- package/dist/cli/service-auth/session.d.ts +17 -0
- package/dist/cli/service-auth/terminal-text.d.ts +1 -0
- package/dist/cli/service-auth/test-support.d.ts +43 -0
- package/dist/cli/service-auth/tls-test-certificate.d.ts +4 -0
- package/dist/cli-node/index.js +950 -51
- package/dist/features/background-agent/task-poller.d.ts +3 -0
- package/dist/features/opencode-skill-loader/opencode-shell-skill-content.d.ts +8 -0
- package/dist/features/opencode-skill-loader/skill-content.d.ts +1 -0
- package/dist/features/team-mode/tools/lifecycle-inline-spec.d.ts +1 -0
- package/dist/hooks/keyword-detector/constants.d.ts +1 -0
- package/dist/hooks/non-interactive-env/non-interactive-env-hook.d.ts +5 -1
- package/dist/index.js +40791 -40412
- package/dist/shared/disabled-agents.d.ts +1 -0
- package/dist/shared/opencode-shell.d.ts +27 -0
- package/dist/skills/browser/SKILL.md +1 -1
- package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
- package/dist/skills/ultimate-browsing/SKILL.md +14 -16
- package/dist/skills/ulw-execute/SKILL.md +2 -2
- package/dist/skills/ulw-research/SKILL.md +17 -18
- package/dist/skills/ulw-research/scripts/cli-support.test.ts +5 -3
- package/dist/skills/ulw-research/scripts/outcome.test.ts +10 -4
- package/dist/skills/ulw-research/scripts/report-tools.test.ts +3 -2
- package/dist/skills/visual-qa/scripts/cli.test.ts +5 -3
- package/dist/tools/delegate-task/executor-types.d.ts +1 -0
- package/dist/tools/delegate-task/sync-session-creator.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +2 -0
- package/dist/tools/session-manager/sdk-storage.d.ts +15 -2
- package/dist/tools/session-manager/session-error.d.ts +7 -0
- package/dist/tools/session-manager/storage.d.ts +4 -2
- package/dist/tui.js +89 -21
- package/package.json +15 -14
- package/packages/git-bash-mcp/dist/cli.js +1 -1
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +90 -0
- package/packages/lsp-core/src/lsp/client.ts +3 -1
- package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +5 -2
- package/packages/lsp-daemon/dist/cli.js +1 -1
- package/packages/lsp-daemon/dist/client.js +1 -1
- package/packages/lsp-daemon/dist/index.js +1 -1
- package/packages/lsp-tools-mcp/dist/cli.js +1 -1
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +1 -1
- package/packages/lsp-tools-mcp/dist/mcp.js +1 -1
- package/packages/lsp-tools-mcp/dist/tools.js +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +7 -4
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +7 -4
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +3 -3
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +18 -15
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +18 -15
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/auto-update-plan.mjs +2 -0
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +2 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +2 -0
- package/packages/omo-codex/plugin/scripts/build-components.mjs +1 -0
- package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +42 -10
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +7 -4
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +3 -3
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +14 -16
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +18 -15
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +17 -18
- package/packages/omo-codex/plugin/test/materialize-shared-upstreams.test.mjs +179 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
- package/packages/omo-codex/plugin/test/windows-console-hide.test.mjs +150 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +11 -6
- package/packages/prompts-core/prompts/ultrawork/codex.md +18 -15
- package/packages/shared-skills/skills/browser/SKILL.md +1 -1
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +14 -16
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +2 -2
- package/packages/shared-skills/skills/ulw-research/SKILL.md +17 -18
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +5 -3
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +10 -4
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +3 -2
- package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +5 -3
package/dist/cli/index.js
CHANGED
|
@@ -64,7 +64,7 @@ var package_default;
|
|
|
64
64
|
var init_package = __esm(() => {
|
|
65
65
|
package_default = {
|
|
66
66
|
name: "oh-my-opencode",
|
|
67
|
-
version: "5.1.
|
|
67
|
+
version: "5.1.32",
|
|
68
68
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
69
69
|
main: "./dist/index.js",
|
|
70
70
|
types: "dist/index.d.ts",
|
|
@@ -241,7 +241,7 @@ var init_package = __esm(() => {
|
|
|
241
241
|
},
|
|
242
242
|
devDependencies: {
|
|
243
243
|
"@clack/prompts": "^1.8.1",
|
|
244
|
-
"@code-yeongyu/senpi": "2026.10.
|
|
244
|
+
"@code-yeongyu/senpi": "2026.10.12",
|
|
245
245
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
246
246
|
"@oh-my-opencode/agents-md-core": "workspace:*",
|
|
247
247
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
|
@@ -283,6 +283,7 @@ var init_package = __esm(() => {
|
|
|
283
283
|
"@types/js-yaml": "^4.0.9",
|
|
284
284
|
"@types/picomatch": "^4.0.3",
|
|
285
285
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
286
|
+
"@typescript/typescript6": "6.0.2",
|
|
286
287
|
"@xterm/addon-unicode11": "^0.9.0",
|
|
287
288
|
"@xterm/xterm": "^6.0.0",
|
|
288
289
|
"bun-types": "1.4.2",
|
|
@@ -299,18 +300,18 @@ var init_package = __esm(() => {
|
|
|
299
300
|
typescript: "^7.0.2"
|
|
300
301
|
},
|
|
301
302
|
optionalDependencies: {
|
|
302
|
-
"oh-my-opencode-darwin-arm64": "5.1.
|
|
303
|
-
"oh-my-opencode-darwin-x64": "5.1.
|
|
304
|
-
"oh-my-opencode-darwin-x64-baseline": "5.1.
|
|
305
|
-
"oh-my-opencode-linux-arm64": "5.1.
|
|
306
|
-
"oh-my-opencode-linux-arm64-musl": "5.1.
|
|
307
|
-
"oh-my-opencode-linux-x64": "5.1.
|
|
308
|
-
"oh-my-opencode-linux-x64-baseline": "5.1.
|
|
309
|
-
"oh-my-opencode-linux-x64-musl": "5.1.
|
|
310
|
-
"oh-my-opencode-linux-x64-musl-baseline": "5.1.
|
|
311
|
-
"oh-my-opencode-windows-arm64": "5.1.
|
|
312
|
-
"oh-my-opencode-windows-x64": "5.1.
|
|
313
|
-
"oh-my-opencode-windows-x64-baseline": "5.1.
|
|
303
|
+
"oh-my-opencode-darwin-arm64": "5.1.32",
|
|
304
|
+
"oh-my-opencode-darwin-x64": "5.1.32",
|
|
305
|
+
"oh-my-opencode-darwin-x64-baseline": "5.1.32",
|
|
306
|
+
"oh-my-opencode-linux-arm64": "5.1.32",
|
|
307
|
+
"oh-my-opencode-linux-arm64-musl": "5.1.32",
|
|
308
|
+
"oh-my-opencode-linux-x64": "5.1.32",
|
|
309
|
+
"oh-my-opencode-linux-x64-baseline": "5.1.32",
|
|
310
|
+
"oh-my-opencode-linux-x64-musl": "5.1.32",
|
|
311
|
+
"oh-my-opencode-linux-x64-musl-baseline": "5.1.32",
|
|
312
|
+
"oh-my-opencode-windows-arm64": "5.1.32",
|
|
313
|
+
"oh-my-opencode-windows-x64": "5.1.32",
|
|
314
|
+
"oh-my-opencode-windows-x64-baseline": "5.1.32"
|
|
314
315
|
},
|
|
315
316
|
overrides: {
|
|
316
317
|
hono: "^4.13.8",
|
|
@@ -5083,6 +5084,9 @@ function resolveStdio(options) {
|
|
|
5083
5084
|
}
|
|
5084
5085
|
return [options.stdin ?? "ignore", options.stdout ?? "pipe", options.stderr ?? "inherit"];
|
|
5085
5086
|
}
|
|
5087
|
+
function createBunSpawnOptions(options) {
|
|
5088
|
+
return { ...options, windowsHide: true };
|
|
5089
|
+
}
|
|
5086
5090
|
function createNodeSpawnOptions(options, platform = process.platform) {
|
|
5087
5091
|
const nodeOptions = {
|
|
5088
5092
|
stdio: resolveStdio(options),
|
|
@@ -5171,7 +5175,7 @@ function spawn(cmdOrOpts, opts) {
|
|
|
5171
5175
|
const { cmd, opts: options } = resolveCommand(cmdOrOpts, opts);
|
|
5172
5176
|
const bun = getBunRuntime();
|
|
5173
5177
|
if (bun)
|
|
5174
|
-
return wrapBunProcess(bun.spawn(cmd, options));
|
|
5178
|
+
return wrapBunProcess(bun.spawn(cmd, createBunSpawnOptions(options)));
|
|
5175
5179
|
const [bin, ...args] = cmd;
|
|
5176
5180
|
if (!bin)
|
|
5177
5181
|
throw new Error("spawn requires a command");
|
|
@@ -5316,7 +5320,7 @@ function isKnownNonGitBashLauncher(path) {
|
|
|
5316
5320
|
}
|
|
5317
5321
|
function whereCommand(command) {
|
|
5318
5322
|
try {
|
|
5319
|
-
return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
5323
|
+
return execFileSync("where", [command], { encoding: "utf8", windowsHide: true }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
5320
5324
|
} catch (error) {
|
|
5321
5325
|
if (error instanceof Error)
|
|
5322
5326
|
return [];
|
|
@@ -5759,7 +5763,8 @@ function defaultVersionProbe(binaryPath) {
|
|
|
5759
5763
|
return String(execFileSync2(binaryPath, ["--version"], {
|
|
5760
5764
|
encoding: "utf8",
|
|
5761
5765
|
stdio: ["ignore", "pipe", "ignore"],
|
|
5762
|
-
timeout: SG_VERSION_PROBE_TIMEOUT_MS
|
|
5766
|
+
timeout: SG_VERSION_PROBE_TIMEOUT_MS,
|
|
5767
|
+
windowsHide: true
|
|
5763
5768
|
}));
|
|
5764
5769
|
}
|
|
5765
5770
|
function probePasses(binaryPath, deps) {
|
|
@@ -7462,10 +7467,11 @@ var init_category_model_requirements = __esm(() => {
|
|
|
7462
7467
|
},
|
|
7463
7468
|
{ providers: ["xiaomi", "opencode-go"], model: "mimo-v2.6-pro", variant: "max" },
|
|
7464
7469
|
{ providers: ["xai", "github-copilot", "opencode-go"], model: "grok-4.7", variant: "xhigh" },
|
|
7470
|
+
{ providers: ["openai", "chatgpt-subscription"], model: "gpt-6.1-sol", variant: "medium" },
|
|
7465
7471
|
{
|
|
7466
7472
|
providers: ["openai", "chatgpt-subscription", "github-copilot", "opencode"],
|
|
7467
|
-
model: "gpt-5.6-
|
|
7468
|
-
variant: "
|
|
7473
|
+
model: "gpt-5.6-sol",
|
|
7474
|
+
variant: "medium"
|
|
7469
7475
|
},
|
|
7470
7476
|
{
|
|
7471
7477
|
providers: ["anthropic", "anthropic-api", "github-copilot", "opencode"],
|
|
@@ -74743,7 +74749,8 @@ var init_memory = __esm(() => {
|
|
|
74743
74749
|
}).strict();
|
|
74744
74750
|
OmoMemoryFactsSchema = object({
|
|
74745
74751
|
enabled: boolean2().default(true),
|
|
74746
|
-
debounce_settles: number2().int().min(1).default(4)
|
|
74752
|
+
debounce_settles: number2().int().min(1).default(4),
|
|
74753
|
+
model: string2().min(1).optional()
|
|
74747
74754
|
}).strict();
|
|
74748
74755
|
OmoMemoryDreamSchema = object({
|
|
74749
74756
|
enabled: boolean2().default(true),
|
|
@@ -74805,7 +74812,8 @@ var init_memory = __esm(() => {
|
|
|
74805
74812
|
}).strict();
|
|
74806
74813
|
OmoMemoryFactsLayerSchema = object({
|
|
74807
74814
|
enabled: boolean2().optional(),
|
|
74808
|
-
debounce_settles: number2().int().min(1).optional()
|
|
74815
|
+
debounce_settles: number2().int().min(1).optional(),
|
|
74816
|
+
model: string2().min(1).optional()
|
|
74809
74817
|
}).strict();
|
|
74810
74818
|
OmoMemoryDreamLayerSchema = object({
|
|
74811
74819
|
enabled: boolean2().optional(),
|
|
@@ -89894,7 +89902,7 @@ var package_default2;
|
|
|
89894
89902
|
var init_package2 = __esm(() => {
|
|
89895
89903
|
package_default2 = {
|
|
89896
89904
|
name: "@oh-my-opencode/omo-codex",
|
|
89897
|
-
version: "5.1.
|
|
89905
|
+
version: "5.1.32",
|
|
89898
89906
|
type: "module",
|
|
89899
89907
|
private: true,
|
|
89900
89908
|
description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
|
|
@@ -110304,6 +110312,921 @@ function createMcpOAuthCommand() {
|
|
|
110304
110312
|
return mcp;
|
|
110305
110313
|
}
|
|
110306
110314
|
|
|
110315
|
+
// packages/omo-opencode/src/cli/service-auth/browser.ts
|
|
110316
|
+
import { execFile as execFile5 } from "child_process";
|
|
110317
|
+
var execute = (file, args, env) => new Promise((resolve) => {
|
|
110318
|
+
execFile5(file, [...args], { env, timeout: 1e4, windowsHide: true }, (error) => resolve(error === null));
|
|
110319
|
+
});
|
|
110320
|
+
async function openSignInBrowser(url, executeBrowser = execute, platform = process.platform, environment = process.env) {
|
|
110321
|
+
const env = {};
|
|
110322
|
+
for (const key of ["PATH", "Path", "HOME", "USERPROFILE", "SystemRoot", "WINDIR", "DISPLAY", "WAYLAND_DISPLAY", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS"]) {
|
|
110323
|
+
const value = environment[key];
|
|
110324
|
+
if (value !== undefined)
|
|
110325
|
+
env[key] = value;
|
|
110326
|
+
}
|
|
110327
|
+
if (platform === "darwin")
|
|
110328
|
+
return executeBrowser("/usr/bin/open", [url], env);
|
|
110329
|
+
if (platform === "win32")
|
|
110330
|
+
return executeBrowser("rundll32.exe", ["url.dll,FileProtocolHandler", url], env);
|
|
110331
|
+
if (platform === "linux" && (env.DISPLAY || env.WAYLAND_DISPLAY))
|
|
110332
|
+
return executeBrowser("xdg-open", [url], env);
|
|
110333
|
+
return false;
|
|
110334
|
+
}
|
|
110335
|
+
|
|
110336
|
+
// packages/omo-opencode/src/cli/service-auth/device-name.ts
|
|
110337
|
+
import { hostname } from "os";
|
|
110338
|
+
|
|
110339
|
+
// packages/omo-opencode/src/cli/service-auth/protocol.ts
|
|
110340
|
+
init_zod();
|
|
110341
|
+
|
|
110342
|
+
// packages/omo-opencode/src/cli/service-auth/terminal-text.ts
|
|
110343
|
+
import { stripVTControlCharacters as stripVTControlCharacters4 } from "util";
|
|
110344
|
+
function terminalText(value) {
|
|
110345
|
+
return stripVTControlCharacters4(value).replace(/[\p{C}\p{Zl}\p{Zp}]/gu, "");
|
|
110346
|
+
}
|
|
110347
|
+
|
|
110348
|
+
// packages/omo-opencode/src/cli/service-auth/protocol.ts
|
|
110349
|
+
class SignInError extends Error {
|
|
110350
|
+
retryable;
|
|
110351
|
+
name = "SignInError";
|
|
110352
|
+
constructor(message, retryable = false) {
|
|
110353
|
+
super(message);
|
|
110354
|
+
this.retryable = retryable;
|
|
110355
|
+
}
|
|
110356
|
+
}
|
|
110357
|
+
|
|
110358
|
+
class RequestFailure extends SignInError {
|
|
110359
|
+
notSent;
|
|
110360
|
+
constructor(notSent) {
|
|
110361
|
+
super("Unable to complete the service request. Check the connection and retry.", true);
|
|
110362
|
+
this.notSent = notSent;
|
|
110363
|
+
}
|
|
110364
|
+
}
|
|
110365
|
+
var platformSchema = _enum(["macos", "linux", "windows"]);
|
|
110366
|
+
var nonempty = string2().min(1);
|
|
110367
|
+
var timestamp = string2().datetime({ offset: true });
|
|
110368
|
+
var tokenSchema = object({
|
|
110369
|
+
accessToken: nonempty,
|
|
110370
|
+
accessTokenExpiresAt: timestamp,
|
|
110371
|
+
refreshToken: nonempty
|
|
110372
|
+
});
|
|
110373
|
+
var grantSchema = tokenSchema.extend({
|
|
110374
|
+
device: object({ id: nonempty, name: nonempty }),
|
|
110375
|
+
offlineGrant: nonempty.optional()
|
|
110376
|
+
});
|
|
110377
|
+
var credentialSchema = grantSchema.extend({
|
|
110378
|
+
signingPrivateKey: nonempty,
|
|
110379
|
+
sealingPrivateKey: nonempty,
|
|
110380
|
+
refreshState: literal("uncertain").optional()
|
|
110381
|
+
});
|
|
110382
|
+
var deviceCodeSchema = object({
|
|
110383
|
+
deviceCode: string2().regex(/^dc\.[A-Za-z0-9_-]{43}$/),
|
|
110384
|
+
userCode: string2().regex(/^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$/),
|
|
110385
|
+
matchingCode: string2().regex(/^\d{3} \d{3}$/),
|
|
110386
|
+
verificationUri: string2().transform(terminalText).pipe(string2().url()),
|
|
110387
|
+
expiresAt: timestamp,
|
|
110388
|
+
intervalSeconds: number2().int().min(1).max(60)
|
|
110389
|
+
});
|
|
110390
|
+
var envelopeSchema = object({
|
|
110391
|
+
error: object({
|
|
110392
|
+
code: nonempty,
|
|
110393
|
+
message: string2(),
|
|
110394
|
+
retryable: boolean2(),
|
|
110395
|
+
requestId: string2(),
|
|
110396
|
+
detail: unknown().optional()
|
|
110397
|
+
})
|
|
110398
|
+
});
|
|
110399
|
+
var limitSchema = object({
|
|
110400
|
+
reason: literal("device_limit"),
|
|
110401
|
+
managementToken: nonempty,
|
|
110402
|
+
used: number2().int().nonnegative(),
|
|
110403
|
+
limit: number2().int().nonnegative()
|
|
110404
|
+
});
|
|
110405
|
+
var deviceListSchema = object({
|
|
110406
|
+
devices: array(object({ id: nonempty, name: nonempty, revokedAt: timestamp.nullable() }))
|
|
110407
|
+
});
|
|
110408
|
+
|
|
110409
|
+
class ApiRefusal extends SignInError {
|
|
110410
|
+
status;
|
|
110411
|
+
code;
|
|
110412
|
+
#detail;
|
|
110413
|
+
#retryAfter;
|
|
110414
|
+
constructor(code, status, detail, retryAfter = null) {
|
|
110415
|
+
super("The service refused this request. Sign in again or check your account.", status === 429 || status >= 500);
|
|
110416
|
+
this.status = status;
|
|
110417
|
+
this.code = [
|
|
110418
|
+
"unauthorized",
|
|
110419
|
+
"account_deleted",
|
|
110420
|
+
"reauth_required",
|
|
110421
|
+
"invalid_grant",
|
|
110422
|
+
"entitlement_required",
|
|
110423
|
+
"authorization_pending",
|
|
110424
|
+
"slow_down",
|
|
110425
|
+
"access_denied",
|
|
110426
|
+
"expired_token",
|
|
110427
|
+
"rate_limited",
|
|
110428
|
+
"internal",
|
|
110429
|
+
"unavailable",
|
|
110430
|
+
"forbidden",
|
|
110431
|
+
"invalid_request",
|
|
110432
|
+
"not_found"
|
|
110433
|
+
].includes(code) ? code : "unknown";
|
|
110434
|
+
this.#detail = detail;
|
|
110435
|
+
this.#retryAfter = retryAfter;
|
|
110436
|
+
}
|
|
110437
|
+
get detail() {
|
|
110438
|
+
return this.#detail;
|
|
110439
|
+
}
|
|
110440
|
+
retryDelay(now, fallback) {
|
|
110441
|
+
const raw = this.#retryAfter;
|
|
110442
|
+
const delay = raw === null ? Number.NaN : /^\d+$/.test(raw) ? Number(raw) * 1000 : Date.parse(raw) - now;
|
|
110443
|
+
return Math.min(60000, Math.max(fallback, Number.isFinite(delay) ? delay : fallback));
|
|
110444
|
+
}
|
|
110445
|
+
}
|
|
110446
|
+
function parseReply(schema, value) {
|
|
110447
|
+
const result = schema.safeParse(value);
|
|
110448
|
+
if (!result.success)
|
|
110449
|
+
throw new SignInError("The service returned an invalid response.");
|
|
110450
|
+
return result.data;
|
|
110451
|
+
}
|
|
110452
|
+
function serviceOrigin(value) {
|
|
110453
|
+
let url;
|
|
110454
|
+
try {
|
|
110455
|
+
url = new URL(value);
|
|
110456
|
+
} catch {
|
|
110457
|
+
throw new SignInError("Invalid service URL.");
|
|
110458
|
+
}
|
|
110459
|
+
const loopback = url.protocol === "http:" && url.hostname === "127.0.0.1" && url.port !== "";
|
|
110460
|
+
if (url.protocol !== "https:" && !loopback || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
110461
|
+
throw new SignInError("Service URLs must be HTTPS origins, or http://127.0.0.1:<port> for local testing.");
|
|
110462
|
+
}
|
|
110463
|
+
return url.origin;
|
|
110464
|
+
}
|
|
110465
|
+
async function requestApi(api, path, init, signal) {
|
|
110466
|
+
let response;
|
|
110467
|
+
let body;
|
|
110468
|
+
try {
|
|
110469
|
+
response = await fetch(`${api}${path}`, {
|
|
110470
|
+
...init,
|
|
110471
|
+
redirect: "error",
|
|
110472
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(30000)]) : AbortSignal.timeout(30000)
|
|
110473
|
+
});
|
|
110474
|
+
} catch (error) {
|
|
110475
|
+
const cause = error instanceof Error && error.cause instanceof Error ? error.cause : error;
|
|
110476
|
+
const code = cause instanceof Error ? Reflect.get(cause, "code") : undefined;
|
|
110477
|
+
throw new RequestFailure(typeof code === "string" && [
|
|
110478
|
+
"ENOTFOUND",
|
|
110479
|
+
"ECONNREFUSED",
|
|
110480
|
+
"ENETUNREACH",
|
|
110481
|
+
"EHOSTUNREACH",
|
|
110482
|
+
"EAI_AGAIN",
|
|
110483
|
+
"ConnectionRefused",
|
|
110484
|
+
"EADDRNOTAVAIL",
|
|
110485
|
+
"ENETDOWN",
|
|
110486
|
+
"EHOSTDOWN",
|
|
110487
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
110488
|
+
"ERR_TLS_HANDSHAKE_TIMEOUT",
|
|
110489
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
110490
|
+
"CERT_HAS_EXPIRED",
|
|
110491
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
110492
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
110493
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
110494
|
+
"CERT_NOT_YET_VALID",
|
|
110495
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
110496
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
110497
|
+
"CERT_SIGNATURE_FAILURE",
|
|
110498
|
+
"CERT_UNTRUSTED",
|
|
110499
|
+
"CERT_REJECTED"
|
|
110500
|
+
].includes(code));
|
|
110501
|
+
}
|
|
110502
|
+
if (response.status === 204)
|
|
110503
|
+
return null;
|
|
110504
|
+
try {
|
|
110505
|
+
body = await response.json();
|
|
110506
|
+
} catch {
|
|
110507
|
+
throw new RequestFailure(false);
|
|
110508
|
+
}
|
|
110509
|
+
if (response.ok)
|
|
110510
|
+
return body;
|
|
110511
|
+
const envelope = envelopeSchema.safeParse(body);
|
|
110512
|
+
if (!envelope.success)
|
|
110513
|
+
throw new SignInError("The service returned an invalid error response.");
|
|
110514
|
+
const error = envelope.data.error;
|
|
110515
|
+
const detail = limitSchema.safeParse(error.detail);
|
|
110516
|
+
throw new ApiRefusal(error.code, response.status, error.code === "entitlement_required" && detail.success ? detail.data : undefined, response.headers.get("retry-after"));
|
|
110517
|
+
}
|
|
110518
|
+
var jsonRequest = (body) => ({
|
|
110519
|
+
method: "POST",
|
|
110520
|
+
headers: { "content-type": "application/json" },
|
|
110521
|
+
body: JSON.stringify(body)
|
|
110522
|
+
});
|
|
110523
|
+
|
|
110524
|
+
// packages/omo-opencode/src/cli/service-auth/device-name.ts
|
|
110525
|
+
function deviceName(host = hostname()) {
|
|
110526
|
+
const clean = `omo CLI on ${host}`.replace(/[\p{Cc}\p{Zl}\p{Zp}\p{Cf}\u3164\u115F\u1160\uFFA0]/gu, "").trim();
|
|
110527
|
+
return [...clean].slice(0, 64).join("").trim();
|
|
110528
|
+
}
|
|
110529
|
+
function devicePlatform() {
|
|
110530
|
+
switch (process.platform) {
|
|
110531
|
+
case "darwin":
|
|
110532
|
+
return "macos";
|
|
110533
|
+
case "linux":
|
|
110534
|
+
return "linux";
|
|
110535
|
+
case "win32":
|
|
110536
|
+
return "windows";
|
|
110537
|
+
default:
|
|
110538
|
+
throw new SignInError("This platform is not supported for OmO sign-in.");
|
|
110539
|
+
}
|
|
110540
|
+
}
|
|
110541
|
+
|
|
110542
|
+
// packages/omo-opencode/src/cli/service-auth/device-flow.ts
|
|
110543
|
+
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
110544
|
+
|
|
110545
|
+
// packages/omo-opencode/src/cli/service-auth/device-keys.ts
|
|
110546
|
+
import { generateKeyPairSync } from "crypto";
|
|
110547
|
+
function generateDeviceKeys() {
|
|
110548
|
+
const signing = generateKeyPairSync("ed25519");
|
|
110549
|
+
const sealing = generateKeyPairSync("x25519");
|
|
110550
|
+
const signingKey = signing.publicKey.export({ format: "jwk" }).x;
|
|
110551
|
+
const sealingKey = sealing.publicKey.export({ format: "jwk" }).x;
|
|
110552
|
+
if (!signingKey || !sealingKey)
|
|
110553
|
+
throw new SignInError("Unable to generate device keys.");
|
|
110554
|
+
return {
|
|
110555
|
+
public: { signingKey, sealingKey },
|
|
110556
|
+
private: {
|
|
110557
|
+
signingPrivateKey: signing.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64"),
|
|
110558
|
+
sealingPrivateKey: sealing.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64")
|
|
110559
|
+
}
|
|
110560
|
+
};
|
|
110561
|
+
}
|
|
110562
|
+
|
|
110563
|
+
// packages/omo-opencode/src/cli/service-auth/device-flow.ts
|
|
110564
|
+
async function loginDevice(session, options) {
|
|
110565
|
+
await session.ready();
|
|
110566
|
+
const keys = generateDeviceKeys();
|
|
110567
|
+
const minted = parseReply(deviceCodeSchema, await session.request("/v1/device/code", jsonRequest({ client: { name: options.name, platform: options.platform } })));
|
|
110568
|
+
const uri = new URL(minted.verificationUri);
|
|
110569
|
+
if (uri.protocol !== "https:" && !(uri.protocol === "http:" && uri.hostname === "127.0.0.1" && uri.port !== "")) {
|
|
110570
|
+
throw new SignInError("The service returned an unsafe verification URL.");
|
|
110571
|
+
}
|
|
110572
|
+
options.show({ verificationUri: minted.verificationUri, userCode: minted.userCode, matchingCode: minted.matchingCode });
|
|
110573
|
+
const now = options.now ?? Date.now;
|
|
110574
|
+
const sleep = options.sleep ?? ((ms) => setTimeout2(ms, undefined, { signal: session.signal }));
|
|
110575
|
+
const expiresAt = Date.parse(minted.expiresAt);
|
|
110576
|
+
let interval = minted.intervalSeconds * 1000;
|
|
110577
|
+
while (now() < expiresAt) {
|
|
110578
|
+
await sleep(Math.min(interval, expiresAt - now()));
|
|
110579
|
+
session.signal?.throwIfAborted();
|
|
110580
|
+
if (now() >= expiresAt)
|
|
110581
|
+
break;
|
|
110582
|
+
try {
|
|
110583
|
+
const grant = parseReply(grantSchema, await session.request("/v1/device/token", jsonRequest({
|
|
110584
|
+
deviceCode: minted.deviceCode,
|
|
110585
|
+
...keys.public
|
|
110586
|
+
})));
|
|
110587
|
+
await session.save({ ...grant, ...keys.private });
|
|
110588
|
+
return;
|
|
110589
|
+
} catch (error) {
|
|
110590
|
+
if (!(error instanceof ApiRefusal))
|
|
110591
|
+
throw error;
|
|
110592
|
+
if (error.status === 400 && error.code === "authorization_pending")
|
|
110593
|
+
continue;
|
|
110594
|
+
if (error.status === 400 && error.code === "slow_down") {
|
|
110595
|
+
interval += 5000;
|
|
110596
|
+
continue;
|
|
110597
|
+
}
|
|
110598
|
+
if (error.status === 429) {
|
|
110599
|
+
interval = error.retryDelay(now(), Math.min(60000, interval + 5000));
|
|
110600
|
+
continue;
|
|
110601
|
+
}
|
|
110602
|
+
if (error.detail !== undefined) {
|
|
110603
|
+
if (await session.removeForLimit(error, options.chooseDevice))
|
|
110604
|
+
continue;
|
|
110605
|
+
throw new SignInError("Sign-in cancelled. No device was removed.");
|
|
110606
|
+
}
|
|
110607
|
+
if (["access_denied", "expired_token", "invalid_grant"].includes(error.code)) {
|
|
110608
|
+
throw new SignInError("Device sign-in was denied or expired. Run omo login again for a new code.");
|
|
110609
|
+
}
|
|
110610
|
+
throw error;
|
|
110611
|
+
}
|
|
110612
|
+
}
|
|
110613
|
+
throw new SignInError("Device sign-in expired. Run omo login again for a new code.");
|
|
110614
|
+
}
|
|
110615
|
+
|
|
110616
|
+
// packages/omo-opencode/src/cli/service-auth/keystore.ts
|
|
110617
|
+
import { createHash as createHash9 } from "crypto";
|
|
110618
|
+
function credentialId(api) {
|
|
110619
|
+
return createHash9("sha256").update(serviceOrigin(api)).digest("hex");
|
|
110620
|
+
}
|
|
110621
|
+
function createCredentialStore(api, secrets = typeof Bun === "undefined" ? null : Bun.secrets, platform = process.platform) {
|
|
110622
|
+
if (!secrets)
|
|
110623
|
+
throw new SignInError("OmO sign-in needs Bun 1.4 or newer with OS credential storage. No file fallback is available.");
|
|
110624
|
+
const platformName = platform === "darwin" ? "macOS Keychain" : platform === "linux" ? "Linux Secret Service (libsecret and an unlocked keyring)" : platform === "win32" ? "Windows Credential Manager" : null;
|
|
110625
|
+
if (platformName === null)
|
|
110626
|
+
throw new SignInError("This platform has no supported OS credential store.");
|
|
110627
|
+
const key = { service: "omo-cli", name: credentialId(api) };
|
|
110628
|
+
const unavailable = () => new SignInError(`Cannot access ${platformName}. Unlock or enable the OS credential store and try again. Credentials are never saved to a file.`);
|
|
110629
|
+
return {
|
|
110630
|
+
async read() {
|
|
110631
|
+
let value;
|
|
110632
|
+
try {
|
|
110633
|
+
value = await secrets.get(key);
|
|
110634
|
+
} catch {
|
|
110635
|
+
throw unavailable();
|
|
110636
|
+
}
|
|
110637
|
+
if (value === null)
|
|
110638
|
+
return null;
|
|
110639
|
+
try {
|
|
110640
|
+
return credentialSchema.parse(JSON.parse(value));
|
|
110641
|
+
} catch {
|
|
110642
|
+
throw new SignInError("Stored sign-in data is invalid. Run omo logout, then omo login.");
|
|
110643
|
+
}
|
|
110644
|
+
},
|
|
110645
|
+
async write(value) {
|
|
110646
|
+
try {
|
|
110647
|
+
await secrets.set({ ...key, value: JSON.stringify(credentialSchema.parse(value)) });
|
|
110648
|
+
} catch {
|
|
110649
|
+
throw unavailable();
|
|
110650
|
+
}
|
|
110651
|
+
},
|
|
110652
|
+
async clear() {
|
|
110653
|
+
try {
|
|
110654
|
+
await secrets.delete(key);
|
|
110655
|
+
} catch {
|
|
110656
|
+
throw unavailable();
|
|
110657
|
+
}
|
|
110658
|
+
}
|
|
110659
|
+
};
|
|
110660
|
+
}
|
|
110661
|
+
|
|
110662
|
+
// packages/omo-opencode/src/cli/service-auth/loopback.ts
|
|
110663
|
+
import { createHash as createHash10, randomBytes as randomBytes3, timingSafeEqual } from "crypto";
|
|
110664
|
+
import { createServer as createServer3 } from "http";
|
|
110665
|
+
class BrowserUnavailable extends SignInError {
|
|
110666
|
+
}
|
|
110667
|
+
async function authorize(options) {
|
|
110668
|
+
const verifier = randomBytes3(32).toString("base64url");
|
|
110669
|
+
const state = randomBytes3(32).toString("base64url");
|
|
110670
|
+
const answer = Promise.withResolvers();
|
|
110671
|
+
const abort = () => answer.reject(new SignInError("Browser sign-in cancelled or timed out."));
|
|
110672
|
+
let accepted = false;
|
|
110673
|
+
let origin = "";
|
|
110674
|
+
const server = createServer3((request, response) => {
|
|
110675
|
+
response.setHeader("cache-control", "no-store");
|
|
110676
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
110677
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
110678
|
+
const url = new URL(request.url ?? "/", origin);
|
|
110679
|
+
if (request.method !== "GET" || request.headers.host !== new URL(origin).host || url.origin !== origin || url.pathname !== "/callback" || request.headers.forwarded || request.headers["x-forwarded-for"]) {
|
|
110680
|
+
response.writeHead(404).end("Not found");
|
|
110681
|
+
return;
|
|
110682
|
+
}
|
|
110683
|
+
const code = url.searchParams.get("code");
|
|
110684
|
+
const suppliedState = Buffer.from(url.searchParams.get("state") ?? "");
|
|
110685
|
+
const expectedState = Buffer.from(state);
|
|
110686
|
+
if (accepted || url.searchParams.getAll("state").length !== 1 || suppliedState.length !== expectedState.length || !timingSafeEqual(suppliedState, expectedState) || url.searchParams.getAll("code").length !== 1 || !code) {
|
|
110687
|
+
response.writeHead(400).end("Invalid sign-in response");
|
|
110688
|
+
return;
|
|
110689
|
+
}
|
|
110690
|
+
accepted = true;
|
|
110691
|
+
response.writeHead(200).end("Return to the terminal to finish signing in.");
|
|
110692
|
+
answer.resolve(code);
|
|
110693
|
+
});
|
|
110694
|
+
try {
|
|
110695
|
+
await new Promise((resolve, reject) => {
|
|
110696
|
+
server.once("error", reject);
|
|
110697
|
+
server.listen(0, "127.0.0.1", () => {
|
|
110698
|
+
server.off("error", reject);
|
|
110699
|
+
resolve();
|
|
110700
|
+
});
|
|
110701
|
+
});
|
|
110702
|
+
const address = server.address();
|
|
110703
|
+
if (!address || typeof address === "string")
|
|
110704
|
+
throw new SignInError("Unable to start the sign-in callback.");
|
|
110705
|
+
origin = `http://127.0.0.1:${address.port}`;
|
|
110706
|
+
const url = new URL(serviceOrigin(options.accounts));
|
|
110707
|
+
url.search = new URLSearchParams({
|
|
110708
|
+
redirect_uri: `${origin}/callback`,
|
|
110709
|
+
code_challenge: createHash10("sha256").update(verifier).digest("base64url"),
|
|
110710
|
+
code_challenge_method: "S256",
|
|
110711
|
+
state
|
|
110712
|
+
}).toString();
|
|
110713
|
+
const timer = setTimeout(abort, 5 * 60000);
|
|
110714
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
110715
|
+
try {
|
|
110716
|
+
options.signal?.throwIfAborted();
|
|
110717
|
+
const openerFailed = () => {
|
|
110718
|
+
if (!accepted)
|
|
110719
|
+
throw new BrowserUnavailable("No browser is available; use device sign-in.");
|
|
110720
|
+
};
|
|
110721
|
+
const [code] = await Promise.all([
|
|
110722
|
+
answer.promise,
|
|
110723
|
+
options.open(url.href).then((opened) => {
|
|
110724
|
+
if (!opened)
|
|
110725
|
+
openerFailed();
|
|
110726
|
+
}, openerFailed)
|
|
110727
|
+
]);
|
|
110728
|
+
return { code, codeVerifier: verifier };
|
|
110729
|
+
} finally {
|
|
110730
|
+
clearTimeout(timer);
|
|
110731
|
+
options.signal?.removeEventListener("abort", abort);
|
|
110732
|
+
}
|
|
110733
|
+
} finally {
|
|
110734
|
+
server.closeAllConnections();
|
|
110735
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
110736
|
+
}
|
|
110737
|
+
}
|
|
110738
|
+
async function loginLoopback(session, options) {
|
|
110739
|
+
await session.ready();
|
|
110740
|
+
const keys = generateDeviceKeys();
|
|
110741
|
+
for (;; ) {
|
|
110742
|
+
const authorization = await authorize({ accounts: options.accounts, open: options.open, signal: session.signal });
|
|
110743
|
+
try {
|
|
110744
|
+
const grant = parseReply(grantSchema, await session.request("/v1/session/exchange", jsonRequest({
|
|
110745
|
+
...authorization,
|
|
110746
|
+
kind: "cli",
|
|
110747
|
+
deviceName: options.name,
|
|
110748
|
+
platform: options.platform,
|
|
110749
|
+
...keys.public
|
|
110750
|
+
})));
|
|
110751
|
+
await session.save({ ...grant, ...keys.private });
|
|
110752
|
+
return;
|
|
110753
|
+
} catch (error) {
|
|
110754
|
+
if (await session.removeForLimit(error, options.chooseDevice))
|
|
110755
|
+
continue;
|
|
110756
|
+
throw new SignInError("Sign-in cancelled. No device was removed.");
|
|
110757
|
+
}
|
|
110758
|
+
}
|
|
110759
|
+
}
|
|
110760
|
+
|
|
110761
|
+
// packages/omo-opencode/src/cli/service-auth/session.ts
|
|
110762
|
+
import { mkdir as mkdir10 } from "fs/promises";
|
|
110763
|
+
import { homedir as homedir27 } from "os";
|
|
110764
|
+
import { join as join97 } from "path";
|
|
110765
|
+
|
|
110766
|
+
// packages/team-core/src/team-state-store/lock-publish.ts
|
|
110767
|
+
import { access as access3, open as open2, unlink as unlink2 } from "fs/promises";
|
|
110768
|
+
import { dirname as dirname38 } from "path";
|
|
110769
|
+
|
|
110770
|
+
// packages/team-core/src/team-state-store/lock-record.ts
|
|
110771
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
110772
|
+
import { readFile as readFile33, stat as stat8 } from "fs/promises";
|
|
110773
|
+
var lockOwnerInstanceId = randomUUID4();
|
|
110774
|
+
var heldLockRecords = new Set;
|
|
110775
|
+
function buildLockRecord(ownerTag) {
|
|
110776
|
+
return `${ownerTag} ${randomUUID4()}
|
|
110777
|
+
${process.pid}
|
|
110778
|
+
${Date.now()}
|
|
110779
|
+
${lockOwnerInstanceId}
|
|
110780
|
+
`;
|
|
110781
|
+
}
|
|
110782
|
+
function markLockRecordHeld(content) {
|
|
110783
|
+
heldLockRecords.add(content);
|
|
110784
|
+
}
|
|
110785
|
+
function markLockRecordReleased(content) {
|
|
110786
|
+
heldLockRecords.delete(content);
|
|
110787
|
+
}
|
|
110788
|
+
function errorCode(error) {
|
|
110789
|
+
if (!(error instanceof Error) || !("code" in error))
|
|
110790
|
+
return null;
|
|
110791
|
+
return typeof error.code === "string" ? error.code : null;
|
|
110792
|
+
}
|
|
110793
|
+
function isPathAbsenceError(error) {
|
|
110794
|
+
const code = errorCode(error);
|
|
110795
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
110796
|
+
}
|
|
110797
|
+
function parseLockOwner(content) {
|
|
110798
|
+
const lines = content.split(/\r?\n/).filter((line) => line.length > 0);
|
|
110799
|
+
if (lines.length !== 3 && lines.length !== 4)
|
|
110800
|
+
return null;
|
|
110801
|
+
const ownerPid = Number.parseInt(lines[1] ?? "", 10);
|
|
110802
|
+
const acquiredAtEpochMs = Number.parseInt(lines[2] ?? "", 10);
|
|
110803
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 0)
|
|
110804
|
+
return null;
|
|
110805
|
+
if (!Number.isInteger(acquiredAtEpochMs) || acquiredAtEpochMs <= 0)
|
|
110806
|
+
return null;
|
|
110807
|
+
return {
|
|
110808
|
+
ownerPid,
|
|
110809
|
+
acquiredAtEpochMs,
|
|
110810
|
+
instanceId: lines.length === 4 ? lines[3] ?? null : null
|
|
110811
|
+
};
|
|
110812
|
+
}
|
|
110813
|
+
function isPidAlive(pid) {
|
|
110814
|
+
try {
|
|
110815
|
+
process.kill(pid, 0);
|
|
110816
|
+
return true;
|
|
110817
|
+
} catch (error) {
|
|
110818
|
+
if (!(error instanceof Error)) {
|
|
110819
|
+
throw error;
|
|
110820
|
+
}
|
|
110821
|
+
return false;
|
|
110822
|
+
}
|
|
110823
|
+
}
|
|
110824
|
+
async function readLockRecord(lockPath) {
|
|
110825
|
+
try {
|
|
110826
|
+
const content = await readFile33(lockPath, "utf8");
|
|
110827
|
+
const { mtimeMs } = await stat8(lockPath);
|
|
110828
|
+
return { content, modifiedAtEpochMs: mtimeMs };
|
|
110829
|
+
} catch (error) {
|
|
110830
|
+
if (!(error instanceof Error))
|
|
110831
|
+
throw error;
|
|
110832
|
+
return null;
|
|
110833
|
+
}
|
|
110834
|
+
}
|
|
110835
|
+
function isLockRecordStale(record, staleAfterMs) {
|
|
110836
|
+
const owner = parseLockOwner(record.content);
|
|
110837
|
+
if (owner === null)
|
|
110838
|
+
return Date.now() - record.modifiedAtEpochMs > staleAfterMs;
|
|
110839
|
+
if (owner.instanceId !== null && owner.ownerPid === process.pid) {
|
|
110840
|
+
return owner.instanceId !== lockOwnerInstanceId || !heldLockRecords.has(record.content);
|
|
110841
|
+
}
|
|
110842
|
+
if (isPidAlive(owner.ownerPid))
|
|
110843
|
+
return false;
|
|
110844
|
+
if (owner.instanceId !== null)
|
|
110845
|
+
return true;
|
|
110846
|
+
return Date.now() - owner.acquiredAtEpochMs > staleAfterMs;
|
|
110847
|
+
}
|
|
110848
|
+
|
|
110849
|
+
// packages/team-core/src/team-state-store/lock-publish.ts
|
|
110850
|
+
async function pathMayExist(path, deps = {}) {
|
|
110851
|
+
const accessPath = deps.access ?? access3;
|
|
110852
|
+
try {
|
|
110853
|
+
await accessPath(path);
|
|
110854
|
+
return true;
|
|
110855
|
+
} catch (error) {
|
|
110856
|
+
if (!(error instanceof Error))
|
|
110857
|
+
throw error;
|
|
110858
|
+
return !isPathAbsenceError(error);
|
|
110859
|
+
}
|
|
110860
|
+
}
|
|
110861
|
+
async function assertRetryableLockOpenError(lockPath, error, deps) {
|
|
110862
|
+
const code = errorCode(error);
|
|
110863
|
+
if (code === "EEXIST")
|
|
110864
|
+
return;
|
|
110865
|
+
if (code === "EPERM") {
|
|
110866
|
+
if (await pathMayExist(lockPath, deps))
|
|
110867
|
+
return;
|
|
110868
|
+
if ((deps?.platform ?? process.platform) === "win32" && await pathMayExist(dirname38(lockPath), deps))
|
|
110869
|
+
return;
|
|
110870
|
+
}
|
|
110871
|
+
throw error;
|
|
110872
|
+
}
|
|
110873
|
+
async function publishLockRecord(lockPath, record) {
|
|
110874
|
+
markLockRecordHeld(record);
|
|
110875
|
+
let fileHandle;
|
|
110876
|
+
try {
|
|
110877
|
+
fileHandle = await open2(lockPath, "wx");
|
|
110878
|
+
} catch (error) {
|
|
110879
|
+
markLockRecordReleased(record);
|
|
110880
|
+
await assertRetryableLockOpenError(lockPath, error);
|
|
110881
|
+
return false;
|
|
110882
|
+
}
|
|
110883
|
+
try {
|
|
110884
|
+
await fileHandle.writeFile(record);
|
|
110885
|
+
} catch (error) {
|
|
110886
|
+
markLockRecordReleased(record);
|
|
110887
|
+
await fileHandle.close();
|
|
110888
|
+
await unlink2(lockPath).catch(() => {
|
|
110889
|
+
return;
|
|
110890
|
+
});
|
|
110891
|
+
throw error;
|
|
110892
|
+
}
|
|
110893
|
+
await fileHandle.close();
|
|
110894
|
+
return true;
|
|
110895
|
+
}
|
|
110896
|
+
|
|
110897
|
+
// packages/team-core/src/team-state-store/lock-reclaim.ts
|
|
110898
|
+
import { createHash as createHash11 } from "crypto";
|
|
110899
|
+
import { unlink as unlink3 } from "fs/promises";
|
|
110900
|
+
var RECLAIM_UNLINK_ATTEMPTS = 3;
|
|
110901
|
+
var RECLAIM_UNLINK_RETRY_MS = 25;
|
|
110902
|
+
function delay2(ms) {
|
|
110903
|
+
return new Promise((resolve) => {
|
|
110904
|
+
setTimeout(resolve, ms);
|
|
110905
|
+
});
|
|
110906
|
+
}
|
|
110907
|
+
function reclaimGuardPath(lockPath, record) {
|
|
110908
|
+
return `${lockPath}.reclaim-${createHash11("sha256").update(record).digest("hex").slice(0, 16)}`;
|
|
110909
|
+
}
|
|
110910
|
+
async function unlinkWithRetry(path, deps) {
|
|
110911
|
+
const wait = deps.delay ?? delay2;
|
|
110912
|
+
const unlinkFile = deps.unlink ?? unlink3;
|
|
110913
|
+
for (let attempt = 1;; attempt += 1) {
|
|
110914
|
+
try {
|
|
110915
|
+
await unlinkFile(path);
|
|
110916
|
+
return "removed";
|
|
110917
|
+
} catch (error) {
|
|
110918
|
+
if (!(error instanceof Error))
|
|
110919
|
+
throw error;
|
|
110920
|
+
if (isPathAbsenceError(error))
|
|
110921
|
+
return "absent";
|
|
110922
|
+
const code = errorCode(error);
|
|
110923
|
+
if (code !== "EPERM" && code !== "EBUSY" || attempt === RECLAIM_UNLINK_ATTEMPTS)
|
|
110924
|
+
return "stuck";
|
|
110925
|
+
await wait(RECLAIM_UNLINK_RETRY_MS);
|
|
110926
|
+
}
|
|
110927
|
+
}
|
|
110928
|
+
}
|
|
110929
|
+
async function removeLockRecordIfUnchanged(lockPath, expectedRecord, staleAfterMs, deps = {}) {
|
|
110930
|
+
const guardPath = reclaimGuardPath(lockPath, expectedRecord);
|
|
110931
|
+
const guardRecord = buildLockRecord("lock-reclaim");
|
|
110932
|
+
if (!await publishLockRecord(guardPath, guardRecord)) {
|
|
110933
|
+
const guard = await readLockRecord(guardPath);
|
|
110934
|
+
if (guard !== null && isLockRecordStale(guard, staleAfterMs)) {
|
|
110935
|
+
await removeLockRecordIfUnchanged(guardPath, guard.content, staleAfterMs, deps);
|
|
110936
|
+
}
|
|
110937
|
+
return "busy";
|
|
110938
|
+
}
|
|
110939
|
+
try {
|
|
110940
|
+
const current = await readLockRecord(lockPath);
|
|
110941
|
+
if (current === null)
|
|
110942
|
+
return "absent";
|
|
110943
|
+
if (current.content !== expectedRecord || !isLockRecordStale(current, staleAfterMs))
|
|
110944
|
+
return "changed";
|
|
110945
|
+
const unlinked = await unlinkWithRetry(lockPath, deps);
|
|
110946
|
+
return unlinked === "absent" ? "absent" : unlinked;
|
|
110947
|
+
} finally {
|
|
110948
|
+
markLockRecordReleased(guardRecord);
|
|
110949
|
+
await unlinkWithRetry(guardPath, deps);
|
|
110950
|
+
}
|
|
110951
|
+
}
|
|
110952
|
+
|
|
110953
|
+
// packages/team-core/src/team-state-store/locks.ts
|
|
110954
|
+
var LOCK_RETRY_MS = 50;
|
|
110955
|
+
var LOCK_WAIT_TIMEOUT_MS = 15000;
|
|
110956
|
+
var DEFAULT_STALE_AFTER_MS = 300000;
|
|
110957
|
+
function delay3(ms) {
|
|
110958
|
+
return new Promise((resolve) => {
|
|
110959
|
+
setTimeout(resolve, ms);
|
|
110960
|
+
});
|
|
110961
|
+
}
|
|
110962
|
+
async function acquireLock(lockPath, ownerTag, staleAfterMs) {
|
|
110963
|
+
const startedAt = Date.now();
|
|
110964
|
+
for (;; ) {
|
|
110965
|
+
if (Date.now() - startedAt > LOCK_WAIT_TIMEOUT_MS) {
|
|
110966
|
+
throw new Error(`Timed out acquiring lock: ${lockPath}`);
|
|
110967
|
+
}
|
|
110968
|
+
const record = buildLockRecord(ownerTag);
|
|
110969
|
+
if (await publishLockRecord(lockPath, record))
|
|
110970
|
+
return record;
|
|
110971
|
+
const outcome = await reapStaleLock(lockPath, staleAfterMs);
|
|
110972
|
+
if (outcome === "removed" || outcome === "absent")
|
|
110973
|
+
continue;
|
|
110974
|
+
await delay3(LOCK_RETRY_MS);
|
|
110975
|
+
}
|
|
110976
|
+
}
|
|
110977
|
+
async function withLock(lockPath, fn, opts) {
|
|
110978
|
+
const staleAfterMs = opts?.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
110979
|
+
const ownerTag = opts?.ownerTag ?? "owner";
|
|
110980
|
+
const record = await acquireLock(lockPath, ownerTag, staleAfterMs);
|
|
110981
|
+
try {
|
|
110982
|
+
return await fn();
|
|
110983
|
+
} finally {
|
|
110984
|
+
markLockRecordReleased(record);
|
|
110985
|
+
await removeLockRecordIfUnchanged(lockPath, record, staleAfterMs);
|
|
110986
|
+
}
|
|
110987
|
+
}
|
|
110988
|
+
async function reapStaleLock(lockPath, staleAfterMs = DEFAULT_STALE_AFTER_MS, deps = {}) {
|
|
110989
|
+
const observed = await readLockRecord(lockPath);
|
|
110990
|
+
if (observed === null)
|
|
110991
|
+
return "absent";
|
|
110992
|
+
if (!isLockRecordStale(observed, staleAfterMs))
|
|
110993
|
+
return "changed";
|
|
110994
|
+
return await removeLockRecordIfUnchanged(lockPath, observed.content, staleAfterMs, deps);
|
|
110995
|
+
}
|
|
110996
|
+
|
|
110997
|
+
// packages/omo-opencode/src/cli/service-auth/session.ts
|
|
110998
|
+
class SessionEnded extends SignInError {
|
|
110999
|
+
deviceRevoked;
|
|
111000
|
+
constructor(message, deviceRevoked) {
|
|
111001
|
+
super(message);
|
|
111002
|
+
this.deviceRevoked = deviceRevoked;
|
|
111003
|
+
}
|
|
111004
|
+
}
|
|
111005
|
+
var DEVICE_REVOKING_REFUSALS = new Set(["account_deleted", "reauth_required"]);
|
|
111006
|
+
function createSession(options) {
|
|
111007
|
+
const api = serviceOrigin(options.api);
|
|
111008
|
+
const { store, signal } = options;
|
|
111009
|
+
const directory = join97(options.home ?? homedir27(), ".omo", "session-locks");
|
|
111010
|
+
const lockPath = join97(directory, `${credentialId(api)}.lock`);
|
|
111011
|
+
async function locked(action) {
|
|
111012
|
+
await mkdir10(directory, { recursive: true, mode: 448 });
|
|
111013
|
+
signal?.throwIfAborted();
|
|
111014
|
+
return withLock(lockPath, async () => {
|
|
111015
|
+
signal?.throwIfAborted();
|
|
111016
|
+
return action();
|
|
111017
|
+
}, { ownerTag: "cli-session", staleAfterMs: Number.POSITIVE_INFINITY });
|
|
111018
|
+
}
|
|
111019
|
+
const request = (path, init) => requestApi(api, path, init, signal);
|
|
111020
|
+
const uncertain = () => new SignInError("The refresh outcome is unknown. Saved credentials and keys were retained, but this refresh token will not be sent again. Retry sign-in with omo login.", true);
|
|
111021
|
+
async function refreshedAccess(current) {
|
|
111022
|
+
if (current.refreshState === "uncertain")
|
|
111023
|
+
throw uncertain();
|
|
111024
|
+
if (Date.parse(current.accessTokenExpiresAt) > Date.now() + 30000)
|
|
111025
|
+
return current.accessToken;
|
|
111026
|
+
await store.write({ ...current, refreshState: "uncertain" });
|
|
111027
|
+
try {
|
|
111028
|
+
const tokens = parseReply(tokenSchema, await request("/v1/session/refresh", jsonRequest({ refreshToken: current.refreshToken })));
|
|
111029
|
+
await store.write({ ...current, ...tokens });
|
|
111030
|
+
return tokens.accessToken;
|
|
111031
|
+
} catch (error) {
|
|
111032
|
+
if (error instanceof ApiRefusal && error.status < 500 && ["unauthorized", "account_deleted", "reauth_required", "invalid_grant"].includes(error.code)) {
|
|
111033
|
+
await store.clear();
|
|
111034
|
+
throw new SessionEnded(error.code === "reauth_required" ? "This session was ended because its refresh token was reused. Sign in again with omo login." : "Sign in again with omo login.", DEVICE_REVOKING_REFUSALS.has(error.code));
|
|
111035
|
+
}
|
|
111036
|
+
if (error instanceof RequestFailure && error.notSent || error instanceof ApiRefusal && error.status === 429 && error.code === "rate_limited") {
|
|
111037
|
+
await store.write(current);
|
|
111038
|
+
throw error;
|
|
111039
|
+
}
|
|
111040
|
+
throw uncertain();
|
|
111041
|
+
}
|
|
111042
|
+
}
|
|
111043
|
+
return {
|
|
111044
|
+
request,
|
|
111045
|
+
signal,
|
|
111046
|
+
async ready() {
|
|
111047
|
+
await store.read();
|
|
111048
|
+
},
|
|
111049
|
+
async save(value) {
|
|
111050
|
+
await locked(() => store.write(value));
|
|
111051
|
+
},
|
|
111052
|
+
async logout() {
|
|
111053
|
+
return locked(async () => {
|
|
111054
|
+
try {
|
|
111055
|
+
const current = await store.read();
|
|
111056
|
+
if (current !== null) {
|
|
111057
|
+
if (current.refreshState === "uncertain")
|
|
111058
|
+
return false;
|
|
111059
|
+
const access = Date.parse(current.accessTokenExpiresAt) <= Date.now() ? await refreshedAccess(current) : current.accessToken;
|
|
111060
|
+
await request(`/v1/devices/${encodeURIComponent(current.device.id)}`, {
|
|
111061
|
+
method: "DELETE",
|
|
111062
|
+
headers: { authorization: `Bearer ${access}` }
|
|
111063
|
+
});
|
|
111064
|
+
}
|
|
111065
|
+
return true;
|
|
111066
|
+
} catch (error) {
|
|
111067
|
+
if (error instanceof SessionEnded)
|
|
111068
|
+
return error.deviceRevoked;
|
|
111069
|
+
if (error instanceof SignInError)
|
|
111070
|
+
return false;
|
|
111071
|
+
throw error;
|
|
111072
|
+
} finally {
|
|
111073
|
+
await store.clear();
|
|
111074
|
+
}
|
|
111075
|
+
});
|
|
111076
|
+
},
|
|
111077
|
+
async accessToken() {
|
|
111078
|
+
return locked(async () => {
|
|
111079
|
+
const current = await store.read();
|
|
111080
|
+
if (current === null)
|
|
111081
|
+
throw new SignInError("Not signed in. Run omo login.");
|
|
111082
|
+
return refreshedAccess(current);
|
|
111083
|
+
});
|
|
111084
|
+
},
|
|
111085
|
+
async removeForLimit(error, choose) {
|
|
111086
|
+
if (!(error instanceof ApiRefusal) || error.detail === undefined)
|
|
111087
|
+
throw error;
|
|
111088
|
+
const headers = { authorization: `Bearer ${error.detail.managementToken}` };
|
|
111089
|
+
const listed = parseReply(deviceListSchema, await request("/v1/devices", { headers }));
|
|
111090
|
+
const devices = listed.devices.filter((device) => device.revokedAt === null);
|
|
111091
|
+
const chosen = await choose(devices);
|
|
111092
|
+
if (chosen === null)
|
|
111093
|
+
return false;
|
|
111094
|
+
if (!devices.some((device) => device.id === chosen))
|
|
111095
|
+
throw new SignInError("No matching device was selected.");
|
|
111096
|
+
await request(`/v1/devices/${encodeURIComponent(chosen)}`, { method: "DELETE", headers });
|
|
111097
|
+
return true;
|
|
111098
|
+
}
|
|
111099
|
+
};
|
|
111100
|
+
}
|
|
111101
|
+
|
|
111102
|
+
// packages/omo-opencode/src/cli/service-auth/commands.ts
|
|
111103
|
+
async function ensureServiceAvailable(session, api) {
|
|
111104
|
+
try {
|
|
111105
|
+
await session.request("/v1/health", { method: "GET" });
|
|
111106
|
+
} catch (error) {
|
|
111107
|
+
if (!(error instanceof SignInError))
|
|
111108
|
+
throw error;
|
|
111109
|
+
if (error instanceof RequestFailure) {
|
|
111110
|
+
throw new SignInError(`Can't reach OmO accounts at ${api}. The service may not be available yet, or your network, proxy or certificate settings are blocking it.`);
|
|
111111
|
+
}
|
|
111112
|
+
throw new SignInError(`OmO accounts at ${api} aren't available right now. Try again later.`);
|
|
111113
|
+
}
|
|
111114
|
+
}
|
|
111115
|
+
function deviceChoices(devices) {
|
|
111116
|
+
return devices.map((device) => ({ value: device.id, label: terminalText(device.name) }));
|
|
111117
|
+
}
|
|
111118
|
+
var chooseDevice = async (devices) => {
|
|
111119
|
+
if (!process.stdin.isTTY || devices.length === 0) {
|
|
111120
|
+
console.error("Device limit reached. Run omo login in an interactive terminal to choose a device to remove.");
|
|
111121
|
+
return null;
|
|
111122
|
+
}
|
|
111123
|
+
const chosen = await select({
|
|
111124
|
+
message: "Device limit reached. Remove a device to sign in?",
|
|
111125
|
+
options: [{ value: "", label: "Cancel (keep all devices)" }, ...deviceChoices(devices)]
|
|
111126
|
+
});
|
|
111127
|
+
if (isCancel(chosen) || chosen === "")
|
|
111128
|
+
return null;
|
|
111129
|
+
const agreed = await confirm({ message: "Sign out the selected device?", initialValue: false });
|
|
111130
|
+
return agreed === true ? chosen : null;
|
|
111131
|
+
};
|
|
111132
|
+
var DEFAULT_ACCOUNTS_ORIGIN = "https://signin.omo.dev";
|
|
111133
|
+
function accountsOrigin(options, env) {
|
|
111134
|
+
return serviceOrigin(options.accounts ?? env.OMO_SERVICE_ACCOUNTS_URL ?? DEFAULT_ACCOUNTS_ORIGIN);
|
|
111135
|
+
}
|
|
111136
|
+
async function run2(action, options) {
|
|
111137
|
+
const controller = new AbortController;
|
|
111138
|
+
const cancel = () => controller.abort();
|
|
111139
|
+
process.once("SIGINT", cancel);
|
|
111140
|
+
process.once("SIGTERM", cancel);
|
|
111141
|
+
try {
|
|
111142
|
+
const api = serviceOrigin(options.api ?? process.env.OMO_SERVICE_API_URL ?? "https://api.omo.dev");
|
|
111143
|
+
const store = createCredentialStore(api);
|
|
111144
|
+
const session = createSession({ api, store, signal: controller.signal });
|
|
111145
|
+
if (action === "logout") {
|
|
111146
|
+
const revoked = await session.logout();
|
|
111147
|
+
console.log("Signed out of OmO. Stored credentials and device keys were removed.");
|
|
111148
|
+
if (!revoked)
|
|
111149
|
+
console.error("Server device revocation could not be confirmed. Remove this device from the Devices page of your OmO account when connected.");
|
|
111150
|
+
return;
|
|
111151
|
+
}
|
|
111152
|
+
if (action === "whoami") {
|
|
111153
|
+
await session.accessToken();
|
|
111154
|
+
const saved = await store.read();
|
|
111155
|
+
console.log(saved ? `Signed in to ${api} as ${terminalText(saved.device.name)}.` : "Not signed in.");
|
|
111156
|
+
return;
|
|
111157
|
+
}
|
|
111158
|
+
await ensureServiceAvailable(session, api);
|
|
111159
|
+
const name = deviceName();
|
|
111160
|
+
const platform = devicePlatform();
|
|
111161
|
+
if (!options.device && options.browser !== false) {
|
|
111162
|
+
try {
|
|
111163
|
+
await loginLoopback(session, {
|
|
111164
|
+
accounts: accountsOrigin(options, process.env),
|
|
111165
|
+
name,
|
|
111166
|
+
platform,
|
|
111167
|
+
open: openSignInBrowser,
|
|
111168
|
+
chooseDevice
|
|
111169
|
+
});
|
|
111170
|
+
console.log("Signed in to OmO.");
|
|
111171
|
+
return;
|
|
111172
|
+
} catch (error) {
|
|
111173
|
+
if (!(error instanceof BrowserUnavailable))
|
|
111174
|
+
throw error;
|
|
111175
|
+
}
|
|
111176
|
+
}
|
|
111177
|
+
await loginDevice(session, {
|
|
111178
|
+
name,
|
|
111179
|
+
platform,
|
|
111180
|
+
chooseDevice,
|
|
111181
|
+
show: (code) => {
|
|
111182
|
+
console.log(`Open ${code.verificationUri}`);
|
|
111183
|
+
console.log(`User code: ${code.userCode}`);
|
|
111184
|
+
console.log(`Matching code: ${code.matchingCode}`);
|
|
111185
|
+
}
|
|
111186
|
+
});
|
|
111187
|
+
console.log("Signed in to OmO.");
|
|
111188
|
+
} catch (error) {
|
|
111189
|
+
console.error(controller.signal.aborted ? "Sign-in cancelled." : error instanceof SignInError ? error.message : "Unable to complete OmO sign-in. Check the OS credential store and try again.");
|
|
111190
|
+
process.exitCode = 1;
|
|
111191
|
+
} finally {
|
|
111192
|
+
process.removeListener("SIGINT", cancel);
|
|
111193
|
+
process.removeListener("SIGTERM", cancel);
|
|
111194
|
+
}
|
|
111195
|
+
}
|
|
111196
|
+
function configureServiceAuthCommands(program) {
|
|
111197
|
+
program.command("login").description("Sign in to OmO using the OS credential store").option("--device", "Use a device code instead of a browser callback").option("--no-browser", "Sign in by device code without opening a browser").option("--api <url>", "OmO API origin (or OMO_SERVICE_API_URL)").option("--accounts <url>", "OmO sign-in page origin (or OMO_SERVICE_ACCOUNTS_URL)").action((options) => run2("login", options));
|
|
111198
|
+
for (const action of ["logout", "whoami"]) {
|
|
111199
|
+
program.command(action).description(action === "logout" ? "Remove stored OmO credentials and device keys" : "Show the current OmO device sign-in").option("--api <url>", "OmO API origin (or OMO_SERVICE_API_URL)").action((options) => run2(action, options));
|
|
111200
|
+
}
|
|
111201
|
+
}
|
|
111202
|
+
|
|
111203
|
+
// packages/omo-opencode/src/cli/install-command-options.ts
|
|
111204
|
+
function resolveInstallArgs(options, invocationName = process.env.OMO_INVOCATION_NAME) {
|
|
111205
|
+
const defaultPlatform = process.env.OMO_EDITION === "codex" || invocationName === "lazycodex" || invocationName === "lazycodex-ai" ? "codex" : undefined;
|
|
111206
|
+
const platform = options.platform ?? defaultPlatform;
|
|
111207
|
+
if (platform === "native-dev" && !isNativeDevPlatformEnabled()) {
|
|
111208
|
+
throw new Error(`The native-dev install platform is not available in this release. Set ${NATIVE_DEV_PLATFORM_ENV_FLAG}=1 to enable it from a source checkout.`);
|
|
111209
|
+
}
|
|
111210
|
+
return {
|
|
111211
|
+
tui: options.tui !== false,
|
|
111212
|
+
claude: options.claude,
|
|
111213
|
+
openai: options.openai,
|
|
111214
|
+
gemini: options.gemini,
|
|
111215
|
+
copilot: options.copilot,
|
|
111216
|
+
platform,
|
|
111217
|
+
opencodeZen: options.opencodeZen,
|
|
111218
|
+
zaiCodingPlan: options.zaiCodingPlan,
|
|
111219
|
+
kimiForCoding: options.kimiForCoding,
|
|
111220
|
+
opencodeGo: options.opencodeGo,
|
|
111221
|
+
bailianCodingPlan: options.bailianCodingPlan,
|
|
111222
|
+
minimaxCnCodingPlan: options.minimaxCnCodingPlan,
|
|
111223
|
+
minimaxCodingPlan: options.minimaxCodingPlan,
|
|
111224
|
+
vercelAiGateway: options.vercelAiGateway,
|
|
111225
|
+
codexAutonomous: options.codexAutonomous,
|
|
111226
|
+
skipAuth: options.skipAuth ?? false
|
|
111227
|
+
};
|
|
111228
|
+
}
|
|
111229
|
+
|
|
110307
111230
|
// packages/omo-opencode/src/cli/boulder/boulder.ts
|
|
110308
111231
|
import { existsSync as existsSync64 } from "fs";
|
|
110309
111232
|
|
|
@@ -110464,11 +111387,11 @@ async function boulder(options) {
|
|
|
110464
111387
|
// packages/omo-opencode/src/cli/codex-ulw-loop.ts
|
|
110465
111388
|
import { spawn as spawn5 } from "child_process";
|
|
110466
111389
|
import { existsSync as existsSync65, readFileSync as readFileSync43, realpathSync as realpathSync11 } from "fs";
|
|
110467
|
-
import { homedir as
|
|
111390
|
+
import { homedir as homedir28 } from "os";
|
|
110468
111391
|
var ULW_LOOP_DELEGATION_SENTINEL = "OMO_ULW_LOOP_DELEGATED";
|
|
110469
111392
|
function resolveCodexUlwLoopCommand(input = {}) {
|
|
110470
111393
|
const env = input.env ?? process.env;
|
|
110471
|
-
const homeDir = input.homeDir ??
|
|
111394
|
+
const homeDir = input.homeDir ?? homedir28();
|
|
110472
111395
|
const localComponentBin = resolveLocalUlwLoopBin(env, homeDir);
|
|
110473
111396
|
if (localComponentBin !== null)
|
|
110474
111397
|
return { executable: localComponentBin, argsPrefix: [] };
|
|
@@ -110709,7 +111632,7 @@ function linkedWorktrees(records) {
|
|
|
110709
111632
|
}
|
|
110710
111633
|
// packages/omo-opencode/src/cli/worktree-sweep/sweep.ts
|
|
110711
111634
|
import { existsSync as existsSync66 } from "fs";
|
|
110712
|
-
import { stat as
|
|
111635
|
+
import { stat as stat9 } from "fs/promises";
|
|
110713
111636
|
import os7 from "os";
|
|
110714
111637
|
import { normalize as normalize2 } from "path";
|
|
110715
111638
|
|
|
@@ -110792,7 +111715,7 @@ async function isOlderThan(worktreePath, days, now) {
|
|
|
110792
111715
|
if (days <= 0)
|
|
110793
111716
|
return false;
|
|
110794
111717
|
try {
|
|
110795
|
-
const stats = await
|
|
111718
|
+
const stats = await stat9(worktreePath);
|
|
110796
111719
|
return now - stats.mtimeMs > days * DAY_MS;
|
|
110797
111720
|
} catch {
|
|
110798
111721
|
return false;
|
|
@@ -111009,31 +111932,6 @@ function runConfigMigrate(options = {}) {
|
|
|
111009
111932
|
init_package();
|
|
111010
111933
|
var VERSION3 = package_default.version;
|
|
111011
111934
|
var program2 = new Command;
|
|
111012
|
-
function resolveInstallArgs(options, invocationName = process.env.OMO_INVOCATION_NAME) {
|
|
111013
|
-
const defaultPlatform = process.env.OMO_EDITION === "codex" || invocationName === "lazycodex" || invocationName === "lazycodex-ai" ? "codex" : undefined;
|
|
111014
|
-
const platform = options.platform ?? defaultPlatform;
|
|
111015
|
-
if (platform === "native-dev" && !isNativeDevPlatformEnabled()) {
|
|
111016
|
-
throw new Error(`The native-dev install platform is not available in this release. Set ${NATIVE_DEV_PLATFORM_ENV_FLAG}=1 to enable it from a source checkout.`);
|
|
111017
|
-
}
|
|
111018
|
-
return {
|
|
111019
|
-
tui: options.tui !== false,
|
|
111020
|
-
claude: options.claude,
|
|
111021
|
-
openai: options.openai,
|
|
111022
|
-
gemini: options.gemini,
|
|
111023
|
-
copilot: options.copilot,
|
|
111024
|
-
platform,
|
|
111025
|
-
opencodeZen: options.opencodeZen,
|
|
111026
|
-
zaiCodingPlan: options.zaiCodingPlan,
|
|
111027
|
-
kimiForCoding: options.kimiForCoding,
|
|
111028
|
-
opencodeGo: options.opencodeGo,
|
|
111029
|
-
bailianCodingPlan: options.bailianCodingPlan,
|
|
111030
|
-
minimaxCnCodingPlan: options.minimaxCnCodingPlan,
|
|
111031
|
-
minimaxCodingPlan: options.minimaxCodingPlan,
|
|
111032
|
-
vercelAiGateway: options.vercelAiGateway,
|
|
111033
|
-
codexAutonomous: options.codexAutonomous,
|
|
111034
|
-
skipAuth: options.skipAuth ?? false
|
|
111035
|
-
};
|
|
111036
|
-
}
|
|
111037
111935
|
program2.name("oh-my-opencode").description("The ultimate OpenCode plugin - multi-model orchestration, LSP tools, and more").version(VERSION3, "-v, --version", "Show version number").helpOption("-h, --help", "Display help for command").addOption(new Option("--platform <platform>", `Install target platform: ${availableInstallPlatforms().join(", ")}`).choices(availableInstallPlatforms()).hideHelp()).enablePositionalOptions();
|
|
111038
111936
|
program2.command("install").alias("setup").description("Install and configure oh-my-opencode with interactive setup").option("--no-tui", "Run in non-interactive mode (requires all options)").option("--claude <value>", "Claude subscription: no, yes, max20").option("--openai <value>", "OpenAI/ChatGPT subscription: no, yes (default: no)").option("--gemini <value>", "Gemini integration: no, yes").option("--copilot <value>", "GitHub Copilot subscription: no, yes").addOption(new Option("--platform <platform>", `Install target platform: ${availableInstallPlatforms().join(", ")}`).choices(availableInstallPlatforms())).option("--opencode-zen <value>", "OpenCode Zen access: no, yes (default: no)").option("--zai-coding-plan <value>", "Z.ai Coding Plan subscription: no, yes (default: no)").option("--kimi-for-coding <value>", "Kimi For Coding subscription: no, yes (default: no)").option("--opencode-go <value>", "OpenCode Go subscription: no, yes (default: no)").option("--bailian-coding-plan <value>", "Bailian Coding Plan subscription: no, yes (default: no)").option("--minimax-cn-coding-plan <value>", "MiniMax Coding Plan (minimaxi.com) subscription: no, yes (default: no)").option("--minimax-coding-plan <value>", "MiniMax Coding Plan (minimax.io) subscription: no, yes (default: no)").option("--vercel-ai-gateway <value>", "Vercel AI Gateway: no, yes (default: no)").option("--codex-autonomous", "Configure Codex with approval never, full filesystem access, and network enabled").option("--no-codex-autonomous", "Leave existing Codex permission settings unchanged").option("--skip-auth", "Skip authentication setup hints").addHelpText("after", `
|
|
111039
111937
|
Examples:
|
|
@@ -111151,6 +112049,7 @@ program2.command("config").description("Manage unified OMO configuration").comma
|
|
|
111151
112049
|
});
|
|
111152
112050
|
configureRuntimeCommands(program2);
|
|
111153
112051
|
program2.addCommand(createMcpOAuthCommand());
|
|
112052
|
+
configureServiceAuthCommands(program2);
|
|
111154
112053
|
function runCli() {
|
|
111155
112054
|
program2.parse();
|
|
111156
112055
|
}
|