cowork-dash 0.2.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -156,6 +156,15 @@ code, pre, .mono {
156
156
  line-height: 1.6;
157
157
  }
158
158
 
159
+ /* Tighter spacing for consecutive AI text blocks */
160
+ #chat-messages .ai-text-block p {
161
+ margin: 0;
162
+ }
163
+
164
+ #chat-messages .ai-text-block p:last-child {
165
+ margin-bottom: 0;
166
+ }
167
+
159
168
  #chat-messages ul, #chat-messages ol {
160
169
  margin: 6px 0;
161
170
  padding-left: 20px;
@@ -1158,3 +1167,14 @@ details.display-inline-container[open] > .display-inline-summary::before {
1158
1167
  text-overflow: ellipsis;
1159
1168
  white-space: nowrap;
1160
1169
  }
1170
+
1171
+ /* Fullscreen button for HTML/PDF preview */
1172
+ .fullscreen-btn {
1173
+ opacity: 0.7;
1174
+ transition: opacity 0.15s ease, background-color 0.15s ease;
1175
+ }
1176
+
1177
+ .fullscreen-btn:hover {
1178
+ opacity: 1;
1179
+ background-color: rgba(255,255,255,1) !important;
1180
+ }
cowork_dash/components.py CHANGED
@@ -82,20 +82,110 @@ def format_loading(colors: Dict):
82
82
 
83
83
 
84
84
  def format_thinking(thinking_text: str, colors: Dict):
85
- """Format thinking as an inline subordinate message."""
85
+ """Format thinking as an inline subordinate message (non-collapsible)."""
86
86
  if not thinking_text:
87
87
  return None
88
88
 
