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-node/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var package_default;
|
|
|
65
65
|
var init_package = __esm(() => {
|
|
66
66
|
package_default = {
|
|
67
67
|
name: "oh-my-opencode",
|
|
68
|
-
version: "5.1.
|
|
68
|
+
version: "5.1.32",
|
|
69
69
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
70
70
|
main: "./dist/index.js",
|
|
71
71
|
types: "dist/index.d.ts",
|
|
@@ -242,7 +242,7 @@ var init_package = __esm(() => {
|
|
|
242
242
|
},
|
|
243
243
|
devDependencies: {
|
|
244
244
|
"@clack/prompts": "^1.8.1",
|
|
245
|
-
"@code-yeongyu/senpi": "2026.10.
|
|
245
|
+
"@code-yeongyu/senpi": "2026.10.12",
|
|
246
246
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
247
247
|
"@oh-my-opencode/agents-md-core": "workspace:*",
|
|
248
248
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
|
@@ -284,6 +284,7 @@ var init_package = __esm(() => {
|
|
|
284
284
|
"@types/js-yaml": "^4.0.9",
|
|
285
285
|
"@types/picomatch": "^4.0.3",
|
|
286
286
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
287
|
+
"@typescript/typescript6": "6.0.2",
|
|
287
288
|
"@xterm/addon-unicode11": "^0.9.0",
|
|
288
289
|
"@xterm/xterm": "^6.0.0",
|
|
289
290
|
"bun-types": "1.4.2",
|
|
@@ -300,18 +301,18 @@ var init_package = __esm(() => {
|
|
|
300
301
|
typescript: "^7.0.2"
|
|
301
302
|
},
|
|
302
303
|
optionalDependencies: {
|
|
303
|
-
"oh-my-opencode-darwin-arm64": "5.1.
|
|
304
|
-
"oh-my-opencode-darwin-x64": "5.1.
|
|
305
|
-
"oh-my-opencode-darwin-x64-baseline": "5.1.
|
|
306
|
-
"oh-my-opencode-linux-arm64": "5.1.
|
|
307
|
-
"oh-my-opencode-linux-arm64-musl": "5.1.
|
|
308
|
-
"oh-my-opencode-linux-x64": "5.1.
|
|
309
|
-
"oh-my-opencode-linux-x64-baseline": "5.1.
|
|
310
|
-
"oh-my-opencode-linux-x64-musl": "5.1.
|
|
311
|
-
"oh-my-opencode-linux-x64-musl-baseline": "5.1.
|
|
312
|
-
"oh-my-opencode-windows-arm64": "5.1.
|
|
313
|
-
"oh-my-opencode-windows-x64": "5.1.
|
|
314
|
-
"oh-my-opencode-windows-x64-baseline": "5.1.
|
|
304
|
+
"oh-my-opencode-darwin-arm64": "5.1.32",
|
|
305
|
+
"oh-my-opencode-darwin-x64": "5.1.32",
|
|
306
|
+
"oh-my-opencode-darwin-x64-baseline": "5.1.32",
|
|
307
|
+
"oh-my-opencode-linux-arm64": "5.1.32",
|
|
308
|
+
"oh-my-opencode-linux-arm64-musl": "5.1.32",
|
|
309
|
+
"oh-my-opencode-linux-x64": "5.1.32",
|
|
310
|
+
"oh-my-opencode-linux-x64-baseline": "5.1.32",
|
|
311
|
+
"oh-my-opencode-linux-x64-musl": "5.1.32",
|
|
312
|
+
"oh-my-opencode-linux-x64-musl-baseline": "5.1.32",
|
|
313
|
+
"oh-my-opencode-windows-arm64": "5.1.32",
|
|
314
|
+
"oh-my-opencode-windows-x64": "5.1.32",
|
|
315
|
+
"oh-my-opencode-windows-x64-baseline": "5.1.32"
|
|
315
316
|
},
|
|
316
317
|
overrides: {
|
|
317
318
|
hono: "^4.13.8",
|
|
@@ -5084,6 +5085,9 @@ function resolveStdio(options) {
|
|
|
5084
5085
|
}
|
|
5085
5086
|
return [options.stdin ?? "ignore", options.stdout ?? "pipe", options.stderr ?? "inherit"];
|
|
5086
5087
|
}
|
|
5088
|
+
function createBunSpawnOptions(options) {
|
|
5089
|
+
return { ...options, windowsHide: true };
|
|
5090
|
+
}
|
|
5087
5091
|
function createNodeSpawnOptions(options, platform = process.platform) {
|
|
5088
5092
|
const nodeOptions = {
|
|
5089
5093
|
stdio: resolveStdio(options),
|
|
@@ -5172,7 +5176,7 @@ function spawn(cmdOrOpts, opts) {
|
|
|
5172
5176
|
const { cmd, opts: options } = resolveCommand(cmdOrOpts, opts);
|
|
5173
5177
|
const bun = getBunRuntime();
|
|
5174
5178
|
if (bun)
|
|
5175
|
-
return wrapBunProcess(bun.spawn(cmd, options));
|
|
5179
|
+
return wrapBunProcess(bun.spawn(cmd, createBunSpawnOptions(options)));
|
|
5176
5180
|
const [bin, ...args] = cmd;
|
|
5177
5181
|
if (!bin)
|
|
5178
5182
|
throw new Error("spawn requires a command");
|
|
@@ -5317,7 +5321,7 @@ function isKnownNonGitBashLauncher(path) {
|
|
|
5317
5321
|
}
|
|
5318
5322
|
function whereCommand(command) {
|
|
5319
5323
|
try {
|
|
5320
|
-
return execFileSync("where", [command], { encoding: "utf8" }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
5324
|
+
return execFileSync("where", [command], { encoding: "utf8", windowsHide: true }).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
5321
5325
|
} catch (error) {
|
|
5322
5326
|
if (error instanceof Error)
|
|
5323
5327
|
return [];
|
|
@@ -5760,7 +5764,8 @@ function defaultVersionProbe(binaryPath) {
|
|
|
5760
5764
|
return String(execFileSync2(binaryPath, ["--version"], {
|
|
5761
5765
|
encoding: "utf8",
|
|
5762
5766
|
stdio: ["ignore", "pipe", "ignore"],
|
|
5763
|
-
timeout: SG_VERSION_PROBE_TIMEOUT_MS
|
|
5767
|
+
timeout: SG_VERSION_PROBE_TIMEOUT_MS,
|
|
5768
|
+
windowsHide: true
|
|
5764
5769
|
}));
|
|
5765
5770
|
}
|
|
5766
5771
|
function probePasses(binaryPath, deps) {
|
|
@@ -7463,10 +7468,11 @@ var init_category_model_requirements = __esm(() => {
|
|
|
7463
7468
|
},
|
|
7464
7469
|
{ providers: ["xiaomi", "opencode-go"], model: "mimo-v2.6-pro", variant: "max" },
|
|
7465
7470
|
{ providers: ["xai", "github-copilot", "opencode-go"], model: "grok-4.7", variant: "xhigh" },
|
|
7471
|
+
{ providers: ["openai", "chatgpt-subscription"], model: "gpt-6.1-sol", variant: "medium" },
|
|
7466
7472
|
{
|
|
7467
7473
|
providers: ["openai", "chatgpt-subscription", "github-copilot", "opencode"],
|
|
7468
|
-
model: "gpt-5.6-
|
|
7469
|
-
variant: "
|
|
7474
|
+
model: "gpt-5.6-sol",
|
|
7475
|
+
variant: "medium"
|
|
7470
7476
|
},
|
|
7471
7477
|
{
|
|
7472
7478
|
providers: ["anthropic", "anthropic-api", "github-copilot", "opencode"],
|
|
@@ -74822,7 +74828,8 @@ var init_memory = __esm(() => {
|
|
|
74822
74828
|
}).strict();
|
|
74823
74829
|
OmoMemoryFactsSchema = object({
|
|
74824
74830
|
enabled: boolean2().default(true),
|
|
74825
|
-
debounce_settles: number2().int().min(1).default(4)
|
|
74831
|
+
debounce_settles: number2().int().min(1).default(4),
|
|
74832
|
+
model: string2().min(1).optional()
|
|
74826
74833
|
}).strict();
|
|
74827
74834
|
OmoMemoryDreamSchema = object({
|
|
74828
74835
|
enabled: boolean2().default(true),
|
|
@@ -74884,7 +74891,8 @@ var init_memory = __esm(() => {
|
|
|
74884
74891
|
}).strict();
|
|
74885
74892
|
OmoMemoryFactsLayerSchema = object({
|
|
74886
74893
|
enabled: boolean2().optional(),
|
|
74887
|
-
debounce_settles: number2().int().min(1).optional()
|
|
74894
|
+
debounce_settles: number2().int().min(1).optional(),
|
|
74895
|
+
model: string2().min(1).optional()
|
|
74888
74896
|
}).strict();
|
|
74889
74897
|
OmoMemoryDreamLayerSchema = object({
|
|
74890
74898
|
enabled: boolean2().optional(),
|
|
@@ -89973,7 +89981,7 @@ var package_default2;
|
|
|
89973
89981
|
var init_package2 = __esm(() => {
|
|
89974
89982
|
package_default2 = {
|
|
89975
89983
|
name: "@oh-my-opencode/omo-codex",
|
|
89976
|
-
version: "5.1.
|
|
89984
|
+
version: "5.1.32",
|
|
89977
89985
|
type: "module",
|
|
89978
89986
|
private: true,
|
|
89979
89987
|
description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
|
|
@@ -110383,6 +110391,921 @@ function createMcpOAuthCommand() {
|
|
|
110383
110391
|
return mcp;
|
|
110384
110392
|
}
|
|
110385
110393
|
|
|
110394
|
+
// packages/omo-opencode/src/cli/service-auth/browser.ts
|
|
110395
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
110396
|
+
var execute = (file, args, env) => new Promise((resolve) => {
|
|
110397
|
+
execFile5(file, [...args], { env, timeout: 1e4, windowsHide: true }, (error) => resolve(error === null));
|
|
110398
|
+
});
|
|
110399
|
+
async function openSignInBrowser(url, executeBrowser = execute, platform = process.platform, environment = process.env) {
|
|
110400
|
+
const env = {};
|
|
110401
|
+
for (const key of ["PATH", "Path", "HOME", "USERPROFILE", "SystemRoot", "WINDIR", "DISPLAY", "WAYLAND_DISPLAY", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS"]) {
|
|
110402
|
+
const value = environment[key];
|
|
110403
|
+
if (value !== undefined)
|
|
110404
|
+
env[key] = value;
|
|
110405
|
+
}
|
|
110406
|
+
if (platform === "darwin")
|
|
110407
|
+
return executeBrowser("/usr/bin/open", [url], env);
|
|
110408
|
+
if (platform === "win32")
|
|
110409
|
+
return executeBrowser("rundll32.exe", ["url.dll,FileProtocolHandler", url], env);
|
|
110410
|
+
if (platform === "linux" && (env.DISPLAY || env.WAYLAND_DISPLAY))
|
|
110411
|
+
return executeBrowser("xdg-open", [url], env);
|
|
110412
|
+
return false;
|
|
110413
|
+
}
|
|
110414
|
+
|
|
110415
|
+
// packages/omo-opencode/src/cli/service-auth/device-name.ts
|
|
110416
|
+
import { hostname } from "node:os";
|
|
110417
|
+
|
|
110418
|
+
// packages/omo-opencode/src/cli/service-auth/protocol.ts
|
|
110419
|
+
init_zod();
|
|
110420
|
+
|
|
110421
|
+
// packages/omo-opencode/src/cli/service-auth/terminal-text.ts
|
|
110422
|
+
import { stripVTControlCharacters as stripVTControlCharacters4 } from "node:util";
|
|
110423
|
+
function terminalText(value) {
|
|
110424
|
+
return stripVTControlCharacters4(value).replace(/[\p{C}\p{Zl}\p{Zp}]/gu, "");
|
|
110425
|
+
}
|
|
110426
|
+
|
|
110427
|
+
// packages/omo-opencode/src/cli/service-auth/protocol.ts
|
|
110428
|
+
class SignInError extends Error {
|
|
110429
|
+
retryable;
|
|
110430
|
+
name = "SignInError";
|
|
110431
|
+
constructor(message, retryable = false) {
|
|
110432
|
+
super(message);
|
|
110433
|
+
this.retryable = retryable;
|
|
110434
|
+
}
|
|
110435
|
+
}
|
|
110436
|
+
|
|
110437
|
+
class RequestFailure extends SignInError {
|
|
110438
|
+
notSent;
|
|
110439
|
+
constructor(notSent) {
|
|
110440
|
+
super("Unable to complete the service request. Check the connection and retry.", true);
|
|
110441
|
+
this.notSent = notSent;
|
|
110442
|
+
}
|
|
110443
|
+
}
|
|
110444
|
+
var platformSchema = _enum(["macos", "linux", "windows"]);
|
|
110445
|
+
var nonempty = string2().min(1);
|
|
110446
|
+
var timestamp = string2().datetime({ offset: true });
|
|
110447
|
+
var tokenSchema = object({
|
|
110448
|
+
accessToken: nonempty,
|
|
110449
|
+
accessTokenExpiresAt: timestamp,
|
|
110450
|
+
refreshToken: nonempty
|
|
110451
|
+
});
|
|
110452
|
+
var grantSchema = tokenSchema.extend({
|
|
110453
|
+
device: object({ id: nonempty, name: nonempty }),
|
|
110454
|
+
offlineGrant: nonempty.optional()
|
|
110455
|
+
});
|
|
110456
|
+
var credentialSchema = grantSchema.extend({
|
|
110457
|
+
signingPrivateKey: nonempty,
|
|
110458
|
+
sealingPrivateKey: nonempty,
|
|
110459
|
+
refreshState: literal("uncertain").optional()
|
|
110460
|
+
});
|
|
110461
|
+
var deviceCodeSchema = object({
|
|
110462
|
+
deviceCode: string2().regex(/^dc\.[A-Za-z0-9_-]{43}$/),
|
|
110463
|
+
userCode: string2().regex(/^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$/),
|
|
110464
|
+
matchingCode: string2().regex(/^\d{3} \d{3}$/),
|
|
110465
|
+
verificationUri: string2().transform(terminalText).pipe(string2().url()),
|
|
110466
|
+
expiresAt: timestamp,
|
|
110467
|
+
intervalSeconds: number2().int().min(1).max(60)
|
|
110468
|
+
});
|
|
110469
|
+
var envelopeSchema = object({
|
|
110470
|
+
error: object({
|
|
110471
|
+
code: nonempty,
|
|
110472
|
+
message: string2(),
|
|
110473
|
+
retryable: boolean2(),
|
|
110474
|
+
requestId: string2(),
|
|
110475
|
+
detail: unknown().optional()
|
|
110476
|
+
})
|
|
110477
|
+
});
|
|
110478
|
+
var limitSchema = object({
|
|
110479
|
+
reason: literal("device_limit"),
|
|
110480
|
+
managementToken: nonempty,
|
|
110481
|
+
used: number2().int().nonnegative(),
|
|
110482
|
+
limit: number2().int().nonnegative()
|
|
110483
|
+
});
|
|
110484
|
+
var deviceListSchema = object({
|
|
110485
|
+
devices: array(object({ id: nonempty, name: nonempty, revokedAt: timestamp.nullable() }))
|
|
110486
|
+
});
|
|
110487
|
+
|
|
110488
|
+
class ApiRefusal extends SignInError {
|
|
110489
|
+
status;
|
|
110490
|
+
code;
|
|
110491
|
+
#detail;
|
|
110492
|
+
#retryAfter;
|
|
110493
|
+
constructor(code, status, detail, retryAfter = null) {
|
|
110494
|
+
super("The service refused this request. Sign in again or check your account.", status === 429 || status >= 500);
|
|
110495
|
+
this.status = status;
|
|
110496
|
+
this.code = [
|
|
110497
|
+
"unauthorized",
|
|
110498
|
+
"account_deleted",
|
|
110499
|
+
"reauth_required",
|
|
110500
|
+
"invalid_grant",
|
|
110501
|
+
"entitlement_required",
|
|
110502
|
+
"authorization_pending",
|
|
110503
|
+
"slow_down",
|
|
110504
|
+
"access_denied",
|
|
110505
|
+
"expired_token",
|
|
110506
|
+
"rate_limited",
|
|
110507
|
+
"internal",
|
|
110508
|
+
"unavailable",
|
|
110509
|
+
"forbidden",
|
|
110510
|
+
"invalid_request",
|
|
110511
|
+
"not_found"
|
|
110512
|
+
].includes(code) ? code : "unknown";
|
|
110513
|
+
this.#detail = detail;
|
|
110514
|
+
this.#retryAfter = retryAfter;
|
|
110515
|
+
}
|
|
110516
|
+
get detail() {
|
|
110517
|
+
return this.#detail;
|
|
110518
|
+
}
|
|
110519
|
+
retryDelay(now, fallback) {
|
|
110520
|
+
const raw = this.#retryAfter;
|
|
110521
|
+
const delay = raw === null ? Number.NaN : /^\d+$/.test(raw) ? Number(raw) * 1000 : Date.parse(raw) - now;
|
|
110522
|
+
return Math.min(60000, Math.max(fallback, Number.isFinite(delay) ? delay : fallback));
|
|
110523
|
+
}
|
|
110524
|
+
}
|
|
110525
|
+
function parseReply(schema, value) {
|
|
110526
|
+
const result = schema.safeParse(value);
|
|
110527
|
+
if (!result.success)
|
|
110528
|
+
throw new SignInError("The service returned an invalid response.");
|
|
110529
|
+
return result.data;
|
|
110530
|
+
}
|
|
110531
|
+
function serviceOrigin(value) {
|
|
110532
|
+
let url;
|
|
110533
|
+
try {
|
|
110534
|
+
url = new URL(value);
|
|
110535
|
+
} catch {
|
|
110536
|
+
throw new SignInError("Invalid service URL.");
|
|
110537
|
+
}
|
|
110538
|
+
const loopback = url.protocol === "http:" && url.hostname === "127.0.0.1" && url.port !== "";
|
|
110539
|
+
if (url.protocol !== "https:" && !loopback || url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
110540
|
+
throw new SignInError("Service URLs must be HTTPS origins, or http://127.0.0.1:<port> for local testing.");
|
|
110541
|
+
}
|
|
110542
|
+
return url.origin;
|
|
110543
|
+
}
|
|
110544
|
+
async function requestApi(api, path, init, signal) {
|
|
110545
|
+
let response;
|
|
110546
|
+
let body;
|
|
110547
|
+
try {
|
|
110548
|
+
response = await fetch(`${api}${path}`, {
|
|
110549
|
+
...init,
|
|
110550
|
+
redirect: "error",
|
|
110551
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(30000)]) : AbortSignal.timeout(30000)
|
|
110552
|
+
});
|
|
110553
|
+
} catch (error) {
|
|
110554
|
+
const cause = error instanceof Error && error.cause instanceof Error ? error.cause : error;
|
|
110555
|
+
const code = cause instanceof Error ? Reflect.get(cause, "code") : undefined;
|
|
110556
|
+
throw new RequestFailure(typeof code === "string" && [
|
|
110557
|
+
"ENOTFOUND",
|
|
110558
|
+
"ECONNREFUSED",
|
|
110559
|
+
"ENETUNREACH",
|
|
110560
|
+
"EHOSTUNREACH",
|
|
110561
|
+
"EAI_AGAIN",
|
|
110562
|
+
"ConnectionRefused",
|
|
110563
|
+
"EADDRNOTAVAIL",
|
|
110564
|
+
"ENETDOWN",
|
|
110565
|
+
"EHOSTDOWN",
|
|
110566
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
110567
|
+
"ERR_TLS_HANDSHAKE_TIMEOUT",
|
|
110568
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
110569
|
+
"CERT_HAS_EXPIRED",
|
|
110570
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
110571
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
110572
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
110573
|
+
"CERT_NOT_YET_VALID",
|
|
110574
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
110575
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
110576
|
+
"CERT_SIGNATURE_FAILURE",
|
|
110577
|
+
"CERT_UNTRUSTED",
|
|
110578
|
+
"CERT_REJECTED"
|
|
110579
|
+
].includes(code));
|
|
110580
|
+
}
|
|
110581
|
+
if (response.status === 204)
|
|
110582
|
+
return null;
|
|
110583
|
+
try {
|
|
110584
|
+
body = await response.json();
|
|
110585
|
+
} catch {
|
|
110586
|
+
throw new RequestFailure(false);
|
|
110587
|
+
}
|
|
110588
|
+
if (response.ok)
|
|
110589
|
+
return body;
|
|
110590
|
+
const envelope = envelopeSchema.safeParse(body);
|
|
110591
|
+
if (!envelope.success)
|
|
110592
|
+
throw new SignInError("The service returned an invalid error response.");
|
|
110593
|
+
const error = envelope.data.error;
|
|
110594
|
+
const detail = limitSchema.safeParse(error.detail);
|
|
110595
|
+
throw new ApiRefusal(error.code, response.status, error.code === "entitlement_required" && detail.success ? detail.data : undefined, response.headers.get("retry-after"));
|
|
110596
|
+
}
|
|
110597
|
+
var jsonRequest = (body) => ({
|
|
110598
|
+
method: "POST",
|
|
110599
|
+
headers: { "content-type": "application/json" },
|
|
110600
|
+
body: JSON.stringify(body)
|
|
110601
|
+
});
|
|
110602
|
+
|
|
110603
|
+
// packages/omo-opencode/src/cli/service-auth/device-name.ts
|
|
110604
|
+
function deviceName(host = hostname()) {
|
|
110605
|
+
const clean = `omo CLI on ${host}`.replace(/[\p{Cc}\p{Zl}\p{Zp}\p{Cf}\u3164\u115F\u1160\uFFA0]/gu, "").trim();
|
|
110606
|
+
return [...clean].slice(0, 64).join("").trim();
|
|
110607
|
+
}
|
|
110608
|
+
function devicePlatform() {
|
|
110609
|
+
switch (process.platform) {
|
|
110610
|
+
case "darwin":
|
|
110611
|
+
return "macos";
|
|
110612
|
+
case "linux":
|
|
110613
|
+
return "linux";
|
|
110614
|
+
case "win32":
|
|
110615
|
+
return "windows";
|
|
110616
|
+
default:
|
|
110617
|
+
throw new SignInError("This platform is not supported for OmO sign-in.");
|
|
110618
|
+
}
|
|
110619
|
+
}
|
|
110620
|
+
|
|
110621
|
+
// packages/omo-opencode/src/cli/service-auth/device-flow.ts
|
|
110622
|
+
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
110623
|
+
|
|
110624
|
+
// packages/omo-opencode/src/cli/service-auth/device-keys.ts
|
|
110625
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
110626
|
+
function generateDeviceKeys() {
|
|
110627
|
+
const signing = generateKeyPairSync("ed25519");
|
|
110628
|
+
const sealing = generateKeyPairSync("x25519");
|
|
110629
|
+
const signingKey = signing.publicKey.export({ format: "jwk" }).x;
|
|
110630
|
+
const sealingKey = sealing.publicKey.export({ format: "jwk" }).x;
|
|
110631
|
+
if (!signingKey || !sealingKey)
|
|
110632
|
+
throw new SignInError("Unable to generate device keys.");
|
|
110633
|
+
return {
|
|
110634
|
+
public: { signingKey, sealingKey },
|
|
110635
|
+
private: {
|
|
110636
|
+
signingPrivateKey: signing.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64"),
|
|
110637
|
+
sealingPrivateKey: sealing.privateKey.export({ format: "der", type: "pkcs8" }).toString("base64")
|
|
110638
|
+
}
|
|
110639
|
+
};
|
|
110640
|
+
}
|
|
110641
|
+
|
|
110642
|
+
// packages/omo-opencode/src/cli/service-auth/device-flow.ts
|
|
110643
|
+
async function loginDevice(session, options) {
|
|
110644
|
+
await session.ready();
|
|
110645
|
+
const keys = generateDeviceKeys();
|
|
110646
|
+
const minted = parseReply(deviceCodeSchema, await session.request("/v1/device/code", jsonRequest({ client: { name: options.name, platform: options.platform } })));
|
|
110647
|
+
const uri = new URL(minted.verificationUri);
|
|
110648
|
+
if (uri.protocol !== "https:" && !(uri.protocol === "http:" && uri.hostname === "127.0.0.1" && uri.port !== "")) {
|
|
110649
|
+
throw new SignInError("The service returned an unsafe verification URL.");
|
|
110650
|
+
}
|
|
110651
|
+
options.show({ verificationUri: minted.verificationUri, userCode: minted.userCode, matchingCode: minted.matchingCode });
|
|
110652
|
+
const now = options.now ?? Date.now;
|
|
110653
|
+
const sleep = options.sleep ?? ((ms) => setTimeout2(ms, undefined, { signal: session.signal }));
|
|
110654
|
+
const expiresAt = Date.parse(minted.expiresAt);
|
|
110655
|
+
let interval = minted.intervalSeconds * 1000;
|
|
110656
|
+
while (now() < expiresAt) {
|
|
110657
|
+
await sleep(Math.min(interval, expiresAt - now()));
|
|
110658
|
+
session.signal?.throwIfAborted();
|
|
110659
|
+
if (now() >= expiresAt)
|
|
110660
|
+
break;
|
|
110661
|
+
try {
|
|
110662
|
+
const grant = parseReply(grantSchema, await session.request("/v1/device/token", jsonRequest({
|
|
110663
|
+
deviceCode: minted.deviceCode,
|
|
110664
|
+
...keys.public
|
|
110665
|
+
})));
|
|
110666
|
+
await session.save({ ...grant, ...keys.private });
|
|
110667
|
+
return;
|
|
110668
|
+
} catch (error) {
|
|
110669
|
+
if (!(error instanceof ApiRefusal))
|
|
110670
|
+
throw error;
|
|
110671
|
+
if (error.status === 400 && error.code === "authorization_pending")
|
|
110672
|
+
continue;
|
|
110673
|
+
if (error.status === 400 && error.code === "slow_down") {
|
|
110674
|
+
interval += 5000;
|
|
110675
|
+
continue;
|
|
110676
|
+
}
|
|
110677
|
+
if (error.status === 429) {
|
|
110678
|
+
interval = error.retryDelay(now(), Math.min(60000, interval + 5000));
|
|
110679
|
+
continue;
|
|
110680
|
+
}
|
|
110681
|
+
if (error.detail !== undefined) {
|
|
110682
|
+
if (await session.removeForLimit(error, options.chooseDevice))
|
|
110683
|
+
continue;
|
|
110684
|
+
throw new SignInError("Sign-in cancelled. No device was removed.");
|
|
110685
|
+
}
|
|
110686
|
+
if (["access_denied", "expired_token", "invalid_grant"].includes(error.code)) {
|
|
110687
|
+
throw new SignInError("Device sign-in was denied or expired. Run omo login again for a new code.");
|
|
110688
|
+
}
|
|
110689
|
+
throw error;
|
|
110690
|
+
}
|
|
110691
|
+
}
|
|
110692
|
+
throw new SignInError("Device sign-in expired. Run omo login again for a new code.");
|
|
110693
|
+
}
|
|
110694
|
+
|
|
110695
|
+
// packages/omo-opencode/src/cli/service-auth/keystore.ts
|
|
110696
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
110697
|
+
function credentialId(api) {
|
|
110698
|
+
return createHash9("sha256").update(serviceOrigin(api)).digest("hex");
|
|
110699
|
+
}
|
|
110700
|
+
function createCredentialStore(api, secrets = typeof Bun === "undefined" ? null : Bun.secrets, platform = process.platform) {
|
|
110701
|
+
if (!secrets)
|
|
110702
|
+
throw new SignInError("OmO sign-in needs Bun 1.4 or newer with OS credential storage. No file fallback is available.");
|
|
110703
|
+
const platformName = platform === "darwin" ? "macOS Keychain" : platform === "linux" ? "Linux Secret Service (libsecret and an unlocked keyring)" : platform === "win32" ? "Windows Credential Manager" : null;
|
|
110704
|
+
if (platformName === null)
|
|
110705
|
+
throw new SignInError("This platform has no supported OS credential store.");
|
|
110706
|
+
const key = { service: "omo-cli", name: credentialId(api) };
|
|
110707
|
+
const unavailable = () => new SignInError(`Cannot access ${platformName}. Unlock or enable the OS credential store and try again. Credentials are never saved to a file.`);
|
|
110708
|
+
return {
|
|
110709
|
+
async read() {
|
|
110710
|
+
let value;
|
|
110711
|
+
try {
|
|
110712
|
+
value = await secrets.get(key);
|
|
110713
|
+
} catch {
|
|
110714
|
+
throw unavailable();
|
|
110715
|
+
}
|
|
110716
|
+
if (value === null)
|
|
110717
|
+
return null;
|
|
110718
|
+
try {
|
|
110719
|
+
return credentialSchema.parse(JSON.parse(value));
|
|
110720
|
+
} catch {
|
|
110721
|
+
throw new SignInError("Stored sign-in data is invalid. Run omo logout, then omo login.");
|
|
110722
|
+
}
|
|
110723
|
+
},
|
|
110724
|
+
async write(value) {
|
|
110725
|
+
try {
|
|
110726
|
+
await secrets.set({ ...key, value: JSON.stringify(credentialSchema.parse(value)) });
|
|
110727
|
+
} catch {
|
|
110728
|
+
throw unavailable();
|
|
110729
|
+
}
|
|
110730
|
+
},
|
|
110731
|
+
async clear() {
|
|
110732
|
+
try {
|
|
110733
|
+
await secrets.delete(key);
|
|
110734
|
+
} catch {
|
|
110735
|
+
throw unavailable();
|
|
110736
|
+
}
|
|
110737
|
+
}
|
|
110738
|
+
};
|
|
110739
|
+
}
|
|
110740
|
+
|
|
110741
|
+
// packages/omo-opencode/src/cli/service-auth/loopback.ts
|
|
110742
|
+
import { createHash as createHash10, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
110743
|
+
import { createServer as createServer3 } from "node:http";
|
|
110744
|
+
class BrowserUnavailable extends SignInError {
|
|
110745
|
+
}
|
|
110746
|
+
async function authorize(options) {
|
|
110747
|
+
const verifier = randomBytes3(32).toString("base64url");
|
|
110748
|
+
const state = randomBytes3(32).toString("base64url");
|
|
110749
|
+
const answer = Promise.withResolvers();
|
|
110750
|
+
const abort = () => answer.reject(new SignInError("Browser sign-in cancelled or timed out."));
|
|
110751
|
+
let accepted = false;
|
|
110752
|
+
let origin = "";
|
|
110753
|
+
const server = createServer3((request, response) => {
|
|
110754
|
+
response.setHeader("cache-control", "no-store");
|
|
110755
|
+
response.setHeader("referrer-policy", "no-referrer");
|
|
110756
|
+
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
110757
|
+
const url = new URL(request.url ?? "/", origin);
|
|
110758
|
+
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"]) {
|
|
110759
|
+
response.writeHead(404).end("Not found");
|
|
110760
|
+
return;
|
|
110761
|
+
}
|
|
110762
|
+
const code = url.searchParams.get("code");
|
|
110763
|
+
const suppliedState = Buffer.from(url.searchParams.get("state") ?? "");
|
|
110764
|
+
const expectedState = Buffer.from(state);
|
|
110765
|
+
if (accepted || url.searchParams.getAll("state").length !== 1 || suppliedState.length !== expectedState.length || !timingSafeEqual(suppliedState, expectedState) || url.searchParams.getAll("code").length !== 1 || !code) {
|
|
110766
|
+
response.writeHead(400).end("Invalid sign-in response");
|
|
110767
|
+
return;
|
|
110768
|
+
}
|
|
110769
|
+
accepted = true;
|
|
110770
|
+
response.writeHead(200).end("Return to the terminal to finish signing in.");
|
|
110771
|
+
answer.resolve(code);
|
|
110772
|
+
});
|
|
110773
|
+
try {
|
|
110774
|
+
await new Promise((resolve, reject) => {
|
|
110775
|
+
server.once("error", reject);
|
|
110776
|
+
server.listen(0, "127.0.0.1", () => {
|
|
110777
|
+
server.off("error", reject);
|
|
110778
|
+
resolve();
|
|
110779
|
+
});
|
|
110780
|
+
});
|
|
110781
|
+
const address = server.address();
|
|
110782
|
+
if (!address || typeof address === "string")
|
|
110783
|
+
throw new SignInError("Unable to start the sign-in callback.");
|
|
110784
|
+
origin = `http://127.0.0.1:${address.port}`;
|
|
110785
|
+
const url = new URL(serviceOrigin(options.accounts));
|
|
110786
|
+
url.search = new URLSearchParams({
|
|
110787
|
+
redirect_uri: `${origin}/callback`,
|
|
110788
|
+
code_challenge: createHash10("sha256").update(verifier).digest("base64url"),
|
|
110789
|
+
code_challenge_method: "S256",
|
|
110790
|
+
state
|
|
110791
|
+
}).toString();
|
|
110792
|
+
const timer = setTimeout(abort, 5 * 60000);
|
|
110793
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
110794
|
+
try {
|
|
110795
|
+
options.signal?.throwIfAborted();
|
|
110796
|
+
const openerFailed = () => {
|
|
110797
|
+
if (!accepted)
|
|
110798
|
+
throw new BrowserUnavailable("No browser is available; use device sign-in.");
|
|
110799
|
+
};
|
|
110800
|
+
const [code] = await Promise.all([
|
|
110801
|
+
answer.promise,
|
|
110802
|
+
options.open(url.href).then((opened) => {
|
|
110803
|
+
if (!opened)
|
|
110804
|
+
openerFailed();
|
|
110805
|
+
}, openerFailed)
|
|
110806
|
+
]);
|
|
110807
|
+
return { code, codeVerifier: verifier };
|
|
110808
|
+
} finally {
|
|
110809
|
+
clearTimeout(timer);
|
|
110810
|
+
options.signal?.removeEventListener("abort", abort);
|
|
110811
|
+
}
|
|
110812
|
+
} finally {
|
|
110813
|
+
server.closeAllConnections();
|
|
110814
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
110815
|
+
}
|
|
110816
|
+
}
|
|
110817
|
+
async function loginLoopback(session, options) {
|
|
110818
|
+
await session.ready();
|
|
110819
|
+
const keys = generateDeviceKeys();
|
|
110820
|
+
for (;; ) {
|
|
110821
|
+
const authorization = await authorize({ accounts: options.accounts, open: options.open, signal: session.signal });
|
|
110822
|
+
try {
|
|
110823
|
+
const grant = parseReply(grantSchema, await session.request("/v1/session/exchange", jsonRequest({
|
|
110824
|
+
...authorization,
|
|
110825
|
+
kind: "cli",
|
|
110826
|
+
deviceName: options.name,
|
|
110827
|
+
platform: options.platform,
|
|
110828
|
+
...keys.public
|
|
110829
|
+
})));
|
|
110830
|
+
await session.save({ ...grant, ...keys.private });
|
|
110831
|
+
return;
|
|
110832
|
+
} catch (error) {
|
|
110833
|
+
if (await session.removeForLimit(error, options.chooseDevice))
|
|
110834
|
+
continue;
|
|
110835
|
+
throw new SignInError("Sign-in cancelled. No device was removed.");
|
|
110836
|
+
}
|
|
110837
|
+
}
|
|
110838
|
+
}
|
|
110839
|
+
|
|
110840
|
+
// packages/omo-opencode/src/cli/service-auth/session.ts
|
|
110841
|
+
import { mkdir as mkdir10 } from "node:fs/promises";
|
|
110842
|
+
import { homedir as homedir27 } from "node:os";
|
|
110843
|
+
import { join as join97 } from "node:path";
|
|
110844
|
+
|
|
110845
|
+
// packages/team-core/src/team-state-store/lock-publish.ts
|
|
110846
|
+
import { access as access3, open as open2, unlink as unlink2 } from "node:fs/promises";
|
|
110847
|
+
import { dirname as dirname38 } from "node:path";
|
|
110848
|
+
|
|
110849
|
+
// packages/team-core/src/team-state-store/lock-record.ts
|
|
110850
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
110851
|
+
import { readFile as readFile33, stat as stat8 } from "node:fs/promises";
|
|
110852
|
+
var lockOwnerInstanceId = randomUUID4();
|
|
110853
|
+
var heldLockRecords = new Set;
|
|
110854
|
+
function buildLockRecord(ownerTag) {
|
|
110855
|
+
return `${ownerTag} ${randomUUID4()}
|
|
110856
|
+
${process.pid}
|
|
110857
|
+
${Date.now()}
|
|
110858
|
+
${lockOwnerInstanceId}
|
|
110859
|
+
`;
|
|
110860
|
+
}
|
|
110861
|
+
function markLockRecordHeld(content) {
|
|
110862
|
+
heldLockRecords.add(content);
|
|
110863
|
+
}
|
|
110864
|
+
function markLockRecordReleased(content) {
|
|
110865
|
+
heldLockRecords.delete(content);
|
|
110866
|
+
}
|
|
110867
|
+
function errorCode(error) {
|
|
110868
|
+
if (!(error instanceof Error) || !("code" in error))
|
|
110869
|
+
return null;
|
|
110870
|
+
return typeof error.code === "string" ? error.code : null;
|
|
110871
|
+
}
|
|
110872
|
+
function isPathAbsenceError(error) {
|
|
110873
|
+
const code = errorCode(error);
|
|
110874
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
110875
|
+
}
|
|
110876
|
+
function parseLockOwner(content) {
|
|
110877
|
+
const lines = content.split(/\r?\n/).filter((line) => line.length > 0);
|
|
110878
|
+
if (lines.length !== 3 && lines.length !== 4)
|
|
110879
|
+
return null;
|
|
110880
|
+
const ownerPid = Number.parseInt(lines[1] ?? "", 10);
|
|
110881
|
+
const acquiredAtEpochMs = Number.parseInt(lines[2] ?? "", 10);
|
|
110882
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 0)
|
|
110883
|
+
return null;
|
|
110884
|
+
if (!Number.isInteger(acquiredAtEpochMs) || acquiredAtEpochMs <= 0)
|
|
110885
|
+
return null;
|
|
110886
|
+
return {
|
|
110887
|
+
ownerPid,
|
|
110888
|
+
acquiredAtEpochMs,
|
|
110889
|
+
instanceId: lines.length === 4 ? lines[3] ?? null : null
|
|
110890
|
+
};
|
|
110891
|
+
}
|
|
110892
|
+
function isPidAlive(pid) {
|
|
110893
|
+
try {
|
|
110894
|
+
process.kill(pid, 0);
|
|
110895
|
+
return true;
|
|
110896
|
+
} catch (error) {
|
|
110897
|
+
if (!(error instanceof Error)) {
|
|
110898
|
+
throw error;
|
|
110899
|
+
}
|
|
110900
|
+
return false;
|
|
110901
|
+
}
|
|
110902
|
+
}
|
|
110903
|
+
async function readLockRecord(lockPath) {
|
|
110904
|
+
try {
|
|
110905
|
+
const content = await readFile33(lockPath, "utf8");
|
|
110906
|
+
const { mtimeMs } = await stat8(lockPath);
|
|
110907
|
+
return { content, modifiedAtEpochMs: mtimeMs };
|
|
110908
|
+
} catch (error) {
|
|
110909
|
+
if (!(error instanceof Error))
|
|
110910
|
+
throw error;
|
|
110911
|
+
return null;
|
|
110912
|
+
}
|
|
110913
|
+
}
|
|
110914
|
+
function isLockRecordStale(record, staleAfterMs) {
|
|
110915
|
+
const owner = parseLockOwner(record.content);
|
|
110916
|
+
if (owner === null)
|
|
110917
|
+
return Date.now() - record.modifiedAtEpochMs > staleAfterMs;
|
|
110918
|
+
if (owner.instanceId !== null && owner.ownerPid === process.pid) {
|
|
110919
|
+
return owner.instanceId !== lockOwnerInstanceId || !heldLockRecords.has(record.content);
|
|
110920
|
+
}
|
|
110921
|
+
if (isPidAlive(owner.ownerPid))
|
|
110922
|
+
return false;
|
|
110923
|
+
if (owner.instanceId !== null)
|
|
110924
|
+
return true;
|
|
110925
|
+
return Date.now() - owner.acquiredAtEpochMs > staleAfterMs;
|
|
110926
|
+
}
|
|
110927
|
+
|
|
110928
|
+
// packages/team-core/src/team-state-store/lock-publish.ts
|
|
110929
|
+
async function pathMayExist(path, deps = {}) {
|
|
110930
|
+
const accessPath = deps.access ?? access3;
|
|
110931
|
+
try {
|
|
110932
|
+
await accessPath(path);
|
|
110933
|
+
return true;
|
|
110934
|
+
} catch (error) {
|
|
110935
|
+
if (!(error instanceof Error))
|
|
110936
|
+
throw error;
|
|
110937
|
+
return !isPathAbsenceError(error);
|
|
110938
|
+
}
|
|
110939
|
+
}
|
|
110940
|
+
async function assertRetryableLockOpenError(lockPath, error, deps) {
|
|
110941
|
+
const code = errorCode(error);
|
|
110942
|
+
if (code === "EEXIST")
|
|
110943
|
+
return;
|
|
110944
|
+
if (code === "EPERM") {
|
|
110945
|
+
if (await pathMayExist(lockPath, deps))
|
|
110946
|
+
return;
|
|
110947
|
+
if ((deps?.platform ?? process.platform) === "win32" && await pathMayExist(dirname38(lockPath), deps))
|
|
110948
|
+
return;
|
|
110949
|
+
}
|
|
110950
|
+
throw error;
|
|
110951
|
+
}
|
|
110952
|
+
async function publishLockRecord(lockPath, record) {
|
|
110953
|
+
markLockRecordHeld(record);
|
|
110954
|
+
let fileHandle;
|
|
110955
|
+
try {
|
|
110956
|
+
fileHandle = await open2(lockPath, "wx");
|
|
110957
|
+
} catch (error) {
|
|
110958
|
+
markLockRecordReleased(record);
|
|
110959
|
+
await assertRetryableLockOpenError(lockPath, error);
|
|
110960
|
+
return false;
|
|
110961
|
+
}
|
|
110962
|
+
try {
|
|
110963
|
+
await fileHandle.writeFile(record);
|
|
110964
|
+
} catch (error) {
|
|
110965
|
+
markLockRecordReleased(record);
|
|
110966
|
+
await fileHandle.close();
|
|
110967
|
+
await unlink2(lockPath).catch(() => {
|
|
110968
|
+
return;
|
|
110969
|
+
});
|
|
110970
|
+
throw error;
|
|
110971
|
+
}
|
|
110972
|
+
await fileHandle.close();
|
|
110973
|
+
return true;
|
|
110974
|
+
}
|
|
110975
|
+
|
|
110976
|
+
// packages/team-core/src/team-state-store/lock-reclaim.ts
|
|
110977
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
110978
|
+
import { unlink as unlink3 } from "node:fs/promises";
|
|
110979
|
+
var RECLAIM_UNLINK_ATTEMPTS = 3;
|
|
110980
|
+
var RECLAIM_UNLINK_RETRY_MS = 25;
|
|
110981
|
+
function delay2(ms) {
|
|
110982
|
+
return new Promise((resolve) => {
|
|
110983
|
+
setTimeout(resolve, ms);
|
|
110984
|
+
});
|
|
110985
|
+
}
|
|
110986
|
+
function reclaimGuardPath(lockPath, record) {
|
|
110987
|
+
return `${lockPath}.reclaim-${createHash11("sha256").update(record).digest("hex").slice(0, 16)}`;
|
|
110988
|
+
}
|
|
110989
|
+
async function unlinkWithRetry(path, deps) {
|
|
110990
|
+
const wait = deps.delay ?? delay2;
|
|
110991
|
+
const unlinkFile = deps.unlink ?? unlink3;
|
|
110992
|
+
for (let attempt = 1;; attempt += 1) {
|
|
110993
|
+
try {
|
|
110994
|
+
await unlinkFile(path);
|
|
110995
|
+
return "removed";
|
|
110996
|
+
} catch (error) {
|
|
110997
|
+
if (!(error instanceof Error))
|
|
110998
|
+
throw error;
|
|
110999
|
+
if (isPathAbsenceError(error))
|
|
111000
|
+
return "absent";
|
|
111001
|
+
const code = errorCode(error);
|
|
111002
|
+
if (code !== "EPERM" && code !== "EBUSY" || attempt === RECLAIM_UNLINK_ATTEMPTS)
|
|
111003
|
+
return "stuck";
|
|
111004
|
+
await wait(RECLAIM_UNLINK_RETRY_MS);
|
|
111005
|
+
}
|
|
111006
|
+
}
|
|
111007
|
+
}
|
|
111008
|
+
async function removeLockRecordIfUnchanged(lockPath, expectedRecord, staleAfterMs, deps = {}) {
|
|
111009
|
+
const guardPath = reclaimGuardPath(lockPath, expectedRecord);
|
|
111010
|
+
const guardRecord = buildLockRecord("lock-reclaim");
|
|
111011
|
+
if (!await publishLockRecord(guardPath, guardRecord)) {
|
|
111012
|
+
const guard = await readLockRecord(guardPath);
|
|
111013
|
+
if (guard !== null && isLockRecordStale(guard, staleAfterMs)) {
|
|
111014
|
+
await removeLockRecordIfUnchanged(guardPath, guard.content, staleAfterMs, deps);
|
|
111015
|
+
}
|
|
111016
|
+
return "busy";
|
|
111017
|
+
}
|
|
111018
|
+
try {
|
|
111019
|
+
const current = await readLockRecord(lockPath);
|
|
111020
|
+
if (current === null)
|
|
111021
|
+
return "absent";
|
|
111022
|
+
if (current.content !== expectedRecord || !isLockRecordStale(current, staleAfterMs))
|
|
111023
|
+
return "changed";
|
|
111024
|
+
const unlinked = await unlinkWithRetry(lockPath, deps);
|
|
111025
|
+
return unlinked === "absent" ? "absent" : unlinked;
|
|
111026
|
+
} finally {
|
|
111027
|
+
markLockRecordReleased(guardRecord);
|
|
111028
|
+
await unlinkWithRetry(guardPath, deps);
|
|
111029
|
+
}
|
|
111030
|
+
}
|
|
111031
|
+
|
|
111032
|
+
// packages/team-core/src/team-state-store/locks.ts
|
|
111033
|
+
var LOCK_RETRY_MS = 50;
|
|
111034
|
+
var LOCK_WAIT_TIMEOUT_MS = 15000;
|
|
111035
|
+
var DEFAULT_STALE_AFTER_MS = 300000;
|
|
111036
|
+
function delay3(ms) {
|
|
111037
|
+
return new Promise((resolve) => {
|
|
111038
|
+
setTimeout(resolve, ms);
|
|
111039
|
+
});
|
|
111040
|
+
}
|
|
111041
|
+
async function acquireLock(lockPath, ownerTag, staleAfterMs) {
|
|
111042
|
+
const startedAt = Date.now();
|
|
111043
|
+
for (;; ) {
|
|
111044
|
+
if (Date.now() - startedAt > LOCK_WAIT_TIMEOUT_MS) {
|
|
111045
|
+
throw new Error(`Timed out acquiring lock: ${lockPath}`);
|
|
111046
|
+
}
|
|
111047
|
+
const record = buildLockRecord(ownerTag);
|
|
111048
|
+
if (await publishLockRecord(lockPath, record))
|
|
111049
|
+
return record;
|
|
111050
|
+
const outcome = await reapStaleLock(lockPath, staleAfterMs);
|
|
111051
|
+
if (outcome === "removed" || outcome === "absent")
|
|
111052
|
+
continue;
|
|
111053
|
+
await delay3(LOCK_RETRY_MS);
|
|
111054
|
+
}
|
|
111055
|
+
}
|
|
111056
|
+
async function withLock(lockPath, fn, opts) {
|
|
111057
|
+
const staleAfterMs = opts?.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
111058
|
+
const ownerTag = opts?.ownerTag ?? "owner";
|
|
111059
|
+
const record = await acquireLock(lockPath, ownerTag, staleAfterMs);
|
|
111060
|
+
try {
|
|
111061
|
+
return await fn();
|
|
111062
|
+
} finally {
|
|
111063
|
+
markLockRecordReleased(record);
|
|
111064
|
+
await removeLockRecordIfUnchanged(lockPath, record, staleAfterMs);
|
|
111065
|
+
}
|
|
111066
|
+
}
|
|
111067
|
+
async function reapStaleLock(lockPath, staleAfterMs = DEFAULT_STALE_AFTER_MS, deps = {}) {
|
|
111068
|
+
const observed = await readLockRecord(lockPath);
|
|
111069
|
+
if (observed === null)
|
|
111070
|
+
return "absent";
|
|
111071
|
+
if (!isLockRecordStale(observed, staleAfterMs))
|
|
111072
|
+
return "changed";
|
|
111073
|
+
return await removeLockRecordIfUnchanged(lockPath, observed.content, staleAfterMs, deps);
|
|
111074
|
+
}
|
|
111075
|
+
|
|
111076
|
+
// packages/omo-opencode/src/cli/service-auth/session.ts
|
|
111077
|
+
class SessionEnded extends SignInError {
|
|
111078
|
+
deviceRevoked;
|
|
111079
|
+
constructor(message, deviceRevoked) {
|
|
111080
|
+
super(message);
|
|
111081
|
+
this.deviceRevoked = deviceRevoked;
|
|
111082
|
+
}
|
|
111083
|
+
}
|
|
111084
|
+
var DEVICE_REVOKING_REFUSALS = new Set(["account_deleted", "reauth_required"]);
|
|
111085
|
+
function createSession(options) {
|
|
111086
|
+
const api = serviceOrigin(options.api);
|
|
111087
|
+
const { store, signal } = options;
|
|
111088
|
+
const directory = join97(options.home ?? homedir27(), ".omo", "session-locks");
|
|
111089
|
+
const lockPath = join97(directory, `${credentialId(api)}.lock`);
|
|
111090
|
+
async function locked(action) {
|
|
111091
|
+
await mkdir10(directory, { recursive: true, mode: 448 });
|
|
111092
|
+
signal?.throwIfAborted();
|
|
111093
|
+
return withLock(lockPath, async () => {
|
|
111094
|
+
signal?.throwIfAborted();
|
|
111095
|
+
return action();
|
|
111096
|
+
}, { ownerTag: "cli-session", staleAfterMs: Number.POSITIVE_INFINITY });
|
|
111097
|
+
}
|
|
111098
|
+
const request = (path, init) => requestApi(api, path, init, signal);
|
|
111099
|
+
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);
|
|
111100
|
+
async function refreshedAccess(current) {
|
|
111101
|
+
if (current.refreshState === "uncertain")
|
|
111102
|
+
throw uncertain();
|
|
111103
|
+
if (Date.parse(current.accessTokenExpiresAt) > Date.now() + 30000)
|
|
111104
|
+
return current.accessToken;
|
|
111105
|
+
await store.write({ ...current, refreshState: "uncertain" });
|
|
111106
|
+
try {
|
|
111107
|
+
const tokens = parseReply(tokenSchema, await request("/v1/session/refresh", jsonRequest({ refreshToken: current.refreshToken })));
|
|
111108
|
+
await store.write({ ...current, ...tokens });
|
|
111109
|
+
return tokens.accessToken;
|
|
111110
|
+
} catch (error) {
|
|
111111
|
+
if (error instanceof ApiRefusal && error.status < 500 && ["unauthorized", "account_deleted", "reauth_required", "invalid_grant"].includes(error.code)) {
|
|
111112
|
+
await store.clear();
|
|
111113
|
+
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));
|
|
111114
|
+
}
|
|
111115
|
+
if (error instanceof RequestFailure && error.notSent || error instanceof ApiRefusal && error.status === 429 && error.code === "rate_limited") {
|
|
111116
|
+
await store.write(current);
|
|
111117
|
+
throw error;
|
|
111118
|
+
}
|
|
111119
|
+
throw uncertain();
|
|
111120
|
+
}
|
|
111121
|
+
}
|
|
111122
|
+
return {
|
|
111123
|
+
request,
|
|
111124
|
+
signal,
|
|
111125
|
+
async ready() {
|
|
111126
|
+
await store.read();
|
|
111127
|
+
},
|
|
111128
|
+
async save(value) {
|
|
111129
|
+
await locked(() => store.write(value));
|
|
111130
|
+
},
|
|
111131
|
+
async logout() {
|
|
111132
|
+
return locked(async () => {
|
|
111133
|
+
try {
|
|
111134
|
+
const current = await store.read();
|
|
111135
|
+
if (current !== null) {
|
|
111136
|
+
if (current.refreshState === "uncertain")
|
|
111137
|
+
return false;
|
|
111138
|
+
const access = Date.parse(current.accessTokenExpiresAt) <= Date.now() ? await refreshedAccess(current) : current.accessToken;
|
|
111139
|
+
await request(`/v1/devices/${encodeURIComponent(current.device.id)}`, {
|
|
111140
|
+
method: "DELETE",
|
|
111141
|
+
headers: { authorization: `Bearer ${access}` }
|
|
111142
|
+
});
|
|
111143
|
+
}
|
|
111144
|
+
return true;
|
|
111145
|
+
} catch (error) {
|
|
111146
|
+
if (error instanceof SessionEnded)
|
|
111147
|
+
return error.deviceRevoked;
|
|
111148
|
+
if (error instanceof SignInError)
|
|
111149
|
+
return false;
|
|
111150
|
+
throw error;
|
|
111151
|
+
} finally {
|
|
111152
|
+
await store.clear();
|
|
111153
|
+
}
|
|
111154
|
+
});
|
|
111155
|
+
},
|
|
111156
|
+
async accessToken() {
|
|
111157
|
+
return locked(async () => {
|
|
111158
|
+
const current = await store.read();
|
|
111159
|
+
if (current === null)
|
|
111160
|
+
throw new SignInError("Not signed in. Run omo login.");
|
|
111161
|
+
return refreshedAccess(current);
|
|
111162
|
+
});
|
|
111163
|
+
},
|
|
111164
|
+
async removeForLimit(error, choose) {
|
|
111165
|
+
if (!(error instanceof ApiRefusal) || error.detail === undefined)
|
|
111166
|
+
throw error;
|
|
111167
|
+
const headers = { authorization: `Bearer ${error.detail.managementToken}` };
|
|
111168
|
+
const listed = parseReply(deviceListSchema, await request("/v1/devices", { headers }));
|
|
111169
|
+
const devices = listed.devices.filter((device) => device.revokedAt === null);
|
|
111170
|
+
const chosen = await choose(devices);
|
|
111171
|
+
if (chosen === null)
|
|
111172
|
+
return false;
|
|
111173
|
+
if (!devices.some((device) => device.id === chosen))
|
|
111174
|
+
throw new SignInError("No matching device was selected.");
|
|
111175
|
+
await request(`/v1/devices/${encodeURIComponent(chosen)}`, { method: "DELETE", headers });
|
|
111176
|
+
return true;
|
|
111177
|
+
}
|
|
111178
|
+
};
|
|
111179
|
+
}
|
|
111180
|
+
|
|
111181
|
+
// packages/omo-opencode/src/cli/service-auth/commands.ts
|
|
111182
|
+
async function ensureServiceAvailable(session, api) {
|
|
111183
|
+
try {
|
|
111184
|
+
await session.request("/v1/health", { method: "GET" });
|
|
111185
|
+
} catch (error) {
|
|
111186
|
+
if (!(error instanceof SignInError))
|
|
111187
|
+
throw error;
|
|
111188
|
+
if (error instanceof RequestFailure) {
|
|
111189
|
+
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.`);
|
|
111190
|
+
}
|
|
111191
|
+
throw new SignInError(`OmO accounts at ${api} aren't available right now. Try again later.`);
|
|
111192
|
+
}
|
|
111193
|
+
}
|
|
111194
|
+
function deviceChoices(devices) {
|
|
111195
|
+
return devices.map((device) => ({ value: device.id, label: terminalText(device.name) }));
|
|
111196
|
+
}
|
|
111197
|
+
var chooseDevice = async (devices) => {
|
|
111198
|
+
if (!process.stdin.isTTY || devices.length === 0) {
|
|
111199
|
+
console.error("Device limit reached. Run omo login in an interactive terminal to choose a device to remove.");
|
|
111200
|
+
return null;
|
|
111201
|
+
}
|
|
111202
|
+
const chosen = await select({
|
|
111203
|
+
message: "Device limit reached. Remove a device to sign in?",
|
|
111204
|
+
options: [{ value: "", label: "Cancel (keep all devices)" }, ...deviceChoices(devices)]
|
|
111205
|
+
});
|
|
111206
|
+
if (isCancel(chosen) || chosen === "")
|
|
111207
|
+
return null;
|
|
111208
|
+
const agreed = await confirm({ message: "Sign out the selected device?", initialValue: false });
|
|
111209
|
+
return agreed === true ? chosen : null;
|
|
111210
|
+
};
|
|
111211
|
+
var DEFAULT_ACCOUNTS_ORIGIN = "https://signin.omo.dev";
|
|
111212
|
+
function accountsOrigin(options, env) {
|
|
111213
|
+
return serviceOrigin(options.accounts ?? env.OMO_SERVICE_ACCOUNTS_URL ?? DEFAULT_ACCOUNTS_ORIGIN);
|
|
111214
|
+
}
|
|
111215
|
+
async function run2(action, options) {
|
|
111216
|
+
const controller = new AbortController;
|
|
111217
|
+
const cancel = () => controller.abort();
|
|
111218
|
+
process.once("SIGINT", cancel);
|
|
111219
|
+
process.once("SIGTERM", cancel);
|
|
111220
|
+
try {
|
|
111221
|
+
const api = serviceOrigin(options.api ?? process.env.OMO_SERVICE_API_URL ?? "https://api.omo.dev");
|
|
111222
|
+
const store = createCredentialStore(api);
|
|
111223
|
+
const session = createSession({ api, store, signal: controller.signal });
|
|
111224
|
+
if (action === "logout") {
|
|
111225
|
+
const revoked = await session.logout();
|
|
111226
|
+
console.log("Signed out of OmO. Stored credentials and device keys were removed.");
|
|
111227
|
+
if (!revoked)
|
|
111228
|
+
console.error("Server device revocation could not be confirmed. Remove this device from the Devices page of your OmO account when connected.");
|
|
111229
|
+
return;
|
|
111230
|
+
}
|
|
111231
|
+
if (action === "whoami") {
|
|
111232
|
+
await session.accessToken();
|
|
111233
|
+
const saved = await store.read();
|
|
111234
|
+
console.log(saved ? `Signed in to ${api} as ${terminalText(saved.device.name)}.` : "Not signed in.");
|
|
111235
|
+
return;
|
|
111236
|
+
}
|
|
111237
|
+
await ensureServiceAvailable(session, api);
|
|
111238
|
+
const name = deviceName();
|
|
111239
|
+
const platform = devicePlatform();
|
|
111240
|
+
if (!options.device && options.browser !== false) {
|
|
111241
|
+
try {
|
|
111242
|
+
await loginLoopback(session, {
|
|
111243
|
+
accounts: accountsOrigin(options, process.env),
|
|
111244
|
+
name,
|
|
111245
|
+
platform,
|
|
111246
|
+
open: openSignInBrowser,
|
|
111247
|
+
chooseDevice
|
|
111248
|
+
});
|
|
111249
|
+
console.log("Signed in to OmO.");
|
|
111250
|
+
return;
|
|
111251
|
+
} catch (error) {
|
|
111252
|
+
if (!(error instanceof BrowserUnavailable))
|
|
111253
|
+
throw error;
|
|
111254
|
+
}
|
|
111255
|
+
}
|
|
111256
|
+
await loginDevice(session, {
|
|
111257
|
+
name,
|
|
111258
|
+
platform,
|
|
111259
|
+
chooseDevice,
|
|
111260
|
+
show: (code) => {
|
|
111261
|
+
console.log(`Open ${code.verificationUri}`);
|
|
111262
|
+
console.log(`User code: ${code.userCode}`);
|
|
111263
|
+
console.log(`Matching code: ${code.matchingCode}`);
|
|
111264
|
+
}
|
|
111265
|
+
});
|
|
111266
|
+
console.log("Signed in to OmO.");
|
|
111267
|
+
} catch (error) {
|
|
111268
|
+
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.");
|
|
111269
|
+
process.exitCode = 1;
|
|
111270
|
+
} finally {
|
|
111271
|
+
process.removeListener("SIGINT", cancel);
|
|
111272
|
+
process.removeListener("SIGTERM", cancel);
|
|
111273
|
+
}
|
|
111274
|
+
}
|
|
111275
|
+
function configureServiceAuthCommands(program) {
|
|
111276
|
+
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));
|
|
111277
|
+
for (const action of ["logout", "whoami"]) {
|
|
111278
|
+
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));
|
|
111279
|
+
}
|
|
111280
|
+
}
|
|
111281
|
+
|
|
111282
|
+
// packages/omo-opencode/src/cli/install-command-options.ts
|
|
111283
|
+
function resolveInstallArgs(options, invocationName = process.env.OMO_INVOCATION_NAME) {
|
|
111284
|
+
const defaultPlatform = process.env.OMO_EDITION === "codex" || invocationName === "lazycodex" || invocationName === "lazycodex-ai" ? "codex" : undefined;
|
|
111285
|
+
const platform = options.platform ?? defaultPlatform;
|
|
111286
|
+
if (platform === "native-dev" && !isNativeDevPlatformEnabled()) {
|
|
111287
|
+
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.`);
|
|
111288
|
+
}
|
|
111289
|
+
return {
|
|
111290
|
+
tui: options.tui !== false,
|
|
111291
|
+
claude: options.claude,
|
|
111292
|
+
openai: options.openai,
|
|
111293
|
+
gemini: options.gemini,
|
|
111294
|
+
copilot: options.copilot,
|
|
111295
|
+
platform,
|
|
111296
|
+
opencodeZen: options.opencodeZen,
|
|
111297
|
+
zaiCodingPlan: options.zaiCodingPlan,
|
|
111298
|
+
kimiForCoding: options.kimiForCoding,
|
|
111299
|
+
opencodeGo: options.opencodeGo,
|
|
111300
|
+
bailianCodingPlan: options.bailianCodingPlan,
|
|
111301
|
+
minimaxCnCodingPlan: options.minimaxCnCodingPlan,
|
|
111302
|
+
minimaxCodingPlan: options.minimaxCodingPlan,
|
|
111303
|
+
vercelAiGateway: options.vercelAiGateway,
|
|
111304
|
+
codexAutonomous: options.codexAutonomous,
|
|
111305
|
+
skipAuth: options.skipAuth ?? false
|
|
111306
|
+
};
|
|
111307
|
+
}
|
|
111308
|
+
|
|
110386
111309
|
// packages/omo-opencode/src/cli/boulder/boulder.ts
|
|
110387
111310
|
import { existsSync as existsSync64 } from "node:fs";
|
|
110388
111311
|
|
|
@@ -110543,11 +111466,11 @@ async function boulder(options) {
|
|
|
110543
111466
|
// packages/omo-opencode/src/cli/codex-ulw-loop.ts
|
|
110544
111467
|
import { spawn as spawn5 } from "node:child_process";
|
|
110545
111468
|
import { existsSync as existsSync65, readFileSync as readFileSync43, realpathSync as realpathSync11 } from "node:fs";
|
|
110546
|
-
import { homedir as
|
|
111469
|
+
import { homedir as homedir28 } from "node:os";
|
|
110547
111470
|
var ULW_LOOP_DELEGATION_SENTINEL = "OMO_ULW_LOOP_DELEGATED";
|
|
110548
111471
|
function resolveCodexUlwLoopCommand(input = {}) {
|
|
110549
111472
|
const env = input.env ?? process.env;
|
|
110550
|
-
const homeDir = input.homeDir ??
|
|
111473
|
+
const homeDir = input.homeDir ?? homedir28();
|
|
110551
111474
|
const localComponentBin = resolveLocalUlwLoopBin(env, homeDir);
|
|
110552
111475
|
if (localComponentBin !== null)
|
|
110553
111476
|
return { executable: localComponentBin, argsPrefix: [] };
|
|
@@ -110788,7 +111711,7 @@ function linkedWorktrees(records) {
|
|
|
110788
111711
|
}
|
|
110789
111712
|
// packages/omo-opencode/src/cli/worktree-sweep/sweep.ts
|
|
110790
111713
|
import { existsSync as existsSync66 } from "node:fs";
|
|
110791
|
-
import { stat as
|
|
111714
|
+
import { stat as stat9 } from "node:fs/promises";
|
|
110792
111715
|
import os7 from "node:os";
|
|
110793
111716
|
import { normalize as normalize2 } from "node:path";
|
|
110794
111717
|
|
|
@@ -110871,7 +111794,7 @@ async function isOlderThan(worktreePath, days, now) {
|
|
|
110871
111794
|
if (days <= 0)
|
|
110872
111795
|
return false;
|
|
110873
111796
|
try {
|
|
110874
|
-
const stats = await
|
|
111797
|
+
const stats = await stat9(worktreePath);
|
|
110875
111798
|
return now - stats.mtimeMs > days * DAY_MS;
|
|
110876
111799
|
} catch {
|
|
110877
111800
|
return false;
|
|
@@ -111088,31 +112011,6 @@ function runConfigMigrate(options = {}) {
|
|
|
111088
112011
|
init_package();
|
|
111089
112012
|
var VERSION3 = package_default.version;
|
|
111090
112013
|
var program2 = new Command;
|
|
111091
|
-
function resolveInstallArgs(options, invocationName = process.env.OMO_INVOCATION_NAME) {
|
|
111092
|
-
const defaultPlatform = process.env.OMO_EDITION === "codex" || invocationName === "lazycodex" || invocationName === "lazycodex-ai" ? "codex" : undefined;
|
|
111093
|
-
const platform = options.platform ?? defaultPlatform;
|
|
111094
|
-
if (platform === "native-dev" && !isNativeDevPlatformEnabled()) {
|
|
111095
|
-
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.`);
|
|
111096
|
-
}
|
|
111097
|
-
return {
|
|
111098
|
-
tui: options.tui !== false,
|
|
111099
|
-
claude: options.claude,
|
|
111100
|
-
openai: options.openai,
|
|
111101
|
-
gemini: options.gemini,
|
|
111102
|
-
copilot: options.copilot,
|
|
111103
|
-
platform,
|
|
111104
|
-
opencodeZen: options.opencodeZen,
|
|
111105
|
-
zaiCodingPlan: options.zaiCodingPlan,
|
|
111106
|
-
kimiForCoding: options.kimiForCoding,
|
|
111107
|
-
opencodeGo: options.opencodeGo,
|
|
111108
|
-
bailianCodingPlan: options.bailianCodingPlan,
|
|
111109
|
-
minimaxCnCodingPlan: options.minimaxCnCodingPlan,
|
|
111110
|
-
minimaxCodingPlan: options.minimaxCodingPlan,
|
|
111111
|
-
vercelAiGateway: options.vercelAiGateway,
|
|
111112
|
-
codexAutonomous: options.codexAutonomous,
|
|
111113
|
-
skipAuth: options.skipAuth ?? false
|
|
111114
|
-
};
|
|
111115
|
-
}
|
|
111116
112014
|
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();
|
|
111117
112015
|
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", `
|
|
111118
112016
|
Examples:
|
|
@@ -111230,6 +112128,7 @@ program2.command("config").description("Manage unified OMO configuration").comma
|
|
|
111230
112128
|
});
|
|
111231
112129
|
configureRuntimeCommands(program2);
|
|
111232
112130
|
program2.addCommand(createMcpOAuthCommand());
|
|
112131
|
+
configureServiceAuthCommands(program2);
|
|
111233
112132
|
function runCli() {
|
|
111234
112133
|
program2.parse();
|
|
111235
112134
|
}
|