@joe-broadhead/opencode-metaview 0.0.0-stage → 0.0.0

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/tui.js ADDED
@@ -0,0 +1,304 @@
1
+ // src/tui.ts
2
+ import { Plugin } from "@opencode/plugin/tui";
3
+
4
+ // src/redact.ts
5
+ var SECRETS = [
6
+ [/\b(bearer|basic|token)\s+[\w.~+/=-]{8,}/gi, "$1 [redacted]"],
7
+ [/\b([\w-]*(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password|signature|credential)["']?\s*[:=]\s*)["']?[^\s"'&,;]+["']?/gi, "$1[redacted]"],
8
+ [/\beyJ[\w-]{4,}\.[\w-]{4,}\.[\w-]{4,}/g, "[redacted]"],
9
+ [/\b(?:tvly|sk|pk|rk|ghp|gho|ghu|ghs|ghr|github_pat|glpat|xox[abposr]|lin_api|lin_oauth)[-_][\w-]{8,}/gi, "[redacted]"],
10
+ [/[\w+=-]{40,}/g, "[redacted]"]
11
+ ];
12
+ var REASON_LIMIT = 160;
13
+ var CONTROLS = /[\u0000-\u001f\u007f-\u009f]+/g;
14
+ var BIDI = /[‎‏‪-‮⁦-⁩]/g;
15
+ var withoutUrlSecrets = (text) => text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, (url) => url.replace(/^(https?:\/\/)[^/?#@]*@/i, "$1").replace(/[?#].*$/s, ""));
16
+ var oneLine = (text) => text.replace(BIDI, "").replace(CONTROLS, " ").replace(/\s+/g, " ").trim();
17
+ function cut(text, limit) {
18
+ const characters = Array.from(text);
19
+ return characters.length > limit ? `${characters.slice(0, limit - 1).join("")}\u2026` : text;
20
+ }
21
+ function shortReason(text) {
22
+ const reason = oneLine(SECRETS.reduce((value, [pattern, replacement]) => value.replace(pattern, replacement), withoutUrlSecrets(text))).replace(/[.\s]+$/, "");
23
+ if (reason === "") return void 0;
24
+ return cut(reason, REASON_LIMIT);
25
+ }
26
+
27
+ // src/tui.ts
28
+ import { createSignal } from "solid-js";
29
+
30
+ // src/constants.ts
31
+ var PLUGIN_ID = "metaview.workspace";
32
+ var SERVER_NAME = "metaview";
33
+ var SUPPORTED_HOST = ">=2.2.3 <2.3.0";
34
+ var TESTED_HOSTS = ["2.2.3", "2.2.4", "2.2.5"];
35
+ var errorText = (code, message, nextStep) => `${code}: ${message} Next step: ${nextStep}`;
36
+
37
+ // src/host-version.ts
38
+ var RELEASE = /^(\d+)\.(\d+)\.(\d+)$/;
39
+ var parse = (version) => RELEASE.exec(version)?.slice(1).map(Number);
40
+ var [LOWEST, ABOVE] = SUPPORTED_HOST.split(" ").map((bound) => parse(bound.replace(/^[<>]=?/, "")));
41
+ var compare = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
42
+ function isSupportedHost(version) {
43
+ const parts = parse(version);
44
+ return parts !== void 0 && compare(parts, LOWEST) >= 0 && compare(parts, ABOVE) < 0;
45
+ }
46
+
47
+ // src/metaview-tools.ts
48
+ var READ_TOOLS = [
49
+ "fetch_candidates",
50
+ "find_candidate_in_sequences",
51
+ "get_application_review_details",
52
+ "get_application_review_rules",
53
+ "get_chart_data",
54
+ "get_enrichment_status",
55
+ "get_job_post",
56
+ "get_job_post_template",
57
+ "get_job_post_thread",
58
+ "get_note_template",
59
+ "get_platform_credits",
60
+ "get_screen_details",
61
+ "get_screen_interview",
62
+ "get_search_details",
63
+ "get_sourcing_analytics",
64
+ "get_sourcing_messages",
65
+ // The account read: workspace name, role and plan, which status shows once the model or a script has called it.
66
+ "get_user_context",
67
+ "group_conversations",
68
+ "list_application_review_candidates",
69
+ "list_application_review_rejection_options",
70
+ "list_application_reviews",
71
+ "list_ats_jobs",
72
+ "list_ats_stages",
73
+ "list_field_values",
74
+ "list_fields",
75
+ "list_job_post_checks",
76
+ "list_job_post_templates",
77
+ "list_job_post_threads",
78
+ "list_job_posts",
79
+ "list_mailboxes",
80
+ "list_note_template_groups",
81
+ "list_note_templates",
82
+ "list_screen_candidates",
83
+ "list_screens",
84
+ "list_sequence_candidates",
85
+ "list_sequences",
86
+ "list_sourcing_candidates",
87
+ "list_sourcing_knowledge",
88
+ "list_sourcing_searches",
89
+ "search_conversations",
90
+ "search_reports",
91
+ "search_snippets"
92
+ ];
93
+ var DENIED_TOOLS = ["give_application_review_candidate_feedback", "manage_candidate_sequence", "submit_feedback"];
94
+
95
+ // src/permissions.ts
96
+ var actionOf = (tool) => `${SERVER_NAME}_${tool}`;
97
+ var rule = (tool, effect) => ({ action: actionOf(tool), resource: "*", effect });
98
+ var defaultRules = () => [rule("*", "ask"), ...READ_TOOLS.map((tool) => rule(tool, "allow")), ...DENIED_TOOLS.map((tool) => rule(tool, "deny"))];
99
+
100
+ // src/status.ts
101
+ var SIGN_IN = `Run /connect and choose Metaview, then press o or Enter to open the link (in a terminal, opencode mcp auth ${SERVER_NAME} opens it automatically).`;
102
+ var AGENT_RULES = defaultRules().map((rule2, index, rules) => ` ${JSON.stringify(rule2)}${index < rules.length - 1 ? "," : ""}`).join("\n");
103
+ var TESTED_TEXT = `${TESTED_HOSTS.slice(0, -1).join(", ")} or ${TESTED_HOSTS.at(-1)}`;
104
+ var STATE_TEXT = {
105
+ connected: "connected",
106
+ connecting: "connecting",
107
+ sign_in_required: "sign-in required",
108
+ failed: "failed",
109
+ disabled: "disabled",
110
+ not_configured: "not configured",
111
+ conflict: "conflict"
112
+ };
113
+ var NOT_READ = "not read yet";
114
+ var ROLE_TEXT = { admin: "admin: every conversation in the workspace", member: "member: your own conversations and those shared with you" };
115
+ var PLAN_TEXT = {
116
+ paid: "paid",
117
+ free: "free: transcripts, summaries and AI fields may be limited",
118
+ credits: "shared credits: conversations unlock on the workspace's platform credits"
119
+ };
120
+ var RULES_NOT_ADDED = "RULES_NOT_ADDED:";
121
+ function serverText(source) {
122
+ if (source.type === "plugin") return `${source.url}, registered by this plugin`;
123
+ if (source.type === "adopted") return `${source.url}, your own mcp.servers.${SERVER_NAME}${source.signIn === "own" ? " without OAuth" : ""}, used unchanged`;
124
+ return `your mcp.servers.${SERVER_NAME} points at ${source.url}`;
125
+ }
126
+ function toolsText({ tools, unknownCount, unknownTools, deniedTools, listing }) {
127
+ if (listing) return "none listed yet (Metaview is still listing them)";
128
+ const off = deniedTools.length ? `; off by default: ${list(deniedTools)}` : "";
129
+ if (unknownCount === 0) return `${tools} available, 0 unknown${off}`;
130
+ const more = unknownCount - unknownTools.length;
131
+ const names = unknownTools.length ? `: ${unknownTools.join(", ")}${more > 0 ? ` and ${more} more` : ""}` : "";
132
+ return `${tools} available, ${unknownCount} unknown${names} (unknown tools ask before running)${off}`;
133
+ }
134
+ var list = (names) => names.length < 2 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
135
+ function accessText({ role, problems, unguardedAgents: unguardedAgents2 }) {
136
+ const rules = problems.some((problem) => problem.startsWith(RULES_NOT_ADDED)) ? "default rules not added (see the OpenCode row)" : unguardedAgents2.length ? "default rules missing in the agents listed below" : "no agent runs new Metaview tools unasked";
137
+ return `${role ? ROLE_TEXT[role] : `role ${NOT_READ}`}; ${rules}`;
138
+ }
139
+ function hostText({ host, problems }) {
140
+ const { version, range, tested } = host;
141
+ const outside = version === null || !isSupportedHost(version);
142
+ const notes = [
143
+ ...outside ? [`It is outside the supported range; the plugin runs because allowUntestedHost is true. If Metaview misbehaves, use OpenCode ${TESTED_TEXT}.`] : [],
144
+ ...problems
145
+ ];
146
+ return `${version ?? "unknown version"} (${tested ? "tested" : "untested"}; supported range ${range})${notes.length ? `. ${notes.join(" ")}` : ""}`;
147
+ }
148
+ function formatStatus(status) {
149
+ const { source, unguardedAgents: agents } = status;
150
+ const details = [status.error, source && serverText(source), status.listing && "still listing its tools"].filter(Boolean).join("; ");
151
+ return [
152
+ "Metaview status",
153
+ `- Connection: ${STATE_TEXT[status.state]}${details ? ` (${details})` : ""}`,
154
+ `- Account: ${status.workspace ?? `${NOT_READ} (it appears after the first ${SERVER_NAME}_get_user_context call since OpenCode started)`}`,
155
+ `- Access: ${accessText(status)}`,
156
+ `- Plan: ${status.plan ? PLAN_TEXT[status.plan] : NOT_READ}`,
157
+ `- Tools: ${toolsText(status)}`,
158
+ `- Agents without default rules: ${agents.length ? `${agents.join(", ")}
159
+ ${AGENT_RULES}` : "none"}`,
160
+ `- OpenCode: ${hostText(status)}`,
161
+ `- Next step: ${status.nextStep}`
162
+ ].join("\n");
163
+ }
164
+ var strings = { type: "array", items: { type: "string" } };
165
+ var count = { type: "integer", minimum: 0 };
166
+ var nullableString = { type: ["string", "null"] };
167
+ var StatusRpc = {
168
+ id: PLUGIN_ID,
169
+ methods: {
170
+ status: {
171
+ input: { type: "object" },
172
+ output: {
173
+ type: "object",
174
+ required: ["version", "state", "workspace", "role", "plan", "tools", "deniedTools", "listing", "unknownCount", "unknownTools", "unguardedAgents", "host", "problems", "nextStep"],
175
+ properties: {
176
+ version: { type: "string" },
177
+ state: { enum: ["connected", "sign_in_required", "connecting", "disabled", "failed", "not_configured", "conflict"] },
178
+ error: { type: "string" },
179
+ source: {
180
+ type: "object",
181
+ required: ["type", "url"],
182
+ properties: { type: { enum: ["plugin", "adopted", "conflict"] }, url: { type: "string" }, expected: { type: "string" }, signIn: { enum: ["oauth", "own"] } }
183
+ },
184
+ workspace: nullableString,
185
+ role: { enum: ["admin", "member", null] },
186
+ plan: { enum: ["paid", "free", "credits", null] },
187
+ tools: count,
188
+ deniedTools: strings,
189
+ listing: { type: "boolean" },
190
+ unknownCount: count,
191
+ unknownTools: strings,
192
+ unguardedAgents: strings,
193
+ shadowedSkills: {
194
+ type: "array",
195
+ items: { type: "object", required: ["id", "path"], properties: { id: { type: "string" }, path: { type: "string" } } }
196
+ },
197
+ host: {
198
+ type: "object",
199
+ required: ["version", "range", "tested"],
200
+ properties: { version: nullableString, range: { type: "string" }, tested: { type: "boolean" } }
201
+ },
202
+ problems: strings,
203
+ nextStep: { type: "string" }
204
+ }
205
+ },
206
+ errors: { unavailable: { type: "object", required: ["nextStep"], properties: { nextStep: { type: "string" } } } }
207
+ }
208
+ },
209
+ events: {}
210
+ };
211
+
212
+ // src/tui.ts
213
+ var TUI_ID = "metaview.workspace.tui";
214
+ var TITLE = "Metaview status";
215
+ var KEYS = "r refresh \xB7 esc close";
216
+ function viewMessage(result) {
217
+ return `${reportOf(result)}
218
+
219
+ ${KEYS}`;
220
+ }
221
+ function reportOf(result) {
222
+ if ("status" in result) return formatStatus(result.status).split("\n").slice(1).join("\n");
223
+ const { error } = result;
224
+ const type = typeof error === "object" && error !== null && "type" in error ? error.type : void 0;
225
+ if (type === "unavailable") {
226
+ const { message, data } = error;
227
+ if (typeof message === "string" && typeof data?.nextStep === "string") return `${message} Next step: ${data.nextStep}`;
228
+ }
229
+ if (type === "rpc.unavailable" || type === "rpc.method_not_found") {
230
+ return errorText("STATUS_UNAVAILABLE", "The Metaview plugin is not running on this OpenCode server.", "Run /plugins to see why it did not start.");
231
+ }
232
+ const reason = shortReason(typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error));
233
+ return errorText("STATUS_UNAVAILABLE", `Could not read Metaview's status${reason ? ` (${reason})` : ""}.`, "Restart OpenCode, then run /metaview again.");
234
+ }
235
+ function setupView(context) {
236
+ let open;
237
+ let latest = 0;
238
+ let stops = [];
239
+ const unsubscribe = () => {
240
+ for (const stop of stops.splice(0)) stop();
241
+ };
242
+ const subscribe = () => {
243
+ if (stops.length) return;
244
+ stops = [
245
+ context.data.on("mcp.status.changed", (event) => {
246
+ if (event.data.server === SERVER_NAME) refresh();
247
+ }),
248
+ // Status events can be missed while the TUI is reconnecting.
249
+ context.data.on("server.connected", refresh),
250
+ context.data.on("agent.updated", refresh),
251
+ // Signing in, out or switching accounts changes the account and the sign-in state.
252
+ context.data.on("integration.updated", refresh),
253
+ context.data.on("credential.updated", refresh),
254
+ context.data.on("credential.switched", refresh)
255
+ ];
256
+ };
257
+ const [shown, setShown] = createSignal(false);
258
+ const show = async (refresh2) => {
259
+ if (refresh2 && open === void 0) return;
260
+ const request = ++latest;
261
+ const location = context.location;
262
+ const result = await context.client.rpc(StatusRpc).status({}, location ? { location } : {}).then((status) => ({ status }), (error) => ({ error }));
263
+ if (request !== latest || refresh2 && open === void 0) return;
264
+ open = request;
265
+ setShown(true);
266
+ subscribe();
267
+ void context.ui.dialog.alert({ title: TITLE, message: viewMessage(result) }).then(() => {
268
+ if (open !== request) return;
269
+ open = void 0;
270
+ setShown(false);
271
+ unsubscribe();
272
+ });
273
+ context.ui.dialog.set({ size: "large" });
274
+ };
275
+ const refresh = () => void show(true);
276
+ context.keymap.layer(() => ({
277
+ mode: "global",
278
+ commands: [
279
+ {
280
+ id: "metaview.workspace.status",
281
+ title: "Metaview: Status",
282
+ description: "Sign-in, account, tools and the next step for Metaview",
283
+ group: "Metaview",
284
+ palette: true,
285
+ slash: { name: "metaview" },
286
+ run: () => show(false)
287
+ },
288
+ { id: "metaview.workspace.status.refresh", title: "Refresh Metaview status", group: "Metaview", bind: "r", enabled: shown, run: refresh }
289
+ ]
290
+ }));
291
+ return () => {
292
+ unsubscribe();
293
+ if (open === void 0) return;
294
+ open = void 0;
295
+ setShown(false);
296
+ context.ui.dialog.clear();
297
+ };
298
+ }
299
+ var tui_default = Plugin.define({ id: TUI_ID, setup: setupView });
300
+ export {
301
+ tui_default as default,
302
+ setupView,
303
+ viewMessage
304
+ };