89
- return html.Details([
90
- html.Summary("Thinking", className="details-summary details-summary-thinking"),
91
- html.Div(thinking_text, className="details-content details-content-thinking", style={
89
+ return html.Div([
90
+ html.Div("Thinking", style={
91
+ "fontSize": "12px",
92
+ "fontWeight": "500",
93
+ "color": colors.get("thinking", colors.get("text_muted", "#888")),
94
+ "marginBottom": "2px",
95
+ }),
96
+ html.Div(thinking_text, style={
92
97
  "whiteSpace": "pre-wrap",
98
+ "fontSize": "13px",
99
+ "color": colors.get("text_muted", "#666"),
100
+ "paddingLeft": "8px",
101
+ "borderLeft": f"2px solid {colors.get('thinking', '#7c4dff')}",
93
102
  })
94
- ], open=True, className="chat-details", style={
103
+ ], style={
95
104
  "marginBottom": "4px",
96
105
  })
97
106
 
98
107
 
108
+ def format_ai_text(text: str, colors: Dict, is_new: bool = False):
109
+ """Format AI text response (without the full message wrapper).
110
+
111
+ This is used when rendering ordered content items where thinking
112
+ and text are interleaved.
113
+ """
114
+ if not text:
115
+ return None
116
+
117
+ return dcc.Markdown(
118
+ text,
119
+ className="ai-text-block",
120
+ style={
121
+ "fontSize": "15px",
122
+ "lineHeight": "1.5",
123
+ "margin": "0",
124
+ "padding": "0",
125
+ }
126
+ )
127
+
128
+
129
+ def render_ordered_content_items(content_items: List[Dict], colors: Dict, styles: Dict = None, response_time: float = None) -> List:
130
+ """Render content items in their original emission order.
131
+
132
+ Args:
133
+ content_items: List of {"type": "text"|"thinking"|"display_inline", "content": ...}
134
+ colors: Color scheme dict
135
+ styles: Optional styles dict
136
+ response_time: Optional response time to show after the last text item
137
+
138
+ Returns:
139
+ List of rendered Dash components in order
140
+ """
141
+ if not content_items:
142
+ return []
143
+
144
+ rendered = []
145
+ last_text_index = None
146
+
147
+ # Find the last text item index for response time display
148
+ for i, item in enumerate(content_items):
149
+ if item.get("type") == "text":
150
+ last_text_index = i
151
+
152
+ for i, item in enumerate(content_items):
153
+ item_type = item.get("type")
154
+ content = item.get("content", "")
155
+
156
+ if not content:
157
+ continue
158
+
159
+ if item_type == "thinking":
160
+ block = format_thinking(content, colors)
161
+ if block:
162
+ rendered.append(block)
163
+ elif item_type == "text":
164
+ # For the last text item, we might want to show response time
165
+ is_last_text = (i == last_text_index)
166
+ block = format_ai_text(content, colors)
167
+ if block:
168
+ rendered.append(block)
169
+ # Add response time after the last text block
170
+ if is_last_text and response_time is not None:
171
+ time_display = f"{response_time:.1f}s" if response_time >= 1 else f"{response_time*1000:.0f}ms"
172
+ rendered.append(html.Span(
173
+ time_display,
174
+ style={
175
+ "fontSize": "11px",
176
+ "color": colors.get("text_muted", "#888"),
177
+ "marginLeft": "4px",
178
+ }
179
+ ))
180
+ elif item_type == "display_inline":
181
+ # content is the full display_inline item dict
182
+ block = render_display_inline_result(content, colors)
183
+ if block:
184
+ rendered.append(block)
185
+
186
+ return rendered
187
+
188
+
99
189
  def format_todos(todos, colors: Dict):
100
190
  """Format todo list. Handles both list format [{"content": ..., "status": ...}] and dict format {task_name: status}."""
101
191
  if not todos:
@@ -349,24 +439,53 @@ def render_display_inline_result(result: Dict, colors: Dict) -> html.Div:
349
439
  ])
350
440
 
351
441
  elif display_type == "html":
352
- # Show preview thumbnail with expand button
353
- preview_content = preview or data
354
- if len(str(preview_content)) > 500:
355
- preview_content = str(preview_content)[:500] + "..."
356
-
357
- content_element = html.Details([
358
- html.Summary("HTML Content", className="tool-call-summary"),
359
- html.Iframe(
360
- srcDoc=data,
442
+ # Show HTML preview directly in iframe (like PDF)
443
+ if not data:
444
+ content_element = html.Div(
445
+ "Error: HTML content is empty or missing",
446
+ style={"color": "red", "padding": "10px"}
447
+ )
448
+ else:
449
+ # Create fullscreen button
450
+ fullscreen_btn = html.Button(
451
+ DashIconify(icon="mdi:fullscreen", width=18),
452
+ id={"type": "fullscreen-btn", "index": item_id},
453
+ className="fullscreen-btn",
454
+ title="View fullscreen",
361
455
  style={
362
- "width": "100%",
363
- "height": "300px",
456
+ "position": "absolute",
457
+ "top": "8px",
458
+ "right": "8px",
459
+ "background": "rgba(255,255,255,0.9)",
364
460
  "border": "1px solid #ddd",
365
- "borderRadius": "5px",
366
- "backgroundColor": "white",
461
+ "borderRadius": "4px",
462
+ "cursor": "pointer",
463
+ "padding": "4px 6px",
464
+ "display": "flex",
465
+ "alignItems": "center",
466
+ "justifyContent": "center",
467
+ "zIndex": "10",
367
468
  }
368
469
  )
369
- ], className="display-inline-html")
470
+ # Store data for fullscreen modal
471
+ fullscreen_store = dcc.Store(
472
+ id={"type": "fullscreen-data", "index": item_id},
473
+ data={"type": "html", "content": data, "title": title or "HTML Preview"}
474
+ )
475
+ content_element = html.Div([
476
+ fullscreen_btn,
477
+ html.Iframe(
478
+ srcDoc=data,
479
+ style={
480
+ "width": "100%",
481
+ "height": "400px",
482
+ "border": "none",
483
+ "borderRadius": "5px",
484
+ "backgroundColor": "white",
485
+ }
486
+ ),
487
+ fullscreen_store,
488
+ ], style={"position": "relative"})
370
489
 
371
490
  elif display_type == "pdf":
372
491
  mime_type = result.get("mime_type", "application/pdf")
@@ -378,16 +497,46 @@ def render_display_inline_result(result: Dict, colors: Dict) -> html.Div:
378
497
  )
379
498
  else:
380
499
  data_url = f"data:{mime_type};base64,{data}"
381
- # Use iframe instead of embed for better browser compatibility
382
- content_element = html.Iframe(
383
- src=data_url,
500
+ # Create fullscreen button
501
+ fullscreen_btn = html.Button(
502
+ DashIconify(icon="mdi:fullscreen", width=18),
503
+ id={"type": "fullscreen-btn", "index": item_id},
504
+ className="fullscreen-btn",
505
+ title="View fullscreen",
384
506
  style={
385
- "width": "100%",
386
- "height": "400px",
387
- "border": "none",
388
- "borderRadius": "5px",
507
+ "position": "absolute",
508
+ "top": "8px",
509
+ "right": "8px",
510
+ "background": "rgba(255,255,255,0.9)",
511
+ "border": "1px solid #ddd",
512
+ "borderRadius": "4px",
513
+ "cursor": "pointer",
514
+ "padding": "4px 6px",
515
+ "display": "flex",
516
+ "alignItems": "center",
517
+ "justifyContent": "center",
518
+ "zIndex": "10",
389
519
  }
390
520
  )
521
+ # Store data for fullscreen modal
522
+ fullscreen_store = dcc.Store(
523
+ id={"type": "fullscreen-data", "index": item_id},
524
+ data={"type": "pdf", "content": data_url, "title": title or "PDF Preview"}
525
+ )
526
+ # Use iframe instead of embed for better browser compatibility
527
+ content_element = html.Div([
528
+ fullscreen_btn,
529
+ html.Iframe(
530
+ src=data_url,
531
+ style={
532
+ "width": "100%",
533
+ "height": "400px",
534
+ "border": "none",
535
+ "borderRadius": "5px",
536
+ }
537
+ ),
538
+ fullscreen_store,
539
+ ], style={"position": "relative"})
391
540
 
392
541
  elif display_type == "json":
393
542
  json_str = json.dumps(data, indent=2) if isinstance(data, (dict, list)) else str(data)
@@ -477,16 +626,38 @@ def render_display_inline_result(result: Dict, colors: Dict) -> html.Div:
477
626
  }
478
627
  )
479
628
 
629
+ # Create download button
630
+ download_btn = html.Button(
631
+ DashIconify(icon="mdi:download", width=16),
632
+ id={"type": "download-display-btn", "index": item_id},
633
+ className="download-display-btn",
634
+ title="Download",
635
+ style={
636
+ "background": "transparent",
637
+ "border": "none",
638
+ "cursor": "pointer",
639
+ "padding": "4px",
640
+ "borderRadius": "4px",
641
+ "display": "flex",
642
+ "alignItems": "center",
643
+ "justifyContent": "center",
644
+ "marginLeft": "4px",
645
+ }
646
+ )
647
+
480
648
  # Store the result data in a hidden div for the callback to retrieve
481
649
  result_store = dcc.Store(
482
650
  id={"type": "display-inline-data", "index": item_id},
483
651
  data=result
484
652
  )
485
653
 
486
- # Build summary with text and button
654
+ # Build summary with text and buttons
487
655
  summary_content = html.Div([
488
656
  html.Span(summary_text, className="display-inline-summary-text"),
489
- add_to_canvas_btn,
657
+ html.Div([
658
+ add_to_canvas_btn,
659
+ download_btn,
660
+ ], style={"display": "flex", "alignItems": "center"}),
490
661
  ], className="display-inline-summary-row", style={
491
662
  "display": "flex",
492
663
  "alignItems": "center",
cowork_dash/layout.py CHANGED
@@ -53,6 +53,7 @@ def create_layout(workspace_root, app_title, app_subtitle, colors, styles, agent
53
53
  dcc.Store(id="theme-store", data="light", storage_type="local"),
54
54
  dcc.Store(id="current-workspace-path", data=""), # Relative path from original workspace root
55
55
  dcc.Store(id="collapsed-canvas-items", data=[]), # Track which canvas items are collapsed
56
+ dcc.Store(id="sidebar-collapsed", data=False), # Track if sidebar is collapsed
56
57
  dcc.Download(id="file-download"),
57
58
 
58
59
  # Interval for polling agent updates (disabled by default)
@@ -134,6 +135,22 @@ def create_layout(workspace_root, app_title, app_subtitle, colors, styles, agent
134
135
  opened=False,
135
136
  ),
136
137
 
138
+ # Fullscreen preview modal for HTML/PDF
139
+ dmc.Modal(
140
+ id="fullscreen-preview-modal",
141
+ title="Preview",
142
+ size="100%",
143
+ children=[
144
+ html.Div(id="fullscreen-preview-content", style={"height": "calc(100vh - 120px)"})
145
+ ],
146
+ opened=False,
147
+ styles={
148
+ "content": {"height": "95vh", "maxHeight": "95vh"},
149
+ "body": {"height": "calc(100% - 60px)", "padding": "0"},
150
+ },
151
+ ),
152
+ dcc.Store(id="fullscreen-preview-data", data=None),
153
+
137
154
  html.Div([
138
155
  # Compact Header
139
156
  html.Header([
@@ -264,6 +281,13 @@ def create_layout(workspace_root, app_title, app_subtitle, colors, styles, agent
264
281
  variant="default",
265
282
  size="md",
266
283
  ),
284
+ dmc.ActionIcon(
285
+ DashIconify(icon="mdi:chevron-right", width=18),
286
+ id="collapse-sidebar-btn",
287
+ variant="subtle",
288
+ size="md",
289
+ **{"aria-label": "Collapse sidebar"},
290
+ ),
267
291
  ], id="files-actions", gap=5)
268
292
  ], id="sidebar-header", style={
269
293
  "display": "flex", "justifyContent": "space-between",
@@ -346,6 +370,23 @@ def create_layout(workspace_root, app_title, app_subtitle, colors, styles, agent
346
370
  "background": "var(--mantine-color-body)",
347
371
  "borderLeft": "1px solid var(--mantine-color-default-border)",
348
372
  }),
373
+
374
+ # Expand button (shown when sidebar is collapsed)
375
+ html.Div([
376
+ dmc.ActionIcon(
377
+ DashIconify(icon="mdi:chevron-left", width=18),
378
+ id="expand-sidebar-btn",
379
+ variant="subtle",
380
+ size="md",
381
+ **{"aria-label": "Expand sidebar"},
382
+ ),
383
+ ], id="sidebar-expand-btn", style={
384
+ "display": "none",
385
+ "alignItems": "flex-start",
386
+ "paddingTop": "10px",
387
+ "borderLeft": "1px solid var(--mantine-color-default-border)",
388
+ "background": "var(--mantine-color-body)",
389
+ }),
349
390
  ], id="main-container", style={"display": "flex", "flex": "1", "overflow": "hidden"}),
350
391
  ], id="app-container", style={"display": "flex", "flexDirection": "column", "height": "100vh"})
351
392
  ])
