userun 0.1.0__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.
- userun-0.1.0/PKG-INFO +11 -0
- userun-0.1.0/README.md +0 -0
- userun-0.1.0/pyproject.toml +54 -0
- userun-0.1.0/src/userun/__init__.py +2 -0
- userun-0.1.0/src/userun/cli/__init__.py +0 -0
- userun-0.1.0/src/userun/cli/commands/__init__.py +0 -0
- userun-0.1.0/src/userun/cli/commands/concurrent_command.py +323 -0
- userun-0.1.0/src/userun/cli/templates/command.py.j2 +67 -0
- userun-0.1.0/src/userun/cli/themes/default.toml +29 -0
- userun-0.1.0/src/userun/cli/title.txt +7 -0
- userun-0.1.0/src/userun/cli/usecli.config.toml +14 -0
userun-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: userun
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Edward Boswell
|
|
6
|
+
Author-email: Edward Boswell <thememium@gmail.com>
|
|
7
|
+
Requires-Dist: asyncio>=4.0.0
|
|
8
|
+
Requires-Dist: usecli>=0.1.48
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
userun-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "userun"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Edward Boswell", email = "thememium@gmail.com" }]
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"asyncio>=4.0.0",
|
|
10
|
+
"usecli>=0.1.48",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
userun = "usecli:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.10.2,<0.11.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"deptry>=0.24.0",
|
|
23
|
+
"isort>=8.0.1",
|
|
24
|
+
"poethepoet>=0.42.1",
|
|
25
|
+
"pytest>=9.0.2",
|
|
26
|
+
"ruff>=0.15.5",
|
|
27
|
+
"ty>=0.0.21",
|
|
28
|
+
"usechange>=0.1.28",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.deptry]
|
|
32
|
+
known_first_party = ["userun"]
|
|
33
|
+
|
|
34
|
+
[tool.deptry.per_rule_ignores]
|
|
35
|
+
DEP002 = ["asyncio"]
|
|
36
|
+
DEP005 = ["asyncio"]
|
|
37
|
+
|
|
38
|
+
[tool.poe.tasks]
|
|
39
|
+
dev = "uv run main.py"
|
|
40
|
+
clean-full = "sh -c 'uv run isort . && uv run ruff check . --fix && uv run ruff format . && uv run deptry . && uv run ty check'"
|
|
41
|
+
clean = "sh -c 'uv run isort . && uv run ruff format .'"
|
|
42
|
+
test = "uv run pytest tests/ -v"
|
|
43
|
+
sort = "uv run isort ."
|
|
44
|
+
lint = "uv run ruff check ."
|
|
45
|
+
format = "uv run ruff format ."
|
|
46
|
+
deptry = "uv deptry ."
|
|
47
|
+
typecheck = "uv run ty check"
|
|
48
|
+
release = "uv run usechange release"
|
|
49
|
+
|
|
50
|
+
concurrent-demo = "uv run userun concurrent -n server,lint,test \"python3 -m http.server 8000\" \"uv run poe lint\" \"uv run poe test\""
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
where = ["."]
|
|
54
|
+
include = ["src*"]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""ConcurrentCommand - CLI command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import TextIO
|
|
10
|
+
|
|
11
|
+
from usecli import Argument, BaseCommand, Option, console, theme
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConcurrentCommand(BaseCommand):
|
|
15
|
+
def signature(self) -> str:
|
|
16
|
+
return "concurrent"
|
|
17
|
+
|
|
18
|
+
def description(self) -> str:
|
|
19
|
+
return "Run multiple commands concurrently"
|
|
20
|
+
|
|
21
|
+
def aliases(self) -> list[str]:
|
|
22
|
+
return ["conc"]
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CommandSpec:
|
|
26
|
+
index: int
|
|
27
|
+
command: str
|
|
28
|
+
prefix: str
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def parse_csv(value: str | None) -> list[str]:
|
|
32
|
+
if value is None or not isinstance(value, str):
|
|
33
|
+
return []
|
|
34
|
+
items = [item.strip() for item in value.split(",")]
|
|
35
|
+
return [item for item in items if item]
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def resolve_color(name: str) -> str | None:
|
|
39
|
+
normalized = name.strip().lower()
|
|
40
|
+
if not normalized:
|
|
41
|
+
return None
|
|
42
|
+
palette = {
|
|
43
|
+
"primary": theme.ANSI.PRIMARY,
|
|
44
|
+
"secondary": theme.ANSI.SECONDARY,
|
|
45
|
+
"accent": theme.ANSI.ACCENT,
|
|
46
|
+
"blue": theme.ANSI.BLUE,
|
|
47
|
+
"green": theme.ANSI.GREEN,
|
|
48
|
+
"yellow": theme.ANSI.YELLOW,
|
|
49
|
+
"red": theme.ANSI.RED,
|
|
50
|
+
"foreground": theme.ANSI.FOREGROUND,
|
|
51
|
+
"foreground-muted": theme.ANSI.FOREGROUND_MUTED,
|
|
52
|
+
"foreground_muted": theme.ANSI.FOREGROUND_MUTED,
|
|
53
|
+
"gray": theme.ANSI.FOREGROUND_MUTED,
|
|
54
|
+
"grey": theme.ANSI.FOREGROUND_MUTED,
|
|
55
|
+
"white": theme.ANSI.FOREGROUND,
|
|
56
|
+
"cyan": theme.ANSI.PRIMARY,
|
|
57
|
+
"magenta": theme.ANSI.ACCENT,
|
|
58
|
+
"purple": theme.ANSI.ACCENT,
|
|
59
|
+
}
|
|
60
|
+
return palette.get(normalized)
|
|
61
|
+
|
|
62
|
+
async def stream_output(
|
|
63
|
+
self, stream: asyncio.StreamReader, prefix: str, output: TextIO
|
|
64
|
+
) -> None:
|
|
65
|
+
while True:
|
|
66
|
+
line = await stream.readline()
|
|
67
|
+
if not line:
|
|
68
|
+
break
|
|
69
|
+
text = line.decode(errors="replace")
|
|
70
|
+
if not text.endswith("\n"):
|
|
71
|
+
text = f"{text}\n"
|
|
72
|
+
output.write(f"{prefix}{text}")
|
|
73
|
+
output.flush()
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def strip_ansi(text: str) -> str:
|
|
77
|
+
return re.sub(r"\x1b\[[0-9;]*m", "", text)
|
|
78
|
+
|
|
79
|
+
def build_prefixes(
|
|
80
|
+
self,
|
|
81
|
+
commands: list[str],
|
|
82
|
+
*,
|
|
83
|
+
names: list[str] | None = None,
|
|
84
|
+
colors: list[str] | None = None,
|
|
85
|
+
prefix_format: str | None = None,
|
|
86
|
+
no_prefix: bool = False,
|
|
87
|
+
no_color: bool = False,
|
|
88
|
+
) -> list[str]:
|
|
89
|
+
if no_prefix:
|
|
90
|
+
return [""] * len(commands)
|
|
91
|
+
|
|
92
|
+
default_colors = [
|
|
93
|
+
theme.ANSI.PRIMARY,
|
|
94
|
+
theme.ANSI.SECONDARY,
|
|
95
|
+
theme.ANSI.ACCENT,
|
|
96
|
+
theme.ANSI.BLUE,
|
|
97
|
+
theme.ANSI.GREEN,
|
|
98
|
+
theme.ANSI.YELLOW,
|
|
99
|
+
]
|
|
100
|
+
resolved_colors = colors or []
|
|
101
|
+
palette = resolved_colors or default_colors
|
|
102
|
+
reset = "" if no_color else theme.ANSI.RESET
|
|
103
|
+
|
|
104
|
+
index_width = len(str(max(len(commands) - 1, 0)))
|
|
105
|
+
prefixes: list[str] = []
|
|
106
|
+
for index in range(len(commands)):
|
|
107
|
+
name = ""
|
|
108
|
+
if names and index < len(names):
|
|
109
|
+
name = names[index]
|
|
110
|
+
label = name if name else f"{index:{index_width}d}"
|
|
111
|
+
format_value = {
|
|
112
|
+
"index": index,
|
|
113
|
+
"name": name or str(index),
|
|
114
|
+
"label": label,
|
|
115
|
+
}
|
|
116
|
+
raw_prefix = None
|
|
117
|
+
if prefix_format:
|
|
118
|
+
try:
|
|
119
|
+
raw_prefix = prefix_format.format_map(format_value)
|
|
120
|
+
except (KeyError, ValueError):
|
|
121
|
+
raw_prefix = None
|
|
122
|
+
if raw_prefix is None:
|
|
123
|
+
raw_prefix = f"[{label}]"
|
|
124
|
+
|
|
125
|
+
if no_color or not palette:
|
|
126
|
+
prefixes.append(f"{raw_prefix} ")
|
|
127
|
+
continue
|
|
128
|
+
color = palette[index % len(palette)]
|
|
129
|
+
prefixes.append(f"{color}{raw_prefix}{reset} ")
|
|
130
|
+
return prefixes
|
|
131
|
+
|
|
132
|
+
async def run_command(
|
|
133
|
+
self,
|
|
134
|
+
spec: CommandSpec,
|
|
135
|
+
queue: asyncio.Queue[str | None],
|
|
136
|
+
*,
|
|
137
|
+
failure_event: asyncio.Event | None = None,
|
|
138
|
+
process_registry: dict[int, asyncio.subprocess.Process] | None = None,
|
|
139
|
+
registry_lock: asyncio.Lock | None = None,
|
|
140
|
+
subprocess_color: bool = True,
|
|
141
|
+
) -> int:
|
|
142
|
+
env = None
|
|
143
|
+
if subprocess_color:
|
|
144
|
+
env = os.environ.copy()
|
|
145
|
+
env.update(
|
|
146
|
+
{
|
|
147
|
+
"FORCE_COLOR": "1",
|
|
148
|
+
"CLICOLOR_FORCE": "1",
|
|
149
|
+
"RICH_FORCE_TERMINAL": "1",
|
|
150
|
+
"TERM": env.get("TERM", "xterm-256color"),
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
process = await asyncio.create_subprocess_shell(
|
|
154
|
+
spec.command,
|
|
155
|
+
stdout=asyncio.subprocess.PIPE,
|
|
156
|
+
stderr=asyncio.subprocess.PIPE,
|
|
157
|
+
env=env,
|
|
158
|
+
)
|
|
159
|
+
if process_registry is not None and registry_lock is not None:
|
|
160
|
+
async with registry_lock:
|
|
161
|
+
process_registry[spec.index] = process
|
|
162
|
+
await queue.put(f"{spec.prefix}started: {spec.command}\n")
|
|
163
|
+
|
|
164
|
+
async def read_and_queue(stream: asyncio.StreamReader) -> None:
|
|
165
|
+
while True:
|
|
166
|
+
line = await stream.readline()
|
|
167
|
+
if not line:
|
|
168
|
+
break
|
|
169
|
+
text = line.decode(errors="replace")
|
|
170
|
+
if not text.endswith("\n"):
|
|
171
|
+
text = f"{text}\n"
|
|
172
|
+
if not subprocess_color:
|
|
173
|
+
text = self.strip_ansi(text)
|
|
174
|
+
await queue.put(f"{spec.prefix}{text}")
|
|
175
|
+
|
|
176
|
+
tasks: list[asyncio.Task[None]] = []
|
|
177
|
+
if process.stdout is not None:
|
|
178
|
+
tasks.append(asyncio.create_task(read_and_queue(process.stdout)))
|
|
179
|
+
if process.stderr is not None:
|
|
180
|
+
tasks.append(asyncio.create_task(read_and_queue(process.stderr)))
|
|
181
|
+
|
|
182
|
+
return_code = await process.wait()
|
|
183
|
+
if tasks:
|
|
184
|
+
await asyncio.gather(*tasks)
|
|
185
|
+
await queue.put(
|
|
186
|
+
f"{spec.prefix}exited with code {return_code}: {spec.command}\n"
|
|
187
|
+
)
|
|
188
|
+
if process_registry is not None and registry_lock is not None:
|
|
189
|
+
async with registry_lock:
|
|
190
|
+
process_registry.pop(spec.index, None)
|
|
191
|
+
if failure_event is not None and return_code != 0:
|
|
192
|
+
failure_event.set()
|
|
193
|
+
return return_code
|
|
194
|
+
|
|
195
|
+
async def run_all(
|
|
196
|
+
self,
|
|
197
|
+
commands: list[str],
|
|
198
|
+
*,
|
|
199
|
+
names: list[str] | None = None,
|
|
200
|
+
colors: list[str] | None = None,
|
|
201
|
+
prefix_format: str | None = None,
|
|
202
|
+
no_prefix: bool = False,
|
|
203
|
+
no_color: bool = False,
|
|
204
|
+
kill_others: bool = False,
|
|
205
|
+
subprocess_color: bool = True,
|
|
206
|
+
) -> list[int]:
|
|
207
|
+
prefixes = self.build_prefixes(
|
|
208
|
+
commands,
|
|
209
|
+
names=names,
|
|
210
|
+
colors=colors,
|
|
211
|
+
prefix_format=prefix_format,
|
|
212
|
+
no_prefix=no_prefix,
|
|
213
|
+
no_color=no_color,
|
|
214
|
+
)
|
|
215
|
+
specs = [
|
|
216
|
+
self.CommandSpec(index=index, command=command, prefix=prefixes[index])
|
|
217
|
+
for index, command in enumerate(commands)
|
|
218
|
+
]
|
|
219
|
+
queue: asyncio.Queue[str | None] = asyncio.Queue()
|
|
220
|
+
failure_event = asyncio.Event() if kill_others else None
|
|
221
|
+
process_registry: dict[int, asyncio.subprocess.Process] = {}
|
|
222
|
+
registry_lock = asyncio.Lock()
|
|
223
|
+
|
|
224
|
+
async def writer() -> None:
|
|
225
|
+
while True:
|
|
226
|
+
item = await queue.get()
|
|
227
|
+
if item is None:
|
|
228
|
+
break
|
|
229
|
+
console.print(item, end="", markup=False, highlight=False)
|
|
230
|
+
|
|
231
|
+
writer_task = asyncio.create_task(writer())
|
|
232
|
+
results_task = asyncio.gather(
|
|
233
|
+
*(
|
|
234
|
+
self.run_command(
|
|
235
|
+
spec,
|
|
236
|
+
queue,
|
|
237
|
+
failure_event=failure_event,
|
|
238
|
+
process_registry=process_registry,
|
|
239
|
+
registry_lock=registry_lock,
|
|
240
|
+
subprocess_color=subprocess_color,
|
|
241
|
+
)
|
|
242
|
+
for spec in specs
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if kill_others and failure_event is not None:
|
|
247
|
+
await failure_event.wait()
|
|
248
|
+
async with registry_lock:
|
|
249
|
+
for process in process_registry.values():
|
|
250
|
+
if process.returncode is None:
|
|
251
|
+
process.terminate()
|
|
252
|
+
|
|
253
|
+
results = await results_task
|
|
254
|
+
await queue.put(None)
|
|
255
|
+
await writer_task
|
|
256
|
+
return list(results)
|
|
257
|
+
|
|
258
|
+
def handle(
|
|
259
|
+
self,
|
|
260
|
+
commands: list[str] = Argument(
|
|
261
|
+
..., help="Commands to run concurrently. Quote each command."
|
|
262
|
+
),
|
|
263
|
+
names: str | None = Option(
|
|
264
|
+
None,
|
|
265
|
+
"--names",
|
|
266
|
+
"-n",
|
|
267
|
+
help="Comma-separated names to label each command.",
|
|
268
|
+
),
|
|
269
|
+
colors: str | None = Option(
|
|
270
|
+
None,
|
|
271
|
+
"--colors",
|
|
272
|
+
"-c",
|
|
273
|
+
help="Comma-separated color names for prefixes.",
|
|
274
|
+
),
|
|
275
|
+
kill_others: bool = Option(
|
|
276
|
+
False,
|
|
277
|
+
"--kill-others",
|
|
278
|
+
"-k",
|
|
279
|
+
help="Stop remaining commands when a command exits non-zero.",
|
|
280
|
+
),
|
|
281
|
+
prefix_format: str | None = Option(
|
|
282
|
+
None,
|
|
283
|
+
"--prefix-format",
|
|
284
|
+
"-p",
|
|
285
|
+
help="Custom prefix format using {index}, {name}, {label}.",
|
|
286
|
+
),
|
|
287
|
+
no_prefix: bool = Option(
|
|
288
|
+
False,
|
|
289
|
+
"--no-prefix",
|
|
290
|
+
help="Disable output prefixes.",
|
|
291
|
+
),
|
|
292
|
+
no_color: bool = Option(
|
|
293
|
+
False,
|
|
294
|
+
"--no-color",
|
|
295
|
+
help="Disable ANSI colors in prefixes.",
|
|
296
|
+
),
|
|
297
|
+
subprocess_color: bool = Option(
|
|
298
|
+
True,
|
|
299
|
+
"--subprocess-color/--no-subprocess-color",
|
|
300
|
+
help="Enable or disable ANSI colors from subprocess output.",
|
|
301
|
+
),
|
|
302
|
+
) -> None:
|
|
303
|
+
name_list = self.parse_csv(names)
|
|
304
|
+
color_names = self.parse_csv(colors)
|
|
305
|
+
resolved_colors: list[str] = []
|
|
306
|
+
for color_name in color_names:
|
|
307
|
+
resolved = self.resolve_color(color_name)
|
|
308
|
+
if resolved:
|
|
309
|
+
resolved_colors.append(resolved)
|
|
310
|
+
exit_codes = asyncio.run(
|
|
311
|
+
self.run_all(
|
|
312
|
+
commands,
|
|
313
|
+
names=name_list,
|
|
314
|
+
colors=resolved_colors,
|
|
315
|
+
prefix_format=prefix_format,
|
|
316
|
+
no_prefix=no_prefix,
|
|
317
|
+
no_color=no_color,
|
|
318
|
+
kill_others=kill_others,
|
|
319
|
+
subprocess_color=subprocess_color,
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
if any(code != 0 for code in exit_codes):
|
|
323
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""{{ class_name }} - CLI command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from usecli import Argument, BaseCommand, Confirm, Menu, Option, Prompt, console, theme
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class {{ class_name }}(BaseCommand):
|
|
9
|
+
def signature(self) -> str:
|
|
10
|
+
return "{{ command_name }}"
|
|
11
|
+
|
|
12
|
+
def description(self) -> str:
|
|
13
|
+
return "Description for {{ command_name }} command"
|
|
14
|
+
|
|
15
|
+
# def aliases(self) -> list[str]:
|
|
16
|
+
# return [{{ command_name[:3] }}]
|
|
17
|
+
|
|
18
|
+
def handle(
|
|
19
|
+
self,
|
|
20
|
+
name: str = Argument(..., help="An example argument"),
|
|
21
|
+
verbose: bool = Option(False, "--verbose", "-v", help="Enable verbose output"),
|
|
22
|
+
) -> None:
|
|
23
|
+
console.print(
|
|
24
|
+
f"[bold {theme.SUCCESS}]Executing {{ command_name }}[/bold {theme.SUCCESS}]"
|
|
25
|
+
)
|
|
26
|
+
console.print(f"Hello, {name}!")
|
|
27
|
+
|
|
28
|
+
if verbose:
|
|
29
|
+
console.print("[dim]Verbose mode enabled[/dim]")
|
|
30
|
+
|
|
31
|
+
# Example: Text input prompt
|
|
32
|
+
favorite_color = Prompt.ask(
|
|
33
|
+
"What's your favorite color?",
|
|
34
|
+
choices=["red", "green", "blue", "yellow"],
|
|
35
|
+
)
|
|
36
|
+
console.print(
|
|
37
|
+
f"You chose: [bold {theme.ACCENT}]{favorite_color}[/bold {theme.ACCENT}]"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Example: Confirmation prompt (only when verbose)
|
|
41
|
+
if verbose:
|
|
42
|
+
if not Confirm.ask("Do you want to continue?"):
|
|
43
|
+
console.print(f"[{theme.WARNING}]Cancelled by user[/{theme.WARNING}]")
|
|
44
|
+
return
|
|
45
|
+
console.print(f"[{theme.SUCCESS}]Proceeding...[/{theme.SUCCESS}]")
|
|
46
|
+
|
|
47
|
+
# Example: Single-select menu
|
|
48
|
+
single_choice = Menu.select(
|
|
49
|
+
["Option A", "Option B", "Option C"],
|
|
50
|
+
title="Pick one option:",
|
|
51
|
+
)
|
|
52
|
+
if single_choice:
|
|
53
|
+
console.print(f"You selected: {single_choice}")
|
|
54
|
+
|
|
55
|
+
# Example: Multi-select menu
|
|
56
|
+
multi_choices = Menu.multi_select(
|
|
57
|
+
["Feature 1", "Feature 2", "Feature 3", "Feature 4"],
|
|
58
|
+
title="Select multiple features (space to select, enter to confirm):",
|
|
59
|
+
)
|
|
60
|
+
if multi_choices:
|
|
61
|
+
console.print(f"You selected {len(multi_choices)} features:")
|
|
62
|
+
for choice in multi_choices:
|
|
63
|
+
console.print(f" - {choice}")
|
|
64
|
+
|
|
65
|
+
console.print(
|
|
66
|
+
f"[bold {theme.PRIMARY}]Command completed![/bold {theme.PRIMARY}]"
|
|
67
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[colors]
|
|
2
|
+
# Core semantic colors
|
|
3
|
+
primary = "#60D7FF"
|
|
4
|
+
secondary = "#5EFF87"
|
|
5
|
+
accent = "#F5FE53"
|
|
6
|
+
|
|
7
|
+
success = "#5EFF87" # Green
|
|
8
|
+
error = "#FE686B" # Red
|
|
9
|
+
warning = "#F5FE53" # Yellow
|
|
10
|
+
info = "#60D7FF" # Teal / Blue
|
|
11
|
+
|
|
12
|
+
# Text
|
|
13
|
+
foreground = "#FFFFFF" # Text
|
|
14
|
+
foreground_muted = "#BBBBBB" # Subtext
|
|
15
|
+
|
|
16
|
+
# Surfaces
|
|
17
|
+
background = "#000000" # Base
|
|
18
|
+
border = "#60D7FF" # Surface
|
|
19
|
+
border_focus = "#5EFF87" # Focus Ring
|
|
20
|
+
|
|
21
|
+
# UI semantics
|
|
22
|
+
command = "#60D7FF"
|
|
23
|
+
option = "#60D7FF"
|
|
24
|
+
link = "#60D7FF"
|
|
25
|
+
prompt = "#5EFF87"
|
|
26
|
+
|
|
27
|
+
panel_primary = "#5EFF87"
|
|
28
|
+
panel_secondary = "#60D7FF"
|
|
29
|
+
panel_accent = "#F5FE53"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[usecli]
|
|
2
|
+
command_name = "userun"
|
|
3
|
+
title = "userun"
|
|
4
|
+
title_file = "title.txt"
|
|
5
|
+
title_font = "ansi_shadow"
|
|
6
|
+
description = "A python command runner for concurrent runs."
|
|
7
|
+
commands_dir = "commands"
|
|
8
|
+
templates_dir = "templates"
|
|
9
|
+
themes_dir = "themes"
|
|
10
|
+
theme = "default"
|
|
11
|
+
hide_init = true
|
|
12
|
+
hide_inspire = true
|
|
13
|
+
hide_make_command = false
|
|
14
|
+
hide_make_theme = true
|