kash-shell 0.3.11__py3-none-any.whl → 0.3.13__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/core/markdownify.py +5 -4
- kash/actions/core/readability.py +4 -4
- kash/actions/core/render_as_html.py +8 -6
- kash/actions/core/show_webpage.py +2 -2
- kash/actions/core/strip_html.py +2 -2
- kash/commands/base/basic_file_commands.py +24 -3
- kash/commands/base/diff_commands.py +38 -3
- kash/commands/base/files_command.py +5 -4
- kash/commands/base/reformat_command.py +1 -1
- kash/commands/base/show_command.py +1 -1
- kash/commands/extras/parse_uv_lock.py +12 -3
- kash/commands/workspace/selection_commands.py +1 -1
- kash/commands/workspace/workspace_commands.py +62 -16
- kash/config/env_settings.py +2 -42
- kash/config/logger.py +30 -25
- kash/config/logger_basic.py +6 -6
- kash/config/settings.py +23 -7
- kash/config/setup.py +33 -5
- kash/config/text_styles.py +25 -22
- kash/docs/load_source_code.py +1 -1
- kash/embeddings/cosine.py +12 -4
- kash/embeddings/embeddings.py +16 -6
- kash/embeddings/text_similarity.py +10 -4
- kash/exec/__init__.py +3 -0
- kash/exec/action_decorators.py +4 -19
- kash/exec/action_exec.py +46 -27
- kash/exec/fetch_url_metadata.py +8 -5
- kash/exec/importing.py +4 -4
- kash/exec/llm_transforms.py +2 -2
- kash/exec/preconditions.py +11 -19
- kash/exec/runtime_settings.py +134 -0
- kash/exec/shell_callable_action.py +5 -3
- kash/file_storage/file_store.py +91 -53
- kash/file_storage/item_file_format.py +6 -3
- kash/file_storage/store_filenames.py +7 -3
- kash/help/help_embeddings.py +2 -2
- kash/llm_utils/clean_headings.py +1 -1
- kash/{text_handling → llm_utils}/custom_sliding_transforms.py +0 -3
- kash/llm_utils/init_litellm.py +16 -0
- kash/llm_utils/llm_api_keys.py +6 -2
- kash/llm_utils/llm_completion.py +12 -5
- kash/local_server/__init__.py +1 -1
- kash/local_server/local_server_commands.py +2 -1
- kash/mcp/__init__.py +1 -1
- kash/mcp/mcp_cli.py +3 -2
- kash/mcp/mcp_server_commands.py +8 -2
- kash/mcp/mcp_server_routes.py +11 -12
- kash/media_base/media_cache.py +10 -3
- kash/media_base/transcription_deepgram.py +15 -2
- kash/model/__init__.py +1 -1
- kash/model/actions_model.py +9 -54
- kash/model/exec_model.py +79 -0
- kash/model/items_model.py +131 -81
- kash/model/operations_model.py +38 -15
- kash/model/paths_model.py +2 -0
- kash/shell/output/shell_output.py +10 -8
- kash/shell/shell_main.py +2 -2
- kash/shell/ui/shell_results.py +2 -1
- kash/shell/utils/exception_printing.py +2 -2
- kash/utils/common/format_utils.py +0 -14
- kash/utils/common/import_utils.py +46 -18
- kash/utils/common/task_stack.py +4 -15
- kash/utils/errors.py +14 -9
- kash/utils/file_utils/file_formats_model.py +61 -26
- kash/utils/file_utils/file_sort_filter.py +10 -3
- kash/utils/file_utils/filename_parsing.py +41 -16
- kash/{text_handling → utils/text_handling}/doc_normalization.py +23 -13
- kash/utils/text_handling/escape_html_tags.py +156 -0
- kash/{text_handling → utils/text_handling}/markdown_utils.py +82 -4
- kash/utils/text_handling/markdownify_utils.py +87 -0
- kash/{text_handling → utils/text_handling}/unified_diffs.py +1 -44
- kash/web_content/file_cache_utils.py +42 -34
- kash/web_content/local_file_cache.py +29 -12
- kash/web_content/web_extract.py +1 -1
- kash/web_content/web_extract_readabilipy.py +4 -2
- kash/web_content/web_fetch.py +42 -7
- kash/web_content/web_page_model.py +2 -1
- kash/web_gen/simple_webpage.py +1 -1
- kash/web_gen/templates/base_styles.css.jinja +139 -16
- kash/web_gen/templates/simple_webpage.html.jinja +1 -1
- kash/workspaces/__init__.py +12 -3
- kash/workspaces/selections.py +2 -2
- kash/workspaces/workspace_dirs.py +58 -0
- kash/workspaces/workspace_importing.py +2 -2
- kash/workspaces/workspace_output.py +2 -2
- kash/workspaces/workspaces.py +26 -90
- kash/xonsh_custom/load_into_xonsh.py +4 -2
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/METADATA +4 -4
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/RECORD +93 -89
- kash/shell/utils/argparse_utils.py +0 -20
- kash/utils/lang_utils/inflection.py +0 -18
- /kash/{text_handling → utils/text_handling}/markdown_render.py +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.11.dist-info → kash_shell-0.3.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
/* Adding Hack Nerd Font to all fonts for icon support, if it is installed. */
|
|
5
5
|
--font-sans: "Source Sans 3 Variable", sans-serif, "Hack Nerd Font";
|
|
6
6
|
--font-serif: "PT Serif", serif, "Hack Nerd Font";
|
|
7
|
-
|
|
7
|
+
/* Source Sans 3 Variable better at these weights. */
|
|
8
|
+
--font-weight-sans-bold: 620;
|
|
8
9
|
--font-mono: "Hack Nerd Font", "Menlo", "DejaVu Sans Mono", Consolas, "Lucida Console", monospace;
|
|
9
10
|
|
|
10
11
|
--font-size-large: 1.2rem;
|
|
11
12
|
--font-size-normal: 1rem;
|
|
12
13
|
--font-size-small: 0.95rem;
|
|
13
14
|
--font-size-smaller: 0.85rem;
|
|
14
|
-
--font-size-mono: 0.
|
|
15
|
+
--font-size-mono: 0.82rem;
|
|
15
16
|
--font-size-mono-small: 0.75rem;
|
|
16
17
|
--font-size-mono-tiny: 0.7rem;
|
|
17
18
|
|
|
@@ -71,7 +72,7 @@
|
|
|
71
72
|
body {
|
|
72
73
|
font-family: var(--font-serif);
|
|
73
74
|
color: var(--color-text);
|
|
74
|
-
line-height: 1.
|
|
75
|
+
line-height: 1.5;
|
|
75
76
|
padding: 0; /* No padding so we can have full width elements. */
|
|
76
77
|
margin: auto;
|
|
77
78
|
background-color: var(--color-bg);
|
|
@@ -81,7 +82,13 @@ body {
|
|
|
81
82
|
|
|
82
83
|
{% block typography %}
|
|
83
84
|
p {
|
|
84
|
-
margin-
|
|
85
|
+
margin-top: 0.75rem;
|
|
86
|
+
margin-bottom: 0.75rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pre {
|
|
90
|
+
margin-top: 0.75rem;
|
|
91
|
+
margin-bottom: 0.75rem;
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
b, strong {
|
|
@@ -114,14 +121,14 @@ h1 {
|
|
|
114
121
|
|
|
115
122
|
h2 {
|
|
116
123
|
font-size: 1.4rem;
|
|
117
|
-
margin-top:
|
|
124
|
+
margin-top: 1.5rem;
|
|
118
125
|
margin-bottom: 1rem;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
h3 {
|
|
122
|
-
font-size: 1.
|
|
123
|
-
margin-top: 1.
|
|
124
|
-
margin-bottom: 0.
|
|
129
|
+
font-size: 1.03rem;
|
|
130
|
+
margin-top: 1.5rem;
|
|
131
|
+
margin-bottom: 0.5rem;
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
h4 {
|
|
@@ -131,7 +138,7 @@ h4 {
|
|
|
131
138
|
|
|
132
139
|
ul {
|
|
133
140
|
list-style-type: none;
|
|
134
|
-
margin-left:
|
|
141
|
+
margin-left: 1.8rem;
|
|
135
142
|
margin-bottom: 1rem;
|
|
136
143
|
padding-left: 0;
|
|
137
144
|
}
|
|
@@ -155,9 +162,9 @@ ul > li::before {
|
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
ol {
|
|
158
|
-
margin-bottom: 0.7rem;
|
|
159
165
|
list-style-type: decimal;
|
|
160
|
-
margin-left:
|
|
166
|
+
margin-left: 1.8rem;
|
|
167
|
+
margin-bottom: 0.7rem;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
ol > li {
|
|
@@ -172,24 +179,60 @@ blockquote {
|
|
|
172
179
|
color: var(--color-secondary);
|
|
173
180
|
}
|
|
174
181
|
|
|
182
|
+
/* Inline code styling */
|
|
175
183
|
code {
|
|
176
184
|
font-family: var(--font-mono);
|
|
177
185
|
font-size: var(--font-size-mono);
|
|
178
186
|
letter-spacing: -0.025em;
|
|
179
|
-
padding: 0.2rem 0.4rem;
|
|
180
187
|
}
|
|
181
188
|
|
|
189
|
+
/* Code blocks (pre + code) */
|
|
182
190
|
pre {
|
|
183
191
|
font-family: var(--font-mono);
|
|
184
192
|
font-size: var(--font-size-mono);
|
|
185
193
|
letter-spacing: -0.025em;
|
|
186
|
-
|
|
194
|
+
background-color: var(--color-bg-alt);
|
|
195
|
+
border-radius: 4px;
|
|
196
|
+
border: 1px dotted var(--color-border-hint);
|
|
197
|
+
padding: 0.2rem 0.2rem 0.1rem 0.2rem;
|
|
198
|
+
overflow-x: auto; /* Enable horizontal scrolling */
|
|
199
|
+
position: relative; /* Create new stacking context */
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Reset code styling when inside pre blocks */
|
|
203
|
+
pre > code {
|
|
204
|
+
display: block; /* Make code block take full width */
|
|
205
|
+
line-height: 1.5; /* Improve readability */
|
|
187
206
|
}
|
|
207
|
+
|
|
208
|
+
hr {
|
|
209
|
+
border: none;
|
|
210
|
+
height: 1.5rem;
|
|
211
|
+
position: relative;
|
|
212
|
+
text-align: center;
|
|
213
|
+
margin: 0.5rem auto;
|
|
214
|
+
overflow: visible;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
hr:before {
|
|
218
|
+
content: "";
|
|
219
|
+
display: block;
|
|
220
|
+
position: absolute;
|
|
221
|
+
top: 50%;
|
|
222
|
+
width: 4rem;
|
|
223
|
+
left: calc(50% - 2rem);
|
|
224
|
+
border-top: 1px solid var(--black-light);
|
|
225
|
+
}
|
|
226
|
+
|
|
188
227
|
{% endblock typography %}
|
|
189
228
|
|
|
190
229
|
{% block long_text_styles %}
|
|
191
230
|
/* Long text stylings, for nicely formatting blog post length or longer texts. */
|
|
192
231
|
|
|
232
|
+
.long-text {
|
|
233
|
+
box-shadow: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
193
236
|
.long-text h1 {
|
|
194
237
|
font-family: var(--font-serif);
|
|
195
238
|
font-weight: 400;
|
|
@@ -204,8 +247,9 @@ pre {
|
|
|
204
247
|
.long-text h3 {
|
|
205
248
|
font-family: var(--font-sans);
|
|
206
249
|
font-weight: var(--font-weight-sans-bold);
|
|
250
|
+
font-size: 1.05rem;
|
|
207
251
|
text-transform: uppercase;
|
|
208
|
-
letter-spacing: 0.
|
|
252
|
+
letter-spacing: 0.025em;
|
|
209
253
|
}
|
|
210
254
|
|
|
211
255
|
.long-text h4 {
|
|
@@ -218,6 +262,41 @@ pre {
|
|
|
218
262
|
font-style: italic;
|
|
219
263
|
font-size: 1rem;
|
|
220
264
|
}
|
|
265
|
+
|
|
266
|
+
.long-text .sans-text {
|
|
267
|
+
font-family: var(--font-sans);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.long-text .sans-text p {
|
|
271
|
+
margin-top: 0.8rem;
|
|
272
|
+
margin-bottom: 0.8rem;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.long-text .sans-text h1 {
|
|
276
|
+
font-family: var(--font-sans);
|
|
277
|
+
font-size: 1.75rem;
|
|
278
|
+
font-weight: 380;
|
|
279
|
+
margin-top: 1rem;
|
|
280
|
+
margin-bottom: 1.2rem;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.long-text .sans-text h2 {
|
|
284
|
+
font-family: var(--font-sans);
|
|
285
|
+
font-size: 1.25rem;
|
|
286
|
+
font-weight: 440;
|
|
287
|
+
margin-top: 1rem;
|
|
288
|
+
margin-bottom: 0.8rem;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.long-text .sans-text h3 {
|
|
292
|
+
font-family: var(--font-sans);
|
|
293
|
+
font-size: 1.03rem;
|
|
294
|
+
font-weight: var(--font-weight-sans-bold);
|
|
295
|
+
text-transform: uppercase;
|
|
296
|
+
letter-spacing: 0.03em;
|
|
297
|
+
margin-top: 1.1rem;
|
|
298
|
+
margin-bottom: 0.8rem;
|
|
299
|
+
}
|
|
221
300
|
{% endblock long_text_styles %}
|
|
222
301
|
|
|
223
302
|
{% block table_styles %}
|
|
@@ -230,12 +309,14 @@ table {
|
|
|
230
309
|
border-collapse: collapse;
|
|
231
310
|
word-break: break-word; /* long words/URLs wrap instead of inflating the column */
|
|
232
311
|
border: 1px solid var(--color-border-hint);
|
|
312
|
+
line-height: 1.3; /* Tables tigher but not as tight as headers */
|
|
233
313
|
}
|
|
234
314
|
|
|
235
315
|
th {
|
|
236
316
|
text-transform: uppercase;
|
|
237
|
-
letter-spacing: 0.
|
|
317
|
+
letter-spacing: 0.03em;
|
|
238
318
|
border-bottom: 1px solid var(--color-border-hint);
|
|
319
|
+
line-height: 1.2;
|
|
239
320
|
}
|
|
240
321
|
|
|
241
322
|
th, td {
|
|
@@ -281,14 +362,17 @@ nav {
|
|
|
281
362
|
{% block footnote_styles %}
|
|
282
363
|
/* Footnotes. */
|
|
283
364
|
sup {
|
|
284
|
-
font-size:
|
|
365
|
+
font-size: 85%;
|
|
285
366
|
}
|
|
286
367
|
|
|
287
368
|
.footnote-ref a, .footnote {
|
|
369
|
+
font-family: var(--font-sans);
|
|
288
370
|
text-decoration: none;
|
|
289
371
|
padding: 0 0.15rem;
|
|
290
372
|
border-radius: 4px;
|
|
291
373
|
transition: all 0.15s ease-in-out;
|
|
374
|
+
font-style: normal;
|
|
375
|
+
font-weight: 500;
|
|
292
376
|
}
|
|
293
377
|
|
|
294
378
|
.footnote-ref a:hover, .footnote:hover {
|
|
@@ -308,11 +392,50 @@ sup {
|
|
|
308
392
|
.table-container {
|
|
309
393
|
width: calc(100vw - 6rem);
|
|
310
394
|
}
|
|
395
|
+
|
|
396
|
+
/* Apply shadow to long-text containers on larger screens */
|
|
397
|
+
.long-text {
|
|
398
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 -2px 6px -1px rgba(0, 0, 0, 0.07);
|
|
399
|
+
}
|
|
400
|
+
/* But remove shadow when wrapped in no-shadow class */
|
|
401
|
+
.no-shadow .long-text {
|
|
402
|
+
box-shadow: none !important;
|
|
403
|
+
}
|
|
311
404
|
}
|
|
312
405
|
|
|
406
|
+
/* Make narrower screens more usable for lists and tables. */
|
|
313
407
|
@media (max-width: 768px) {
|
|
408
|
+
/* Prevent horizontal scrolling on the body */
|
|
409
|
+
body {
|
|
410
|
+
overflow-x: hidden;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* Constrain the long-text container */
|
|
414
|
+
.long-text {
|
|
415
|
+
max-width: 100%;
|
|
416
|
+
overflow-x: hidden;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* Make table containers scrollable without affecting page layout */
|
|
420
|
+
.table-container {
|
|
421
|
+
max-width: 100%;
|
|
422
|
+
overflow-x: auto;
|
|
423
|
+
transform: none;
|
|
424
|
+
left: 0;
|
|
425
|
+
position: relative;
|
|
426
|
+
margin-left: auto;
|
|
427
|
+
margin-right: auto;
|
|
428
|
+
}
|
|
429
|
+
|
|
314
430
|
table {
|
|
315
431
|
font-size: var(--font-size-smaller);
|
|
432
|
+
/* Tables can be wider than container */
|
|
433
|
+
width: auto;
|
|
434
|
+
min-width: 100%;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
ul, ol {
|
|
438
|
+
margin-left: 1rem;
|
|
316
439
|
}
|
|
317
440
|
}
|
|
318
441
|
{% endblock responsive_styles %}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- simple_webpage begin main_content block -->
|
|
4
4
|
{% block main_content %}
|
|
5
|
-
<div class="long-text container max-w-3xl mx-auto bg-white py-
|
|
5
|
+
<div class="long-text container max-w-3xl mx-auto bg-white py-4 px-6 md:px-16">
|
|
6
6
|
{% block page_title %}
|
|
7
7
|
{% if title and add_title_h1 %}
|
|
8
8
|
<h1 class="text-center text-4xl mt-6 mb-6">{{ title }}</h1>
|
kash/workspaces/__init__.py
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
from kash.workspaces.selections import Selection, SelectionHistory
|
|
2
|
+
from kash.workspaces.workspace_dirs import (
|
|
3
|
+
enclosing_ws_dir,
|
|
4
|
+
global_ws_dir,
|
|
5
|
+
is_global_ws_dir,
|
|
6
|
+
is_ws_dir,
|
|
7
|
+
)
|
|
2
8
|
from kash.workspaces.workspaces import (
|
|
3
9
|
Workspace,
|
|
10
|
+
_switch_ws_settings,
|
|
4
11
|
current_ignore,
|
|
5
12
|
current_ws,
|
|
6
13
|
get_global_ws,
|
|
7
14
|
get_ws,
|
|
8
|
-
global_ws_dir,
|
|
9
15
|
resolve_ws,
|
|
10
|
-
switch_to_ws,
|
|
11
16
|
ws_param_value,
|
|
12
17
|
)
|
|
13
18
|
|
|
14
19
|
__all__ = [
|
|
15
20
|
"Selection",
|
|
16
21
|
"SelectionHistory",
|
|
22
|
+
"enclosing_ws_dir",
|
|
23
|
+
"global_ws_dir",
|
|
24
|
+
"is_global_ws_dir",
|
|
25
|
+
"is_ws_dir",
|
|
17
26
|
"Workspace",
|
|
18
27
|
"current_ignore",
|
|
19
28
|
"current_ws",
|
|
@@ -21,6 +30,6 @@ __all__ = [
|
|
|
21
30
|
"get_ws",
|
|
22
31
|
"global_ws_dir",
|
|
23
32
|
"resolve_ws",
|
|
33
|
+
"_switch_ws_settings",
|
|
24
34
|
"ws_param_value",
|
|
25
|
-
"switch_to_ws",
|
|
26
35
|
]
|
kash/workspaces/selections.py
CHANGED
|
@@ -6,13 +6,13 @@ from pathlib import Path
|
|
|
6
6
|
from typing import TypeVar
|
|
7
7
|
|
|
8
8
|
from frontmatter_format import new_yaml, yaml_util
|
|
9
|
-
from prettyfmt import fmt_lines
|
|
9
|
+
from prettyfmt import fmt_count_items, fmt_lines
|
|
10
10
|
from pydantic import BaseModel, Field, PrivateAttr, field_serializer, field_validator
|
|
11
11
|
|
|
12
12
|
from kash.config.logger import get_logger
|
|
13
13
|
from kash.model.paths_model import StorePath
|
|
14
14
|
from kash.shell.utils.native_utils import native_trash
|
|
15
|
-
from kash.utils.common.format_utils import
|
|
15
|
+
from kash.utils.common.format_utils import fmt_loc
|
|
16
16
|
from kash.utils.errors import InvalidInput, InvalidOperation
|
|
17
17
|
|
|
18
18
|
log = get_logger(__name__)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from functools import cache
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from kash.config.logger import get_logger
|
|
8
|
+
from kash.config.settings import global_settings, resolve_and_create_dirs
|
|
9
|
+
from kash.file_storage.metadata_dirs import MetadataDirs
|
|
10
|
+
from kash.utils.errors import InvalidInput
|
|
11
|
+
|
|
12
|
+
log = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cache
|
|
16
|
+
def global_ws_dir() -> Path:
|
|
17
|
+
kb_path = resolve_and_create_dirs(global_settings().global_ws_dir, is_dir=True)
|
|
18
|
+
log.debug("Global workspace path: %s", kb_path)
|
|
19
|
+
return kb_path
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_global_ws_dir(path: Path) -> bool:
|
|
23
|
+
return path.resolve() == global_settings().global_ws_dir
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_ws_dir(path: Path) -> bool:
|
|
27
|
+
dirs = MetadataDirs(path, False)
|
|
28
|
+
return dirs.is_initialized()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def enclosing_ws_dir(path: Path | None = None) -> Path | None:
|
|
32
|
+
"""
|
|
33
|
+
Get the workspace directory enclosing the given path, or of the current
|
|
34
|
+
working directory if no path is given.
|
|
35
|
+
"""
|
|
36
|
+
if not path:
|
|
37
|
+
path = Path(".")
|
|
38
|
+
|
|
39
|
+
path = path.absolute()
|
|
40
|
+
while path != Path("/"):
|
|
41
|
+
if is_ws_dir(path):
|
|
42
|
+
return path
|
|
43
|
+
path = path.parent
|
|
44
|
+
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def normalize_workspace_name(ws_name: str) -> str:
|
|
49
|
+
return str(ws_name).strip().rstrip("/")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check_strict_workspace_name(ws_name: str) -> str:
|
|
53
|
+
ws_name = normalize_workspace_name(ws_name)
|
|
54
|
+
if not re.match(r"^[\w.-]+$", ws_name):
|
|
55
|
+
raise InvalidInput(
|
|
56
|
+
f"Use an alphanumeric name (- and . also allowed) for the workspace name: `{ws_name}`"
|
|
57
|
+
)
|
|
58
|
+
return ws_name
|
|
@@ -24,7 +24,7 @@ def import_url(ws: FileStore, url: Url) -> Item:
|
|
|
24
24
|
)
|
|
25
25
|
item = Item(ItemType.resource, url=canon_url, format=Format.url)
|
|
26
26
|
# No need to overwrite any resource we already have for the identical URL.
|
|
27
|
-
store_path = ws.save(item,
|
|
27
|
+
store_path = ws.save(item, skip_dup_names=True)
|
|
28
28
|
# Load to fill in any metadata we may already have.
|
|
29
29
|
item = ws.load(store_path)
|
|
30
30
|
return item
|
|
@@ -45,7 +45,7 @@ def import_and_load(ws: FileStore, locator: Locator | str) -> Item:
|
|
|
45
45
|
# It's already a StorePath.
|
|
46
46
|
item = ws.load(locator)
|
|
47
47
|
else:
|
|
48
|
-
log.
|
|
48
|
+
log.info("Importing locator as local path: %r", locator)
|
|
49
49
|
path = Path(locator)
|
|
50
50
|
if not path.exists():
|
|
51
51
|
raise InvalidInput(f"File not found: {path}")
|
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from chopdiff.divs.parse_divs import parse_divs
|
|
4
4
|
from flowmark import Wrap
|
|
5
5
|
from frontmatter_format import fmf_read, fmf_read_frontmatter_raw
|
|
6
|
-
from prettyfmt import fmt_size_dual
|
|
6
|
+
from prettyfmt import fmt_count_items, fmt_size_dual
|
|
7
7
|
from rich.box import SQUARE
|
|
8
8
|
from rich.panel import Panel
|
|
9
9
|
from rich.text import Text
|
|
@@ -15,7 +15,7 @@ from kash.model.items_model import ItemType
|
|
|
15
15
|
from kash.shell.output.kerm_code_utils import click_to_paste
|
|
16
16
|
from kash.shell.output.shell_formatting import format_name_and_value
|
|
17
17
|
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
18
|
-
from kash.utils.common.format_utils import
|
|
18
|
+
from kash.utils.common.format_utils import fmt_loc
|
|
19
19
|
from kash.utils.file_formats.chat_format import ChatHistory
|
|
20
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
|
kash/workspaces/workspaces.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from functools import cache
|
|
5
5
|
from pathlib import Path
|
|
@@ -13,10 +13,13 @@ from kash.config.settings import (
|
|
|
13
13
|
global_settings,
|
|
14
14
|
resolve_and_create_dirs,
|
|
15
15
|
)
|
|
16
|
+
from kash.config.text_styles import STYLE_HINT
|
|
16
17
|
from kash.file_storage.metadata_dirs import MetadataDirs
|
|
17
18
|
from kash.model.params_model import GLOBAL_PARAMS, RawParamValues
|
|
19
|
+
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
18
20
|
from kash.utils.errors import FileNotFound, InvalidInput, InvalidState
|
|
19
21
|
from kash.utils.file_utils.ignore_files import IgnoreFilter, is_ignored_default
|
|
22
|
+
from kash.workspaces.workspace_dirs import check_strict_workspace_name, is_global_ws_dir, is_ws_dir
|
|
20
23
|
from kash.workspaces.workspace_registry import WorkspaceInfo, get_ws_registry
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
@@ -25,19 +28,6 @@ if TYPE_CHECKING:
|
|
|
25
28
|
log = get_logger(__name__)
|
|
26
29
|
|
|
27
30
|
|
|
28
|
-
def normalize_workspace_name(ws_name: str) -> str:
|
|
29
|
-
return str(ws_name).strip().rstrip("/")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def check_strict_workspace_name(ws_name: str) -> str:
|
|
33
|
-
ws_name = normalize_workspace_name(ws_name)
|
|
34
|
-
if not re.match(r"^[\w.-]+$", ws_name):
|
|
35
|
-
raise InvalidInput(
|
|
36
|
-
f"Use an alphanumeric name (- and . also allowed) for the workspace name: `{ws_name}`"
|
|
37
|
-
)
|
|
38
|
-
return ws_name
|
|
39
|
-
|
|
40
|
-
|
|
41
31
|
class Workspace(ABC):
|
|
42
32
|
"""
|
|
43
33
|
A workspace is the context for actions and is tied to a folder on disk.
|
|
@@ -59,50 +49,6 @@ class Workspace(ABC):
|
|
|
59
49
|
def base_dir(self) -> Path:
|
|
60
50
|
"""The base directory for this workspace."""
|
|
61
51
|
|
|
62
|
-
def __enter__(self):
|
|
63
|
-
"""
|
|
64
|
-
Context manager to set this workspace as the current workspace.
|
|
65
|
-
"""
|
|
66
|
-
from kash.workspaces.workspaces import current_ws_context
|
|
67
|
-
|
|
68
|
-
self._token = current_ws_context.set(self.base_dir)
|
|
69
|
-
return self
|
|
70
|
-
|
|
71
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
72
|
-
"""
|
|
73
|
-
Restore the previous workspace context.
|
|
74
|
-
"""
|
|
75
|
-
from kash.workspaces.workspaces import current_ws_context
|
|
76
|
-
|
|
77
|
-
current_ws_context.reset(self._token)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
current_ws_context: contextvars.ContextVar[Path | None] = contextvars.ContextVar(
|
|
81
|
-
"current_ws_context", default=None
|
|
82
|
-
)
|
|
83
|
-
"""
|
|
84
|
-
Context variable that tracks the current workspace. Only used if it is
|
|
85
|
-
explicitly set with a `with ws.as_current()` block.
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def is_ws_dir(path: Path) -> bool:
|
|
90
|
-
dirs = MetadataDirs(path, False)
|
|
91
|
-
return dirs.is_initialized()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def enclosing_ws_dir(path: Path) -> Path | None:
|
|
95
|
-
"""
|
|
96
|
-
Get the workspace directory enclosing the given path (itself or a parent or None).
|
|
97
|
-
"""
|
|
98
|
-
path = path.absolute()
|
|
99
|
-
while path != Path("/"):
|
|
100
|
-
if is_ws_dir(path):
|
|
101
|
-
return path
|
|
102
|
-
path = path.parent
|
|
103
|
-
|
|
104
|
-
return None
|
|
105
|
-
|
|
106
52
|
|
|
107
53
|
def resolve_ws(name: str | Path) -> WorkspaceInfo:
|
|
108
54
|
"""
|
|
@@ -137,10 +83,10 @@ def resolve_ws(name: str | Path) -> WorkspaceInfo:
|
|
|
137
83
|
|
|
138
84
|
ws_name = check_strict_workspace_name(resolved.name)
|
|
139
85
|
|
|
140
|
-
return WorkspaceInfo(ws_name, resolved,
|
|
86
|
+
return WorkspaceInfo(ws_name, resolved, is_global_ws_dir(resolved))
|
|
141
87
|
|
|
142
88
|
|
|
143
|
-
def get_ws(name_or_path: str | Path, auto_init: bool = True) ->
|
|
89
|
+
def get_ws(name_or_path: str | Path, auto_init: bool = True) -> FileStore:
|
|
144
90
|
"""
|
|
145
91
|
Get a workspace by name or path. Adds to the in-memory registry so we reuse it.
|
|
146
92
|
With `auto_init` true, will initialize the workspace if it is not already initialized.
|
|
@@ -162,18 +108,14 @@ def global_ws_dir() -> Path:
|
|
|
162
108
|
return kb_path
|
|
163
109
|
|
|
164
110
|
|
|
165
|
-
def
|
|
166
|
-
return path.name.lower() == GLOBAL_WS_NAME.lower()
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def get_global_ws() -> "FileStore":
|
|
111
|
+
def get_global_ws() -> FileStore:
|
|
170
112
|
"""
|
|
171
113
|
Get the global_ws workspace.
|
|
172
114
|
"""
|
|
173
115
|
return get_ws_registry().load(GLOBAL_WS_NAME, global_ws_dir(), True)
|
|
174
116
|
|
|
175
117
|
|
|
176
|
-
def
|
|
118
|
+
def _switch_ws_settings(base_dir: Path) -> FileStore:
|
|
177
119
|
"""
|
|
178
120
|
Switch the current workspace to the given directory.
|
|
179
121
|
Updates logging and cache directories to be within that workspace.
|
|
@@ -199,41 +141,35 @@ def switch_to_ws(base_dir: Path) -> "FileStore":
|
|
|
199
141
|
return get_ws_registry().load(info.name, info.base_dir, info.is_global_ws)
|
|
200
142
|
|
|
201
143
|
|
|
202
|
-
def
|
|
203
|
-
"""
|
|
204
|
-
Infer the current workspace from context or the current working directory.
|
|
205
|
-
Does not load the workspace.
|
|
206
|
-
"""
|
|
207
|
-
# First check if we have an explicit workspace context.
|
|
208
|
-
override_dir = current_ws_context.get()
|
|
209
|
-
if override_dir:
|
|
210
|
-
return override_dir, is_global_ws_path(override_dir)
|
|
211
|
-
|
|
212
|
-
# Fall back to detecting from the current working directory.
|
|
213
|
-
dir = enclosing_ws_dir(Path("."))
|
|
214
|
-
is_global_ws = is_global_ws_path(dir) if dir else False
|
|
215
|
-
if not dir or is_global_ws:
|
|
216
|
-
dir = global_ws_dir()
|
|
217
|
-
return dir, is_global_ws
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def current_ws(silent: bool = False) -> "FileStore":
|
|
144
|
+
def current_ws(silent: bool = False) -> FileStore:
|
|
221
145
|
"""
|
|
222
146
|
Get the current workspace based on the current working directory.
|
|
223
147
|
Loads and registers the workspace if it is not already loaded.
|
|
224
|
-
|
|
148
|
+
|
|
149
|
+
As a convenience, this call also auto-updates logging and cache directories
|
|
150
|
+
if this has changed.
|
|
225
151
|
"""
|
|
226
|
-
|
|
152
|
+
from kash.exec.runtime_settings import current_ws_context
|
|
153
|
+
|
|
154
|
+
ws_context = current_ws_context()
|
|
155
|
+
base_dir = ws_context.current_ws_dir
|
|
227
156
|
if not base_dir:
|
|
228
157
|
raise InvalidState(
|
|
229
158
|
f"No workspace found in: {fmt_path(Path('.').absolute(), resolve=False)}\n"
|
|
230
159
|
"Create one with the `workspace` command."
|
|
231
160
|
)
|
|
232
161
|
|
|
233
|
-
ws =
|
|
162
|
+
ws = _switch_ws_settings(base_dir)
|
|
234
163
|
|
|
235
164
|
if not silent:
|
|
236
|
-
ws.log_workspace_info(once=True)
|
|
165
|
+
did_log = ws.log_workspace_info(once=True)
|
|
166
|
+
if did_log and ws.is_global_ws and not ws_context.override_dir:
|
|
167
|
+
PrintHooks.spacer()
|
|
168
|
+
log.warning("Note you are currently using the default global workspace.")
|
|
169
|
+
cprint(
|
|
170
|
+
"Create or switch to another workspace with the `workspace` command.",
|
|
171
|
+
style=STYLE_HINT,
|
|
172
|
+
)
|
|
237
173
|
|
|
238
174
|
return ws
|
|
239
175
|
|
|
@@ -13,8 +13,6 @@ from kash.commands.help.welcome import welcome
|
|
|
13
13
|
from kash.config.logger import get_logger
|
|
14
14
|
from kash.config.settings import RECOMMENDED_PKGS, check_kerm_code_support
|
|
15
15
|
from kash.config.text_styles import LOGO_NAME, STYLE_HINT
|
|
16
|
-
from kash.local_server.local_server import start_ui_server
|
|
17
|
-
from kash.local_server.local_url_formatters import enable_local_urls
|
|
18
16
|
from kash.mcp.mcp_server_commands import start_mcp_server
|
|
19
17
|
from kash.shell.output.shell_output import PrintHooks, cprint
|
|
20
18
|
from kash.shell.version import get_version_tag
|
|
@@ -89,6 +87,10 @@ def load_into_xonsh():
|
|
|
89
87
|
# Currently only Kerm supports our advanced UI with Kerm codes.
|
|
90
88
|
supports_kerm_codes = check_kerm_code_support()
|
|
91
89
|
if supports_kerm_codes:
|
|
90
|
+
# Don't pay for import until needed.
|
|
91
|
+
from kash.local_server.local_server import start_ui_server
|
|
92
|
+
from kash.local_server.local_url_formatters import enable_local_urls
|
|
93
|
+
|
|
92
94
|
start_ui_server()
|
|
93
95
|
enable_local_urls(True)
|
|
94
96
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kash-shell
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.13
|
|
4
4
|
Summary: The knowledge agent shell (core)
|
|
5
5
|
Project-URL: Repository, https://github.com/jlevy/kash-shell
|
|
6
6
|
Author-email: Joshua Levy <joshua@cal.berkeley.edu>
|
|
@@ -20,17 +20,16 @@ Requires-Dist: anyio>=4.8.0
|
|
|
20
20
|
Requires-Dist: audioop-lts>=0.2.1; python_version >= '3.13'
|
|
21
21
|
Requires-Dist: cachetools>=5.5.2
|
|
22
22
|
Requires-Dist: chopdiff>=0.2.1
|
|
23
|
-
Requires-Dist: clideps>=0.1.
|
|
23
|
+
Requires-Dist: clideps>=0.1.4
|
|
24
24
|
Requires-Dist: colour>=0.1.5
|
|
25
25
|
Requires-Dist: cssselect>=1.2.0
|
|
26
26
|
Requires-Dist: deepgram-sdk>=3.10.1
|
|
27
27
|
Requires-Dist: dunamai>=1.23.0
|
|
28
28
|
Requires-Dist: fastapi>=0.115.11
|
|
29
|
-
Requires-Dist: flowmark>=0.4.
|
|
29
|
+
Requires-Dist: flowmark>=0.4.6
|
|
30
30
|
Requires-Dist: frontmatter-format>=0.2.1
|
|
31
31
|
Requires-Dist: funlog>=0.2.0
|
|
32
32
|
Requires-Dist: humanfriendly>=10.0
|
|
33
|
-
Requires-Dist: inflect>=7.5.0
|
|
34
33
|
Requires-Dist: inquirerpy>=0.3.4
|
|
35
34
|
Requires-Dist: jinja2>=3.1.6
|
|
36
35
|
Requires-Dist: justext>=3.0.2
|
|
@@ -43,6 +42,7 @@ Requires-Dist: openai>=1.66.3
|
|
|
43
42
|
Requires-Dist: pandas>=2.2.3
|
|
44
43
|
Requires-Dist: patch-ng>=1.18.1
|
|
45
44
|
Requires-Dist: pathspec>=0.12.1
|
|
45
|
+
Requires-Dist: pluralizer>=1.2.0
|
|
46
46
|
Requires-Dist: prettyfmt>=0.3.1
|
|
47
47
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
48
48
|
Requires-Dist: pydantic>=2.10.6
|