kash-shell 0.3.15__py3-none-any.whl → 0.3.16__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/render_as_html.py +3 -1
- kash/actions/core/tabbed_webpage_generate.py +3 -1
- kash/config/colors.py +87 -13
- kash/file_storage/file_store.py +71 -53
- kash/file_storage/item_file_format.py +0 -2
- kash/local_server/local_server_routes.py +1 -1
- kash/model/actions_model.py +5 -1
- kash/model/items_model.py +39 -38
- kash/shell/completions/shell_completions.py +1 -1
- kash/utils/file_utils/file_formats_model.py +6 -4
- kash/web_gen/simple_webpage.py +4 -1
- kash/web_gen/tabbed_webpage.py +8 -3
- kash/web_gen/templates/base_styles.css.jinja +41 -2
- kash/web_gen/templates/base_webpage.html.jinja +128 -0
- kash/web_gen/templates/item_view.html.jinja +4 -2
- kash/web_gen/templates/simple_webpage.html.jinja +24 -0
- kash/xontrib/kash_extension.py +3 -2
- {kash_shell-0.3.15.dist-info → kash_shell-0.3.16.dist-info}/METADATA +1 -1
- {kash_shell-0.3.15.dist-info → kash_shell-0.3.16.dist-info}/RECORD +22 -22
- {kash_shell-0.3.15.dist-info → kash_shell-0.3.16.dist-info}/WHEEL +0 -0
- {kash_shell-0.3.15.dist-info → kash_shell-0.3.16.dist-info}/entry_points.txt +0 -0
- {kash_shell-0.3.15.dist-info → kash_shell-0.3.16.dist-info}/licenses/LICENSE +0 -0
|
@@ -27,7 +27,9 @@ def render_as_html(input: ActionInput, no_title: bool = False) -> ActionResult:
|
|
|
27
27
|
"""
|
|
28
28
|
if len(input.items) == 1:
|
|
29
29
|
input_item = input.items[0]
|
|
30
|
-
html_body = simple_webpage_render(
|
|
30
|
+
html_body = simple_webpage_render(
|
|
31
|
+
input_item, add_title_h1=not no_title, show_theme_toggle=True
|
|
32
|
+
)
|
|
31
33
|
result_item = input_item.derived_copy(
|
|
32
34
|
type=ItemType.export, format=Format.html, body=html_body
|
|
33
35
|
)
|
|
@@ -17,7 +17,9 @@ def tabbed_webpage_generate(input: ActionInput, add_title: bool = False) -> Acti
|
|
|
17
17
|
Generate a tabbed web page from a config item for the tabbed template.
|
|
18
18
|
"""
|
|
19
19
|
config_item = input.items[0]
|
|
20
|
-
html = tabbed_webpage.tabbed_webpage_generate(
|
|
20
|
+
html = tabbed_webpage.tabbed_webpage_generate(
|
|
21
|
+
config_item, add_title_h1=add_title, show_theme_toggle=True
|
|
22
|
+
)
|
|
21
23
|
|
|
22
24
|
webpage_item = Item(
|
|
23
25
|
title=config_item.title,
|
kash/config/colors.py
CHANGED
|
@@ -148,12 +148,40 @@ web_light_translucent = SimpleNamespace(
|
|
|
148
148
|
tooltip_bg=hsl_to_hex("hsla(188, 6%, 37%, 0.7)"),
|
|
149
149
|
popover_bg=hsl_to_hex("hsla(188, 6%, 37%, 0.7)"),
|
|
150
150
|
bright=hsl_to_hex("hsl(134, 43%, 60%)"),
|
|
151
|
+
success=hsl_to_hex("hsl(134, 43%, 60%)"),
|
|
151
152
|
selection="hsla(225, 61%, 82%, 0.80)",
|
|
152
153
|
scrollbar=hsl_to_hex("hsla(189, 12%, 55%, 0.9)"),
|
|
153
154
|
scrollbar_hover=hsl_to_hex("hsla(190, 12%, 38%, 0.9)"),
|
|
154
155
|
)
|
|
155
156
|
|
|
156
157
|
|
|
158
|
+
# Web dark colors
|
|
159
|
+
web_dark_translucent = SimpleNamespace(
|
|
160
|
+
primary=hsl_to_hex("hsl(188, 40%, 62%)"),
|
|
161
|
+
primary_light=hsl_to_hex("hsl(188, 50%, 72%)"),
|
|
162
|
+
secondary=hsl_to_hex("hsl(188, 12%, 65%)"),
|
|
163
|
+
bg=hsl_to_hex("hsla(220, 14%, 7%, 0.95)"),
|
|
164
|
+
bg_solid=hsl_to_hex("hsl(220, 14%, 7%)"),
|
|
165
|
+
bg_header=hsl_to_hex("hsla(188, 42%, 20%, 0.3)"),
|
|
166
|
+
bg_alt=hsl_to_hex("hsla(220, 14%, 12%, 0.5)"),
|
|
167
|
+
bg_alt_solid=hsl_to_hex("hsl(220, 14%, 12%)"),
|
|
168
|
+
text=hsl_to_hex("hsl(188, 20%, 90%)"),
|
|
169
|
+
border=hsl_to_hex("hsl(188, 8%, 25%)"),
|
|
170
|
+
border_hint=hsl_to_hex("hsla(188, 8%, 35%, 0.7)"),
|
|
171
|
+
border_accent=hsl_to_hex("hsla(305, 30%, 55%, 0.85)"),
|
|
172
|
+
hover=hsl_to_hex("hsl(188, 12%, 35%)"),
|
|
173
|
+
hover_bg=hsl_to_hex("hsla(188, 20%, 25%, 0.4)"),
|
|
174
|
+
hint=hsl_to_hex("hsl(188, 11%, 55%)"),
|
|
175
|
+
tooltip_bg=hsl_to_hex("hsla(188, 6%, 20%, 0.9)"),
|
|
176
|
+
popover_bg=hsl_to_hex("hsla(188, 6%, 20%, 0.9)"),
|
|
177
|
+
bright=hsl_to_hex("hsl(134, 43%, 60%)"),
|
|
178
|
+
success=hsl_to_hex("hsl(134, 43%, 60%)"),
|
|
179
|
+
selection=hsl_to_hex("hsla(225, 61%, 40%, 0.40)"),
|
|
180
|
+
scrollbar=hsl_to_hex("hsla(189, 12%, 35%, 0.9)"),
|
|
181
|
+
scrollbar_hover=hsl_to_hex("hsla(190, 12%, 50%, 0.9)"),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
157
185
|
rich_terminal_dark = TerminalTheme(
|
|
158
186
|
hex_to_int(terminal_dark.background),
|
|
159
187
|
hex_to_int(terminal_dark.foreground),
|
|
@@ -211,9 +239,6 @@ rich_terminal_light = TerminalTheme(
|
|
|
211
239
|
# We default to light colors for Rich content in HTML.
|
|
212
240
|
rich_terminal = rich_terminal_light
|
|
213
241
|
|
|
214
|
-
# Only support light web colors for now.
|
|
215
|
-
web = web_light_translucent
|
|
216
|
-
|
|
217
242
|
# Logical colors
|
|
218
243
|
logical = SimpleNamespace(
|
|
219
244
|
concept_dark=terminal.green_dark,
|
|
@@ -234,18 +259,23 @@ logical = SimpleNamespace(
|
|
|
234
259
|
)
|
|
235
260
|
|
|
236
261
|
|
|
237
|
-
def consolidate_color_vars(
|
|
262
|
+
def consolidate_color_vars(
|
|
263
|
+
overrides: dict[str, str] | None = None, web_colors: SimpleNamespace | None = None
|
|
264
|
+
) -> dict[str, str]:
|
|
238
265
|
"""
|
|
239
266
|
Consolidate all color variables into a single dictionary with appropriate prefixes.
|
|
240
267
|
Terminal variables have no prefix, while web and logical variables have "color-" prefix.
|
|
241
268
|
"""
|
|
242
269
|
if overrides is None:
|
|
243
270
|
overrides = {}
|
|
271
|
+
if web_colors is None:
|
|
272
|
+
web_colors = web_light_translucent
|
|
273
|
+
|
|
244
274
|
return {
|
|
245
275
|
# Terminal variables (no prefix)
|
|
246
276
|
**terminal.__dict__,
|
|
247
277
|
# Web and logical variables with "color-" prefix
|
|
248
|
-
**{f"color-{k}": v for k, v in
|
|
278
|
+
**{f"color-{k}": v for k, v in web_colors.__dict__.items()},
|
|
249
279
|
**{f"color-{k}": v for k, v in logical.__dict__.items()},
|
|
250
280
|
# Overrides take precedence (assume they already have correct prefixes)
|
|
251
281
|
**overrides,
|
|
@@ -262,19 +292,63 @@ def normalize_var_names(variables: dict[str, str]) -> dict[str, str]:
|
|
|
262
292
|
|
|
263
293
|
def generate_css_vars(overrides: dict[str, str] | None = None) -> str:
|
|
264
294
|
"""
|
|
265
|
-
Generate CSS variables for
|
|
295
|
+
Generate CSS variables for terminal and both light and dark themes.
|
|
266
296
|
"""
|
|
267
297
|
if overrides is None:
|
|
268
298
|
overrides = {}
|
|
269
|
-
normalized_vars = normalize_var_names(consolidate_color_vars(overrides))
|
|
270
299
|
|
|
271
|
-
#
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
300
|
+
# Get base variables (terminal colors stay the same)
|
|
301
|
+
base_vars = normalize_var_names({k: v for k, v in terminal.__dict__.items()})
|
|
302
|
+
|
|
303
|
+
# Get light theme color variables
|
|
304
|
+
light_color_vars = normalize_var_names(
|
|
305
|
+
{f"color-{k}": v for k, v in web_light_translucent.__dict__.items()}
|
|
306
|
+
)
|
|
307
|
+
light_color_vars.update(
|
|
308
|
+
normalize_var_names({f"color-{k}": v for k, v in logical.__dict__.items()})
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Get dark theme color variables
|
|
312
|
+
dark_color_vars = normalize_var_names(
|
|
313
|
+
{f"color-{k}": v for k, v in web_dark_translucent.__dict__.items()}
|
|
314
|
+
)
|
|
315
|
+
dark_color_vars.update(
|
|
316
|
+
normalize_var_names({f"color-{k}": v for k, v in logical.__dict__.items()})
|
|
317
|
+
)
|
|
276
318
|
|
|
277
|
-
|
|
319
|
+
# Apply overrides
|
|
320
|
+
if overrides:
|
|
321
|
+
normalized_overrides = normalize_var_names(overrides)
|
|
322
|
+
light_color_vars.update(normalized_overrides)
|
|
323
|
+
dark_color_vars.update(normalized_overrides)
|
|
324
|
+
|
|
325
|
+
# Generate CSS
|
|
326
|
+
css_parts = []
|
|
327
|
+
|
|
328
|
+
# Root with all variables (defaults to light)
|
|
329
|
+
css_parts.append(":root {")
|
|
330
|
+
css_parts.extend(f" --{k}: {v};" for k, v in base_vars.items())
|
|
331
|
+
css_parts.extend(f" --{k}: {v};" for k, v in light_color_vars.items())
|
|
332
|
+
css_parts.append("}\n")
|
|
333
|
+
|
|
334
|
+
# Light theme (only color- variables)
|
|
335
|
+
css_parts.append('[data-theme="light"] {')
|
|
336
|
+
css_parts.extend(f" --{k}: {v};" for k, v in light_color_vars.items())
|
|
337
|
+
css_parts.append("}\n")
|
|
338
|
+
|
|
339
|
+
# Dark theme (only color- variables)
|
|
340
|
+
css_parts.append('[data-theme="dark"] {')
|
|
341
|
+
css_parts.extend(f" --{k}: {v};" for k, v in dark_color_vars.items())
|
|
342
|
+
css_parts.append("}\n")
|
|
343
|
+
|
|
344
|
+
# Print media
|
|
345
|
+
css_parts.append("@media print {")
|
|
346
|
+
css_parts.append(' :root, [data-theme="dark"] {')
|
|
347
|
+
css_parts.extend(f" --{k}: {v} !important;" for k, v in light_color_vars.items())
|
|
348
|
+
css_parts.append(" }")
|
|
349
|
+
css_parts.append("}")
|
|
350
|
+
|
|
351
|
+
return "\n".join(css_parts)
|
|
278
352
|
|
|
279
353
|
|
|
280
354
|
if __name__ == "__main__":
|
kash/file_storage/file_store.py
CHANGED
|
@@ -16,7 +16,11 @@ from kash.config.logger import get_log_settings, get_logger
|
|
|
16
16
|
from kash.config.text_styles import EMOJI_SAVED
|
|
17
17
|
from kash.file_storage.item_file_format import read_item, write_item
|
|
18
18
|
from kash.file_storage.metadata_dirs import MetadataDirs
|
|
19
|
-
from kash.file_storage.store_filenames import
|
|
19
|
+
from kash.file_storage.store_filenames import (
|
|
20
|
+
folder_for_type,
|
|
21
|
+
join_suffix,
|
|
22
|
+
parse_item_filename,
|
|
23
|
+
)
|
|
20
24
|
from kash.model.items_model import Item, ItemId, ItemType
|
|
21
25
|
from kash.model.paths_model import StorePath
|
|
22
26
|
from kash.shell.output.shell_output import PrintHooks
|
|
@@ -39,7 +43,9 @@ T = TypeVar("T")
|
|
|
39
43
|
P = ParamSpec("P")
|
|
40
44
|
|
|
41
45
|
|
|
42
|
-
def synchronized(
|
|
46
|
+
def synchronized(
|
|
47
|
+
method: Callable[Concatenate[SelfT, P], T],
|
|
48
|
+
) -> Callable[Concatenate[SelfT, P], T]:
|
|
43
49
|
"""
|
|
44
50
|
Simple way to synchronize a few methods.
|
|
45
51
|
"""
|
|
@@ -142,7 +148,10 @@ class FileStore(Workspace):
|
|
|
142
148
|
"""
|
|
143
149
|
name, item_type, _format, file_ext = parse_item_filename(store_path)
|
|
144
150
|
if not file_ext:
|
|
145
|
-
log.debug(
|
|
151
|
+
log.debug(
|
|
152
|
+
"Skipping file with unrecognized name or extension: %s",
|
|
153
|
+
fmt_path(store_path),
|
|
154
|
+
)
|
|
146
155
|
return None
|
|
147
156
|
|
|
148
157
|
full_suffix = join_suffix(item_type.name, file_ext.name) if item_type else file_ext.name
|
|
@@ -158,11 +167,17 @@ class FileStore(Workspace):
|
|
|
158
167
|
if old_path and old_path != store_path:
|
|
159
168
|
dup_path = old_path
|
|
160
169
|
log.info(
|
|
161
|
-
"Duplicate items (%s):\n%s",
|
|
170
|
+
"Duplicate items (%s):\n%s",
|
|
171
|
+
item_id,
|
|
172
|
+
fmt_lines([old_path, store_path]),
|
|
162
173
|
)
|
|
163
174
|
self.id_map[item_id] = store_path
|
|
164
|
-
except SkippableError as e:
|
|
165
|
-
log.warning(
|
|
175
|
+
except (ValueError, SkippableError) as e:
|
|
176
|
+
log.warning(
|
|
177
|
+
"Could not load file, skipping from store index: %s: %s",
|
|
178
|
+
fmt_path(store_path),
|
|
179
|
+
e,
|
|
180
|
+
)
|
|
166
181
|
|
|
167
182
|
return dup_path
|
|
168
183
|
|
|
@@ -219,13 +234,17 @@ class FileStore(Workspace):
|
|
|
219
234
|
@synchronized
|
|
220
235
|
def _pick_filename_for(self, item: Item, *, overwrite: bool = False) -> tuple[str, str | None]:
|
|
221
236
|
"""
|
|
222
|
-
Get a suitable filename for this item.
|
|
223
|
-
|
|
224
|
-
If
|
|
225
|
-
and in this case
|
|
237
|
+
Get a suitable filename for this item. If `overwrite` is true, use the the slugified
|
|
238
|
+
title, regardless of whether it is already in the store.
|
|
239
|
+
If `overwrite` is false, use the slugified title with a suffix to make it unique
|
|
240
|
+
(and in this case also return the old filename for this item).
|
|
226
241
|
"""
|
|
227
242
|
if overwrite:
|
|
228
|
-
log.info(
|
|
243
|
+
log.info(
|
|
244
|
+
"Picked default filename: %s for item: %s",
|
|
245
|
+
item.default_filename(),
|
|
246
|
+
item,
|
|
247
|
+
)
|
|
229
248
|
return item.default_filename(), None
|
|
230
249
|
|
|
231
250
|
slug = item.slug_name()
|
|
@@ -283,14 +302,13 @@ class FileStore(Workspace):
|
|
|
283
302
|
@synchronized
|
|
284
303
|
def store_path_for(
|
|
285
304
|
self, item: Item, *, as_tmp: bool = False, overwrite: bool = False
|
|
286
|
-
) -> tuple[StorePath,
|
|
305
|
+
) -> tuple[StorePath, StorePath | None]:
|
|
287
306
|
"""
|
|
288
307
|
Return the store path for an item. If the item already has a `store_path`, we use that.
|
|
289
308
|
Otherwise we need to find the store path or generate a new one that seems suitable.
|
|
290
309
|
|
|
291
|
-
Returns `store_path,
|
|
292
|
-
|
|
293
|
-
is the previous similarly named item with a different identity (or None there is none).
|
|
310
|
+
Returns `store_path, old_store_path` where `old_store_path` is the previous similarly
|
|
311
|
+
named item with a different identity (or None there is none).
|
|
294
312
|
|
|
295
313
|
If `as_tmp` is true, will return a path from the temporary directory in the store.
|
|
296
314
|
Normally an item is always saved to a unique store path but if `overwrite` is true,
|
|
@@ -299,17 +317,17 @@ class FileStore(Workspace):
|
|
|
299
317
|
item_id = item.item_id()
|
|
300
318
|
old_filename = None
|
|
301
319
|
if as_tmp:
|
|
302
|
-
return self._tmp_path_for(item),
|
|
320
|
+
return self._tmp_path_for(item), None
|
|
303
321
|
elif item.store_path:
|
|
304
|
-
return StorePath(item.store_path),
|
|
322
|
+
return StorePath(item.store_path), None
|
|
305
323
|
elif item_id in self.id_map and self.exists(self.id_map[item_id]):
|
|
306
324
|
# If this item has an identity and we've saved under that id before, use the same store path.
|
|
307
325
|
store_path = self.id_map[item_id]
|
|
308
326
|
log.info(
|
|
309
|
-
"
|
|
327
|
+
"When picking a store path, found an existing item with same id:\n%s",
|
|
310
328
|
fmt_lines([fmt_loc(store_path), item_id]),
|
|
311
329
|
)
|
|
312
|
-
return store_path,
|
|
330
|
+
return store_path, None
|
|
313
331
|
else:
|
|
314
332
|
# We need to pick the path and filename.
|
|
315
333
|
folder_path = folder_for_type(item.type)
|
|
@@ -320,14 +338,14 @@ class FileStore(Workspace):
|
|
|
320
338
|
if old_filename and Path(self.base_dir / folder_path / old_filename).exists():
|
|
321
339
|
old_store_path = StorePath(folder_path / old_filename)
|
|
322
340
|
|
|
323
|
-
return StorePath(store_path),
|
|
341
|
+
return StorePath(store_path), old_store_path
|
|
324
342
|
|
|
325
343
|
def _tmp_path_for(self, item: Item) -> StorePath:
|
|
326
344
|
"""
|
|
327
345
|
Find a path for an item in the tmp directory.
|
|
328
346
|
"""
|
|
329
347
|
if not item.store_path:
|
|
330
|
-
store_path,
|
|
348
|
+
store_path, _old = self.store_path_for(item, as_tmp=False)
|
|
331
349
|
return StorePath(self.dirs.tmp_dir / store_path)
|
|
332
350
|
elif (self.base_dir / item.store_path).is_relative_to(self.dirs.tmp_dir):
|
|
333
351
|
return StorePath(item.store_path)
|
|
@@ -346,14 +364,14 @@ class FileStore(Workspace):
|
|
|
346
364
|
item: Item,
|
|
347
365
|
*,
|
|
348
366
|
overwrite: bool = False,
|
|
349
|
-
skip_dup_names: bool = False,
|
|
350
367
|
as_tmp: bool = False,
|
|
351
368
|
no_format: bool = False,
|
|
352
369
|
no_frontmatter: bool = False,
|
|
353
370
|
) -> StorePath:
|
|
354
371
|
"""
|
|
355
372
|
Save the item. Uses the `store_path` if it's already set or generates a new one.
|
|
356
|
-
Updates `item.store_path`.
|
|
373
|
+
Updates `item.store_path`. An existing file can be added by having the item's
|
|
374
|
+
`external_path` set to a location (inside or outside the store).
|
|
357
375
|
|
|
358
376
|
Unless `no_format` is true, also normalizes body text formatting (for Markdown)
|
|
359
377
|
and updates the item's body to match.
|
|
@@ -363,65 +381,62 @@ class FileStore(Workspace):
|
|
|
363
381
|
If `overwrite` is true, will overwrite a file that has the same path.
|
|
364
382
|
|
|
365
383
|
If `as_tmp` is true, will save the item to a temporary file.
|
|
366
|
-
|
|
367
|
-
If `skip_dup_names` is true, will skip saving if an item if an item with a
|
|
368
|
-
matching path (based on its title) already exists.
|
|
369
384
|
"""
|
|
370
|
-
if overwrite and skip_dup_names:
|
|
371
|
-
raise ValueError("Cannot both overwrite and skip duplicate names.")
|
|
372
385
|
if overwrite and as_tmp:
|
|
373
386
|
raise ValueError("Cannot both overwrite and save to a temporary file.")
|
|
374
387
|
|
|
375
|
-
# If external
|
|
388
|
+
# If external path already exists and is within the workspace, the file was
|
|
389
|
+
# already saved (e.g. by an action that wrote the item directly to the store).
|
|
376
390
|
external_path = item.external_path and Path(item.external_path).resolve()
|
|
377
391
|
if external_path and self._is_in_store(external_path):
|
|
378
|
-
log.
|
|
392
|
+
log.info("Item with external_path already saved: %s", fmt_loc(external_path))
|
|
379
393
|
rel_path = external_path.relative_to(self.base_dir)
|
|
380
|
-
# Indicate this is
|
|
394
|
+
# Indicate this is an item with a store path, not an external path.
|
|
395
|
+
# Keep external_path set so we know body is in that file.
|
|
381
396
|
item.store_path = str(rel_path)
|
|
382
|
-
item.external_path = None
|
|
383
397
|
return StorePath(rel_path)
|
|
384
398
|
else:
|
|
385
399
|
# Otherwise it's still in memory or in a file outside the workspace and we need to save it.
|
|
386
|
-
store_path,
|
|
400
|
+
store_path, old_store_path = self.store_path_for(
|
|
387
401
|
item, as_tmp=as_tmp, overwrite=overwrite
|
|
388
402
|
)
|
|
389
403
|
|
|
390
|
-
if skip_dup_names and found:
|
|
391
|
-
log.message(
|
|
392
|
-
"Skipping save because an item of the same name already exists: %s",
|
|
393
|
-
fmt_loc(store_path),
|
|
394
|
-
)
|
|
395
|
-
item.store_path = str(store_path)
|
|
396
|
-
return store_path
|
|
397
|
-
|
|
398
404
|
full_path = self.base_dir / store_path
|
|
399
405
|
|
|
400
|
-
|
|
406
|
+
supports_frontmatter = item.format and item.format.supports_frontmatter
|
|
407
|
+
log.info(
|
|
408
|
+
"Saving item in format %s (supports_frontmatter=%s) to %s: %s",
|
|
409
|
+
item.format,
|
|
410
|
+
supports_frontmatter,
|
|
411
|
+
fmt_loc(full_path),
|
|
412
|
+
item,
|
|
413
|
+
)
|
|
401
414
|
|
|
402
|
-
# If we're overwriting an existing file, archive it first.
|
|
415
|
+
# If we're overwriting an existing file, archive it first so it is in the archive, not lost.
|
|
403
416
|
if full_path.exists():
|
|
404
417
|
try:
|
|
418
|
+
log.info(
|
|
419
|
+
"Previous file exists so will archive it: %s",
|
|
420
|
+
fmt_loc(store_path),
|
|
421
|
+
)
|
|
405
422
|
self.archive(store_path, quiet=True)
|
|
406
423
|
except Exception as e:
|
|
407
424
|
log.info("Exception archiving existing file: %s", e)
|
|
408
425
|
|
|
409
426
|
# Now save the new item.
|
|
410
427
|
try:
|
|
411
|
-
|
|
412
|
-
# For binary or unknown formats or if we're not adding frontmatter, copy the file exactly.
|
|
428
|
+
# For binary or unknown formats or if we're not adding frontmatter, copy the file.
|
|
413
429
|
if item.external_path and (no_frontmatter or not supports_frontmatter):
|
|
430
|
+
log.info(
|
|
431
|
+
"Path is an external path, so copying: %s -> %s",
|
|
432
|
+
fmt_path(item.external_path),
|
|
433
|
+
fmt_path(full_path),
|
|
434
|
+
)
|
|
414
435
|
copyfile_atomic(item.external_path, full_path, make_parents=True)
|
|
415
436
|
else:
|
|
416
437
|
# Save as a text item with frontmatter.
|
|
417
438
|
if item.external_path:
|
|
418
439
|
item.body = Path(item.external_path).read_text()
|
|
419
|
-
if overwrite and full_path.exists():
|
|
420
|
-
log.info(
|
|
421
|
-
"Overwrite is enabled and a previous file exists so will archive it: %s",
|
|
422
|
-
fmt_loc(store_path),
|
|
423
|
-
)
|
|
424
|
-
self.archive(store_path, quiet=True)
|
|
425
440
|
write_item(item, full_path, normalize=not no_format)
|
|
426
441
|
except OSError as e:
|
|
427
442
|
log.error("Error saving item: %s", e)
|
|
@@ -545,7 +560,7 @@ class FileStore(Workspace):
|
|
|
545
560
|
else:
|
|
546
561
|
# Binary or other files we just copy over as-is, preserving the name.
|
|
547
562
|
# We know the extension is recognized.
|
|
548
|
-
store_path,
|
|
563
|
+
store_path, old_store_path = self.store_path_for(item)
|
|
549
564
|
if self.exists(store_path):
|
|
550
565
|
raise FileExists(f"Resource already in store: {fmt_loc(store_path)}")
|
|
551
566
|
|
|
@@ -690,7 +705,10 @@ class FileStore(Workspace):
|
|
|
690
705
|
for warning in self.warnings:
|
|
691
706
|
log.warning("%s", warning)
|
|
692
707
|
|
|
693
|
-
log.info(
|
|
708
|
+
log.info(
|
|
709
|
+
"File store startup took %s.",
|
|
710
|
+
format_duration(self.end_time - self.start_time),
|
|
711
|
+
)
|
|
694
712
|
# TODO: Log more info like number of items by type.
|
|
695
713
|
return True
|
|
696
714
|
|
|
@@ -28,8 +28,6 @@ def write_item(item: Item, path: Path, normalize: bool = True):
|
|
|
28
28
|
By default normalizes formatting of the body text and updates the item's body.
|
|
29
29
|
"""
|
|
30
30
|
item.validate()
|
|
31
|
-
if item.is_binary:
|
|
32
|
-
raise ValueError(f"Binary items should be external files: {item}")
|
|
33
31
|
if item.format and not item.format.supports_frontmatter:
|
|
34
32
|
raise ValueError(f"Item format `{item.format.value}` does not support frontmatter: {item}")
|
|
35
33
|
|
|
@@ -244,7 +244,7 @@ def _serve_item(
|
|
|
244
244
|
media_type=mime_type,
|
|
245
245
|
)
|
|
246
246
|
else:
|
|
247
|
-
display_title = item.
|
|
247
|
+
display_title = item.pick_title() if item else str(path)
|
|
248
248
|
|
|
249
249
|
# For HEAD requests, return header with mime type only.
|
|
250
250
|
if request.method == "HEAD":
|
kash/model/actions_model.py
CHANGED
|
@@ -22,7 +22,7 @@ from kash.exec_model.shell_model import ShellResult
|
|
|
22
22
|
from kash.llm_utils import LLM, LLMName
|
|
23
23
|
from kash.llm_utils.llm_messages import Message, MessageTemplate
|
|
24
24
|
from kash.model.exec_model import ExecContext
|
|
25
|
-
from kash.model.items_model import UNTITLED, Item, ItemType
|
|
25
|
+
from kash.model.items_model import UNTITLED, Format, Item, ItemType
|
|
26
26
|
from kash.model.operations_model import Operation, Source
|
|
27
27
|
from kash.model.params_model import (
|
|
28
28
|
ALL_COMMON_PARAMS,
|
|
@@ -102,6 +102,10 @@ class ActionResult:
|
|
|
102
102
|
shell_result: ShellResult | None = None
|
|
103
103
|
"""Customize control of how the action's result is displayed in the shell."""
|
|
104
104
|
|
|
105
|
+
def get_by_format(self, format: Format) -> Item:
|
|
106
|
+
"""Convenience method to get an item for actions that return multiple formats."""
|
|
107
|
+
return next(item for item in self.items if item.format == format)
|
|
108
|
+
|
|
105
109
|
def has_hints(self) -> bool:
|
|
106
110
|
return bool(
|
|
107
111
|
self.replaces_input or self.skip_duplicates or self.path_ops or self.shell_result
|
kash/model/items_model.py
CHANGED
|
@@ -219,6 +219,12 @@ class Item:
|
|
|
219
219
|
a text document, PDF or other resource, URL, etc.
|
|
220
220
|
"""
|
|
221
221
|
|
|
222
|
+
# TODO: A few cleanups:
|
|
223
|
+
# - Consider adding aliases and tags. See also Obsidian frontmatter format:
|
|
224
|
+
# https://help.obsidian.md/Editing+and+formatting/Properties#Default%20properties
|
|
225
|
+
# - Can eliminate context here as we now have ExectContext in a contextvar.
|
|
226
|
+
# - Change store_path and external_path to a StorePath and Path instead of a str.
|
|
227
|
+
|
|
222
228
|
type: ItemType
|
|
223
229
|
state: State = State.draft
|
|
224
230
|
title: str | None = None
|
|
@@ -230,17 +236,15 @@ class Item:
|
|
|
230
236
|
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
231
237
|
modified_at: datetime | None = None
|
|
232
238
|
|
|
233
|
-
# TODO: Consider adding aliases and tags. See also Obsidian frontmatter format:
|
|
234
|
-
# https://help.obsidian.md/Editing+and+formatting/Properties#Default%20properties
|
|
235
|
-
|
|
236
239
|
# Content of the item.
|
|
237
240
|
# Text items are in body. Large or binary items may be stored externally.
|
|
241
|
+
# The external_path if present should always hold the current body of the content
|
|
242
|
+
# (and body will not be set). This is necessary for large or binary files.
|
|
238
243
|
body: str | None = None
|
|
239
244
|
external_path: str | None = None
|
|
240
245
|
original_filename: str | None = None
|
|
241
246
|
|
|
242
247
|
# Path to the item in the store, if it has been saved.
|
|
243
|
-
# TODO: Migrate this to StorePath.
|
|
244
248
|
store_path: str | None = None
|
|
245
249
|
|
|
246
250
|
# Optionally, relations to other items, including any time this item is derived from.
|
|
@@ -547,7 +551,7 @@ class Item:
|
|
|
547
551
|
if filename_stem and not prefer_title:
|
|
548
552
|
return slugify_snake(filename_stem)
|
|
549
553
|
else:
|
|
550
|
-
return slugify_snake(self.
|
|
554
|
+
return slugify_snake(self.pick_title(max_len=max_len, add_ops_suffix=True))
|
|
551
555
|
|
|
552
556
|
def default_filename(self) -> str:
|
|
553
557
|
"""
|
|
@@ -560,7 +564,16 @@ class Item:
|
|
|
560
564
|
full_suffix = self.get_full_suffix()
|
|
561
565
|
return join_suffix(slug, full_suffix)
|
|
562
566
|
|
|
563
|
-
def
|
|
567
|
+
def body_heading(self, allowed_tags: tuple[str, ...] = ("h1", "h2")) -> str | None:
|
|
568
|
+
"""
|
|
569
|
+
Get the first heading (by default h1 or h2) from the body text, if present.
|
|
570
|
+
"""
|
|
571
|
+
if self.format in [Format.markdown, Format.md_html]:
|
|
572
|
+
return first_heading(self.body_text(), allowed_tags=allowed_tags)
|
|
573
|
+
# TODO: Support HTML <h1> and <h2> as well.
|
|
574
|
+
return None
|
|
575
|
+
|
|
576
|
+
def pick_title(
|
|
564
577
|
self,
|
|
565
578
|
*,
|
|
566
579
|
max_len: int = 100,
|
|
@@ -619,41 +632,20 @@ class Item:
|
|
|
619
632
|
|
|
620
633
|
return final_text
|
|
621
634
|
|
|
622
|
-
def
|
|
623
|
-
"""
|
|
624
|
-
A display title for this item. Same as abbrev_title() but will fall back
|
|
625
|
-
to the filename if it is available.
|
|
626
|
-
"""
|
|
627
|
-
display_title = self.title
|
|
628
|
-
if not display_title and self.store_path:
|
|
629
|
-
display_title = Path(self.store_path).name
|
|
630
|
-
if not display_title:
|
|
631
|
-
display_title = self.abbrev_title(pull_body_heading=True)
|
|
632
|
-
return display_title
|
|
633
|
-
|
|
634
|
-
def abbrev_description(self, max_len: int = 1000) -> str:
|
|
635
|
+
def pick_description(self, max_len: int = 1000) -> str:
|
|
635
636
|
"""
|
|
636
637
|
Get or infer description.
|
|
637
638
|
"""
|
|
638
639
|
return abbrev_on_words(html_to_plaintext(self.description or self.body or ""), max_len)
|
|
639
640
|
|
|
640
|
-
def body_heading(self) -> str | None:
|
|
641
|
-
"""
|
|
642
|
-
Get the first h1 or h2 heading from the body text, if present.
|
|
643
|
-
"""
|
|
644
|
-
if self.format in [Format.markdown, Format.md_html]:
|
|
645
|
-
return first_heading(self.body_text(), allowed_tags=("h1", "h2"))
|
|
646
|
-
# TODO: Support HTML <h1> and <h2> as well.
|
|
647
|
-
return None
|
|
648
|
-
|
|
649
641
|
def abbrev_body(self, max_len: int) -> str:
|
|
650
642
|
"""
|
|
651
643
|
Get an abbreviated version of the body text. Must not be a binary Item.
|
|
652
644
|
Abbreviates YAML bodies like {"role": "user", "content": "Hello"} to "user Hello".
|
|
653
645
|
"""
|
|
654
|
-
body_text = self.body_text()
|
|
646
|
+
body_text = abbrev_str(self.body_text(), max_len)
|
|
655
647
|
|
|
656
|
-
# Just for aesthetics especially for titles of chat files.
|
|
648
|
+
# Just for aesthetics, especially for titles of chat files.
|
|
657
649
|
if self.type in [ItemType.chat, ItemType.config] or self.format == Format.yaml:
|
|
658
650
|
try:
|
|
659
651
|
yaml_obj = list(new_yaml().load_all(self.body_text()))
|
|
@@ -662,7 +654,7 @@ class Item:
|
|
|
662
654
|
except Exception as e:
|
|
663
655
|
log.info("Error parsing YAML body: %s", e)
|
|
664
656
|
|
|
665
|
-
return body_text
|
|
657
|
+
return abbrev_str(body_text, max_len)
|
|
666
658
|
|
|
667
659
|
@property
|
|
668
660
|
def has_body(self) -> bool:
|
|
@@ -745,9 +737,14 @@ class Item:
|
|
|
745
737
|
self,
|
|
746
738
|
other: Item | None = None,
|
|
747
739
|
update_timestamp: bool = False,
|
|
740
|
+
clear_fields=(
|
|
741
|
+
"store_path", # Will be set at save time.
|
|
742
|
+
"source", # Should be cleared so the ItemId of a copy is not the same as the original.
|
|
743
|
+
"modified_at",
|
|
744
|
+
),
|
|
748
745
|
**other_updates: Unpack[ItemUpdateOptions],
|
|
749
746
|
) -> dict[str, Any]:
|
|
750
|
-
overrides: dict[str, Any] = {
|
|
747
|
+
overrides: dict[str, Any] = {f: None for f in clear_fields}
|
|
751
748
|
if update_timestamp:
|
|
752
749
|
overrides["created_at"] = datetime.now()
|
|
753
750
|
|
|
@@ -767,8 +764,9 @@ class Item:
|
|
|
767
764
|
self, update_timestamp: bool = True, **other_updates: Unpack[ItemUpdateOptions]
|
|
768
765
|
) -> Item:
|
|
769
766
|
"""
|
|
770
|
-
Copy item with the given field updates. Resets `store_path` to None
|
|
771
|
-
|
|
767
|
+
Copy item with the given field updates. Resets `store_path` and `source` to None
|
|
768
|
+
since those should be set explicitly later. Preserves other fields, including
|
|
769
|
+
the body.
|
|
772
770
|
"""
|
|
773
771
|
new_fields = self._copy_and_update(update_timestamp=update_timestamp, **other_updates)
|
|
774
772
|
return Item(**new_fields)
|
|
@@ -783,9 +781,12 @@ class Item:
|
|
|
783
781
|
|
|
784
782
|
def derived_copy(self, **updates: Unpack[ItemUpdateOptions]) -> Item:
|
|
785
783
|
"""
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
784
|
+
Copy item with the given field updates. Resets `store_path` and `source` to None
|
|
785
|
+
since those should be set explicitly later. Preserves other fields, including
|
|
786
|
+
the body.
|
|
787
|
+
|
|
788
|
+
Same as `new_copy_with` but also updates the `derived_from` relation. If we also
|
|
789
|
+
have an action context, then use the `title_template` to derive a new title.
|
|
789
790
|
"""
|
|
790
791
|
if not self.store_path:
|
|
791
792
|
if self.relations.derived_from:
|
|
@@ -898,7 +899,7 @@ class Item:
|
|
|
898
899
|
elif self.external_path:
|
|
899
900
|
return fmt_loc(self.external_path)
|
|
900
901
|
else:
|
|
901
|
-
return repr(self.
|
|
902
|
+
return repr(self.pick_title())
|
|
902
903
|
|
|
903
904
|
def as_chat_history(self) -> ChatHistory:
|
|
904
905
|
if self.type != ItemType.chat:
|
|
@@ -106,7 +106,9 @@ class Format(Enum):
|
|
|
106
106
|
@property
|
|
107
107
|
def is_simple_text(self) -> bool:
|
|
108
108
|
"""
|
|
109
|
-
Is this plaintext or close to it, like Markdown?
|
|
109
|
+
Is this plaintext or close to it, like Markdown or Markdown with limited HTML?
|
|
110
|
+
"Simple text" should be a format that converts canonically to clean HTML.
|
|
111
|
+
Does not include full-page general HTML.
|
|
110
112
|
"""
|
|
111
113
|
return self in [self.plaintext, self.markdown, self.md_html]
|
|
112
114
|
|
|
@@ -174,8 +176,8 @@ class Format(Enum):
|
|
|
174
176
|
"""
|
|
175
177
|
Is this format compatible with frontmatter format metadata?
|
|
176
178
|
PDF and docx unfortunately won't work with frontmatter.
|
|
177
|
-
CSV does to some degree, depending on the tool, and this
|
|
178
|
-
|
|
179
|
+
CSV does to some degree, depending on the tool, and this can be useful so we support it.
|
|
180
|
+
Including JSON here (assuming it's JSON5) for similar reasons.
|
|
179
181
|
"""
|
|
180
182
|
return self in [
|
|
181
183
|
self.url,
|
|
@@ -189,7 +191,7 @@ class Format(Enum):
|
|
|
189
191
|
self.python,
|
|
190
192
|
self.shellscript,
|
|
191
193
|
self.xonsh,
|
|
192
|
-
self.csv,
|
|
194
|
+
self.csv, # Often but not always supported.
|
|
193
195
|
self.log,
|
|
194
196
|
]
|
|
195
197
|
|
kash/web_gen/simple_webpage.py
CHANGED
|
@@ -7,6 +7,7 @@ def simple_webpage_render(
|
|
|
7
7
|
item: Item,
|
|
8
8
|
page_template: str = "simple_webpage.html.jinja",
|
|
9
9
|
add_title_h1: bool = True,
|
|
10
|
+
show_theme_toggle: bool = False,
|
|
10
11
|
) -> str:
|
|
11
12
|
"""
|
|
12
13
|
Generate a simple web page from a single item.
|
|
@@ -15,10 +16,12 @@ def simple_webpage_render(
|
|
|
15
16
|
return render_web_template(
|
|
16
17
|
template_filename=page_template,
|
|
17
18
|
data={
|
|
18
|
-
"title": item.
|
|
19
|
+
"title": item.pick_title(),
|
|
19
20
|
"add_title_h1": add_title_h1,
|
|
20
21
|
"content_html": item.body_as_html(),
|
|
21
22
|
"thumbnail_url": item.thumbnail_url,
|
|
23
|
+
"enable_themes": show_theme_toggle,
|
|
24
|
+
"show_theme_toggle": show_theme_toggle,
|
|
22
25
|
},
|
|
23
26
|
)
|
|
24
27
|
|
kash/web_gen/tabbed_webpage.py
CHANGED
|
@@ -68,14 +68,14 @@ def tabbed_webpage_config(
|
|
|
68
68
|
|
|
69
69
|
tabs = [
|
|
70
70
|
TabInfo(
|
|
71
|
-
label=clean_label(item.
|
|
71
|
+
label=clean_label(item.pick_title()),
|
|
72
72
|
store_path=item.store_path,
|
|
73
73
|
thumbnail_url=get_thumbnail_url(item),
|
|
74
74
|
)
|
|
75
75
|
for item in items
|
|
76
76
|
]
|
|
77
77
|
_fill_in_ids(tabs)
|
|
78
|
-
title = summary_heading([item.
|
|
78
|
+
title = summary_heading([item.pick_title() for item in items])
|
|
79
79
|
config = TabbedWebpage(
|
|
80
80
|
title=title, tabs=tabs, show_tabs=len(tabs) > 1, add_title_h1=add_title_h1
|
|
81
81
|
)
|
|
@@ -100,7 +100,10 @@ def _load_tab_content(config: TabbedWebpage):
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
def tabbed_webpage_generate(
|
|
103
|
-
config_item: Item,
|
|
103
|
+
config_item: Item,
|
|
104
|
+
page_template: str = "base_webpage.html.jinja",
|
|
105
|
+
add_title_h1: bool = True,
|
|
106
|
+
show_theme_toggle: bool = False,
|
|
104
107
|
) -> str:
|
|
105
108
|
"""
|
|
106
109
|
Generate a web page using the supplied config.
|
|
@@ -121,6 +124,8 @@ def tabbed_webpage_generate(
|
|
|
121
124
|
"title": tabbed_webpage.title,
|
|
122
125
|
"add_title_h1": add_title_h1,
|
|
123
126
|
"content": content,
|
|
127
|
+
"enable_themes": show_theme_toggle,
|
|
128
|
+
"show_theme_toggle": show_theme_toggle,
|
|
124
129
|
},
|
|
125
130
|
)
|
|
126
131
|
|
|
@@ -176,11 +176,10 @@ ol > li {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
blockquote {
|
|
179
|
-
border-left:
|
|
179
|
+
border-left: 2px solid var(--color-primary);
|
|
180
180
|
padding-left: 1rem;
|
|
181
181
|
margin-left: 0;
|
|
182
182
|
margin-right: 0;
|
|
183
|
-
color: var(--color-secondary);
|
|
184
183
|
}
|
|
185
184
|
|
|
186
185
|
/* Inline code styling */
|
|
@@ -188,6 +187,7 @@ code {
|
|
|
188
187
|
font-family: var(--font-mono);
|
|
189
188
|
font-size: var(--font-size-mono);
|
|
190
189
|
letter-spacing: -0.025em;
|
|
190
|
+
transition: color 0.4s ease-in-out;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/* Code blocks (pre + code) */
|
|
@@ -201,6 +201,7 @@ pre {
|
|
|
201
201
|
padding: 0.2rem 0.2rem 0.1rem 0.2rem;
|
|
202
202
|
overflow-x: auto; /* Enable horizontal scrolling */
|
|
203
203
|
position: relative; /* Create new stacking context */
|
|
204
|
+
transition: background-color 0.4s ease-in-out, border-color 0.4s ease-in-out;
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
/* Reset code styling when inside pre blocks */
|
|
@@ -209,6 +210,44 @@ pre > code {
|
|
|
209
210
|
line-height: 1.5; /* Improve readability */
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
/* Copy button for code blocks */
|
|
214
|
+
.code-copy-button {
|
|
215
|
+
position: absolute;
|
|
216
|
+
top: 0;
|
|
217
|
+
right: 0;
|
|
218
|
+
background: var(--color-bg-alt);
|
|
219
|
+
color: var(--color-hint);
|
|
220
|
+
border: none;
|
|
221
|
+
border-radius: 0.25rem;
|
|
222
|
+
padding: 0.25rem;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
font-size: 0.75rem;
|
|
225
|
+
z-index: 10;
|
|
226
|
+
transition: all 0.2s ease-in-out;
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
width: 1.5rem;
|
|
231
|
+
height: 1.5rem;
|
|
232
|
+
opacity: 0.6;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.code-copy-button:hover {
|
|
236
|
+
background: var(--color-hover-bg);
|
|
237
|
+
color: var(--color-primary);
|
|
238
|
+
opacity: 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.code-copy-button.copied {
|
|
242
|
+
color: var(--color-success);
|
|
243
|
+
opacity: 1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.code-copy-button svg {
|
|
247
|
+
width: 0.875rem;
|
|
248
|
+
height: 0.875rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
212
251
|
hr {
|
|
213
252
|
border: none;
|
|
214
253
|
height: 1.5rem;
|
|
@@ -5,10 +5,32 @@
|
|
|
5
5
|
{% block meta %}
|
|
6
6
|
<meta charset="UTF-8" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<meta name="color-scheme" content="light dark">
|
|
8
9
|
{% endblock meta %}
|
|
9
10
|
|
|
10
11
|
{% block title %}<title>{{ title }}</title>{% endblock title %}
|
|
11
12
|
|
|
13
|
+
{% block dark_mode_script %}
|
|
14
|
+
<script>
|
|
15
|
+
// Set theme before body renders to prevent flash of unstyled content
|
|
16
|
+
function applyTheme(theme) {
|
|
17
|
+
document.documentElement.dataset.theme = theme;
|
|
18
|
+
localStorage.setItem('theme', theme);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// If theme toggle is enabled, respect stored preference or system preference.
|
|
22
|
+
// Otherwise default to light mode.
|
|
23
|
+
{% if enable_themes %}
|
|
24
|
+
const storedTheme = localStorage.getItem('theme');
|
|
25
|
+
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
26
|
+
const initialTheme = storedTheme || (systemPrefersDark ? 'dark' : 'light');
|
|
27
|
+
{% else %}
|
|
28
|
+
const initialTheme = 'light';
|
|
29
|
+
{% endif %}
|
|
30
|
+
applyTheme(initialTheme);
|
|
31
|
+
</script>
|
|
32
|
+
{% endblock dark_mode_script %}
|
|
33
|
+
|
|
12
34
|
{% block head_basic %}
|
|
13
35
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
14
36
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
@@ -35,6 +57,52 @@
|
|
|
35
57
|
{% block head_extra %}{% endblock head_extra %}
|
|
36
58
|
|
|
37
59
|
<style>
|
|
60
|
+
|
|
61
|
+
body {
|
|
62
|
+
background: var(--color-bg);
|
|
63
|
+
color: var(--color-text);
|
|
64
|
+
transition: background 0.4s ease-in-out, color 0.4s ease-in-out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.theme-toggle {
|
|
68
|
+
position: fixed;
|
|
69
|
+
top: 1rem;
|
|
70
|
+
right: 1rem;
|
|
71
|
+
background: transparent;
|
|
72
|
+
color: var(--color-hint);
|
|
73
|
+
border: none;
|
|
74
|
+
padding: 0;
|
|
75
|
+
border-radius: 0.3rem;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
font-size: 1rem;
|
|
78
|
+
z-index: 100;
|
|
79
|
+
transition: all 0.2s ease-in-out;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
width: 2.5rem;
|
|
84
|
+
height: 2.5rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.theme-toggle:hover {
|
|
88
|
+
background: var(--color-hover-bg);
|
|
89
|
+
color: var(--color-primary);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[data-theme="dark"] .theme-toggle {
|
|
93
|
+
color: var(--color-primary-light);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[data-theme="dark"] .theme-toggle:hover {
|
|
97
|
+
color: var(--color-bright);
|
|
98
|
+
background: var(--color-hover-bg);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.theme-toggle svg {
|
|
102
|
+
width: 1rem;
|
|
103
|
+
height: 1rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
38
106
|
{% block font_faces %}
|
|
39
107
|
/* https://fontsource.org/fonts/pt-serif/cdn */
|
|
40
108
|
/* pt-serif-latin-400-normal */
|
|
@@ -108,6 +176,14 @@
|
|
|
108
176
|
</head>
|
|
109
177
|
|
|
110
178
|
<body>
|
|
179
|
+
{% block theme_toggle %}
|
|
180
|
+
{% if show_theme_toggle %}
|
|
181
|
+
<button class="theme-toggle" aria-label="toggle dark mode">
|
|
182
|
+
<i data-feather="moon"></i>
|
|
183
|
+
</button>
|
|
184
|
+
{% endif %}
|
|
185
|
+
{% endblock theme_toggle %}
|
|
186
|
+
|
|
111
187
|
{% block body_header %}{% endblock body_header %}
|
|
112
188
|
|
|
113
189
|
{% block main_content %}
|
|
@@ -119,6 +195,53 @@
|
|
|
119
195
|
{% block scripts %}
|
|
120
196
|
<script>
|
|
121
197
|
document.addEventListener('DOMContentLoaded', () => {
|
|
198
|
+
// Add copy buttons to code blocks
|
|
199
|
+
document.querySelectorAll('pre').forEach(pre => {
|
|
200
|
+
// Skip if already has a copy button
|
|
201
|
+
if (pre.querySelector('.code-copy-button')) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const copyButton = document.createElement('button');
|
|
206
|
+
copyButton.className = 'code-copy-button';
|
|
207
|
+
copyButton.setAttribute('aria-label', 'Copy code');
|
|
208
|
+
|
|
209
|
+
const copyIcon = typeof feather !== 'undefined' ? feather.icons.copy.toSvg() : '<i data-feather="copy"></i>';
|
|
210
|
+
const checkIcon = typeof feather !== 'undefined' ? feather.icons.check.toSvg() : '<i data-feather="check"></i>';
|
|
211
|
+
|
|
212
|
+
copyButton.innerHTML = copyIcon;
|
|
213
|
+
copyButton.addEventListener('click', async () => {
|
|
214
|
+
const codeElement = pre.querySelector('code') || pre;
|
|
215
|
+
const textToCopy = (codeElement.textContent || codeElement.innerText).trim();
|
|
216
|
+
|
|
217
|
+
// Works on modern browsers.
|
|
218
|
+
navigator.clipboard.writeText(textToCopy).then(() => {
|
|
219
|
+
copyButton.innerHTML = checkIcon;
|
|
220
|
+
copyButton.classList.add('copied');
|
|
221
|
+
|
|
222
|
+
// Reset after 2 seconds
|
|
223
|
+
setTimeout(() => {
|
|
224
|
+
copyButton.innerHTML = copyIcon;
|
|
225
|
+
copyButton.classList.remove('copied');
|
|
226
|
+
}, 2000);
|
|
227
|
+
}).catch(err => {
|
|
228
|
+
console.error('Failed to copy text: ', err);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
pre.appendChild(copyButton);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Theme toggle (if present on page)
|
|
236
|
+
const themeToggleButton = document.querySelector('.theme-toggle');
|
|
237
|
+
if (themeToggleButton) {
|
|
238
|
+
themeToggleButton.addEventListener('click', () => {
|
|
239
|
+
const currentTheme = document.documentElement.dataset.theme;
|
|
240
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
241
|
+
applyTheme(newTheme);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
122
245
|
// Send messages to the parent window, in case we are in a viewport where that matters
|
|
123
246
|
// (e.g. an iframe tooltip).
|
|
124
247
|
// Request a resize of the parent viewport. This iframe size message format isn't
|
|
@@ -159,6 +282,11 @@
|
|
|
159
282
|
}
|
|
160
283
|
});
|
|
161
284
|
});
|
|
285
|
+
|
|
286
|
+
// Initialize Feather icons once at the end, after all DOM manipulation.
|
|
287
|
+
if (typeof feather !== 'undefined') {
|
|
288
|
+
feather.replace();
|
|
289
|
+
}
|
|
162
290
|
});
|
|
163
291
|
|
|
164
292
|
// Double-click to expand (e.g. expand tooltip to popover).
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.item-header {
|
|
13
|
-
padding: 0
|
|
13
|
+
padding: 0 1rem 0rem 1rem;
|
|
14
14
|
border-bottom: 1px solid var(--color-border-hint);
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -109,7 +109,7 @@ h1.item-title {
|
|
|
109
109
|
|
|
110
110
|
.item-file-info {
|
|
111
111
|
{# font-family: var(--font-sans); #}
|
|
112
|
-
padding: 0
|
|
112
|
+
padding: 0;
|
|
113
113
|
{# font-size: var(--font-size-mono-small); #}
|
|
114
114
|
word-break: break-word;
|
|
115
115
|
}
|
|
@@ -118,6 +118,8 @@ h1.item-title {
|
|
|
118
118
|
font-family: var(--font-sans);
|
|
119
119
|
font-size: var(--font-size-small);
|
|
120
120
|
letter-spacing: 0;
|
|
121
|
+
border: none;
|
|
122
|
+
background-color: transparent;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
.item-url {
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
{% extends "base_webpage.html.jinja" %}
|
|
2
2
|
|
|
3
|
+
{% block custom_styles %}
|
|
4
|
+
{{ super() }}
|
|
5
|
+
<style>
|
|
6
|
+
/* Override Tailwind's bg-white in dark mode */
|
|
7
|
+
[data-theme="dark"] .bg-white {
|
|
8
|
+
background-color: var(--color-bg-alt-solid) !important;
|
|
9
|
+
}
|
|
10
|
+
.long-text {
|
|
11
|
+
transition: background 0.4s ease-in-out, color 0.4s ease-in-out;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Ensure long-text containers respect theme */
|
|
15
|
+
[data-theme="dark"] .long-text {
|
|
16
|
+
background-color: var(--color-bg-alt-solid);
|
|
17
|
+
color: var(--color-text);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Adjust shadow for dark mode */
|
|
21
|
+
[data-theme="dark"] .long-text {
|
|
22
|
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 -2px 6px -1px rgba(0, 0, 0, 0.2);
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
25
|
+
{% endblock custom_styles %}
|
|
26
|
+
|
|
3
27
|
<!-- simple_webpage begin main_content block -->
|
|
4
28
|
{% block main_content %}
|
|
5
29
|
<div class="long-text container max-w-3xl mx-auto bg-white py-4 px-6 md:px-16">
|
kash/xontrib/kash_extension.py
CHANGED
|
@@ -12,7 +12,6 @@ Can run from the custom kash shell (main.py) or from a regular xonsh shell.
|
|
|
12
12
|
import kash.exec.command_registry
|
|
13
13
|
import kash.xonsh_custom.load_into_xonsh
|
|
14
14
|
import kash.xonsh_custom.xonsh_env
|
|
15
|
-
from kash.config.logger import get_logger
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
# We add action loading here directly in the xontrib so we expose `load` and
|
|
@@ -55,6 +54,8 @@ kash.xonsh_custom.xonsh_env.set_alias("load", load)
|
|
|
55
54
|
try:
|
|
56
55
|
kash.xonsh_custom.load_into_xonsh.load_into_xonsh()
|
|
57
56
|
except Exception as e:
|
|
57
|
+
from kash.config.logger import get_logger
|
|
58
|
+
|
|
58
59
|
log = get_logger(__name__)
|
|
59
|
-
log.error("Could not initialize kash: %s", e)
|
|
60
|
+
log.error("Could not initialize kash: %s", e, exc_info=True)
|
|
60
61
|
raise
|
|
@@ -6,12 +6,12 @@ kash/actions/core/chat.py,sha256=yCannBFa0cSpR_in-XSSuMm1x2ZZQUCKmlqzhsUfpOo,269
|
|
|
6
6
|
kash/actions/core/format_markdown_template.py,sha256=ZJbtyTSypPo2ewLiGRSyIpVf711vQMhI_-Ng-FgCs80,2991
|
|
7
7
|
kash/actions/core/markdownify.py,sha256=KjdUeY4c9EhZ5geQrn22IoBv0P_p62q4zyyOYE0NRHM,1270
|
|
8
8
|
kash/actions/core/readability.py,sha256=ljdB2rOpzfKU2FpEJ2UELIzcdOAWvdUjFsxoHRTE3xo,989
|
|
9
|
-
kash/actions/core/render_as_html.py,sha256=
|
|
9
|
+
kash/actions/core/render_as_html.py,sha256=CIPGKCjUEVNsnXmpqHCUnjGwTfEfOyCXxlYFUN8mahY,1870
|
|
10
10
|
kash/actions/core/show_webpage.py,sha256=Ggba9jkx9U-FZOcuL0lkS-SwtPNUyxVsGdeQrqwWs1s,887
|
|
11
11
|
kash/actions/core/strip_html.py,sha256=FDLN_4CKB11q5cU4NixTf7PGrAq92AjQNbKAdvQDwCY,849
|
|
12
12
|
kash/actions/core/summarize_as_bullets.py,sha256=Zwr8lNzL77pwpnW_289LQjNBijNDpTPANfFdOJA-PZ4,2070
|
|
13
13
|
kash/actions/core/tabbed_webpage_config.py,sha256=rIbzEhBTmnkbSiRZC-Rj46T1J6c0jOztiKE9Usa4nsc,980
|
|
14
|
-
kash/actions/core/tabbed_webpage_generate.py,sha256=
|
|
14
|
+
kash/actions/core/tabbed_webpage_generate.py,sha256=oYx9fdgY8NndFekXsZbil0luGa2OFDi2e0s5l7Toh7E,992
|
|
15
15
|
kash/actions/meta/write_instructions.py,sha256=zeKKX-Yi8jSyjvZ4Ii_4MNBRtM2MENuHyrD0Vxsaos8,1277
|
|
16
16
|
kash/actions/meta/write_new_action.py,sha256=w7SJZ2FjzRbKwqKX9PeozUrh8cNJAumX7F80wW7dQts,6356
|
|
17
17
|
kash/commands/__init__.py,sha256=MhdPSluWGE3XVQ7LSv2L8_aAxaey8CLjQBjGC9B4nRM,191
|
|
@@ -37,7 +37,7 @@ kash/commands/workspace/selection_commands.py,sha256=nZzA-H7Pk8kqSJVRlX7j1m6cZX-
|
|
|
37
37
|
kash/commands/workspace/workspace_commands.py,sha256=ZJ3aPsnQ0FOkaA6stpV4YPEOQRCOKTazbMCIQkk9Cmk,25119
|
|
38
38
|
kash/config/__init__.py,sha256=ytly9Typ1mWV4CXfV9G3CIPtPQ02u2rpZ304L3GlFro,148
|
|
39
39
|
kash/config/capture_output.py,sha256=ud3uUVNuDicHj3mI_nBUBO-VmOrxtBdA3z-I3D1lSCU,2398
|
|
40
|
-
kash/config/colors.py,sha256=
|
|
40
|
+
kash/config/colors.py,sha256=XrvSpEMJ345e0oCpsr-mASgjROvct9a3JyYkhkaP0Ww,12831
|
|
41
41
|
kash/config/env_settings.py,sha256=uhCdfs9-TzJ15SzbuIQP1yIORaLUqYXCxh9qq_Z8cJc,996
|
|
42
42
|
kash/config/init.py,sha256=aE4sZ6DggBmmoZEx9C5mQKrEbcDiswX--HF7pfCFKzc,526
|
|
43
43
|
kash/config/lazy_imports.py,sha256=MCZXLnKvNyfHi0k7MU5rNwcdJtUF28naCixuogsAOAA,805
|
|
@@ -103,8 +103,8 @@ kash/exec_model/commands_model.py,sha256=iM8QhzA0tAas5OwF5liUfHtm45XIH1LcvCviuh3
|
|
|
103
103
|
kash/exec_model/script_model.py,sha256=1VG3LhkTmlKzHOYouZ92ZpOSKSCcsz3-tHNcFMQF788,5031
|
|
104
104
|
kash/exec_model/shell_model.py,sha256=LUhQivbpXlerM-DUzNY7BtctNBbn08Wto8CSSxQDxRU,568
|
|
105
105
|
kash/file_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
106
|
-
kash/file_storage/file_store.py,sha256=
|
|
107
|
-
kash/file_storage/item_file_format.py,sha256=
|
|
106
|
+
kash/file_storage/file_store.py,sha256=7KUOtEKuwSyz8uP61I8hcgB5TC8yd9oSeU9wzt_dLo4,30093
|
|
107
|
+
kash/file_storage/item_file_format.py,sha256=_o2CjWstk_Z__qMr-Inct9wJm2VEUK0GZvF-fDZ8bcc,5377
|
|
108
108
|
kash/file_storage/metadata_dirs.py,sha256=9AqO3S3SSY1dtvP2iLX--E4ui0VIzXttG8R040otfyg,3820
|
|
109
109
|
kash/file_storage/persisted_yaml.py,sha256=4-4RkFqdlBUkTOwkdA4vRKUywEE9TaDo13OGaDUyU9M,1309
|
|
110
110
|
kash/file_storage/store_cache_warmer.py,sha256=cQ_KwxkBPWT3lMmYOCTkXgo7CKaGINns2YzIH32ExSU,1013
|
|
@@ -136,7 +136,7 @@ kash/llm_utils/llms.py,sha256=Zz45v7qrmBNGnmyF-Mn5Q4F1dZGFT2xf_kmWGUMSUUw,3518
|
|
|
136
136
|
kash/local_server/__init__.py,sha256=AyNpvCOJlQF6A4DnlYKRMbbfRNzdizEA-ytp-F2SLZU,162
|
|
137
137
|
kash/local_server/local_server.py,sha256=EugjL30VM0pWdZDsiQxU-o6EdEa082qlGd_7RHvI5tk,5863
|
|
138
138
|
kash/local_server/local_server_commands.py,sha256=ZMp1DpYgg-MJ0iqH0DHfhWKDpFqNRG_txkRdODIr9mU,1661
|
|
139
|
-
kash/local_server/local_server_routes.py,sha256=
|
|
139
|
+
kash/local_server/local_server_routes.py,sha256=JlIVsrbsU0yiwv7vAoD9BctqiBI0w6u8Ld3BYY4jmo8,10530
|
|
140
140
|
kash/local_server/local_url_formatters.py,sha256=SqHjGMEufvm43n34SCa_8Asdwm7utx91Wwymj15TuSY,5327
|
|
141
141
|
kash/local_server/port_tools.py,sha256=oFfOvO6keqS5GowTpVg2FTu5KqkPHBq-dWAEomUIgGo,2008
|
|
142
142
|
kash/local_server/rich_html_template.py,sha256=O9CnkMYkWuMvKJkqD0P8jaZqfUe6hMP4LXFvcLpwN8Q,196
|
|
@@ -158,13 +158,13 @@ kash/media_base/transcription_format.py,sha256=rOVPTpwvW22c27BRwYF-Tc_xzqK_wOtUZ
|
|
|
158
158
|
kash/media_base/transcription_whisper.py,sha256=GqvroW9kBAH4-gcbYkMgNCfs2MpMIgm1ip3NMWtJ0IE,1169
|
|
159
159
|
kash/media_base/services/local_file_media.py,sha256=-A-tK6XP7XDCqXQIAoohesFZ3OIvpXWsDoktYBvBuNA,5399
|
|
160
160
|
kash/model/__init__.py,sha256=kFfBKb5N70NWYUfpRRxn_Sb9p_vXlB6BBaTCqWmSReo,2978
|
|
161
|
-
kash/model/actions_model.py,sha256=
|
|
161
|
+
kash/model/actions_model.py,sha256=nOo-xbPp_J0aVKul2zBvG7zRaqvz6F-wFUH4oBYU2q0,22085
|
|
162
162
|
kash/model/assistant_response_model.py,sha256=6eDfC27nyuBDFjv5nCYMa_Qb2mPbKwDzZy7uLOIyskI,2653
|
|
163
163
|
kash/model/compound_actions_model.py,sha256=HiDK5wwCu3WwZYHATZoLEguiqwR9V6V296wiKtGIX8s,6926
|
|
164
164
|
kash/model/concept_model.py,sha256=we2qOcy9Mv1q7XPfkDLp_CyO_-8DwAUfUYlpgy_jrFs,1011
|
|
165
165
|
kash/model/exec_model.py,sha256=IlfvtQyoFRRWhWju7vdXp9J-w_NGcGtL5DhDLy9gRd8,2250
|
|
166
166
|
kash/model/graph_model.py,sha256=jnctrPiBZ0xwAR8D54JMAJPanA1yZdaxSFQoIpe8anA,2662
|
|
167
|
-
kash/model/items_model.py,sha256=
|
|
167
|
+
kash/model/items_model.py,sha256=oH6qsexLTrjYwE_wS_pLalYRV8bX1avHXha4klBK8UI,35237
|
|
168
168
|
kash/model/language_list.py,sha256=I3RIbxTseVmPdhExQimimEv18Gmy2ImMbpXe0-_t1Qw,450
|
|
169
169
|
kash/model/llm_actions_model.py,sha256=a29uXVNfS2CiqvM7HPdC6H9A23rSQQihAideuBLMH8g,2110
|
|
170
170
|
kash/model/media_model.py,sha256=64Zic4cRjQpgf_-tOuZlZZe59mz_qu0s6OQSU0YlDUI,3357
|
|
@@ -177,7 +177,7 @@ kash/shell/shell_main.py,sha256=nqbP8NyxtxAwh787YWhpbWelt_ObfIRZzCMK1aaQcmg,2094
|
|
|
177
177
|
kash/shell/version.py,sha256=FRGs_jmzk6QiRPZfZv36SPzeyJCsVWZ_E2CKRkExazw,835
|
|
178
178
|
kash/shell/completions/completion_scoring.py,sha256=-svesm2cR1AA86jYcxlynXCBZON26eUJce93FlL2nQo,10209
|
|
179
179
|
kash/shell/completions/completion_types.py,sha256=FocRXd6Df3Df0nL2Y1GevMx3FsljJwbQdVgWsIngpaQ,4793
|
|
180
|
-
kash/shell/completions/shell_completions.py,sha256=
|
|
180
|
+
kash/shell/completions/shell_completions.py,sha256=A0WWrm2lEwguasDMnDuaI8xGny9MhwGfNvCY3rHNUnw,8844
|
|
181
181
|
kash/shell/file_icons/color_for_format.py,sha256=bFuE1lwiyUkgWB3Gk3jrch-9EIQz9thILQiX_a5dGb8,2034
|
|
182
182
|
kash/shell/file_icons/nerd_icons.py,sha256=qF1Awc5cWIwaOWo7k_nmG9_A6XXwdNNcaluUUlde3KM,36046
|
|
183
183
|
kash/shell/input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -217,7 +217,7 @@ kash/utils/file_utils/__init__.py,sha256=loL_iW0oOZs0mJ5GelBPptBcqzYKSWdsGcHrpRy
|
|
|
217
217
|
kash/utils/file_utils/dir_info.py,sha256=HamMr58k_DanTLifj7A2JDxTGWXEZZx2pQuE6Hjcm8g,1856
|
|
218
218
|
kash/utils/file_utils/file_ext.py,sha256=-H63vlrVI3pfE2Cn_9qF7-QLDaUIu_njc4TieNgAHSY,1860
|
|
219
219
|
kash/utils/file_utils/file_formats.py,sha256=vnihRFLl85G1uzpqDc_uiGH9SIvbFTYVszz3srdSSz0,4949
|
|
220
|
-
kash/utils/file_utils/file_formats_model.py,sha256=
|
|
220
|
+
kash/utils/file_utils/file_formats_model.py,sha256=KA697MPW-V0Xw82eeNATW1UrnUMnzldFKA1XWM3Obq0,15628
|
|
221
221
|
kash/utils/file_utils/file_sort_filter.py,sha256=_k1chT3dJl5lSmKA2PW90KaoG4k4zftGdtwWoNEljP4,7136
|
|
222
222
|
kash/utils/file_utils/file_walk.py,sha256=cpwVDPuaVm95_ZwFJiAdIuZAGhASI3gJ3ZUsCGP75b8,5527
|
|
223
223
|
kash/utils/file_utils/filename_parsing.py,sha256=drHrH2B9W_5yAbXURNGJxNqj9GmTe8FayH6Gjw9e4-U,4194
|
|
@@ -248,15 +248,15 @@ kash/web_content/web_extract_readabilipy.py,sha256=IT7ET5IoU2-Nf37-Neh6CkKMvLL3W
|
|
|
248
248
|
kash/web_content/web_fetch.py,sha256=J8DLFP1vzp7aScanFq0Bd7xCP6AVL4JgMMBqyRPtZjQ,4720
|
|
249
249
|
kash/web_content/web_page_model.py,sha256=9bPuqZxXo6hSUB_llEcz8bs3W1lW0r-Y3Q7pZgknlQU,693
|
|
250
250
|
kash/web_gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
251
|
-
kash/web_gen/simple_webpage.py,sha256=
|
|
252
|
-
kash/web_gen/tabbed_webpage.py,sha256=
|
|
251
|
+
kash/web_gen/simple_webpage.py,sha256=ks_0ljxCeS2-gAAEaUc1JEnzY3JY0nzqGFiyyqyRuZs,1537
|
|
252
|
+
kash/web_gen/tabbed_webpage.py,sha256=DiZV48TVvcjOf31g3nzTAtGKpH5Cek1Unksr7Cwcwog,4949
|
|
253
253
|
kash/web_gen/template_render.py,sha256=aypo6UanouftV4RpxgNm6JdquelI52fV0IlihdA3yjE,1908
|
|
254
|
-
kash/web_gen/templates/base_styles.css.jinja,sha256=
|
|
255
|
-
kash/web_gen/templates/base_webpage.html.jinja,sha256=
|
|
254
|
+
kash/web_gen/templates/base_styles.css.jinja,sha256=xSC0eACITRz1IcIizanhmzjH0aLB28fKdkSmvTEAJUY,9975
|
|
255
|
+
kash/web_gen/templates/base_webpage.html.jinja,sha256=xQ2p4Kv42VPfGUnGLedEjPn6WOnq8G8Fly5XjJUdnXc,12043
|
|
256
256
|
kash/web_gen/templates/content_styles.css.jinja,sha256=3qcIwIt3DipCDJa9z6oIM_BMxmwoT7E_loTK0F3L9Vo,3629
|
|
257
257
|
kash/web_gen/templates/explain_view.html.jinja,sha256=DNw5Iw5SrhIUFRGB4qNvfcKXsBHVbEJVURGdhvyC75Q,949
|
|
258
|
-
kash/web_gen/templates/item_view.html.jinja,sha256
|
|
259
|
-
kash/web_gen/templates/simple_webpage.html.jinja,sha256=
|
|
258
|
+
kash/web_gen/templates/item_view.html.jinja,sha256=cYGyGKFcX8-5L2SM7-BC5oK6GLuH6blrzcxw2DxX-Q8,6828
|
|
259
|
+
kash/web_gen/templates/simple_webpage.html.jinja,sha256=MVXbs0359TTMAqY_A_T8-fP4mrkH5GalwwRBbAzWPeE,1333
|
|
260
260
|
kash/web_gen/templates/tabbed_webpage.html.jinja,sha256=u7gabDsCs3neQQlH1y1rIaqT8UumPKVPPZG4VKg5R_g,1823
|
|
261
261
|
kash/workspaces/__init__.py,sha256=q1gFERRZLJMA9-XSUKvB1ulauHDKqyzzc86GFLbxAuk,700
|
|
262
262
|
kash/workspaces/param_state.py,sha256=vT_eGWqg2SRviIM5jqEAauznX2B5Xt2nHHu2oRxTcIU,746
|
|
@@ -278,9 +278,9 @@ kash/xonsh_custom/xonsh_keybindings.py,sha256=pSnYoZdJmyvC32U1JeqSADV-wReXEHC_Le
|
|
|
278
278
|
kash/xonsh_custom/xonsh_modern_tools.py,sha256=mj_b34LZXfE8MJe9EpDmp5JZ0tDM1biYNPAS8jdcURo,1467
|
|
279
279
|
kash/xonsh_custom/xonsh_ranking_completer.py,sha256=ZRGiAfoEgqgnlq2-ReUVEaX5oOgW1DQ9WxIv2OJLuTo,5620
|
|
280
280
|
kash/xontrib/fnm.py,sha256=V2tsOdmIDgbFbZSfMLpsvDIwwJJqiYnOkOySD1cXNXw,3700
|
|
281
|
-
kash/xontrib/kash_extension.py,sha256=
|
|
282
|
-
kash_shell-0.3.
|
|
283
|
-
kash_shell-0.3.
|
|
284
|
-
kash_shell-0.3.
|
|
285
|
-
kash_shell-0.3.
|
|
286
|
-
kash_shell-0.3.
|
|
281
|
+
kash/xontrib/kash_extension.py,sha256=FLIMlgR3C_6A1fwKE-Ul0nmmpJSszVPbAriinUyQ8Zg,1896
|
|
282
|
+
kash_shell-0.3.16.dist-info/METADATA,sha256=4nG6g2qUrGSvUHNGVQe389ak5Kv20uV5JBM_ByIkqKk,31258
|
|
283
|
+
kash_shell-0.3.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
284
|
+
kash_shell-0.3.16.dist-info/entry_points.txt,sha256=SQraWDAo8SqYpthLXThei0mf_hGGyhYBUO-Er_0HcwI,85
|
|
285
|
+
kash_shell-0.3.16.dist-info/licenses/LICENSE,sha256=rCh2PsfYeiU6FK_0wb58kHGm_Fj5c43fdcHEexiVzIo,34562
|
|
286
|
+
kash_shell-0.3.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|