wisp-agents 0.8.0 → 0.9.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/CHANGELOG.md +20 -1
- package/README.md +28 -12
- package/bin/wisp.js +1 -1
- package/package.json +2 -2
- package/server/adapters/claude.js +3 -1
- package/server/adapters/grok.js +251 -0
- package/server/adapters/index.js +2 -1
- package/server/adapters/opencode.js +3 -1
- package/server/index.js +169 -18
- package/server/machines.js +672 -0
- package/server/mcp-ask.js +1 -1
- package/server/mcp-wisp.js +2 -2
- package/server/openapi.js +17 -2
- package/server/remote.js +8 -4
- package/server/runner.js +2 -0
- package/server/store.js +11 -6
- package/server/subagents.js +2 -1
- package/skills/wisp/SKILL.md +2 -2
- package/web/css/app.css +166 -39
- package/web/index.html +5 -0
- package/web/js/app.js +603 -80
- package/web/logos/grok.svg +1 -0
package/server/mcp-wisp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// `wisp mcp`: a stdio MCP server that lets any MCP-capable agent or editor (Claude Code, Codex,
|
|
2
|
-
// opencode, Cursor, Antigravity, Claude Desktop, Zed, …) hand tasks to wisp and read the results back.
|
|
2
|
+
// opencode, Cursor, Antigravity, Grok, Claude Desktop, Zed, …) hand tasks to wisp and read the results back.
|
|
3
3
|
// It talks to the running wisp over HTTP (starting a headless one if needed) via the client SDK.
|
|
4
4
|
//
|
|
5
5
|
// claude mcp add wisp -- npx -y wisp-agents mcp
|
|
@@ -9,7 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import { WispClient } from './client.js';
|
|
10
10
|
|
|
11
11
|
const VERSION = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
12
|
-
const AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'antigravity'];
|
|
12
|
+
const AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'antigravity', 'grok'];
|
|
13
13
|
const DEFAULT_WAIT_S = 240; // stay under typical MCP client tool-call timeouts; call wisp_wait again for more
|
|
14
14
|
|
|
15
15
|
const TOOLS = [
|
package/server/openapi.js
CHANGED
|
@@ -16,7 +16,7 @@ export function openapi({ version, port }) {
|
|
|
16
16
|
title: 'wisp',
|
|
17
17
|
version,
|
|
18
18
|
description:
|
|
19
|
-
'Local API of wisp, a launcher and queue for coding agents (Claude Code, Codex, opencode, Cursor, Antigravity). ' +
|
|
19
|
+
'Local API of wisp, a launcher and queue for coding agents (Claude Code, Codex, opencode, Cursor, Antigravity, Grok). ' +
|
|
20
20
|
'Binds to 127.0.0.1 only. Requests must carry a Host of 127.0.0.1/localhost:<port>; non-GET requests must be ' +
|
|
21
21
|
'application/json (uploads: application/octet-stream) and, if they send an Origin, a local one. ' +
|
|
22
22
|
'The running server advertises { url, port, pid, version } in ~/.wisp/server.json.',
|
|
@@ -143,6 +143,21 @@ export function openapi({ version, port }) {
|
|
|
143
143
|
responses: { 200: json({ type: 'object' }), 400: err[400] },
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
|
+
'/machines': {
|
|
147
|
+
get: { summary: 'Machines (other computers over SSH) with connection state, steps, hints and their wisp version and harnesses', responses: { 200: json({ type: 'array', items: { type: 'object' } }) } },
|
|
148
|
+
post: {
|
|
149
|
+
summary: 'Add a machine and connect (unless connect: false). wisp starts itself there over SSH and tunnels back',
|
|
150
|
+
requestBody: body({ type: 'object', required: ['target'], properties: { target: { type: 'string', description: 'user@host, a host, or a Host from ~/.ssh/config' }, name: { type: 'string' }, port: { type: 'integer', default: 4477, description: 'wisp port on the machine' }, command: { type: 'string', description: 'Start command; default npx -y wisp-agents@<this version>' }, autoConnect: { type: 'boolean', default: true }, connect: { type: 'boolean', default: true } } }),
|
|
151
|
+
responses: { 201: json({ type: 'object' }), 400: err[400] },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
'/machines/ssh-hosts': { get: { summary: 'Hosts from ~/.ssh/config, for suggestions', responses: { 200: json({ type: 'array', items: { type: 'object' } }) } } },
|
|
155
|
+
'/machines/{mid}': { parameters: [{ name: 'mid', in: 'path', required: true, schema: { type: 'string' } }], patch: { summary: 'Edit a machine (host, port or command changes reconnect)', responses: { 200: json({ type: 'object' }) } }, delete: { summary: 'Remove a machine and its projects here (nothing on the machine is touched)', responses: { 200: json({ type: 'object' }) } } },
|
|
156
|
+
'/machines/{mid}/connect': { parameters: [{ name: 'mid', in: 'path', required: true, schema: { type: 'string' } }], post: { summary: 'Connect: SSH in, start wisp there if needed, open the tunnel', responses: { 200: json({ type: 'object' }) } } },
|
|
157
|
+
'/machines/{mid}/disconnect': { parameters: [{ name: 'mid', in: 'path', required: true, schema: { type: 'string' } }], post: { summary: 'Close the tunnel; wisp and its agents keep running there', responses: { 200: json({ type: 'object' }) } } },
|
|
158
|
+
'/machines/{mid}/stop': { parameters: [{ name: 'mid', in: 'path', required: true, schema: { type: 'string' } }], post: { summary: 'Stop wisp on the machine (its agents too); restart: true starts this version in its place', requestBody: body({ type: 'object', properties: { restart: { type: 'boolean' } } }), responses: { 200: json({ type: 'object' }) } } },
|
|
159
|
+
'/machines/{mid}/fs': { parameters: [{ name: 'mid', in: 'path', required: true, schema: { type: 'string' } }, { name: 'q', in: 'query', schema: { type: 'string' } }], get: { summary: 'Folder suggestions on the machine', responses: { 200: json({ type: 'array', items: { type: 'string' } }) } } },
|
|
160
|
+
'/machines/{mid}/discover': { parameters: [{ name: 'mid', in: 'path', required: true, schema: { type: 'string' } }], get: { summary: 'Git repos found on the machine that are not projects yet', responses: { 200: json({ type: 'array', items: { type: 'object' } }) } } },
|
|
146
161
|
'/remote/test': { post: { summary: 'Send a test message to the enabled channels', responses: { 200: json({ type: 'object' }), 400: err[400] } } },
|
|
147
162
|
'/usage': {
|
|
148
163
|
get: {
|
|
@@ -231,7 +246,7 @@ export function openapi({ version, port }) {
|
|
|
231
246
|
properties: {
|
|
232
247
|
prompt: { type: 'string' },
|
|
233
248
|
cwd: { type: 'string', description: 'Project folder (absolute; ~ allowed). Default: the server’s cwd' },
|
|
234
|
-
agent: { enum: ['claude', 'codex', 'opencode', 'cursor', 'antigravity'], default: 'claude' },
|
|
249
|
+
agent: { enum: ['claude', 'codex', 'opencode', 'cursor', 'antigravity', 'grok'], default: 'claude' },
|
|
235
250
|
mode: { enum: ['plan', 'edits', 'auto'], default: 'edits' },
|
|
236
251
|
model: { type: 'string' },
|
|
237
252
|
effort: { type: 'string', description: 'Thinking level: low, medium, high, xhigh, max (per harness)' },
|
package/server/remote.js
CHANGED
|
@@ -20,6 +20,7 @@ export class Remote {
|
|
|
20
20
|
constructor({ store, act, onChange = () => {} }) {
|
|
21
21
|
Object.assign(this, { store, act, onChange });
|
|
22
22
|
this.status = { ntfy: null, telegram: null };
|
|
23
|
+
this.tgElsewhere = new Map(); // Telegram message id -> session id, for sessions on a machine (server/machines.js)
|
|
23
24
|
this.ac = null;
|
|
24
25
|
this.ntfyPollMs = 4000;
|
|
25
26
|
this.tgLongPoll = 25;
|
|
@@ -188,9 +189,12 @@ export class Remote {
|
|
|
188
189
|
text: clip(`${msg.title}\n\n${msg.text}${s ? '\n\n↩ Reply to this message to answer or follow up.' : ''}`, 4000),
|
|
189
190
|
...(buttons.length && { reply_markup: { inline_keyboard: buttons } }),
|
|
190
191
|
});
|
|
191
|
-
if (s && sent?.message_id) {
|
|
192
|
+
if (s && sent?.message_id && this.store.get(s.id) === s) {
|
|
192
193
|
s.remote = { ...s.remote, tg: [...(s.remote?.tg || []), sent.message_id].slice(-MAX_TG_IDS) };
|
|
193
194
|
this.store.save(s);
|
|
195
|
+
} else if (s && sent?.message_id) {
|
|
196
|
+
this.tgElsewhere.set(sent.message_id, s.id);
|
|
197
|
+
if (this.tgElsewhere.size > 200) this.tgElsewhere.delete(this.tgElsewhere.keys().next().value);
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
|
|
@@ -238,9 +242,9 @@ export class Remote {
|
|
|
238
242
|
}
|
|
239
243
|
if (String(m.chat.id) !== String(t.chatId)) return;
|
|
240
244
|
const replyTo = m.reply_to_message?.message_id;
|
|
241
|
-
const
|
|
242
|
-
if (!
|
|
243
|
-
await say(await this.act({ sessionId
|
|
245
|
+
const sessionId = replyTo && (this.store.all().find((x) => x.remote?.tg?.includes(replyTo))?.id || this.tgElsewhere.get(replyTo));
|
|
246
|
+
if (!sessionId) return say('Reply to one of wisp’s messages to answer it or send a follow-up.');
|
|
247
|
+
await say(await this.act({ sessionId, kind: 'text', text: m.text }), { reply_to_message_id: m.message_id });
|
|
244
248
|
}
|
|
245
249
|
|
|
246
250
|
// For the settings view: config with secrets masked, plus channel health.
|
package/server/runner.js
CHANGED
|
@@ -351,6 +351,8 @@ export class Runner extends EventEmitter {
|
|
|
351
351
|
model: session.model,
|
|
352
352
|
effort: session.effort || '',
|
|
353
353
|
systemPrompt: prompt0 + workspaceNote(session) + this.promptExtra(session),
|
|
354
|
+
// A lead with a team delegates through wisp, so the harness's own subagent tool is switched off.
|
|
355
|
+
delegating: team.length > 0 && !!this.bridge,
|
|
354
356
|
mcpServers: this.bridge && adapter.askTool !== null && !session.parentId
|
|
355
357
|
? {
|
|
356
358
|
wisp: {
|
package/server/store.js
CHANGED
|
@@ -7,12 +7,13 @@ import { randomUUID } from 'node:crypto';
|
|
|
7
7
|
import { parsePresentation } from './protocol.js';
|
|
8
8
|
|
|
9
9
|
const DEFAULT_CONFIG = {
|
|
10
|
-
projects: [], // [{ id, name, path, color, addedAt }]
|
|
10
|
+
projects: [], // [{ id, name, path, color, addedAt, machine? }]; machine: the server/machines.js id it lives on
|
|
11
11
|
defaults: { agent: 'claude', mode: 'edits', model: '', effort: '' },
|
|
12
12
|
recipes: [], // saved prompts with {params}: server/recipes.js
|
|
13
13
|
triggers: [], // schedules and GitHub watchers: server/triggers.js
|
|
14
14
|
teams: [], // saved subagent presets: server/subagents.js
|
|
15
15
|
remote: {}, // phone channels (ntfy, Telegram): server/remote.js
|
|
16
|
+
machines: [], // other computers reached over SSH: server/machines.js
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
const IMAGE_EXT = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg']);
|
|
@@ -55,6 +56,7 @@ export class Store {
|
|
|
55
56
|
cfg.triggers ||= [];
|
|
56
57
|
cfg.teams ||= [];
|
|
57
58
|
cfg.remote ||= {};
|
|
59
|
+
cfg.machines ||= [];
|
|
58
60
|
return cfg;
|
|
59
61
|
} catch {
|
|
60
62
|
return structuredClone(DEFAULT_CONFIG);
|
|
@@ -67,21 +69,24 @@ export class Store {
|
|
|
67
69
|
|
|
68
70
|
// --- projects: plain folders, matched to sessions by path prefix ---
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
// machine: a project on another computer (paths there are POSIX); null = this one.
|
|
73
|
+
projectFor(cwd = '', machine = null) {
|
|
74
|
+
const sep = machine ? '/' : path.sep;
|
|
71
75
|
let best = null;
|
|
72
76
|
for (const p of this.config.projects)
|
|
73
|
-
if ((cwd === p.path || cwd.startsWith(p.path +
|
|
77
|
+
if ((p.machine || null) === machine && (cwd === p.path || cwd.startsWith(p.path + sep)) && (!best || p.path.length > best.path.length)) best = p;
|
|
74
78
|
return best;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
addProject(dir, { name, color } = {}) {
|
|
78
|
-
const existing = this.config.projects.find((p) => p.path === dir);
|
|
81
|
+
addProject(dir, { name, color, machine } = {}) {
|
|
82
|
+
const existing = this.config.projects.find((p) => p.path === dir && (p.machine || null) === (machine || null));
|
|
79
83
|
if (existing) return existing;
|
|
80
84
|
const used = new Set(this.config.projects.map((p) => p.color));
|
|
81
85
|
const project = {
|
|
82
86
|
id: Math.random().toString(36).slice(2, 10),
|
|
83
|
-
name: name || path.basename(dir) || dir,
|
|
87
|
+
name: name || (machine ? path.posix.basename(dir) : path.basename(dir)) || dir,
|
|
84
88
|
path: dir,
|
|
89
|
+
...(machine && { machine }),
|
|
85
90
|
color: color || PROJECT_COLORS.find((c) => !used.has(c)) || PROJECT_COLORS[this.config.projects.length % PROJECT_COLORS.length],
|
|
86
91
|
addedAt: Date.now(),
|
|
87
92
|
};
|
package/server/subagents.js
CHANGED
|
@@ -65,7 +65,8 @@ Hand them work with the \`delegate\` tool (${tool}): pass one or more tasks, \`{
|
|
|
65
65
|
${multi ? `- A subagent with ×N has N identical copies that run at once. Put several tasks for \`${multi.name}\` in one delegate call and each goes to a free copy; name a copy (\`${copies(multi)[1]}\`) to continue that copy's conversation.\n` : ''}- Subagents work in your folder and see each other's edits. Give parallel subagents separate files or concerns.
|
|
66
66
|
- Brief them like a colleague: goal, files, constraints, how to verify. They cannot see your conversation or ask the user.
|
|
67
67
|
- Follow the user's "use it for" notes when you pick a subagent: send each kind of work to the one meant for it.
|
|
68
|
-
-
|
|
68
|
+
- These are your subagents for this session. Whenever you would spawn a subagent, sub-task or helper agent (your harness's built-in Task/Agent tool included), use \`delegate\` instead: don't use built-in subagents.
|
|
69
|
+
- Delegate when the user's request calls for it or when the work splits cleanly; you stay responsible for the result. Review what they changed before you report.
|
|
69
70
|
- Your presentation covers the whole task; wisp shows each subagent's report next to it, so don't repeat them in full.`;
|
|
70
71
|
}
|
|
71
72
|
|
package/skills/wisp/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wisp
|
|
3
|
-
description: Delegate coding tasks to background agents (Claude Code, Codex, opencode, Cursor, Antigravity) through wisp, then read back their presentation (summary, diffs, tests). Use when the user asks to "send this to wisp", run something in the background or in parallel, fan a task out to other agents, or check on / follow up a wisp session.
|
|
3
|
+
description: Delegate coding tasks to background agents (Claude Code, Codex, opencode, Cursor, Antigravity, Grok) through wisp, then read back their presentation (summary, diffs, tests). Use when the user asks to "send this to wisp", run something in the background or in parallel, fan a task out to other agents, or check on / follow up a wisp session.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# wisp: hand tasks to background agents
|
|
@@ -22,7 +22,7 @@ wisp "audit dependencies for known CVEs" --mode plan --agent codex --json
|
|
|
22
22
|
wisp "add pagination to /api/users" --cwd ~/code/api --wait # block until done, print the result
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Options: `--agent claude|codex|opencode|cursor|antigravity`, `--mode plan|edits|auto`, `--model ID`,
|
|
25
|
+
Options: `--agent claude|codex|opencode|cursor|antigravity|grok`, `--mode plan|edits|auto`, `--model ID`,
|
|
26
26
|
`--effort low|medium|high`, `--cwd DIR` (default: current folder), `--wait`, `--json`.
|
|
27
27
|
`--json` prints the session summary; its `id` is what every other command takes.
|
|
28
28
|
|
package/web/css/app.css
CHANGED
|
@@ -140,12 +140,43 @@ kbd {
|
|
|
140
140
|
.link { color: var(--accent-ink); font-weight: 500; }
|
|
141
141
|
.link:hover { text-decoration: underline; text-underline-offset: 3px; }
|
|
142
142
|
.link.on { color: var(--ink); }
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
/* ---------- form kit ----------
|
|
144
|
+
One look for every standalone control. Use these classes, don't restyle controls per component:
|
|
145
|
+
<input class="input"> <textarea class="textarea"> <select> (styled by default)
|
|
146
|
+
.mono code-ish values: paths, cron, tokens, commands
|
|
147
|
+
.sm / .lg 30px rows in dense panels / the 38px add-project row
|
|
148
|
+
.field a box holding an icon / prefix + a bare <input>: <label class="field">icon<input></label>
|
|
149
|
+
.check a label wrapping a checkbox and its text
|
|
150
|
+
Components set only layout (width, flex) on these; a container already tinted surface-2 sets
|
|
151
|
+
--field-bg: var(--surface) so its fields don't melt into it. Prompt boxes (launcher, composer, rerun) and
|
|
152
|
+
inline renames (project name, memory edit) are their own, deliberately borderless pattern. */
|
|
153
|
+
:is(.input, .textarea, select, .field) {
|
|
154
|
+
width: 100%; min-width: 0; height: 32px; padding: 0 10px; border-radius: 9px; border: 1px solid var(--line-2);
|
|
155
|
+
background-color: var(--field-bg, var(--surface-2)); color: var(--ink); font: 13px/1.4 var(--sans); outline: 0;
|
|
156
|
+
transition: border-color 0.15s, box-shadow 0.15s, background-color 0.15s;
|
|
157
|
+
}
|
|
158
|
+
:is(.input, .textarea, select, .field):hover { border-color: color-mix(in srgb, var(--ink) 14%, var(--line-2)); }
|
|
159
|
+
:is(.input, .textarea, select):focus, .field:focus-within {
|
|
160
|
+
border-color: var(--accent); background-color: var(--surface); box-shadow: 0 0 0 3px var(--accent-soft);
|
|
161
|
+
}
|
|
162
|
+
:is(.input, .textarea, .field input)::placeholder { color: var(--faint); }
|
|
163
|
+
:is(.input, .textarea, select):disabled { opacity: 0.55; cursor: not-allowed; }
|
|
164
|
+
:is(.input, .textarea, select):is([aria-invalid='true'], :user-invalid) { border-color: var(--bad); }
|
|
165
|
+
:is(.input, .textarea, select):is([aria-invalid='true'], :user-invalid):focus { box-shadow: 0 0 0 3px var(--del-bg); }
|
|
166
|
+
.textarea { display: block; height: auto; min-height: 64px; padding: 7px 10px; line-height: 1.5; resize: vertical; }
|
|
167
|
+
:is(.input, .textarea, .field).mono { font-family: var(--mono); font-size: 12.5px; }
|
|
168
|
+
:is(.input, .field).sm { height: 30px; padding: 0 8px; }
|
|
169
|
+
:is(.input, .field).lg { height: 38px; padding: 0 12px; border-radius: 11px; }
|
|
170
|
+
select:not([multiple]) {
|
|
171
|
+
width: auto; padding-right: 28px; appearance: none; cursor: pointer; text-overflow: ellipsis;
|
|
146
172
|
background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
|
|
147
|
-
background-position: calc(100% -
|
|
173
|
+
background-position: calc(100% - 14px) 50%, calc(100% - 10px) 50%; background-size: 4px 4px; background-repeat: no-repeat;
|
|
148
174
|
}
|
|
175
|
+
.field { display: flex; align-items: center; gap: 8px; color: var(--muted); }
|
|
176
|
+
.field .i { width: 14px; height: 14px; flex: none; }
|
|
177
|
+
.field > input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; outline: 0; background: none; font: inherit; color: var(--ink); }
|
|
178
|
+
input[type='checkbox'] { width: 15px; height: 15px; margin: 0; flex: none; accent-color: var(--accent); cursor: pointer; }
|
|
179
|
+
.check { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-2); cursor: pointer; }
|
|
149
180
|
|
|
150
181
|
/* ---------- the mark ---------- */
|
|
151
182
|
/* A plain dot. Brand: ink dot with an offset accent dot. Live: a thin spinner ring. */
|
|
@@ -379,10 +410,7 @@ body:has(.launcher.docked:is(:focus-within, :has(.open))) .to-top { display: non
|
|
|
379
410
|
.pk-text small:empty { display: none; }
|
|
380
411
|
.pk-dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
|
|
381
412
|
.pk-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
|
|
382
|
-
.pk-search { flex: none;
|
|
383
|
-
.pk-search:focus-within { border-color: var(--accent); background: var(--surface); }
|
|
384
|
-
.pk-search .i { width: 14px; height: 14px; flex: none; }
|
|
385
|
-
.pk-search input { flex: 1; min-width: 0; border: 0; outline: 0; background: none; font: inherit; font-size: 13px; color: var(--ink); }
|
|
413
|
+
.pk-search { flex: none; width: auto; margin: 8px 8px 2px; color: var(--faint); }
|
|
386
414
|
.pk-list { flex: 1; overflow-y: auto; padding: 6px; }
|
|
387
415
|
.pk-group + .pk-group { margin-top: 4px; }
|
|
388
416
|
.pk-check { width: 15px; height: 15px; flex: none; color: var(--accent); visibility: hidden; }
|
|
@@ -600,9 +628,7 @@ body:has(.launcher.docked:is(:focus-within, :has(.open))) .to-top { display: non
|
|
|
600
628
|
.opt .k { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 6px; background: var(--bg-2); font: 500 11px var(--mono); color: var(--muted); flex: none; }
|
|
601
629
|
.opt:hover:not(:disabled) { border-color: var(--accent); background: var(--accent-soft); }
|
|
602
630
|
.opt.own { color: var(--muted); border-style: dashed; padding-left: 12px; }
|
|
603
|
-
.q-own { display: flex; gap: 8px; margin-top: 12px; }
|
|
604
|
-
.q-own input { flex: 1; height: 34px; border-radius: 10px; border: 1px solid var(--line); background: var(--surface-2); padding: 0 12px; outline: 0; }
|
|
605
|
-
.q-own input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
631
|
+
.q-own { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
|
|
606
632
|
.ask-card.sent { opacity: 0.5; pointer-events: none; }
|
|
607
633
|
|
|
608
634
|
/* ---------- presentation blocks ---------- */
|
|
@@ -1105,13 +1131,11 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1105
1131
|
.rr-file .i { width: 12px; height: 12px; color: var(--muted); }
|
|
1106
1132
|
.overlay:not([hidden]) ~ :is(.picker, .menu) { z-index: 55; }
|
|
1107
1133
|
.add-row { display: flex; gap: 8px; }
|
|
1108
|
-
.path-input { position: relative; flex: 1;
|
|
1109
|
-
.path-input
|
|
1110
|
-
.path-input .i { color: var(--muted); }
|
|
1134
|
+
.path-input { position: relative; flex: 1; }
|
|
1135
|
+
.path-input .i { width: 16px; height: 16px; }
|
|
1111
1136
|
.path-input .browse { display: grid; place-items: center; margin-left: -4px; width: 26px; height: 26px; border-radius: 7px; }
|
|
1112
1137
|
.path-input .browse:hover { background: var(--bg-2); }
|
|
1113
1138
|
.path-input .browse:hover .i { color: var(--accent); }
|
|
1114
|
-
.path-input input { flex: 1; border: 0; outline: 0; background: transparent; font: 13px var(--mono); }
|
|
1115
1139
|
.suggest { position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 2; padding: 4px; border-radius: 12px; background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow-2); }
|
|
1116
1140
|
.suggest button { display: block; width: 100%; text-align: left; padding: 6px 10px; border-radius: 8px; font: 12.5px var(--mono); color: var(--ink-2); }
|
|
1117
1141
|
.suggest button:hover, .suggest button.on { background: var(--accent-soft); color: var(--ink); }
|
|
@@ -1141,7 +1165,7 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1141
1165
|
.switch[aria-checked='true'] { background: var(--accent); }
|
|
1142
1166
|
.switch[aria-checked='true']::after { transform: translateX(14px); }
|
|
1143
1167
|
.switch:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
1144
|
-
.settings-modal input { flex: 1; max-width: 340px;
|
|
1168
|
+
.settings-modal label > .input { flex: 1; max-width: 340px; }
|
|
1145
1169
|
|
|
1146
1170
|
/* settings: sidebar of sections, one pane at a time */
|
|
1147
1171
|
.modal.sx { display: grid; grid-template-columns: 196px 1fr; width: min(880px, 100%); height: min(620px, 86vh); padding: 0; overflow: hidden; }
|
|
@@ -1158,7 +1182,7 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1158
1182
|
.sx-main > header { align-items: flex-start; gap: 16px; margin-bottom: 18px; }
|
|
1159
1183
|
.sx-title { margin: 2px 0 4px; font: 600 16px/1.3 var(--display); letter-spacing: -0.01em; }
|
|
1160
1184
|
.sx-lede { margin: 0; font-size: 12.5px; color: var(--muted); }
|
|
1161
|
-
.sx-group { border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); }
|
|
1185
|
+
.sx-group { --field-bg: var(--surface); border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); }
|
|
1162
1186
|
.sx-group + .sx-group, .sx-pane > * + .sx-group { margin-top: 14px; }
|
|
1163
1187
|
.sx-row { display: flex; align-items: center; gap: 16px; min-height: 52px; padding: 9px 14px; }
|
|
1164
1188
|
.sx-row + .sx-row { border-top: 1px solid var(--line); }
|
|
@@ -1168,7 +1192,7 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1168
1192
|
.sx-text small { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; }
|
|
1169
1193
|
.sx-text code { font-size: 11px; }
|
|
1170
1194
|
.sx-ctl { flex: none; display: flex; align-items: center; gap: 8px; }
|
|
1171
|
-
.sx-ctl input { width: 300px; }
|
|
1195
|
+
.sx-ctl .input { width: 300px; }
|
|
1172
1196
|
.sx-state { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
|
|
1173
1197
|
.sx-state.bad { color: var(--bad); }
|
|
1174
1198
|
.sx-usage .it-loading { grid-column: 1 / -1; }
|
|
@@ -1184,7 +1208,7 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1184
1208
|
.sx-nav h2 { display: none; }
|
|
1185
1209
|
.sx-nav button { flex: none; }
|
|
1186
1210
|
.sx-main { padding: 16px; }
|
|
1187
|
-
.sx-ctl input { width: 180px; }
|
|
1211
|
+
.sx-ctl .input { width: 180px; }
|
|
1188
1212
|
.sx-usage { grid-template-columns: repeat(2, 1fr); }
|
|
1189
1213
|
}
|
|
1190
1214
|
|
|
@@ -1226,7 +1250,6 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1226
1250
|
.mem-item:hover .mem-acts, .mem-item:focus-within .mem-acts { opacity: 1; }
|
|
1227
1251
|
.mem-edit { align-items: center; padding-top: 7px; padding-bottom: 7px; background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--accent); }
|
|
1228
1252
|
.mem-edit input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font: 13.5px var(--sans); color: var(--ink); }
|
|
1229
|
-
.memory-modal .mem-edit input { max-width: none; height: auto; padding: 0; }
|
|
1230
1253
|
.mem-add { display: inline-flex; align-items: center; gap: 6px; margin: 4px 0 0; padding: 4px 8px 4px 6px; border-radius: 7px; font-size: 12.5px; color: var(--muted); }
|
|
1231
1254
|
.mem-add:hover { color: var(--accent-ink); background: var(--accent-soft); }
|
|
1232
1255
|
.mem-add .i { width: 13px; height: 13px; }
|
|
@@ -1335,6 +1358,117 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1335
1358
|
@keyframes ring { from { transform: scale(0.6); opacity: 0.9; } to { transform: scale(1.7); opacity: 0; } }
|
|
1336
1359
|
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: -200% 0; } }
|
|
1337
1360
|
|
|
1361
|
+
/* ---------- machines: other computers over SSH ---------- */
|
|
1362
|
+
.machines-btn.trouble .i { color: var(--warn); }
|
|
1363
|
+
.q-machine {
|
|
1364
|
+
display: inline-flex; align-items: center; gap: 4px; max-width: 120px; padding: 0 6px; border-radius: 5px; border: 1px solid var(--line);
|
|
1365
|
+
font-size: 11px; font-weight: 500; line-height: 17px; color: var(--ink-2); white-space: nowrap;
|
|
1366
|
+
}
|
|
1367
|
+
.q-machine span { overflow: hidden; text-overflow: ellipsis; }
|
|
1368
|
+
.q-machine .i { flex: none; width: 11px; height: 11px; color: var(--accent); }
|
|
1369
|
+
.q-machine.off { color: var(--faint); border-style: dashed; }
|
|
1370
|
+
.q-machine.off .i { color: var(--faint); }
|
|
1371
|
+
/* the launcher: one group of project chips per place it runs on */
|
|
1372
|
+
.l-projects { row-gap: 6px; }
|
|
1373
|
+
.lp-group { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
|
1374
|
+
.lp-group + .lp-group { margin-left: 4px; padding-left: 10px; border-left: 1px solid var(--line); }
|
|
1375
|
+
.lp-gl { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; font-weight: 500; color: var(--ink-2); }
|
|
1376
|
+
.lp-gl .i { width: 13px; height: 13px; color: var(--accent); }
|
|
1377
|
+
.lp-gl.off { color: var(--faint); }
|
|
1378
|
+
.lp-gl.off .i { color: var(--faint); }
|
|
1379
|
+
.lp-gl .link { margin-left: 2px; }
|
|
1380
|
+
.where-btn .chev { width: 11px; height: 11px; opacity: 0.6; transform: rotate(90deg); }
|
|
1381
|
+
.where-btn.bare span, .where-btn.bare .chev { display: none; }
|
|
1382
|
+
.where-menu { min-width: 300px; }
|
|
1383
|
+
.wm-h { display: flex; align-items: baseline; gap: 8px; padding: 6px 10px 6px; font-size: 12px; font-weight: 600; color: var(--ink); }
|
|
1384
|
+
.wm-h small { font-weight: 400; color: var(--faint); }
|
|
1385
|
+
.where-menu .wm-row { gap: 10px; }
|
|
1386
|
+
.wm-check { display: grid; place-items: center; width: 16px; height: 16px; flex: none; border-radius: 5px; border: 1.5px solid var(--line-2); color: transparent; }
|
|
1387
|
+
.wm-check .i { width: 11px; height: 11px; }
|
|
1388
|
+
.wm-row.on .wm-check { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
1389
|
+
.wm-icon { position: relative; display: inline-grid; place-items: center; flex: none; color: var(--muted); }
|
|
1390
|
+
.wm-icon .i { width: 16px; height: 16px; }
|
|
1391
|
+
.wm-icon .dot { position: absolute; right: -3px; bottom: -2px; width: 7px; height: 7px; border: 1.5px solid var(--surface); box-sizing: content-box; }
|
|
1392
|
+
.wm-row.on .wm-icon { color: var(--accent); }
|
|
1393
|
+
.wm-name { flex: 1; min-width: 0; display: flex; flex-direction: column; text-align: left; }
|
|
1394
|
+
.wm-name b { font-weight: 500; color: var(--ink); }
|
|
1395
|
+
.wm-name small { font: 11px var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1396
|
+
.wm-agents { display: inline-flex; gap: 5px; }
|
|
1397
|
+
.wm-only { padding: 1px 6px; border-radius: 6px; font-size: 11px; color: var(--muted); opacity: 0; transition: opacity 0.12s; }
|
|
1398
|
+
.wm-row:hover .wm-only { opacity: 1; }
|
|
1399
|
+
.wm-only:hover { background: var(--bg-2); color: var(--ink); }
|
|
1400
|
+
.wm-sep { height: 1px; margin: 5px 4px; background: var(--line); }
|
|
1401
|
+
.p-chip .p-where { width: 11px; height: 11px; color: var(--muted); }
|
|
1402
|
+
.projects-modal .where { margin-bottom: 12px; }
|
|
1403
|
+
.projects-modal .where .i { width: 13px; height: 13px; color: var(--muted); }
|
|
1404
|
+
.projects-modal .where button.on .i { color: var(--accent); }
|
|
1405
|
+
.projects-modal .where .dot { width: 6px; height: 6px; }
|
|
1406
|
+
.machines-modal { width: min(640px, 100%); }
|
|
1407
|
+
.mc-list { display: flex; flex-direction: column; gap: 10px; }
|
|
1408
|
+
.mc { padding: 12px 14px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface); }
|
|
1409
|
+
.mc-head { display: flex; align-items: center; gap: 10px; }
|
|
1410
|
+
.mc-icon { position: relative; flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: 9px; background: var(--bg-2); color: var(--muted); }
|
|
1411
|
+
.mc-icon .i { width: 17px; height: 17px; }
|
|
1412
|
+
.mc-dot { position: absolute; right: -2px; bottom: -2px; width: 10px; height: 10px; border-radius: 50%; background: var(--faint); border: 2px solid var(--surface); }
|
|
1413
|
+
.mc.st-online .mc-icon { color: var(--accent); }
|
|
1414
|
+
.mc.st-online .mc-dot { background: var(--good); }
|
|
1415
|
+
.mc.st-error .mc-dot { background: var(--bad); }
|
|
1416
|
+
.mc:is(.st-connecting, .st-reconnecting) .mc-dot { background: var(--warn); animation: blink 1.2s infinite; }
|
|
1417
|
+
.mc-title { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
1418
|
+
.mc-title b { font-size: 14px; font-weight: 600; }
|
|
1419
|
+
.mc-title span { font: 11.5px var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1420
|
+
.mc-state { font-size: 12px; color: var(--muted); white-space: nowrap; }
|
|
1421
|
+
.mc.st-online .mc-state { color: var(--good); }
|
|
1422
|
+
.mc.st-error .mc-state { color: var(--bad); }
|
|
1423
|
+
.mc-meta, .mc-steps, .mc-hint, .mc-projects, .mc-edit { margin-left: 42px; }
|
|
1424
|
+
.mc-meta { display: flex; align-items: center; gap: 12px; margin-top: 8px; font-size: 12px; color: var(--muted); }
|
|
1425
|
+
.mc-meta .bad { color: var(--bad); }
|
|
1426
|
+
.mc-agents { display: inline-flex; gap: 7px; }
|
|
1427
|
+
.mc-steps { list-style: none; display: flex; flex-direction: column; gap: 5px; margin-top: 10px; margin-bottom: 0; padding: 0; }
|
|
1428
|
+
.mc-steps li { display: grid; grid-template-columns: 16px auto 1fr; align-items: center; column-gap: 8px; font-size: 12.5px; color: var(--faint); }
|
|
1429
|
+
.mc-steps li b { font-weight: 500; }
|
|
1430
|
+
.mc-steps li small { font: 11.5px var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1431
|
+
.mc-si { display: grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--line-2); }
|
|
1432
|
+
.mc-si .i { width: 10px; height: 10px; }
|
|
1433
|
+
.mc-steps .s-done { color: var(--ink-2); }
|
|
1434
|
+
.mc-steps .s-done .mc-si { background: var(--good); border-color: var(--good); color: #fff; animation: check-in 0.25s var(--ease); }
|
|
1435
|
+
.mc-steps .s-run { color: var(--ink); }
|
|
1436
|
+
.mc-steps .s-run .mc-si { border-color: var(--accent); border-top-color: transparent; animation: spin 0.8s linear infinite; }
|
|
1437
|
+
.mc-steps .s-fail { color: var(--bad); }
|
|
1438
|
+
.mc-steps .s-fail .mc-si { background: var(--bad); border-color: var(--bad); color: #fff; }
|
|
1439
|
+
.mc-steps .s-fail small { color: var(--bad); white-space: normal; }
|
|
1440
|
+
.mc-steps .s-warn { color: var(--warn); }
|
|
1441
|
+
.mc-steps .s-warn .mc-si { border-color: var(--warn); color: var(--warn); }
|
|
1442
|
+
.mc-steps .s-warn small { white-space: normal; }
|
|
1443
|
+
.mc-hint { margin-top: 10px; padding: 10px 12px; border-radius: 10px; border: 1px solid color-mix(in srgb, var(--bad) 25%, var(--line)); background: var(--del-bg); font-size: 12.5px; color: var(--ink-2); }
|
|
1444
|
+
.mc-hint.soft { border-color: var(--line); background: var(--surface-2); }
|
|
1445
|
+
.mc-hint p { margin: 0 0 6px; }
|
|
1446
|
+
.mc-hint > :last-child { margin-bottom: 0; }
|
|
1447
|
+
.mc-cmd { display: flex; align-items: center; gap: 6px; margin: 4px 0 8px; padding: 3px 3px 3px 10px; border-radius: 8px; background: var(--term-bg); color: var(--term-ink); }
|
|
1448
|
+
.mc-cmd code { flex: 1; font: 12px var(--mono); }
|
|
1449
|
+
.mc-cmd .ghost { color: var(--term-ink); }
|
|
1450
|
+
.mc-cmd .ghost:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
|
|
1451
|
+
.mc-log summary { cursor: pointer; font-size: 12px; color: var(--muted); width: max-content; }
|
|
1452
|
+
.mc-log pre { max-height: 180px; overflow: auto; margin: 6px 0 0; padding: 8px 10px; border-radius: 8px; background: var(--surface); border: 1px solid var(--line); font: 11px/1.5 var(--mono); white-space: pre-wrap; }
|
|
1453
|
+
.mc-projects { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px; }
|
|
1454
|
+
.mc-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
|
|
1455
|
+
.mc-add { margin-top: 12px; }
|
|
1456
|
+
.mc-add h4 { margin-top: 18px; }
|
|
1457
|
+
.mc-new { height: 32px; }
|
|
1458
|
+
.mc-form { display: flex; flex-direction: column; gap: 10px; }
|
|
1459
|
+
.mc-row { display: flex; gap: 10px; }
|
|
1460
|
+
.mc-field { display: flex; flex-direction: column; gap: 5px; width: 150px; min-width: 0; }
|
|
1461
|
+
.mc-field.grow { flex: 1; width: auto; }
|
|
1462
|
+
.mc-field span { font-size: 12px; font-weight: 500; color: var(--ink-2); }
|
|
1463
|
+
.mc-adv summary { cursor: pointer; width: max-content; font-size: 12.5px; color: var(--muted); }
|
|
1464
|
+
.mc-adv[open] summary { margin-bottom: 8px; }
|
|
1465
|
+
.mc-check { margin-top: 8px; }
|
|
1466
|
+
.mc-foot { display: flex; align-items: center; gap: 10px; }
|
|
1467
|
+
.mc-foot .hint { flex: 1; margin: 0 !important; font-size: 12px !important; }
|
|
1468
|
+
.mc-foot .primary { flex: none; }
|
|
1469
|
+
.mc-foot .primary .i { width: 14px; height: 14px; }
|
|
1470
|
+
.menu button.danger:hover { color: var(--bad); background: var(--del-bg); }
|
|
1471
|
+
|
|
1338
1472
|
/* ---------- responsive ---------- */
|
|
1339
1473
|
@media (max-width: 720px) {
|
|
1340
1474
|
.topbar { padding: 0 12px; gap: 10px; }
|
|
@@ -1343,7 +1477,10 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1343
1477
|
.search-btn { padding: 0 8px; border-color: transparent; background: none; }
|
|
1344
1478
|
.feed-wrap { padding: 14px 16px 80px; }
|
|
1345
1479
|
.launcher.docked { width: calc(100% - 32px); }
|
|
1346
|
-
.it-side .q-proj, .it-actions span, .c-done span { display: none; }
|
|
1480
|
+
.it-side .q-proj, .it-actions span, .c-done span, .q-machine span { display: none; }
|
|
1481
|
+
.mc-row { flex-direction: column; }
|
|
1482
|
+
.mc-field { width: auto; }
|
|
1483
|
+
.mc-meta, .mc-steps, .mc-hint, .mc-projects, .mc-edit { margin-left: 0; }
|
|
1347
1484
|
.turns { padding: 6px 14px 8px; }
|
|
1348
1485
|
.it-dock { padding: 4px 10px 10px; }
|
|
1349
1486
|
.b-hero h2 { font-size: 22px; }
|
|
@@ -1441,12 +1578,8 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1441
1578
|
.ship-route .muted { margin-left: auto; }
|
|
1442
1579
|
.ship-field { display: flex; flex-direction: column; gap: 6px; margin: 12px 0 0; }
|
|
1443
1580
|
.ship-field > span { font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
|
|
1444
|
-
.ship-field
|
|
1445
|
-
|
|
1446
|
-
}
|
|
1447
|
-
.ship-field textarea { font: 12.5px/1.5 var(--mono); }
|
|
1448
|
-
.ship-field :is(input, textarea, select):focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
1449
|
-
.ship-check { display: flex; align-items: center; gap: 8px; margin-top: 10px; font-size: 13px; }
|
|
1581
|
+
.ship-field select { width: 100%; }
|
|
1582
|
+
.ship-check { display: flex; margin-top: 10px; }
|
|
1450
1583
|
.ship-foot { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
|
|
1451
1584
|
.ship-foot > .muted { margin-right: auto; }
|
|
1452
1585
|
.ship-foot code { font-size: 11px; }
|
|
@@ -1490,9 +1623,7 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1490
1623
|
.au-form label > span, .au-row > span { width: 92px; flex: none; font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
|
|
1491
1624
|
.au-form label.stack { flex-direction: column; align-items: stretch; gap: 6px; }
|
|
1492
1625
|
.au-form label.stack > span { width: auto; }
|
|
1493
|
-
.au-form input, .
|
|
1494
|
-
.au-form textarea { font: 12.5px/1.5 var(--mono); resize: vertical; }
|
|
1495
|
-
.au-form :is(input, select, textarea):focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
1626
|
+
.au-form :is(.input, .textarea, select) { flex: 1; }
|
|
1496
1627
|
.au-form [hidden] { display: none !important; }
|
|
1497
1628
|
.au-params { margin: 6px 0 2px; }
|
|
1498
1629
|
.au-params code, .k-vars code { font-size: 11px; }
|
|
@@ -1531,7 +1662,7 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1531
1662
|
.phone-modal .ph-sec h4 { display: flex; align-items: center; gap: 8px; }
|
|
1532
1663
|
.phone-modal .ph-sec h4 .switch { margin-left: auto; }
|
|
1533
1664
|
.phone-modal .settings-modal { width: auto; padding: 0; border: 0; box-shadow: none; background: none; }
|
|
1534
|
-
.
|
|
1665
|
+
.settings-modal label.check { padding: 0; border: 0; gap: 6px; }
|
|
1535
1666
|
.ph-ok, .ph-bad { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 500; }
|
|
1536
1667
|
.ph-ok { color: var(--good); }
|
|
1537
1668
|
.ph-bad { color: var(--bad); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -1594,14 +1725,12 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1594
1725
|
.tp-warn[hidden] { display: none; }
|
|
1595
1726
|
.tp-warn .i { flex: none; width: 14px; height: 14px; margin-top: 1px; }
|
|
1596
1727
|
.tp-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 0 14px 12px; }
|
|
1597
|
-
.tp-row { display: grid; gap: 6px; padding: 8px; border-radius: 11px; border: 1px solid var(--line); background: var(--surface-2); animation: rise 0.25s var(--ease) both; }
|
|
1728
|
+
.tp-row { --field-bg: var(--surface); display: grid; gap: 6px; padding: 8px; border-radius: 11px; border: 1px solid var(--line); background: var(--surface-2); animation: rise 0.25s var(--ease) both; }
|
|
1598
1729
|
.tp-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
1599
1730
|
.tp-runner { flex: 0 1 auto; min-width: 0; overflow: hidden; }
|
|
1600
1731
|
.tp-runner .rb-m { max-width: 140px; }
|
|
1601
|
-
.tp-name { flex: 1 1 90px; min-width: 70px;
|
|
1602
|
-
.tp-
|
|
1603
|
-
.tp-name input, .tp-use input { flex: 1; min-width: 0; border: 0; outline: 0; background: none; font: inherit; color: var(--ink); }
|
|
1604
|
-
.tp-copies { flex: none; display: inline-flex; align-items: center; height: 30px; padding: 0 2px; border-radius: 9px; border: 1px solid var(--line); background: var(--surface); }
|
|
1732
|
+
.tp-name { flex: 1 1 90px; min-width: 70px; width: auto; gap: 0; color: var(--faint); }
|
|
1733
|
+
.tp-copies { flex: none; display: inline-flex; align-items: center; height: 30px; padding: 0 2px; border-radius: 9px; border: 1px solid var(--line-2); background: var(--surface); }
|
|
1605
1734
|
.tp-copies b { min-width: 22px; text-align: center; font: 500 12px var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
1606
1735
|
.tp-copies.on { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
|
|
1607
1736
|
.tp-copies.on b { color: var(--accent-ink); }
|
|
@@ -1612,16 +1741,14 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1612
1741
|
.tp-copies .i { width: 11px; height: 11px; }
|
|
1613
1742
|
.tp-del { flex: none; width: 28px; height: 28px; }
|
|
1614
1743
|
.tp-del .i { width: 13px; height: 13px; }
|
|
1615
|
-
.tp-use { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 9px; border-radius: 9px; border: 1px solid var(--line); background: var(--surface); font-size: 13px; }
|
|
1616
1744
|
.tp-use > span { flex: none; font-size: 11px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint); }
|
|
1617
|
-
.tp-use input::placeholder, .tp-name input::placeholder { color: var(--faint); }
|
|
1618
1745
|
.tp-empty { display: flex; align-items: center; gap: 12px; padding: 16px; border-radius: 11px; border: 1px dashed var(--line-2); color: var(--muted); font-size: 13px; }
|
|
1619
1746
|
.tp-empty .i { flex: none; width: 22px; height: 22px; color: var(--faint); }
|
|
1620
1747
|
.tp-empty p { margin: 0; line-height: 1.4; }
|
|
1621
1748
|
.tp-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--line); background: var(--surface-2); }
|
|
1622
1749
|
.tp-count { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
|
|
1623
1750
|
.tp-saveform { display: flex; align-items: center; gap: 8px; flex: 1; }
|
|
1624
|
-
.tp-saveform input { flex: 1;
|
|
1751
|
+
.tp-saveform .input { flex: 1; }
|
|
1625
1752
|
.menu.tp-menu { width: 300px; }
|
|
1626
1753
|
.menu.tp-menu button { align-items: center; text-align: left; }
|
|
1627
1754
|
.menu.tp-menu button.on b { color: var(--accent-ink); }
|
package/web/index.html
CHANGED
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
<symbol id="i-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></symbol>
|
|
54
54
|
<symbol id="i-bolt" viewBox="0 0 24 24"><path d="M13.5 3L5 13.5h6L10.5 21 19 10.5h-6L13.5 3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></symbol>
|
|
55
55
|
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M12 7.5V12l3 2" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></symbol>
|
|
56
|
+
<symbol id="i-laptop" viewBox="0 0 24 24"><rect x="4.5" y="5" width="15" height="10.5" rx="1.8" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M2.5 19h19" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></symbol>
|
|
57
|
+
<symbol id="i-server" viewBox="0 0 24 24"><rect x="3.5" y="4" width="17" height="7" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><rect x="3.5" y="13" width="17" height="7" rx="2" fill="none" stroke="currentColor" stroke-width="1.7"/><circle cx="7.3" cy="7.5" r="1.1" fill="currentColor"/><circle cx="7.3" cy="16.5" r="1.1" fill="currentColor"/><path d="M11 7.5h5.5M11 16.5h5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></symbol>
|
|
56
58
|
<symbol id="i-play" viewBox="0 0 24 24"><path d="M7 5.5v13l11-6.5-11-6.5z" fill="currentColor"/></symbol>
|
|
57
59
|
</defs>
|
|
58
60
|
</svg>
|
|
@@ -67,6 +69,9 @@
|
|
|
67
69
|
<button class="update-btn" id="updateBtn" hidden>
|
|
68
70
|
<svg class="i"><use href="#i-update"/></svg><span>Update</span>
|
|
69
71
|
</button>
|
|
72
|
+
<button class="auto-btn machines-btn" id="machinesBtn" title="Machines: run agents on another computer over SSH">
|
|
73
|
+
<svg class="i"><use href="#i-server"/></svg><span>Machines</span><b class="ab-n" hidden></b>
|
|
74
|
+
</button>
|
|
70
75
|
<button class="auto-btn" id="autoBtn" title="Automations: recipes and triggers">
|
|
71
76
|
<svg class="i"><use href="#i-bolt"/></svg><span>Automations</span><b class="ab-n" hidden></b>
|
|
72
77
|
</button>
|