reqdeck 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. reqdeck/__init__.py +3 -0
  2. reqdeck/__main__.py +7 -0
  3. reqdeck/cli.py +706 -0
  4. reqdeck/config.py +185 -0
  5. reqdeck/editor.py +51 -0
  6. reqdeck/init.py +317 -0
  7. reqdeck/markers.py +65 -0
  8. reqdeck/model.py +393 -0
  9. reqdeck/ops.py +124 -0
  10. reqdeck/questions.py +62 -0
  11. reqdeck/render.py +140 -0
  12. reqdeck/skills/reqdeck-cli/SKILL.md +166 -0
  13. reqdeck/skills/reqdeck-method/SKILL.md +166 -0
  14. reqdeck/skills/reqdeck-method/references/markers.md +92 -0
  15. reqdeck/skills/reqdeck-method/references/writing-requirements.md +125 -0
  16. reqdeck/store.py +186 -0
  17. reqdeck/templates/OPEN-QUESTIONS.md +23 -0
  18. reqdeck/themes/README.md +31 -0
  19. reqdeck/themes/__init__.py +73 -0
  20. reqdeck/themes/atom-one-dark.toml +27 -0
  21. reqdeck/themes/atom-one-light.toml +27 -0
  22. reqdeck/themes/catppuccin-frappe.toml +27 -0
  23. reqdeck/themes/catppuccin-latte.toml +27 -0
  24. reqdeck/themes/catppuccin-macchiato.toml +27 -0
  25. reqdeck/themes/catppuccin-mocha.toml +27 -0
  26. reqdeck/themes/dracula.toml +27 -0
  27. reqdeck/themes/flexoki.toml +27 -0
  28. reqdeck/themes/gruvbox.toml +27 -0
  29. reqdeck/themes/monokai.toml +27 -0
  30. reqdeck/themes/nord.toml +27 -0
  31. reqdeck/themes/rose-pine-dawn.toml +27 -0
  32. reqdeck/themes/rose-pine-moon.toml +27 -0
  33. reqdeck/themes/rose-pine.toml +27 -0
  34. reqdeck/themes/solarized-dark.toml +27 -0
  35. reqdeck/themes/solarized-light.toml +27 -0
  36. reqdeck/themes/textual-dark.toml +27 -0
  37. reqdeck/themes/textual-light.toml +27 -0
  38. reqdeck/themes/tokyo-night.toml +27 -0
  39. reqdeck/viewer.py +1452 -0
  40. reqdeck-0.1.0.dist-info/METADATA +305 -0
  41. reqdeck-0.1.0.dist-info/RECORD +44 -0
  42. reqdeck-0.1.0.dist-info/WHEEL +4 -0
  43. reqdeck-0.1.0.dist-info/entry_points.txt +2 -0
  44. reqdeck-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,125 @@
