smolcoder-plus 1.0.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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/dist/agent.js +748 -0
  4. package/dist/attachments.js +158 -0
  5. package/dist/config.js +87 -0
  6. package/dist/context.js +498 -0
  7. package/dist/detect.js +474 -0
  8. package/dist/events.js +24 -0
  9. package/dist/history.js +9 -0
  10. package/dist/hosts.js +107 -0
  11. package/dist/index.js +391 -0
  12. package/dist/logo.js +48 -0
  13. package/dist/netscan.js +159 -0
  14. package/dist/network.js +193 -0
  15. package/dist/plan.js +102 -0
  16. package/dist/prompt.js +84 -0
  17. package/dist/providers/lmstudio.js +347 -0
  18. package/dist/providers/ollama.js +269 -0
  19. package/dist/providers/scheduler.js +57 -0
  20. package/dist/providers/transport.js +86 -0
  21. package/dist/providers/types.js +62 -0
  22. package/dist/sandbox.js +207 -0
  23. package/dist/session.js +639 -0
  24. package/dist/tools/check.js +193 -0
  25. package/dist/tools/fs-tools.js +431 -0
  26. package/dist/tools/index.js +260 -0
  27. package/dist/tools/search-worker.js +34 -0
  28. package/dist/tools/shell.js +186 -0
  29. package/dist/tools/tasks.js +147 -0
  30. package/dist/tools/web-search.js +155 -0
  31. package/dist/tui/editor.js +134 -0
  32. package/dist/tui/keys.js +145 -0
  33. package/dist/tui/tui.js +723 -0
  34. package/dist/ui.js +226 -0
  35. package/dist/util.js +91 -0
  36. package/dist/verification.js +71 -0
  37. package/dist/web/channel.js +260 -0
  38. package/dist/web/client.js +1010 -0
  39. package/dist/web/hub.js +952 -0
  40. package/dist/web/page.js +87 -0
  41. package/dist/web/store.js +199 -0
  42. package/dist/web/styles.js +333 -0
  43. package/dist/web/terminal.js +190 -0
  44. package/package.json +49 -0
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ // The web page: one self-contained HTML document (styles + script inlined,
3
+ // no dependencies) served by the hub. Structure only — the styling lives in
4
+ // styles.ts and the behaviour in client.ts.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PAGE_HTML = void 0;
7
+ const logo_1 = require("../logo");
8
+ const client_1 = require("./client");
9
+ const styles_1 = require("./styles");
10
+ const FAVICON = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%230b0d0e'/%3E%3Ctext x='16' y='23' font-size='19' font-family='monospace' font-weight='700' text-anchor='middle' fill='%2335bfd4'%3ES%3C/text%3E%3C/svg%3E";
11
+ const ICON_BROWSER = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>';
12
+ const ICON_TERMINAL = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>';
13
+ const ICON_ATTACH = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>';
14
+ exports.PAGE_HTML = `<!doctype html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="utf-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1">
19
+ <title>smol</title>
20
+ <link rel="icon" href="${FAVICON}">
21
+ <style>${styles_1.STYLES}</style>
22
+ </head>
23
+ <body>
24
+ <aside id="side">
25
+ <div class="sidehdr">
26
+ <div class="brand" role="img" aria-label="smolcoder">${logo_1.LOGO_TEXT}</div>
27
+ <span class="grow"></span>
28
+ <button class="iconbtn" id="sidecollapse" title="hide sidebar (ctrl+b)">«</button>
29
+ </div>
30
+ <button id="openfolder" class="ghost">+ Open folder…</button>
31
+ <div id="wslist"></div>
32
+ <div class="sidefoot"><span id="ver"></span><span class="grow"></span><button class="iconbtn" id="keys" title="Keyboard shortcuts" aria-label="Keyboard shortcuts">?</button></div>
33
+ </aside>
34
+ <div id="main">
35
+ <div id="top">
36
+ <button class="iconbtn" id="sidetoggle" title="show sidebar (ctrl+b)" hidden>☰</button>
37
+ <div id="crumb"></div>
38
+ <button class="iconbtn" id="btnbrowser" title="browser panel: preview a dev server next to the chat">${ICON_BROWSER}</button>
39
+ <button class="iconbtn" id="btnterm" title="terminal panel (ctrl+\`)">${ICON_TERMINAL}</button>
40
+ </div>
41
+ <div id="logwrap" tabindex="0" role="region" aria-label="Session messages">
42
+ <div id="welcome" hidden>
43
+ <div id="logo" role="img" aria-label="smolcoder">${logo_1.LOGO_TEXT} <span class="coder">coder — web</span></div>
44
+ <h1>What are we building?</h1>
45
+ <div class="row"><button class="primary" id="welcomeopen">Open a folder</button></div>
46
+ <div id="recent"></div>
47
+ </div>
48
+ <div id="logs"></div>
49
+ <div id="busywrap"><div id="busy"><span class="spin">⠋</span> <span id="busylabel">thinking…</span> <span id="busysecs"></span></div></div>
50
+ </div>
51
+ <div id="bottom" hidden><div class="inner">
52
+ <button id="jumpbottom" class="ghost" type="button" aria-label="Scroll to bottom" title="Scroll to bottom" hidden>↓ Latest messages</button>
53
+ <div id="menu"></div>
54
+ <div id="inputbox">
55
+ <div id="attachrow" hidden></div>
56
+ <div class="inputrow">
57
+ <textarea id="input" aria-label="Message" rows="2" placeholder="Describe a change…" title="Enter to send · Shift+Enter for a new line · / for commands · paste or drop files to attach"></textarea>
58
+ <button id="attachbtn" class="iconbtn" title="Attach a file or image — or paste / drop one" aria-label="Attach a file">${ICON_ATTACH}</button>
59
+ <input type="file" id="filepick" multiple hidden>
60
+ <button id="actionbtn" title="send (enter)">send</button>
61
+ </div>
62
+ <div id="status">connecting…</div>
63
+ </div>
64
+ <span id="ws" hidden></span>
65
+ </div></div>
66
+ </div>
67
+ <div id="panel" hidden>
68
+ <div id="panelgrip" title="drag to resize"></div>
69
+ <div id="paneltabs"></div>
70
+ <div id="panelviews"></div>
71
+ </div>
72
+ <div id="modal" hidden>
73
+ <div class="dlg">
74
+ <div class="dlghdr">Open folder<span class="grow"></span><button class="iconbtn" id="fsclose" title="close">×</button></div>
75
+ <div class="pathrow"><input id="fspath" spellcheck="false" placeholder="type or paste a path"><button class="ghost" id="fsgo">go</button></div>
76
+ <div id="fsroots"></div>
77
+ <div id="fslist"></div>
78
+ <div class="dlgfoot">
79
+ <button class="primary" id="fsopen">Open this folder</button>
80
+ <label><input type="checkbox" id="fsstart" checked> start a session</label>
81
+ <span class="grow"></span>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <script>${client_1.CLIENT_JS}</script>
86
+ </body>
87
+ </html>`;
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ // On-disk memory for the web hub: saved sessions (so the sidebar survives a
3
+ // restart and any session can be resumed) and the list of workspaces the
4
+ // user has opened. Plain JSON files under ~/.smolcoder/, one pair per session:
5
+ // a small .meta.json that is read at startup, and the heavy transcript body
6
+ // that is only loaded when a session is resumed.
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.WorkspaceStore = exports.SessionStore = void 0;
42
+ exports.workspaceKey = workspaceKey;
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ function writeAtomic(file, data) {
46
+ const tmp = `${file}.${process.pid}.tmp`;
47
+ fs.writeFileSync(tmp, data);
48
+ fs.renameSync(tmp, file);
49
+ }
50
+ async function writeAtomicAsync(file, data) {
51
+ const tmp = `${file}.${process.pid}.tmp`;
52
+ await fs.promises.writeFile(tmp, data);
53
+ await fs.promises.rename(tmp, file);
54
+ }
55
+ class SessionStore {
56
+ dir;
57
+ constructor(dataDir) {
58
+ this.dir = path.join(dataDir, "sessions");
59
+ fs.mkdirSync(this.dir, { recursive: true });
60
+ }
61
+ metaPath(id) {
62
+ return path.join(this.dir, `${id}.meta.json`);
63
+ }
64
+ bodyPath(id) {
65
+ return path.join(this.dir, `${id}.json`);
66
+ }
67
+ listMetas() {
68
+ const out = [];
69
+ let names = [];
70
+ try {
71
+ names = fs.readdirSync(this.dir);
72
+ }
73
+ catch {
74
+ return out;
75
+ }
76
+ for (const n of names) {
77
+ if (!n.endsWith(".meta.json"))
78
+ continue;
79
+ try {
80
+ const m = JSON.parse(fs.readFileSync(path.join(this.dir, n), "utf8"));
81
+ if (m && typeof m.id === "string" && typeof m.workspace === "string") {
82
+ out.push({
83
+ id: m.id,
84
+ workspace: m.workspace,
85
+ title: String(m.title ?? ""),
86
+ createdAt: Number(m.createdAt) || 0,
87
+ updatedAt: Number(m.updatedAt) || 0,
88
+ model: m.model,
89
+ backend: m.backend,
90
+ });
91
+ }
92
+ }
93
+ catch {
94
+ /* skip a broken file */
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+ saveMeta(meta) {
100
+ writeAtomic(this.metaPath(meta.id), JSON.stringify(meta));
101
+ }
102
+ saveBody(id, body) {
103
+ return writeAtomicAsync(this.bodyPath(id), JSON.stringify(body));
104
+ }
105
+ saveBodySync(id, body) {
106
+ writeAtomic(this.bodyPath(id), JSON.stringify(body));
107
+ }
108
+ loadBody(id) {
109
+ try {
110
+ const b = JSON.parse(fs.readFileSync(this.bodyPath(id), "utf8"));
111
+ if (!b || typeof b !== "object" || !b.snapshot)
112
+ return null;
113
+ return { snapshot: b.snapshot, events: Array.isArray(b.events) ? b.events : [] };
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ delete(id) {
120
+ for (const p of [this.metaPath(id), this.bodyPath(id)]) {
121
+ try {
122
+ fs.unlinkSync(p);
123
+ }
124
+ catch {
125
+ /* already gone */
126
+ }
127
+ }
128
+ }
129
+ }
130
+ exports.SessionStore = SessionStore;
131
+ /** Normalized form for comparisons: resolved, no trailing separator, and
132
+ * case-folded on Windows where paths are case-insensitive. */
133
+ function workspaceKey(p) {
134
+ let r = path.resolve(p);
135
+ if (r.length > 1 && /[\\/]$/.test(r) && path.dirname(r) !== r)
136
+ r = r.slice(0, -1);
137
+ return process.platform === "win32" ? r.toLowerCase() : r;
138
+ }
139
+ class WorkspaceStore {
140
+ file;
141
+ entries = [];
142
+ constructor(dataDir) {
143
+ fs.mkdirSync(dataDir, { recursive: true });
144
+ this.file = path.join(dataDir, "workspaces.json");
145
+ try {
146
+ const raw = JSON.parse(fs.readFileSync(this.file, "utf8"));
147
+ if (Array.isArray(raw)) {
148
+ this.entries = raw
149
+ .filter((e) => e && typeof e.path === "string")
150
+ .map((e) => ({ path: e.path, addedAt: Number(e.addedAt) || 0, lastOpened: Number(e.lastOpened) || 0 }));
151
+ }
152
+ }
153
+ catch {
154
+ this.entries = [];
155
+ }
156
+ }
157
+ list() {
158
+ return this.entries.slice();
159
+ }
160
+ has(p) {
161
+ const key = workspaceKey(p);
162
+ return this.entries.some((e) => workspaceKey(e.path) === key);
163
+ }
164
+ add(p) {
165
+ const key = workspaceKey(p);
166
+ let e = this.entries.find((x) => workspaceKey(x.path) === key);
167
+ if (!e) {
168
+ e = { path: path.resolve(p), addedAt: Date.now(), lastOpened: Date.now() };
169
+ this.entries.push(e);
170
+ }
171
+ else {
172
+ e.lastOpened = Date.now();
173
+ }
174
+ this.save();
175
+ return e;
176
+ }
177
+ touch(p) {
178
+ const key = workspaceKey(p);
179
+ const e = this.entries.find((x) => workspaceKey(x.path) === key);
180
+ if (e) {
181
+ e.lastOpened = Date.now();
182
+ this.save();
183
+ }
184
+ }
185
+ remove(p) {
186
+ const key = workspaceKey(p);
187
+ this.entries = this.entries.filter((x) => workspaceKey(x.path) !== key);
188
+ this.save();
189
+ }
190
+ save() {
191
+ try {
192
+ writeAtomic(this.file, JSON.stringify(this.entries, null, 2));
193
+ }
194
+ catch {
195
+ /* non-fatal */
196
+ }
197
+ }
198
+ }
199
+ exports.WorkspaceStore = WorkspaceStore;
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ // Compact workspace chrome, with monospace reserved for code and commands.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.STYLES = void 0;
5
+ const logo_1 = require("../logo");
6
+ // The logo as a CSS string: "\A " is a newline whose trailing space is eaten
7
+ // by the escape, so each row starts where it should.
8
+ const LOGO_CSS = logo_1.LOGO_ROWS.map((r) => r.trimEnd()).join("\\A ");
9
+ exports.STYLES = String.raw `
10
+ :root {
11
+ --bg: #111416; --fg: #e1e5e7; --dim: #a0a8ae; --gray: #79838b;
12
+ --accent: #35bfd4; --yellow: #e0af68; --red: #f7768e; --green: #9ece6a;
13
+ --magenta: #bb9af7; --box: #14181a; --sel: #1a7f94; --line: #232a2f; --side: #0e1113;
14
+ }
15
+ * { box-sizing: border-box; }
16
+ /* Class rules below set display; the hidden attribute must still win. */
17
+ [hidden] { display: none !important; }
18
+ /* Scrollbars in the page's own colors (native ones are light and chunky). */
19
+ html { color-scheme: dark; scrollbar-color: #2c343a transparent; }
20
+ * { scrollbar-width: thin; }
21
+ ::-webkit-scrollbar { width: 9px; height: 9px; }
22
+ ::-webkit-scrollbar-track { background: transparent; }
23
+ ::-webkit-scrollbar-thumb { background: #2c343a; border-radius: 5px; border: 2px solid transparent; background-clip: padding-box; }
24
+ ::-webkit-scrollbar-thumb:hover { background: #3d474f; background-clip: padding-box; }
25
+ ::-webkit-scrollbar-corner { background: transparent; }
26
+ html, body { height: 100%; }
27
+ body {
28
+ margin: 0; background: var(--bg); color: var(--fg); display: flex; overflow: hidden;
29
+ font: 14px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
30
+ }
31
+ button { font: inherit; }
32
+ a { color: var(--accent); }
33
+ .grow { flex: 1; }
34
+ .iconbtn { background: transparent; border: 1px solid transparent; color: var(--dim); cursor: pointer; border-radius: 4px; padding: 2px 6px; line-height: 1.2; display: inline-flex; align-items: center; gap: 4px; }
35
+ .iconbtn:hover { color: var(--fg); border-color: var(--line); background: #1a2023; }
36
+ .iconbtn.on { color: var(--accent); border-color: var(--line); background: #13202a; }
37
+ .iconbtn svg { display: block; }
38
+ .primary { background: #173f47; color: #eef3f5; border: 1px solid #1f5d68; padding: 5px 12px; border-radius: 4px; cursor: pointer; }
39
+ .primary:hover { border-color: var(--accent); }
40
+ .ghost { background: #151b1e; color: var(--fg); border: 1px solid var(--line); border-radius: 4px; padding: 5px 10px; cursor: pointer; }
41
+ .ghost:hover { border-color: var(--accent); }
42
+ .hint { color: var(--dim); font-size: 12px; }
43
+
44
+ /* ---- left sidebar ---- */
45
+ #side { width: 272px; flex: none; background: var(--side); border-right: 1px solid var(--line); display: flex; flex-direction: column; min-height: 0; }
46
+ #side.collapsed { display: none; }
47
+ .sidehdr { display: flex; align-items: center; gap: 8px; padding: 12px 10px 8px 14px; }
48
+ /* The SMOL block logo, sized so all 39 columns fit beside the collapse button. */
49
+ .brand { color: var(--accent); white-space: pre; font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 7px; line-height: 1.15; }
50
+ #openfolder { margin: 2px 10px 10px; text-align: left; }
51
+ #wslist { flex: 1; overflow-y: auto; padding: 0 6px 10px; }
52
+ .sidehint { color: var(--dim); font-size: 12px; padding: 8px 10px; }
53
+ .ws { margin: 2px 0 10px; }
54
+ .wshdr { display: flex; align-items: center; gap: 6px; padding: 4px 4px 4px 8px; border-radius: 4px; color: var(--dim); font-size: 12.5px; }
55
+ .wshdr:hover { background: #141a1d; }
56
+ .wsname { color: var(--fg); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: none; max-width: 55%; }
57
+ .wspath { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: var(--gray); }
58
+ .wshdr .iconbtn { visibility: hidden; padding: 0 5px; }
59
+ .wshdr:hover .iconbtn { visibility: visible; }
60
+ .sess { display: flex; align-items: center; gap: 8px; padding: 4px 4px 4px 12px; border-radius: 4px; cursor: pointer; color: var(--dim); font-size: 13px; }
61
+ .sess:hover { background: #141a1d; color: var(--fg); }
62
+ .sess.active { background: #17232a; color: #eef3f5; }
63
+ .sess .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--gray); flex: none; }
64
+ .sess.busy .dot, .sess.starting .dot { background: var(--accent); animation: pulse 1s infinite; }
65
+ .sess.waiting .dot { background: var(--yellow); box-shadow: 0 0 6px var(--yellow); }
66
+ .sess.error .dot { background: var(--red); }
67
+ .sess.stored .dot { background: transparent; border: 1px solid var(--gray); }
68
+ .sess.unread .stitle::after { content: " •"; color: var(--accent); }
69
+ .stitle { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
70
+ .stitle.untitled { font-style: italic; }
71
+ .stime { font-size: 11px; color: var(--gray); flex: none; }
72
+ .sess .iconbtn { visibility: hidden; padding: 0 5px; }
73
+ .sess:hover .iconbtn { visibility: visible; }
74
+ .sidefoot { padding: 8px 14px; font-size: 11.5px; color: var(--gray); border-top: 1px solid var(--line); display: flex; gap: 10px; }
75
+
76
+ /* ---- main column ---- */
77
+ #main { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
78
+ #top { display: flex; align-items: center; gap: 6px; padding: 7px 10px; border-bottom: 1px solid var(--line); font-size: 12.5px; color: var(--dim); min-height: 40px; }
79
+ #crumb { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-left: 4px; }
80
+ #crumb .ws { color: var(--fg); font-weight: 700; }
81
+ #crumb .sep { margin: 0 6px; color: var(--gray); }
82
+ #crumb .model { color: var(--gray); margin-left: 10px; }
83
+ #logwrap { flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0; overflow-anchor: none; }
84
+ #logs, #busywrap { max-width: 920px; margin: 0 auto; padding: 16px 16px 0; }
85
+ #logs { overflow-wrap: anywhere; }
86
+ #wslist, #fslist, .tabbody.term .out { overflow-x: hidden; }
87
+ #busywrap { padding-bottom: 20px; }
88
+ #welcome { max-width: 920px; margin: 0 auto; padding: 48px 16px; }
89
+ #welcome p { color: var(--dim); max-width: 60ch; }
90
+ #welcome .row { display: flex; gap: 10px; align-items: center; margin: 14px 0 22px; }
91
+ #recent { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }
92
+ .wsbtn { background: transparent; border: 1px solid transparent; color: var(--fg); padding: 4px 8px; border-radius: 4px; cursor: pointer; text-align: left; }
93
+ .wsbtn:hover { border-color: var(--line); background: #141a1d; }
94
+ .wsbtn .dim { color: var(--gray); font-size: 12px; }
95
+ #logo { color: var(--accent); white-space: pre; font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 11px; line-height: 1.15; margin: 8px 0 12px; }
96
+ #logo .coder { color: var(--dim); }
97
+
98
+ .user { border-left: 3px solid var(--accent); background: var(--box); padding: 8px 12px; margin: 18px 0 10px; font-weight: 600; white-space: pre-wrap; }
99
+ .thought { color: var(--gray); margin-top: 4px; }
100
+ .thought summary { display: flex; align-items: center; gap: 8px; padding: 4px 0; cursor: pointer; list-style: none; }
101
+ .thought summary::-webkit-details-marker { display: none; }
102
+ .thought summary::before { content: "›"; flex: none; }
103
+ .thought[open] summary::before { content: "⌄"; }
104
+ .thought summary:hover { color: var(--fg); }
105
+ .thought-preview { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
106
+ .thought-expand, .thought-collapse { flex: none; color: var(--dim); }
107
+ .thought-collapse, .thought[open] .thought-expand { display: none; }
108
+ .thought[open] .thought-collapse { display: inline; }
109
+ .thought-body { color: var(--dim); white-space: pre-wrap; overflow-wrap: anywhere; border-left: 2px solid var(--line); padding: 8px 12px; margin: 4px 0 12px 4px; line-height: 1.7; }
110
+ .md { white-space: normal; }
111
+ .md p { margin: 6px 0; }
112
+ .md h1, .md h2, .md h3, .md h4, .md h5, .md h6 { margin: 14px 0 6px; line-height: 1.3; color: #eef3f5; }
113
+ .md h1 { font-size: 1.3em; } .md h2 { font-size: 1.17em; } .md h3 { font-size: 1.06em; }
114
+ .md h4, .md h5, .md h6 { font-size: 1em; }
115
+ .md ul, .md ol { margin: 6px 0; padding-left: 22px; }
116
+ .md li { margin: 2px 0; }
117
+ .md strong { color: #eef3f5; }
118
+ .md code { background: #1b2124; padding: 1px 5px; border-radius: 3px; color: var(--yellow); }
119
+ .md pre { background: #11161a; border: 1px solid var(--line); border-radius: 4px; padding: 10px 12px; overflow-x: auto; margin: 8px 0; }
120
+ .md pre code { background: none; padding: 0; color: var(--fg); }
121
+ .md table { border-collapse: collapse; margin: 8px 0; display: block; overflow-x: auto; max-width: 100%; }
122
+ .md th, .md td { border: 1px solid var(--line); padding: 4px 10px; text-align: left; }
123
+ .md th { background: #171d20; color: #eef3f5; }
124
+ .md blockquote { border-left: 3px solid #2c343a; margin: 8px 0; padding-left: 12px; color: var(--dim); }
125
+ .md hr { border: 0; border-top: 1px solid var(--line); margin: 12px 0; }
126
+ .tool { color: var(--dim); margin-top: 4px; }
127
+ .tool .name { color: var(--accent); font-weight: 600; }
128
+ .result { color: var(--dim); padding-left: 16px; }
129
+ .result.err { color: var(--red); }
130
+ .plan { background: var(--box); border-left: 3px solid var(--accent); padding: 8px 12px; margin: 10px 0; }
131
+ .plan .hdr { font-weight: 700; } .plan .hdr small { color: var(--dim); font-weight: 400; }
132
+ .plan .done { color: var(--gray); text-decoration: line-through; }
133
+ .plan .cur { color: var(--accent); font-weight: 600; }
134
+ .plan .todo { color: var(--dim); }
135
+ .turnend { color: var(--gray); margin: 8px 0 4px; }
136
+ .line-status { color: var(--gray); white-space: pre-wrap; } .line-warn { color: var(--yellow); white-space: pre-wrap; } .line-error { color: var(--red); white-space: pre-wrap; }
137
+ #busy { color: var(--dim); display: none; }
138
+ #busy.on { display: block; }
139
+ #busy .spin { display: inline-block; color: var(--accent); animation: pulse 1s infinite; }
140
+ @keyframes pulse { 50% { opacity: .3; } }
141
+ .ask { background: var(--box); border-left: 3px solid var(--yellow); padding: 10px 12px; margin: 10px 0; }
142
+ .ask .cmd { font-weight: 700; }
143
+ .ask button, .ask .opt { margin: 6px 8px 0 0; background: #1e2428; color: var(--fg); border: 1px solid #2c343a; padding: 4px 12px; cursor: pointer; font: inherit; border-radius: 3px; }
144
+ .ask button:hover, .ask .opt:hover { border-color: var(--accent); }
145
+ .ask .opt.current { border-color: var(--green); }
146
+ .ask .askinput { margin-top: 8px; width: min(420px, 100%); box-sizing: border-box; background: #14181b; color: var(--fg); border: 1px solid #2c343a; padding: 6px 8px; font: inherit; border-radius: 3px; outline: none; }
147
+ .ask .askinput:focus { border-color: var(--accent); }
148
+ .ask .opt .hint { color: var(--dim); font-size: 12px; margin-left: 8px; }
149
+ .ask > .hint { color: var(--dim); font-size: 12px; margin-top: 2px; }
150
+
151
+ #bottom { flex: none; padding: 8px 16px 12px; background: var(--bg); }
152
+ #bottom .inner { max-width: 920px; margin: 0 auto; position: relative; }
153
+ #jumpbottom { position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); z-index: 4; border-radius: 20px; padding: 6px 14px; white-space: nowrap; box-shadow: 0 4px 16px #0006; font-size: 12px; }
154
+ #menu { position: absolute; bottom: 100%; left: 0; right: 0; background: var(--box); border: 1px solid var(--line); display: none; z-index: 5; }
155
+ #menu .item { padding: 4px 10px; cursor: pointer; }
156
+ #menu .item .nm { font-weight: 700; } #menu .item .ds { color: var(--dim); margin-left: 10px; }
157
+ #menu .item.sel { background: var(--sel); color: #f2f7f8; }
158
+ #menu .item.sel .ds { color: #c8dde2; }
159
+ #inputbox { border-left: 3px solid var(--accent); background: var(--box); padding: 8px 12px; }
160
+ .inputrow { display: flex; align-items: flex-end; gap: 10px; }
161
+ #input { flex: 1; background: transparent; border: 0; outline: 0; color: var(--fg); font: inherit; resize: none; }
162
+ #actionbtn { flex: none; background: #1e2428; color: var(--dim); border: 1px solid #2c343a; padding: 3px 14px; cursor: pointer; font: inherit; font-size: 12px; border-radius: 3px; }
163
+ #actionbtn:hover { border-color: var(--accent); color: var(--accent); }
164
+ #actionbtn.stop { color: var(--red); border-color: #3d2d31; }
165
+ #actionbtn.stop:hover { border-color: var(--red); color: var(--red); }
166
+ #status { margin-top: 6px; font-size: 12.5px; color: var(--dim); }
167
+ #status .mode { font-weight: 700; }
168
+ #status .mode.edit { color: var(--accent); } #status .mode.bypass { color: var(--red); } #status .mode.ro { color: var(--magenta); }
169
+ #status .eff { color: var(--yellow); } #status .plan-chip { color: var(--accent); } #status .plan-chip.done { color: var(--green); }
170
+ #hintrow { font-size: 12px; color: var(--gray); margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
171
+
172
+ /* ---- right panel: browser + terminal tabs ---- */
173
+ #panel { flex: none; width: 520px; min-width: 300px; max-width: 80vw; border-left: 1px solid var(--line); display: flex; flex-direction: column; min-height: 0; position: relative; background: var(--side); }
174
+ #panel[hidden] { display: none; }
175
+ #panelgrip { position: absolute; left: -3px; top: 0; bottom: 0; width: 7px; cursor: col-resize; z-index: 6; }
176
+ #panelgrip:hover, body.dragging #panelgrip { background: var(--sel); }
177
+ body.dragging { cursor: col-resize; user-select: none; }
178
+ body.dragging iframe { pointer-events: none; }
179
+ #paneltabs { display: flex; align-items: center; gap: 2px; padding: 5px 6px; border-bottom: 1px solid var(--line); overflow-x: auto; flex: none; min-height: 40px; }
180
+ .ptab { display: flex; align-items: center; gap: 6px; padding: 3px 6px 3px 8px; border-radius: 4px; color: var(--dim); cursor: pointer; font-size: 12px; white-space: nowrap; max-width: 210px; border: 1px solid transparent; }
181
+ .ptab:hover { color: var(--fg); background: #141a1d; }
182
+ .ptab.on { color: #eef3f5; background: #17232a; border-color: var(--line); }
183
+ .ptab .ico { color: var(--accent); font-size: 11px; }
184
+ .ptab .lbl { overflow: hidden; text-overflow: ellipsis; }
185
+ .ptab .x { color: var(--gray); padding: 0 2px; }
186
+ .ptab .x:hover { color: var(--red); }
187
+ #panelviews { flex: 1; min-height: 0; display: flex; }
188
+ .panelview { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; }
189
+ .panelview[hidden] { display: none; }
190
+ .tabbody { flex: 1; min-height: 0; display: flex; flex-direction: column; }
191
+ .tabbody[hidden] { display: none; }
192
+ .tabbody.browser .bar { display: flex; gap: 6px; padding: 6px 8px; border-bottom: 1px solid var(--line); align-items: center; flex: none; }
193
+ .tabbody.browser .bar input { flex: 1; min-width: 0; background: var(--box); border: 1px solid var(--line); color: var(--fg); font: inherit; font-size: 12.5px; padding: 3px 8px; border-radius: 4px; outline: 0; }
194
+ .tabbody.browser .bar input:focus { border-color: var(--accent); }
195
+ .tabbody.browser iframe { flex: 1; border: 0; background: #fff; width: 100%; min-height: 0; }
196
+ .tabbody.browser iframe[hidden] { display: none; }
197
+ .tabbody.browser .empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--dim); text-align: center; padding: 20px; gap: 8px; font-size: 13px; }
198
+ .tabbody.browser .empty[hidden] { display: none; }
199
+ .tabbody.browser .empty .urls { display: flex; flex-direction: column; gap: 4px; }
200
+ .tabbody.term { background: #0a0c0d; }
201
+ .tabbody.term .out { flex: 1; overflow-y: auto; margin: 0; padding: 10px 12px; white-space: pre-wrap; word-break: break-word; font-size: 12.5px; line-height: 1.4; font-family: inherit; }
202
+ .tabbody.term .out .l { min-height: 1.4em; }
203
+ .tabbody.term .trow { display: flex; gap: 8px; padding: 6px 12px 8px; border-top: 1px solid var(--line); align-items: baseline; flex: none; }
204
+ .tabbody.term .prompt { color: var(--accent); white-space: nowrap; max-width: 45%; overflow: hidden; text-overflow: ellipsis; font-size: 12.5px; }
205
+ .tabbody.term input { flex: 1; min-width: 0; background: transparent; border: 0; outline: 0; color: var(--fg); font: inherit; font-size: 12.5px; }
206
+ .ab { font-weight: 700; } .ad { opacity: .6; }
207
+ .a30 { color: #3b4048; } .a31 { color: var(--red); } .a32 { color: var(--green); } .a33 { color: var(--yellow); }
208
+ .a34 { color: #7aa2f7; } .a35 { color: var(--magenta); } .a36 { color: var(--accent); } .a37 { color: #c0caf5; }
209
+ .a90 { color: var(--gray); } .a91 { color: #ff9e9e; } .a92 { color: #b9f27c; } .a93 { color: #ffd580; }
210
+ .a94 { color: #8db4ff; } .a95 { color: #d0b4ff; } .a96 { color: #74e0f0; } .a97 { color: #eef3f5; }
211
+
212
+ /* Narrow windows: the sidebar floats over the chat instead of squeezing it,
213
+ and the panel cannot take more than half the width. */
214
+ @media (max-width: 1000px) {
215
+ #side { position: absolute; left: 0; top: 0; bottom: 0; z-index: 20; box-shadow: 8px 0 30px rgba(0,0,0,.5); }
216
+ #main { padding-left: 0; }
217
+ #sidetoggle { display: inline-flex; }
218
+ #panel { max-width: 55vw; }
219
+ }
220
+
221
+ /* ---- folder picker ---- */
222
+ #modal { position: fixed; inset: 0; background: rgba(0,0,0,.55); display: flex; align-items: center; justify-content: center; z-index: 50; }
223
+ #modal[hidden] { display: none; }
224
+ .dlg { width: min(680px, 92vw); max-height: 82vh; background: var(--box); border: 1px solid var(--line); border-radius: 6px; display: flex; flex-direction: column; box-shadow: 0 12px 40px rgba(0,0,0,.5); }
225
+ .dlghdr { display: flex; align-items: center; padding: 10px 14px; border-bottom: 1px solid var(--line); font-weight: 700; gap: 8px; }
226
+ .pathrow { display: flex; gap: 6px; padding: 10px 14px 6px; }
227
+ .pathrow input { flex: 1; background: var(--bg); border: 1px solid var(--line); color: var(--fg); font: inherit; font-size: 13px; padding: 4px 8px; border-radius: 4px; outline: 0; }
228
+ .pathrow input:focus { border-color: var(--accent); }
229
+ #fsroots { padding: 2px 14px 8px; display: flex; gap: 6px; flex-wrap: wrap; }
230
+ .chip { background: #1a2023; border: 1px solid var(--line); color: var(--dim); border-radius: 12px; padding: 1px 10px; font-size: 12px; cursor: pointer; }
231
+ .chip:hover { color: var(--fg); border-color: var(--accent); }
232
+ #fslist { flex: 1; overflow-y: auto; padding: 0 8px 8px; min-height: 240px; }
233
+ .fsitem { padding: 4px 8px; border-radius: 4px; cursor: pointer; display: flex; gap: 10px; align-items: baseline; }
234
+ .fsitem:hover { background: #1a2023; }
235
+ .fsitem .proj { color: var(--accent); font-size: 11px; }
236
+ .fsitem.up { color: var(--dim); }
237
+ .dlgfoot { display: flex; align-items: center; gap: 14px; padding: 10px 14px; border-top: 1px solid var(--line); font-size: 12.5px; color: var(--dim); }
238
+ .dlgfoot label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
239
+
240
+ /* The working surface: quieter navigation and a single compact control row. */
241
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
242
+ #side { width: 248px; }
243
+ .sidehdr { padding: 22px 18px 18px; }
244
+
245
+ #openfolder { margin: 0 14px 20px; padding: 8px 10px; background: transparent; }
246
+ .wshdr { padding: 8px; }
247
+ .wsname { max-width: 78%; font-weight: 600; }
248
+ .sess { padding: 8px 8px 8px 12px; border-radius: 6px; }
249
+ .sess.active { background: #20292d; }
250
+ .stitle.untitled { font-style: normal; }
251
+ .stime { display: none; }
252
+ .sess:hover .stime { display: inline; }
253
+ .sidefoot { align-items: center; padding: 12px 18px; }
254
+ #top { min-height: 60px; padding: 12px 20px; }
255
+ #crumb .ws { font-weight: 500; color: var(--dim); }
256
+ #crumb .title { color: var(--fg); font-weight: 600; }
257
+ #logs, #busywrap { max-width: 860px; padding-left: 32px; padding-right: 32px; }
258
+ #welcome { max-width: 660px; padding-top: clamp(48px, 16vh, 180px); }
259
+ #welcome h1 { font-size: clamp(26px, 3vw, 38px); letter-spacing: -1.2px; font-weight: 500; margin: 16px 0 24px; }
260
+ .primary { padding: 9px 18px; border-radius: 7px; }
261
+ .recent-label { font-size: 12px; margin-top: 30px; margin-bottom: 6px; color: var(--gray); }
262
+ .wsbtn { width: 100%; padding: 9px 0; }
263
+ .wsbtn .dim { float: right; }
264
+ /* A fresh session opens on the logo until the first message arrives. */
265
+ .log:empty::before { content: "${LOGO_CSS}"; display: block; margin-top: 14vh; color: var(--accent); white-space: pre; font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 11px; line-height: 1.15; }
266
+ .log:empty::after { content: "What are we building?"; display: block; margin-top: 18px; color: var(--dim); font-size: 26px; letter-spacing: -.7px; }
267
+ .user { border: 1px solid var(--line); border-radius: 10px; background: #1b2125; padding: 14px 18px; margin: 24px 0; font-weight: 400; }
268
+ .md { line-height: 1.8; }
269
+ .md p { margin: 12px 0; }
270
+ .md code, .md pre, .tool, .tabbody.term, .plan { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 12.5px; }
271
+ .tool { margin: 3px 0; border-radius: 5px; }
272
+ .tool summary { cursor: pointer; display: flex; align-items: baseline; gap: 10px; padding: 4px 0; list-style: none; }
273
+ .tool summary::before { content: "›"; color: var(--gray); }
274
+ .tool[open] summary::before { content: "⌄"; }
275
+ .tool .name { color: var(--dim); font-weight: 400; white-space: nowrap; }
276
+ .tool .tool-args { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; color: var(--gray); }
277
+ .tool.finished .name { color: #b9c6c7; }
278
+ .tool.failed .name, .tool.failed summary::before { color: var(--red); }
279
+ .result { margin: 4px 0 12px 16px; border: 1px solid var(--line); border-radius: 6px; background: #0e1214; padding: 12px; max-height: 280px; overflow: auto; white-space: pre-wrap; }
280
+ .plan { border: 1px solid var(--line); border-radius: 8px; padding: 12px 16px; background: transparent; line-height: 1.9; }
281
+ .thought, .turnend { font-size: 12px; }
282
+ .turnend { margin-top: 20px; margin-bottom: 20px; }
283
+ #bottom { padding: 16px 32px 24px; }
284
+ #bottom .inner { max-width: 796px; }
285
+ #inputbox { border: 1px solid #354149; border-radius: 12px; padding: 14px 16px 10px; background: #1b2125; box-shadow: 0 8px 30px #0002; }
286
+ #inputbox:focus-within { border-color: #55737b; }
287
+ #input { min-height: 50px; line-height: 1.6; }
288
+ #input::placeholder { color: var(--gray); }
289
+ #actionbtn { border-radius: 6px; padding: 5px 12px; color: var(--fg); }
290
+ #status { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-size: 11px; margin-top: 12px; }
291
+ .statusbtn { background: transparent; color: var(--dim); border: 0; border-radius: 4px; cursor: pointer; padding: 3px 4px; font: inherit; }
292
+ .statusbtn:hover { background: #293138; color: var(--fg); }
293
+ .modelpick { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
294
+ #status .mode { font-weight: 500; }
295
+ #status .eff { color: var(--dim); }
296
+ .context-chip { display: flex; align-items: center; gap: 6px; }
297
+ .context-chip.pressure { color: var(--yellow); }
298
+ meter { width: 35px; height: 8px; appearance: none; border: none; background: #364047; border-radius: 9px; overflow: hidden; }
299
+ meter::-webkit-meter-bar { background: #364047; border: none; }
300
+ meter::-webkit-meter-optimum-value { background: #7dabad; }
301
+ meter::-moz-meter-bar { background: #7dabad; }
302
+ .task-chip, .plan-chip { white-space: nowrap; }
303
+ #busywrap { padding-top: 8px; }
304
+ #busy { font-size: 12px; }
305
+ #panel { background: #101416; }
306
+ .shortcut-list { line-height: 2; padding: 16px 24px; }
307
+
308
+ /* Attachments: chips in the composer; thumbnails and file links in a sent message. */
309
+ #attachrow { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
310
+ .attach { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; border: 1px solid var(--line); border-radius: 8px; padding: 4px 6px 4px 8px; background: #141a1d; font-size: 12px; color: var(--fg); }
311
+ .attach.uploading { opacity: .7; }
312
+ .attach.warn { border-color: var(--yellow); }
313
+ .attach-thumb { width: 28px; height: 28px; object-fit: cover; border-radius: 4px; }
314
+ .attach-name { max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
315
+ .attach .note { color: var(--yellow); }
316
+ .attach-x { background: transparent; border: 0; color: var(--dim); cursor: pointer; font: inherit; font-size: 15px; padding: 0 2px; line-height: 1; }
317
+ .attach-x:hover { color: var(--red); }
318
+ #attachbtn { flex: none; padding: 5px 7px; }
319
+ #main.dragging #inputbox { border-color: var(--accent); box-shadow: 0 0 0 3px #35bfd433; }
320
+ .user .files { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
321
+ .user .thumb { display: block; max-width: min(320px, 100%); max-height: 240px; border-radius: 8px; border: 1px solid var(--line); }
322
+ .user .filechip { display: inline-block; border: 1px solid var(--line); border-radius: 8px; padding: 4px 10px; font-size: 12px; color: var(--fg); text-decoration: none; background: #141a1d; }
323
+ .user .filechip:hover { border-color: var(--accent); }
324
+ @media (max-width: 600px) {
325
+ #top { min-height: 48px; padding: 8px 12px; }
326
+ #logs, #busywrap { padding-left: 16px; padding-right: 16px; }
327
+ #bottom { padding: 12px; }
328
+ #welcome { padding: 48px 24px; }
329
+ #panel { position: absolute; inset: 48px 0 0; width: 100% !important; max-width: 100%; z-index: 15; }
330
+ .modelpick { max-width: 150px; }
331
+ .wshdr .iconbtn, .sess .iconbtn { visibility: visible; }
332
+ }
333
+ `;