@oharness/server 0.9.3 → 0.11.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/analytics.d.ts +32 -29
- package/dist/analytics.js +225 -278
- package/dist/analytics.js.map +1 -1
- package/dist/i18n.js +500 -64
- package/dist/i18n.js.map +1 -1
- package/dist/icons.js +1 -0
- package/dist/icons.js.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/model-order.js +1 -0
- package/dist/model-order.js.map +1 -1
- package/dist/page/client/account.js +570 -27
- package/dist/page/client/account.js.map +1 -1
- package/dist/page/client/base.js +55 -12
- package/dist/page/client/base.js.map +1 -1
- package/dist/page/client/browser.js +1 -1
- package/dist/page/client/composer.js +802 -59
- package/dist/page/client/composer.js.map +1 -1
- package/dist/page/client/computer.d.ts +27 -0
- package/dist/page/client/computer.js +358 -0
- package/dist/page/client/computer.js.map +1 -0
- package/dist/page/client/dialogs.js +168 -6
- package/dist/page/client/dialogs.js.map +1 -1
- package/dist/page/client/events.js +119 -9
- package/dist/page/client/events.js.map +1 -1
- package/dist/page/client/files.js +86 -1
- package/dist/page/client/files.js.map +1 -1
- package/dist/page/client/index.js +8 -0
- package/dist/page/client/index.js.map +1 -1
- package/dist/page/client/panels.js +14 -10
- package/dist/page/client/panels.js.map +1 -1
- package/dist/page/client/ripple.d.ts +13 -0
- package/dist/page/client/ripple.js +247 -0
- package/dist/page/client/ripple.js.map +1 -0
- package/dist/page/client/routines.js +432 -45
- package/dist/page/client/routines.js.map +1 -1
- package/dist/page/client/shell.js +386 -104
- package/dist/page/client/shell.js.map +1 -1
- package/dist/page/client/state.js +278 -19
- package/dist/page/client/state.js.map +1 -1
- package/dist/page/client/tasks.d.ts +8 -0
- package/dist/page/client/tasks.js +293 -0
- package/dist/page/client/tasks.js.map +1 -0
- package/dist/page/client/terminal.js +34 -7
- package/dist/page/client/terminal.js.map +1 -1
- package/dist/page/client/theme.d.ts +17 -0
- package/dist/page/client/theme.js +365 -0
- package/dist/page/client/theme.js.map +1 -0
- package/dist/page/client/transcript.js +196 -8
- package/dist/page/client/transcript.js.map +1 -1
- package/dist/page/client/usage.js +1 -11
- package/dist/page/client/usage.js.map +1 -1
- package/dist/page/style.js +745 -76
- package/dist/page/style.js.map +1 -1
- package/dist/page.js +94 -16
- package/dist/page.js.map +1 -1
- package/dist/prefs.d.ts +31 -0
- package/dist/prefs.js +13 -1
- package/dist/prefs.js.map +1 -1
- package/dist/remote-crypto.d.ts +78 -0
- package/dist/remote-crypto.js +216 -0
- package/dist/remote-crypto.js.map +1 -0
- package/dist/remote.d.ts +103 -0
- package/dist/remote.js +498 -0
- package/dist/remote.js.map +1 -0
- package/dist/server.d.ts +70 -0
- package/dist/server.js +1738 -73
- package/dist/server.js.map +1 -1
- package/dist/starters.d.ts +92 -0
- package/dist/starters.js +213 -0
- package/dist/starters.js.map +1 -0
- package/dist/suggest.d.ts +42 -0
- package/dist/suggest.js +215 -0
- package/dist/suggest.js.map +1 -0
- package/dist/terminal.d.ts +28 -2
- package/dist/terminal.js +45 -3
- package/dist/terminal.js.map +1 -1
- package/dist/theme-assets.d.ts +39 -0
- package/dist/theme-assets.js +109 -0
- package/dist/theme-assets.js.map +1 -0
- package/dist/theme-store.d.ts +50 -0
- package/dist/theme-store.js +124 -0
- package/dist/theme-store.js.map +1 -0
- package/dist/theme-tools.d.ts +25 -0
- package/dist/theme-tools.js +243 -0
- package/dist/theme-tools.js.map +1 -0
- package/dist/turn-shots.d.ts +43 -0
- package/dist/turn-shots.js +80 -0
- package/dist/turn-shots.js.map +1 -0
- package/dist/user-theme.d.ts +143 -0
- package/dist/user-theme.js +510 -0
- package/dist/user-theme.js.map +1 -0
- package/package.json +2 -2
package/dist/terminal.js
CHANGED
|
@@ -71,6 +71,40 @@ function shell() {
|
|
|
71
71
|
const file = process.env.SHELL || (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash");
|
|
72
72
|
return { file, args: ["-l"] };
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Programs a new shell may be opened running, by name. A fixed list rather
|
|
76
|
+
* than a command from the request: the page asks for "claude", and what that
|
|
77
|
+
* means is decided here.
|
|
78
|
+
*
|
|
79
|
+
* `claude` is the hand-off to Claude Code. It is the user's own install,
|
|
80
|
+
* signed in to their own account through Claude Code's own login — OHarness
|
|
81
|
+
* starts it and does nothing else, which is the one way of using a Claude
|
|
82
|
+
* plan from another app that Anthropic's terms leave plainly open. It is
|
|
83
|
+
* typed into the shell rather than spawned in its place, so PATH is the
|
|
84
|
+
* login shell's and the shell is still there when Claude Code exits.
|
|
85
|
+
*/
|
|
86
|
+
export const LAUNCHERS = {
|
|
87
|
+
claude: (platform, resume) => {
|
|
88
|
+
// `--resume` opens a session `@claude` ran, in Claude Code itself.
|
|
89
|
+
const run = resume ? `claude --resume ${resume}` : "claude";
|
|
90
|
+
return platform === "win32"
|
|
91
|
+
? `if (Get-Command claude -ErrorAction SilentlyContinue) { ${run} } ` +
|
|
92
|
+
"else { Write-Host 'Claude Code is not installed. Get it at https://claude.com/claude-code' -ForegroundColor Yellow }"
|
|
93
|
+
: `if command -v claude >/dev/null 2>&1; then ${run}; ` +
|
|
94
|
+
"else printf '\\033[33mClaude Code is not installed. Get it at https://claude.com/claude-code\\033[0m\\n'; fi";
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
export function isLauncher(value) {
|
|
98
|
+
return typeof value === "string" && Object.hasOwn(LAUNCHERS, value);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* An agent session id fit to put on a command line: letters, digits and
|
|
102
|
+
* dashes, the shape Claude Code's ids have. Anything else is refused rather
|
|
103
|
+
* than escaped — there is no legitimate id that needs escaping.
|
|
104
|
+
*/
|
|
105
|
+
export function isAgentSessionId(value) {
|
|
106
|
+
return typeof value === "string" && /^[A-Za-z0-9-]{8,64}$/.test(value);
|
|
107
|
+
}
|
|
74
108
|
function clampSize(value, fallback, max) {
|
|
75
109
|
const n = Math.floor(Number(value));
|
|
76
110
|
return Number.isFinite(n) && n > 0 ? Math.min(n, max) : fallback;
|
|
@@ -84,8 +118,11 @@ export class TerminalHub {
|
|
|
84
118
|
.filter((term) => term.cwd === cwd)
|
|
85
119
|
.map((term) => ({ id: term.id, name: term.name, exited: term.exited }));
|
|
86
120
|
}
|
|
87
|
-
/**
|
|
88
|
-
|
|
121
|
+
/**
|
|
122
|
+
* A new shell in `cwd`; `replace` names one it takes the place of, and
|
|
123
|
+
* `run` a launcher typed into it as soon as it starts.
|
|
124
|
+
*/
|
|
125
|
+
async open(cwd, size, replace, run, resume) {
|
|
89
126
|
if (replace)
|
|
90
127
|
this.close(replace);
|
|
91
128
|
const pty = await loadPty();
|
|
@@ -108,7 +145,7 @@ export class TerminalHub {
|
|
|
108
145
|
return { error: err.message };
|
|
109
146
|
}
|
|
110
147
|
const id = `t${++this.seq}`;
|
|
111
|
-
const name = path.basename(file).replace(/\.exe$/i, "");
|
|
148
|
+
const name = run ?? path.basename(file).replace(/\.exe$/i, "");
|
|
112
149
|
const term = { id, cwd, name, pty: proc, backlog: "", listeners: new Set(), exited: false };
|
|
113
150
|
this.terminals.set(id, term);
|
|
114
151
|
proc.onData((data) => {
|
|
@@ -119,6 +156,11 @@ export class TerminalHub {
|
|
|
119
156
|
term.exited = true;
|
|
120
157
|
this.emit(term, { type: "exit", code: exitCode });
|
|
121
158
|
});
|
|
159
|
+
// Typed rather than passed as `-c`, so the shell reads its profile first
|
|
160
|
+
// and is still at its prompt afterwards. The pty buffers it until the
|
|
161
|
+
// shell is ready to read.
|
|
162
|
+
if (run)
|
|
163
|
+
proc.write(LAUNCHERS[run](process.platform, resume && isAgentSessionId(resume) ? resume : undefined) + "\r");
|
|
122
164
|
return { id, name };
|
|
123
165
|
}
|
|
124
166
|
/** Attach a page: what has been written so far, then everything after. */
|
package/dist/terminal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Images a theme uses, kept where the theme is.
|
|
3
|
+
*
|
|
4
|
+
* A theme never points at a file in a project or at a URL: the image is copied
|
|
5
|
+
* here when the agent proposes it, named by the hash of its bytes, and the
|
|
6
|
+
* theme refers to that name. So a theme keeps working after the project it
|
|
7
|
+
* was made in is moved or deleted, the same picture used twice is stored once,
|
|
8
|
+
* and the name is the proof of the content — `GET /theme-asset/` can serve it
|
|
9
|
+
* without the session token, the way an unguessable capability URL works,
|
|
10
|
+
* because nobody can ask for a file they could not already have.
|
|
11
|
+
*
|
|
12
|
+
* Only still PNG, JPEG and WebP. SVG is a document that can carry script and
|
|
13
|
+
* fetch other things; an animation would run behind every message with no
|
|
14
|
+
* regard for reduced motion.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_ASSET_BYTES: number;
|
|
17
|
+
export declare const assetPath: (id: string) => string;
|
|
18
|
+
export declare const ASSET_TYPES: Record<string, string>;
|
|
19
|
+
/** What the bytes are, from their header — never from the file's name. */
|
|
20
|
+
export declare function sniffImage(bytes: Uint8Array): {
|
|
21
|
+
ext: "png" | "jpg" | "webp";
|
|
22
|
+
} | {
|
|
23
|
+
error: string;
|
|
24
|
+
};
|
|
25
|
+
/** Copy an image file in as an asset; returns its id. */
|
|
26
|
+
export declare function importAsset(file: string): Promise<{
|
|
27
|
+
ok: true;
|
|
28
|
+
id: string;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
error: string;
|
|
32
|
+
}>;
|
|
33
|
+
export declare function assetExists(id: string): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Delete assets nothing refers to any more. Anything touched in the last day
|
|
36
|
+
* is kept: it may belong to a preview card nobody has acted on yet, which only
|
|
37
|
+
* lives in the server's memory.
|
|
38
|
+
*/
|
|
39
|
+
export declare function pruneAssets(keep: Iterable<string>): Promise<void>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { userPath } from "@oharness/engine/paths.ts";
|
|
5
|
+
import { ASSET_ID } from "./user-theme.js";
|
|
6
|
+
/**
|
|
7
|
+
* Images a theme uses, kept where the theme is.
|
|
8
|
+
*
|
|
9
|
+
* A theme never points at a file in a project or at a URL: the image is copied
|
|
10
|
+
* here when the agent proposes it, named by the hash of its bytes, and the
|
|
11
|
+
* theme refers to that name. So a theme keeps working after the project it
|
|
12
|
+
* was made in is moved or deleted, the same picture used twice is stored once,
|
|
13
|
+
* and the name is the proof of the content — `GET /theme-asset/` can serve it
|
|
14
|
+
* without the session token, the way an unguessable capability URL works,
|
|
15
|
+
* because nobody can ask for a file they could not already have.
|
|
16
|
+
*
|
|
17
|
+
* Only still PNG, JPEG and WebP. SVG is a document that can carry script and
|
|
18
|
+
* fetch other things; an animation would run behind every message with no
|
|
19
|
+
* regard for reduced motion.
|
|
20
|
+
*/
|
|
21
|
+
export const MAX_ASSET_BYTES = 8 * 1024 * 1024;
|
|
22
|
+
const DIR = () => userPath("theme-assets");
|
|
23
|
+
export const assetPath = (id) => path.join(DIR(), id);
|
|
24
|
+
export const ASSET_TYPES = { png: "image/png", jpg: "image/jpeg", webp: "image/webp" };
|
|
25
|
+
/** What the bytes are, from their header — never from the file's name. */
|
|
26
|
+
export function sniffImage(bytes) {
|
|
27
|
+
const ascii = (from, to) => String.fromCharCode(...bytes.subarray(from, to));
|
|
28
|
+
if (bytes.length > 8 && bytes[0] === 0x89 && ascii(1, 4) === "PNG") {
|
|
29
|
+
// An animated PNG declares itself with an acTL chunk before the first IDAT.
|
|
30
|
+
const head = Buffer.from(bytes.subarray(0, Math.min(bytes.length, 65536)));
|
|
31
|
+
const actl = head.indexOf("acTL"), idat = head.indexOf("IDAT");
|
|
32
|
+
if (actl >= 0 && (idat < 0 || actl < idat))
|
|
33
|
+
return { error: "animated PNGs are not supported" };
|
|
34
|
+
return { ext: "png" };
|
|
35
|
+
}
|
|
36
|
+
if (bytes.length > 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
|
|
37
|
+
return { ext: "jpg" };
|
|
38
|
+
if (bytes.length > 30 && ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") {
|
|
39
|
+
// Extended WebP carries an animation flag in its VP8X header.
|
|
40
|
+
if (ascii(12, 16) === "VP8X" && (bytes[20] & 0x02) !== 0)
|
|
41
|
+
return { error: "animated WebP is not supported" };
|
|
42
|
+
return { ext: "webp" };
|
|
43
|
+
}
|
|
44
|
+
if (ascii(0, 5).toLowerCase().startsWith("<svg") || ascii(0, 5) === "<?xml")
|
|
45
|
+
return { error: "SVG is not supported; use PNG, JPEG or WebP" };
|
|
46
|
+
if (ascii(0, 3) === "GIF")
|
|
47
|
+
return { error: "GIF is not supported; use PNG, JPEG or WebP" };
|
|
48
|
+
return { error: "not a PNG, JPEG or WebP image" };
|
|
49
|
+
}
|
|
50
|
+
/** Copy an image file in as an asset; returns its id. */
|
|
51
|
+
export async function importAsset(file) {
|
|
52
|
+
let info;
|
|
53
|
+
try {
|
|
54
|
+
info = await stat(file);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { ok: false, error: `no file at ${file}` };
|
|
58
|
+
}
|
|
59
|
+
if (!info.isFile())
|
|
60
|
+
return { ok: false, error: `${file} is not a file` };
|
|
61
|
+
if (info.size > MAX_ASSET_BYTES)
|
|
62
|
+
return { ok: false, error: `${path.basename(file)} is over ${MAX_ASSET_BYTES / 1024 / 1024} MB` };
|
|
63
|
+
const bytes = await readFile(file);
|
|
64
|
+
const kind = sniffImage(bytes);
|
|
65
|
+
if ("error" in kind)
|
|
66
|
+
return { ok: false, error: `${path.basename(file)}: ${kind.error}` };
|
|
67
|
+
const id = `${createHash("sha256").update(bytes).digest("hex")}.${kind.ext}`;
|
|
68
|
+
await mkdir(DIR(), { recursive: true });
|
|
69
|
+
await writeFile(assetPath(id), bytes);
|
|
70
|
+
return { ok: true, id };
|
|
71
|
+
}
|
|
72
|
+
export async function assetExists(id) {
|
|
73
|
+
if (!ASSET_ID.test(id))
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
return (await stat(assetPath(id))).isFile();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Delete assets nothing refers to any more. Anything touched in the last day
|
|
84
|
+
* is kept: it may belong to a preview card nobody has acted on yet, which only
|
|
85
|
+
* lives in the server's memory.
|
|
86
|
+
*/
|
|
87
|
+
export async function pruneAssets(keep) {
|
|
88
|
+
const wanted = new Set(keep);
|
|
89
|
+
let names;
|
|
90
|
+
try {
|
|
91
|
+
names = await readdir(DIR());
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const dayAgo = Date.now() - 24 * 60 * 60 * 1000;
|
|
97
|
+
for (const name of names) {
|
|
98
|
+
if (!ASSET_ID.test(name) || wanted.has(name))
|
|
99
|
+
continue;
|
|
100
|
+
try {
|
|
101
|
+
if ((await stat(assetPath(name))).mtimeMs < dayAgo)
|
|
102
|
+
await unlink(assetPath(name));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Gone already, or not ours to remove: either way nothing to do.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=theme-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-assets.js","sourceRoot":"","sources":["../src/theme-assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ThemeInput } from "./user-theme.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The theme in use, the ones used before it, and the way back.
|
|
4
|
+
*
|
|
5
|
+
* One file in the user directory, beside prefs.json: a theme is a preference
|
|
6
|
+
* about how the app looks, not a property of a project. Stored as the input the
|
|
7
|
+
* model gave rather than as CSS, and resolved again on every load, so a stored
|
|
8
|
+
* theme always goes through today's validation and can never smuggle in
|
|
9
|
+
* anything the current rules would refuse.
|
|
10
|
+
*
|
|
11
|
+
* A file that cannot be read or does not validate is treated as no theme. The
|
|
12
|
+
* file itself is left where it is, for whoever wants to see what was in it:
|
|
13
|
+
* the one thing a broken theme must never do is stop the app from starting.
|
|
14
|
+
*/
|
|
15
|
+
export interface StoredTheme {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
appliedAt: number;
|
|
19
|
+
input: ThemeInput;
|
|
20
|
+
}
|
|
21
|
+
export interface ThemeState {
|
|
22
|
+
active: {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
} | null;
|
|
26
|
+
css: string;
|
|
27
|
+
canUndo: boolean;
|
|
28
|
+
history: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
appliedAt: number;
|
|
32
|
+
active: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare class ThemeStore {
|
|
36
|
+
private file;
|
|
37
|
+
private resolved;
|
|
38
|
+
static load(): Promise<ThemeStore>;
|
|
39
|
+
state(): ThemeState;
|
|
40
|
+
/** The active theme as given, for the model to start a revision from. */
|
|
41
|
+
activeInput(): ThemeInput | undefined;
|
|
42
|
+
apply(input: ThemeInput): Promise<ThemeState>;
|
|
43
|
+
applyHistory(id: string): Promise<ThemeState | undefined>;
|
|
44
|
+
reset(): Promise<ThemeState>;
|
|
45
|
+
undo(): Promise<ThemeState>;
|
|
46
|
+
private moveTo;
|
|
47
|
+
private activeEntry;
|
|
48
|
+
private resolveActive;
|
|
49
|
+
private save;
|
|
50
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { userPath } from "@oharness/engine/paths.ts";
|
|
5
|
+
import { parseJson } from "@oharness/engine/util/json.ts";
|
|
6
|
+
import { renameWithRetry } from "@oharness/engine/util/fs.ts";
|
|
7
|
+
import { pruneAssets } from "./theme-assets.js";
|
|
8
|
+
import { resolveTheme, themeAssets, themeCss } from "./user-theme.js";
|
|
9
|
+
const MAX_HISTORY = 10;
|
|
10
|
+
const MAX_UNDO = 20;
|
|
11
|
+
const FILE = () => userPath("theme.json");
|
|
12
|
+
export class ThemeStore {
|
|
13
|
+
file = { version: 1, active: null, history: [], undo: [] };
|
|
14
|
+
resolved;
|
|
15
|
+
static async load() {
|
|
16
|
+
const store = new ThemeStore();
|
|
17
|
+
try {
|
|
18
|
+
const parsed = parseJson(await readFile(FILE(), "utf8"));
|
|
19
|
+
if (parsed.ok)
|
|
20
|
+
store.file = clean(parsed.value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// No file yet, or one that cannot be read: the built-in theme.
|
|
24
|
+
}
|
|
25
|
+
store.resolveActive();
|
|
26
|
+
// Images no theme in the history uses any more. In the background: a slow
|
|
27
|
+
// disk must not hold up the page.
|
|
28
|
+
void pruneAssets(store.file.history.flatMap((h) => themeAssets(h.input)));
|
|
29
|
+
return store;
|
|
30
|
+
}
|
|
31
|
+
state() {
|
|
32
|
+
const entry = this.activeEntry();
|
|
33
|
+
return {
|
|
34
|
+
active: entry && this.resolved ? { id: entry.id, name: entry.name } : null,
|
|
35
|
+
css: this.resolved ? themeCss(this.resolved) : "",
|
|
36
|
+
canUndo: this.file.undo.length > 0,
|
|
37
|
+
history: this.file.history.map((h) => ({ id: h.id, name: h.name, appliedAt: h.appliedAt, active: h.id === this.file.active })),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The active theme as given, for the model to start a revision from. */
|
|
41
|
+
activeInput() {
|
|
42
|
+
return this.resolved ? this.activeEntry()?.input : undefined;
|
|
43
|
+
}
|
|
44
|
+
async apply(input) {
|
|
45
|
+
const entry = { id: randomUUID(), name: input.name, appliedAt: Date.now(), input };
|
|
46
|
+
this.file.history = [entry, ...this.file.history].slice(0, MAX_HISTORY);
|
|
47
|
+
return this.moveTo(entry.id);
|
|
48
|
+
}
|
|
49
|
+
async applyHistory(id) {
|
|
50
|
+
if (!this.file.history.some((h) => h.id === id))
|
|
51
|
+
return undefined;
|
|
52
|
+
return this.moveTo(id);
|
|
53
|
+
}
|
|
54
|
+
async reset() {
|
|
55
|
+
return this.moveTo(null);
|
|
56
|
+
}
|
|
57
|
+
async undo() {
|
|
58
|
+
const previous = this.file.undo.pop();
|
|
59
|
+
if (previous === undefined)
|
|
60
|
+
return this.state();
|
|
61
|
+
// An entry pushed out of history since cannot be gone back to; the
|
|
62
|
+
// built-in theme always can.
|
|
63
|
+
this.file.active = previous && this.file.history.some((h) => h.id === previous) ? previous : null;
|
|
64
|
+
this.resolveActive();
|
|
65
|
+
await this.save();
|
|
66
|
+
return this.state();
|
|
67
|
+
}
|
|
68
|
+
async moveTo(id) {
|
|
69
|
+
if (id === this.file.active)
|
|
70
|
+
return this.state();
|
|
71
|
+
this.file.undo = [...this.file.undo, this.file.active].slice(-MAX_UNDO);
|
|
72
|
+
this.file.active = id;
|
|
73
|
+
this.resolveActive();
|
|
74
|
+
await this.save();
|
|
75
|
+
return this.state();
|
|
76
|
+
}
|
|
77
|
+
activeEntry() {
|
|
78
|
+
return this.file.history.find((h) => h.id === this.file.active);
|
|
79
|
+
}
|
|
80
|
+
resolveActive() {
|
|
81
|
+
const entry = this.activeEntry();
|
|
82
|
+
const result = entry ? resolveTheme(entry.input) : undefined;
|
|
83
|
+
this.resolved = result?.ok ? result.theme : undefined;
|
|
84
|
+
}
|
|
85
|
+
async save() {
|
|
86
|
+
const file = FILE();
|
|
87
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
88
|
+
tempSeq += 1;
|
|
89
|
+
const temp = `${file}.${process.pid}.${tempSeq}.tmp`;
|
|
90
|
+
await writeFile(temp, `${JSON.stringify(this.file, null, 2)}\n`, "utf8");
|
|
91
|
+
await renameWithRetry(temp, file);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
let tempSeq = 0;
|
|
95
|
+
/** Only the shape this module wrote; anything else is dropped, not trusted. */
|
|
96
|
+
function clean(raw) {
|
|
97
|
+
const out = { version: 1, active: null, history: [], undo: [] };
|
|
98
|
+
if (typeof raw !== "object" || raw === null)
|
|
99
|
+
return out;
|
|
100
|
+
const body = raw;
|
|
101
|
+
if (Array.isArray(body["history"])) {
|
|
102
|
+
for (const item of body["history"]) {
|
|
103
|
+
if (typeof item !== "object" || item === null)
|
|
104
|
+
continue;
|
|
105
|
+
const h = item;
|
|
106
|
+
if (typeof h["id"] !== "string" || typeof h["input"] !== "object" || h["input"] === null)
|
|
107
|
+
continue;
|
|
108
|
+
out.history.push({
|
|
109
|
+
id: h["id"],
|
|
110
|
+
name: typeof h["name"] === "string" ? h["name"] : "Custom",
|
|
111
|
+
appliedAt: typeof h["appliedAt"] === "number" ? h["appliedAt"] : 0,
|
|
112
|
+
input: h["input"],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const ids = new Set(out.history.map((h) => h.id));
|
|
117
|
+
if (typeof body["active"] === "string" && ids.has(body["active"]))
|
|
118
|
+
out.active = body["active"];
|
|
119
|
+
if (Array.isArray(body["undo"])) {
|
|
120
|
+
out.undo = body["undo"].filter((v) => v === null || (typeof v === "string" && ids.has(v))).slice(-MAX_UNDO);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=theme-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-store.js","sourceRoot":"","sources":["../src/theme-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Tool } from "@oharness/engine/tools/types.ts";
|
|
2
|
+
import type { ResolvedTheme, ThemeInput } from "./user-theme.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The agent's side of theming: learn what can change, and propose a theme.
|
|
5
|
+
*
|
|
6
|
+
* Proposing is all it can do. `set_theme` makes a preview card in the
|
|
7
|
+
* conversation and returns; the theme is applied only when the person clicks
|
|
8
|
+
* Apply on it. That is why the tool is read-only and needs no approval: the
|
|
9
|
+
* card is the approval, and it comes with the result in front of them rather
|
|
10
|
+
* than a description of it.
|
|
11
|
+
*
|
|
12
|
+
* Registered by the web server only. A terminal has no page to theme.
|
|
13
|
+
*/
|
|
14
|
+
export declare const GET_THEME_SCHEMA_TOOL = "get_theme_schema";
|
|
15
|
+
export declare const SET_THEME_TOOL = "set_theme";
|
|
16
|
+
export declare const RESET_THEME_TOOL = "reset_theme";
|
|
17
|
+
export interface ThemeToolHost {
|
|
18
|
+
/** The theme in use as it was given, or undefined for the built-in one. */
|
|
19
|
+
activeInput(): ThemeInput | undefined;
|
|
20
|
+
/** Put a preview card in front of the person; returns its id. */
|
|
21
|
+
propose(sessionId: string, theme: ResolvedTheme): string;
|
|
22
|
+
/** Go back to the built-in theme; returns the name of the one it replaced. */
|
|
23
|
+
reset(): Promise<string | undefined>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createThemeTools(host: ThemeToolHost): Tool[];
|