dsh-claude-style 0.13.2 → 0.14.0-dev.12
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/CHANGELOG.md +182 -0
- package/LICENSE +0 -5
- package/README.en.md +1 -1
- package/README.md +2 -2
- package/THIRD_PARTY_NOTICES.md +282 -6
- package/docs/FEATURES.en.md +13 -14
- package/docs/FEATURES.md +13 -14
- package/docs/SETTINGS.en.md +21 -12
- package/docs/SETTINGS.md +21 -12
- package/docs/STYLE.md +151 -12
- package/lib/assets/1952ecd55916.js.br +0 -0
- package/lib/assets/3b91bf51db72.js.br +0 -0
- package/lib/assets/4ec527e58330.js.br +0 -0
- package/lib/assets/7323c8c86470.js.br +0 -0
- package/lib/assets/755d09170f37.js.br +0 -0
- package/lib/assets/c02a70a798bc.js.br +0 -0
- package/lib/assets/d8df7a57fbe3.js.br +0 -0
- package/lib/assets/manifest.json +28 -28
- package/lib/client.js +11 -8
- package/lib/host/brand.js +136 -0
- package/lib/host/http.js +74 -0
- package/lib/host/peakrate.js +7 -0
- package/lib/host/routes.js +26 -88
- package/lib/host/search.js +8 -1
- package/lib/host/settings.js +5 -0
- package/lib/model-descriptions.json +195 -43
- package/package.json +12 -4
- package/docs/decisions/D11-private-routes.md +0 -26
- package/docs/decisions/D12-fail-fast.md +0 -36
- package/docs/decisions/D14-account-surface.md +0 -26
- package/docs/decisions/D15-hdsl-contract.md +0 -24
- package/docs/decisions/D16-popover-baseline.md +0 -23
- package/docs/decisions/D17-permission-presets.md +0 -26
- package/docs/decisions/D19-host-contracts.md +0 -27
- package/docs/decisions/D20-sliding-pill.md +0 -26
- package/docs/decisions/D21-archived-list.md +0 -24
- package/docs/decisions/D22-search-panel.md +0 -30
- package/docs/decisions/D24-mascot.md +0 -32
- package/docs/decisions/D25-ban-screen-titlebar.md +0 -24
- package/docs/decisions/D26-motion-preference.md +0 -23
- package/docs/decisions/D27-context-stats.md +0 -35
- package/docs/decisions/D28-desktop-band.md +0 -23
- package/docs/decisions/D29-feature-switches.md +0 -24
- package/docs/decisions/D3-host-selectors.md +0 -28
- package/docs/decisions/D30-palette-and-typeface.md +0 -25
- package/docs/decisions/D31-windows-titlebar.md +0 -24
- package/docs/decisions/D32-chat-interactions.md +0 -33
- package/docs/decisions/D33-stylesheet-ownership.md +0 -26
- package/docs/decisions/D34-turn-navigator.md +0 -29
- package/docs/decisions/D35-selection-scope.md +0 -23
- package/docs/decisions/D36-modules-and-bundling.md +0 -35
- package/docs/decisions/D38-assets.md +0 -28
- package/docs/decisions/D39-on-demand-loading.md +0 -33
- package/docs/decisions/D4-composer-gate.md +0 -22
- package/docs/decisions/D40-observation-bus.md +0 -36
- package/docs/decisions/D41-scroll-owner.md +0 -40
- package/docs/decisions/D42-feature-manifest.md +0 -28
- package/docs/decisions/D43-overlay-and-upstream.md +0 -27
- package/docs/decisions/D44-contract-module.md +0 -36
- package/docs/decisions/D45-testing.md +0 -30
- package/docs/decisions/D46-monorepo.md +0 -31
- package/docs/decisions/D47-build-output.md +0 -30
- package/docs/decisions/D48-documentation.md +0 -28
- package/docs/decisions/D49-visual-yield.md +0 -30
- package/docs/decisions/D5-model-copy.md +0 -29
- package/docs/decisions/D50-brand.md +0 -24
- package/docs/decisions/D51-css-toolchain.md +0 -34
- package/docs/decisions/D52-header-band.md +0 -31
- package/docs/decisions/D53-usage-merge.md +0 -33
- package/docs/decisions/D54-peakrate-meter.md +0 -33
- package/docs/decisions/D58-redraw-retired.md +0 -29
- package/docs/decisions/D60-settings-transport.md +0 -31
- package/docs/decisions/D61-wallpaper-under-one-mask.md +0 -28
- package/docs/decisions/D62-file-row-merge.md +0 -28
- package/docs/decisions/D63-turn-status-retired.md +0 -29
- package/docs/decisions/D7-one-package.md +0 -23
- package/docs/decisions/D9-style-performance.md +0 -25
- package/docs/decisions/README.md +0 -86
- package/lib/assets/2c01384360ac.js.map.br +0 -0
- package/lib/assets/31253ed42440.js.br +0 -0
- package/lib/assets/5063c46824b6.js.br +0 -0
- package/lib/assets/55a6154f87b5.js.map.br +0 -0
- package/lib/assets/5e05b29d5005.js.br +0 -0
- package/lib/assets/668d06ca781b.js.map.br +0 -0
- package/lib/assets/930546669455.js.map.br +0 -0
- package/lib/assets/968fcbfc3e22.js.br +0 -0
- package/lib/assets/abf0547f08a4.js.br +0 -0
- package/lib/assets/ac9b0e625837.js.map.br +0 -0
- package/lib/assets/ad2905345a69.js.br +0 -0
- package/lib/assets/ba68f60ecc71.js.map.br +0 -0
- package/lib/assets/c52e41b4842b.js.map.br +0 -0
- package/lib/assets/e1291f79b7c9.js.br +0 -0
- package/lib/client.js.map +0 -7
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// packages/contracts/src/brand.ts
|
|
2
|
+
var BRAND_IMAGE_KINDS = ["mark", "wordmark"];
|
|
3
|
+
var BRAND_IMAGE_TYPES = {
|
|
4
|
+
"image/png": "png",
|
|
5
|
+
"image/svg+xml": "svg",
|
|
6
|
+
"image/webp": "webp"
|
|
7
|
+
};
|
|
8
|
+
var BRAND_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
// packages/contracts/src/routes.ts
|
|
11
|
+
var ROUTE_PREFIX = "/dsh-claude-style";
|
|
12
|
+
var USERNAME_PATH = `${ROUTE_PREFIX}/username`;
|
|
13
|
+
var HDSL_PATH = `${ROUTE_PREFIX}/hdsl`;
|
|
14
|
+
var HDSL_SKIN_PATH = `${ROUTE_PREFIX}/hdsl-skin.png`;
|
|
15
|
+
var SESSION_DELETE_PATH = `${ROUTE_PREFIX}/session-delete`;
|
|
16
|
+
var USAGE_PATH = `${ROUTE_PREFIX}/usage`;
|
|
17
|
+
var SESSION_SEARCH_PATH = `${ROUTE_PREFIX}/session-search`;
|
|
18
|
+
var BRAND_PATH = `${ROUTE_PREFIX}/brand`;
|
|
19
|
+
var BRAND_MARK_PATH = `${ROUTE_PREFIX}/brand/mark`;
|
|
20
|
+
var BRAND_WORDMARK_PATH = `${ROUTE_PREFIX}/brand/wordmark`;
|
|
21
|
+
var PEAKRATE_PATH = `${ROUTE_PREFIX}/peakrate`;
|
|
22
|
+
|
|
23
|
+
// packages/host/src/brand.ts
|
|
24
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { harnessPath } from "./harness-home.js";
|
|
27
|
+
import { fenceRefused, methodRefused, readRequestBody, sendJson } from "./http.js";
|
|
28
|
+
var IMAGE_PATHS = { mark: BRAND_MARK_PATH, wordmark: BRAND_WORDMARK_PATH };
|
|
29
|
+
var IMAGE_HEADERS = {
|
|
30
|
+
"cache-control": "no-cache",
|
|
31
|
+
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
|
32
|
+
"x-content-type-options": "nosniff"
|
|
33
|
+
};
|
|
34
|
+
var SIGNATURES = {
|
|
35
|
+
"image/png": [{ offset: 0, bytes: Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]) }],
|
|
36
|
+
"image/webp": [{ offset: 0, bytes: Buffer.from("RIFF", "latin1") }, { offset: 8, bytes: Buffer.from("WEBP", "latin1") }]
|
|
37
|
+
};
|
|
38
|
+
function createBrandStore(ctx) {
|
|
39
|
+
const dir = () => harnessPath(ctx, "dsh-claude-style", "brand");
|
|
40
|
+
const find = (kind) => {
|
|
41
|
+
for (const [type, extension] of Object.entries(BRAND_IMAGE_TYPES)) {
|
|
42
|
+
const path = join(dir(), `${kind}.${extension}`);
|
|
43
|
+
const stat = statSync(path, { throwIfNoEntry: false });
|
|
44
|
+
if (stat?.isFile() !== true) continue;
|
|
45
|
+
return { path, type, state: { type, version: `${Math.round(stat.mtimeMs)}-${stat.size}` } };
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
const answer = () => ({ ok: true, mark: find("mark")?.state ?? null, wordmark: find("wordmark")?.state ?? null });
|
|
50
|
+
const write = (kind, type, bytes) => {
|
|
51
|
+
const extension = BRAND_IMAGE_TYPES[type];
|
|
52
|
+
mkdirSync(dir(), { recursive: true });
|
|
53
|
+
const target = join(dir(), `${kind}.${extension}`);
|
|
54
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
55
|
+
writeFileSync(temporary, bytes);
|
|
56
|
+
renameSync(temporary, target);
|
|
57
|
+
for (const other of Object.values(BRAND_IMAGE_TYPES)) {
|
|
58
|
+
if (other !== extension) rmSync(join(dir(), `${kind}.${other}`), { force: true });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const remove = (kind) => {
|
|
62
|
+
for (const extension of Object.values(BRAND_IMAGE_TYPES)) rmSync(join(dir(), `${kind}.${extension}`), { force: true });
|
|
63
|
+
};
|
|
64
|
+
return { find, answer, write, remove };
|
|
65
|
+
}
|
|
66
|
+
function matchesSignature(type, bytes) {
|
|
67
|
+
const marks = SIGNATURES[type];
|
|
68
|
+
if (marks === void 0) return true;
|
|
69
|
+
return marks.every(({ offset, bytes: expected }) => bytes.subarray(offset, offset + expected.length).equals(expected));
|
|
70
|
+
}
|
|
71
|
+
async function storeImage(store, kind, req, res) {
|
|
72
|
+
const type = (req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
73
|
+
if (BRAND_IMAGE_TYPES[type] === void 0) {
|
|
74
|
+
sendJson(res, 415, { ok: false, error: "unsupported image type" });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const bytes = await readRequestBody(req, BRAND_IMAGE_MAX_BYTES);
|
|
78
|
+
if (bytes === null) {
|
|
79
|
+
res.once("finish", () => req.destroy());
|
|
80
|
+
sendJson(res, 413, { ok: false, error: "image too large" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (bytes.length === 0 || !matchesSignature(type, bytes)) {
|
|
84
|
+
sendJson(res, 400, { ok: false, error: "not an image of the declared type" });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
store.write(kind, type, bytes);
|
|
88
|
+
sendJson(res, 200, store.answer());
|
|
89
|
+
}
|
|
90
|
+
function brandRoutes(ctx) {
|
|
91
|
+
const store = createBrandStore(ctx);
|
|
92
|
+
const status = {
|
|
93
|
+
kind: "exact",
|
|
94
|
+
path: BRAND_PATH,
|
|
95
|
+
handler: (req, res) => {
|
|
96
|
+
if (methodRefused(req, res, ["GET"])) return;
|
|
97
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
98
|
+
sendJson(res, 200, store.answer());
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const images = BRAND_IMAGE_KINDS.map((kind) => ({
|
|
102
|
+
label: `brand ${kind}`,
|
|
103
|
+
route: {
|
|
104
|
+
kind: "exact",
|
|
105
|
+
path: IMAGE_PATHS[kind],
|
|
106
|
+
handler: (req, res) => {
|
|
107
|
+
if (methodRefused(req, res, ["GET", "HEAD", "PUT", "DELETE"])) return;
|
|
108
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
109
|
+
if (req.method === "PUT" || req.method === "DELETE") {
|
|
110
|
+
const done = req.method === "PUT" ? storeImage(store, kind, req, res) : Promise.resolve().then(() => {
|
|
111
|
+
store.remove(kind);
|
|
112
|
+
sendJson(res, 200, store.answer());
|
|
113
|
+
});
|
|
114
|
+
void done.catch((error) => {
|
|
115
|
+
if (res.headersSent) res.destroy();
|
|
116
|
+
else sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const found = store.find(kind);
|
|
121
|
+
if (found === null) {
|
|
122
|
+
res.writeHead(404, { "cache-control": "no-store" });
|
|
123
|
+
res.end();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const body = readFileSync(found.path);
|
|
127
|
+
res.writeHead(200, { ...IMAGE_HEADERS, "content-type": found.type, "content-length": String(body.byteLength) });
|
|
128
|
+
res.end(req.method === "HEAD" ? void 0 : body);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
return [{ label: "brand status", route: status }, ...images];
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
brandRoutes
|
|
136
|
+
};
|
package/lib/host/http.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// packages/host/src/http.ts
|
|
2
|
+
function refusalOf(ctx, req) {
|
|
3
|
+
const connection = ctx.get("connection");
|
|
4
|
+
if (typeof connection?.requestRejection === "function") return connection.requestRejection(req);
|
|
5
|
+
if (!isLoopbackPeer(req.socket.remoteAddress)) return 403;
|
|
6
|
+
const host = req.headers.host;
|
|
7
|
+
if (host !== void 0) {
|
|
8
|
+
if (!URL.canParse(`http://${host}`)) return 403;
|
|
9
|
+
const name = new URL(`http://${host}`).hostname;
|
|
10
|
+
if (name !== "localhost" && name !== "[::1]" && !/^127\.\d+\.\d+\.\d+$/.test(name)) return 403;
|
|
11
|
+
}
|
|
12
|
+
const site = req.headers["sec-fetch-site"];
|
|
13
|
+
if (site !== void 0 && site !== "same-origin" && site !== "none") return 403;
|
|
14
|
+
const origin = req.headers.origin;
|
|
15
|
+
if (origin === void 0) return void 0;
|
|
16
|
+
if (!URL.canParse(origin)) return 403;
|
|
17
|
+
return new URL(origin).host === host ? void 0 : 403;
|
|
18
|
+
}
|
|
19
|
+
function isLoopbackPeer(address) {
|
|
20
|
+
if (address === void 0) return false;
|
|
21
|
+
const plain = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
|
|
22
|
+
return plain === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(plain);
|
|
23
|
+
}
|
|
24
|
+
function sendJson(res, status, payload) {
|
|
25
|
+
const body = Buffer.from(JSON.stringify(payload));
|
|
26
|
+
res.writeHead(status, {
|
|
27
|
+
"content-type": "application/json; charset=utf-8",
|
|
28
|
+
"content-length": String(body.byteLength),
|
|
29
|
+
"cache-control": "no-store"
|
|
30
|
+
});
|
|
31
|
+
res.end(body);
|
|
32
|
+
}
|
|
33
|
+
function methodRefused(req, res, methods) {
|
|
34
|
+
if (req.method !== void 0 && methods.includes(req.method)) return false;
|
|
35
|
+
res.writeHead(405, { allow: methods.join(", ") });
|
|
36
|
+
res.end();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
function fenceRefused(ctx, req, res) {
|
|
40
|
+
const refused = refusalOf(ctx, req);
|
|
41
|
+
if (refused === void 0) return false;
|
|
42
|
+
sendJson(res, refused, { ok: false, error: refused === 401 ? "unauthorized" : "forbidden" });
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function readRequestBody(req, limit) {
|
|
46
|
+
return new Promise((settle) => {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
let size = 0;
|
|
49
|
+
let over = false;
|
|
50
|
+
req.on("data", (chunk) => {
|
|
51
|
+
if (over) return;
|
|
52
|
+
size += chunk.length;
|
|
53
|
+
if (size > limit) {
|
|
54
|
+
over = true;
|
|
55
|
+
settle(null);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
chunks.push(Buffer.from(chunk));
|
|
59
|
+
});
|
|
60
|
+
req.on("end", () => {
|
|
61
|
+
if (!over) settle(Buffer.concat(chunks));
|
|
62
|
+
});
|
|
63
|
+
req.on("error", () => {
|
|
64
|
+
if (!over) settle(null);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
fenceRefused,
|
|
70
|
+
methodRefused,
|
|
71
|
+
readRequestBody,
|
|
72
|
+
refusalOf,
|
|
73
|
+
sendJson
|
|
74
|
+
};
|
package/lib/host/peakrate.js
CHANGED
|
@@ -17,6 +17,7 @@ function createPeakRate(ctx) {
|
|
|
17
17
|
let fetchedAt = 0;
|
|
18
18
|
let loaded = false;
|
|
19
19
|
let timer;
|
|
20
|
+
let inFlight = null;
|
|
20
21
|
let lastAskedAt = 0;
|
|
21
22
|
const warn = (message) => ctx.logger?.warn?.(message);
|
|
22
23
|
function shippedCatalog() {
|
|
@@ -56,6 +57,7 @@ function createPeakRate(ctx) {
|
|
|
56
57
|
let raw;
|
|
57
58
|
try {
|
|
58
59
|
const controller = new AbortController();
|
|
60
|
+
inFlight = controller;
|
|
59
61
|
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
60
62
|
try {
|
|
61
63
|
const response = await fetch(PEAKRATE_CATALOG_URL, { signal: controller.signal });
|
|
@@ -66,6 +68,7 @@ function createPeakRate(ctx) {
|
|
|
66
68
|
raw = await response.json();
|
|
67
69
|
} finally {
|
|
68
70
|
clearTimeout(timeout);
|
|
71
|
+
if (inFlight === controller) inFlight = null;
|
|
69
72
|
}
|
|
70
73
|
} catch (error) {
|
|
71
74
|
warn(`dsh-claude-style: peak rate source unreachable, keeping the current catalog: ${error.message}`);
|
|
@@ -119,6 +122,10 @@ function createPeakRate(ctx) {
|
|
|
119
122
|
payload,
|
|
120
123
|
dispose() {
|
|
121
124
|
stopTimer();
|
|
125
|
+
if (inFlight !== null) {
|
|
126
|
+
inFlight.abort();
|
|
127
|
+
inFlight = null;
|
|
128
|
+
}
|
|
122
129
|
}
|
|
123
130
|
};
|
|
124
131
|
}
|
package/lib/host/routes.js
CHANGED
|
@@ -6,6 +6,9 @@ var HDSL_SKIN_PATH = `${ROUTE_PREFIX}/hdsl-skin.png`;
|
|
|
6
6
|
var SESSION_DELETE_PATH = `${ROUTE_PREFIX}/session-delete`;
|
|
7
7
|
var USAGE_PATH = `${ROUTE_PREFIX}/usage`;
|
|
8
8
|
var SESSION_SEARCH_PATH = `${ROUTE_PREFIX}/session-search`;
|
|
9
|
+
var BRAND_PATH = `${ROUTE_PREFIX}/brand`;
|
|
10
|
+
var BRAND_MARK_PATH = `${ROUTE_PREFIX}/brand/mark`;
|
|
11
|
+
var BRAND_WORDMARK_PATH = `${ROUTE_PREFIX}/brand/wordmark`;
|
|
9
12
|
var PEAKRATE_PATH = `${ROUTE_PREFIX}/peakrate`;
|
|
10
13
|
|
|
11
14
|
// packages/host/src/routes.ts
|
|
@@ -13,7 +16,9 @@ import { existsSync, readFileSync, readdirSync, realpathSync, rmSync, statSync }
|
|
|
13
16
|
import { userInfo } from "node:os";
|
|
14
17
|
import { join, resolve, sep } from "node:path";
|
|
15
18
|
import { brotliDecompressSync } from "node:zlib";
|
|
19
|
+
import { brandRoutes } from "./brand.js";
|
|
16
20
|
import { harnessPath } from "./harness-home.js";
|
|
21
|
+
import { fenceRefused, methodRefused, readRequestBody, refusalOf, sendJson } from "./http.js";
|
|
17
22
|
import { createHdslAccount } from "./hdsl.js";
|
|
18
23
|
import { packageRoot } from "./package-root.js";
|
|
19
24
|
import { createPeakRate } from "./peakrate.js";
|
|
@@ -33,37 +38,6 @@ var ASSETS_MANIFEST = "manifest.json";
|
|
|
33
38
|
var USAGE_TTL_MS = 5 * 60 * 1e3;
|
|
34
39
|
var SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
|
|
35
40
|
var DELETE_BODY_MAX = 4096;
|
|
36
|
-
function refusalOf(ctx, req) {
|
|
37
|
-
const connection = ctx.get("connection");
|
|
38
|
-
if (typeof connection?.requestRejection === "function") return connection.requestRejection(req);
|
|
39
|
-
if (!isLoopbackPeer(req.socket.remoteAddress)) return 403;
|
|
40
|
-
const host = req.headers.host;
|
|
41
|
-
if (host !== void 0) {
|
|
42
|
-
if (!URL.canParse(`http://${host}`)) return 403;
|
|
43
|
-
const name = new URL(`http://${host}`).hostname;
|
|
44
|
-
if (name !== "localhost" && name !== "[::1]" && !/^127\.\d+\.\d+\.\d+$/.test(name)) return 403;
|
|
45
|
-
}
|
|
46
|
-
const site = req.headers["sec-fetch-site"];
|
|
47
|
-
if (site !== void 0 && site !== "same-origin" && site !== "none") return 403;
|
|
48
|
-
const origin = req.headers.origin;
|
|
49
|
-
if (origin === void 0) return void 0;
|
|
50
|
-
if (!URL.canParse(origin)) return 403;
|
|
51
|
-
return new URL(origin).host === host ? void 0 : 403;
|
|
52
|
-
}
|
|
53
|
-
function isLoopbackPeer(address) {
|
|
54
|
-
if (address === void 0) return false;
|
|
55
|
-
const plain = address.startsWith("::ffff:") ? address.slice("::ffff:".length) : address;
|
|
56
|
-
return plain === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(plain);
|
|
57
|
-
}
|
|
58
|
-
function sendJson(res, status, payload) {
|
|
59
|
-
const body = Buffer.from(JSON.stringify(payload));
|
|
60
|
-
res.writeHead(status, {
|
|
61
|
-
"content-type": "application/json; charset=utf-8",
|
|
62
|
-
"content-length": String(body.byteLength),
|
|
63
|
-
"cache-control": "no-store"
|
|
64
|
-
});
|
|
65
|
-
res.end(body);
|
|
66
|
-
}
|
|
67
41
|
function sessionIsLive(ctx, sessionId) {
|
|
68
42
|
const sessions = ctx.get("sessions");
|
|
69
43
|
if (sessions === null || sessions === void 0) return false;
|
|
@@ -79,29 +53,6 @@ function sessionIsLive(ctx, sessionId) {
|
|
|
79
53
|
}
|
|
80
54
|
return true;
|
|
81
55
|
}
|
|
82
|
-
function readRequestBody(req, limit) {
|
|
83
|
-
return new Promise((settle) => {
|
|
84
|
-
const chunks = [];
|
|
85
|
-
let size = 0;
|
|
86
|
-
let over = false;
|
|
87
|
-
req.on("data", (chunk) => {
|
|
88
|
-
if (over) return;
|
|
89
|
-
size += chunk.length;
|
|
90
|
-
if (size > limit) {
|
|
91
|
-
over = true;
|
|
92
|
-
settle(null);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
chunks.push(Buffer.from(chunk));
|
|
96
|
-
});
|
|
97
|
-
req.on("end", () => {
|
|
98
|
-
if (!over) settle(Buffer.concat(chunks).toString("utf8"));
|
|
99
|
-
});
|
|
100
|
-
req.on("error", () => {
|
|
101
|
-
if (!over) settle(null);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
56
|
async function unarchiveSession(ctx, sessionId) {
|
|
106
57
|
const registry = ctx.get("workspaceRegistry");
|
|
107
58
|
if (typeof registry?.unarchiveSession !== "function") return;
|
|
@@ -119,7 +70,7 @@ async function deleteSession(ctx, req, res) {
|
|
|
119
70
|
}
|
|
120
71
|
let request = null;
|
|
121
72
|
try {
|
|
122
|
-
request = raw === null ? null : JSON.parse(raw);
|
|
73
|
+
request = raw === null ? null : JSON.parse(raw.toString("utf8"));
|
|
123
74
|
} catch {
|
|
124
75
|
request = null;
|
|
125
76
|
}
|
|
@@ -217,18 +168,6 @@ function registerRoutes(ctx, scope) {
|
|
|
217
168
|
return void 0;
|
|
218
169
|
}
|
|
219
170
|
};
|
|
220
|
-
const methodRefused = (req, res, methods) => {
|
|
221
|
-
if (req.method !== void 0 && methods.includes(req.method)) return false;
|
|
222
|
-
res.writeHead(405, { allow: methods.join(", ") });
|
|
223
|
-
res.end();
|
|
224
|
-
return true;
|
|
225
|
-
};
|
|
226
|
-
const fenceRefused = (req, res) => {
|
|
227
|
-
const refused = refusalOf(ctx, req);
|
|
228
|
-
if (refused === void 0) return false;
|
|
229
|
-
sendJson(res, refused, { ok: false, error: refused === 401 ? "unauthorized" : "forbidden" });
|
|
230
|
-
return true;
|
|
231
|
-
};
|
|
232
171
|
scope.effect(() => {
|
|
233
172
|
const disposers = [];
|
|
234
173
|
const report = (message, error) => ctx.logger?.warn?.(`dsh-claude-style: ${message}: ${error?.message ?? String(error)}`);
|
|
@@ -249,6 +188,13 @@ function registerRoutes(ctx, scope) {
|
|
|
249
188
|
report(`${label} route unavailable`, error);
|
|
250
189
|
}
|
|
251
190
|
};
|
|
191
|
+
const sendPayload = (res, answer) => {
|
|
192
|
+
void answer.then((value) => {
|
|
193
|
+
sendJson(res, 200, { ok: true, ...value });
|
|
194
|
+
}, (error) => {
|
|
195
|
+
sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
|
|
196
|
+
});
|
|
197
|
+
};
|
|
252
198
|
register("model copy", {
|
|
253
199
|
kind: "prefix",
|
|
254
200
|
path: ROUTE_PREFIX,
|
|
@@ -289,7 +235,7 @@ function registerRoutes(ctx, scope) {
|
|
|
289
235
|
path: USERNAME_PATH,
|
|
290
236
|
handler: (req, res) => {
|
|
291
237
|
if (methodRefused(req, res, ["GET", "HEAD"])) return;
|
|
292
|
-
if (fenceRefused(req, res)) return;
|
|
238
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
293
239
|
const username = userInfo().username || "";
|
|
294
240
|
const body = Buffer.from(JSON.stringify({ ok: true, username }));
|
|
295
241
|
res.writeHead(200, {
|
|
@@ -305,13 +251,11 @@ function registerRoutes(ctx, scope) {
|
|
|
305
251
|
path: HDSL_PATH,
|
|
306
252
|
handler: (req, res) => {
|
|
307
253
|
if (methodRefused(req, res, ["GET", "HEAD"])) return;
|
|
308
|
-
if (fenceRefused(req, res)) return;
|
|
309
|
-
hdsl.read().then((profile) => {
|
|
254
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
255
|
+
sendPayload(res, hdsl.read().then((profile) => {
|
|
310
256
|
const { skinFile, ...account } = profile;
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
|
|
314
|
-
});
|
|
257
|
+
return account;
|
|
258
|
+
}));
|
|
315
259
|
}
|
|
316
260
|
});
|
|
317
261
|
register("HDSL skin", {
|
|
@@ -319,7 +263,7 @@ function registerRoutes(ctx, scope) {
|
|
|
319
263
|
path: HDSL_SKIN_PATH,
|
|
320
264
|
handler: (req, res) => {
|
|
321
265
|
if (methodRefused(req, res, ["GET", "HEAD"])) return;
|
|
322
|
-
if (fenceRefused(req, res)) return;
|
|
266
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
323
267
|
return hdsl.read().then((profile) => {
|
|
324
268
|
if (typeof profile.skinFile !== "string" || !existsSync(profile.skinFile)) {
|
|
325
269
|
res.writeHead(404, { "cache-control": "no-store" });
|
|
@@ -352,7 +296,7 @@ function registerRoutes(ctx, scope) {
|
|
|
352
296
|
path: USAGE_PATH,
|
|
353
297
|
handler: (req, res) => {
|
|
354
298
|
if (methodRefused(req, res, ["GET", "HEAD"])) return;
|
|
355
|
-
if (fenceRefused(req, res)) return;
|
|
299
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
356
300
|
let snapshot = usage.snapshot();
|
|
357
301
|
const stale = snapshot.value === null || snapshot.computing === true || Date.now() - (snapshot.value?.computedAt ?? 0) > USAGE_TTL_MS;
|
|
358
302
|
if (stale) {
|
|
@@ -372,14 +316,10 @@ function registerRoutes(ctx, scope) {
|
|
|
372
316
|
path: SESSION_SEARCH_PATH,
|
|
373
317
|
handler: (req, res) => {
|
|
374
318
|
if (methodRefused(req, res, ["GET"])) return;
|
|
375
|
-
if (fenceRefused(req, res)) return;
|
|
319
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
376
320
|
const query = (new URL(req.url ?? "/", "http://local").searchParams.get("q") ?? "").trim().slice(0, QUERY_MAX);
|
|
377
321
|
const answer = query === "" ? sessionSearch.warm().then(() => ({ sessions: [] })) : sessionSearch.search(query);
|
|
378
|
-
|
|
379
|
-
sendJson(res, 200, { ok: true, ...value });
|
|
380
|
-
}, (error) => {
|
|
381
|
-
sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
|
|
382
|
-
});
|
|
322
|
+
sendPayload(res, answer);
|
|
383
323
|
}
|
|
384
324
|
});
|
|
385
325
|
register("peak rate", {
|
|
@@ -387,19 +327,17 @@ function registerRoutes(ctx, scope) {
|
|
|
387
327
|
path: PEAKRATE_PATH,
|
|
388
328
|
handler: (req, res) => {
|
|
389
329
|
if (methodRefused(req, res, ["GET", "POST"])) return;
|
|
390
|
-
if (fenceRefused(req, res)) return;
|
|
330
|
+
if (fenceRefused(ctx, req, res)) return;
|
|
391
331
|
peakrate.ensure();
|
|
392
332
|
const answer = req.method === "POST" ? peakrate.refresh().then(() => peakrate.payload()) : Promise.resolve(peakrate.payload());
|
|
393
|
-
|
|
394
|
-
sendJson(res, 200, { ok: true, ...payload });
|
|
395
|
-
}, (error) => {
|
|
396
|
-
sendJson(res, 500, { ok: false, error: String(error?.message ?? String(error)) });
|
|
397
|
-
});
|
|
333
|
+
sendPayload(res, answer);
|
|
398
334
|
}
|
|
399
335
|
});
|
|
336
|
+
for (const { label, route } of brandRoutes(ctx)) register(label, route);
|
|
400
337
|
return () => {
|
|
401
338
|
usage.dispose();
|
|
402
339
|
peakrate.dispose();
|
|
340
|
+
sessionSearch.dispose();
|
|
403
341
|
for (const dispose of disposers) {
|
|
404
342
|
try {
|
|
405
343
|
dispose();
|
package/lib/host/search.js
CHANGED
|
@@ -201,7 +201,14 @@ function createSessionSearch(ctx) {
|
|
|
201
201
|
hits.sort((a, b) => b.time - a.time);
|
|
202
202
|
return { sessions: hits.slice(0, SESSION_LIMIT), scanned: cache.size };
|
|
203
203
|
}
|
|
204
|
-
|
|
204
|
+
function dispose() {
|
|
205
|
+
cache.clear();
|
|
206
|
+
lower.clear();
|
|
207
|
+
writtenAt.clear();
|
|
208
|
+
stored = null;
|
|
209
|
+
refreshing = null;
|
|
210
|
+
}
|
|
211
|
+
return { search, warm: () => catchUp(true), dispose };
|
|
205
212
|
}
|
|
206
213
|
export {
|
|
207
214
|
QUERY_MAX,
|
package/lib/host/settings.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// packages/contracts/src/prefs.ts
|
|
2
2
|
var PREFS_DEFAULT = Object.freeze({
|
|
3
3
|
brand: "claude",
|
|
4
|
+
brandName: "",
|
|
5
|
+
brandInk: "original",
|
|
6
|
+
brandAccent: "",
|
|
4
7
|
motion: "system",
|
|
8
|
+
statusMotion: "sweep",
|
|
9
|
+
statusDetail: "precise",
|
|
5
10
|
collapseFooter: true,
|
|
6
11
|
autoPopover: "all",
|
|
7
12
|
composerScope: "all",
|