skit-cli 0.0.1__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.
- skit/__init__.py +3 -0
- skit/analyzer.py +410 -0
- skit/argspec.py +528 -0
- skit/argstate.py +166 -0
- skit/atomic.py +101 -0
- skit/cli.py +1621 -0
- skit/config.py +255 -0
- skit/editor.py +77 -0
- skit/flows.py +596 -0
- skit/i18n.py +324 -0
- skit/inlineform.py +61 -0
- skit/launcher.py +326 -0
- skit/locales/zh_CN/LC_MESSAGES/skit.mo +0 -0
- skit/locales/zh_TW/LC_MESSAGES/skit.mo +0 -0
- skit/metawriter.py +267 -0
- skit/models.py +154 -0
- skit/paths.py +44 -0
- skit/pep723.py +292 -0
- skit/promptform.py +67 -0
- skit/reconcile.py +321 -0
- skit/shim.py +386 -0
- skit/store.py +566 -0
- skit/theme.py +123 -0
- skit/tokens.py +112 -0
- skit/tui.py +774 -0
- skit/tui_add.py +361 -0
- skit/tui_footer.py +38 -0
- skit/tui_form.py +586 -0
- skit/tui_health.py +150 -0
- skit/tui_prefs.py +175 -0
- skit/tui_settings.py +374 -0
- skit/uvman.py +288 -0
- skit_cli-0.0.1.dist-info/METADATA +199 -0
- skit_cli-0.0.1.dist-info/RECORD +37 -0
- skit_cli-0.0.1.dist-info/WHEEL +4 -0
- skit_cli-0.0.1.dist-info/entry_points.txt +3 -0
- skit_cli-0.0.1.dist-info/licenses/LICENSE +21 -0
skit/tui_health.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Health check (D): the checklist that can also act.
|
|
2
|
+
|
|
3
|
+
Every ⚠ row is selectable — Enter jumps the Library to that script; R rebuilds the
|
|
4
|
+
index in place. Checks only what existing capabilities can actually verify (uv, the
|
|
5
|
+
registry↔meta correspondence, missing targets, the one deliberate whole-library drift
|
|
6
|
+
sweep, mirror state). Nothing invented.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import override
|
|
12
|
+
|
|
13
|
+
from rich.markup import escape
|
|
14
|
+
from textual import on
|
|
15
|
+
from textual.app import ComposeResult
|
|
16
|
+
from textual.binding import Binding
|
|
17
|
+
from textual.containers import VerticalScroll
|
|
18
|
+
from textual.screen import Screen
|
|
19
|
+
from textual.widgets import OptionList, Static
|
|
20
|
+
from textual.widgets.option_list import Option
|
|
21
|
+
|
|
22
|
+
from . import config, flows, launcher, store, tui_footer
|
|
23
|
+
from .i18n import gettext, ngettext
|
|
24
|
+
from .paths import scripts_dir
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HealthScreen(Screen[str | None]):
|
|
28
|
+
"""Dismisses with a slug to select in the Library, or None."""
|
|
29
|
+
|
|
30
|
+
BINDINGS = [
|
|
31
|
+
Binding("escape", "close", gettext("Back")),
|
|
32
|
+
Binding("R", "rebuild", gettext("Rebuild index")),
|
|
33
|
+
]
|
|
34
|
+
DEFAULT_CSS = """
|
|
35
|
+
HealthScreen #hc-body {
|
|
36
|
+
padding: 0 1;
|
|
37
|
+
border: round $skit-box-green;
|
|
38
|
+
border-title-color: ansi_bright_white;
|
|
39
|
+
border-title-style: bold;
|
|
40
|
+
}
|
|
41
|
+
HealthScreen #hc-issues { height: auto; border: none; }
|
|
42
|
+
HealthScreen .ok { color: $success; }
|
|
43
|
+
HealthScreen .warn { color: $warning; }
|
|
44
|
+
HealthScreen .bad { color: $error; }
|
|
45
|
+
HealthScreen .hint { color: $text-muted; }
|
|
46
|
+
HealthScreen #hc-keys { dock: bottom; height: 1; color: $text-muted; padding: 0 1; }
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def on_mount(self) -> None:
|
|
50
|
+
self.query_one("#hc-body").border_title = gettext("Health check")
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
def compose(self) -> ComposeResult:
|
|
54
|
+
with VerticalScroll(id="hc-body"):
|
|
55
|
+
uv = launcher.find_uv()
|
|
56
|
+
if uv:
|
|
57
|
+
yield Static(f"✓ {gettext('uv: %(path)s') % {'path': escape(uv)}}", classes="ok")
|
|
58
|
+
else:
|
|
59
|
+
yield Static(
|
|
60
|
+
f"✗ {gettext('uv: not found. Install it from https://docs.astral.sh/uv/getting-started/installation/')}",
|
|
61
|
+
classes="bad",
|
|
62
|
+
)
|
|
63
|
+
entries = store.list_entries()
|
|
64
|
+
yield Static(
|
|
65
|
+
"✓ "
|
|
66
|
+
+ ngettext(
|
|
67
|
+
"%(count)s script registered", "%(count)s scripts registered", len(entries)
|
|
68
|
+
)
|
|
69
|
+
% {"count": len(entries)},
|
|
70
|
+
classes="ok",
|
|
71
|
+
)
|
|
72
|
+
issues: list[Option] = [
|
|
73
|
+
Option(
|
|
74
|
+
f"⚠ {escape(e.meta.name)} — " + gettext("the launch target is gone from disk"),
|
|
75
|
+
id=e.slug,
|
|
76
|
+
)
|
|
77
|
+
for e in entries
|
|
78
|
+
if launcher.target_missing(e)
|
|
79
|
+
]
|
|
80
|
+
for e in entries:
|
|
81
|
+
if e.meta.kind != "python" or not e.script_path.exists():
|
|
82
|
+
continue
|
|
83
|
+
if flows.plan_for_entry(e).drift_lines:
|
|
84
|
+
issues.append(
|
|
85
|
+
Option(
|
|
86
|
+
f"⚠ {escape(e.meta.name)} — "
|
|
87
|
+
+ gettext(
|
|
88
|
+
"form definitions are out of sync (open Script settings → Resync)"
|
|
89
|
+
),
|
|
90
|
+
id=e.slug,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
if issues:
|
|
94
|
+
yield Static(gettext("Issues (Enter jumps to the script):"), classes="warn")
|
|
95
|
+
yield OptionList(*issues, id="hc-issues")
|
|
96
|
+
mirror = config.load_mirror()
|
|
97
|
+
if mirror.enabled:
|
|
98
|
+
yield Static(
|
|
99
|
+
"✓ " + gettext("Mirror: on (%(pypi)s)") % {"pypi": escape(mirror.pypi)},
|
|
100
|
+
classes="ok",
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
yield Static("✓ " + gettext("Mirror: off"), classes="ok")
|
|
104
|
+
location = scripts_dir()
|
|
105
|
+
yield Static(
|
|
106
|
+
gettext("Library: %(path)s (%(count)s · %(size)s)")
|
|
107
|
+
% {
|
|
108
|
+
"path": escape(str(location)),
|
|
109
|
+
"count": len(entries),
|
|
110
|
+
"size": store.human_size(store.dir_size(location)),
|
|
111
|
+
},
|
|
112
|
+
classes="hint",
|
|
113
|
+
)
|
|
114
|
+
yield Static("", id="hc-rebuilt", classes="ok")
|
|
115
|
+
yield Static(
|
|
116
|
+
tui_footer.bar(
|
|
117
|
+
tui_footer.chip("screen.jump", "Enter", gettext("Jump to script")),
|
|
118
|
+
tui_footer.chip("screen.rebuild", "R", gettext("Rebuild index")),
|
|
119
|
+
tui_footer.chip("screen.close", "Esc", gettext("Back")),
|
|
120
|
+
),
|
|
121
|
+
id="hc-keys",
|
|
122
|
+
markup=True,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@on(OptionList.OptionSelected, "#hc-issues")
|
|
126
|
+
def _jump(self, event: OptionList.OptionSelected) -> None:
|
|
127
|
+
self.dismiss(str(event.option.id))
|
|
128
|
+
|
|
129
|
+
def action_jump(self) -> None:
|
|
130
|
+
"""Footer/Enter twin of clicking an issue line: dismiss to the highlighted
|
|
131
|
+
script. A no-op when the store is healthy (there is no issue list to jump into)."""
|
|
132
|
+
issues = self.query("#hc-issues")
|
|
133
|
+
if not issues:
|
|
134
|
+
return
|
|
135
|
+
option_list = issues.first(OptionList)
|
|
136
|
+
if option_list.highlighted is not None:
|
|
137
|
+
self.dismiss(str(option_list.get_option_at_index(option_list.highlighted).id))
|
|
138
|
+
|
|
139
|
+
def action_rebuild(self) -> None:
|
|
140
|
+
count, problems = store.doctor_rebuild()
|
|
141
|
+
report = self.query_one("#hc-rebuilt", Static)
|
|
142
|
+
lines = [
|
|
143
|
+
ngettext("Index rebuilt: %(count)s entry", "Index rebuilt: %(count)s entries", count)
|
|
144
|
+
% {"count": count}
|
|
145
|
+
]
|
|
146
|
+
lines += [escape(p) for p in problems]
|
|
147
|
+
report.update("\n".join(lines))
|
|
148
|
+
|
|
149
|
+
def action_close(self) -> None:
|
|
150
|
+
self.dismiss(None)
|
skit/tui_prefs.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Preferences (,): skit's global settings, one screen, everything visible.
|
|
2
|
+
|
|
3
|
+
Every setting shows what is ACTUALLY in effect right now (the most common question a
|
|
4
|
+
settings screen gets is "what happens if I leave this empty"). Language is a dropdown
|
|
5
|
+
(the locale list will grow); the form style governs every interactive flow; the custom
|
|
6
|
+
mirror enforces https for the uv binary inline (downloaded-and-executed ⇒ MITM→RCE).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import override
|
|
13
|
+
|
|
14
|
+
from rich.markup import escape
|
|
15
|
+
from textual import on
|
|
16
|
+
from textual.app import ComposeResult
|
|
17
|
+
from textual.binding import Binding
|
|
18
|
+
from textual.containers import VerticalScroll
|
|
19
|
+
from textual.screen import Screen
|
|
20
|
+
from textual.widgets import Input, RadioButton, RadioSet, Select, Static
|
|
21
|
+
|
|
22
|
+
from . import config, i18n, tui_footer
|
|
23
|
+
from .i18n import gettext
|
|
24
|
+
|
|
25
|
+
_MIRROR_CHOICES = [*config.PYPI_PRESETS, "custom", "off"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PreferencesScreen(Screen[bool]):
|
|
29
|
+
BINDINGS = [
|
|
30
|
+
Binding("escape", "close", gettext("Back")),
|
|
31
|
+
Binding("ctrl+a", "save", gettext("Save"), priority=True),
|
|
32
|
+
]
|
|
33
|
+
DEFAULT_CSS = """
|
|
34
|
+
PreferencesScreen #pf-body {
|
|
35
|
+
padding: 0 1;
|
|
36
|
+
border: round $skit-box-indigo;
|
|
37
|
+
border-title-color: ansi_bright_white;
|
|
38
|
+
border-title-style: bold;
|
|
39
|
+
}
|
|
40
|
+
PreferencesScreen .section { color: $accent; margin: 1 0 0 0; }
|
|
41
|
+
PreferencesScreen .hint { color: $text-muted; }
|
|
42
|
+
PreferencesScreen .error { color: $error; }
|
|
43
|
+
PreferencesScreen RadioSet { layout: horizontal; height: auto; border: none; }
|
|
44
|
+
PreferencesScreen RadioSet > RadioButton { width: auto; margin: 0 3 0 0; }
|
|
45
|
+
PreferencesScreen #pf-keys { dock: bottom; height: 1; color: $text-muted; padding: 0 1; }
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def on_mount(self) -> None:
|
|
49
|
+
self.query_one("#pf-body").border_title = gettext("Preferences")
|
|
50
|
+
self._toggle_custom()
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
def compose(self) -> ComposeResult:
|
|
54
|
+
with VerticalScroll(id="pf-body"):
|
|
55
|
+
yield Static(gettext("Interface language"), classes="section")
|
|
56
|
+
current = config.load_config().get("language", "")
|
|
57
|
+
options = [(gettext("Automatic (follow the system)"), "auto")]
|
|
58
|
+
options += [(locale, locale) for locale in i18n.available_locales()]
|
|
59
|
+
yield Select(
|
|
60
|
+
options, value=current if current else "auto", allow_blank=False, id="pf-lang"
|
|
61
|
+
)
|
|
62
|
+
yield Static(
|
|
63
|
+
gettext("Currently in effect: %(locale)s") % {"locale": i18n.current_locale()},
|
|
64
|
+
classes="hint",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
yield Static(gettext("Editor"), classes="section")
|
|
68
|
+
yield Input(
|
|
69
|
+
value=config.load_editor(),
|
|
70
|
+
placeholder=gettext("e.g. code --wait (empty = use $VISUAL / $EDITOR)"),
|
|
71
|
+
id="pf-editor",
|
|
72
|
+
)
|
|
73
|
+
fallback = os.environ.get("VISUAL") or os.environ.get("EDITOR") or ""
|
|
74
|
+
if fallback:
|
|
75
|
+
yield Static(
|
|
76
|
+
gettext("Empty means: %(cmd)s (from $VISUAL / $EDITOR)")
|
|
77
|
+
% {"cmd": escape(fallback)},
|
|
78
|
+
classes="hint",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
yield Static(gettext("Interactive form"), classes="section")
|
|
82
|
+
with RadioSet(id="pf-form"):
|
|
83
|
+
yield RadioButton(
|
|
84
|
+
gettext("Mini form — opens in place, fully clickable"),
|
|
85
|
+
value=config.load_form() == "tui",
|
|
86
|
+
)
|
|
87
|
+
yield RadioButton(
|
|
88
|
+
gettext("Line-by-line prompts — plainest, best over slow terminals"),
|
|
89
|
+
value=config.load_form() == "plain",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
yield Static(
|
|
93
|
+
gettext("Download mirror (mainland-China acceleration)"), classes="section"
|
|
94
|
+
)
|
|
95
|
+
mirror = config.load_mirror()
|
|
96
|
+
if not mirror.enabled:
|
|
97
|
+
selected = "off"
|
|
98
|
+
else:
|
|
99
|
+
selected = next(
|
|
100
|
+
(k for k, v in config.PYPI_PRESETS.items() if v == mirror.pypi), "custom"
|
|
101
|
+
)
|
|
102
|
+
with RadioSet(id="pf-mirror"):
|
|
103
|
+
for choice in _MIRROR_CHOICES:
|
|
104
|
+
yield RadioButton(choice, value=(choice == selected))
|
|
105
|
+
yield Static(
|
|
106
|
+
gettext("Affects where dependencies and Python itself are downloaded from."),
|
|
107
|
+
classes="hint",
|
|
108
|
+
)
|
|
109
|
+
yield Input(value=mirror.pypi, placeholder=gettext("PyPI index URL"), id="pf-pypi")
|
|
110
|
+
yield Input(
|
|
111
|
+
value=mirror.python_install,
|
|
112
|
+
placeholder=gettext("Python-install mirror URL"),
|
|
113
|
+
id="pf-pyinstall",
|
|
114
|
+
)
|
|
115
|
+
yield Input(
|
|
116
|
+
value=mirror.uv_binary, placeholder=gettext("uv binary mirror URL"), id="pf-uv"
|
|
117
|
+
)
|
|
118
|
+
yield Static("", id="pf-uv-error", classes="error")
|
|
119
|
+
yield Static(
|
|
120
|
+
tui_footer.bar(
|
|
121
|
+
tui_footer.chip("screen.save", "Ctrl+A", gettext("Save")),
|
|
122
|
+
tui_footer.chip("screen.close", "Esc", gettext("Back")),
|
|
123
|
+
),
|
|
124
|
+
id="pf-keys",
|
|
125
|
+
markup=True,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@on(RadioSet.Changed, "#pf-mirror")
|
|
129
|
+
def _mirror_changed(self, event: RadioSet.Changed) -> None:
|
|
130
|
+
self._toggle_custom()
|
|
131
|
+
|
|
132
|
+
def _mirror_choice(self) -> str:
|
|
133
|
+
index = self.query_one("#pf-mirror", RadioSet).pressed_index
|
|
134
|
+
return _MIRROR_CHOICES[index] if 0 <= index < len(_MIRROR_CHOICES) else "off"
|
|
135
|
+
|
|
136
|
+
def _toggle_custom(self) -> None:
|
|
137
|
+
custom = self._mirror_choice() == "custom"
|
|
138
|
+
for wid in ("#pf-pypi", "#pf-pyinstall", "#pf-uv"):
|
|
139
|
+
self.query_one(wid, Input).display = custom
|
|
140
|
+
self.query_one("#pf-uv-error", Static).display = custom
|
|
141
|
+
|
|
142
|
+
def action_save(self) -> None:
|
|
143
|
+
lang_value = self.query_one("#pf-lang", Select).value
|
|
144
|
+
i18n.set_language("" if lang_value in ("auto", Select.BLANK) else str(lang_value))
|
|
145
|
+
config.save_editor(self.query_one("#pf-editor", Input).value)
|
|
146
|
+
form_index = self.query_one("#pf-form", RadioSet).pressed_index
|
|
147
|
+
config.save_form("plain" if form_index == 1 else "tui")
|
|
148
|
+
choice = self._mirror_choice()
|
|
149
|
+
if choice == "off":
|
|
150
|
+
config.disable()
|
|
151
|
+
elif choice == "custom":
|
|
152
|
+
uv_url = self.query_one("#pf-uv", Input).value.strip()
|
|
153
|
+
if uv_url and not uv_url.startswith("https://"):
|
|
154
|
+
self.query_one("#pf-uv-error", Static).update(
|
|
155
|
+
gettext(
|
|
156
|
+
"The uv binary is downloaded and executed, so its mirror URL must "
|
|
157
|
+
"use https:// (got: %(url)s)."
|
|
158
|
+
)
|
|
159
|
+
% {"url": escape(uv_url)}
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
config.save_mirror(
|
|
163
|
+
config.MirrorConfig(
|
|
164
|
+
enabled=True,
|
|
165
|
+
pypi=self.query_one("#pf-pypi", Input).value.strip(),
|
|
166
|
+
python_install=self.query_one("#pf-pyinstall", Input).value.strip(),
|
|
167
|
+
uv_binary=uv_url,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
config.save_mirror(config.preset(choice))
|
|
172
|
+
self.dismiss(True)
|
|
173
|
+
|
|
174
|
+
def action_close(self) -> None:
|
|
175
|
+
self.dismiss(False)
|