@aexol/opencode-wizard 0.2.2 → 0.3.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/dist/oauth-callback-page.d.ts +4 -0
- package/dist/oauth-callback-page.js +192 -0
- package/dist/oauth-callback-page.js.map +1 -0
- package/dist/server.js +9 -196
- package/dist/server.js.map +1 -1
- package/dist/smoke-published-skills.js +45 -6
- package/dist/smoke-published-skills.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type http from 'node:http';
|
|
2
|
+
export declare const escapeHtml: (value: string) => string;
|
|
3
|
+
export declare const renderOAuthCallbackPage: (statusCode: number, title: string, message: string) => string;
|
|
4
|
+
export declare const sendOAuthCallbackHtmlResponse: (response: http.ServerResponse, statusCode: number, title: string, message: string) => void;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
export const escapeHtml = value => {
|
|
2
|
+
return value.replace(/[&<>'"]/g, character => {
|
|
3
|
+
const replacements = {
|
|
4
|
+
'&': '&',
|
|
5
|
+
'<': '<',
|
|
6
|
+
'>': '>',
|
|
7
|
+
"'": ''',
|
|
8
|
+
'"': '"'
|
|
9
|
+
};
|
|
10
|
+
return replacements[character] ?? character;
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export const renderOAuthCallbackPage = (statusCode, title, message) => {
|
|
14
|
+
const escapedTitle = escapeHtml(title);
|
|
15
|
+
const escapedMessage = escapeHtml(message);
|
|
16
|
+
const isSuccess = statusCode >= 200 && statusCode < 300;
|
|
17
|
+
const pageState = isSuccess ? 'success' : statusCode === 404 ? 'not-found' : 'error';
|
|
18
|
+
const cardTitle = isSuccess ? 'Authorization successful' : statusCode === 404 ? 'Callback not found' : 'Authorization failed';
|
|
19
|
+
const escapedCardTitle = escapeHtml(cardTitle);
|
|
20
|
+
const eyebrow = isSuccess ? 'Authorization complete' : statusCode === 404 ? 'Callback route not found' : 'Authorization needs attention';
|
|
21
|
+
const actionText = isSuccess ? 'This window will close automatically in a moment. You can also close it now and return to OpenCode.' : 'You can close this window and return to OpenCode to try again.';
|
|
22
|
+
const autoCloseScript = isSuccess ? `<script>
|
|
23
|
+
window.setTimeout(() => window.close(), 2000);
|
|
24
|
+
</script>` : '';
|
|
25
|
+
const stateIcon = isSuccess ? '<path d="M7 12.5l3.1 3.1L17.5 8" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>' : statusCode === 404 ? '<path d="M10.5 17a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13Z" stroke="currentColor" stroke-width="2.2"/><path d="m15.5 15.5 4 4" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>' : '<path d="M12 7v6" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/><path d="M12 17.2v.1" stroke="currentColor" stroke-width="3.2" stroke-linecap="round"/>';
|
|
26
|
+
return `<!doctype html>
|
|
27
|
+
<html lang="en">
|
|
28
|
+
<head>
|
|
29
|
+
<meta charset="utf-8">
|
|
30
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
31
|
+
<meta name="color-scheme" content="light dark">
|
|
32
|
+
<title>${escapedTitle}</title>
|
|
33
|
+
<style>
|
|
34
|
+
:root {
|
|
35
|
+
color-scheme: light dark;
|
|
36
|
+
--page-bg: #f2efe7;
|
|
37
|
+
--page-ink: #211d18;
|
|
38
|
+
--muted: #6c6258;
|
|
39
|
+
--panel: rgba(255, 252, 245, 0.82);
|
|
40
|
+
--panel-border: rgba(78, 66, 52, 0.16);
|
|
41
|
+
--success: #167848;
|
|
42
|
+
--error: #ba3329;
|
|
43
|
+
--not-found: #986614;
|
|
44
|
+
--glow: rgba(22, 120, 72, 0.18);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (prefers-color-scheme: dark) {
|
|
48
|
+
:root {
|
|
49
|
+
--page-bg: #12100d;
|
|
50
|
+
--page-ink: #f7efe2;
|
|
51
|
+
--muted: #b8aa98;
|
|
52
|
+
--panel: rgba(30, 26, 22, 0.78);
|
|
53
|
+
--panel-border: rgba(255, 244, 224, 0.14);
|
|
54
|
+
--success: #71e0a6;
|
|
55
|
+
--error: #ff897e;
|
|
56
|
+
--not-found: #f7c96f;
|
|
57
|
+
--glow: rgba(113, 224, 166, 0.2);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
* {
|
|
62
|
+
box-sizing: border-box;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
body {
|
|
66
|
+
min-height: 100vh;
|
|
67
|
+
margin: 0;
|
|
68
|
+
display: grid;
|
|
69
|
+
place-items: center;
|
|
70
|
+
padding: 24px;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
background:
|
|
73
|
+
radial-gradient(circle at 18% 18%, var(--glow), transparent 34rem),
|
|
74
|
+
radial-gradient(circle at 82% 12%, rgba(209, 142, 72, 0.18), transparent 30rem),
|
|
75
|
+
linear-gradient(135deg, var(--page-bg), color-mix(in srgb, var(--page-bg) 76%, #000 24%));
|
|
76
|
+
color: var(--page-ink);
|
|
77
|
+
font-family: ui-rounded, "SF Pro Rounded", "Segoe UI", system-ui, sans-serif;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
body::before {
|
|
81
|
+
content: "";
|
|
82
|
+
position: fixed;
|
|
83
|
+
inset: -20%;
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
background-image:
|
|
86
|
+
linear-gradient(rgba(128, 104, 74, 0.08) 1px, transparent 1px),
|
|
87
|
+
linear-gradient(90deg, rgba(128, 104, 74, 0.08) 1px, transparent 1px);
|
|
88
|
+
background-size: 42px 42px;
|
|
89
|
+
mask-image: radial-gradient(circle at center, black, transparent 68%);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main {
|
|
93
|
+
position: relative;
|
|
94
|
+
width: min(100%, 560px);
|
|
95
|
+
padding: clamp(28px, 7vw, 56px);
|
|
96
|
+
border: 1px solid var(--panel-border);
|
|
97
|
+
border-radius: 32px;
|
|
98
|
+
background: var(--panel);
|
|
99
|
+
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.24);
|
|
100
|
+
text-align: center;
|
|
101
|
+
backdrop-filter: blur(18px) saturate(1.2);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.mark {
|
|
105
|
+
width: 72px;
|
|
106
|
+
height: 72px;
|
|
107
|
+
margin: 0 auto 24px;
|
|
108
|
+
display: grid;
|
|
109
|
+
place-items: center;
|
|
110
|
+
border-radius: 24px;
|
|
111
|
+
color: var(--state-color);
|
|
112
|
+
background: color-mix(in srgb, var(--state-color) 16%, transparent);
|
|
113
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--state-color) 28%, transparent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
[data-state="success"] { --state-color: var(--success); }
|
|
117
|
+
[data-state="error"] { --state-color: var(--error); }
|
|
118
|
+
[data-state="not-found"] { --state-color: var(--not-found); }
|
|
119
|
+
|
|
120
|
+
.eyebrow {
|
|
121
|
+
margin: 0 0 10px;
|
|
122
|
+
color: var(--state-color);
|
|
123
|
+
font-size: 0.78rem;
|
|
124
|
+
font-weight: 800;
|
|
125
|
+
letter-spacing: 0.14em;
|
|
126
|
+
text-transform: uppercase;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
h1 {
|
|
130
|
+
margin: 0;
|
|
131
|
+
font-size: clamp(2rem, 7vw, 3.35rem);
|
|
132
|
+
line-height: 0.95;
|
|
133
|
+
letter-spacing: -0.06em;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.message {
|
|
137
|
+
margin: 22px auto 0;
|
|
138
|
+
max-width: 38rem;
|
|
139
|
+
color: var(--muted);
|
|
140
|
+
font-size: clamp(1rem, 2.5vw, 1.1rem);
|
|
141
|
+
line-height: 1.65;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.next-step {
|
|
145
|
+
margin: 26px 0 0;
|
|
146
|
+
padding: 14px 18px;
|
|
147
|
+
border-radius: 999px;
|
|
148
|
+
background: color-mix(in srgb, var(--state-color) 12%, transparent);
|
|
149
|
+
color: var(--page-ink);
|
|
150
|
+
font-size: 0.94rem;
|
|
151
|
+
line-height: 1.5;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@media (max-width: 520px) {
|
|
155
|
+
body {
|
|
156
|
+
padding: 16px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
main {
|
|
160
|
+
border-radius: 24px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.next-step {
|
|
164
|
+
border-radius: 18px;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<main data-state="${pageState}" aria-labelledby="callback-title">
|
|
171
|
+
<div class="mark" aria-hidden="true">
|
|
172
|
+
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
173
|
+
${stateIcon}
|
|
174
|
+
</svg>
|
|
175
|
+
</div>
|
|
176
|
+
<p class="eyebrow">${eyebrow}</p>
|
|
177
|
+
<h1 id="callback-title">${escapedCardTitle}</h1>
|
|
178
|
+
<p class="message">${escapedMessage}</p>
|
|
179
|
+
<p class="next-step">${actionText}</p>
|
|
180
|
+
</main>
|
|
181
|
+
${autoCloseScript}
|
|
182
|
+
</body>
|
|
183
|
+
</html>`;
|
|
184
|
+
};
|
|
185
|
+
export const sendOAuthCallbackHtmlResponse = (response, statusCode, title, message) => {
|
|
186
|
+
response.writeHead(statusCode, {
|
|
187
|
+
'content-type': 'text/html; charset=utf-8',
|
|
188
|
+
'cache-control': 'no-store'
|
|
189
|
+
});
|
|
190
|
+
response.end(renderOAuthCallbackPage(statusCode, title, message));
|
|
191
|
+
};
|
|
192
|
+
//# sourceMappingURL=oauth-callback-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["escapeHtml","value","replace","character","replacements","renderOAuthCallbackPage","statusCode","title","message","escapedTitle","escapedMessage","isSuccess","pageState","cardTitle","escapedCardTitle","eyebrow","actionText","autoCloseScript","stateIcon","sendOAuthCallbackHtmlResponse","response","writeHead","end"],"sources":["../src/oauth-callback-page.ts"],"sourcesContent":["import type http from 'node:http';\n\nexport const escapeHtml = (value: string): string => {\n return value.replace(/[&<>'\"]/g, (character) => {\n const replacements: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n \"'\": ''',\n '\"': '"',\n };\n\n return replacements[character] ?? character;\n });\n};\n\nexport const renderOAuthCallbackPage = (statusCode: number, title: string, message: string): string => {\n const escapedTitle = escapeHtml(title);\n const escapedMessage = escapeHtml(message);\n const isSuccess = statusCode >= 200 && statusCode < 300;\n const pageState = isSuccess ? 'success' : statusCode === 404 ? 'not-found' : 'error';\n const cardTitle = isSuccess\n ? 'Authorization successful'\n : statusCode === 404\n ? 'Callback not found'\n : 'Authorization failed';\n const escapedCardTitle = escapeHtml(cardTitle);\n const eyebrow = isSuccess\n ? 'Authorization complete'\n : statusCode === 404\n ? 'Callback route not found'\n : 'Authorization needs attention';\n const actionText = isSuccess\n ? 'This window will close automatically in a moment. You can also close it now and return to OpenCode.'\n : 'You can close this window and return to OpenCode to try again.';\n const autoCloseScript = isSuccess\n ? `<script>\n window.setTimeout(() => window.close(), 2000);\n </script>`\n : '';\n const stateIcon = isSuccess\n ? '<path d=\"M7 12.5l3.1 3.1L17.5 8\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>'\n : statusCode === 404\n ? '<path d=\"M10.5 17a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13Z\" stroke=\"currentColor\" stroke-width=\"2.2\"/><path d=\"m15.5 15.5 4 4\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\"/>'\n : '<path d=\"M12 7v6\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\"/><path d=\"M12 17.2v.1\" stroke=\"currentColor\" stroke-width=\"3.2\" stroke-linecap=\"round\"/>';\n\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"color-scheme\" content=\"light dark\">\n <title>${escapedTitle}</title>\n <style>\n :root {\n color-scheme: light dark;\n --page-bg: #f2efe7;\n --page-ink: #211d18;\n --muted: #6c6258;\n --panel: rgba(255, 252, 245, 0.82);\n --panel-border: rgba(78, 66, 52, 0.16);\n --success: #167848;\n --error: #ba3329;\n --not-found: #986614;\n --glow: rgba(22, 120, 72, 0.18);\n }\n\n @media (prefers-color-scheme: dark) {\n :root {\n --page-bg: #12100d;\n --page-ink: #f7efe2;\n --muted: #b8aa98;\n --panel: rgba(30, 26, 22, 0.78);\n --panel-border: rgba(255, 244, 224, 0.14);\n --success: #71e0a6;\n --error: #ff897e;\n --not-found: #f7c96f;\n --glow: rgba(113, 224, 166, 0.2);\n }\n }\n\n * {\n box-sizing: border-box;\n }\n\n body {\n min-height: 100vh;\n margin: 0;\n display: grid;\n place-items: center;\n padding: 24px;\n overflow: hidden;\n background:\n radial-gradient(circle at 18% 18%, var(--glow), transparent 34rem),\n radial-gradient(circle at 82% 12%, rgba(209, 142, 72, 0.18), transparent 30rem),\n linear-gradient(135deg, var(--page-bg), color-mix(in srgb, var(--page-bg) 76%, #000 24%));\n color: var(--page-ink);\n font-family: ui-rounded, \"SF Pro Rounded\", \"Segoe UI\", system-ui, sans-serif;\n }\n\n body::before {\n content: \"\";\n position: fixed;\n inset: -20%;\n pointer-events: none;\n background-image:\n linear-gradient(rgba(128, 104, 74, 0.08) 1px, transparent 1px),\n linear-gradient(90deg, rgba(128, 104, 74, 0.08) 1px, transparent 1px);\n background-size: 42px 42px;\n mask-image: radial-gradient(circle at center, black, transparent 68%);\n }\n\n main {\n position: relative;\n width: min(100%, 560px);\n padding: clamp(28px, 7vw, 56px);\n border: 1px solid var(--panel-border);\n border-radius: 32px;\n background: var(--panel);\n box-shadow: 0 24px 90px rgba(0, 0, 0, 0.24);\n text-align: center;\n backdrop-filter: blur(18px) saturate(1.2);\n }\n\n .mark {\n width: 72px;\n height: 72px;\n margin: 0 auto 24px;\n display: grid;\n place-items: center;\n border-radius: 24px;\n color: var(--state-color);\n background: color-mix(in srgb, var(--state-color) 16%, transparent);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--state-color) 28%, transparent);\n }\n\n [data-state=\"success\"] { --state-color: var(--success); }\n [data-state=\"error\"] { --state-color: var(--error); }\n [data-state=\"not-found\"] { --state-color: var(--not-found); }\n\n .eyebrow {\n margin: 0 0 10px;\n color: var(--state-color);\n font-size: 0.78rem;\n font-weight: 800;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n }\n\n h1 {\n margin: 0;\n font-size: clamp(2rem, 7vw, 3.35rem);\n line-height: 0.95;\n letter-spacing: -0.06em;\n }\n\n .message {\n margin: 22px auto 0;\n max-width: 38rem;\n color: var(--muted);\n font-size: clamp(1rem, 2.5vw, 1.1rem);\n line-height: 1.65;\n }\n\n .next-step {\n margin: 26px 0 0;\n padding: 14px 18px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--state-color) 12%, transparent);\n color: var(--page-ink);\n font-size: 0.94rem;\n line-height: 1.5;\n }\n\n @media (max-width: 520px) {\n body {\n padding: 16px;\n }\n\n main {\n border-radius: 24px;\n }\n\n .next-step {\n border-radius: 18px;\n }\n }\n </style>\n </head>\n <body>\n <main data-state=\"${pageState}\" aria-labelledby=\"callback-title\">\n <div class=\"mark\" aria-hidden=\"true\">\n <svg width=\"34\" height=\"34\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n ${stateIcon}\n </svg>\n </div>\n <p class=\"eyebrow\">${eyebrow}</p>\n <h1 id=\"callback-title\">${escapedCardTitle}</h1>\n <p class=\"message\">${escapedMessage}</p>\n <p class=\"next-step\">${actionText}</p>\n </main>\n ${autoCloseScript}\n </body>\n</html>`;\n};\n\nexport const sendOAuthCallbackHtmlResponse = (\n response: http.ServerResponse,\n statusCode: number,\n title: string,\n message: string,\n) => {\n response.writeHead(statusCode, {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n });\n response.end(renderOAuthCallbackPage(statusCode, title, message));\n};\n"],"mappings":"AAEA,OAAO,MAAMA,UAAU,GAAIC,KAAa,IAAa;EACnD,OAAOA,KAAK,CAACC,OAAO,CAAC,UAAU,EAAGC,SAAS,IAAK;IAC9C,MAAMC,YAAoC,GAAG;MAC3C,GAAG,EAAE,OAAO;MACZ,GAAG,EAAE,MAAM;MACX,GAAG,EAAE,MAAM;MACX,GAAG,EAAE,OAAO;MACZ,GAAG,EAAE;IACP,CAAC;IAED,OAAOA,YAAY,CAACD,SAAS,CAAC,IAAIA,SAAS;EAC7C,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,MAAME,uBAAuB,GAAGA,CAACC,UAAkB,EAAEC,KAAa,EAAEC,OAAe,KAAa;EACrG,MAAMC,YAAY,GAAGT,UAAU,CAACO,KAAK,CAAC;EACtC,MAAMG,cAAc,GAAGV,UAAU,CAACQ,OAAO,CAAC;EAC1C,MAAMG,SAAS,GAAGL,UAAU,IAAI,GAAG,IAAIA,UAAU,GAAG,GAAG;EACvD,MAAMM,SAAS,GAAGD,SAAS,GAAG,SAAS,GAAGL,UAAU,KAAK,GAAG,GAAG,WAAW,GAAG,OAAO;EACpF,MAAMO,SAAS,GAAGF,SAAS,GACvB,0BAA0B,GAC1BL,UAAU,KAAK,GAAG,GAChB,oBAAoB,GACpB,sBAAsB;EAC5B,MAAMQ,gBAAgB,GAAGd,UAAU,CAACa,SAAS,CAAC;EAC9C,MAAME,OAAO,GAAGJ,SAAS,GACrB,wBAAwB,GACxBL,UAAU,KAAK,GAAG,GAChB,0BAA0B,GAC1B,+BAA+B;EACrC,MAAMU,UAAU,GAAGL,SAAS,GACxB,qGAAqG,GACrG,gEAAgE;EACpE,MAAMM,eAAe,GAAGN,SAAS,GAC7B;AACN;AACA,cAAc,GACR,EAAE;EACN,MAAMO,SAAS,GAAGP,SAAS,GACvB,4HAA4H,GAC5HL,UAAU,KAAK,GAAG,GAChB,gMAAgM,GAChM,4KAA4K;EAElL,OAAO;AACT;AACA;AACA;AACA;AACA;AACA,aAAaG,YAAY;AACzB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwBG,SAAS;AACjC;AACA;AACA,YAAYM,SAAS;AACrB;AACA;AACA,2BAA2BH,OAAO;AAClC,gCAAgCD,gBAAgB;AAChD,2BAA2BJ,cAAc;AACzC,6BAA6BM,UAAU;AACvC;AACA,MAAMC,eAAe;AACrB;AACA,QAAQ;AACR,CAAC;AAED,OAAO,MAAME,6BAA6B,GAAGA,CAC3CC,QAA6B,EAC7Bd,UAAkB,EAClBC,KAAa,EACbC,OAAe,KACZ;EACHY,QAAQ,CAACC,SAAS,CAACf,UAAU,EAAE;IAC7B,cAAc,EAAE,0BAA0B;IAC1C,eAAe,EAAE;EACnB,CAAC,CAAC;EACFc,QAAQ,CAACE,GAAG,CAACjB,uBAAuB,CAACC,UAAU,EAAEC,KAAK,EAAEC,OAAO,CAAC,CAAC;AACnE,CAAC","ignoreList":[]}
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { execFile } from 'node:child_process';
|
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { URL, fileURLToPath } from 'node:url';
|
|
9
9
|
import { resolveBackendOriginFromValues } from './config.js';
|
|
10
|
+
import { sendOAuthCallbackHtmlResponse } from './oauth-callback-page.js';
|
|
10
11
|
import { deleteFileIfExists, readJsonFile, writePrivateJsonFile } from './storage.js';
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
const MODULE_FILE_PATH = fileURLToPath(import.meta.url);
|
|
@@ -858,8 +859,8 @@ export const buildSystemNote = (result, config, details) => {
|
|
|
858
859
|
const projectSkills = catalog.skills.filter(skill => skill.contextKind === 'project' && !isUserPublishedSkillAssignment(skill.assignmentSource)).slice(0, 5).map(buildSkillCatalogLine);
|
|
859
860
|
const userSkills = catalog.skills.filter(skill => isUserPublishedSkillAssignment(skill.assignmentSource)).slice(0, 5).map(buildSkillCatalogLine);
|
|
860
861
|
const detailLines = details.slice(0, SYSTEM_NOTE_DETAIL_LIMIT).map(buildSkillDetailSnippetLine);
|
|
861
|
-
const detailBlock = detailLines.length > 0 ? `
|
|
862
|
-
return [result.fetchResult.payload.workspace ? `
|
|
862
|
+
const detailBlock = detailLines.length > 0 ? `Loaded body snippets (capped):\n${truncateText(detailLines.join('\n'), SYSTEM_NOTE_DETAIL_CHAR_LIMIT)}` : '';
|
|
863
|
+
return [result.fetchResult.payload.workspace ? `Workspace: ${result.fetchResult.payload.workspace.slug}.` : 'Workspace not found; workspace-scoped wizard skills are unavailable.', `Current directory: ${result.directoryPath}.`, `Active wizard skills: ${renderedSkillNames}${renderedCountSuffix}.`, `Counts: ${catalog.assignmentCounts.global} global, ${catalog.assignmentCounts.project} project, ${catalog.assignmentCounts.user} user, ${catalog.assignmentCounts.other} other.`, 'Wizard-listed skills are backend-published, not native OpenCode skills.', 'Use native skill tooling only for names in native available_skills.', 'When a wizard skill matches by whenToUse, fetch its current body with opencode_wizard_published_skills_fetch before using it.', 'Use `skills` for multiple wizard skill identifiers; use `skill` for one.', 'If native skill loading cannot find a wizard-listed skill, fetch it as a wizard skill instead.', 'Fetched wizard bodies are authoritative over local seed/native sources.', globalSkills.length > 0 ? `Global skills:\n${globalSkills.join('\n')}` : 'Global skills: none.', projectSkills.length > 0 ? `Project skills:\n${projectSkills.join('\n')}` : 'Project skills: none.', userSkills.length > 0 ? `User skills:\n${userSkills.join('\n')}` : 'User skills: none.', detailBlock].filter(Boolean).join(' ');
|
|
863
864
|
};
|
|
864
865
|
const toWorkspaceResolutionOutput = resolution => ({
|
|
865
866
|
requestedDirectory: resolution.requestedDirectory,
|
|
@@ -1216,194 +1217,6 @@ const toCallbackServerStartError = error => {
|
|
|
1216
1217
|
}
|
|
1217
1218
|
return new Error('OAuth login cannot start because localhost:24953 is already in use. Another OpenCode login is likely in progress; finish it or close the other instance, then retry.');
|
|
1218
1219
|
};
|
|
1219
|
-
const escapeHtml = value => {
|
|
1220
|
-
return value.replace(/[&<>'"]/g, character => {
|
|
1221
|
-
const replacements = {
|
|
1222
|
-
'&': '&',
|
|
1223
|
-
'<': '<',
|
|
1224
|
-
'>': '>',
|
|
1225
|
-
"'": ''',
|
|
1226
|
-
'"': '"'
|
|
1227
|
-
};
|
|
1228
|
-
return replacements[character] ?? character;
|
|
1229
|
-
});
|
|
1230
|
-
};
|
|
1231
|
-
const sendHtmlResponse = (response, statusCode, title, message) => {
|
|
1232
|
-
const escapedTitle = escapeHtml(title);
|
|
1233
|
-
const escapedMessage = escapeHtml(message);
|
|
1234
|
-
const isSuccess = statusCode >= 200 && statusCode < 300;
|
|
1235
|
-
const pageState = isSuccess ? 'success' : statusCode === 404 ? 'not-found' : 'error';
|
|
1236
|
-
const cardTitle = isSuccess ? 'Authorization successful' : statusCode === 404 ? 'Callback not found' : 'Authorization failed';
|
|
1237
|
-
const escapedCardTitle = escapeHtml(cardTitle);
|
|
1238
|
-
const eyebrow = isSuccess ? 'Authorization complete' : statusCode === 404 ? 'Callback route not found' : 'Authorization needs attention';
|
|
1239
|
-
const actionText = isSuccess ? 'This window will close automatically in a moment. You can also close it now and return to OpenCode.' : 'You can close this window and return to OpenCode to try again.';
|
|
1240
|
-
const autoCloseScript = isSuccess ? `<script>
|
|
1241
|
-
window.setTimeout(() => window.close(), 2000);
|
|
1242
|
-
</script>` : '';
|
|
1243
|
-
const stateIcon = isSuccess ? '<path d="M7 12.5l3.1 3.1L17.5 8" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>' : statusCode === 404 ? '<path d="M10.5 17a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13Z" stroke="currentColor" stroke-width="2.2"/><path d="m15.5 15.5 4 4" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/>' : '<path d="M12 7v6" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/><path d="M12 17.2v.1" stroke="currentColor" stroke-width="3.2" stroke-linecap="round"/>';
|
|
1244
|
-
response.writeHead(statusCode, {
|
|
1245
|
-
'content-type': 'text/html; charset=utf-8',
|
|
1246
|
-
'cache-control': 'no-store'
|
|
1247
|
-
});
|
|
1248
|
-
response.end(`<!doctype html>
|
|
1249
|
-
<html lang="en">
|
|
1250
|
-
<head>
|
|
1251
|
-
<meta charset="utf-8">
|
|
1252
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1253
|
-
<meta name="color-scheme" content="light dark">
|
|
1254
|
-
<title>${escapedTitle}</title>
|
|
1255
|
-
<style>
|
|
1256
|
-
:root {
|
|
1257
|
-
color-scheme: light dark;
|
|
1258
|
-
--page-bg: #f2efe7;
|
|
1259
|
-
--page-ink: #211d18;
|
|
1260
|
-
--muted: #6c6258;
|
|
1261
|
-
--panel: rgba(255, 252, 245, 0.82);
|
|
1262
|
-
--panel-border: rgba(78, 66, 52, 0.16);
|
|
1263
|
-
--success: #167848;
|
|
1264
|
-
--error: #ba3329;
|
|
1265
|
-
--not-found: #986614;
|
|
1266
|
-
--glow: rgba(22, 120, 72, 0.18);
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
@media (prefers-color-scheme: dark) {
|
|
1270
|
-
:root {
|
|
1271
|
-
--page-bg: #12100d;
|
|
1272
|
-
--page-ink: #f7efe2;
|
|
1273
|
-
--muted: #b8aa98;
|
|
1274
|
-
--panel: rgba(30, 26, 22, 0.78);
|
|
1275
|
-
--panel-border: rgba(255, 244, 224, 0.14);
|
|
1276
|
-
--success: #71e0a6;
|
|
1277
|
-
--error: #ff897e;
|
|
1278
|
-
--not-found: #f7c96f;
|
|
1279
|
-
--glow: rgba(113, 224, 166, 0.2);
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
* {
|
|
1284
|
-
box-sizing: border-box;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
body {
|
|
1288
|
-
min-height: 100vh;
|
|
1289
|
-
margin: 0;
|
|
1290
|
-
display: grid;
|
|
1291
|
-
place-items: center;
|
|
1292
|
-
padding: 24px;
|
|
1293
|
-
overflow: hidden;
|
|
1294
|
-
background:
|
|
1295
|
-
radial-gradient(circle at 18% 18%, var(--glow), transparent 34rem),
|
|
1296
|
-
radial-gradient(circle at 82% 12%, rgba(209, 142, 72, 0.18), transparent 30rem),
|
|
1297
|
-
linear-gradient(135deg, var(--page-bg), color-mix(in srgb, var(--page-bg) 76%, #000 24%));
|
|
1298
|
-
color: var(--page-ink);
|
|
1299
|
-
font-family: ui-rounded, "SF Pro Rounded", "Segoe UI", system-ui, sans-serif;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
body::before {
|
|
1303
|
-
content: "";
|
|
1304
|
-
position: fixed;
|
|
1305
|
-
inset: -20%;
|
|
1306
|
-
pointer-events: none;
|
|
1307
|
-
background-image:
|
|
1308
|
-
linear-gradient(rgba(128, 104, 74, 0.08) 1px, transparent 1px),
|
|
1309
|
-
linear-gradient(90deg, rgba(128, 104, 74, 0.08) 1px, transparent 1px);
|
|
1310
|
-
background-size: 42px 42px;
|
|
1311
|
-
mask-image: radial-gradient(circle at center, black, transparent 68%);
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
main {
|
|
1315
|
-
position: relative;
|
|
1316
|
-
width: min(100%, 560px);
|
|
1317
|
-
padding: clamp(28px, 7vw, 56px);
|
|
1318
|
-
border: 1px solid var(--panel-border);
|
|
1319
|
-
border-radius: 32px;
|
|
1320
|
-
background: var(--panel);
|
|
1321
|
-
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.24);
|
|
1322
|
-
text-align: center;
|
|
1323
|
-
backdrop-filter: blur(18px) saturate(1.2);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
.mark {
|
|
1327
|
-
width: 72px;
|
|
1328
|
-
height: 72px;
|
|
1329
|
-
margin: 0 auto 24px;
|
|
1330
|
-
display: grid;
|
|
1331
|
-
place-items: center;
|
|
1332
|
-
border-radius: 24px;
|
|
1333
|
-
color: var(--state-color);
|
|
1334
|
-
background: color-mix(in srgb, var(--state-color) 16%, transparent);
|
|
1335
|
-
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--state-color) 28%, transparent);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
[data-state="success"] { --state-color: var(--success); }
|
|
1339
|
-
[data-state="error"] { --state-color: var(--error); }
|
|
1340
|
-
[data-state="not-found"] { --state-color: var(--not-found); }
|
|
1341
|
-
|
|
1342
|
-
.eyebrow {
|
|
1343
|
-
margin: 0 0 10px;
|
|
1344
|
-
color: var(--state-color);
|
|
1345
|
-
font-size: 0.78rem;
|
|
1346
|
-
font-weight: 800;
|
|
1347
|
-
letter-spacing: 0.14em;
|
|
1348
|
-
text-transform: uppercase;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
h1 {
|
|
1352
|
-
margin: 0;
|
|
1353
|
-
font-size: clamp(2rem, 7vw, 3.35rem);
|
|
1354
|
-
line-height: 0.95;
|
|
1355
|
-
letter-spacing: -0.06em;
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
.message {
|
|
1359
|
-
margin: 22px auto 0;
|
|
1360
|
-
max-width: 38rem;
|
|
1361
|
-
color: var(--muted);
|
|
1362
|
-
font-size: clamp(1rem, 2.5vw, 1.1rem);
|
|
1363
|
-
line-height: 1.65;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
.next-step {
|
|
1367
|
-
margin: 26px 0 0;
|
|
1368
|
-
padding: 14px 18px;
|
|
1369
|
-
border-radius: 999px;
|
|
1370
|
-
background: color-mix(in srgb, var(--state-color) 12%, transparent);
|
|
1371
|
-
color: var(--page-ink);
|
|
1372
|
-
font-size: 0.94rem;
|
|
1373
|
-
line-height: 1.5;
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
@media (max-width: 520px) {
|
|
1377
|
-
body {
|
|
1378
|
-
padding: 16px;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
main {
|
|
1382
|
-
border-radius: 24px;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
.next-step {
|
|
1386
|
-
border-radius: 18px;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
</style>
|
|
1390
|
-
</head>
|
|
1391
|
-
<body>
|
|
1392
|
-
<main data-state="${pageState}" aria-labelledby="callback-title">
|
|
1393
|
-
<div class="mark" aria-hidden="true">
|
|
1394
|
-
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1395
|
-
${stateIcon}
|
|
1396
|
-
</svg>
|
|
1397
|
-
</div>
|
|
1398
|
-
<p class="eyebrow">${eyebrow}</p>
|
|
1399
|
-
<h1 id="callback-title">${escapedCardTitle}</h1>
|
|
1400
|
-
<p class="message">${escapedMessage}</p>
|
|
1401
|
-
<p class="next-step">${actionText}</p>
|
|
1402
|
-
</main>
|
|
1403
|
-
${autoCloseScript}
|
|
1404
|
-
</body>
|
|
1405
|
-
</html>`);
|
|
1406
|
-
};
|
|
1407
1220
|
const startLocalCallbackServer = async ({
|
|
1408
1221
|
expectedState,
|
|
1409
1222
|
signal
|
|
@@ -1428,14 +1241,14 @@ const startLocalCallbackServer = async ({
|
|
|
1428
1241
|
const server = http.createServer((request, response) => {
|
|
1429
1242
|
const requestUrl = new URL(request.url ?? '/', OIDC_CALLBACK_ORIGIN);
|
|
1430
1243
|
if (requestUrl.pathname !== OIDC_CALLBACK_PATH) {
|
|
1431
|
-
|
|
1244
|
+
sendOAuthCallbackHtmlResponse(response, 404, 'opencode-wizard plugin login', 'Unknown callback path.');
|
|
1432
1245
|
return;
|
|
1433
1246
|
}
|
|
1434
1247
|
const error = requestUrl.searchParams.get('error');
|
|
1435
1248
|
const errorDescription = requestUrl.searchParams.get('error_description');
|
|
1436
1249
|
if (error) {
|
|
1437
1250
|
const message = errorDescription ?? error;
|
|
1438
|
-
|
|
1251
|
+
sendOAuthCallbackHtmlResponse(response, 400, 'opencode-wizard plugin login failed', message);
|
|
1439
1252
|
finalize({
|
|
1440
1253
|
status: 'error',
|
|
1441
1254
|
message
|
|
@@ -1445,7 +1258,7 @@ const startLocalCallbackServer = async ({
|
|
|
1445
1258
|
const state = requestUrl.searchParams.get('state');
|
|
1446
1259
|
const code = requestUrl.searchParams.get('code');
|
|
1447
1260
|
if (!state || state !== expectedState) {
|
|
1448
|
-
|
|
1261
|
+
sendOAuthCallbackHtmlResponse(response, 400, 'opencode-wizard plugin login failed', 'OAuth state did not match the login request.');
|
|
1449
1262
|
finalize({
|
|
1450
1263
|
status: 'error',
|
|
1451
1264
|
message: 'OAuth state did not match the login request.'
|
|
@@ -1453,14 +1266,14 @@ const startLocalCallbackServer = async ({
|
|
|
1453
1266
|
return;
|
|
1454
1267
|
}
|
|
1455
1268
|
if (!code) {
|
|
1456
|
-
|
|
1269
|
+
sendOAuthCallbackHtmlResponse(response, 400, 'opencode-wizard plugin login failed', 'OAuth callback did not include an authorization code.');
|
|
1457
1270
|
finalize({
|
|
1458
1271
|
status: 'error',
|
|
1459
1272
|
message: 'OAuth callback did not include an authorization code.'
|
|
1460
1273
|
});
|
|
1461
1274
|
return;
|
|
1462
1275
|
}
|
|
1463
|
-
|
|
1276
|
+
sendOAuthCallbackHtmlResponse(response, 200, 'opencode-wizard plugin callback received', 'Callback received. OpenCode is finalizing the backend session now.');
|
|
1464
1277
|
finalize({
|
|
1465
1278
|
status: 'success',
|
|
1466
1279
|
code,
|
|
@@ -2486,7 +2299,7 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2486
2299
|
return {
|
|
2487
2300
|
tool: {
|
|
2488
2301
|
opencode_wizard_published_skills_fetch: tool({
|
|
2489
|
-
description: 'Fetch one or multiple published skill bodies/details for the current scope
|
|
2302
|
+
description: 'Fetch one or multiple wizard-published skill bodies/details for the current scope. Use this for wizard-listed/private/scoped/backend-published skill slugs instead of the native OpenCode skill tool, and after native errors like `Skill "..." not found`; prefer `skills` for multiple identifiers and call with no args to discover the catalog and bootstrap auth when needed',
|
|
2490
2303
|
args: {
|
|
2491
2304
|
skill: tool.schema.string().optional().describe('Single skill slug, artifact name, or skill name; backward-compatible with comma/newline-delimited lists'),
|
|
2492
2305
|
skills: tool.schema.string().optional().describe('One or more comma-separated or newline-separated skill slugs, artifact names, or skill names'),
|