@homescriptone/opencode-viewer-extender 0.0.0-stage → 1.2.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.
@@ -0,0 +1,95 @@
1
+ // Bulk select plugin: select multiple projects and sessions, then export them
2
+ // as a single .zip of JSON files or delete them (project deletes cascade).
3
+
4
+ OCV.define({
5
+ id: "bulk-select",
6
+ name: "Bulk select · export & delete",
7
+ description: "Select multiple projects and sessions to export as a .zip or delete.",
8
+
9
+ setup(api) {
10
+ api.registerBulkAction({
11
+ id: "bulk-export-zip",
12
+ label: "Export selected (.zip)",
13
+ async handler({ projectIds, sessionIds, host }) {
14
+ const entries = [];
15
+ const seenSessions = new Set();
16
+ const projectsById = new Map(host.getProjects().map((project) => [project.id, project]));
17
+
18
+ const addSession = async (sessionId, folder) => {
19
+ if (seenSessions.has(sessionId)) return;
20
+ seenSessions.add(sessionId);
21
+ const detail = await host.api.getSessionDetail(sessionId);
22
+ entries.push({ name: `${folder}/${sessionId}.json`, data: JSON.stringify(detail, null, 2) });
23
+ };
24
+
25
+ for (const projectId of projectIds) {
26
+ const project = projectsById.get(projectId);
27
+ const folderName = sanitize(`${project?.name || projectId}-${projectId}`);
28
+ entries.push({
29
+ name: `projects/${folderName}/project.json`,
30
+ data: JSON.stringify(project ?? { id: projectId }, null, 2)
31
+ });
32
+ const sessions = await host.api.getProjectSessions(projectId);
33
+ for (const session of sessions) {
34
+ await addSession(session.id, `projects/${folderName}/sessions`);
35
+ }
36
+ }
37
+
38
+ for (const sessionId of sessionIds) {
39
+ await addSession(sessionId, "sessions");
40
+ }
41
+
42
+ entries.push({
43
+ name: "manifest.json",
44
+ data: JSON.stringify(
45
+ { exportedAt: new Date().toISOString(), projectIds, sessionIds, files: entries.length + 1 },
46
+ null,
47
+ 2
48
+ )
49
+ });
50
+
51
+ const blob = host.createZip(entries);
52
+ host.download(`ocv-export-${Date.now()}.zip`, blob, "application/zip");
53
+ host.notify(`Exported ${entries.length} files.`);
54
+ }
55
+ });
56
+
57
+ api.registerBulkAction({
58
+ id: "bulk-delete",
59
+ label: "Delete selected",
60
+ danger: true,
61
+ write: true,
62
+ async handler({ projectIds, sessionIds, host }) {
63
+ const parts = [];
64
+ if (projectIds.length) parts.push(`${projectIds.length} project(s) (and all their sessions)`);
65
+ if (sessionIds.length) parts.push(`${sessionIds.length} session(s)`);
66
+ if (!parts.length) return;
67
+
68
+ const ok = window.confirm(`Delete ${parts.join(" and ")}? This cannot be undone.`);
69
+ if (!ok) return;
70
+
71
+ let deleted = 0;
72
+ for (const projectId of projectIds) {
73
+ await host.api.deleteProject(projectId);
74
+ deleted += 1;
75
+ }
76
+ for (const sessionId of sessionIds) {
77
+ await host.api.deleteSession(sessionId);
78
+ deleted += 1;
79
+ }
80
+
81
+ host.clearSelection();
82
+ host.refresh();
83
+ host.notify(`Deleted ${deleted} item(s).`);
84
+ }
85
+ });
86
+ }
87
+ });
88
+
89
+ function sanitize(value) {
90
+ return String(value ?? "item")
91
+ .toLowerCase()
92
+ .replace(/[^a-z0-9]+/g, "-")
93
+ .replace(/^-+|-+$/g, "")
94
+ .slice(0, 60) || "item";
95
+ }
@@ -0,0 +1,75 @@
1
+ // Example OCV plugin (API v2).
2
+ // Drop any .js file into public/plugins/ and it is auto-loaded at startup.
3
+ //
4
+ // A plugin calls OCV.define({ id, name, description, setup(api), teardown? }).
5
+ // setup() registers features and returns nothing; every register* call returns
6
+ // an unregister function, and disabled plugins are torn down automatically.
7
+ //
8
+ // api registration hooks:
9
+ // api.registerPartRenderer(type, renderer) renderer(part, helpers) -> HTML
10
+ // api.registerExportFormat({ id, label, extension, mime, build })
11
+ // api.registerSessionAction({ id, label, handler })
12
+ // api.registerBulkAction({ id, label, danger?, handler })
13
+ //
14
+ // api.host exposes host data/actions:
15
+ // host.getProjects(), host.getSessions(), host.getSelection()
16
+ // host.api.getProjectSessions(id), host.api.getSessionDetail(id)
17
+ // host.api.deleteProject(id), host.api.deleteSession(id)
18
+ // host.download(name, content, mime), host.createZip(entries), host.refresh()
19
+
20
+ OCV.define({
21
+ id: "example",
22
+ name: "Example plugin",
23
+ description: "Demonstrates the plugin hooks: snapshot renderer, text export, copy session ID.",
24
+
25
+ setup(api) {
26
+ // 1. Custom part renderer: surface "snapshot" parts (hidden by default).
27
+ api.registerPartRenderer("snapshot", function (part, helpers) {
28
+ const hash = String(part.hash ?? part.snapshot ?? "").slice(0, 12);
29
+ if (!hash) return "";
30
+ return `<div class="part part-snapshot"><span class="tiny-muted">snapshot</span> <code>${helpers.escapeHtml(hash)}</code></div>`;
31
+ });
32
+
33
+ // 2. Custom export format: plain text transcript.
34
+ api.registerExportFormat({
35
+ id: "example-txt",
36
+ label: "Plain text (.txt) — example",
37
+ extension: "txt",
38
+ mime: "text/plain",
39
+ build(ctx) {
40
+ const lines = [];
41
+ lines.push(ctx.session?.title || "Session");
42
+ lines.push("");
43
+ for (const message of ctx.messages ?? []) {
44
+ if (message._type === "subagent") {
45
+ lines.push(`[subagent] ${message.title || message.id}`);
46
+ continue;
47
+ }
48
+ lines.push(`## ${message.role || "unknown"}`);
49
+ for (const part of message.parts ?? []) {
50
+ if (typeof part.text === "string" && part.text.trim()) lines.push(part.text.trim());
51
+ else if (part.type === "tool") lines.push(`[tool: ${part.tool ?? "unknown"}]`);
52
+ }
53
+ lines.push("");
54
+ }
55
+ return lines.join("\n");
56
+ }
57
+ });
58
+
59
+ // 3. Custom session action: copy the session id.
60
+ api.registerSessionAction({
61
+ id: "example-copy-id",
62
+ label: "Copy session ID",
63
+ handler(ctx) {
64
+ if (ctx.session?.id && navigator.clipboard) {
65
+ navigator.clipboard.writeText(String(ctx.session.id));
66
+ }
67
+ }
68
+ });
69
+ },
70
+
71
+ teardown() {
72
+ // registrations returned by api.register* are removed automatically;
73
+ // release any listeners/timers here if needed.
74
+ }
75
+ });