@steipete/oracle 0.14.0 → 0.14.1
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.md +2 -2
- package/dist/bin/oracle-cli.js +8 -8
- package/dist/src/browser/actions/assistantResponse.js +2 -1
- package/dist/src/browser/actions/modelSelection.js +344 -29
- package/dist/src/browser/actions/thinkingTime.js +239 -46
- package/dist/src/browser/chatgptImages.js +126 -24
- package/dist/src/browser/index.js +63 -8
- package/dist/src/browser/projectSourcesRunner.js +2 -1
- package/dist/src/browser/prompt.js +151 -22
- package/dist/src/cli/browserConfig.js +2 -1
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/options.js +8 -0
- package/dist/src/mcp/tools/chatgptImage.js +8 -3
- package/dist/src/mcp/tools/consult.js +9 -8
- package/dist/src/mcp/types.js +11 -2
- package/dist/src/oracle/thinkingTime.js +40 -0
- package/dist/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/package.json +32 -34
- package/vendor/oracle-notifier/build-notifier.sh +0 -0
- package/dist/bin/oracle.js +0 -569
- package/dist/docs-site/.nojekyll +0 -0
- package/dist/docs-site/CNAME +0 -1
- package/dist/docs-site/RELEASING.html +0 -410
- package/dist/docs-site/agents.html +0 -374
- package/dist/docs-site/anthropic.html +0 -368
- package/dist/docs-site/bridge.html +0 -400
- package/dist/docs-site/browser-mode.html +0 -593
- package/dist/docs-site/chromium-forks.html +0 -347
- package/dist/docs-site/cli-reference.html +0 -346
- package/dist/docs-site/configuration.html +0 -452
- package/dist/docs-site/favicon.svg +0 -14
- package/dist/docs-site/followup.html +0 -375
- package/dist/docs-site/gemini.html +0 -383
- package/dist/docs-site/grok.html +0 -325
- package/dist/docs-site/index.html +0 -360
- package/dist/docs-site/install.html +0 -335
- package/dist/docs-site/linux.html +0 -321
- package/dist/docs-site/llms.txt +0 -43
- package/dist/docs-site/manual-tests.html +0 -596
- package/dist/docs-site/mcp.html +0 -391
- package/dist/docs-site/multimodel.html +0 -364
- package/dist/docs-site/mythical-pro-agents.html +0 -360
- package/dist/docs-site/notifier.html +0 -338
- package/dist/docs-site/openai-endpoints.html +0 -387
- package/dist/docs-site/openrouter.html +0 -344
- package/dist/docs-site/quickstart.html +0 -369
- package/dist/docs-site/refactor/ux.html +0 -532
- package/dist/docs-site/sessions.html +0 -388
- package/dist/docs-site/social-card.png +0 -0
- package/dist/docs-site/social-card.svg +0 -79
- package/dist/docs-site/spec.html +0 -363
- package/dist/docs-site/testing.html +0 -320
- package/dist/docs-site/tui-debug.html +0 -326
- package/dist/docs-site/windows-work.html +0 -323
- package/dist/docs-site/windows.html +0 -320
- package/dist/src/browser/chromeCookies.js +0 -312
- package/dist/src/browser/keytarShim.js +0 -56
- package/dist/src/browser/windowsCookies.js +0 -219
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +0 -20
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +0 -128
- package/vendor/oracle-notifier/README.md +0 -26
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Windows compatibility notes — oracle</title>
|
|
7
|
-
<meta name="description" content="Windows compatibility notes — oracle CLI documentation.">
|
|
8
|
-
<link rel="canonical" href="https://askoracle.sh/windows.html">
|
|
9
|
-
<meta property="og:type" content="website">
|
|
10
|
-
<meta property="og:site_name" content="oracle">
|
|
11
|
-
<meta property="og:title" content="Windows compatibility notes — oracle">
|
|
12
|
-
<meta property="og:description" content="Windows compatibility notes — oracle CLI documentation.">
|
|
13
|
-
<meta property="og:url" content="https://askoracle.sh/windows.html">
|
|
14
|
-
<meta property="og:image" content="https://askoracle.sh/social-card.png">
|
|
15
|
-
<meta property="og:image:width" content="1200">
|
|
16
|
-
<meta property="og:image:height" content="630">
|
|
17
|
-
<meta name="twitter:card" content="summary_large_image">
|
|
18
|
-
<meta name="twitter:title" content="Windows compatibility notes — oracle">
|
|
19
|
-
<meta name="twitter:description" content="Windows compatibility notes — oracle CLI documentation.">
|
|
20
|
-
<meta name="twitter:image" content="https://askoracle.sh/social-card.png">
|
|
21
|
-
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
|
22
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
23
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
24
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
25
|
-
<script>(function(){var s;try{s=localStorage.getItem('theme')}catch(e){}var d=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.dataset.theme=s||(d?'dark':'light')})();</script>
|
|
26
|
-
<style>
|
|
27
|
-
:root{
|
|
28
|
-
--ink:#0e0a1f;
|
|
29
|
-
--text:#1d1832;
|
|
30
|
-
--muted:#6b6582;
|
|
31
|
-
--subtle:#9c95b5;
|
|
32
|
-
--bg:#fafaff;
|
|
33
|
-
--paper:#ffffff;
|
|
34
|
-
--accent:#7c3aed;
|
|
35
|
-
--accent-soft:rgba(124,58,237,.10);
|
|
36
|
-
--accent-strong:#5b21b6;
|
|
37
|
-
--accent-2:#0ea5e9;
|
|
38
|
-
--accent-glow:#a78bfa;
|
|
39
|
-
--line:#e7e3f0;
|
|
40
|
-
--line-soft:#f2eff8;
|
|
41
|
-
--code-bg:#0c0a1a;
|
|
42
|
-
--code-fg:#e9e6f5;
|
|
43
|
-
--code-inline-fg:#1c1734;
|
|
44
|
-
--pill-border:#dcd5ec;
|
|
45
|
-
--shadow-card:0 4px 14px rgba(14,10,31,.08);
|
|
46
|
-
--scrollbar:#cbc4dc;
|
|
47
|
-
}
|
|
48
|
-
:root[data-theme="dark"]{
|
|
49
|
-
--ink:#f5f2ff;
|
|
50
|
-
--text:#cec7e4;
|
|
51
|
-
--muted:#9189a8;
|
|
52
|
-
--subtle:#6a6383;
|
|
53
|
-
--bg:#0a0815;
|
|
54
|
-
--paper:#15102a;
|
|
55
|
-
--accent:#a78bfa;
|
|
56
|
-
--accent-soft:rgba(167,139,250,.18);
|
|
57
|
-
--accent-strong:#c4b5fd;
|
|
58
|
-
--accent-2:#38bdf8;
|
|
59
|
-
--accent-glow:#c4b5fd;
|
|
60
|
-
--line:#272140;
|
|
61
|
-
--line-soft:#1c1734;
|
|
62
|
-
--code-bg:#06040d;
|
|
63
|
-
--code-fg:#e9e6f5;
|
|
64
|
-
--code-inline-fg:#e9e6f5;
|
|
65
|
-
--pill-border:#2c2549;
|
|
66
|
-
--shadow-card:0 4px 18px rgba(0,0,0,.45);
|
|
67
|
-
--scrollbar:#3a3358;
|
|
68
|
-
}
|
|
69
|
-
:root{color-scheme:light}
|
|
70
|
-
:root[data-theme="dark"]{color-scheme:dark}
|
|
71
|
-
*{box-sizing:border-box}
|
|
72
|
-
html{scroll-behavior:smooth;scroll-padding-top:24px}
|
|
73
|
-
body{margin:0;background:var(--bg);color:var(--text);font-family:"Inter",ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;line-height:1.65;overflow-x:hidden;-webkit-font-smoothing:antialiased;font-feature-settings:"cv02","cv03","cv04","cv11";transition:background-color .18s,color .18s}
|
|
74
|
-
::selection{background:var(--accent);color:#fff}
|
|
75
|
-
a{color:var(--accent);text-decoration:none;transition:color .12s}
|
|
76
|
-
a:hover{text-decoration:underline;text-underline-offset:.2em}
|
|
77
|
-
.shell{display:grid;grid-template-columns:268px minmax(0,1fr);min-height:100vh}
|
|
78
|
-
.sidebar{position:sticky;top:0;height:100vh;overflow:auto;padding:24px 22px;background:var(--paper);border-right:1px solid var(--line);scrollbar-width:thin;scrollbar-color:var(--line) transparent;transition:background-color .18s,border-color .18s}
|
|
79
|
-
.sidebar::-webkit-scrollbar{width:6px}
|
|
80
|
-
.sidebar::-webkit-scrollbar-thumb{background:var(--line);border-radius:6px}
|
|
81
|
-
.sidebar-head{display:flex;align-items:center;gap:10px;margin-bottom:24px}
|
|
82
|
-
.brand{display:flex;align-items:center;gap:11px;color:var(--ink);text-decoration:none;flex:1;min-width:0}
|
|
83
|
-
.brand:hover{text-decoration:none}
|
|
84
|
-
.brand .mark{flex:0 0 30px;width:30px;height:30px;border-radius:50%;background:radial-gradient(circle at 32% 32%,#fff 0,#e0d4ff 22%,#7c3aed 60%,#1e1240 100%);box-shadow:0 0 0 1px rgba(124,58,237,.35),0 6px 18px rgba(124,58,237,.35);position:relative}
|
|
85
|
-
.brand .mark::after{content:"";position:absolute;left:50%;top:50%;width:9px;height:9px;border-radius:50%;background:#0c0820;transform:translate(-50%,-50%)}
|
|
86
|
-
.brand strong{display:block;font-size:1.05rem;line-height:1.1;font-weight:600;letter-spacing:0;color:var(--ink)}
|
|
87
|
-
.brand small{display:block;color:var(--muted);font-size:.74rem;margin-top:3px;font-weight:400}
|
|
88
|
-
.theme-toggle{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:34px;height:34px;border-radius:8px;border:1px solid var(--line);background:var(--paper);color:var(--muted);cursor:pointer;padding:0;transition:border-color .15s,color .15s,background-color .15s,transform .12s}
|
|
89
|
-
.theme-toggle:hover{border-color:var(--ink);color:var(--ink)}
|
|
90
|
-
.theme-toggle:active{transform:scale(.94)}
|
|
91
|
-
.theme-toggle svg{width:16px;height:16px;display:block}
|
|
92
|
-
.theme-icon-sun{display:none}
|
|
93
|
-
:root[data-theme="dark"] .theme-icon-sun{display:block}
|
|
94
|
-
:root[data-theme="dark"] .theme-icon-moon{display:none}
|
|
95
|
-
.search{display:block;margin:0 0 22px}
|
|
96
|
-
.search span{display:block;color:var(--muted);font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:0;margin-bottom:7px}
|
|
97
|
-
.search input{width:100%;border:1px solid var(--line);background:var(--paper);border-radius:8px;padding:9px 12px;font:inherit;font-size:.9rem;color:var(--text);outline:none;transition:border-color .15s,box-shadow .15s,background-color .18s}
|
|
98
|
-
.search input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
|
|
99
|
-
nav section{margin:0 0 18px}
|
|
100
|
-
nav h2{font-size:.68rem;color:var(--muted);text-transform:uppercase;letter-spacing:0;margin:0 0 6px;font-weight:600}
|
|
101
|
-
.nav-link{display:block;color:var(--text);text-decoration:none;border-radius:6px;padding:5px 10px;margin:1px 0;font-size:.9rem;line-height:1.4;transition:background .12s,color .12s}
|
|
102
|
-
.nav-link:hover{background:var(--line-soft);color:var(--ink);text-decoration:none}
|
|
103
|
-
.nav-link.active{background:var(--accent-soft);color:var(--accent);font-weight:600}
|
|
104
|
-
main{min-width:0;padding:32px clamp(20px,4.5vw,56px) 80px;max-width:1180px;margin:0 auto;width:100%}
|
|
105
|
-
.hero{display:flex;align-items:flex-end;justify-content:space-between;gap:22px;border-bottom:1px solid var(--line);padding:8px 0 22px;margin-bottom:8px;flex-wrap:wrap}
|
|
106
|
-
.hero-text{min-width:0;flex:1 1 320px}
|
|
107
|
-
.eyebrow{margin:0 0 8px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:0;font-size:.7rem}
|
|
108
|
-
.hero h1{font-size:2.25rem;line-height:1.1;letter-spacing:0;margin:0;font-weight:700;color:var(--ink)}
|
|
109
|
-
.hero-meta{display:flex;gap:8px;flex:0 0 auto;flex-wrap:wrap}
|
|
110
|
-
.repo,.edit,.btn-ghost{border:1px solid var(--line);color:var(--text);text-decoration:none;border-radius:7px;padding:6px 11px;font-weight:500;font-size:.83rem;background:var(--paper);transition:border-color .15s,color .15s,background .15s}
|
|
111
|
-
.repo:hover,.edit:hover,.btn-ghost:hover{border-color:var(--ink);color:var(--ink);text-decoration:none}
|
|
112
|
-
.edit{color:var(--muted)}
|
|
113
|
-
.home-hero{position:relative;padding:24px 0 32px;margin-bottom:8px;border-bottom:1px solid var(--line)}
|
|
114
|
-
.home-hero>*{position:relative;z-index:1}
|
|
115
|
-
.home-hero h1{font-size:3.4rem;line-height:1.04;letter-spacing:0;margin:0 0 .35em;font-weight:700;color:var(--ink);background:linear-gradient(135deg,var(--ink) 0,#7c3aed 60%,#0ea5e9 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
|
116
|
-
:root[data-theme="dark"] .home-hero h1{background:linear-gradient(135deg,#f5f2ff 0,#c4b5fd 50%,#7dd3fc 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
|
117
|
-
.home-hero .lede{font-size:1.18rem;line-height:1.55;color:var(--text);margin:0 0 1.2em;max-width:62ch}
|
|
118
|
-
.home-cta{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:0 0 18px}
|
|
119
|
-
.home-cta .btn{display:inline-flex;align-items:center;gap:7px;border-radius:8px;padding:10px 16px;font-weight:600;font-size:.92rem;text-decoration:none;transition:background .15s,border-color .15s,color .15s,transform .12s}
|
|
120
|
-
.home-cta .btn-primary{background:linear-gradient(135deg,var(--accent),var(--accent-strong));color:#fff;border:1px solid var(--accent);box-shadow:0 4px 14px rgba(124,58,237,.35)}
|
|
121
|
-
.home-cta .btn-primary:hover{background:var(--accent-strong);border-color:var(--accent-strong);text-decoration:none;transform:translateY(-1px)}
|
|
122
|
-
.home-cta .btn-ghost{padding:10px 16px}
|
|
123
|
-
.home-install{display:flex;align-items:center;gap:12px;background:var(--code-bg);color:var(--code-fg);border-radius:8px;padding:10px 10px 10px 16px;font:500 .9rem/1.2 "JetBrains Mono","SF Mono",ui-monospace,monospace;max-width:34em;border:1px solid #1f1840}
|
|
124
|
-
.home-install .prompt{color:#6f63a3;user-select:none;flex:0 0 auto}
|
|
125
|
-
.home-install code{flex:1;background:transparent;border:0;color:var(--code-fg);font:inherit;padding:0;white-space:pre;overflow:hidden;text-overflow:ellipsis}
|
|
126
|
-
.home-install .copy{flex:0 0 auto;background:rgba(255,255,255,.08);color:var(--code-fg);border:1px solid rgba(255,255,255,.16);border-radius:6px;padding:5px 11px;font:500 .72rem/1 "Inter",sans-serif;cursor:pointer;transition:background .15s,border-color .15s}
|
|
127
|
-
.home-install .copy:hover{background:rgba(255,255,255,.16)}
|
|
128
|
-
.home-install .copy.copied{background:var(--accent);border-color:var(--accent)}
|
|
129
|
-
.home-services{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0 18px}
|
|
130
|
-
.home-services span{display:inline-block;padding:3px 10px;border:1px solid var(--pill-border);border-radius:999px;font-size:.78rem;color:var(--muted);background:var(--paper)}
|
|
131
|
-
.home-services span.pill-pro{background:var(--accent-soft);color:var(--accent);border-color:transparent;font-weight:600}
|
|
132
|
-
.home-visual{display:block;width:min(100%,620px);height:auto;margin:22px 0 0;border-radius:8px;border:1px solid var(--line);box-shadow:var(--shadow-card);background:var(--paper)}
|
|
133
|
-
.doc-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:48px;margin-top:24px}
|
|
134
|
-
.doc-grid-home{margin-top:8px}
|
|
135
|
-
@media(min-width:1180px){.doc-grid{grid-template-columns:minmax(0,72ch) 200px;justify-content:start}.doc-grid-home{grid-template-columns:minmax(0,76ch);justify-content:start}}
|
|
136
|
-
.doc{min-width:0;max-width:72ch;overflow-wrap:break-word}
|
|
137
|
-
.doc-home{max-width:76ch}
|
|
138
|
-
.doc h1{font-size:2.6rem;line-height:1.08;letter-spacing:0;margin:0 0 .4em;font-weight:700;color:var(--ink)}
|
|
139
|
-
body:not(.home) .doc>h1:first-child{display:none}
|
|
140
|
-
.doc h2{font-size:1.45rem;line-height:1.2;margin:2em 0 .5em;font-weight:600;letter-spacing:0;color:var(--ink);position:relative}
|
|
141
|
-
.doc h3{font-size:1.1rem;margin:1.7em 0 .35em;position:relative;font-weight:600;color:var(--ink);letter-spacing:0}
|
|
142
|
-
.doc h4{font-size:.98rem;margin:1.4em 0 .25em;color:var(--ink);position:relative;font-weight:600}
|
|
143
|
-
.doc h2:first-child,.doc h3:first-child,.doc h4:first-child{margin-top:.2em}
|
|
144
|
-
.doc :is(h2,h3,h4) .anchor{position:absolute;left:-1.05em;top:0;color:var(--subtle);opacity:0;text-decoration:none;font-weight:400;padding-right:.3em;transition:opacity .12s,color .12s}
|
|
145
|
-
.doc :is(h2,h3,h4):hover .anchor{opacity:.7}
|
|
146
|
-
.doc :is(h2,h3,h4) .anchor:hover{opacity:1;color:var(--accent);text-decoration:none}
|
|
147
|
-
.doc p{margin:0 0 1.05em}
|
|
148
|
-
.doc ul,.doc ol{padding-left:1.3rem;margin:0 0 1.15em}
|
|
149
|
-
.doc li{margin:.25em 0}
|
|
150
|
-
.doc li>p{margin:0 0 .4em}
|
|
151
|
-
.doc strong{font-weight:600;color:var(--ink)}
|
|
152
|
-
.doc em{font-style:italic}
|
|
153
|
-
.doc code{font-family:"JetBrains Mono","SF Mono",ui-monospace,monospace;font-size:.84em;background:var(--line-soft);border:1px solid var(--line);border-radius:5px;padding:.08em .35em;color:var(--code-inline-fg)}
|
|
154
|
-
.doc pre{position:relative;overflow:auto;background:var(--code-bg);color:var(--code-fg);border-radius:8px;padding:14px 18px;margin:1.3em 0;font-size:.85em;line-height:1.6;scrollbar-width:thin;scrollbar-color:#3a3358 transparent;border:1px solid #221c40}
|
|
155
|
-
.doc pre.shiki{background:var(--code-bg)!important;color:var(--code-fg)!important}
|
|
156
|
-
.doc pre::-webkit-scrollbar{height:8px;width:8px}
|
|
157
|
-
.doc pre::-webkit-scrollbar-thumb{background:#3a3358;border-radius:8px}
|
|
158
|
-
.doc pre code{display:block;background:transparent;border:0;color:inherit;padding:0;font-size:1em;white-space:pre}
|
|
159
|
-
.doc pre code .line{display:block;min-height:1.6em}
|
|
160
|
-
.doc pre .copy{position:absolute;top:8px;right:8px;background:rgba(255,255,255,.06);color:var(--code-fg);border:1px solid rgba(255,255,255,.16);border-radius:6px;padding:3px 9px;font:500 .7rem/1 "Inter",sans-serif;cursor:pointer;opacity:0;transition:opacity .15s,background .15s,border-color .15s}
|
|
161
|
-
.doc pre:hover .copy,.doc pre .copy:focus{opacity:1}
|
|
162
|
-
.doc pre .copy:hover{background:rgba(255,255,255,.12)}
|
|
163
|
-
.doc pre .copy.copied{background:var(--accent);border-color:var(--accent);opacity:1}
|
|
164
|
-
.doc blockquote{margin:1.4em 0;padding:10px 16px;border-left:3px solid var(--accent);background:var(--accent-soft);border-radius:0 8px 8px 0;color:var(--text)}
|
|
165
|
-
.doc blockquote p:last-child{margin-bottom:0}
|
|
166
|
-
.doc table{width:100%;border-collapse:collapse;margin:1.2em 0;font-size:.92em}
|
|
167
|
-
.doc th,.doc td{border-bottom:1px solid var(--line);padding:9px 10px;text-align:left;vertical-align:top}
|
|
168
|
-
.doc th{font-weight:600;color:var(--ink);background:var(--line-soft);border-bottom:1px solid var(--line)}
|
|
169
|
-
.doc hr{border:0;border-top:1px solid var(--line);margin:2.2em 0}
|
|
170
|
-
.toc{position:sticky;top:24px;align-self:start;font-size:.84rem;padding-left:14px;border-left:1px solid var(--line);max-height:calc(100vh - 48px);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--line) transparent}
|
|
171
|
-
.toc::-webkit-scrollbar{width:5px}
|
|
172
|
-
.toc::-webkit-scrollbar-thumb{background:var(--line);border-radius:5px}
|
|
173
|
-
.toc h2{font-size:.66rem;color:var(--muted);text-transform:uppercase;letter-spacing:0;margin:0 0 10px;font-weight:600}
|
|
174
|
-
.toc a{display:block;color:var(--muted);text-decoration:none;padding:4px 0 4px 10px;line-height:1.35;border-left:2px solid transparent;margin-left:-12px;transition:color .12s,border-color .12s}
|
|
175
|
-
.toc a:hover{color:var(--ink);text-decoration:none}
|
|
176
|
-
.toc a.active{color:var(--accent);border-left-color:var(--accent);font-weight:500}
|
|
177
|
-
.toc-l3{padding-left:22px!important;font-size:.94em}
|
|
178
|
-
@media(max-width:1179px){.toc{display:none}}
|
|
179
|
-
.page-nav{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:48px;border-top:1px solid var(--line);padding-top:20px}
|
|
180
|
-
.page-nav>a{display:block;border:1px solid var(--line);background:var(--paper);border-radius:9px;padding:13px 16px;text-decoration:none;color:var(--text);transition:border-color .15s,transform .15s,box-shadow .15s,background-color .18s}
|
|
181
|
-
.page-nav>a:hover{border-color:var(--accent);text-decoration:none;color:var(--ink)}
|
|
182
|
-
.page-nav small{display:block;color:var(--muted);font-size:.7rem;text-transform:uppercase;letter-spacing:0;margin-bottom:5px;font-weight:600}
|
|
183
|
-
.page-nav span{display:block;font-weight:600;line-height:1.3;color:var(--ink)}
|
|
184
|
-
.page-nav-prev{text-align:left}
|
|
185
|
-
.page-nav-next{text-align:right;grid-column:2}
|
|
186
|
-
.page-nav-prev:only-child{grid-column:1}
|
|
187
|
-
.nav-toggle{display:none;position:fixed;top:14px;right:14px;top:calc(14px + env(safe-area-inset-top, 0px));right:calc(14px + env(safe-area-inset-right, 0px));z-index:20;width:40px;height:40px;border-radius:9px;background:var(--paper);border:1px solid var(--line);color:var(--ink);cursor:pointer;padding:10px 9px;flex-direction:column;align-items:stretch;justify-content:space-between;box-shadow:var(--shadow-card)}
|
|
188
|
-
.nav-toggle span{display:block;width:100%;height:2px;flex:0 0 2px;background:currentColor;border-radius:2px;transition:transform .2s,opacity .2s}
|
|
189
|
-
.nav-toggle[aria-expanded="true"] span:nth-child(1){transform:translateY(8px) rotate(45deg)}
|
|
190
|
-
.nav-toggle[aria-expanded="true"] span:nth-child(2){opacity:0}
|
|
191
|
-
.nav-toggle[aria-expanded="true"] span:nth-child(3){transform:translateY(-8px) rotate(-45deg)}
|
|
192
|
-
@media(max-width:900px){
|
|
193
|
-
.shell{display:block}
|
|
194
|
-
.sidebar{position:fixed;inset:0 30% 0 0;max-width:320px;height:100vh;z-index:15;transform:translateX(-100%);transition:transform .25s ease,background-color .18s,border-color .18s;box-shadow:0 18px 40px rgba(0,0,0,.18);background:var(--paper);pointer-events:none}
|
|
195
|
-
.sidebar.open{transform:translateX(0);pointer-events:auto}
|
|
196
|
-
.nav-toggle{display:flex}
|
|
197
|
-
main{padding:64px 18px 56px}
|
|
198
|
-
.hero{padding-top:6px}
|
|
199
|
-
.hero h1{font-size:1.8rem}
|
|
200
|
-
.home-hero h1{font-size:2.45rem}
|
|
201
|
-
.doc h1{font-size:2.1rem}
|
|
202
|
-
.hero-meta{width:100%;justify-content:flex-start}
|
|
203
|
-
.home-hero{padding-top:8px}
|
|
204
|
-
.home-visual{margin-top:18px}
|
|
205
|
-
.doc{padding:0}
|
|
206
|
-
.doc-grid{margin-top:18px;gap:24px}
|
|
207
|
-
.doc :is(h2,h3,h4) .anchor{display:none}
|
|
208
|
-
}
|
|
209
|
-
@media(max-width:520px){
|
|
210
|
-
main{padding:60px 14px 48px}
|
|
211
|
-
.doc pre{margin-left:-14px;margin-right:-14px;border-radius:0;border-left:0;border-right:0}
|
|
212
|
-
.home-install{flex-wrap:wrap}
|
|
213
|
-
}
|
|
214
|
-
</style>
|
|
215
|
-
</head>
|
|
216
|
-
<body>
|
|
217
|
-
<button class="nav-toggle" type="button" aria-label="Toggle navigation" aria-expanded="false">
|
|
218
|
-
<span aria-hidden="true"></span><span aria-hidden="true"></span><span aria-hidden="true"></span>
|
|
219
|
-
</button>
|
|
220
|
-
<div class="shell">
|
|
221
|
-
<aside class="sidebar">
|
|
222
|
-
<div class="sidebar-head">
|
|
223
|
-
<a class="brand" href="./" aria-label="oracle docs home">
|
|
224
|
-
<span class="mark" aria-hidden="true"></span>
|
|
225
|
-
<span><strong>oracle</strong><small>askoracle.sh</small></span>
|
|
226
|
-
</a>
|
|
227
|
-
<button class="theme-toggle" type="button" aria-label="Toggle dark mode" aria-pressed="false" data-theme-toggle>
|
|
228
|
-
<svg class="theme-icon-moon" viewBox="0 0 20 20" aria-hidden="true"><path d="M14.6 12.1A6.5 6.5 0 0 1 7.4 2.7a6.5 6.5 0 1 0 7.2 9.4z" fill="currentColor"/></svg>
|
|
229
|
-
<svg class="theme-icon-sun" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="3.4" fill="currentColor"/><g stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><line x1="10" y1="2" x2="10" y2="4"/><line x1="10" y1="16" x2="10" y2="18"/><line x1="2" y1="10" x2="4" y2="10"/><line x1="16" y1="10" x2="18" y2="10"/><line x1="4.2" y1="4.2" x2="5.6" y2="5.6"/><line x1="14.4" y1="14.4" x2="15.8" y2="15.8"/><line x1="4.2" y1="15.8" x2="5.6" y2="14.4"/><line x1="14.4" y1="5.6" x2="15.8" y2="4.2"/></g></svg>
|
|
230
|
-
</button>
|
|
231
|
-
</div>
|
|
232
|
-
<label class="search"><span>Search</span><input id="doc-search" type="search" placeholder="browser, mcp, sessions"></label>
|
|
233
|
-
<nav><section><h2>Start</h2><a class="nav-link" href="./">Overview</a><a class="nav-link" href="install.html">Install</a><a class="nav-link" href="quickstart.html">Quickstart</a><a class="nav-link" href="configuration.html">Local configuration (JSON5)</a></section><section><h2>Mythical Pro Agents</h2><a class="nav-link" href="mythical-pro-agents.html">Mythical Pro Agents</a><a class="nav-link" href="openai-endpoints.html">OpenAI-Compatible Endpoints</a><a class="nav-link" href="anthropic.html">Anthropic (Claude) Integration Plan</a><a class="nav-link" href="gemini.html">Gemini Integration</a><a class="nav-link" href="openrouter.html">OpenRouter</a><a class="nav-link" href="grok.html">Grok 4.1 (xAI) Support</a></section><section><h2>Browser Mode</h2><a class="nav-link" href="browser-mode.html">Browser Mode</a><a class="nav-link" href="chromium-forks.html">Chromium-based browsers (Chromium, Edge, Brave variants)</a><a class="nav-link" href="linux.html">Linux Notes</a><a class="nav-link active" href="windows.html">Windows compatibility notes</a><a class="nav-link" href="windows-work.html">Windows Work</a><a class="nav-link" href="bridge.html">Bridge (Windows-hosted ChatGPT session → Linux clients)</a></section><section><h2>Agents & MCP</h2><a class="nav-link" href="agents.html">Coding Agents</a><a class="nav-link" href="mcp.html">MCP Server</a></section><section><h2>Workflows</h2><a class="nav-link" href="sessions.html">Sessions</a><a class="nav-link" href="followup.html">Followups & Lineage</a><a class="nav-link" href="multimodel.html">Multi-Model Execution</a></section><section><h2>Reference</h2><a class="nav-link" href="cli-reference.html">CLI Reference</a><a class="nav-link" href="spec.html">Spec</a><a class="nav-link" href="notifier.html">Session completion notifications</a><a class="nav-link" href="tui-debug.html">TUI navigation crash notes (2025-11-19)</a><a class="nav-link" href="manual-tests.html">Manual Test Suite (Browser Mode + Live API)</a><a class="nav-link" href="testing.html">Testing quickstart</a><a class="nav-link" href="RELEASING.html">Release Checklist (npm + Homebrew)</a></section></nav>
|
|
234
|
-
</aside>
|
|
235
|
-
<main>
|
|
236
|
-
<header class="hero">
|
|
237
|
-
<div class="hero-text">
|
|
238
|
-
<p class="eyebrow">Browser Mode</p>
|
|
239
|
-
<h1>Windows compatibility notes</h1>
|
|
240
|
-
</div>
|
|
241
|
-
<div class="hero-meta">
|
|
242
|
-
<a class="repo" href="https://github.com/steipete/oracle" rel="noopener">GitHub</a>
|
|
243
|
-
<a class="edit" href="https://github.com/steipete/oracle/edit/main/docs/windows.md" rel="noopener">Edit page</a>
|
|
244
|
-
</div>
|
|
245
|
-
</header>
|
|
246
|
-
<div class="doc-grid">
|
|
247
|
-
<article class="doc"><h1 id="windows-compatibility-notes">Windows compatibility notes</h1>
|
|
248
|
-
<p>Keep this in sync as we learn more. Read this before doing browser runs on Windows.</p>
|
|
249
|
-
<ul>
|
|
250
|
-
<li>Browser engine is enabled on Windows now, but automation is flakier than macOS. If it fails, rerun with <code>--engine api --wait</code> or use <code>--remote-chrome</code> to point at a logged-in Chrome with remote debugging.</li>
|
|
251
|
-
<li>Cookies: cookie sync is disabled by default on Windows because ChatGPT cookies are app-bound (<code>v20</code>) and fail decryption. Use <code>--browser-manual-login</code> to reuse a persistent automation profile and sign in once (skips cookie copy entirely). Inline cookies remain available (<code>--browser-inline-cookies(-file)</code> / <code>ORACLE_BROWSER_COOKIES_JSON</code>).</li>
|
|
252
|
-
<li>Manual login flow: run with <code>--browser-manual-login</code> and sign into chatgpt.com in the opened Chrome; Oracle waits until the session is active. For initial login/setup or debugging, add <code>--browser-keep-browser</code> to keep the window open after the run; otherwise Oracle closes Chrome but preserves the profile at <code>~/.oracle/browser-profile</code> (override with <code>ORACLE_BROWSER_PROFILE_DIR</code> or <code>browser.manualLoginProfileDir</code> in <code>~/.oracle/config.json</code>). If that automation Chrome is already running with remote debugging enabled (DevToolsActivePort present), reuse it instead of relaunching by pointing Oracle at it via <code>--remote-chrome <host:port></code>.</li>
|
|
253
|
-
<li>Cookie paths: preferred path is <code>%LOCALAPPDATA%\\Google\\Chrome\\User Data\\<Profile>\\Network\\Cookies</code>. If that errors, try the top-level <code>Cookies</code> file or supply the exact path via <code>--browser-cookie-path</code>.</li>
|
|
254
|
-
<li>mcporter chrome-devtools: requires a valid <code>CHROME_DEVTOOLS_URL</code> from a live session; otherwise calls will fail.</li>
|
|
255
|
-
<li>agent-scripts helpers (<code>runner</code>, <code>scripts/committer</code>) are bash-based and may fail under PowerShell/CMD; run commands directly if they misbehave.</li>
|
|
256
|
-
</ul><nav class="page-nav" aria-label="Pager"><a class="page-nav-prev" href="linux.html"><small>Previous</small><span>Linux Notes</span></a><a class="page-nav-next" href="windows-work.html"><small>Next</small><span>Windows Work</span></a></nav></article>
|
|
257
|
-
|
|
258
|
-
</div>
|
|
259
|
-
</main>
|
|
260
|
-
</div>
|
|
261
|
-
<script>
|
|
262
|
-
const themeRoot=document.documentElement;
|
|
263
|
-
function applyTheme(mode){themeRoot.dataset.theme=mode;document.querySelectorAll('[data-theme-toggle]').forEach(b=>b.setAttribute('aria-pressed',mode==='dark'?'true':'false'))}
|
|
264
|
-
function storedTheme(){try{return localStorage.getItem('theme')}catch(e){return null}}
|
|
265
|
-
function persistTheme(mode){try{localStorage.setItem('theme',mode)}catch(e){}}
|
|
266
|
-
applyTheme(themeRoot.dataset.theme==='dark'?'dark':'light');
|
|
267
|
-
document.querySelectorAll('[data-theme-toggle]').forEach(btn=>{btn.addEventListener('click',()=>{const next=themeRoot.dataset.theme==='dark'?'light':'dark';applyTheme(next);persistTheme(next)})});
|
|
268
|
-
const systemDark=window.matchMedia&&matchMedia('(prefers-color-scheme: dark)');
|
|
269
|
-
function onSystemChange(e){if(storedTheme())return;applyTheme(e.matches?'dark':'light')}
|
|
270
|
-
if(systemDark){if(systemDark.addEventListener)systemDark.addEventListener('change',onSystemChange);else if(systemDark.addListener)systemDark.addListener(onSystemChange)}
|
|
271
|
-
const sidebar=document.querySelector('.sidebar');
|
|
272
|
-
const toggle=document.querySelector('.nav-toggle');
|
|
273
|
-
const mobileNav=window.matchMedia('(max-width: 900px)');
|
|
274
|
-
const sidebarFocusable='a[href],button,input,select,textarea,[tabindex]';
|
|
275
|
-
function setSidebarFocusable(enabled){
|
|
276
|
-
sidebar?.querySelectorAll(sidebarFocusable).forEach((el)=>{
|
|
277
|
-
if(enabled){
|
|
278
|
-
if(el.dataset.sidebarTabindex!==undefined){
|
|
279
|
-
if(el.dataset.sidebarTabindex)el.setAttribute('tabindex',el.dataset.sidebarTabindex);
|
|
280
|
-
else el.removeAttribute('tabindex');
|
|
281
|
-
delete el.dataset.sidebarTabindex;
|
|
282
|
-
}
|
|
283
|
-
}else if(el.dataset.sidebarTabindex===undefined){
|
|
284
|
-
el.dataset.sidebarTabindex=el.getAttribute('tabindex')??'';
|
|
285
|
-
el.setAttribute('tabindex','-1');
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
function setSidebarOpen(open){
|
|
290
|
-
if(!sidebar||!toggle)return;
|
|
291
|
-
sidebar.classList.toggle('open',open);
|
|
292
|
-
toggle.setAttribute('aria-expanded',open?'true':'false');
|
|
293
|
-
if(mobileNav.matches){
|
|
294
|
-
sidebar.inert=!open;
|
|
295
|
-
if(open)sidebar.removeAttribute('aria-hidden');
|
|
296
|
-
else sidebar.setAttribute('aria-hidden','true');
|
|
297
|
-
setSidebarFocusable(open);
|
|
298
|
-
}else{
|
|
299
|
-
sidebar.inert=false;
|
|
300
|
-
sidebar.removeAttribute('aria-hidden');
|
|
301
|
-
setSidebarFocusable(true);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
setSidebarOpen(false);
|
|
305
|
-
toggle?.addEventListener('click',()=>setSidebarOpen(!sidebar?.classList.contains('open')));
|
|
306
|
-
document.addEventListener('click',(e)=>{if(!sidebar?.classList.contains('open'))return;if(sidebar.contains(e.target)||toggle?.contains(e.target))return;setSidebarOpen(false)});
|
|
307
|
-
document.addEventListener('keydown',(e)=>{if(e.key==='Escape')setSidebarOpen(false)});
|
|
308
|
-
const syncSidebarForViewport=()=>setSidebarOpen(sidebar?.classList.contains('open')??false);
|
|
309
|
-
if(mobileNav.addEventListener)mobileNav.addEventListener('change',syncSidebarForViewport);
|
|
310
|
-
else mobileNav.addListener?.(syncSidebarForViewport);
|
|
311
|
-
const input=document.getElementById('doc-search');
|
|
312
|
-
input?.addEventListener('input',()=>{const q=input.value.trim().toLowerCase();document.querySelectorAll('nav section').forEach(sec=>{let any=false;sec.querySelectorAll('.nav-link').forEach(a=>{const m=!q||a.textContent.toLowerCase().includes(q);a.style.display=m?'block':'none';if(m)any=true});sec.style.display=any?'block':'none'})});
|
|
313
|
-
function attachCopy(target,getText){const btn=document.createElement('button');btn.type='button';btn.className='copy';btn.textContent='Copy';btn.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(getText());btn.textContent='Copied';btn.classList.add('copied');setTimeout(()=>{btn.textContent='Copy';btn.classList.remove('copied')},1400)}catch{btn.textContent='Failed';setTimeout(()=>{btn.textContent='Copy'},1400)}});target.appendChild(btn)}
|
|
314
|
-
document.querySelectorAll('.doc pre').forEach(pre=>attachCopy(pre,()=>pre.querySelector('code')?.textContent??''));
|
|
315
|
-
document.querySelectorAll('.home-install').forEach(el=>attachCopy(el,()=>el.querySelector('code')?.textContent??''));
|
|
316
|
-
const tocLinks=document.querySelectorAll('.toc a');
|
|
317
|
-
if(tocLinks.length){const map=new Map();tocLinks.forEach(a=>{const id=a.getAttribute('href').slice(1);const el=document.getElementById(id);if(el)map.set(el,a)});const setActive=l=>{tocLinks.forEach(x=>x.classList.remove('active'));l.classList.add('active')};const obs=new IntersectionObserver(entries=>{const visible=entries.filter(e=>e.isIntersecting).sort((a,b)=>a.boundingClientRect.top-b.boundingClientRect.top);if(visible.length){const link=map.get(visible[0].target);if(link)setActive(link)}},{rootMargin:'-15% 0px -65% 0px',threshold:0});map.forEach((_,el)=>obs.observe(el))}
|
|
318
|
-
</script>
|
|
319
|
-
</body>
|
|
320
|
-
</html>
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import fs from 'node:fs/promises';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
|
-
import chromeCookies from 'chrome-cookies-secure';
|
|
7
|
-
import { COOKIE_URLS } from './constants.js';
|
|
8
|
-
import { ensureCookiesDirForFallback } from './windowsCookies.js';
|
|
9
|
-
const COOKIE_READ_TIMEOUT_MS = readDuration('ORACLE_COOKIE_LOAD_TIMEOUT_MS', 5_000);
|
|
10
|
-
const KEYCHAIN_PROBE_TIMEOUT_MS = readDuration('ORACLE_KEYCHAIN_PROBE_TIMEOUT_MS', 3_000);
|
|
11
|
-
const MAC_KEYCHAIN_LABELS = loadKeychainLabels();
|
|
12
|
-
export async function loadChromeCookies({ targetUrl, profile, explicitCookiePath, filterNames, }) {
|
|
13
|
-
if (process.platform === 'win32') {
|
|
14
|
-
throw new Error('Cookie sync is disabled on Windows; use --browser-manual-login (persistent profile) or inline cookies instead.');
|
|
15
|
-
}
|
|
16
|
-
const urlsToCheck = Array.from(new Set([stripQuery(targetUrl), ...COOKIE_URLS]));
|
|
17
|
-
const merged = new Map();
|
|
18
|
-
const cookieFile = await resolveCookieFilePath({ explicitPath: explicitCookiePath, profile });
|
|
19
|
-
const cookiesPath = await materializeCookieFile(cookieFile); // returns the copied file (or source on non-Windows)
|
|
20
|
-
const fallbackDir = await ensureCookiesDirForFallback(cookiesPath);
|
|
21
|
-
if (process.env.ORACLE_DEBUG_COOKIES === '1') {
|
|
22
|
-
// eslint-disable-next-line no-console
|
|
23
|
-
console.log(`[cookies] resolved cookie file: ${cookiesPath}`);
|
|
24
|
-
console.log(`[cookies] fallback dir for chrome-cookies-secure: ${fallbackDir}`);
|
|
25
|
-
}
|
|
26
|
-
await ensureMacKeychainReadable();
|
|
27
|
-
for (const url of urlsToCheck) {
|
|
28
|
-
let raw;
|
|
29
|
-
try {
|
|
30
|
-
const pathForSecure = await adaptPathForChromeCookies(fallbackDir);
|
|
31
|
-
raw = await settleWithTimeout(chromeCookies.getCookiesPromised(url, 'puppeteer', pathForSecure), COOKIE_READ_TIMEOUT_MS, `Timed out reading Chrome cookies from ${pathForSecure} (after ${COOKIE_READ_TIMEOUT_MS} ms)`);
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
-
throw new Error(`Failed to load Chrome cookies for ${url}: ${message}`);
|
|
36
|
-
}
|
|
37
|
-
if (!Array.isArray(raw))
|
|
38
|
-
continue;
|
|
39
|
-
const fallbackHost = new URL(url).hostname;
|
|
40
|
-
for (const cookie of raw) {
|
|
41
|
-
if (filterNames && filterNames.size > 0 && !filterNames.has(cookie.name))
|
|
42
|
-
continue;
|
|
43
|
-
const normalized = normalizeCookie(cookie, fallbackHost);
|
|
44
|
-
if (!normalized)
|
|
45
|
-
continue;
|
|
46
|
-
const key = `${normalized.domain ?? fallbackHost}:${normalized.name}`;
|
|
47
|
-
if (!merged.has(key)) {
|
|
48
|
-
merged.set(key, normalized);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return Array.from(merged.values());
|
|
53
|
-
}
|
|
54
|
-
async function ensureMacKeychainReadable() {
|
|
55
|
-
if (process.platform === 'win32') {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
// chrome-cookies-secure can hang forever when the platform keyring rejects access (e.g., SSH/no GUI).
|
|
59
|
-
// Probe the keyring ourselves with a timeout so callers fail fast instead of blocking the run.
|
|
60
|
-
let keytar = null;
|
|
61
|
-
let keytarPath = null;
|
|
62
|
-
try {
|
|
63
|
-
const require = createRequire(import.meta.url);
|
|
64
|
-
keytarPath = require.resolve('keytar');
|
|
65
|
-
const keytarModule = await import('keytar');
|
|
66
|
-
keytar = (keytarModule.default ?? keytarModule);
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
const base = error instanceof Error ? error.message : String(error);
|
|
70
|
-
const rebuildHint = keytarPath
|
|
71
|
-
? ` You may need to rebuild keytar: PYTHON=/usr/bin/python3 /Users/steipete/Projects/oracle/runner npx node-gyp rebuild (run inside ${path.dirname(keytarPath)}).`
|
|
72
|
-
: '';
|
|
73
|
-
throw new Error(`Failed to load keytar for secure cookie copy (${base}). Install keyring deps (macOS Keychain / libsecret) or rerun with --render --copy to paste into ChatGPT manually.${rebuildHint}`);
|
|
74
|
-
}
|
|
75
|
-
const password = await settleWithTimeout(findKeychainPassword(keytar, MAC_KEYCHAIN_LABELS), KEYCHAIN_PROBE_TIMEOUT_MS, `Timed out reading macOS Keychain while looking up Chrome Safe Storage (after ${KEYCHAIN_PROBE_TIMEOUT_MS} ms). Unlock the login keychain or start oracle serve from a GUI session.`);
|
|
76
|
-
if (!password) {
|
|
77
|
-
throw new Error('macOS Keychain denied access to Chrome cookies. Unlock the login keychain or run oracle serve from a GUI session, then retry.');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
async function findKeychainPassword(keytar, labels) {
|
|
81
|
-
let lastError = null;
|
|
82
|
-
for (const label of labels) {
|
|
83
|
-
try {
|
|
84
|
-
const value = await keytar.getPassword(label.service, label.account);
|
|
85
|
-
if (value)
|
|
86
|
-
return value;
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (lastError) {
|
|
93
|
-
throw lastError;
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
function settleWithTimeout(promise, timeoutMs, timeoutMessage) {
|
|
98
|
-
return new Promise((resolve, reject) => {
|
|
99
|
-
const timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
|
|
100
|
-
timer.unref?.();
|
|
101
|
-
promise.then((value) => {
|
|
102
|
-
clearTimeout(timer);
|
|
103
|
-
resolve(value);
|
|
104
|
-
}, (error) => {
|
|
105
|
-
clearTimeout(timer);
|
|
106
|
-
reject(error);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
function normalizeCookie(cookie, fallbackHost) {
|
|
111
|
-
if (!cookie?.name)
|
|
112
|
-
return null;
|
|
113
|
-
const domain = cookie.domain?.startsWith('.') ? cookie.domain.slice(1) : cookie.domain ?? fallbackHost;
|
|
114
|
-
const expires = normalizeExpiration(cookie.expires);
|
|
115
|
-
const secure = typeof cookie.Secure === 'boolean' ? cookie.Secure : true;
|
|
116
|
-
const httpOnly = typeof cookie.HttpOnly === 'boolean' ? cookie.HttpOnly : false;
|
|
117
|
-
return {
|
|
118
|
-
name: cookie.name,
|
|
119
|
-
value: cleanValue(cookie.value ?? ''),
|
|
120
|
-
domain,
|
|
121
|
-
path: cookie.path ?? '/',
|
|
122
|
-
expires,
|
|
123
|
-
secure,
|
|
124
|
-
httpOnly,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
function cleanValue(value) {
|
|
128
|
-
let i = 0;
|
|
129
|
-
while (i < value.length && value.charCodeAt(i) < 0x20)
|
|
130
|
-
i += 1;
|
|
131
|
-
return value.slice(i);
|
|
132
|
-
}
|
|
133
|
-
function normalizeExpiration(expires) {
|
|
134
|
-
if (!expires || Number.isNaN(expires)) {
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
const value = Number(expires);
|
|
138
|
-
if (value <= 0)
|
|
139
|
-
return undefined;
|
|
140
|
-
if (value > 1_000_000_000_000) {
|
|
141
|
-
return Math.round(value / 1_000_000 - 11644473600);
|
|
142
|
-
}
|
|
143
|
-
if (value > 1_000_000_000) {
|
|
144
|
-
return Math.round(value / 1000);
|
|
145
|
-
}
|
|
146
|
-
return Math.round(value);
|
|
147
|
-
}
|
|
148
|
-
async function resolveCookieFilePath({ explicitPath, profile, }) {
|
|
149
|
-
if (explicitPath && explicitPath.trim().length > 0) {
|
|
150
|
-
return ensureCookieFile(explicitPath);
|
|
151
|
-
}
|
|
152
|
-
if (profile && looksLikePath(profile)) {
|
|
153
|
-
return ensureCookieFile(profile);
|
|
154
|
-
}
|
|
155
|
-
const profileName = profile && profile.trim().length > 0 ? profile : 'Default';
|
|
156
|
-
const baseDir = await defaultProfileRoot();
|
|
157
|
-
return ensureCookieFile(path.join(baseDir, profileName));
|
|
158
|
-
}
|
|
159
|
-
async function adaptPathForChromeCookies(resolved) {
|
|
160
|
-
const stat = await fs.stat(resolved).catch(() => null);
|
|
161
|
-
if (stat?.isFile()) {
|
|
162
|
-
// chrome-cookies-secure appends "Cookies" when given a directory; if we already have the file, return its directory.
|
|
163
|
-
return path.dirname(resolved);
|
|
164
|
-
}
|
|
165
|
-
return resolved;
|
|
166
|
-
}
|
|
167
|
-
async function ensureCookieFile(inputPath) {
|
|
168
|
-
const expanded = expandPath(inputPath);
|
|
169
|
-
const stat = await fs.stat(expanded).catch(() => null);
|
|
170
|
-
if (!stat) {
|
|
171
|
-
throw new Error(`Unable to locate Chrome cookie DB at ${expanded}`);
|
|
172
|
-
}
|
|
173
|
-
if (stat.isDirectory()) {
|
|
174
|
-
const directFile = path.join(expanded, 'Cookies');
|
|
175
|
-
if (await fileExists(directFile))
|
|
176
|
-
return directFile;
|
|
177
|
-
const networkFile = path.join(expanded, 'Network', 'Cookies');
|
|
178
|
-
if (await fileExists(networkFile))
|
|
179
|
-
return networkFile;
|
|
180
|
-
throw new Error(`No Cookies DB found under ${expanded}`);
|
|
181
|
-
}
|
|
182
|
-
return expanded;
|
|
183
|
-
}
|
|
184
|
-
async function fileExists(candidate) {
|
|
185
|
-
try {
|
|
186
|
-
const stat = await fs.stat(candidate);
|
|
187
|
-
return stat.isFile();
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function expandPath(input) {
|
|
194
|
-
if (input.startsWith('~/')) {
|
|
195
|
-
return path.join(os.homedir(), input.slice(2));
|
|
196
|
-
}
|
|
197
|
-
return path.isAbsolute(input) ? input : path.resolve(process.cwd(), input);
|
|
198
|
-
}
|
|
199
|
-
function looksLikePath(value) {
|
|
200
|
-
return value.includes('/') || value.includes('\\');
|
|
201
|
-
}
|
|
202
|
-
async function materializeCookieFile(sourcePath) {
|
|
203
|
-
if (process.platform !== 'win32')
|
|
204
|
-
return sourcePath;
|
|
205
|
-
// Chrome can keep the Cookies DB locked; copy to a temp file so sqlite can open it reliably.
|
|
206
|
-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'oracle-cookies-'));
|
|
207
|
-
const tempPath = path.join(tempDir, 'Cookies');
|
|
208
|
-
try {
|
|
209
|
-
await fs.copyFile(sourcePath, tempPath);
|
|
210
|
-
return tempPath;
|
|
211
|
-
}
|
|
212
|
-
catch (_error) {
|
|
213
|
-
// Fall back to the original path if the copy fails; upstream error handling will surface issues.
|
|
214
|
-
return sourcePath;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
async function defaultProfileRoot() {
|
|
218
|
-
const candidates = [];
|
|
219
|
-
if (process.platform === 'darwin') {
|
|
220
|
-
candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome'), path.join(os.homedir(), 'Library', 'Application Support', 'Microsoft Edge'), path.join(os.homedir(), 'Library', 'Application Support', 'Chromium'));
|
|
221
|
-
}
|
|
222
|
-
else if (process.platform === 'linux') {
|
|
223
|
-
if (isWsl()) {
|
|
224
|
-
const windowsHomes = [process.env.USERPROFILE, process.env.WIN_HOME, process.env.HOME?.replace('/home', '/mnt/c/Users')]
|
|
225
|
-
.filter((p) => Boolean(p))
|
|
226
|
-
.map((p) => p.replace(/\\/g, '/'));
|
|
227
|
-
const wslCandidates = [];
|
|
228
|
-
for (const home of windowsHomes) {
|
|
229
|
-
const normalized = home.startsWith('/mnt/') ? home : `/mnt/c/Users/${path.basename(home)}`;
|
|
230
|
-
wslCandidates.push(path.join(normalized, 'AppData', 'Local', 'Google', 'Chrome', 'User Data'), path.join(normalized, 'AppData', 'Local', 'Microsoft', 'Edge', 'User Data'));
|
|
231
|
-
}
|
|
232
|
-
// Ensure we don't pick a non-user Default profile ahead of real ones.
|
|
233
|
-
for (const candidate of wslCandidates) {
|
|
234
|
-
const hasProfile = existsSync(path.join(candidate, 'Default'));
|
|
235
|
-
if (hasProfile) {
|
|
236
|
-
candidates.push(candidate);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
candidates.push(path.join(os.homedir(), '.config', 'google-chrome'), path.join(os.homedir(), '.config', 'microsoft-edge'), path.join(os.homedir(), '.config', 'chromium'),
|
|
241
|
-
// Snap Chromium profiles
|
|
242
|
-
path.join(os.homedir(), 'snap', 'chromium', 'common', 'chromium'), path.join(os.homedir(), 'snap', 'chromium', 'current', 'chromium'));
|
|
243
|
-
}
|
|
244
|
-
else if (process.platform === 'win32') {
|
|
245
|
-
const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
|
|
246
|
-
candidates.push(path.join(localAppData, 'Google', 'Chrome', 'User Data'), path.join(localAppData, 'Microsoft', 'Edge', 'User Data'), path.join(localAppData, 'Chromium', 'User Data'));
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
250
|
-
}
|
|
251
|
-
for (const candidate of candidates) {
|
|
252
|
-
if (existsSync(candidate)) {
|
|
253
|
-
return candidate;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// fallback: first candidate even if missing; upstream will throw clearer error
|
|
257
|
-
return candidates[0];
|
|
258
|
-
}
|
|
259
|
-
function stripQuery(url) {
|
|
260
|
-
try {
|
|
261
|
-
const parsed = new URL(url);
|
|
262
|
-
parsed.hash = '';
|
|
263
|
-
parsed.search = '';
|
|
264
|
-
return parsed.toString();
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
return url;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
function readDuration(envKey, fallback) {
|
|
271
|
-
const raw = process.env[envKey];
|
|
272
|
-
if (!raw)
|
|
273
|
-
return fallback;
|
|
274
|
-
const parsed = Number.parseInt(raw, 10);
|
|
275
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
276
|
-
}
|
|
277
|
-
function isWsl() {
|
|
278
|
-
if (process.platform !== 'linux')
|
|
279
|
-
return false;
|
|
280
|
-
return Boolean(process.env.WSL_DISTRO_NAME || os.release().toLowerCase().includes('microsoft'));
|
|
281
|
-
}
|
|
282
|
-
function loadKeychainLabels() {
|
|
283
|
-
const defaults = [
|
|
284
|
-
{ service: 'Chrome Safe Storage', account: 'Chrome' },
|
|
285
|
-
{ service: 'Chromium Safe Storage', account: 'Chromium' },
|
|
286
|
-
{ service: 'Microsoft Edge Safe Storage', account: 'Microsoft Edge' },
|
|
287
|
-
{ service: 'Brave Safe Storage', account: 'Brave' },
|
|
288
|
-
{ service: 'Vivaldi Safe Storage', account: 'Vivaldi' },
|
|
289
|
-
];
|
|
290
|
-
const rawEnv = process.env.ORACLE_KEYCHAIN_LABELS;
|
|
291
|
-
if (!rawEnv)
|
|
292
|
-
return defaults;
|
|
293
|
-
try {
|
|
294
|
-
const parsed = JSON.parse(rawEnv);
|
|
295
|
-
if (!Array.isArray(parsed))
|
|
296
|
-
return defaults;
|
|
297
|
-
const envLabels = parsed
|
|
298
|
-
.map((entry) => (entry && typeof entry === 'object' ? entry : null))
|
|
299
|
-
.filter((entry) => Boolean(entry?.service && entry?.account));
|
|
300
|
-
return envLabels.length ? [...envLabels, ...defaults] : defaults;
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
return defaults;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
// biome-ignore lint/style/useNamingConvention: test-only export used in vitest suite
|
|
307
|
-
export const __test__ = {
|
|
308
|
-
normalizeExpiration,
|
|
309
|
-
cleanValue,
|
|
310
|
-
looksLikePath,
|
|
311
|
-
defaultProfileRoot,
|
|
312
|
-
};
|