@aexol/opencode-wizard 0.3.0 → 0.3.2
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 +3 -1
- 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 +133 -196
- package/dist/server.js.map +1 -1
- package/dist/smoke-published-skills.js +2 -2
- package/dist/smoke-published-skills.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,7 +50,9 @@ Catalog discovery uses the backend-issued plugin session token stored at `~/.con
|
|
|
50
50
|
|
|
51
51
|
Call `opencode_wizard_published_skills_fetch` without `skill` or `skills` to manually bootstrap plugin login if needed and return catalog-only discovery output for the current directory scope.
|
|
52
52
|
|
|
53
|
-
No-arg discovery returns published skill summaries, assignment counts split into `global` and `
|
|
53
|
+
No-arg discovery returns published skill summaries, assignment counts split into `global`, `project`, `user`, and `other`, policy metadata, and no markdown bodies. Existing `skill` and `skills` calls still fetch one or more full skill body/detail payloads by slug, artifact name, or skill name.
|
|
54
|
+
|
|
55
|
+
Use `opencode_wizard_published_skill_preference_set` for non-TUI preference actions (`install`, `uninstall`, `ignore`, `unignore`) against the same server-backed preference API used by the TUI overlay.
|
|
54
56
|
|
|
55
57
|
`GLOBAL_CONTEXT` skills are active context skills and are not meant to be installed per project. `PROJECT_INSTALLABLE` skills are gallery/installable skills that may be attached globally or to a workspace/path through assignment records; those assignments remain the source of truth for what is active in a catalog response.
|
|
56
58
|
|
|
@@ -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);
|
|
@@ -54,7 +55,7 @@ const statusPathLoginBootstrap = {
|
|
|
54
55
|
failedAt: null
|
|
55
56
|
};
|
|
56
57
|
const importOpencodePluginModule = new Function('specifier', 'return import(specifier)');
|
|
57
|
-
export const AVAILABLE_PUBLISHED_SKILL_TOOLS = ['opencode_wizard_published_skills_fetch', 'opencode_wizard_status'];
|
|
58
|
+
export const AVAILABLE_PUBLISHED_SKILL_TOOLS = ['opencode_wizard_published_skills_fetch', 'opencode_wizard_published_skill_preference_set', 'opencode_wizard_status'];
|
|
58
59
|
let publishedSkillPreferenceCacheVersion = 0;
|
|
59
60
|
export const NATIVE_SKILLS_URL_COMPATIBILITY = {
|
|
60
61
|
configKey: 'skills.urls',
|
|
@@ -516,6 +517,19 @@ const toIgnoredSkillSlug = value => {
|
|
|
516
517
|
if (!normalized) return null;
|
|
517
518
|
return normalized;
|
|
518
519
|
};
|
|
520
|
+
const toPublishedSkillPreferenceAction = value => {
|
|
521
|
+
const normalized = value.trim().toLowerCase();
|
|
522
|
+
if (normalized === 'install' || normalized === 'uninstall' || normalized === 'ignore' || normalized === 'unignore') {
|
|
523
|
+
return normalized;
|
|
524
|
+
}
|
|
525
|
+
throw new Error('Published skill preference action must be one of: install, uninstall, ignore, unignore.');
|
|
526
|
+
};
|
|
527
|
+
const toPublishedSkillPreferenceScope = (value, defaultScope) => {
|
|
528
|
+
if (!value) return defaultScope;
|
|
529
|
+
const normalized = value.trim().toLowerCase();
|
|
530
|
+
if (normalized === 'global' || normalized === 'project') return normalized;
|
|
531
|
+
throw new Error('Published skill preferenceScope must be either global or project.');
|
|
532
|
+
};
|
|
519
533
|
const getPublishedSkillIgnoreScopeKey = (resolution, payload) => {
|
|
520
534
|
const workspaceSlug = payload?.workspace?.slug ?? resolution.fallbackWorkspaceSlug;
|
|
521
535
|
if (workspaceSlug) return `workspace:${toWorkspaceSlug(workspaceSlug)}`;
|
|
@@ -858,8 +872,8 @@ export const buildSystemNote = (result, config, details) => {
|
|
|
858
872
|
const projectSkills = catalog.skills.filter(skill => skill.contextKind === 'project' && !isUserPublishedSkillAssignment(skill.assignmentSource)).slice(0, 5).map(buildSkillCatalogLine);
|
|
859
873
|
const userSkills = catalog.skills.filter(skill => isUserPublishedSkillAssignment(skill.assignmentSource)).slice(0, 5).map(buildSkillCatalogLine);
|
|
860
874
|
const detailLines = details.slice(0, SYSTEM_NOTE_DETAIL_LIMIT).map(buildSkillDetailSnippetLine);
|
|
861
|
-
const detailBlock = detailLines.length > 0 ? `
|
|
862
|
-
return [result.fetchResult.payload.workspace ? `
|
|
875
|
+
const detailBlock = detailLines.length > 0 ? `Loaded body snippets (capped):\n${truncateText(detailLines.join('\n'), SYSTEM_NOTE_DETAIL_CHAR_LIMIT)}` : '';
|
|
876
|
+
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
877
|
};
|
|
864
878
|
const toWorkspaceResolutionOutput = resolution => ({
|
|
865
879
|
requestedDirectory: resolution.requestedDirectory,
|
|
@@ -1216,194 +1230,6 @@ const toCallbackServerStartError = error => {
|
|
|
1216
1230
|
}
|
|
1217
1231
|
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
1232
|
};
|
|
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
1233
|
const startLocalCallbackServer = async ({
|
|
1408
1234
|
expectedState,
|
|
1409
1235
|
signal
|
|
@@ -1428,14 +1254,14 @@ const startLocalCallbackServer = async ({
|
|
|
1428
1254
|
const server = http.createServer((request, response) => {
|
|
1429
1255
|
const requestUrl = new URL(request.url ?? '/', OIDC_CALLBACK_ORIGIN);
|
|
1430
1256
|
if (requestUrl.pathname !== OIDC_CALLBACK_PATH) {
|
|
1431
|
-
|
|
1257
|
+
sendOAuthCallbackHtmlResponse(response, 404, 'opencode-wizard plugin login', 'Unknown callback path.');
|
|
1432
1258
|
return;
|
|
1433
1259
|
}
|
|
1434
1260
|
const error = requestUrl.searchParams.get('error');
|
|
1435
1261
|
const errorDescription = requestUrl.searchParams.get('error_description');
|
|
1436
1262
|
if (error) {
|
|
1437
1263
|
const message = errorDescription ?? error;
|
|
1438
|
-
|
|
1264
|
+
sendOAuthCallbackHtmlResponse(response, 400, 'opencode-wizard plugin login failed', message);
|
|
1439
1265
|
finalize({
|
|
1440
1266
|
status: 'error',
|
|
1441
1267
|
message
|
|
@@ -1445,7 +1271,7 @@ const startLocalCallbackServer = async ({
|
|
|
1445
1271
|
const state = requestUrl.searchParams.get('state');
|
|
1446
1272
|
const code = requestUrl.searchParams.get('code');
|
|
1447
1273
|
if (!state || state !== expectedState) {
|
|
1448
|
-
|
|
1274
|
+
sendOAuthCallbackHtmlResponse(response, 400, 'opencode-wizard plugin login failed', 'OAuth state did not match the login request.');
|
|
1449
1275
|
finalize({
|
|
1450
1276
|
status: 'error',
|
|
1451
1277
|
message: 'OAuth state did not match the login request.'
|
|
@@ -1453,14 +1279,14 @@ const startLocalCallbackServer = async ({
|
|
|
1453
1279
|
return;
|
|
1454
1280
|
}
|
|
1455
1281
|
if (!code) {
|
|
1456
|
-
|
|
1282
|
+
sendOAuthCallbackHtmlResponse(response, 400, 'opencode-wizard plugin login failed', 'OAuth callback did not include an authorization code.');
|
|
1457
1283
|
finalize({
|
|
1458
1284
|
status: 'error',
|
|
1459
1285
|
message: 'OAuth callback did not include an authorization code.'
|
|
1460
1286
|
});
|
|
1461
1287
|
return;
|
|
1462
1288
|
}
|
|
1463
|
-
|
|
1289
|
+
sendOAuthCallbackHtmlResponse(response, 200, 'opencode-wizard plugin callback received', 'Callback received. OpenCode is finalizing the backend session now.');
|
|
1464
1290
|
finalize({
|
|
1465
1291
|
status: 'success',
|
|
1466
1292
|
code,
|
|
@@ -2473,6 +2299,9 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2473
2299
|
// Keep returning the safe missing-auth snapshot when interactive login is cancelled or fails.
|
|
2474
2300
|
}
|
|
2475
2301
|
}
|
|
2302
|
+
if (snapshot.status === 'ready') {
|
|
2303
|
+
await scheduleInteractivePresenceStart();
|
|
2304
|
+
}
|
|
2476
2305
|
const metadata = toPluginStatusMetadata(snapshot);
|
|
2477
2306
|
context.metadata({
|
|
2478
2307
|
title: `opencode-wizard status: ${snapshot.status} / auth ${snapshot.authState.status}`,
|
|
@@ -2483,6 +2312,96 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2483
2312
|
metadata
|
|
2484
2313
|
};
|
|
2485
2314
|
};
|
|
2315
|
+
const executePublishedSkillPreferenceTool = async ({
|
|
2316
|
+
args,
|
|
2317
|
+
context
|
|
2318
|
+
}) => {
|
|
2319
|
+
const requestedDirectory = normalizeDirectoryArg(context.directory, args.directory);
|
|
2320
|
+
const directoryPath = normalizeRepositoryPath(workspacePath, requestedDirectory);
|
|
2321
|
+
lastInteractiveDirectoryPath = directoryPath;
|
|
2322
|
+
const requestedSkill = typeof args.skill === 'string' ? args.skill.trim() : '';
|
|
2323
|
+
const emitPreferenceOutcome = async event => {
|
|
2324
|
+
await emitActionEventForCurrentSession({
|
|
2325
|
+
event,
|
|
2326
|
+
directoryPath
|
|
2327
|
+
});
|
|
2328
|
+
};
|
|
2329
|
+
try {
|
|
2330
|
+
if (!requestedSkill) {
|
|
2331
|
+
throw new Error('Published skill preference tool requires a non-empty skill slug, artifact name, or skill name.');
|
|
2332
|
+
}
|
|
2333
|
+
if (typeof args.action !== 'string') {
|
|
2334
|
+
throw new Error('Published skill preference tool requires an action: install, uninstall, ignore, or unignore.');
|
|
2335
|
+
}
|
|
2336
|
+
const action = toPublishedSkillPreferenceAction(args.action);
|
|
2337
|
+
const catalogResult = await loadPublishedSkillCatalog({
|
|
2338
|
+
directory: requestedDirectory,
|
|
2339
|
+
useCache: true,
|
|
2340
|
+
signal: context.abort
|
|
2341
|
+
});
|
|
2342
|
+
if (!catalogResult.fetchResult.ok) {
|
|
2343
|
+
throw new Error(`Cannot resolve published skill preference target: ${catalogResult.fetchResult.message}`);
|
|
2344
|
+
}
|
|
2345
|
+
const selectableCatalogSkills = catalogResult.fetchResult.payload.catalogSkills.map(item => ({
|
|
2346
|
+
...item,
|
|
2347
|
+
assignmentSource: 'CATALOG',
|
|
2348
|
+
assignmentType: 'PATH',
|
|
2349
|
+
scopePath: '',
|
|
2350
|
+
includeChildren: true
|
|
2351
|
+
}));
|
|
2352
|
+
const preferenceSelection = selectPublishedSkills({
|
|
2353
|
+
...catalogResult.fetchResult.payload,
|
|
2354
|
+
skills: [...catalogResult.fetchResult.payload.skills, ...selectableCatalogSkills, ...catalogResult.fetchResult.payload.userPreferences.ignoredSkills]
|
|
2355
|
+
}, [requestedSkill]);
|
|
2356
|
+
const matchedSkill = preferenceSelection.selectedItems[0];
|
|
2357
|
+
if (!matchedSkill) {
|
|
2358
|
+
throw new Error(`Published skill preference target was not found for identifier: ${requestedSkill}.`);
|
|
2359
|
+
}
|
|
2360
|
+
const skillSlug = matchedSkill.skill.slug;
|
|
2361
|
+
const preferenceState = action === 'ignore' || action === 'unignore' ? await setPublishedSkillIgnored({
|
|
2362
|
+
worktree: input.worktree,
|
|
2363
|
+
directory: requestedDirectory,
|
|
2364
|
+
skillSlug,
|
|
2365
|
+
ignored: action === 'ignore',
|
|
2366
|
+
preferenceScope: toPublishedSkillPreferenceScope(args.preferenceScope, 'project')
|
|
2367
|
+
}) : await setPublishedSkillInstalled({
|
|
2368
|
+
worktree: input.worktree,
|
|
2369
|
+
directory: requestedDirectory,
|
|
2370
|
+
skillSlug,
|
|
2371
|
+
installed: action === 'install',
|
|
2372
|
+
preferenceScope: toPublishedSkillPreferenceScope(args.preferenceScope, 'project')
|
|
2373
|
+
});
|
|
2374
|
+
await scheduleInteractivePresenceStart();
|
|
2375
|
+
await emitPreferenceOutcome('PREFERENCE_SUCCESS');
|
|
2376
|
+
const metadata = {
|
|
2377
|
+
status: 'updated',
|
|
2378
|
+
skillSlug,
|
|
2379
|
+
action,
|
|
2380
|
+
directoryPath,
|
|
2381
|
+
ignoredSkillCount: preferenceState.ignoredSkillSlugs.length.toString()
|
|
2382
|
+
};
|
|
2383
|
+
context.metadata({
|
|
2384
|
+
title: `opencode-wizard published skill preference: ${action} ${skillSlug}`,
|
|
2385
|
+
metadata
|
|
2386
|
+
});
|
|
2387
|
+
return {
|
|
2388
|
+
output: JSON.stringify({
|
|
2389
|
+
pluginId: PLUGIN_ID,
|
|
2390
|
+
status: 'updated',
|
|
2391
|
+
requestedIdentifier: requestedSkill,
|
|
2392
|
+
skillSlug,
|
|
2393
|
+
action,
|
|
2394
|
+
requestedDirectoryPath: directoryPath,
|
|
2395
|
+
preferenceState,
|
|
2396
|
+
message: 'Published skill preference updated through the shared server-backed API; TUI views will reflect this after refresh.'
|
|
2397
|
+
}, null, 2),
|
|
2398
|
+
metadata
|
|
2399
|
+
};
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
await emitPreferenceOutcome('PREFERENCE_FAILED');
|
|
2402
|
+
throw error;
|
|
2403
|
+
}
|
|
2404
|
+
};
|
|
2486
2405
|
return {
|
|
2487
2406
|
tool: {
|
|
2488
2407
|
opencode_wizard_published_skills_fetch: tool({
|
|
@@ -2500,6 +2419,21 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2500
2419
|
});
|
|
2501
2420
|
}
|
|
2502
2421
|
}),
|
|
2422
|
+
opencode_wizard_published_skill_preference_set: tool({
|
|
2423
|
+
description: 'Install, uninstall, ignore, or unignore a backend-published wizard skill for non-TUI workflows using the same shared server-backed preference API as the TUI overlay',
|
|
2424
|
+
args: {
|
|
2425
|
+
skill: tool.schema.string().describe('Published skill slug, artifact name, or skill name to update'),
|
|
2426
|
+
action: tool.schema.string().describe('Preference action: install, uninstall, ignore, or unignore'),
|
|
2427
|
+
preferenceScope: tool.schema.string().optional().describe('Preference scope for the action: project or global; defaults to project'),
|
|
2428
|
+
directory: tool.schema.string().optional().describe('Optional absolute or relative directory override')
|
|
2429
|
+
},
|
|
2430
|
+
async execute(args, context) {
|
|
2431
|
+
return executePublishedSkillPreferenceTool({
|
|
2432
|
+
args,
|
|
2433
|
+
context
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
}),
|
|
2503
2437
|
opencode_wizard_status: tool({
|
|
2504
2438
|
description: 'Report opencode-wizard plugin status, bootstrap auth when missing, and return a safe auth summary without exposing tokens',
|
|
2505
2439
|
args: {
|
|
@@ -2539,6 +2473,9 @@ const OpencodeWizardSkillsPlugin = async input => {
|
|
|
2539
2473
|
return;
|
|
2540
2474
|
}
|
|
2541
2475
|
}
|
|
2476
|
+
if (publishedSkillsResult.fetchResult.ok) {
|
|
2477
|
+
await scheduleInteractivePresenceStart();
|
|
2478
|
+
}
|
|
2542
2479
|
const filteredPublishedSkillsResult = await filterIgnoredPublishedSkills(config, publishedSkillsResult);
|
|
2543
2480
|
const details = await loadSystemNoteDetails({
|
|
2544
2481
|
publishedSkillsResult: filteredPublishedSkillsResult,
|