cowork_dash/tools.py CHANGED
@@ -230,37 +230,6 @@ except (ImportError, AttributeError):
230
230
  if VIRTUAL_FS:
231
231
  self._inject_virtual_fs_helpers()
232
232
 
233
- # Inject add_to_canvas function that captures items
234
- def _add_to_canvas_wrapper(content: Any) -> Dict[str, Any]:
235
- """Add content to the canvas for visualization.
236
-
237
- Supports: DataFrames, matplotlib figures, plotly figures,
238
- PIL images, and markdown strings.
239
- """
240
- try:
241
- # Use session's VirtualFilesystem in virtual FS mode, otherwise physical path
242
- if VIRTUAL_FS and self._session_id:
243
- from .virtual_fs import get_session_manager
244
- workspace_root = get_session_manager().get_filesystem(self._session_id)
245
- if workspace_root is None:
246
- raise RuntimeError(f"Session {self._session_id} not found")
247
- else:
248
- workspace_root = WORKSPACE_ROOT
249
-
250
- parsed = parse_canvas_object(content, workspace_root=workspace_root)
251
- self._canvas_items.append(parsed)
252
- return parsed
253
- except Exception as e:
254
- error_result = {
255
- "type": "error",
256
- "data": f"Failed to add to canvas: {str(e)}",
257
- "error": str(e)
258
- }
259
- self._canvas_items.append(error_result)
260
- return error_result
261
-
262
- self._namespace["add_to_canvas"] = _add_to_canvas_wrapper
263
-
264
233
  def _inject_virtual_fs_helpers(self):
