agent-memory-git 0.1.9__tar.gz → 0.1.11__tar.gz
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.
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/PKG-INFO +3 -3
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/README.md +2 -2
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/pyproject.toml +1 -1
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/pyproject.toml.orig +1 -1
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/PROTOCOL.md +1 -1
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/cli.py +15 -4
- agent_memory_git-0.1.11/src/agent_memory/hooks.py +141 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/install.py +5 -1
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/ui_server.py +6 -1
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/app.css +159 -124
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/avatar.js +5 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/main.js +2 -8
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/store.js +22 -5
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/views/overlays.js +3 -0
- agent_memory_git-0.1.11/src/agent_memory/webui/js/views/profiles.js +66 -0
- agent_memory_git-0.1.11/src/agent_memory/webui/js/views/settings.js +128 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/views/ui.js +0 -3
- agent_memory_git-0.1.9/src/agent_memory/hooks.py +0 -122
- agent_memory_git-0.1.9/src/agent_memory/webui/js/views/profiles.js +0 -85
- agent_memory_git-0.1.9/src/agent_memory/webui/js/views/settings.js +0 -138
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/LICENSE +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/__init__.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/__main__.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/capture.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/config.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/context.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/personal/2026/08/20260816-blog-publishing-k3qm.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/personal/2026/09/20260906-push-personal-repos-w8re.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/personal/2026/09/20260921-home-server-n2vb.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/personal/2026/10/20261004-options-first-7hcx.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/personal/2026/10/20261009-thesis-setup-fr6e.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/08/20260804-alex-profile-ujzd.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/08/20260804-reply-language-xdnc.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/08/20260812-ask-before-push-pf3d.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/08/20260820-tests-before-done-odzd.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/08/20260827-etl-orders-export-deadline-pft7.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/08/20260828-etl-dashboard-72ce.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/09/20260902-report-eta-is2j.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/09/20260908-shop-web-stripe-checkout-lgmx.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/09/20260910-shop-web-deploys-dn73.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/09/20260915-etl-parquet-partitioning-seh2.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/09/20260922-design-tokens-source-uvw7.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/10/20261002-shop-web-v3-freeze-5xtp.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo/work/2026/10/20261012-payments-runbook-4syw.md +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/demo.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/embed_worker.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/frontmatter.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/gitsync.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/importers.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/lint.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/profiles.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/select.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/sessions.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/store.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/ui.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/update.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/util.py +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/index.html +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/api.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/filters.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/format.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/h.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/icons.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/markdown.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/search.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/views/insights.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/views/list.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/views/memory.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/webui/js/views/shell.js +0 -0
- {agent_memory_git-0.1.9 → agent_memory_git-0.1.11}/src/agent_memory/workdir.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-memory-git
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: Shared, git-backed memory for Claude Code and Codex.
|
|
5
5
|
Keywords: claude-code,codex,memory,agents,git
|
|
6
6
|
Author: Shichao Song
|
|
@@ -58,8 +58,8 @@ Description-Content-Type: text/markdown
|
|
|
58
58
|
- **Memory in git.** One Markdown file per memory in a git repository you own, with where it came
|
|
59
59
|
from recorded by the hooks. Every change is a commit you can review or revert; add a private
|
|
60
60
|
remote to sync machines, or keep it all on one. Here is [an example](https://github.com/Ki-Seki/agent-memory-data-demo).
|
|
61
|
-
[Profiles](https://ki-seki.github.io/agent-memory/docs.html#profiles) keep
|
|
62
|
-
memories in repositories of their own.
|
|
61
|
+
[Profiles](https://ki-seki.github.io/agent-memory/docs.html#profiles) keep separate sets of
|
|
62
|
+
memories in repositories of their own, and a switch reaches running sessions too.
|
|
63
63
|
- **Nothing to run.** Two hooks and git. No service, no account; search by meaning runs on your CPU.
|
|
64
64
|
- **A page to keep it tidy.** `mem ui` searches by words and by meaning, edits with undo, and shows
|
|
65
65
|
what changed week by week.
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
- **Memory in git.** One Markdown file per memory in a git repository you own, with where it came
|
|
38
38
|
from recorded by the hooks. Every change is a commit you can review or revert; add a private
|
|
39
39
|
remote to sync machines, or keep it all on one. Here is [an example](https://github.com/Ki-Seki/agent-memory-data-demo).
|
|
40
|
-
[Profiles](https://ki-seki.github.io/agent-memory/docs.html#profiles) keep
|
|
41
|
-
memories in repositories of their own.
|
|
40
|
+
[Profiles](https://ki-seki.github.io/agent-memory/docs.html#profiles) keep separate sets of
|
|
41
|
+
memories in repositories of their own, and a switch reaches running sessions too.
|
|
42
42
|
- **Nothing to run.** Two hooks and git. No service, no account; search by meaning runs on your CPU.
|
|
43
43
|
- **A page to keep it tidy.** `mem ui` searches by words and by meaning, edits with undo, and shows
|
|
44
44
|
what changed week by week.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Shared memory
|
|
2
2
|
|
|
3
|
-
You have a persistent memory that Claude Code and Codex share. Every memory is one Markdown file in a git repository. At the start of each session a hook injects a block titled "Shared memory index": it lists the memories relevant to the current folder and repository, and gives the memories directory, this session's staging directory and the path of the `mem` command. If that block is missing, the memory system is not active in this session; ignore this section.
|
|
3
|
+
You have a persistent memory that Claude Code and Codex share. Every memory is one Markdown file in a git repository. At the start of each session a hook injects a block titled "Shared memory index": it lists the memories relevant to the current folder and repository, and gives the memories directory, this session's staging directory and the path of the `mem` command. If the active profile changes while the session runs, a new index arrives with your next message and replaces the earlier one: from then on use only its memories and paths. If that block is missing, the memory system is not active in this session; ignore this section.
|
|
4
4
|
|
|
5
5
|
## Reading
|
|
6
6
|
|
|
@@ -249,25 +249,36 @@ def _access_note():
|
|
|
249
249
|
print(f"updated {' and '.join(tools)} so agents can reach every profile's folder")
|
|
250
250
|
|
|
251
251
|
|
|
252
|
+
SWITCH_NOTE = "they keep what they've already read; start a new session for a clean slate"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _switched(name):
|
|
256
|
+
print(f"sessions now use {name}; running ones switch with their next message")
|
|
257
|
+
if config.load().get("switch_note", True):
|
|
258
|
+
print(SWITCH_NOTE)
|
|
259
|
+
|
|
260
|
+
|
|
252
261
|
def cmd_profile(args):
|
|
253
262
|
if profiles.locked():
|
|
254
263
|
sys.exit("AGENT_MEMORY_DATA picks the data repository here, so profiles do not apply")
|
|
255
264
|
try:
|
|
256
265
|
if args.action == "use":
|
|
257
266
|
pid = profiles.use(args.profile)
|
|
258
|
-
|
|
267
|
+
_switched(profiles.listing()[0][pid]["name"])
|
|
259
268
|
elif args.action == "add":
|
|
260
269
|
pid, note = profiles.add(args.folder, remote=args.remote, switch=not args.keep)
|
|
261
270
|
found, active = profiles.listing()
|
|
262
271
|
print(note)
|
|
263
|
-
print(f"added {found[pid]['name']}"
|
|
272
|
+
print(f"added {found[pid]['name']}")
|
|
273
|
+
if active == pid:
|
|
274
|
+
_switched(found[pid]["name"])
|
|
264
275
|
_access_note()
|
|
265
276
|
elif args.action == "remove":
|
|
266
277
|
found, _ = profiles.listing()
|
|
267
278
|
pid = profiles.find(args.profile, found)
|
|
268
279
|
active = profiles.remove(pid)
|
|
269
280
|
print(f"removed {found[pid]['name']}; {profiles.short(found[pid]['data_dir'])} is left as it is")
|
|
270
|
-
|
|
281
|
+
_switched(profiles.listing()[0][active]["name"])
|
|
271
282
|
_access_note()
|
|
272
283
|
else:
|
|
273
284
|
for p in profiles.summary()["profiles"]:
|
|
@@ -387,7 +398,7 @@ def main(argv=None):
|
|
|
387
398
|
sp.set_defaults(fn=cmd_profile)
|
|
388
399
|
|
|
389
400
|
sp = sub.add_parser("hook", help=argparse.SUPPRESS)
|
|
390
|
-
sp.add_argument("event"
|
|
401
|
+
sp.add_argument("event") # any name: an event from a newer install is logged and ignored, never refused
|
|
391
402
|
sp.add_argument("--tool", required=True, choices=["claude-code", "codex"])
|
|
392
403
|
sp.set_defaults(fn=cmd_hook)
|
|
393
404
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Hook entry points shared by Claude Code and Codex.
|
|
2
|
+
|
|
3
|
+
Both tools send a JSON payload on stdin with at least `session_id` and `cwd`,
|
|
4
|
+
and accept `hookSpecificOutput.additionalContext` from SessionStart hooks.
|
|
5
|
+
A hook must never break the host session, so every failure is logged and
|
|
6
|
+
swallowed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import secrets
|
|
11
|
+
import sys
|
|
12
|
+
import time
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from . import capture, config, gitsync, profiles, select, store, workdir
|
|
17
|
+
from .context import detect
|
|
18
|
+
from .util import log, log_exc
|
|
19
|
+
|
|
20
|
+
PULL_INTERVAL = 60
|
|
21
|
+
SWITCHED = (
|
|
22
|
+
"The active memory profile changed while this session was running. This index replaces the one shown "
|
|
23
|
+
"earlier: from now on use only its memories directory, its staging directory and the memories it lists, "
|
|
24
|
+
"and set aside memories you read under the earlier one.\n\n"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _payload():
|
|
29
|
+
try:
|
|
30
|
+
data = sys.stdin.read()
|
|
31
|
+
return json.loads(data) if data.strip() else {}
|
|
32
|
+
except (OSError, ValueError):
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _session_id(payload):
|
|
37
|
+
return str(payload.get("session_id") or f"nosession-{secrets.token_hex(4)}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def session_start(payload, tool, data_dir):
|
|
41
|
+
sid = _session_id(payload)
|
|
42
|
+
ctx = detect(payload.get("cwd"))
|
|
43
|
+
sdir = capture.session_dir(data_dir, tool, sid)
|
|
44
|
+
sdir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
capture.write_session(
|
|
46
|
+
sdir,
|
|
47
|
+
{
|
|
48
|
+
"tool": tool,
|
|
49
|
+
"session_id": sid,
|
|
50
|
+
"session_title": payload.get("session_title"),
|
|
51
|
+
"model": payload.get("model"),
|
|
52
|
+
"transcript_path": payload.get("transcript_path"),
|
|
53
|
+
"ctx": ctx,
|
|
54
|
+
"shown_at": time.time(),
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
recovered = capture.recover_orphans(data_dir, keep=sdir)
|
|
59
|
+
stamp = Path(data_dir) / ".cache" / "last-pull"
|
|
60
|
+
if gitsync.has_remote(data_dir):
|
|
61
|
+
last = stamp.stat().st_mtime if stamp.exists() else 0
|
|
62
|
+
if recovered or time.time() - last > PULL_INTERVAL:
|
|
63
|
+
stamp.parent.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
stamp.touch()
|
|
65
|
+
gitsync.spawn_sync(data_dir, push=recovered)
|
|
66
|
+
|
|
67
|
+
paths = {
|
|
68
|
+
"memories": str(store.memories_dir(data_dir)),
|
|
69
|
+
"staging": str(sdir),
|
|
70
|
+
"mem": str(config.BIN),
|
|
71
|
+
}
|
|
72
|
+
return select.render(
|
|
73
|
+
store.load_all(data_dir),
|
|
74
|
+
ctx,
|
|
75
|
+
paths,
|
|
76
|
+
throwaway=workdir.is_throwaway(ctx.get("cwd")),
|
|
77
|
+
profile=profiles.active_name(),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _shown(sdir):
|
|
82
|
+
"""When this staging folder's profile last gave the session its index; 0 when it never did."""
|
|
83
|
+
info = capture.read_session(sdir)
|
|
84
|
+
if isinstance(info.get("shown_at"), (int, float)):
|
|
85
|
+
return info["shown_at"]
|
|
86
|
+
try: # sessions that started before shown_at existed got their index when the folder was made
|
|
87
|
+
return datetime.fromisoformat(info["started_at"]).timestamp()
|
|
88
|
+
except (KeyError, TypeError, ValueError):
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def prompt(payload, tool, data_dir):
|
|
93
|
+
"""The active profile's index when the session last got another profile's; None when nothing changed.
|
|
94
|
+
Running sessions follow the active profile, so a switch reaches them with their next message."""
|
|
95
|
+
sid = payload.get("session_id")
|
|
96
|
+
if not sid:
|
|
97
|
+
return None
|
|
98
|
+
seen = [(_shown(sdir), d) for d in profiles.dirs() if (sdir := capture.session_dir(d, tool, str(sid))).is_dir()]
|
|
99
|
+
if not seen or max(seen, key=lambda s: s[0])[1] == data_dir:
|
|
100
|
+
return None # no index yet (SessionStart gives it), or this profile's is the latest
|
|
101
|
+
return SWITCHED + session_start(payload, tool, data_dir)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def flush(payload, tool, final):
|
|
105
|
+
"""Commit what the session staged, into each profile it staged in: a switch can leave some in the one
|
|
106
|
+
before, written while the session still had that profile's index."""
|
|
107
|
+
sid = _session_id(payload)
|
|
108
|
+
for data_dir in profiles.dirs():
|
|
109
|
+
sdir = capture.session_dir(data_dir, tool, sid)
|
|
110
|
+
if not sdir.is_dir() or not (data_dir / ".git").exists():
|
|
111
|
+
continue
|
|
112
|
+
if payload.get("transcript_path"):
|
|
113
|
+
capture.write_session(sdir, {"transcript_path": payload["transcript_path"]})
|
|
114
|
+
committed = capture.flush(data_dir, sdir, final=final, lock_timeout=1.0 if final else 5.0)
|
|
115
|
+
if committed and gitsync.has_remote(data_dir):
|
|
116
|
+
gitsync.spawn_sync(data_dir, push=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _context(event, text):
|
|
120
|
+
return json.dumps({"hookSpecificOutput": {"hookEventName": event, "additionalContext": text}}, ensure_ascii=False)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main(event, tool):
|
|
124
|
+
payload = _payload()
|
|
125
|
+
data_dir = config.data_dir() # the active profile's: running sessions follow it too
|
|
126
|
+
if not data_dir or not (data_dir / ".git").exists():
|
|
127
|
+
return 0
|
|
128
|
+
try:
|
|
129
|
+
if event == "session-start":
|
|
130
|
+
print(_context("SessionStart", session_start(payload, tool, data_dir)))
|
|
131
|
+
elif event == "prompt":
|
|
132
|
+
text = prompt(payload, tool, data_dir)
|
|
133
|
+
if text:
|
|
134
|
+
print(_context("UserPromptSubmit", text))
|
|
135
|
+
elif event in ("stop", "session-end"):
|
|
136
|
+
flush(payload, tool, final=event == "session-end")
|
|
137
|
+
else:
|
|
138
|
+
log(data_dir, f"unknown hook event {event!r}")
|
|
139
|
+
except Exception:
|
|
140
|
+
log_exc(data_dir, f"hook {event} ({tool})")
|
|
141
|
+
return 0
|
|
@@ -35,6 +35,7 @@ PATH_LINE = 'export PATH="$HOME/.local/bin:$PATH"'
|
|
|
35
35
|
|
|
36
36
|
HOOK_EVENTS = [
|
|
37
37
|
("SessionStart", "session-start", 15),
|
|
38
|
+
("UserPromptSubmit", "prompt", 10),
|
|
38
39
|
("Stop", "stop", 15),
|
|
39
40
|
("SessionEnd", "session-end", 3),
|
|
40
41
|
] # Codex clamps SessionEnd to 3s
|
|
@@ -44,7 +45,9 @@ HOOK_EVENTS = [
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def hook_command(event, tool):
|
|
47
|
-
|
|
48
|
+
command = " ".join(shlex.quote(a) for a in config.mem_command()) + f" hook {event} --tool {tool}"
|
|
49
|
+
# a mem from before this event exits 2 on it, and exit 2 from UserPromptSubmit blocks the prompt
|
|
50
|
+
return command + " || true" if event == "prompt" else command
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
def _is_ours(group):
|
|
@@ -318,6 +321,7 @@ def _codex_plan(plan, data_dirs, state):
|
|
|
318
321
|
handler = {"type": "command", "command": hook_command(name, "codex"), "timeout": timeout}
|
|
319
322
|
if event == "SessionStart":
|
|
320
323
|
handler["statusMessage"] = "Loading shared memory"
|
|
324
|
+
if event in ("SessionStart", "UserPromptSubmit"):
|
|
321
325
|
handler["additionalContextLimit"] = 0 # keep the index inline, never spill to disk
|
|
322
326
|
groups.append({"hooks": [handler]})
|
|
323
327
|
hooks[event] = groups
|
|
@@ -173,6 +173,7 @@ class UiApp:
|
|
|
173
173
|
"remoteUrl": re.sub(r"(://)[^/@]+@", r"\1", url) or None, # without any credentials in it
|
|
174
174
|
"sync": self.sync_status(),
|
|
175
175
|
"keywordStopwords": config.load().get("keyword_stopwords", []),
|
|
176
|
+
"switchNote": config.load().get("switch_note", True),
|
|
176
177
|
"model": {
|
|
177
178
|
"name": ui.MODEL["name"],
|
|
178
179
|
"ready": ui.model_ready(),
|
|
@@ -185,7 +186,7 @@ class UiApp:
|
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
def set_settings(self, p):
|
|
188
|
-
"""Change keyword_stopwords, check_updates, or
|
|
189
|
+
"""Change keyword_stopwords, check_updates, switch_note, or several."""
|
|
189
190
|
cfg = config.load()
|
|
190
191
|
if "keyword_stopwords" in p:
|
|
191
192
|
words = p["keyword_stopwords"]
|
|
@@ -196,6 +197,10 @@ class UiApp:
|
|
|
196
197
|
if not isinstance(p["check_updates"], bool):
|
|
197
198
|
raise ApiError(400, "check_updates must be true or false")
|
|
198
199
|
cfg["check_updates"] = p["check_updates"]
|
|
200
|
+
if "switch_note" in p:
|
|
201
|
+
if not isinstance(p["switch_note"], bool):
|
|
202
|
+
raise ApiError(400, "switch_note must be true or false")
|
|
203
|
+
cfg["switch_note"] = p["switch_note"]
|
|
199
204
|
config.save(cfg)
|
|
200
205
|
if "keyword_stopwords" in p:
|
|
201
206
|
self.refresh() # keywords are chosen when the state is built
|
|
@@ -112,8 +112,6 @@ body.resizing { cursor: col-resize; user-select: none; }
|
|
|
112
112
|
.dot.t-project { background: var(--t-project); } .dot.t-user { background: var(--t-user); }
|
|
113
113
|
.dot.ok { background: var(--ok); } .dot.warn { background: var(--warn); } .dot.off { background: var(--ink-3); }
|
|
114
114
|
.type-label { display: inline-flex; align-items: center; gap: 6px; }
|
|
115
|
-
.status { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ink-2); }
|
|
116
|
-
.status.warn-text { color: var(--warn); }
|
|
117
115
|
|
|
118
116
|
.seg { display: inline-flex; padding: 2px; border-radius: 9px; background: var(--chip); }
|
|
119
117
|
.seg > button { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border-radius: 7px; font-size: 13px; color: var(--ink-2); transition: color .15s; }
|
|
@@ -445,135 +443,159 @@ table.nums td { padding: 4px 8px; border-top: 1px solid var(--line-soft); text-a
|
|
|
445
443
|
table.nums th:first-child, table.nums td:first-child { padding-left: 0; text-align: left; color: var(--ink); }
|
|
446
444
|
table.nums td:last-child { color: var(--ink); }
|
|
447
445
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
.setting { display: grid; grid-template-columns: minmax(0, 1fr); align-items: center; gap: 12px; padding: 14px 0; border-top: 1px solid var(--line-soft); }
|
|
457
|
-
.group h2 + .setting, .group .setting:first-of-type { border-top: 0; }
|
|
458
|
-
@media (min-width: 640px) {
|
|
459
|
-
.setting:not(.below) { grid-template-columns: minmax(220px, 1fr) minmax(0, auto); gap: 24px; }
|
|
460
|
-
.setting:not(.below) .setting-control { max-width: 440px; justify-self: end; }
|
|
446
|
+
/* settings: the open profile as a hero, with its picture blurred into a glow of its own colours behind it; a few
|
|
447
|
+
quiet rows of preferences; the installation as one line. Controls here are pills. */
|
|
448
|
+
.settings { --glow: .62; --glow-w: 600px; --glow-h: 400px; --glow-top: -150px; --glow-sat: 1.15;
|
|
449
|
+
--pic-shadow: drop-shadow(0 10px 22px rgb(60 50 120 / .16)) drop-shadow(0 1px 2px rgb(0 0 0 / .08));
|
|
450
|
+
width: 100%; max-width: 680px; margin: 16px auto 0; }
|
|
451
|
+
:root[data-theme="dark"] .settings { --glow: .3; --glow-w: 380px; --glow-h: 280px; --glow-top: -40px; --glow-sat: 2.8; --pic-shadow: drop-shadow(0 10px 24px rgb(0 0 0 / .45)); }
|
|
452
|
+
@media (prefers-color-scheme: dark) {
|
|
453
|
+
:root:not([data-theme]) .settings { --glow: .3; --glow-w: 380px; --glow-h: 280px; --glow-top: -40px; --glow-sat: 2.8; --pic-shadow: drop-shadow(0 10px 24px rgb(0 0 0 / .45)); }
|
|
461
454
|
}
|
|
462
|
-
.
|
|
463
|
-
.
|
|
464
|
-
.
|
|
465
|
-
.
|
|
466
|
-
.
|
|
467
|
-
.
|
|
468
|
-
.
|
|
469
|
-
.
|
|
470
|
-
|
|
471
|
-
.
|
|
472
|
-
|
|
473
|
-
.
|
|
474
|
-
|
|
475
|
-
.
|
|
476
|
-
.
|
|
477
|
-
.
|
|
455
|
+
.settings .btn { height: 32px; padding: 0 14px; border-radius: 999px; font-weight: 500; }
|
|
456
|
+
.settings .btn .i { width: 15px; height: 15px; }
|
|
457
|
+
.settings .btn.ghost { padding: 0 10px; }
|
|
458
|
+
.settings .input { height: 36px; font-size: 13px; }
|
|
459
|
+
.settings .seg { padding: 3px; border-radius: 999px; }
|
|
460
|
+
.settings .seg > button { padding: 0 13px; border-radius: 999px; }
|
|
461
|
+
.settings .switch { width: 38px; height: 22px; }
|
|
462
|
+
.settings .switch > span { width: 18px; height: 18px; }
|
|
463
|
+
|
|
464
|
+
.hero { position: relative; isolation: isolate; overflow: hidden; border: 1px solid var(--line-soft); border-radius: calc(var(--r-card) * 1.5);
|
|
465
|
+
background: var(--surface); box-shadow: 0 1px 2px rgb(0 0 0 / .03), 0 8px 32px -12px rgb(40 30 90 / .08); }
|
|
466
|
+
.hero-glow { position: absolute; z-index: -1; top: var(--glow-top); left: 50%; width: var(--glow-w); height: var(--glow-h); translate: -50% 0; border-radius: 50%;
|
|
467
|
+
overflow: hidden; filter: blur(60px) saturate(var(--glow-sat)); opacity: var(--glow); pointer-events: none; }
|
|
468
|
+
.hero-glow svg { display: block; width: 100%; height: 100%; }
|
|
469
|
+
.hero-main { display: flex; flex-direction: column; align-items: center; padding: 48px 32px 36px; text-align: center; }
|
|
470
|
+
.pavatar.hero-pic { width: 88px; height: 88px; filter: var(--pic-shadow); }
|
|
471
|
+
.hero-name { margin-top: 20px; font-size: 34px; font-weight: 650; line-height: 1.15; letter-spacing: -0.024em; }
|
|
472
|
+
.hero-sub { margin-top: 6px; font-size: 13.5px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
|
|
473
|
+
.hero-sub .sep { margin: 0 6px; color: var(--ink-3); }
|
|
474
|
+
.hero-meta { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 4px 20px; margin-top: 20px; }
|
|
475
|
+
.hero-meta-item { position: relative; display: inline-flex; min-width: 0; max-width: 100%; align-items: center; gap: 7px; font-family: var(--mono); font-size: 12.5px; color: var(--ink-2); }
|
|
476
|
+
.hero-meta-item > .i { width: 14px; height: 14px; color: var(--ink-3); }
|
|
477
|
+
.hero-meta-item .v { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
478
|
+
.hero-meta-item .k { font-family: var(--sans); color: var(--ink-3); }
|
|
479
|
+
button.hero-meta-item { border-radius: 6px; transition: color .15s; }
|
|
480
|
+
button.hero-meta-item:hover { color: var(--ink); }
|
|
481
|
+
.hero-meta-item > .copy-hint { position: absolute; right: -20px; width: 13px; height: 13px; opacity: 0; transition: opacity .15s; }
|
|
482
|
+
button.hero-meta-item:hover .copy-hint, button.hero-meta-item:focus-visible .copy-hint { opacity: 1; }
|
|
483
|
+
.hero-link { flex-shrink: 0; margin-left: -2px; padding: 2px 6px; border-radius: 6px; font-family: var(--sans); font-size: 12.5px; font-weight: 500; color: var(--accent); }
|
|
484
|
+
.hero-link:hover { background: var(--accent-soft); }
|
|
485
|
+
.hero-note { margin-top: 12px; font-size: 12.5px; color: var(--ok); }
|
|
486
|
+
.hero-note.bad { color: var(--bad); }
|
|
487
|
+
.remote-edit { width: 100%; max-width: 520px; margin-top: 16px; padding: 12px; border-radius: var(--r-card); background: color-mix(in srgb, var(--surface) 70%, transparent);
|
|
488
|
+
box-shadow: var(--ring); text-align: left; animation: rise .15s ease-out; }
|
|
489
|
+
.remote-edit label { display: block; margin: 0 0 6px 2px; font-size: 12px; font-weight: 600; color: var(--ink-3); }
|
|
490
|
+
.remote-edit-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
|
|
491
|
+
.sync-pill { display: inline-flex; max-width: 100%; height: 38px; align-items: center; margin-top: 24px; padding: 0 4px 0 14px; border-radius: 999px;
|
|
492
|
+
background: color-mix(in srgb, var(--surface) 78%, transparent); box-shadow: var(--ring); backdrop-filter: blur(6px); font-size: 13px; white-space: nowrap; }
|
|
493
|
+
.sync-pill.solo { padding-right: 14px; }
|
|
494
|
+
.sync-state { display: inline-flex; min-width: 0; align-items: center; gap: 8px; overflow: hidden; color: var(--ink-3); text-overflow: ellipsis; }
|
|
495
|
+
.sync-state b { font-weight: 500; color: var(--ink); }
|
|
496
|
+
.sync-sep { width: 1px; height: 18px; flex-shrink: 0; margin: 0 4px 0 12px; background: var(--line); }
|
|
497
|
+
.sync-btn { display: inline-flex; flex-shrink: 0; height: 30px; align-items: center; gap: 6px; padding: 0 12px; border-radius: 999px; font-weight: 500; color: var(--ink-2); transition: background-color .15s, color .15s; }
|
|
498
|
+
.sync-btn:hover { background: var(--hover); color: var(--ink); }
|
|
499
|
+
.sync-btn .i { width: 14px; height: 14px; }
|
|
500
|
+
|
|
501
|
+
.dock { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 12px 14px 12px 20px; border-top: 1px solid var(--line-soft);
|
|
502
|
+
background: color-mix(in srgb, var(--panel) 72%, transparent); }
|
|
503
|
+
.dock-label { margin-right: 4px; font-size: 12.5px; color: var(--ink-3); }
|
|
504
|
+
.pchip { display: inline-flex; height: 34px; align-items: center; gap: 8px; padding: 0 13px 0 5px; border-radius: 999px; background: var(--surface); box-shadow: var(--ring);
|
|
505
|
+
font-size: 13.5px; font-weight: 500; transition: background-color .15s; }
|
|
506
|
+
.pchip:hover { background: var(--hover); }
|
|
507
|
+
.pchip:disabled { opacity: .6; }
|
|
508
|
+
.pchip[aria-pressed="true"] { box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 4px var(--accent-soft); cursor: default; }
|
|
509
|
+
.pchip[aria-pressed="true"]:hover { background: var(--surface); }
|
|
510
|
+
.pchip .pavatar { width: 24px; height: 24px; }
|
|
511
|
+
.pchip .n { font-size: 12px; font-weight: 400; color: var(--ink-3); font-variant-numeric: tabular-nums; }
|
|
512
|
+
.pchip.add { background: transparent; box-shadow: none; font-weight: 400; color: var(--ink-2); }
|
|
513
|
+
.pchip.add:hover { background: var(--hover); color: var(--ink); }
|
|
514
|
+
.pchip.add .plus { display: grid; width: 24px; height: 24px; place-items: center; border-radius: 7px; background: var(--chip); color: var(--ink-2); }
|
|
515
|
+
.pchip.add .plus .i { width: 14px; height: 14px; }
|
|
516
|
+
.dock-remove { height: 30px; padding: 0 10px; border-radius: 999px; font-size: 12.5px; color: var(--ink-3); transition: color .15s, background-color .15s; }
|
|
517
|
+
.dock-remove:hover { background: color-mix(in srgb, var(--bad) 9%, transparent); color: var(--bad); }
|
|
518
|
+
.pform { padding: 20px; border-top: 1px solid var(--line-soft); background: color-mix(in srgb, var(--panel) 72%, transparent); animation: rise .15s ease-out; }
|
|
519
|
+
.pform-top { display: flex; align-items: center; gap: 12px; }
|
|
520
|
+
.pform-top .pavatar { width: 44px; height: 44px; filter: drop-shadow(0 4px 10px rgb(60 50 120 / .14)); }
|
|
521
|
+
.pform-name { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
|
|
522
|
+
.pform-note { font-size: 12.5px; color: var(--ink-3); }
|
|
523
|
+
.pform-fields { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 12px; margin-top: 16px; }
|
|
524
|
+
.pfield { display: grid; gap: 6px; }
|
|
525
|
+
.pfield span { font-size: 12px; font-weight: 600; color: var(--ink-3); }
|
|
526
|
+
.pfield span i { font-style: normal; font-weight: 400; }
|
|
527
|
+
.pform-error { margin-top: 12px; font-size: 13px; color: var(--bad); }
|
|
528
|
+
.pform-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
|
529
|
+
|
|
530
|
+
.prefs { margin-top: 40px; }
|
|
531
|
+
.pref { display: flex; min-height: 64px; align-items: center; gap: 24px; padding: 14px 4px; }
|
|
532
|
+
.pref + .pref, .pref + .callout, .callout + .pref { border-top: 1px solid var(--line-soft); }
|
|
533
|
+
.prefs .callout { margin: 0; }
|
|
534
|
+
.pref-text { display: grid; flex: 1 0 auto; gap: 1px; }
|
|
535
|
+
.pref-label { font-size: 14px; font-weight: 500; }
|
|
536
|
+
.pref-sub { font-size: 12.5px; color: var(--ink-3); }
|
|
537
|
+
.pref-ctl { display: flex; flex: 0 1 auto; min-width: 0; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; }
|
|
538
|
+
.pref-ctl > .btn.ghost { margin-right: -10px; }
|
|
539
|
+
.tag { display: inline-flex; height: 28px; align-items: center; gap: 2px; padding: 0 3px 0 11px; border-radius: 999px; background: var(--chip); font-size: 13px; }
|
|
540
|
+
.tag button { display: grid; width: 22px; height: 22px; place-items: center; border-radius: 50%; color: var(--ink-3); }
|
|
541
|
+
.tag button:hover { background: var(--hover); color: var(--ink); }
|
|
478
542
|
.tag .i { width: 12px; height: 12px; }
|
|
479
|
-
.
|
|
480
|
-
.
|
|
481
|
-
.
|
|
482
|
-
.
|
|
483
|
-
.
|
|
484
|
-
.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
.
|
|
489
|
-
.
|
|
490
|
-
.
|
|
491
|
-
.
|
|
492
|
-
.
|
|
493
|
-
.
|
|
494
|
-
.
|
|
495
|
-
.
|
|
496
|
-
.
|
|
497
|
-
.
|
|
498
|
-
.
|
|
499
|
-
.
|
|
500
|
-
.
|
|
501
|
-
.
|
|
502
|
-
.
|
|
503
|
-
.
|
|
504
|
-
.
|
|
505
|
-
.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
.
|
|
509
|
-
.remote-title, .remote-label { display: block; font-size: 14px; font-weight: 500; }
|
|
510
|
-
.remote-url { display: block; margin-top: 2px; font-size: 12.5px; color: var(--ink-2); overflow-wrap: anywhere; }
|
|
511
|
-
.remote-help { margin-top: 3px; font-size: 12.5px; color: var(--ink-3); }
|
|
512
|
-
.remote-form { display: flex; gap: 8px; margin-top: 8px; }
|
|
513
|
-
.remote-form .input { height: 32px; padding: 0 10px; font-size: 12.5px; }
|
|
514
|
-
.remote-note { margin-top: 8px; font-size: 12.5px; color: var(--ok); }
|
|
515
|
-
.remote-note.bad { color: var(--bad); }
|
|
516
|
-
.remote-row .inline-gap { gap: 4px; }
|
|
517
|
-
.health-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px; padding: 14px 0; }
|
|
518
|
-
.health-icon { display: grid; width: 32px; height: 32px; flex-shrink: 0; place-items: center; border-radius: 50%; background: var(--chip); color: var(--ink-3); }
|
|
519
|
-
.health-icon.ok { background: color-mix(in srgb, var(--ok) 13%, transparent); color: var(--ok); }
|
|
520
|
-
.health-icon.warn { background: color-mix(in srgb, var(--warn) 13%, transparent); color: var(--warn); }
|
|
521
|
-
.health-icon .i { width: 16px; height: 16px; }
|
|
522
|
-
.health .checks { border-top: 1px solid var(--line-soft); }
|
|
523
|
-
.health .setting-help code { font-family: var(--mono); }
|
|
524
|
-
@media (max-width: 639px) {
|
|
525
|
-
.repo-card-head, .remote-row { padding-inline: 16px; }
|
|
526
|
-
.repo-card-head .grow { flex: 1 1 calc(100% - 56px); } /* name and path on their own line; status and Sync under them */
|
|
527
|
-
.repo-facts > div { padding: 10px 16px; }
|
|
528
|
-
.remote-row { flex-wrap: wrap; }
|
|
529
|
-
.remote-form { flex-wrap: wrap; } .remote-form .input { flex-basis: 100%; }
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/* settings: profiles. A profile's picture (avatar.js) is drawn from its name; CSS gives it a size. */
|
|
533
|
-
.pavatar { display: block; width: 24px; height: 24px; flex-shrink: 0; }
|
|
543
|
+
.tag-add { display: inline-flex; height: 28px; align-items: center; gap: 4px; padding: 0 10px 0 8px; border-radius: 999px; box-shadow: inset 0 0 0 1px var(--line); color: var(--ink-3); cursor: text; }
|
|
544
|
+
.tag-add:focus-within { box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 3px var(--accent-soft); }
|
|
545
|
+
.tag-add .i { width: 13px; height: 13px; }
|
|
546
|
+
.tag-add input { width: 84px; font-size: 13px; outline: none; color: var(--ink); }
|
|
547
|
+
.tag-add input::placeholder { color: var(--ink-3); }
|
|
548
|
+
.code-copy { display: inline-flex; max-width: 100%; height: 28px; align-items: center; gap: 8px; padding: 0 10px; border-radius: 999px; background: var(--chip); font-family: var(--mono); font-size: 12px; color: var(--ink-2); overflow-wrap: anywhere; }
|
|
549
|
+
.code-copy:hover { color: var(--ink); }
|
|
550
|
+
.code-copy .i { width: 13px; height: 13px; }
|
|
551
|
+
|
|
552
|
+
.doctor { display: flex; flex-direction: column; align-items: center; margin-top: 40px; }
|
|
553
|
+
.doctor-line { display: inline-flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 2px; font-size: 12.5px; color: var(--ink-3); }
|
|
554
|
+
.doctor-line .sep { margin: 0 4px; }
|
|
555
|
+
.doctor-line button { display: inline-flex; height: 28px; align-items: center; gap: 7px; padding: 0 8px; border-radius: 999px; transition: background-color .15s, color .15s; }
|
|
556
|
+
.doctor-line button:hover { background: var(--hover); color: var(--ink); }
|
|
557
|
+
.doctor-sum { color: var(--ink-2); }
|
|
558
|
+
.doctor-sum.warn { color: var(--warn); }
|
|
559
|
+
.doctor-line .i { width: 13px; height: 13px; }
|
|
560
|
+
.doctor-sum .chev { transition: transform .15s; }
|
|
561
|
+
.doctor-sum .chev.turned { transform: rotate(180deg); }
|
|
562
|
+
.checks { width: 100%; margin-top: 12px; padding: 16px 20px 18px; border-radius: var(--r-card); background: var(--panel); animation: rise .15s ease-out; }
|
|
563
|
+
.checks-note { margin-bottom: 10px; font-size: 12px; color: var(--ink-3); }
|
|
564
|
+
.checks-note code { font-size: 11.5px; color: var(--ink-2); }
|
|
565
|
+
.checks ul { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px 24px; }
|
|
566
|
+
.checks li { display: flex; min-width: 0; align-items: flex-start; gap: 8px; font-size: 12.5px; color: var(--ink-2); overflow-wrap: anywhere; }
|
|
567
|
+
.checks li .i { width: 14px; height: 14px; margin-top: 2px; color: var(--ok); }
|
|
568
|
+
.checks li.bad { color: var(--ink); }
|
|
569
|
+
.checks li.bad .i { color: var(--warn); }
|
|
570
|
+
|
|
571
|
+
/* profiles: a picture (avatar.js) is drawn from the name; CSS gives it a size */
|
|
572
|
+
.pavatar { display: block; width: 28px; height: 28px; flex-shrink: 0; }
|
|
534
573
|
.svg-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
|
|
535
|
-
.pavatar.sm { width: 20px; height: 20px; }
|
|
536
|
-
.pavatar.lg { width: 40px; height: 40px; }
|
|
537
|
-
.repo-stack { display: grid; gap: 10px; min-width: 0; }
|
|
538
|
-
.profiles { display: grid; gap: 8px; }
|
|
539
|
-
.ptabs { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
540
|
-
.ptab { display: inline-flex; max-width: 100%; height: 36px; align-items: center; gap: 8px; padding: 0 12px 0 6px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); font-size: 13.5px; font-weight: 500; color: var(--ink-2); transition: border-color .15s, background-color .15s, color .15s, box-shadow .15s; }
|
|
541
|
-
.ptab:hover { background: var(--hover); color: var(--ink); }
|
|
542
|
-
.ptab:disabled { opacity: .6; }
|
|
543
|
-
.ptab[aria-checked="true"] { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); background: var(--surface); color: var(--ink); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
544
|
-
.ptab-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
545
|
-
.ptab-count { font-size: 12px; font-weight: 400; color: var(--ink-3); font-variant-numeric: tabular-nums; }
|
|
546
|
-
.ptab.add { gap: 6px; padding: 0 12px; border-style: dashed; background: transparent; color: var(--ink-3); }
|
|
547
|
-
.ptab.add .i { width: 15px; height: 15px; }
|
|
548
|
-
.ptab.add:hover { color: var(--ink); }
|
|
549
|
-
.ptab.add[aria-pressed="true"] { border-style: solid; border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); background: var(--surface); color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
550
|
-
.profiles-help { font-size: 12.5px; color: var(--ink-3); }
|
|
551
|
-
.profiles-help b { font-weight: 600; color: var(--ink-2); }
|
|
552
|
-
.repo-title { display: flex; align-items: center; gap: 2px; }
|
|
553
|
-
.repo-title .btn.icon { width: 26px; height: 26px; color: var(--ink-3); }
|
|
554
|
-
.repo-title .btn.icon .i { width: 14px; height: 14px; }
|
|
555
|
-
.pform { display: grid; gap: 16px; padding: 20px; }
|
|
556
|
-
.pform-top { display: flex; align-items: center; gap: 14px; }
|
|
557
|
-
.pform-top h2 { font-size: 16px; font-weight: 640; letter-spacing: -0.01em; }
|
|
558
|
-
.pform-sub { margin-top: 2px; font-size: 13px; color: var(--ink-3); }
|
|
559
|
-
.pfield { display: grid; }
|
|
560
|
-
.pfield .field-label { justify-content: flex-start; gap: 4px; }
|
|
561
|
-
.pfield .field-label .faint { font-weight: 400; }
|
|
562
|
-
.pfield .input { height: 36px; }
|
|
563
|
-
.field-help { margin-top: 6px; font-size: 12.5px; color: var(--ink-3); }
|
|
564
|
-
.pform-error { font-size: 13px; color: var(--bad); }
|
|
565
|
-
.pform-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
566
574
|
.rail-profile { position: relative; display: grid; width: 40px; height: 40px; place-items: center; border-radius: 9px; transition: background-color .15s, box-shadow .15s; }
|
|
567
|
-
.rail-profile:hover
|
|
575
|
+
.rail-profile:hover { background: var(--hover); }
|
|
568
576
|
.rail-profile[aria-current="page"] { background: var(--surface); box-shadow: 0 0 0 1px var(--line); }
|
|
569
|
-
.rail-profile:has(+ .menu:popover-open)::after { display: none; } /* the menu says it already */
|
|
570
577
|
.rail-profile .pavatar { width: 26px; height: 26px; }
|
|
571
|
-
.menu-item.tall { height: 36px; }
|
|
572
|
-
.menu-sep { height: 1px; margin: 4px -4px; background: var(--line); }
|
|
573
|
-
.menu-note.pad { margin: 0; padding: 6px 8px 4px; }
|
|
574
|
-
.tick-space { width: 14px; flex-shrink: 0; }
|
|
575
578
|
@media (max-width: 639px) {
|
|
576
|
-
.
|
|
579
|
+
.settings { margin-top: 4px; }
|
|
580
|
+
.hero-main { padding: 40px 20px 28px; }
|
|
581
|
+
.pavatar.hero-pic { width: 80px; height: 80px; }
|
|
582
|
+
.hero-name { margin-top: 18px; font-size: 28px; }
|
|
583
|
+
.hero-meta { flex-direction: column; gap: 6px; }
|
|
584
|
+
.hero-glow { width: min(var(--glow-w), 460px); }
|
|
585
|
+
.sync-pill .wide-only { display: none; }
|
|
586
|
+
.dock { padding: 12px 12px 12px 16px; }
|
|
587
|
+
.dock-label { flex: 1 0 50%; margin: 0; } /* the label and Remove share the first line, the chips the next */
|
|
588
|
+
.dock-remove { order: 1; margin-right: -6px; }
|
|
589
|
+
.dock .grow { display: none; }
|
|
590
|
+
.dock .pchip { order: 2; }
|
|
591
|
+
.pform { padding: 18px 16px; }
|
|
592
|
+
.pform-fields { grid-template-columns: minmax(0, 1fr); }
|
|
593
|
+
.prefs { margin-top: 28px; }
|
|
594
|
+
.pref { flex-wrap: wrap; gap: 10px 16px; min-height: 60px; padding-inline: 2px; }
|
|
595
|
+
.pref-text { flex: 1 1 auto; }
|
|
596
|
+
.pref.wrap .pref-ctl { flex-basis: 100%; justify-content: flex-start; }
|
|
597
|
+
.checks { padding: 14px 16px; }
|
|
598
|
+
.checks ul { grid-template-columns: minmax(0, 1fr); }
|
|
577
599
|
.rail-profile[data-tip]::after { display: none; }
|
|
578
600
|
}
|
|
579
601
|
|
|
@@ -595,6 +617,19 @@ table.nums td:last-child { color: var(--ink); }
|
|
|
595
617
|
.dialog::backdrop { background: rgb(0 0 0 / .3); }
|
|
596
618
|
.dialog-title { font-size: 15px; font-weight: 600; }
|
|
597
619
|
.dialog-body { margin-top: 8px; font-size: 13.5px; white-space: pre-line; color: var(--ink-2); }
|
|
620
|
+
.sline { display: grid; grid-template-columns: auto auto; justify-content: start; margin-top: 14px; font-size: 12px; color: var(--ink-3); }
|
|
621
|
+
.sline > span { padding-right: 14px; }
|
|
622
|
+
.sline > .next:not(.sline-name) { padding-left: 14px; border-left: 1.5px dashed var(--ink-3); }
|
|
623
|
+
.sline > .sline-name.next { padding-left: 15.5px; }
|
|
624
|
+
.sline > .sline-when { padding-bottom: 4px; }
|
|
625
|
+
.sline-msgs { display: flex; align-items: center; gap: 5px; height: 22px; }
|
|
626
|
+
.sline-msgs i { width: 20px; height: 11px; border-radius: 6px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--line); }
|
|
627
|
+
.sline-msgs i svg { display: block; width: 100%; height: 100%; }
|
|
628
|
+
.sline-msgs b { font-weight: 400; margin-left: 2px; }
|
|
629
|
+
.sline-name { display: flex; align-items: center; gap: 6px; margin-top: 6px; color: var(--ink-2); }
|
|
630
|
+
.sline-name .pavatar { width: 16px; height: 16px; }
|
|
631
|
+
.dialog-check { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding: 0; border: 0; background: none; color: var(--ink-2); font: inherit; font-size: 13px; cursor: pointer; }
|
|
632
|
+
.dialog-check[aria-checked="true"] .check { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }
|
|
598
633
|
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
|
|
599
634
|
.dialog.wide { width: min(1040px, calc(100vw - 32px)); max-height: min(760px, calc(100vh - 48px)); padding: 0; }
|
|
600
635
|
.dialog.wide[open] { display: flex; flex-direction: column; }
|