@oharness/server 0.10.0 → 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.
Files changed (70) hide show
  1. package/dist/analytics.d.ts +32 -29
  2. package/dist/analytics.js +225 -278
  3. package/dist/analytics.js.map +1 -1
  4. package/dist/i18n.js +152 -8
  5. package/dist/i18n.js.map +1 -1
  6. package/dist/icons.js +1 -0
  7. package/dist/icons.js.map +1 -1
  8. package/dist/model-order.js +1 -0
  9. package/dist/model-order.js.map +1 -1
  10. package/dist/page/client/account.js +140 -4
  11. package/dist/page/client/account.js.map +1 -1
  12. package/dist/page/client/base.js +40 -16
  13. package/dist/page/client/base.js.map +1 -1
  14. package/dist/page/client/browser.js +1 -1
  15. package/dist/page/client/composer.js +111 -7
  16. package/dist/page/client/composer.js.map +1 -1
  17. package/dist/page/client/dialogs.js +75 -0
  18. package/dist/page/client/dialogs.js.map +1 -1
  19. package/dist/page/client/events.js +25 -2
  20. package/dist/page/client/events.js.map +1 -1
  21. package/dist/page/client/index.js +4 -0
  22. package/dist/page/client/index.js.map +1 -1
  23. package/dist/page/client/panels.js +4 -4
  24. package/dist/page/client/ripple.d.ts +13 -0
  25. package/dist/page/client/ripple.js +247 -0
  26. package/dist/page/client/ripple.js.map +1 -0
  27. package/dist/page/client/routines.js +173 -41
  28. package/dist/page/client/routines.js.map +1 -1
  29. package/dist/page/client/shell.js +200 -54
  30. package/dist/page/client/shell.js.map +1 -1
  31. package/dist/page/client/state.js +7 -2
  32. package/dist/page/client/state.js.map +1 -1
  33. package/dist/page/client/terminal.js +34 -7
  34. package/dist/page/client/terminal.js.map +1 -1
  35. package/dist/page/client/theme.d.ts +17 -0
  36. package/dist/page/client/theme.js +365 -0
  37. package/dist/page/client/theme.js.map +1 -0
  38. package/dist/page/client/transcript.js +63 -7
  39. package/dist/page/client/transcript.js.map +1 -1
  40. package/dist/page/style.js +183 -22
  41. package/dist/page/style.js.map +1 -1
  42. package/dist/page.js +4 -5
  43. package/dist/page.js.map +1 -1
  44. package/dist/prefs.d.ts +17 -0
  45. package/dist/prefs.js +5 -0
  46. package/dist/prefs.js.map +1 -1
  47. package/dist/server.js +465 -42
  48. package/dist/server.js.map +1 -1
  49. package/dist/suggest.d.ts +42 -0
  50. package/dist/suggest.js +215 -0
  51. package/dist/suggest.js.map +1 -0
  52. package/dist/terminal.d.ts +28 -2
  53. package/dist/terminal.js +45 -3
  54. package/dist/terminal.js.map +1 -1
  55. package/dist/theme-assets.d.ts +39 -0
  56. package/dist/theme-assets.js +109 -0
  57. package/dist/theme-assets.js.map +1 -0
  58. package/dist/theme-store.d.ts +50 -0
  59. package/dist/theme-store.js +124 -0
  60. package/dist/theme-store.js.map +1 -0
  61. package/dist/theme-tools.d.ts +25 -0
  62. package/dist/theme-tools.js +243 -0
  63. package/dist/theme-tools.js.map +1 -0
  64. package/dist/turn-shots.d.ts +18 -2
  65. package/dist/turn-shots.js +10 -1
  66. package/dist/turn-shots.js.map +1 -1
  67. package/dist/user-theme.d.ts +143 -0
  68. package/dist/user-theme.js +510 -0
  69. package/dist/user-theme.js.map +1 -0
  70. package/package.json +2 -2