1
+ # Writing requirements
2
+
3
+ ## Levels
4
+
5
+ Requirements form a tree. Each level satisfies the one above it, and only that link is
6
+ ever written — the reverse is derived by the tool, so the two directions cannot disagree.
7
+
8
+ ```
9
+ A-01, B-02 user stories the root everything traces to
10
+ ↑ satisfies
11
+ TSR_001 system what the product must do
12
+ ↑ satisfies
13
+ ICD_BUS_001 interface behaviour at a boundary between parts
14
+ TSSR_FWN_001 sub-system what one part must do
15
+ ↑ @REQ_REF
16
+ code and tests
17
+ ```
18
+
19
+ The levels are configured in `.reqdeck.toml`; `reqdeck levels` shows them. The setup offers
20
+ three shapes:
21
+
22
+ | Shape | Levels | Fits |
23
+ |---|---|---|
24
+ | 1 | Story → System (`TSR`) → Sub-system (`TSSR_<unit>`) + Interface (`ICD_<if>`) | A product of several parts that talk to each other — firmware, hardware, an app |
25
+ | 2 | Story → System → Sub-system | Several parts, no boundary worth specifying on its own |
26
+ | 3 | Story → Requirement (`REQ`) | One app or one component |
27
+
28
+ **Which level a requirement belongs to:**
29
+
30
+ - **System** — what the product must do, independent of how it is built. A user could
31
+ observe it. "The system shall support at least 50 independently controlled LEDs per
32
+ model." No part named.
33
+ - **Interface** — behaviour at a boundary, which both sides must agree on: a protocol
34
+ field, a command, an API endpoint, a connector pinout. Written once, satisfied by the
35
+ sub-system requirements of both sides.
36
+ - **Sub-system** — what one part must do to meet the levels above. Names the part. "The
37
+ Node shall drive each channel with 12-bit PWM at 1 kHz."
38
+
39
+ Each sub-system or interface prefix is one *unit*: one CSV, one `.toml`. A new unit is a
40
+ new part of the design — agree it with the user before `reqdeck unit`.
41
+
42
+ A level with `traced = false` (typically the system level) is never referenced from code.
43
+ It is traced through what satisfies it, and a test against a lower requirement verifies
44
+ everything above it.
45
+
46
+ ## The fields
47
+
48
+ | Field | |
49
+ |---|---|
50
+ | `id` | Allocated by `reqdeck add`. Sequential per prefix, append-only, never reused |
51
+ | `title` | A few words |
52
+ | `statement` | One sentence with "shall". The obligation, nothing else |
53
+ | `rationale` | Optional, one line — only where the reason is not obvious |
54
+ | `satisfies` | IDs one level up. Empty is legitimate but rare, and then the rationale says why |
55
+ | `topic` | The OPEN-QUESTIONS topic that decided it, on levels that require one |
56
+ | `source` | What decided it: `OQ-0608.`, `ADR-0007.`, `Chosen 2026-07-26.` Always present |
57
+ | `verification` | How it will be shown to be met — one code, see below |
58
+ | `status` | `Proposed`, `Accepted` or `Reopened` |
59
+ | `security` | `Yes` where it carries a security property; empty otherwise |
60
+ | `section` | Where it is shown in its unit's document, a key from the `.toml` |
61
+
62
+ Multi-line detail — a byte layout, a state table — goes in `<unit>.details.md` under
63
+ `## <ID>`. It is part of the requirement and shown with it.
64
+
65
+ ## The statement
66
+
67
+ - **One obligation.** Two "shall"s joined by "and" are usually two requirements. Split
68
+ them unless they cannot be verified separately.
69
+ - **Verifiable.** Someone must be able to say pass or fail. "Fast", "robust" and
70
+ "user-friendly" are not; a number, an observable behaviour or an inspectable property is.
71
+ - **Every number chosen by someone.** If the user did not choose it and no answered
72
+ question fixes it, it is an open question.
73
+ - **What, not how**, at the system level. Sub-system requirements may name a mechanism
74
+ when the mechanism *is* the decision.
75
+ - **No reasoning in the statement.** The why goes in `rationale`, or stays in the answered
76
+ question that `source` points at.
77
+ - **Name the part.** "The Node shall", "The app shall", "The system shall".
78
+
79
+ ## Linking
80
+
81
+ `satisfies` names what the requirement exists to fulfil, one level up — the link `check`
82
+ enforces against the levels in `.reqdeck.toml`. Look for the parent before leaving it
83
+ empty; an empty link must not mean the parent was never looked for. A requirement driven
84
+ by a constraint rather than a user need — a risk, a regulation — may genuinely have none,
85
+ and then its rationale says so.
86
+
87
+ ## Verification methods
88
+
89
+ Configured per project (`reqdeck verification`). The common set:
90
+
91
+ | | | |
92
+ |---|---|---|
93
+ | **A** | Analysis | Calculation, modelling or simulation — a worst-case budget, a timing margin |
94
+ | **I** | Inspection | Examination of the design, drawings or source without operating it |
95
+ | **D** | Demonstration | A person operates the system and observes, with no instrument |
96
+ | **T** | Test | An automated test |
97
+
98
+ Embedded projects often split `T` into **U** unit test (host, hardware mocked), **S**
99
+ integration test (real silicon, driven by a rig) and **M** manual test (instruments, a
100
+ person). The split records *where* a requirement can be signed off, which is what says
101
+ when it can be.
102
+
103
+ A method marked `automated` makes the requirement untraced until a test names it.
104
+ Two distinctions decide most cases: **D against M** — the difference is an instrument;
105
+ **U against S** — logic is U, what a device actually does is S.
106
+
107
+ ## Status
108
+
109
+ `Proposed` — written, not yet read by the user. `Accepted` — reviewed and agreed.
110
+ `Reopened` — was accepted, then changed, so it needs reading again. An agent writes
111
+ `Proposed` (the only thing `reqdeck add` can write); `reqdeck edit` on an accepted requirement
112
+ makes it `Reopened`, and the change and its reason go in `source`. Only the user accepts.
113
+
114
+ ## Security relevance
115
+
116
+ `security = Yes` marks a requirement carrying a security property, so the set is
117
+ enumerable for a conformance argument (`reqdeck list --security`). Judge by whether an attacker
118
+ is in the story: a CRC against a marginal cable is not a security control, and marking it
119
+ would misdescribe it.
120
+
121
+ ## Sections
122
+
123
+ Sections are presentation, not meaning. Group requirements by subject so a unit reads as
124
+ a document; the section key is stable and its number is derived, so restructuring a
125
+ document never touches a requirement's ID or row.
reqdeck/store.py ADDED
@@ -0,0 +1,186 @@
1
+ """The files requirements live in.
2
+
3
+ One *unit* per prefix, as three files side by side in the requirements folder:
4
+
5
+ firmware-node.csv the requirements, one row each, ordered by ID
6
+ firmware-node.toml the unit: level, prefix, title, sections
7
+ firmware-node.details.md optional multi-line detail, one `## <ID>` each
8
+
9
+ Rows are only ever appended, so a row's line number is as permanent as its ID.
10
+ Where a requirement is *shown* is its `section`, a stable key declared in the
11
+ `.toml`; section numbers are derived from declaration order when rendering, so
12
+ inserting a section renumbers the document without touching a single row.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import csv
18
+ import io
19
+ import re
20
+ import tomllib
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+
24
+ #: Columns reqdeck understands, in the order a new file gets them. Any other
25
+ #: column is kept, shown, and otherwise left alone.
26
+ STANDARD = ["id", "section", "title", "statement", "rationale", "satisfies", "topic",
27
+ "source", "verification", "status", "security"]
28
+ STORY_COLUMNS = ["id", "section", "statement"]
29
+
30
+ DETAIL_HEADING = re.compile(r"^## (\S+)[ \t]*$", re.M)
31
+
32
+
33
+ @dataclass
34
+ class Section:
35
+ key: str
36
+ title: str
37
+ description: str = ""
38
+
39
+ @property
40
+ def parent(self) -> str:
41
+ return self.key.rpartition(".")[0]
42
+
43
+
44
+ @dataclass
45
+ class Unit:
46
+ stem: str
47
+ level: str
48
+ prefixes: list[str]
49
+ title: str
50
+ description: str
51
+ code: bool
52
+ sections: list[Section]
53
+ csv_path: Path
54
+ toml_path: Path
55
+ details_path: Path
56
+ header: list[str] = field(default_factory=list)
57
+
58
+ def section(self, key: str) -> Section | None:
59
+ return next((s for s in self.sections if s.key == key), None)
60
+
61
+ def numbering(self) -> dict[str, str]:
62
+ """Section key to its displayed number, from declaration order."""
63
+ out: dict[str, str] = {}
64
+ counters: dict[str, int] = {}
65
+ for s in self.sections:
66
+ counters[s.parent] = counters.get(s.parent, 0) + 1
67
+ base = out.get(s.parent, "")
68
+ out[s.key] = f"{base}.{counters[s.parent]}" if base else str(counters[s.parent])
69
+ return out
70
+
71
+
72
+ @dataclass
73
+ class Row:
74
+ values: dict[str, str]
75
+ line: int
76
+
77
+
78
+ class StoreError(Exception):
79
+ pass
80
+
81
+
82
+ def units(req_dir: Path) -> list[Unit]:
83
+ out = []
84
+ for toml_path in sorted(req_dir.glob("*.toml")):
85
+ stem = toml_path.stem
86
+ try:
87
+ meta = tomllib.loads(toml_path.read_text())
88
+ except tomllib.TOMLDecodeError as e:
89
+ raise StoreError(f"{toml_path}: {e}") from e
90
+ prefixes = meta.get("prefixes") or ([meta["prefix"]] if "prefix" in meta else [])
91
+ sections = [Section(s.get("key", ""), s.get("title", ""), s.get("description", "").strip())
92
+ for s in meta.get("sections", [])]
93
+ out.append(Unit(
94
+ stem=stem, level=meta.get("level", ""), prefixes=list(prefixes),
95
+ title=meta.get("title", stem), description=meta.get("description", "").strip(),
96
+ code=bool(meta.get("code", False)), sections=sections,
97
+ csv_path=req_dir / f"{stem}.csv", toml_path=toml_path,
98
+ details_path=req_dir / f"{stem}.details.md",
99
+ ))
100
+ return out
101
+
102
+
103
+ def read_rows(unit: Unit) -> list[Row]:
104
+ if not unit.csv_path.exists():
105
+ raise StoreError(f"{unit.toml_path.name} has no {unit.csv_path.name} beside it")
106
+ with unit.csv_path.open(newline="") as f:
107
+ reader = csv.reader(f)
108
+ try:
109
+ unit.header = next(reader)
110
+ except StopIteration:
111
+ raise StoreError(f"{unit.csv_path.name} is empty — it needs at least a header row")
112
+ rows = []
113
+ for raw in reader:
114
+ if not any(c.strip() for c in raw):
115
+ continue
116
+ if len(raw) != len(unit.header):
117
+ raise StoreError(f"{unit.csv_path.name}:{reader.line_num}: {len(raw)} fields, "
118
+ f"header has {len(unit.header)}")
119
+ rows.append(Row(dict(zip(unit.header, raw)), reader.line_num))
120
+ return rows
121
+
122
+
123
+ def read_details(unit: Unit) -> list[tuple[str, str, int]]:
124
+ """(id, markdown, line) for every `## <ID>` block in the details file."""
125
+ if not unit.details_path.exists():
126
+ return []
127
+ text = unit.details_path.read_text()
128
+ heads = list(DETAIL_HEADING.finditer(text))
129
+ out = []
130
+ for i, h in enumerate(heads):
131
+ end = heads[i + 1].start() if i + 1 < len(heads) else len(text)
132
+ out.append((h.group(1), text[h.end():end].strip("\n"), text[:h.start()].count("\n") + 1))
133
+ return out
134
+
135
+
136
+ def render_csv(header: list[str], rows: list[dict[str, str]]) -> str:
137
+ buf = io.StringIO()
138
+ w = csv.writer(buf, lineterminator="\n")
139
+ w.writerow(header)
140
+ for r in rows:
141
+ w.writerow([r.get(c, "") for c in header])
142
+ return buf.getvalue()
143
+
144
+
145
+ def write_rows(unit: Unit, rows: list[dict[str, str]]) -> None:
146
+ unit.csv_path.write_text(render_csv(unit.header, rows))
147
+
148
+
149
+ def append_row(unit: Unit, row: dict[str, str]) -> int:
150
+ """Append one row and return its line number."""
151
+ text = unit.csv_path.read_text()
152
+ if text and not text.endswith("\n"):
153
+ text += "\n"
154
+ line = render_csv(unit.header, [row]).split("\n", 1)[1]
155
+ unit.csv_path.write_text(text + line)
156
+ return text.count("\n") + 1
157
+
158
+
159
+ def is_canonical(unit: Unit, rows: list[Row]) -> bool:
160
+ """Whether the file is byte-for-byte what reqdeck itself would write."""
161
+ return unit.csv_path.read_text() == render_csv(unit.header, [r.values for r in rows])
162
+
163
+
164
+ def toml_str(s: str) -> str:
165
+ return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
166
+
167
+
168
+ def create_unit(req_dir: Path, stem: str, level: str, prefixes: list[str], title: str,
169
+ code: bool, columns: list[str]) -> Unit:
170
+ csv_path, toml_path = req_dir / f"{stem}.csv", req_dir / f"{stem}.toml"
171
+ for p in (csv_path, toml_path):
172
+ if p.exists():
173
+ raise StoreError(f"{p} already exists")
174
+ req_dir.mkdir(parents=True, exist_ok=True)
175
+ prefix_line = (f"prefix = {toml_str(prefixes[0])}" if len(prefixes) == 1
176
+ else f"prefixes = [{', '.join(toml_str(p) for p in prefixes)}]")
177
+ lines = [f"level = {toml_str(level)}", prefix_line, f"title = {toml_str(title)}"]
178
+ if code:
179
+ lines.append("code = true")
180
+ lines += ['description = """', '"""', "",
181
+ "# Sections, in the order they are shown. A key with a dot nests under",
182
+ "# the key before it: `power.bus` is a subsection of `power`.",
183
+ "#", "# [[sections]]", '# key = "power"', '# title = "Power"', ""]
184
+ toml_path.write_text("\n".join(lines))
185
+ csv_path.write_text(render_csv(columns, []))
186
+ return next(u for u in units(req_dir) if u.stem == stem)
@@ -0,0 +1,23 @@
1
+ # Open Questions
2
+
3
+ Everything not yet decided. One question per `###` heading, fields on single lines.
4
+
5
+ ```
6
+ ### <ID> — <short title>
7
+ **Question:** <what has to be decided>
8
+ **Answer:** <blank until decided>
9
+ **Blocks:** <what is waiting on it, or —>
10
+ ```
11
+
12
+ IDs are `OQ-TTNN` — `TT` is the topic number, `NN` the question within it. To add a
13
+ question, take the next free `NN` in that topic. **Questions never move.** When one is
14
+ answered, fill in `Answer:` and leave it where it is.
15
+
16
+ ---
17
+
18
+ ## Topic 01 — Product scope
19
+
20
+ ### OQ-0101 — Are the user stories real?
21
+ **Question:** Which of the user stories come from a real person's need, which are guesses, and which are missing?
22
+ **Answer:**
23
+ **Blocks:** Everything traces to the stories
@@ -0,0 +1,31 @@
1
+ # Themes
2
+
3
+ One file per theme, named after the [Textual theme](https://textual.textualize.io/guide/design/)
4
+ it extends. Textual supplies the base colours; the file adds every colour reqdeck draws
5
+ with, taken from that theme's own palette, in the roles below. Nothing paints a
6
+ background — the terminal's own shows through — so a role is a text or border colour,
7
+ except `selection`, the one filled row.
8
+
9
+ | Role | Draws |
10
+ |---|---|
11
+ | `foreground` | Ordinary text |
12
+ | `muted` | Field labels, rationale, guides, the key hint line |
13
+ | `border` | Every panel border |
14
+ | `border_active` | The focused panel's border |
15
+ | `border_search` | The tree's border while a search filters it |
16
+ | `selection` | The cursor row in the focused panel |
17
+ | `title` | Panel titles, the unit and level names in the tree |
18
+ | `heading` | Section headings and numbers in a document |
19
+ | `id` | A requirement or question ID |
20
+ | `link` | An ID in running text, which can be followed |
21
+ | `link_active` | The link under the keyboard cursor, and the preview's border |
22
+ | `code` | Inline code — byte values, command codes |
23
+ | `table_header` | A table's header row |
24
+ | `statement_bar` | The bar beside a requirement's statement |
25
+ | `accepted` `proposed` `reopened` | Review status |
26
+ | `security` | The security-relevant marker |
27
+ | `open` `answered` | An open question's state |
28
+ | `keys` | Key names in the hint line |
29
+
30
+ A theme in `~/.config/reqdeck/themes/` is loaded as well, and replaces a bundled one of the
31
+ same name. Its `extends` names the Textual theme to start from.
@@ -0,0 +1,73 @@
1
+ """Themes: Textual's own, each given reqdeck's full colour suite by a file in
2
+ this folder. See README.md for the roles."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import dataclasses
7
+ import os
8
+ import re
9
+ import tomllib
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+
13
+ from textual.theme import BUILTIN_THEMES, Theme
14
+
15
+ ROLES = ("foreground", "muted", "border", "border_active", "border_search", "selection",
16
+ "title", "heading", "id", "link", "link_active", "code", "table_header",
17
+ "statement_bar", "accepted", "proposed", "reopened", "security", "open",
18
+ "answered", "keys")
19
+ HEX = re.compile(r"#[0-9a-fA-F]{6}")
20
+ DEFAULT = "dracula"
21
+
22
+
23
+ class ThemeError(Exception):
24
+ pass
25
+
26
+
27
+ @dataclass
28
+ class Palette:
29
+ name: str
30
+ title: str
31
+ extends: str
32
+ colors: dict[str, str]
33
+
34
+ def theme(self) -> Theme:
35
+ """The Textual theme, with every role as a `$rk-<role>` CSS variable."""
36
+ base = BUILTIN_THEMES[self.extends]
37
+ variables = {**base.variables, **{f"rk-{k.replace('_', '-')}": v for k, v in self.colors.items()}}
38
+ return dataclasses.replace(base, name=self.name, variables=variables)
39
+
40
+
41
+ def user_dir() -> Path:
42
+ base = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
43
+ return Path(base) / "reqdeck" / "themes"
44
+
45
+
46
+ def parse(path: Path) -> Palette:
47
+ try:
48
+ data = tomllib.loads(path.read_text())
49
+ except tomllib.TOMLDecodeError as e:
50
+ raise ThemeError(f"{path}: {e}") from e
51
+ name = data.get("name", path.stem)
52
+ extends = data.get("extends", name)
53
+ if extends not in BUILTIN_THEMES:
54
+ raise ThemeError(f"{path}: extends {extends!r}, which is not a Textual theme")
55
+ colors = data.get("colors", {})
56
+ missing = [r for r in ROLES if r not in colors]
57
+ unknown = [r for r in colors if r not in ROLES]
58
+ bad = [r for r, v in colors.items() if not HEX.fullmatch(str(v))]
59
+ for what, items in (("missing", missing), ("unknown", unknown), ("not #rrggbb", bad)):
60
+ if items:
61
+ raise ThemeError(f"{path}: {what}: {', '.join(items)}")
62
+ return Palette(name, data.get("title", name), extends, dict(colors))
63
+
64
+
65
+ def load_all() -> dict[str, Palette]:
66
+ """Bundled themes, then the user's, which win on a name clash."""
67
+ out: dict[str, Palette] = {}
68
+ dirs = [Path(__file__).parent, user_dir()]
69
+ for d in dirs:
70
+ for path in sorted(d.glob("*.toml")) if d.is_dir() else []:
71
+ p = parse(path)
72
+ out[p.name] = p
73
+ return out
@@ -0,0 +1,27 @@
1
+ # One Dark — https://github.com/atom/one-dark-syntax
2
+
3
+ name = "atom-one-dark"
4
+ title = "One Dark"
5
+
6
+ [colors]
7
+ foreground = "#abb2bf" # mono-1
8
+ muted = "#7f848e" # mono-2
9
+ border = "#4b5263" # gutter
10
+ border_active = "#61afef" # blue
11
+ border_search = "#56b6c2" # cyan
12
+ selection = "#3e4451" # selection
13
+ title = "#61afef" # blue
14
+ heading = "#c678dd" # purple
15
+ id = "#56b6c2" # cyan
16
+ link = "#61afef" # blue
17
+ link_active = "#d19a66" # orange
18
+ code = "#98c379" # green
19
+ table_header = "#e5c07b" # yellow
20
+ statement_bar = "#c678dd" # purple
21
+ accepted = "#98c379" # green
22
+ proposed = "#d19a66" # orange
23
+ reopened = "#e06c75" # red
24
+ security = "#c678dd" # purple
25
+ open = "#e06c75" # red
26
+ answered = "#98c379" # green
27
+ keys = "#c678dd" # purple
@@ -0,0 +1,27 @@
1
+ # One Light — https://github.com/atom/one-light-syntax
2
+
3
+ name = "atom-one-light"
4
+ title = "One Light"
5
+
6
+ [colors]
7
+ foreground = "#383a42" # mono-1
8
+ muted = "#a0a1a7" # mono-3
9
+ border = "#c8c8ca" # border
10
+ border_active = "#4078f2" # blue
11
+ border_search = "#0184bc" # cyan
12
+ selection = "#e5e5e6" # selection
13
+ title = "#4078f2" # blue
14
+ heading = "#a626a4" # purple
15
+ id = "#0184bc" # cyan
16
+ link = "#4078f2" # blue
17
+ link_active = "#986801" # orange
18
+ code = "#50a14f" # green
19
+ table_header = "#c18401" # yellow
20
+ statement_bar = "#a626a4" # purple
21
+ accepted = "#50a14f" # green
22
+ proposed = "#c18401" # yellow
23
+ reopened = "#e45649" # red
24
+ security = "#a626a4" # purple
25
+ open = "#e45649" # red
26
+ answered = "#50a14f" # green
27
+ keys = "#a626a4" # purple
@@ -0,0 +1,27 @@
1
+ # Catppuccin Frappé — https://catppuccin.com/palette
2
+
3
+ name = "catppuccin-frappe"
4
+ title = "Catppuccin Frappé"
5
+
6
+ [colors]
7
+ foreground = "#c6d0f5" # text
8
+ muted = "#838ba7" # overlay1
9
+ border = "#626880" # surface2
10
+ border_active = "#ca9ee6" # mauve
11
+ border_search = "#e5c890" # yellow
12
+ selection = "#414559" # surface0
13
+ title = "#ca9ee6" # mauve
14
+ heading = "#8caaee" # blue
15
+ id = "#85c1dc" # sapphire
16
+ link = "#99d1db" # sky
17
+ link_active = "#ef9f76" # peach
18
+ code = "#ef9f76" # peach
19
+ table_header = "#babbf1" # lavender
20
+ statement_bar = "#ca9ee6" # mauve
21
+ accepted = "#a6d189" # green
22
+ proposed = "#e5c890" # yellow
23
+ reopened = "#e78284" # red
24
+ security = "#f4b8e4" # pink
25
+ open = "#ea999c" # maroon
26
+ answered = "#a6d189" # green
27
+ keys = "#ca9ee6" # mauve
@@ -0,0 +1,27 @@
1
+ # Catppuccin Latte — https://catppuccin.com/palette
2
+
3
+ name = "catppuccin-latte"
4
+ title = "Catppuccin Latte"
5
+
6
+ [colors]
7
+ foreground = "#4c4f69" # text
8
+ muted = "#8c8fa1" # overlay1
9
+ border = "#acb0be" # surface2
10
+ border_active = "#8839ef" # mauve
11
+ border_search = "#df8e1d" # yellow
12
+ selection = "#ccd0da" # surface0
13
+ title = "#8839ef" # mauve
14
+ heading = "#1e66f5" # blue
15
+ id = "#209fb5" # sapphire
16
+ link = "#04a5e5" # sky
17
+ link_active = "#fe640b" # peach
18
+ code = "#fe640b" # peach
19
+ table_header = "#7287fd" # lavender
20
+ statement_bar = "#8839ef" # mauve
21
+ accepted = "#40a02b" # green
22
+ proposed = "#df8e1d" # yellow
23
+ reopened = "#d20f39" # red
24
+ security = "#ea76cb" # pink
25
+ open = "#e64553" # maroon
26
+ answered = "#40a02b" # green
27
+ keys = "#8839ef" # mauve
@@ -0,0 +1,27 @@
1
+ # Catppuccin Macchiato — https://catppuccin.com/palette
2
+
3
+ name = "catppuccin-macchiato"
4
+ title = "Catppuccin Macchiato"
5
+
6
+ [colors]
7
+ foreground = "#cad3f5" # text
8
+ muted = "#8087a2" # overlay1
9
+ border = "#5b6078" # surface2
10
+ border_active = "#c6a0f6" # mauve
11
+ border_search = "#eed49f" # yellow
12
+ selection = "#363a4f" # surface0
13
+ title = "#c6a0f6" # mauve
14
+ heading = "#8aadf4" # blue
15
+ id = "#7dc4e4" # sapphire
16
+ link = "#91d7e3" # sky
17
+ link_active = "#f5a97f" # peach
18
+ code = "#f5a97f" # peach
19
+ table_header = "#b7bdf8" # lavender
20
+ statement_bar = "#c6a0f6" # mauve
21
+ accepted = "#a6da95" # green
22
+ proposed = "#eed49f" # yellow
23
+ reopened = "#ed8796" # red
24
+ security = "#f5bde6" # pink
25
+ open = "#ee99a0" # maroon
26
+ answered = "#a6da95" # green
27
+ keys = "#c6a0f6" # mauve
@@ -0,0 +1,27 @@
1
+ # Catppuccin Mocha — https://catppuccin.com/palette
2
+
3
+ name = "catppuccin-mocha"
4
+ title = "Catppuccin Mocha"
5
+
6
+ [colors]
7
+ foreground = "#cdd6f4" # text
8
+ muted = "#7f849c" # overlay1
9
+ border = "#585b70" # surface2
10
+ border_active = "#cba6f7" # mauve
11
+ border_search = "#f9e2af" # yellow
12
+ selection = "#313244" # surface0
13
+ title = "#cba6f7" # mauve
14
+ heading = "#89b4fa" # blue
15
+ id = "#74c7ec" # sapphire
16
+ link = "#89dceb" # sky
17
+ link_active = "#fab387" # peach
18
+ code = "#fab387" # peach
19
+ table_header = "#b4befe" # lavender
20
+ statement_bar = "#cba6f7" # mauve
21
+ accepted = "#a6e3a1" # green
22
+ proposed = "#f9e2af" # yellow
23
+ reopened = "#f38ba8" # red
24
+ security = "#f5c2e7" # pink
25
+ open = "#eba0ac" # maroon
26
+ answered = "#a6e3a1" # green
27
+ keys = "#cba6f7" # mauve
@@ -0,0 +1,27 @@
1
+ # Dracula — https://draculatheme.com/contribute
2
+
3
+ name = "dracula"
4
+ title = "Dracula"
5
+
6
+ [colors]
7
+ foreground = "#f8f8f2" # foreground
8
+ muted = "#6272a4" # comment
9
+ border = "#bd93f9" # purple
10
+ border_active = "#ff79c6" # pink
11
+ border_search = "#8be9fd" # cyan
12
+ selection = "#44475a" # current-line
13
+ title = "#ff79c6" # pink
14
+ heading = "#bd93f9" # purple
15
+ id = "#8be9fd" # cyan
16
+ link = "#8be9fd" # cyan
17
+ link_active = "#ff79c6" # pink
18
+ code = "#f1fa8c" # yellow
19
+ table_header = "#bd93f9" # purple
20
+ statement_bar = "#ff79c6" # pink
21
+ accepted = "#50fa7b" # green
22
+ proposed = "#ffb86c" # orange
23
+ reopened = "#ff5555" # red
24
+ security = "#ff79c6" # pink
25
+ open = "#ff5555" # red
26
+ answered = "#50fa7b" # green
27
+ keys = "#ff79c6" # pink
@@ -0,0 +1,27 @@
1
+ # Flexoki — https://stephango.com/flexoki
2
+
3
+ name = "flexoki"
4
+ title = "Flexoki"
5
+
6
+ [colors]
7
+ foreground = "#cecdc3" # tx
8
+ muted = "#878580" # tx-2
9
+ border = "#403e3c" # ui-3
10
+ border_active = "#da702c" # orange
11
+ border_search = "#3aa99f" # cyan
12
+ selection = "#343331" # ui-2
13
+ title = "#da702c" # orange
14
+ heading = "#8b7ec8" # purple
15
+ id = "#3aa99f" # cyan
16
+ link = "#4385be" # blue
17
+ link_active = "#da702c" # orange
18
+ code = "#d0a215" # yellow
19
+ table_header = "#8b7ec8" # purple
20
+ statement_bar = "#ce5d97" # magenta
21
+ accepted = "#879a39" # green
22
+ proposed = "#d0a215" # yellow
23
+ reopened = "#d14d41" # red
24
+ security = "#ce5d97" # magenta
25
+ open = "#d14d41" # red
26
+ answered = "#879a39" # green
27
+ keys = "#da702c" # orange