wisp-agents 0.8.0 → 0.10.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 +35 -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/git.js +6 -3
- package/server/index.js +212 -19
- package/server/machines.js +736 -0
- package/server/mcp-ask.js +1 -1
- package/server/mcp-wisp.js +2 -2
- package/server/openapi.js +18 -2
- package/server/remote.js +8 -4
- package/server/runner.js +3 -0
- package/server/store.js +11 -6
- package/server/subagents.js +2 -1
- package/server/update.js +8 -4
- package/skills/wisp/SKILL.md +2 -2
- package/web/css/app.css +173 -39
- package/web/index.html +5 -0
- package/web/js/app.js +649 -86
- package/web/logos/grok.svg +1 -0
package/server/mcp-ask.js
CHANGED
|
@@ -38,7 +38,7 @@ const TEAM_TOOLS = [
|
|
|
38
38
|
'Hand tasks to your subagents (separate coding agents the user picked, each on its own harness and model). ' +
|
|
39
39
|
'Tasks in one call run in parallel in your working folder. By default the call waits until every task finishes and ' +
|
|
40
40
|
'returns each report plus the files it changed; wait=false returns delegation ids at once. Delegating to a subagent ' +
|
|
41
|
-
'again continues its conversation.\n\nThe team:\n' + roster,
|
|
41
|
+
'again continues its conversation. Use this instead of any built-in subagent or task tool.\n\nThe team:\n' + roster,
|
|
42
42
|
inputSchema: {
|
|
43
43
|
type: 'object',
|
|
44
44
|
properties: {
|
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,22 @@ 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/discover': { get: { summary: 'Hosts this computer can reach: ~/.ssh/config, Tailscale peers, ~/.ssh/known_hosts', responses: { 200: json({ type: 'array', items: { type: 'object' } }) } } },
|
|
155
|
+
'/machines/ssh-hosts': { get: { summary: 'Hosts from ~/.ssh/config, for suggestions', responses: { 200: json({ type: 'array', items: { type: 'object' } }) } } },
|
|
156
|
+
'/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' }) } } },
|
|
157
|
+
'/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' }) } } },
|
|
158
|
+
'/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' }) } } },
|
|
159
|
+
'/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' }) } } },
|
|
160
|
+
'/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' } }) } } },
|
|
161
|
+
'/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
162
|
'/remote/test': { post: { summary: 'Send a test message to the enabled channels', responses: { 200: json({ type: 'object' }), 400: err[400] } } },
|
|
147
163
|
'/usage': {
|
|
148
164
|
get: {
|
|
@@ -231,7 +247,7 @@ export function openapi({ version, port }) {
|
|
|
231
247
|
properties: {
|
|
232
248
|
prompt: { type: 'string' },
|
|
233
249
|
cwd: { type: 'string', description: 'Project folder (absolute; ~ allowed). Default: the server’s cwd' },
|
|
234
|
-
agent: { enum: ['claude', 'codex', 'opencode', 'cursor', 'antigravity'], default: 'claude' },
|
|
250
|
+
agent: { enum: ['claude', 'codex', 'opencode', 'cursor', 'antigravity', 'grok'], default: 'claude' },
|
|
235
251
|
mode: { enum: ['plan', 'edits', 'auto'], default: 'edits' },
|
|
236
252
|
model: { type: 'string' },
|
|
237
253
|
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
|
@@ -67,6 +67,7 @@ export function summarize(s, store) {
|
|
|
67
67
|
asks: !!s.ask || !!last?.presentation?.blocks?.some((b) => b.type === 'question'),
|
|
68
68
|
question: s.ask?.question || '',
|
|
69
69
|
branch: s.worktree?.branch || '',
|
|
70
|
+
toShip: s.toShip || null, // { dirty, ahead, unpushed } of the worktree, null until checked
|
|
70
71
|
pr: s.pr ? { number: s.pr.number, url: s.pr.url, state: s.pr.state, draft: s.pr.draft, review: s.pr.review, checks: s.pr.checks } : null,
|
|
71
72
|
groupId: s.groupId || null,
|
|
72
73
|
origin: s.origin || null, // { kind: 'recipe' | 'trigger', id, name }
|
|
@@ -351,6 +352,8 @@ export class Runner extends EventEmitter {
|
|
|
351
352
|
model: session.model,
|
|
352
353
|
effort: session.effort || '',
|
|
353
354
|
systemPrompt: prompt0 + workspaceNote(session) + this.promptExtra(session),
|
|
355
|
+
// A lead with a team delegates through wisp, so the harness's own subagent tool is switched off.
|
|
356
|
+
delegating: team.length > 0 && !!this.bridge,
|
|
354
357
|
mcpServers: this.bridge && adapter.askTool !== null && !session.parentId
|
|
355
358
|
? {
|
|
356
359
|
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/server/update.js
CHANGED
|
@@ -88,12 +88,16 @@ export class Updater {
|
|
|
88
88
|
this.set({ status: 'idle', error: err.message });
|
|
89
89
|
throw err;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
const node = JSON.stringify(process.versions.electron ? 'node' : process.execPath);
|
|
93
|
-
const relaunch = this.method === 'npx' ? `npx -y ${PKG.name}@latest${args}` : `${node} ${JSON.stringify(path.join(ROOT, 'bin', 'wisp.js'))}${args}`;
|
|
94
|
-
handOff(this.port, relaunch);
|
|
91
|
+
handOff(this.port, this.relaunchCommand());
|
|
95
92
|
this.set({ status: 'restarting' });
|
|
96
93
|
}
|
|
94
|
+
|
|
95
|
+
// Same port as now (wisp may have been started with --port), so the open tab finds it again.
|
|
96
|
+
relaunchCommand(electron = !!process.versions.electron) {
|
|
97
|
+
const args = `${electron ? '' : ' --browser --no-open'} --port ${this.port}`; // desktop: reopen the app; browser: keep the tab
|
|
98
|
+
const node = JSON.stringify(electron ? 'node' : process.execPath);
|
|
99
|
+
return this.method === 'npx' ? `npx -y ${PKG.name}@latest${args}` : `${node} ${JSON.stringify(path.join(ROOT, 'bin', 'wisp.js'))}${args}`;
|
|
100
|
+
}
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
function run(cmd) {
|
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,124 @@ 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-found { margin-bottom: 14px; }
|
|
1459
|
+
.mc-found h4 { margin: 0 0 8px; }
|
|
1460
|
+
.mc-found-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1461
|
+
.mc-found .lp-chip .i { width: 14px; height: 14px; color: var(--muted); }
|
|
1462
|
+
.mc-found .lp-chip.off { color: var(--muted); }
|
|
1463
|
+
.mc-fdot { width: 7px; height: 7px; border-radius: 50%; background: var(--good); }
|
|
1464
|
+
.lp-chip.off .mc-fdot { background: var(--faint); }
|
|
1465
|
+
.mc-form { display: flex; flex-direction: column; gap: 10px; }
|
|
1466
|
+
.mc-row { display: flex; gap: 10px; }
|
|
1467
|
+
.mc-field { display: flex; flex-direction: column; gap: 5px; width: 150px; min-width: 0; }
|
|
1468
|
+
.mc-field.grow { flex: 1; width: auto; }
|
|
1469
|
+
.mc-field span { font-size: 12px; font-weight: 500; color: var(--ink-2); }
|
|
1470
|
+
.mc-adv summary { cursor: pointer; width: max-content; font-size: 12.5px; color: var(--muted); }
|
|
1471
|
+
.mc-adv[open] summary { margin-bottom: 8px; }
|
|
1472
|
+
.mc-check { margin-top: 8px; }
|
|
1473
|
+
.mc-foot { display: flex; align-items: center; gap: 10px; }
|
|
1474
|
+
.mc-foot .hint { flex: 1; margin: 0 !important; font-size: 12px !important; }
|
|
1475
|
+
.mc-foot .primary { flex: none; }
|
|
1476
|
+
.mc-foot .primary .i { width: 14px; height: 14px; }
|
|
1477
|
+
.menu button.danger:hover { color: var(--bad); background: var(--del-bg); }
|
|
1478
|
+
|
|
1338
1479
|
/* ---------- responsive ---------- */
|
|
1339
1480
|
@media (max-width: 720px) {
|
|
1340
1481
|
.topbar { padding: 0 12px; gap: 10px; }
|
|
@@ -1343,7 +1484,10 @@ pre.code { margin: 0 0 12px; padding: 12px 14px; border-radius: 10px; background
|
|
|
1343
1484
|
.search-btn { padding: 0 8px; border-color: transparent; background: none; }
|
|
1344
1485
|
.feed-wrap { padding: 14px 16px 80px; }
|
|
1345
1486
|
.launcher.docked { width: calc(100% - 32px); }
|
|
1346
|
-
.it-side .q-proj, .it-actions span, .c-done span { display: none; }
|
|
1487
|
+
.it-side .q-proj, .it-actions span, .c-done span, .q-machine span { display: none; }
|
|
1488
|
+
.mc-row { flex-direction: column; }
|
|
1489
|
+
.mc-field { width: auto; }
|
|
1490
|
+
.mc-meta, .mc-steps, .mc-hint, .mc-projects, .mc-edit { margin-left: 0; }
|
|
1347
1491
|
.turns { padding: 6px 14px 8px; }
|
|
1348
1492
|
.it-dock { padding: 4px 10px 10px; }
|
|
1349
1493
|
.b-hero h2 { font-size: 22px; }
|
|
@@ -1441,12 +1585,8 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1441
1585
|
.ship-route .muted { margin-left: auto; }
|
|
1442
1586
|
.ship-field { display: flex; flex-direction: column; gap: 6px; margin: 12px 0 0; }
|
|
1443
1587
|
.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; }
|
|
1588
|
+
.ship-field select { width: 100%; }
|
|
1589
|
+
.ship-check { display: flex; margin-top: 10px; }
|
|
1450
1590
|
.ship-foot { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
|
|
1451
1591
|
.ship-foot > .muted { margin-right: auto; }
|
|
1452
1592
|
.ship-foot code { font-size: 11px; }
|
|
@@ -1490,9 +1630,7 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1490
1630
|
.au-form label > span, .au-row > span { width: 92px; flex: none; font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
|
|
1491
1631
|
.au-form label.stack { flex-direction: column; align-items: stretch; gap: 6px; }
|
|
1492
1632
|
.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); }
|
|
1633
|
+
.au-form :is(.input, .textarea, select) { flex: 1; }
|
|
1496
1634
|
.au-form [hidden] { display: none !important; }
|
|
1497
1635
|
.au-params { margin: 6px 0 2px; }
|
|
1498
1636
|
.au-params code, .k-vars code { font-size: 11px; }
|
|
@@ -1531,7 +1669,7 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1531
1669
|
.phone-modal .ph-sec h4 { display: flex; align-items: center; gap: 8px; }
|
|
1532
1670
|
.phone-modal .ph-sec h4 .switch { margin-left: auto; }
|
|
1533
1671
|
.phone-modal .settings-modal { width: auto; padding: 0; border: 0; box-shadow: none; background: none; }
|
|
1534
|
-
.
|
|
1672
|
+
.settings-modal label.check { padding: 0; border: 0; gap: 6px; }
|
|
1535
1673
|
.ph-ok, .ph-bad { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 500; }
|
|
1536
1674
|
.ph-ok { color: var(--good); }
|
|
1537
1675
|
.ph-bad { color: var(--bad); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -1594,14 +1732,12 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1594
1732
|
.tp-warn[hidden] { display: none; }
|
|
1595
1733
|
.tp-warn .i { flex: none; width: 14px; height: 14px; margin-top: 1px; }
|
|
1596
1734
|
.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; }
|
|
1735
|
+
.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
1736
|
.tp-top { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
1599
1737
|
.tp-runner { flex: 0 1 auto; min-width: 0; overflow: hidden; }
|
|
1600
1738
|
.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); }
|
|
1739
|
+
.tp-name { flex: 1 1 90px; min-width: 70px; width: auto; gap: 0; color: var(--faint); }
|
|
1740
|
+
.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
1741
|
.tp-copies b { min-width: 22px; text-align: center; font: 500 12px var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
1606
1742
|
.tp-copies.on { border-color: color-mix(in srgb, var(--accent) 35%, var(--line)); }
|
|
1607
1743
|
.tp-copies.on b { color: var(--accent-ink); }
|
|
@@ -1612,16 +1748,14 @@ body.electron.mac .topbar { padding-left: 88px; }
|
|
|
1612
1748
|
.tp-copies .i { width: 11px; height: 11px; }
|
|
1613
1749
|
.tp-del { flex: none; width: 28px; height: 28px; }
|
|
1614
1750
|
.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
1751
|
.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
1752
|
.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
1753
|
.tp-empty .i { flex: none; width: 22px; height: 22px; color: var(--faint); }
|
|
1620
1754
|
.tp-empty p { margin: 0; line-height: 1.4; }
|
|
1621
1755
|
.tp-foot { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--line); background: var(--surface-2); }
|
|
1622
1756
|
.tp-count { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
|
|
1623
1757
|
.tp-saveform { display: flex; align-items: center; gap: 8px; flex: 1; }
|
|
1624
|
-
.tp-saveform input { flex: 1;
|
|
1758
|
+
.tp-saveform .input { flex: 1; }
|
|
1625
1759
|
.menu.tp-menu { width: 300px; }
|
|
1626
1760
|
.menu.tp-menu button { align-items: center; text-align: left; }
|
|
1627
1761
|
.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>
|