@@ -0,0 +1,42 @@
1
+ import type { Message } from "@oharness/engine/core/types.ts";
2
+ import type { Harness } from "@oharness/engine/harness.ts";
3
+ /**
4
+ * The grey line in the composer: what the user is likely to type next.
5
+ *
6
+ * Written once a turn has ended on its own, from the last few exchanges — the
7
+ * requests, the replies' final answers and, for the latest, a line per tool
8
+ * call; never tool output — and offered for Tab to
9
+ * take. On the conversation's own model, the way Claude Code does it: the
10
+ * guess reads like the agent the user is talking to, and costs what that
11
+ * model costs, which the Settings switch is there to turn off.
12
+ *
13
+ * Absent whenever the model is unsure: an empty box is better than a wrong
14
+ * prediction sitting in it.
15
+ */
16
+ export declare const SUGGEST_PROMPT = "You predict the next message a user will send to their AI agent, so it can be offered in their input box for them to accept with one key.\n\nYou are given the last few exchanges of the conversation, oldest first. For the latest one you also see, in <agent_actions>, the tools the agent used on the way to its answer: what it read, changed and ran, and what failed.\n\nWrite the single most likely next message, as the user would type it:\n- a short instruction, at most 12 words, in the same language the user wrote in;\n- grounded in the latest reply: approving what the agent proposed, answering a question it asked, or picking up something it said was left unfinished or failing;\n- specific to this conversation, never generic (\"thanks\", \"continue\", \"ok\");\n- never something the agent already did, and never a new task the conversation does not point to.\n\nIf the user has wrapped up (thanked the agent, said it is done) or the latest reply leaves nothing open, reply with exactly NONE.\nReply with only the message or NONE: no quotes, no preamble.";
17
+ /** One request, the answer it got, and what the agent did in between. */
18
+ export interface Exchange {
19
+ ask: string;
20
+ reply: string;
21
+ actions: string[];
22
+ }
23
+ /** Roughly what a tokenizer makes of it: a token per CJK character, four of anything else. */
24
+ export declare function estimateTokens(text: string): number;
25
+ /** Cut to `tokens`, from the start, the end, or both ends with the middle elided. */
26
+ export declare function cut(text: string, tokens: number, keep: "head" | "tail" | "ends"): string;
27
+ /**
28
+ * The last few exchanges, oldest first, cut to their budgets and redacted.
29
+ *
30
+ * A request is a user message with text that the harness did not write:
31
+ * tool results come back as user messages with none, and a compaction
32
+ * summary, a task notification or a goal nudge are marked synthetic. The
33
+ * reply is the agent's final answer — the text after its last tool call —
34
+ * because the narration on the way ("let me look at X") is not what the user
35
+ * answers; when a turn ends on a call, its last words are used instead.
36
+ */
37
+ export declare function recentExchanges(messages: readonly Message[], count?: number): Exchange[];
38
+ /** The exchanges as the model reads them. */
39
+ export declare function suggestContext(messages: readonly Message[]): string | undefined;
40
+ /** One line fit for the box, or nothing: a long, multi-line or quoted answer is the model not following the brief. */
41
+ export declare function cleanSuggestion(raw: string): string | undefined;
42
+ export declare function suggestNext(harness: Harness, messages: readonly Message[], model: string, signal: AbortSignal): Promise<string | undefined>;
@@ -0,0 +1,215 @@
1
+ import { visibleText } from "@oharness/engine/core/types.ts";
2
+ import { redactSecrets } from "@oharness/engine/agent/memory-redact.ts";
3
+ /**
4
+ * The grey line in the composer: what the user is likely to type next.
5
+ *
6
+ * Written once a turn has ended on its own, from the last few exchanges — the
7
+ * requests, the replies' final answers and, for the latest, a line per tool
8
+ * call; never tool output — and offered for Tab to
9
+ * take. On the conversation's own model, the way Claude Code does it: the
10
+ * guess reads like the agent the user is talking to, and costs what that
11
+ * model costs, which the Settings switch is there to turn off.
12
+ *
13
+ * Absent whenever the model is unsure: an empty box is better than a wrong
14
+ * prediction sitting in it.
15
+ */
16
+ export const SUGGEST_PROMPT = `You predict the next message a user will send to their AI agent, so it can be offered in their input box for them to accept with one key.
17
+
18
+ You are given the last few exchanges of the conversation, oldest first. For the latest one you also see, in <agent_actions>, the tools the agent used on the way to its answer: what it read, changed and ran, and what failed.
19
+
20
+ Write the single most likely next message, as the user would type it:
21
+ - a short instruction, at most 12 words, in the same language the user wrote in;
22
+ - grounded in the latest reply: approving what the agent proposed, answering a question it asked, or picking up something it said was left unfinished or failing;
23
+ - specific to this conversation, never generic ("thanks", "continue", "ok");
24
+ - never something the agent already did, and never a new task the conversation does not point to.
25
+
26
+ If the user has wrapped up (thanked the agent, said it is done) or the latest reply leaves nothing open, reply with exactly NONE.
27
+ Reply with only the message or NONE: no quotes, no preamble.`;
28
+ /**
29
+ * What the guess reads, in estimated tokens rather than characters: a Chinese
30
+ * character is about a token and an English one about a quarter, so a budget
31
+ * in characters gave a Chinese conversation three times the context, and the
32
+ * bill to match.
33
+ *
34
+ * Most of it goes to the exchange that just ended — its request kept at both
35
+ * ends, where a pasted log's actual question sits, and its reply's final
36
+ * answer — and a little to each one before it, enough to say what the thread
37
+ * is about. With these caps three exchanges always fit.
38
+ */
39
+ const EXCHANGES = 3;
40
+ const LATEST_ASK_TOKENS = 400;
41
+ const LATEST_REPLY_TOKENS = 450;
42
+ const EARLIER_ASK_TOKENS = 80;
43
+ const EARLIER_REPLY_TOKENS = 80;
44
+ /** The actions line: the latest turn's tool calls, most recent kept. */
45
+ const MAX_ACTIONS = 12;
46
+ const ARG_TOKENS = 20;
47
+ const MAX_LENGTH = 120;
48
+ const TIMEOUT_MS = 15_000;
49
+ const WIDE = /[\u1100-\u11ff\u2e80-\ua4cf\uac00-\ud7af\uf900-\ufaff\ufe30-\ufe4f\uff00-\uffef]/;
50
+ /** Roughly what a tokenizer makes of it: a token per CJK character, four of anything else. */
51
+ export function estimateTokens(text) {
52
+ let tokens = 0;
53
+ for (const char of text)
54
+ tokens += WIDE.test(char) ? 1 : 0.25;
55
+ return Math.ceil(tokens);
56
+ }
57
+ /** The longest start, or end, of `text` within `tokens`. */
58
+ function fit(text, tokens, from) {
59
+ const chars = [...text];
60
+ let used = 0;
61
+ let n = 0;
62
+ for (; n < chars.length; n++) {
63
+ const char = chars[from === "head" ? n : chars.length - 1 - n];
64
+ used += WIDE.test(char) ? 1 : 0.25;
65
+ if (used > tokens)
66
+ break;
67
+ }
68
+ return from === "head" ? chars.slice(0, n).join("") : chars.slice(chars.length - n).join("");
69
+ }
70
+ /** Cut to `tokens`, from the start, the end, or both ends with the middle elided. */
71
+ export function cut(text, tokens, keep) {
72
+ if (estimateTokens(text) <= tokens)
73
+ return text;
74
+ if (keep === "head")
75
+ return fit(text, tokens, "head") + "…";
76
+ if (keep === "tail")
77
+ return "…" + fit(text, tokens, "tail");
78
+ return fit(text, tokens / 2, "head") + "\n…\n" + fit(text, tokens / 2, "tail");
79
+ }
80
+ /** The argument that says what a call was about: a path, a command, a pattern. */
81
+ const KEY_ARGS = ["path", "file_path", "command", "pattern", "url", "query", "description", "name"];
82
+ function describeCall(name, input, failed) {
83
+ let arg = "";
84
+ if (input && typeof input === "object") {
85
+ for (const key of KEY_ARGS) {
86
+ const value = input[key];
87
+ if (typeof value === "string" && value.trim()) {
88
+ arg = cut(redactSecrets(value.replace(/\s+/g, " ").trim()).text, ARG_TOKENS, "head");
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ return `${name}${arg ? ` ${JSON.stringify(arg)}` : ""}${failed ? " → failed" : ""}`;
94
+ }
95
+ /**
96
+ * A run of the same call is one line with a count: twenty reads of one file
97
+ * say less than "read ×20", and the room is for what else happened.
98
+ */
99
+ function collapse(actions) {
100
+ const out = [];
101
+ for (const line of actions) {
102
+ const last = out.at(-1);
103
+ if (last && last.line === line)
104
+ last.times++;
105
+ else
106
+ out.push({ line, times: 1 });
107
+ }
108
+ const lines = out.map(({ line, times }) => (times > 1 ? `${line} ×${times}` : line));
109
+ if (lines.length <= MAX_ACTIONS)
110
+ return lines;
111
+ return [`(${lines.length - MAX_ACTIONS} earlier)`, ...lines.slice(-MAX_ACTIONS)];
112
+ }
113
+ /**
114
+ * The last few exchanges, oldest first, cut to their budgets and redacted.
115
+ *
116
+ * A request is a user message with text that the harness did not write:
117
+ * tool results come back as user messages with none, and a compaction
118
+ * summary, a task notification or a goal nudge are marked synthetic. The
119
+ * reply is the agent's final answer — the text after its last tool call —
120
+ * because the narration on the way ("let me look at X") is not what the user
121
+ * answers; when a turn ends on a call, its last words are used instead.
122
+ */
123
+ export function recentExchanges(messages, count = EXCHANGES) {
124
+ const exchanges = [];
125
+ let after = [];
126
+ for (let i = messages.length - 1; i >= 0 && exchanges.length < count; i--) {
127
+ const message = messages[i];
128
+ const text = message.role === "user" && !message.meta?.synthetic ? visibleText(message).trim() : "";
129
+ if (!text) {
130
+ after.unshift(message);
131
+ continue;
132
+ }
133
+ const turn = after;
134
+ after = [];
135
+ const latest = exchanges.length === 0;
136
+ const reply = finalAnswer(turn);
137
+ if (!reply)
138
+ continue;
139
+ exchanges.unshift({
140
+ // Redacted whole, then cut: a key cut in half no longer looks like one.
141
+ ask: cut(redactSecrets(text).text, latest ? LATEST_ASK_TOKENS : EARLIER_ASK_TOKENS, latest ? "ends" : "head"),
142
+ reply: cut(redactSecrets(reply).text, latest ? LATEST_REPLY_TOKENS : EARLIER_REPLY_TOKENS, "tail"),
143
+ actions: latest ? collapse(actionsOf(turn)) : [],
144
+ });
145
+ }
146
+ return exchanges;
147
+ }
148
+ function finalAnswer(turn) {
149
+ let final = [];
150
+ let last = "";
151
+ for (const message of turn) {
152
+ if (message.role !== "assistant")
153
+ continue;
154
+ for (const block of message.content) {
155
+ if (block.type === "tool_call")
156
+ final = [];
157
+ else if (block.type === "text" && block.text.trim()) {
158
+ final.push(block.text.trim());
159
+ last = block.text.trim();
160
+ }
161
+ }
162
+ }
163
+ return final.length ? final.join("\n") : last;
164
+ }
165
+ function actionsOf(turn) {
166
+ const failed = new Set();
167
+ for (const message of turn) {
168
+ for (const block of message.content) {
169
+ if (block.type === "tool_result" && block.isError)
170
+ failed.add(block.toolCallId);
171
+ }
172
+ }
173
+ const actions = [];
174
+ for (const message of turn) {
175
+ if (message.role !== "assistant")
176
+ continue;
177
+ for (const block of message.content) {
178
+ if (block.type === "tool_call")
179
+ actions.push(describeCall(block.name, block.input, failed.has(block.id)));
180
+ }
181
+ }
182
+ return actions;
183
+ }
184
+ /** The exchanges as the model reads them. */
185
+ export function suggestContext(messages) {
186
+ const exchanges = recentExchanges(messages);
187
+ if (!exchanges.length)
188
+ return undefined;
189
+ return exchanges
190
+ .map((exchange) => {
191
+ const parts = [`<user>\n${exchange.ask}\n</user>`];
192
+ if (exchange.actions.length)
193
+ parts.push(`<agent_actions>\n${exchange.actions.map((line) => `- ${line}`).join("\n")}\n</agent_actions>`);
194
+ parts.push(`<agent>\n${exchange.reply}\n</agent>`);
195
+ return parts.join("\n");
196
+ })
197
+ .join("\n");
198
+ }
199
+ /** One line fit for the box, or nothing: a long, multi-line or quoted answer is the model not following the brief. */
200
+ export function cleanSuggestion(raw) {
201
+ const text = raw.trim().replace(/^["'“「]+|["'”」]+$/g, "").trim();
202
+ // The model's way of saying there is nothing to offer; an empty reply is
203
+ // the other, but small models will not give one.
204
+ if (!text || /^none\.?$/i.test(text) || text.includes("\n") || text.length > MAX_LENGTH)
205
+ return undefined;
206
+ return text;
207
+ }
208
+ export async function suggestNext(harness, messages, model, signal) {
209
+ const context = suggestContext(messages);
210
+ if (!context)
211
+ return undefined;
212
+ const reply = await harness.generateWith(model, SUGGEST_PROMPT, context, AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT_MS)]), 200);
213
+ return cleanSuggestion(reply);
214
+ }
215
+ //# sourceMappingURL=suggest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.js","sourceRoot":"","sources":["../src/suggest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAGxE;;;;;;;;;;;;GAYG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;6DAW+B,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAChC,wEAAwE;AACxE,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,UAAU,GAAG,MAAM,CAAC;AAS1B,MAAM,IAAI,GAAG,kFAAkF,CAAC;AAEhG,8FAA8F;AAC9F,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,IAAI,IAAI,IAAI;QAAE,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9D,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,4DAA4D;AAC5D,SAAS,GAAG,CAAC,IAAY,EAAE,MAAc,EAAE,IAAqB;IAC9D,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACxB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAE,CAAC;QAChE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACnC,IAAI,IAAI,GAAG,MAAM;YAAE,MAAM;IAC3B,CAAC;IACD,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,MAAc,EAAE,IAA8B;IAC9E,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC;IAC5D,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5D,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,MAAM,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AACjF,CAAC;AAED,kFAAkF;AAClF,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AAEpG,SAAS,YAAY,CAAC,IAAY,EAAE,KAAc,EAAE,MAAe;IACjE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAC;YACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC9C,GAAG,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;gBACrF,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,OAAiB;IACjC,MAAM,GAAG,GAA2C,EAAE,CAAC;IACvD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;;YACxC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrF,IAAI,KAAK,CAAC,MAAM,IAAI,WAAW;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,WAAW,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AACnF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,QAA4B,EAAE,KAAK,GAAG,SAAS;IAC7E,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,IAAI,KAAK,GAAc,EAAE,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1E,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpG,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC;QACnB,KAAK,GAAG,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,SAAS,CAAC,OAAO,CAAC;YAChB,wEAAwE;YACxE,GAAG,EAAE,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;YAC7G,KAAK,EAAE,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,oBAAoB,EAAE,MAAM,CAAC;YAClG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;SACjD,CAAC,CAAC;IACL,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,WAAW,CAAC,IAAwB;IAC3C,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW;YAAE,SAAS;QAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;gBAAE,KAAK,GAAG,EAAE,CAAC;iBACtC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC9B,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED,SAAS,SAAS,CAAC,IAAwB;IACzC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,OAAO;gBAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW;YAAE,SAAS;QAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW;gBAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,cAAc,CAAC,QAA4B;IACzD,MAAM,SAAS,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACxC,OAAO,SAAS;SACb,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,CAAC,WAAW,QAAQ,CAAC,GAAG,WAAW,CAAC,CAAC;QACnD,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACxI,KAAK,CAAC,IAAI,CAAC,YAAY,QAAQ,CAAC,KAAK,YAAY,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjE,yEAAyE;IACzE,iDAAiD;IACjD,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU;QAAE,OAAO,SAAS,CAAC;IAC1G,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAgB,EAChB,QAA4B,EAC5B,KAAa,EACb,MAAmB;IAEnB,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,YAAY,CACtC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,CAChG,CAAC;IACF,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC"}
@@ -1,4 +1,27 @@
1
1
  import type { ServerResponse } from "node:http";
2
+ /**
3
+ * Programs a new shell may be opened running, by name. A fixed list rather
4
+ * than a command from the request: the page asks for "claude", and what that
5
+ * means is decided here.
6
+ *
7
+ * `claude` is the hand-off to Claude Code. It is the user's own install,
8
+ * signed in to their own account through Claude Code's own login — OHarness
9
+ * starts it and does nothing else, which is the one way of using a Claude
10
+ * plan from another app that Anthropic's terms leave plainly open. It is
11
+ * typed into the shell rather than spawned in its place, so PATH is the
12
+ * login shell's and the shell is still there when Claude Code exits.
13
+ */
14
+ export declare const LAUNCHERS: {
15
+ readonly claude: (platform: NodeJS.Platform, resume?: string) => string;
16
+ };
17
+ export type Launcher = keyof typeof LAUNCHERS;
18
+ export declare function isLauncher(value: unknown): value is Launcher;
19
+ /**
20
+ * An agent session id fit to put on a command line: letters, digits and
21
+ * dashes, the shape Claude Code's ids have. Anything else is refused rather
22
+ * than escaped — there is no legitimate id that needs escaping.
23
+ */
24
+ export declare function isAgentSessionId(value: unknown): value is string;
2
25
  export declare class TerminalHub {
3
26
  private readonly terminals;
4
27
  private seq;
@@ -8,11 +31,14 @@ export declare class TerminalHub {
8
31
  name: string;
9
32
  exited: boolean;
10
33
  }>;
11
- /** A new shell in `cwd`; `replace` names one it takes the place of. */
34
+ /**
35
+ * A new shell in `cwd`; `replace` names one it takes the place of, and
36
+ * `run` a launcher typed into it as soon as it starts.
37
+ */
12
38
  open(cwd: string, size: {
13
39
  cols?: unknown;
14
40
  rows?: unknown;
15
- }, replace?: string): Promise<{
41
+ }, replace?: string, run?: Launcher, resume?: string): Promise<{
16
42
  id: string;
17
43
  name: string;
18
44
  } | {
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
- /** A new shell in `cwd`; `replace` names one it takes the place of. */
88
- async open(cwd, size, replace) {
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. */
@@ -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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;AAE3C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,WAAW,GAA2B,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;AAE/G,0EAA0E;AAC1E,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC1C,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IAC7F,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;QACnE,4EAA4E;QAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/D,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC;QAChG,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC3G,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC;QAC3E,8DAA8D;QAC9D,IAAI,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,CAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;QAC9G,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC;IAC7I,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC;IAC3F,OAAO,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;AACpD,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,IAAI,EAAE,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,gBAAgB,EAAE,CAAC;IACzE,IAAI,IAAI,CAAC,IAAI,GAAG,eAAe;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,eAAe,GAAG,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;IACnI,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;IAC1F,MAAM,EAAE,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7E,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;IACtC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAU;IAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAsB;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACvD,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,MAAM;gBAAE,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACpF,CAAC;QAAC,MAAM,CAAC;YACP,iEAAiE;QACnE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -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
+ }