opencode-self-improvement 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/logfmt.ts +11 -3
- package/src/viewer/server.ts +33 -0
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ Add a `plugins` entry to your opencode config —
|
|
|
130
130
|
```json
|
|
131
131
|
{
|
|
132
132
|
"$schema": "https://opencode.ai/config.json",
|
|
133
|
-
"plugins": ["opencode-self-improvement@0.2.
|
|
133
|
+
"plugins": ["opencode-self-improvement@0.2.2"]
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
@@ -144,7 +144,7 @@ bump the version in the config and restart. On Opencode v2 you can also manage
|
|
|
144
144
|
it with the CLI:
|
|
145
145
|
|
|
146
146
|
```sh
|
|
147
|
-
opencode plugin add opencode-self-improvement@0.2.
|
|
147
|
+
opencode plugin add opencode-self-improvement@0.2.2
|
|
148
148
|
opencode plugin update opencode-self-improvement
|
|
149
149
|
```
|
|
150
150
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-self-improvement",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Durable memory for opencode: remember facts, decisions, and commands across sessions, plus a one-command per-repo setup (/repo-init).",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
package/src/logfmt.ts
CHANGED
|
@@ -5,10 +5,17 @@ import { mkdir } from "node:fs/promises";
|
|
|
5
5
|
|
|
6
6
|
export type Fields = Record<string, unknown>;
|
|
7
7
|
|
|
8
|
+
// Escapes for quoted values. One record is one line, so a raw newline inside a
|
|
9
|
+
// value would end the record early and the rest of the value would be read
|
|
10
|
+
// back as a separate line of fields. Backslash is escaped too, so a value
|
|
11
|
+
// ending in `\` cannot swallow its closing quote.
|
|
12
|
+
const ESCAPE: Record<string, string> = { "\\": "\\\\", '"': '\\"', "\n": "\\n", "\r": "\\r", "\t": "\\t" };
|
|
13
|
+
const UNESCAPE: Record<string, string> = { "\\": "\\", '"': '"', n: "\n", r: "\r", t: "\t" };
|
|
14
|
+
|
|
8
15
|
export function quote(value: unknown): string {
|
|
9
16
|
const s = String(value);
|
|
10
17
|
if (s === "") return '""';
|
|
11
|
-
return /[\s"=]/.test(s) ? `"${s.replace(/"/g,
|
|
18
|
+
return /[\s"=]/.test(s) ? `"${s.replace(/[\\"\n\r\t]/g, (c) => ESCAPE[c])}"` : s;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
export function formatLine(fields: Fields): string {
|
|
@@ -20,9 +27,10 @@ export function formatLine(fields: Fields): string {
|
|
|
20
27
|
return parts.join(" ");
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
// Unescape a quoted logfmt value and read it back.
|
|
30
|
+
// Unescape a quoted logfmt value and read it back. Unknown sequences are kept
|
|
31
|
+
// as written: older files stored backslashes unescaped (`C:\Program Files`).
|
|
24
32
|
function unquote(s: string): string {
|
|
25
|
-
return s.replace(/\\
|
|
33
|
+
return s.replace(/\\(.)/g, (seq, c: string) => UNESCAPE[c] ?? seq);
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
export function parseLine(line: string): Fields | null {
|
package/src/viewer/server.ts
CHANGED
|
@@ -28,6 +28,36 @@ function error(msg: string, status = 400): Response {
|
|
|
28
28
|
return json({ error: msg }, status);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Binding 127.0.0.1 keeps other machines out, but not other websites open in
|
|
32
|
+
// the user's browser. Two checks close that:
|
|
33
|
+
// - Host must name this viewer. A DNS-rebinding page reaches 127.0.0.1 under
|
|
34
|
+
// its own hostname, so its requests arrive with a foreign Host.
|
|
35
|
+
// - Writes must be JSON from this viewer's own origin. A cross-site form or
|
|
36
|
+
// no-cors fetch can POST text/plain without a CORS preflight, and every
|
|
37
|
+
// memory edited here is injected into the agent's system prompt.
|
|
38
|
+
// A viewer deliberately exposed under another name (OPENCODE_VIEWER_HOST set
|
|
39
|
+
// to a LAN address, a hostname) can list it in OPENCODE_VIEWER_ALLOWED_HOSTS.
|
|
40
|
+
function allowedHostnames(): Set<string> {
|
|
41
|
+
const extra = (process.env.OPENCODE_VIEWER_ALLOWED_HOSTS ?? "").split(",");
|
|
42
|
+
const names = ["127.0.0.1", "localhost", "::1", HOST, ...extra]
|
|
43
|
+
.map((n) => n.trim().toLowerCase())
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.map((n) => (n.includes(":") && !n.startsWith("[") ? `[${n}]` : n));
|
|
46
|
+
return new Set(names);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function rejectRequest(req: Request, url: URL, port: number): Response | null {
|
|
50
|
+
if (!allowedHostnames().has(url.hostname) || (url.port || "80") !== String(port)) {
|
|
51
|
+
return error("unexpected Host header", 403);
|
|
52
|
+
}
|
|
53
|
+
if (req.method === "GET" || req.method === "HEAD") return null;
|
|
54
|
+
const origin = req.headers.get("origin");
|
|
55
|
+
if (origin !== null && origin !== url.origin) return error("cross-origin request refused", 403);
|
|
56
|
+
const type = (req.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
57
|
+
if (type !== "application/json") return error("content-type must be application/json", 415);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
31
61
|
interface Row {
|
|
32
62
|
ts: string;
|
|
33
63
|
type: string;
|
|
@@ -86,7 +116,10 @@ export async function startViewer(port: number = PORT): Promise<ViewerServer> {
|
|
|
86
116
|
hostname: HOST,
|
|
87
117
|
port,
|
|
88
118
|
fetch(req, srv) {
|
|
119
|
+
// Bun builds req.url from the request's Host header.
|
|
89
120
|
const url = new URL(req.url);
|
|
121
|
+
const rejected = rejectRequest(req, url, server.port ?? port);
|
|
122
|
+
if (rejected) return rejected;
|
|
90
123
|
if (url.pathname === "/") {
|
|
91
124
|
return new Response(page, {
|
|
92
125
|
headers: { "content-type": "text/html; charset=utf-8" },
|