kash-shell 0.3.8__py3-none-any.whl → 0.3.10__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.
- kash/actions/__init__.py +4 -4
- kash/actions/core/markdownify.py +5 -2
- kash/actions/core/readability.py +5 -2
- kash/actions/core/render_as_html.py +18 -0
- kash/actions/core/webpage_config.py +12 -4
- kash/commands/__init__.py +8 -20
- kash/commands/base/basic_file_commands.py +15 -0
- kash/commands/base/debug_commands.py +15 -2
- kash/commands/base/general_commands.py +27 -18
- kash/commands/base/logs_commands.py +1 -4
- kash/commands/base/model_commands.py +8 -8
- kash/commands/base/search_command.py +3 -2
- kash/commands/base/show_command.py +5 -3
- kash/commands/extras/parse_uv_lock.py +186 -0
- kash/commands/help/doc_commands.py +2 -31
- kash/commands/help/welcome.py +33 -0
- kash/commands/workspace/selection_commands.py +11 -6
- kash/commands/workspace/workspace_commands.py +19 -16
- kash/config/colors.py +2 -0
- kash/config/env_settings.py +72 -0
- kash/config/init.py +2 -2
- kash/config/logger.py +61 -59
- kash/config/logger_basic.py +12 -5
- kash/config/server_config.py +6 -6
- kash/config/settings.py +117 -67
- kash/config/setup.py +35 -9
- kash/config/suppress_warnings.py +30 -12
- kash/config/text_styles.py +3 -13
- kash/docs/load_api_docs.py +2 -1
- kash/docs/markdown/topics/a2_installation.md +7 -3
- kash/docs/markdown/topics/a3_getting_started.md +3 -2
- kash/docs/markdown/warning.md +3 -8
- kash/docs/markdown/welcome.md +4 -0
- kash/docs_base/load_recipe_snippets.py +1 -1
- kash/docs_base/recipes/{general_system_commands.ksh → general_system_commands.sh} +1 -1
- kash/{concepts → embeddings}/cosine.py +2 -1
- kash/embeddings/text_similarity.py +57 -0
- kash/exec/__init__.py +20 -3
- kash/exec/action_decorators.py +18 -4
- kash/exec/action_exec.py +41 -23
- kash/exec/action_registry.py +13 -48
- kash/exec/command_registry.py +2 -1
- kash/exec/fetch_url_metadata.py +4 -6
- kash/exec/importing.py +56 -0
- kash/exec/llm_transforms.py +6 -6
- kash/exec/precondition_registry.py +2 -1
- kash/exec/preconditions.py +16 -1
- kash/exec/shell_callable_action.py +33 -19
- kash/file_storage/file_store.py +23 -14
- kash/file_storage/item_file_format.py +13 -3
- kash/file_storage/metadata_dirs.py +11 -2
- kash/help/assistant.py +2 -2
- kash/help/assistant_instructions.py +2 -1
- kash/help/help_embeddings.py +2 -2
- kash/help/help_printing.py +14 -10
- kash/help/tldr_help.py +5 -3
- kash/llm_utils/clean_headings.py +1 -1
- kash/llm_utils/llm_api_keys.py +4 -4
- kash/llm_utils/llm_completion.py +2 -2
- kash/llm_utils/llm_features.py +68 -0
- kash/llm_utils/llm_messages.py +1 -2
- kash/llm_utils/llm_names.py +1 -1
- kash/llm_utils/llms.py +17 -12
- kash/local_server/__init__.py +5 -2
- kash/local_server/local_server.py +56 -46
- kash/local_server/local_server_commands.py +15 -15
- kash/local_server/local_server_routes.py +2 -2
- kash/local_server/local_url_formatters.py +1 -1
- kash/mcp/__init__.py +5 -2
- kash/mcp/mcp_cli.py +54 -17
- kash/mcp/mcp_server_commands.py +5 -6
- kash/mcp/mcp_server_routes.py +14 -11
- kash/mcp/mcp_server_sse.py +61 -34
- kash/mcp/mcp_server_stdio.py +0 -8
- kash/media_base/audio_processing.py +81 -7
- kash/media_base/media_cache.py +18 -18
- kash/media_base/media_services.py +1 -1
- kash/media_base/media_tools.py +6 -6
- kash/media_base/services/local_file_media.py +2 -2
- kash/media_base/{speech_transcription.py → transcription_deepgram.py} +25 -109
- kash/media_base/transcription_format.py +73 -0
- kash/media_base/transcription_whisper.py +38 -0
- kash/model/__init__.py +73 -5
- kash/model/actions_model.py +38 -4
- kash/model/concept_model.py +30 -0
- kash/model/items_model.py +56 -13
- kash/model/params_model.py +24 -0
- kash/shell/completions/completion_scoring.py +37 -5
- kash/shell/output/kerm_codes.py +1 -2
- kash/shell/output/shell_formatting.py +14 -4
- kash/shell/shell_main.py +2 -2
- kash/shell/utils/exception_printing.py +6 -0
- kash/shell/utils/native_utils.py +26 -20
- kash/text_handling/custom_sliding_transforms.py +12 -4
- kash/text_handling/doc_normalization.py +6 -2
- kash/text_handling/markdown_render.py +117 -0
- kash/text_handling/markdown_utils.py +204 -0
- kash/utils/common/import_utils.py +12 -3
- kash/utils/common/type_utils.py +0 -29
- kash/utils/common/url.py +80 -28
- kash/utils/errors.py +6 -0
- kash/utils/file_utils/{dir_size.py → dir_info.py} +25 -4
- kash/utils/file_utils/file_ext.py +2 -3
- kash/utils/file_utils/file_formats.py +28 -2
- kash/utils/file_utils/file_formats_model.py +50 -19
- kash/utils/file_utils/filename_parsing.py +10 -4
- kash/web_content/dir_store.py +1 -2
- kash/web_content/file_cache_utils.py +37 -10
- kash/web_content/file_processing.py +68 -0
- kash/web_content/local_file_cache.py +12 -9
- kash/web_content/web_extract.py +8 -3
- kash/web_content/web_fetch.py +12 -4
- kash/web_gen/tabbed_webpage.py +5 -2
- kash/web_gen/templates/base_styles.css.jinja +120 -14
- kash/web_gen/templates/base_webpage.html.jinja +60 -13
- kash/web_gen/templates/content_styles.css.jinja +4 -2
- kash/web_gen/templates/item_view.html.jinja +2 -2
- kash/web_gen/templates/tabbed_webpage.html.jinja +1 -2
- kash/workspaces/__init__.py +15 -2
- kash/workspaces/selections.py +18 -3
- kash/workspaces/source_items.py +4 -2
- kash/workspaces/workspace_output.py +11 -4
- kash/workspaces/workspaces.py +5 -11
- kash/xonsh_custom/command_nl_utils.py +40 -19
- kash/xonsh_custom/custom_shell.py +44 -12
- kash/xonsh_custom/customize_prompt.py +39 -21
- kash/xonsh_custom/load_into_xonsh.py +26 -27
- kash/xonsh_custom/shell_load_commands.py +2 -2
- kash/xonsh_custom/xonsh_completers.py +2 -249
- kash/xonsh_custom/xonsh_keybindings.py +282 -0
- kash/xonsh_custom/xonsh_modern_tools.py +3 -3
- kash/xontrib/kash_extension.py +5 -6
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/METADATA +26 -12
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/RECORD +140 -140
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/entry_points.txt +1 -1
- kash/concepts/concept_formats.py +0 -23
- kash/concepts/text_similarity.py +0 -112
- kash/shell/clideps/api_keys.py +0 -99
- kash/shell/clideps/dotenv_setup.py +0 -114
- kash/shell/clideps/dotenv_utils.py +0 -89
- kash/shell/clideps/pkg_deps.py +0 -232
- kash/shell/clideps/platforms.py +0 -11
- kash/shell/clideps/terminal_features.py +0 -56
- kash/shell/utils/osc_utils.py +0 -95
- kash/shell/utils/terminal_images.py +0 -133
- kash/text_handling/markdown_util.py +0 -167
- kash/utils/common/atomic_var.py +0 -158
- kash/utils/common/string_replace.py +0 -93
- kash/utils/common/string_template.py +0 -101
- /kash/docs_base/recipes/{python_dev_commands.ksh → python_dev_commands.sh} +0 -0
- /kash/docs_base/recipes/{tldr_standard_commands.ksh → tldr_standard_commands.sh} +0 -0
- /kash/{concepts → embeddings}/embeddings.py +0 -0
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.8.dist-info → kash_shell-0.3.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
:root {
|
|
2
2
|
font-size: 16px;
|
|
3
|
-
|
|
4
|
-
--font-sans: "
|
|
3
|
+
/* Adding Hack Nerd Font to all fonts for icon support, if it is installed. */
|
|
4
|
+
--font-sans: "Source Sans 3 Variable", sans-serif, "Hack Nerd Font";
|
|
5
5
|
--font-serif: "PT Serif", serif, "Hack Nerd Font";
|
|
6
|
+
--font-weight-sans-bold: 625; /* Source Sans 3 Variable better at this weight. */
|
|
6
7
|
--font-mono: "Hack Nerd Font", "Menlo", "DejaVu Sans Mono", Consolas, "Lucida Console", monospace;
|
|
7
8
|
|
|
8
9
|
--font-size-large: 1.2rem;
|
|
@@ -63,16 +64,32 @@
|
|
|
63
64
|
body {
|
|
64
65
|
font-family: var(--font-serif);
|
|
65
66
|
color: var(--color-text);
|
|
66
|
-
line-height: 1.
|
|
67
|
+
line-height: 1.4;
|
|
67
68
|
padding: 0; /* No padding so we can have full width elements. */
|
|
68
69
|
margin: auto;
|
|
69
70
|
background-color: var(--color-bg);
|
|
71
|
+
overflow-wrap: break-word; /* Don't let long words/URLs break layout. */
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
p {
|
|
73
75
|
margin-bottom: 1rem;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
b, strong {
|
|
79
|
+
font-weight: var(--font-weight-sans-bold);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
a {
|
|
83
|
+
color: var(--color-primary);
|
|
84
|
+
text-decoration: none;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
a:hover {
|
|
88
|
+
color: var(--color-primary-light);
|
|
89
|
+
text-decoration: underline;
|
|
90
|
+
transition: all 0.15s ease-in-out;
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
h1,
|
|
77
94
|
h2,
|
|
78
95
|
h3,
|
|
@@ -88,19 +105,19 @@ h1 {
|
|
|
88
105
|
|
|
89
106
|
h2 {
|
|
90
107
|
font-size: 1.4rem;
|
|
91
|
-
margin-top:
|
|
108
|
+
margin-top: 2.5rem;
|
|
92
109
|
margin-bottom: 1rem;
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
h3 {
|
|
96
|
-
font-size: 1.
|
|
97
|
-
margin-top: 1.
|
|
98
|
-
margin-bottom:
|
|
113
|
+
font-size: 1.09rem;
|
|
114
|
+
margin-top: 1.7rem;
|
|
115
|
+
margin-bottom: 0.7rem;
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
h4 {
|
|
102
119
|
margin-top: 1rem;
|
|
103
|
-
margin-bottom:
|
|
120
|
+
margin-bottom: 0.7rem;
|
|
104
121
|
}
|
|
105
122
|
|
|
106
123
|
/* Long text stylings, for nicely formatting blog post length or longer texts. */
|
|
@@ -118,8 +135,9 @@ h4 {
|
|
|
118
135
|
|
|
119
136
|
.long-text h3 {
|
|
120
137
|
font-family: var(--font-sans);
|
|
121
|
-
font-weight:
|
|
138
|
+
font-weight: var(--font-weight-sans-bold);
|
|
122
139
|
text-transform: uppercase;
|
|
140
|
+
letter-spacing: 0.02em;
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
.long-text h4 {
|
|
@@ -141,22 +159,31 @@ ul {
|
|
|
141
159
|
}
|
|
142
160
|
|
|
143
161
|
li {
|
|
144
|
-
margin-top: 0.
|
|
162
|
+
margin-top: 0.7rem;
|
|
145
163
|
margin-bottom: 0;
|
|
146
164
|
position: relative;
|
|
147
165
|
}
|
|
148
166
|
|
|
149
|
-
|
|
167
|
+
li > p {
|
|
168
|
+
margin-bottom: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
ul > li::before {
|
|
150
172
|
content: "▪︎";
|
|
151
173
|
position: absolute;
|
|
152
|
-
left:
|
|
153
|
-
|
|
174
|
+
left: -.85rem;
|
|
175
|
+
top: .25rem;
|
|
176
|
+
font-size: 0.62rem;
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
ol {
|
|
180
|
+
margin-bottom: 0.7rem;
|
|
157
181
|
list-style-type: decimal;
|
|
158
182
|
margin-left: 2rem;
|
|
159
|
-
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
ol > li {
|
|
186
|
+
padding-left: 0.25rem;
|
|
160
187
|
}
|
|
161
188
|
|
|
162
189
|
blockquote {
|
|
@@ -181,6 +208,37 @@ pre {
|
|
|
181
208
|
{# overflow-x: auto; #}
|
|
182
209
|
}
|
|
183
210
|
|
|
211
|
+
table {
|
|
212
|
+
font-family: var(--font-sans);
|
|
213
|
+
font-size: var(--font-size-small);
|
|
214
|
+
width: auto;
|
|
215
|
+
margin-left: auto;
|
|
216
|
+
margin-right: auto;
|
|
217
|
+
border-collapse: collapse;
|
|
218
|
+
word-break: break-word; /* long words/URLs wrap instead of inflating the column */
|
|
219
|
+
border: 1px solid var(--color-border-hint);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
th {
|
|
223
|
+
text-transform: uppercase;
|
|
224
|
+
letter-spacing: 0.02em;
|
|
225
|
+
border-bottom: 1px solid var(--color-border-hint);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
th, td {
|
|
229
|
+
padding: 0.3rem 0.6rem;
|
|
230
|
+
max-width: 40rem;
|
|
231
|
+
min-width: 6rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
th {
|
|
235
|
+
background-color: var(--color-bg-alt-solid);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
tbody tr:nth-child(even) {
|
|
239
|
+
background-color: var(--color-bg-alt-solid);
|
|
240
|
+
}
|
|
241
|
+
|
|
184
242
|
nav {
|
|
185
243
|
display: flex;
|
|
186
244
|
flex-wrap: wrap;
|
|
@@ -190,3 +248,51 @@ nav {
|
|
|
190
248
|
gap: 1rem;
|
|
191
249
|
/* Add some space between the buttons */
|
|
192
250
|
}
|
|
251
|
+
|
|
252
|
+
/* Container for wide tables to allow tables to break out of parent width. */
|
|
253
|
+
.table-container {
|
|
254
|
+
{# max-width: calc(100vw - 6rem); #}
|
|
255
|
+
position: relative;
|
|
256
|
+
left: 50%;
|
|
257
|
+
transform: translateX(-50%);
|
|
258
|
+
box-sizing: border-box;
|
|
259
|
+
margin-bottom: 1rem;
|
|
260
|
+
background-color: var(--color-bg-solid);
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Bleed wide on larger screens. */
|
|
265
|
+
/* TODO: Don't make so wide if table itself isn't large? */
|
|
266
|
+
@media (min-width: 768px) {
|
|
267
|
+
table {
|
|
268
|
+
width: calc(100vw - 6rem);
|
|
269
|
+
}
|
|
270
|
+
.table-container {
|
|
271
|
+
width: calc(100vw - 6rem);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
@media (max-width: 768px) {
|
|
276
|
+
table {
|
|
277
|
+
font-size: var(--font-size-smaller);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Footnotes. */
|
|
282
|
+
sup {
|
|
283
|
+
font-size: 80%;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.footnote-ref a, .footnote {
|
|
287
|
+
text-decoration: none;
|
|
288
|
+
padding: 0 0.15rem;
|
|
289
|
+
border-radius: 4px;
|
|
290
|
+
transition: all 0.15s ease-in-out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.footnote-ref a:hover, .footnote:hover {
|
|
294
|
+
background-color: var(--color-hover-bg);
|
|
295
|
+
color: var(--color-primary-light);
|
|
296
|
+
text-decoration: none;
|
|
297
|
+
}
|
|
298
|
+
|
|
@@ -20,12 +20,19 @@
|
|
|
20
20
|
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
21
21
|
href="https://cdn.jsdelivr.net/fontsource/fonts/pt-serif@latest/latin-700-italic.woff2" />
|
|
22
22
|
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
23
|
-
href="https://cdn.jsdelivr.net/fontsource/fonts/
|
|
23
|
+
href="https://cdn.jsdelivr.net/fontsource/fonts/source-sans-3:vf@latest/latin-wght-normal.woff2" />
|
|
24
|
+
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
25
|
+
href="https://cdn.jsdelivr.net/fontsource/fonts/source-sans-3:vf@latest/latin-wght-italic.woff2" />
|
|
24
26
|
|
|
25
27
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
|
|
26
28
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js" defer></script>
|
|
27
29
|
|
|
30
|
+
{% if extra_head %}
|
|
31
|
+
{{ extra_head|safe }}
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
28
34
|
<style>
|
|
35
|
+
/* https://fontsource.org/fonts/pt-serif/cdn */
|
|
29
36
|
/* pt-serif-latin-400-normal */
|
|
30
37
|
@font-face {
|
|
31
38
|
font-family: 'PT Serif';
|
|
@@ -62,14 +69,27 @@
|
|
|
62
69
|
src: url(https://cdn.jsdelivr.net/fontsource/fonts/pt-serif@latest/latin-700-italic.woff2) format('woff2');
|
|
63
70
|
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
|
64
71
|
}
|
|
72
|
+
/* https://fontsource.org/fonts/source-sans-3/cdn */
|
|
73
|
+
/* source-sans-3-latin-wght-normal */
|
|
65
74
|
@font-face {
|
|
66
|
-
font-family: '
|
|
75
|
+
font-family: 'Source Sans 3 Variable';
|
|
67
76
|
font-style: normal;
|
|
68
77
|
font-display: block;
|
|
69
|
-
font-weight:
|
|
70
|
-
src: url(https://cdn.jsdelivr.net/fontsource/fonts/
|
|
71
|
-
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+
|
|
78
|
+
font-weight: 200 900;
|
|
79
|
+
src: url(https://cdn.jsdelivr.net/fontsource/fonts/source-sans-3:vf@latest/latin-wght-normal.woff2) format('woff2-variations');
|
|
80
|
+
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
|
72
81
|
}
|
|
82
|
+
/* source-sans-3-latin-wght-normal */
|
|
83
|
+
@font-face {
|
|
84
|
+
font-family: 'Source Sans 3 Variable';
|
|
85
|
+
font-style: italic;
|
|
86
|
+
font-display: block;
|
|
87
|
+
font-weight: 200 900;
|
|
88
|
+
src: url(https://cdn.jsdelivr.net/fontsource/fonts/source-sans-3:vf@latest/latin-wght-italic.woff2) format('woff2-variations');
|
|
89
|
+
unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
{# Other decent sans serif options: Work Sans Variable, Nunito Sans Variable #}
|
|
73
93
|
|
|
74
94
|
{% include "base_styles.css.jinja" %}
|
|
75
95
|
{% include "content_styles.css.jinja" %}
|
|
@@ -82,24 +102,45 @@
|
|
|
82
102
|
</head>
|
|
83
103
|
|
|
84
104
|
<body>
|
|
85
|
-
|
|
86
|
-
</body>
|
|
105
|
+
{{ content|safe }}
|
|
87
106
|
|
|
88
107
|
<script>
|
|
89
|
-
// Some messages are sent to the parent window, in case we are in a viewport like a
|
|
90
|
-
// tooltip that supports it.
|
|
91
|
-
|
|
92
|
-
// Request a resize of the parent viewport (e.g. tooltip).
|
|
93
108
|
document.addEventListener('DOMContentLoaded', () => {
|
|
109
|
+
// Send messages to the parent window, in case we are in a viewport where that matters
|
|
110
|
+
// (e.g. an iframe tooltip).
|
|
111
|
+
// Request a resize of the parent viewport. This iframe size message format isn't
|
|
112
|
+
// standardized by ResizeObserver, but is common. It is supported by Kerm.
|
|
94
113
|
const content = document.body;
|
|
95
114
|
console.log("Suggesting resize to parent:", content.offsetWidth, content.offsetHeight);
|
|
96
|
-
|
|
97
|
-
// It is supported by Kerm.
|
|
115
|
+
|
|
98
116
|
window.parent.postMessage({
|
|
99
117
|
type: 'resize',
|
|
100
118
|
width: Math.max(content.offsetWidth, 600),
|
|
101
119
|
height: Math.max(content.offsetHeight, 100)
|
|
102
120
|
}, '*');
|
|
121
|
+
|
|
122
|
+
// Wrap tables within the main content area for horizontal scrolling.
|
|
123
|
+
const containers = [];
|
|
124
|
+
document.querySelectorAll('.long-text').forEach(el => {
|
|
125
|
+
const pane = el.querySelector('.tab-pane');
|
|
126
|
+
containers.push(pane || el);
|
|
127
|
+
});
|
|
128
|
+
containers.forEach(container => {
|
|
129
|
+
// Grab all tables, then only wrap the ones whose parent is this container.
|
|
130
|
+
container.querySelectorAll('table').forEach(table => {
|
|
131
|
+
if (table.parentElement !== container) {
|
|
132
|
+
return; // Only direct children.
|
|
133
|
+
}
|
|
134
|
+
if (table.parentNode.classList.contains('table-container')) {
|
|
135
|
+
return; // Already wrapped.
|
|
136
|
+
}
|
|
137
|
+
const wrapper = document.createElement('div');
|
|
138
|
+
wrapper.className = 'table-container';
|
|
139
|
+
container.insertBefore(wrapper, table);
|
|
140
|
+
wrapper.appendChild(table);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
103
144
|
});
|
|
104
145
|
|
|
105
146
|
// Double-click to expand (e.g. expand tooltip to popover).
|
|
@@ -119,6 +160,12 @@
|
|
|
119
160
|
}, '*');
|
|
120
161
|
}
|
|
121
162
|
});
|
|
163
|
+
|
|
164
|
+
{% if extra_footer_js %}
|
|
165
|
+
{{ extra_footer_js|safe }}
|
|
166
|
+
{% endif %}
|
|
122
167
|
</script>
|
|
123
168
|
|
|
169
|
+
</body>
|
|
170
|
+
|
|
124
171
|
</html>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
padding: 0 0.4rem;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
{# More novel bracket ideas: [❲⟦⟪⟬〔〘〚〖 ]❳⟧⟫⟭ 〕〙〛〗 #}
|
|
19
19
|
.citation::before {
|
|
20
20
|
content: "[";
|
|
21
21
|
}
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
font-weight: 500;
|
|
58
58
|
font-size: 1.2rem;
|
|
59
59
|
text-transform: uppercase;
|
|
60
|
+
letter-spacing: 0.02em;
|
|
60
61
|
margin-bottom: 0.5rem;
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
font-weight: 500;
|
|
82
83
|
font-size: 1.2rem;
|
|
83
84
|
text-transform: uppercase;
|
|
85
|
+
letter-spacing: 0.02em;
|
|
84
86
|
margin-bottom: 0.5rem;
|
|
85
87
|
|
|
86
88
|
/* Hack to center the header above the columns */
|
|
@@ -107,7 +109,7 @@
|
|
|
107
109
|
font-size: var(--font-size-small);
|
|
108
110
|
font-weight: 600;
|
|
109
111
|
text-transform: uppercase;
|
|
110
|
-
letter-spacing: 0.
|
|
112
|
+
letter-spacing: 0.02em;
|
|
111
113
|
line-height: 1.2;
|
|
112
114
|
padding: 0 0.5rem;
|
|
113
115
|
border-bottom-width: 2px;
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
.item-type {
|
|
20
20
|
font-family: var(--font-sans);
|
|
21
21
|
text-transform: uppercase;
|
|
22
|
+
letter-spacing: 0.02em;
|
|
22
23
|
font-size: var(--font-size-smaller);
|
|
23
|
-
font-weight:
|
|
24
|
-
letter-spacing: 0.05em;
|
|
24
|
+
font-weight: var(--font-weight-sans-bold);
|
|
25
25
|
padding-right: 1rem;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div class="long-text container max-w-3xl mx-auto bg-white py-8 px-16 shadow-lg">
|
|
1
|
+
<div class="long-text container max-w-3xl mx-auto bg-white py-8 px-6 md:px-16 md:shadow-lg">
|
|
2
2
|
<h1 class="text-center text-4xl mt-6 mb-6">{{ title }}</h1>
|
|
3
3
|
<div>
|
|
4
4
|
<!-- Navigation Tabs -->
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
{% endfor %}
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
|
33
|
-
<!-- TODO: Footer info (match with pdf export) -->
|
|
34
33
|
</div>
|
|
35
34
|
|
|
36
35
|
<script>
|
kash/workspaces/__init__.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# flake8: noqa: F401
|
|
2
|
-
|
|
3
1
|
from kash.workspaces.selections import Selection, SelectionHistory
|
|
4
2
|
from kash.workspaces.workspaces import (
|
|
5
3
|
Workspace,
|
|
@@ -9,5 +7,20 @@ from kash.workspaces.workspaces import (
|
|
|
9
7
|
get_ws,
|
|
10
8
|
global_ws_dir,
|
|
11
9
|
resolve_ws,
|
|
10
|
+
switch_to_ws,
|
|
12
11
|
ws_param_value,
|
|
13
12
|
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Selection",
|
|
16
|
+
"SelectionHistory",
|
|
17
|
+
"Workspace",
|
|
18
|
+
"current_ignore",
|
|
19
|
+
"current_ws",
|
|
20
|
+
"get_global_ws",
|
|
21
|
+
"get_ws",
|
|
22
|
+
"global_ws_dir",
|
|
23
|
+
"resolve_ws",
|
|
24
|
+
"ws_param_value",
|
|
25
|
+
"switch_to_ws",
|
|
26
|
+
]
|
kash/workspaces/selections.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from collections.abc import Callable, Sequence
|
|
2
4
|
from functools import wraps
|
|
3
5
|
from pathlib import Path
|
|
@@ -78,6 +80,12 @@ class Selection(BaseModel):
|
|
|
78
80
|
if current_path == old_path:
|
|
79
81
|
self.paths[idx] = new_path
|
|
80
82
|
|
|
83
|
+
def refresh(self, base_dir: Path) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Refresh the selection paths, dropping any that no longer exist.
|
|
86
|
+
"""
|
|
87
|
+
self.paths[:] = [p for p in self.paths if (base_dir / p).exists()]
|
|
88
|
+
|
|
81
89
|
def as_str(self, max_lines: int = SELECTION_DISPLAY_MAX) -> str:
|
|
82
90
|
lines = [
|
|
83
91
|
f"{fmt_count_items(len(self.paths), 'item')}:",
|
|
@@ -113,7 +121,7 @@ class SelectionHistory(BaseModel):
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
@classmethod
|
|
116
|
-
def init(cls, save_path: Path, max_history: int = SELECTION_HISTORY_MAX) ->
|
|
124
|
+
def init(cls, save_path: Path, max_history: int = SELECTION_HISTORY_MAX) -> SelectionHistory:
|
|
117
125
|
"""
|
|
118
126
|
Initialize selection history, loading from save_path if it exists.
|
|
119
127
|
"""
|
|
@@ -144,9 +152,9 @@ class SelectionHistory(BaseModel):
|
|
|
144
152
|
yaml_util.write_yaml_file(data, str(self._save_path))
|
|
145
153
|
|
|
146
154
|
@persist_after(_save)
|
|
147
|
-
def
|
|
155
|
+
def clear_all(self) -> None:
|
|
148
156
|
"""
|
|
149
|
-
Clear the history.
|
|
157
|
+
Clear the entire selection history.
|
|
150
158
|
"""
|
|
151
159
|
self.history.clear()
|
|
152
160
|
self.current_index = 0
|
|
@@ -306,6 +314,13 @@ class SelectionHistory(BaseModel):
|
|
|
306
314
|
for selection in self.history:
|
|
307
315
|
selection.replace_values(replacements)
|
|
308
316
|
|
|
317
|
+
@persist_after(_save)
|
|
318
|
+
def refresh_current(self, base_dir: Path) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Refresh the current selection to drop any paths that no longer exist.
|
|
321
|
+
"""
|
|
322
|
+
self.current.refresh(base_dir)
|
|
323
|
+
|
|
309
324
|
def previous_n(self, n: int, expected_size: int | None = None) -> list[Selection]:
|
|
310
325
|
"""
|
|
311
326
|
Get the `n` previous selections (backwards and including the current position),
|
kash/workspaces/source_items.py
CHANGED
|
@@ -7,7 +7,6 @@ from kash.model.items_model import Item
|
|
|
7
7
|
from kash.model.paths_model import StorePath
|
|
8
8
|
from kash.model.preconditions_model import Precondition
|
|
9
9
|
from kash.utils.common.format_utils import fmt_loc
|
|
10
|
-
from kash.utils.common.type_utils import not_none
|
|
11
10
|
from kash.utils.errors import NoMatch
|
|
12
11
|
from kash.workspaces import current_ws
|
|
13
12
|
|
|
@@ -59,7 +58,10 @@ def find_upstream_item(
|
|
|
59
58
|
|
|
60
59
|
log.message("Looking for upstream item that matches precondition: %s", precondition)
|
|
61
60
|
for source_item in source_items:
|
|
62
|
-
source_path =
|
|
61
|
+
source_path = source_item.store_path
|
|
62
|
+
if not source_path:
|
|
63
|
+
log.error("Source item has no store path: %s", source_item)
|
|
64
|
+
continue
|
|
63
65
|
if precondition(source_item):
|
|
64
66
|
log.message(
|
|
65
67
|
"Found source item that matches requirements: %s",
|
|
@@ -17,7 +17,7 @@ from kash.shell.output.shell_formatting import format_name_and_value
|
|
|
17
17
|
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
18
18
|
from kash.utils.common.format_utils import fmt_count_items, fmt_loc
|
|
19
19
|
from kash.utils.file_formats.chat_format import ChatHistory
|
|
20
|
-
from kash.utils.file_utils.
|
|
20
|
+
from kash.utils.file_utils.dir_info import get_dir_info
|
|
21
21
|
from kash.utils.file_utils.file_formats_model import file_format_info
|
|
22
22
|
from kash.workspaces import Selection, current_ws
|
|
23
23
|
|
|
@@ -67,14 +67,21 @@ def post_shell_result(res: ShellResult) -> None:
|
|
|
67
67
|
suggest_actions()
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def print_dir_info(path: Path, text_wrap: Wrap = Wrap.NONE):
|
|
71
|
-
dir_info =
|
|
70
|
+
def print_dir_info(path: Path, tally_formats: bool = False, text_wrap: Wrap = Wrap.NONE):
|
|
71
|
+
dir_info = get_dir_info(path, tally_formats)
|
|
72
72
|
|
|
73
73
|
cprint(format_name_and_value("total files", f"{dir_info.file_count}"), text_wrap=text_wrap)
|
|
74
74
|
cprint(
|
|
75
75
|
format_name_and_value("total size", fmt_size_dual(dir_info.total_size)), text_wrap=text_wrap
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
+
if tally_formats and dir_info.format_tallies:
|
|
79
|
+
for format, count in dir_info.format_tallies.items():
|
|
80
|
+
cprint(
|
|
81
|
+
format_name_and_value(f"format: {format}", f"{count}"),
|
|
82
|
+
text_wrap=text_wrap,
|
|
83
|
+
)
|
|
84
|
+
|
|
78
85
|
|
|
79
86
|
def print_file_info(
|
|
80
87
|
input_path: Path,
|
|
@@ -83,7 +90,7 @@ def print_file_info(
|
|
|
83
90
|
text_wrap: Wrap = Wrap.NONE,
|
|
84
91
|
):
|
|
85
92
|
if input_path.is_dir():
|
|
86
|
-
print_dir_info(input_path, text_wrap)
|
|
93
|
+
print_dir_info(input_path, tally_formats=True, text_wrap=text_wrap)
|
|
87
94
|
return
|
|
88
95
|
|
|
89
96
|
# Format info.
|
kash/workspaces/workspaces.py
CHANGED
|
@@ -7,18 +7,14 @@ from typing import TYPE_CHECKING, TypeVar
|
|
|
7
7
|
|
|
8
8
|
from prettyfmt import fmt_path
|
|
9
9
|
|
|
10
|
-
from kash.config.logger import get_logger,
|
|
10
|
+
from kash.config.logger import get_logger, reset_rich_logging
|
|
11
11
|
from kash.config.settings import (
|
|
12
12
|
GLOBAL_WS_NAME,
|
|
13
|
-
RECOMMENDED_API_KEYS,
|
|
14
|
-
get_global_ws_dir,
|
|
15
|
-
get_ws_root_dir,
|
|
16
13
|
global_settings,
|
|
17
14
|
resolve_and_create_dirs,
|
|
18
15
|
)
|
|
19
16
|
from kash.file_storage.metadata_dirs import MetadataDirs
|
|
20
17
|
from kash.model.params_model import GLOBAL_PARAMS, RawParamValues
|
|
21
|
-
from kash.shell.clideps.api_keys import print_api_key_setup
|
|
22
18
|
from kash.utils.errors import FileNotFound, InvalidInput, InvalidState
|
|
23
19
|
from kash.utils.file_utils.ignore_files import IgnoreFilter, is_ignored_default
|
|
24
20
|
from kash.workspaces.workspace_registry import WorkspaceInfo, get_ws_registry
|
|
@@ -128,7 +124,7 @@ def resolve_ws(name: str | Path) -> WorkspaceInfo:
|
|
|
128
124
|
resolved = name
|
|
129
125
|
parent_dir = resolved.parent
|
|
130
126
|
else:
|
|
131
|
-
parent_dir =
|
|
127
|
+
parent_dir = global_settings().ws_root_dir
|
|
132
128
|
resolved = parent_dir / name
|
|
133
129
|
elif name_str.startswith(".") or name_str.startswith("/"):
|
|
134
130
|
# Explicit paths respected otherwise use workspace root.
|
|
@@ -136,7 +132,7 @@ def resolve_ws(name: str | Path) -> WorkspaceInfo:
|
|
|
136
132
|
parent_dir = resolved.parent
|
|
137
133
|
name = resolved.name
|
|
138
134
|
else:
|
|
139
|
-
parent_dir =
|
|
135
|
+
parent_dir = global_settings().ws_root_dir
|
|
140
136
|
resolved = parent_dir / Path(name_str)
|
|
141
137
|
|
|
142
138
|
ws_name = check_strict_workspace_name(resolved.name)
|
|
@@ -161,7 +157,7 @@ def get_ws(name_or_path: str | Path, auto_init: bool = True) -> "FileStore":
|
|
|
161
157
|
|
|
162
158
|
@cache
|
|
163
159
|
def global_ws_dir() -> Path:
|
|
164
|
-
kb_path = resolve_and_create_dirs(
|
|
160
|
+
kb_path = resolve_and_create_dirs(global_settings().global_ws_dir, is_dir=True)
|
|
165
161
|
log.debug("Global workspace path: %s", kb_path)
|
|
166
162
|
return kb_path
|
|
167
163
|
|
|
@@ -190,7 +186,7 @@ def switch_to_ws(base_dir: Path) -> "FileStore":
|
|
|
190
186
|
ws_dirs = MetadataDirs(base_dir=info.base_dir, is_global_ws=info.is_global_ws)
|
|
191
187
|
|
|
192
188
|
# Use the global log root for the global_ws, and the workspace log root otherwise.
|
|
193
|
-
|
|
189
|
+
reset_rich_logging(None, info.name if not info.is_global_ws else None)
|
|
194
190
|
|
|
195
191
|
if info.is_global_ws:
|
|
196
192
|
# If not in a workspace, use the global cache locations.
|
|
@@ -237,8 +233,6 @@ def current_ws(silent: bool = False) -> "FileStore":
|
|
|
237
233
|
ws = switch_to_ws(base_dir)
|
|
238
234
|
|
|
239
235
|
if not silent:
|
|
240
|
-
# Delayed, once-only logging of any setup warnings.
|
|
241
|
-
print_api_key_setup(RECOMMENDED_API_KEYS, once=True)
|
|
242
236
|
ws.log_workspace_info(once=True)
|
|
243
237
|
|
|
244
238
|
return ws
|
|
@@ -3,20 +3,16 @@ import re
|
|
|
3
3
|
from prettyfmt import fmt_words
|
|
4
4
|
|
|
5
5
|
INNER_PUNCT_CHARS = r"-'’–—"
|
|
6
|
-
OUTER_PUNCT_CHARS = r".,'\"
|
|
6
|
+
OUTER_PUNCT_CHARS = r".,'\"" "''':;!?()"
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
)
|
|
11
|
-
"""
|
|
12
|
-
Pattern to match a word in natural language text (i.e. words and natural
|
|
13
|
-
language-only punctuation).
|
|
14
|
-
"""
|
|
8
|
+
ESCAPED_INNER_PUNCT_CHARS = re.escape(INNER_PUNCT_CHARS)
|
|
9
|
+
ESCAPED_OUTER_PUNCT_CHARS = re.escape(OUTER_PUNCT_CHARS)
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
PUNCT_SEQ_RE = re.compile(rf"[{ESCAPED_INNER_PUNCT_CHARS}{ESCAPED_OUTER_PUNCT_CHARS}]+")
|
|
12
|
+
|
|
13
|
+
ONLY_WORDS_RE = re.compile(rf"^[\w\s{ESCAPED_INNER_PUNCT_CHARS}]*$")
|
|
14
|
+
|
|
15
|
+
PLAIN_WORD_RE = re.compile(r"^\w.*\w$")
|
|
20
16
|
|
|
21
17
|
|
|
22
18
|
def as_nl_words(text: str) -> str:
|
|
@@ -30,18 +26,25 @@ def as_nl_words(text: str) -> str:
|
|
|
30
26
|
|
|
31
27
|
def looks_like_nl(text: str) -> bool:
|
|
32
28
|
"""
|
|
33
|
-
Check if a text looks like plain natural language text
|
|
34
|
-
|
|
35
|
-
code or punctuation.
|
|
29
|
+
Check if a text looks like plain natural language text. Just very simple
|
|
30
|
+
based on words and only basic punctuation.
|
|
36
31
|
"""
|
|
37
|
-
|
|
32
|
+
is_only_word_chars = bool(ONLY_WORDS_RE.fullmatch(text))
|
|
33
|
+
without_punct = PUNCT_SEQ_RE.sub("", text)
|
|
34
|
+
is_only_words_punct = bool(ONLY_WORDS_RE.fullmatch(without_punct))
|
|
35
|
+
words = without_punct.strip().split()
|
|
36
|
+
one_longer_word = any(len(word) > 3 for word in words)
|
|
37
|
+
|
|
38
|
+
return one_longer_word and (
|
|
39
|
+
(is_only_words_punct and len(words) >= 3) or (is_only_word_chars and len(words) >= 2)
|
|
40
|
+
)
|
|
38
41
|
|
|
39
42
|
|
|
40
43
|
## Tests
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
def test_as_nl_words():
|
|
44
|
-
assert as_nl_words("x=3+9; foo('bar')") == "x=3+9
|
|
47
|
+
assert as_nl_words("x=3+9; foo('bar')") == "x=3+9 foo('bar"
|
|
45
48
|
assert as_nl_words("cd ..") == "cd .."
|
|
46
49
|
assert as_nl_words("transcribe some-file_23.mp3") == "transcribe some-file_23.mp3"
|
|
47
50
|
assert as_nl_words("hello world ") == "hello world"
|
|
@@ -57,14 +60,32 @@ def test_looks_like_nl():
|
|
|
57
60
|
assert looks_like_nl("hello world")
|
|
58
61
|
assert looks_like_nl(" hello world ")
|
|
59
62
|
assert looks_like_nl("what's up")
|
|
60
|
-
assert looks_like_nl("hello-world")
|
|
61
63
|
assert looks_like_nl("is this a question?")
|
|
62
64
|
assert looks_like_nl("'quoted text'")
|
|
63
65
|
assert looks_like_nl("git push origin main")
|
|
66
|
+
assert looks_like_nl("this is natural language")
|
|
67
|
+
assert looks_like_nl(" what's up, doc? ")
|
|
68
|
+
assert looks_like_nl("multiple spaces here")
|
|
69
|
+
assert looks_like_nl("go to the store (buy milk)")
|
|
70
|
+
assert looks_like_nl("'quoted text' has three words")
|
|
71
|
+
assert looks_like_nl("git push origin main")
|
|
72
|
+
assert looks_like_nl("what's up")
|
|
64
73
|
|
|
74
|
+
assert not looks_like_nl("hello-world")
|
|
75
|
+
assert not looks_like_nl("cd ..")
|
|
76
|
+
assert not looks_like_nl("file_name.txt")
|
|
77
|
+
assert not looks_like_nl("ls -la")
|
|
78
|
+
assert not looks_like_nl("https://example.com")
|
|
79
|
+
assert not looks_like_nl("cmd | grep pattern")
|
|
80
|
+
assert not looks_like_nl("use a+b")
|
|
81
|
+
assert not looks_like_nl("x=3")
|
|
82
|
+
assert not looks_like_nl("a > b")
|
|
83
|
+
assert not looks_like_nl("file_name.txt")
|
|
84
|
+
assert not looks_like_nl("foo;")
|
|
85
|
+
assert not looks_like_nl("hello-world")
|
|
65
86
|
assert not looks_like_nl("ls -la")
|
|
66
87
|
assert not looks_like_nl("cd ..")
|
|
67
88
|
assert not looks_like_nl("echo $HOME")
|
|
68
89
|
assert not looks_like_nl("https://example.com")
|
|
69
|
-
assert not looks_like_nl("file.txt")
|
|
90
|
+
assert not looks_like_nl(text="file.txt")
|
|
70
91
|
assert not looks_like_nl("cmd | grep pattern")
|