265
234
  """Inject virtual filesystem helper functions into the namespace."""
266
235
  from .virtual_fs import get_session_manager
@@ -1089,6 +1058,9 @@ def _display_inline_impl(
1089
1058
  # Handle explicit display_type for strings
1090
1059
  if isinstance(content, str) and display_type:
1091
1060
  if display_type == "html":
1061
+ # Check if it's a file path first
1062
+ if _is_file_path(content, workspace_root):
1063
+ return _process_file_for_display(content, workspace_root, title, "html")
1092
1064
  result["display_type"] = "html"
1093
1065
  result["data"] = content
1094
1066
  result["preview"] = content[:500] + "..." if len(content) > 500 else content
@@ -1098,6 +1070,9 @@ def _display_inline_impl(
1098
1070
  result["data"] = content
1099
1071
  return result
1100
1072
  elif display_type in ("csv", "dataframe"):
1073
+ # Check if it's a file path first
1074
+ if _is_file_path(content, workspace_root):
1075
+ return _process_file_for_display(content, workspace_root, title, "csv")
1101
1076
  # Parse CSV string
1102
1077
  try:
1103
1078
  import pandas as pd
@@ -1109,6 +1084,9 @@ def _display_inline_impl(
1109
1084
  result["error"] = f"Could not parse as CSV: {e}"
1110
1085
  return result
1111
1086
  elif display_type == "json":
1087
+ # Check if it's a file path first
1088
+ if _is_file_path(content, workspace_root):
1089
+ return _process_file_for_display(content, workspace_root, title, "json")
1112
1090
  result["display_type"] = "json"
1113
1091
  try:
1114
1092
  result["data"] = json.loads(content) if isinstance(content, str) else content
@@ -1123,6 +1101,9 @@ def _display_inline_impl(
1123
1101
  result["data"] = content # Assume base64
1124
1102
  return result
1125
1103
  elif display_type == "plotly":
1104
+ # Check if it's a file path first
1105
+ if _is_file_path(content, workspace_root):
1106
+ return _process_file_for_display(content, workspace_root, title, "plotly")
1126
1107
  result["display_type"] = "plotly"
1127
1108
  if isinstance(content, str):
1128
1109
  result["data"] = json.loads(content)
@@ -1406,14 +1387,18 @@ def _process_file_for_display(
1406
1387
  result["error"] = f"Could not parse as CSV: {e}"
1407
1388
  return result
1408
1389
 
1409
- # JSON files (check for Plotly)
1410
- if ext == '.json' or display_type == "json":
1390
+ # JSON files (check for Plotly) or explicit plotly display_type
1391
+ if ext == '.json' or display_type in ("json", "plotly"):
1411
1392
  content, is_text, error = read_file_content(workspace_root, file_path)
1412
1393
  if content:
1413
1394
  try:
1414
1395
  data = json.loads(content)
1415
- # Check if it's Plotly JSON
1416
- if isinstance(data, dict) and 'data' in data and isinstance(data.get('data'), list):
1396
+ # Force plotly if display_type is explicitly set, otherwise auto-detect
1397
+ if display_type == "plotly":
1398
+ result["display_type"] = "plotly"
1399
+ result["data"] = data
1400
+ # Check if it's Plotly JSON (auto-detect)
1401
+ elif isinstance(data, dict) and 'data' in data and isinstance(data.get('data'), list):
1417
1402
  result["display_type"] = "plotly"
1418
1403
  result["data"] = data
1419
1404
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cowork-dash
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: AI Agent Web Interface with Filesystem and Canvas Visualization
5
5
  Project-URL: Homepage, https://github.com/dkedar7/cowork-dash
6
6
  Project-URL: Documentation, https://github.com/dkedar7/cowork-dash/blob/main/README.md
@@ -1,23 +1,23 @@
1
1
  cowork_dash/__init__.py,sha256=37qBKl7g12Zos8GFukXLXligCdpD32e2qe9F8cd8Qdk,896
2
2
  cowork_dash/__main__.py,sha256=CCM9VIkWuwh7hwVGNBBgCCbeVAcHj1soyBVXUaPgABk,131
3
- cowork_dash/agent.py,sha256=ZtpgRAft29OaD_GdJidfmZJ7_gzhP4ezQz136tUCByM,6853
4
- cowork_dash/app.py,sha256=jKpaHlsZ1GH17OVB3PXb2xMoqLjl_XI7u1mdcNEMtvA,148643
3
+ cowork_dash/agent.py,sha256=jQZGHrpyhz9c57GgSe7MAo-pohNc8exd0WRw-zJrrV0,6495
4
+ cowork_dash/app.py,sha256=OpUqnwKgobT_9x0QeGWOxq9_ExkFyVNsrYOFvQx3RKc,162756
5
5
  cowork_dash/backends.py,sha256=YQE8f65o2qGxIIfvBITAS6krCLjl8D9UixW-pgbdgZk,15050
6
6
  cowork_dash/canvas.py,sha256=sYOQ5WBLm29UazA5KO7j8jvIeQpx55LToz1o44o2U-k,17261
7
7
  cowork_dash/cli.py,sha256=HRxu_k_AphxD8JuLOVTIb6a1lmSdX7ziGCxnEBrW2gA,7561
8
- cowork_dash/components.py,sha256=fbqxTgFVILiWdxlJFBQVBc7klM3SaSvhjnwWlOl0V0s,36289
8
+ cowork_dash/components.py,sha256=dUpmav4XWJmlr1CziNXfyoXwSXhTHFj_-TtQIGHYkwI,42468
9
9
  cowork_dash/config.py,sha256=vVyDiX9Cdjk1wiZ_ktC3I2aiFC-GndNvLXWGW4aXUOM,5094
10
10
  cowork_dash/file_utils.py,sha256=qIpTycCNRIQWhdGcO8OIAKmehrxwkMMumgqw-5MjbSg,15431
11
- cowork_dash/layout.py,sha256=eDMingJVkcG5NeYuCFREaH21LNMBa6Ut5MSw9MV5eO0,16662
11
+ cowork_dash/layout.py,sha256=B4A7tiMnm836HYBAU4J3hirS3HvGBhJHbdlPwGquHB4,18526
12
12
  cowork_dash/sandbox.py,sha256=T0TMnZefPJj4OV1D1GNaYrircSG4MhXHk0IPizHXqME,11766
13
- cowork_dash/tools.py,sha256=MP-OX-OEeROtgcqJVQERAx91IP0th13O95L4EMP_dY8,58448
13
+ cowork_dash/tools.py,sha256=JXpYcE7AjBXki1HxZjDrSzMzyHBk0qdscbIEOl1AIZw,58150
14
14
  cowork_dash/virtual_fs.py,sha256=PAAdRiMkxgJ4xPpGUyAUd69KbH-nFlt-FjldfB1FrQ4,16296
15
15
  cowork_dash/assets/app.js,sha256=Rln4MQPxfyOcyH4lkSbT4RAP-fqF1lT_Qw25cHHUFS8,10157
16
16
  cowork_dash/assets/favicon.ico,sha256=IiP0rVr0m-TBGGmCY88SyFC14drNwDhLRqb0n2ZufKk,54252
17
17
  cowork_dash/assets/favicon.svg,sha256=MdT50APCvIlWh3HSwW5SNXYWB3q_wKfuLP-JV53SnKg,1065
18
- cowork_dash/assets/styles.css,sha256=9XoUoDB6s5xX3o3nifwiY6WFgrb_2y2JXP4OYlCs-Lc,28914
19
- cowork_dash-0.2.0.dist-info/METADATA,sha256=NAS-bflWJMhTrYQmMhwJ-HCp7PX6kCVqW4IPOuYHosg,6719
20
- cowork_dash-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
21
- cowork_dash-0.2.0.dist-info/entry_points.txt,sha256=lL_9XJINiky3nh13tLqWd61LitKbbyh085ROATH9fck,53
22
- cowork_dash-0.2.0.dist-info/licenses/LICENSE,sha256=2SFXFfIa_c_g_uwY0JApQDXI1mWqEfJeG87Pn4ehLMQ,1072
23
- cowork_dash-0.2.0.dist-info/RECORD,,
18
+ cowork_dash/assets/styles.css,sha256=hUmw_dYhZJJQQ20iuz7sosrOiGouNE1tkWuyEdBMvaI,29335
19
+ cowork_dash-0.2.1.dist-info/METADATA,sha256=k7EHUdfLUzlhYvlVD0lZfYiLynzQJO3opHEUeo0qxmY,6719
20
+ cowork_dash-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
21
+ cowork_dash-0.2.1.dist-info/entry_points.txt,sha256=lL_9XJINiky3nh13tLqWd61LitKbbyh085ROATH9fck,53
22
+ cowork_dash-0.2.1.dist-info/licenses/LICENSE,sha256=2SFXFfIa_c_g_uwY0JApQDXI1mWqEfJeG87Pn4ehLMQ,1072
23
+ cowork_dash-0.2.1.dist-info/RECORD,,