scrypted-tuya 0.1.2-beta → 2.1.0

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.
package/db_to_md.py ADDED
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env python3
2
+ """Convert Gemini antigravity conversation .db files to Markdown."""
3
+
4
+ import sqlite3
5
+ import re
6
+ import json
7
+ import sys
8
+ import os
9
+
10
+
11
+ def parse_protobuf(data: bytes) -> dict:
12
+ result = {}
13
+ pos = 0
14
+ while pos < len(data):
15
+ if pos >= len(data):
16
+ break
17
+ byte = data[pos]
18
+ field_tag = byte >> 3
19
+ wire_type = byte & 0x7
20
+ pos += 1
21
+ if wire_type == 0:
22
+ value = 0; shift = 0
23
+ while pos < len(data):
24
+ b = data[pos]
25
+ value |= (b & 0x7f) << shift
26
+ pos += 1
27
+ if (b & 0x80) == 0: break
28
+ shift += 7
29
+ result[field_tag] = value
30
+ elif wire_type == 2:
31
+ if pos >= len(data): break
32
+ length = 0; shift = 0
33
+ while pos < len(data):
34
+ b = data[pos]
35
+ length |= (b & 0x7f) << shift
36
+ pos += 1
37
+ if (b & 0x80) == 0: break
38
+ shift += 7
39
+ value = data[pos:pos + length]
40
+ pos += length
41
+ result[field_tag] = value
42
+ else:
43
+ break
44
+ return result
45
+
46
+
47
+ def extract_text_from_bytes(data: bytes) -> list:
48
+ if not data: return []
49
+ return [t.decode('utf-8', errors='replace') for t in re.findall(b'[\x20-\x7e]{4,}', data)]
50
+
51
+
52
+ def extract_text_segments(data: bytes) -> list:
53
+ if not data: return []
54
+ return [t.decode('utf-8', errors='replace') for t in re.findall(b'[^\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff]{5,}', data)]
55
+
56
+
57
+ def extract_json_from_bytes(data: bytes):
58
+ if not data: return None
59
+ json_match = re.search(b'\{[^\}]{5,}[^\}]*\}', data)
60
+ if json_match:
61
+ try:
62
+ return json.loads(json_match.group().decode('utf-8'))
63
+ except (json.JSONDecodeError, UnicodeDecodeError):
64
+ pass
65
+ return None
66
+
67
+
68
+ def parse_step_payload(payload: bytes) -> dict:
69
+ if not payload: return {}
70
+ result = {}
71
+ parsed = parse_protobuf(payload)
72
+ for field_tag, value in parsed.items():
73
+ if field_tag == 5 and isinstance(value, bytes):
74
+ nested = parse_protobuf(value)
75
+ result['nested'] = nested
76
+ if 4 in nested and isinstance(nested[4], bytes):
77
+ field4_text = extract_text_from_bytes(nested[4])
78
+ result['tool_names'] = field4_text
79
+ result['json'] = extract_json_from_bytes(nested[4])
80
+ if result.get('json'):
81
+ j = result['json']
82
+ result['tool_action'] = j.get('toolAction', '')
83
+ result['tool_summary'] = j.get('toolSummary', '')
84
+ result['command_line'] = j.get('CommandLine', '')
85
+ result['cwd'] = j.get('Cwd', '')
86
+ result['pattern'] = j.get('Pattern', '')
87
+ result['search_directory'] = j.get('SearchDirectory', '')
88
+ result['directory_path'] = j.get('DirectoryPath', '')
89
+ result['excludes'] = j.get('Excludes', [])
90
+ if 9 in nested and isinstance(nested[9], bytes):
91
+ result['session_info'] = extract_text_from_bytes(nested[9])
92
+ elif field_tag == 4 and isinstance(value, bytes):
93
+ result['field4_text'] = extract_text_from_bytes(value)
94
+ result['json'] = extract_json_from_bytes(value)
95
+ return result
96
+
97
+
98
+ def parse_metadata(metadata: bytes) -> dict:
99
+ if not metadata: return {}
100
+ result = {}
101
+ result['all_text'] = extract_text_from_bytes(metadata)
102
+ call_match = re.search(b'call_[a-zA-Z0-9_]+', metadata)
103
+ if call_match:
104
+ result['call_id'] = call_match.group().decode('utf-8')
105
+ result['json'] = extract_json_from_bytes(metadata)
106
+ return result
107
+
108
+
109
+ def parse_executor_metadata(data: bytes) -> dict:
110
+ result = {}
111
+ parsed = parse_protobuf(data)
112
+ for field_tag, value in parsed.items():
113
+ if field_tag == 10 and isinstance(value, bytes):
114
+ nested = parse_protobuf(value)
115
+ for ft2, fv2 in nested.items():
116
+ if ft2 == 1 and isinstance(fv2, bytes):
117
+ result['content_segments'] = extract_text_segments(fv2)
118
+ result['json'] = extract_json_from_bytes(fv2)
119
+ elif ft2 == 3 and isinstance(fv2, bytes):
120
+ result['tool_definitions'] = extract_text_segments(fv2)
121
+ elif ft2 == 4 and isinstance(fv2, bytes):
122
+ result['system_prompt'] = extract_text_segments(fv2)
123
+ return result
124
+
125
+
126
+ STEP_TYPE_NAMES = {
127
+ 14: 'user', 15: 'assistant', 17: 'system',
128
+ 23: 'assistant', 101: 'assistant', 132: 'tool_call',
129
+ }
130
+ STATUS_NAMES = {3: 'completed', 5: 'completed', 6: 'error', 7: 'error'}
131
+
132
+
133
+ def extract_paren_content(text, prefix):
134
+ """Extract content after prefix up to matching closing paren."""
135
+ if not text.startswith(prefix):
136
+ return None
137
+ start = len(prefix)
138
+ depth = 1
139
+ pos = start
140
+ while pos < len(text) and depth > 0:
141
+ if text[pos] == '(':
142
+ depth += 1
143
+ elif text[pos] == ')':
144
+ depth -= 1
145
+ pos += 1
146
+ return text[start:pos - 1]
147
+
148
+
149
+ def convert_db(db_path: str, output_path: str = None):
150
+ conn = sqlite3.connect(db_path)
151
+ conn.row_factory = sqlite3.Row
152
+
153
+ trajectory_meta = conn.execute('SELECT * FROM trajectory_meta LIMIT 1').fetchone()
154
+ if trajectory_meta:
155
+ trajectory_id, cascade_id = trajectory_meta['trajectory_id'], trajectory_meta['cascade_id']
156
+ trajectory_type, source = trajectory_meta['trajectory_type'], trajectory_meta['source']
157
+ else:
158
+ trajectory_id = cascade_id = trajectory_type = source = None
159
+
160
+ steps = conn.execute('SELECT idx, step_type, status, step_payload, metadata FROM steps ORDER BY idx').fetchall()
161
+
162
+ # Load executor_metadata - use only first entry to avoid duplication
163
+ executor_content = []
164
+ em_cur = conn.execute('SELECT data FROM executor_metadata ORDER BY idx LIMIT 1')
165
+ em_row = em_cur.fetchone()
166
+ if em_row:
167
+ em_result = parse_executor_metadata(em_row[0])
168
+ if 'content_segments' in em_result:
169
+ executor_content = em_result['content_segments']
170
+
171
+ # Get unique content segments (deduplicate)
172
+ seen = set()
173
+ unique_segments = []
174
+ for s in executor_content:
175
+ s = s.strip()
176
+ if len(s) > 3 and s not in seen:
177
+ seen.add(s)
178
+ unique_segments.append(s)
179
+
180
+ lines = []
181
+ lines.append('# Conversation Export')
182
+ lines.append('')
183
+
184
+ if trajectory_meta:
185
+ lines.append('## Trajectory Metadata')
186
+ lines.append('')
187
+ lines.append(f'- **Trajectory ID**: `{trajectory_id}`')
188
+ lines.append(f'- **Cascade ID**: `{cascade_id}`')
189
+ lines.append(f'- **Trajectory Type**: {trajectory_type}')
190
+ lines.append(f'- **Source**: {source}')
191
+ lines.append(f'- **Total Steps**: {len(steps)}')
192
+ lines.append('')
193
+ lines.append('---')
194
+ lines.append('')
195
+
196
+ # --- Executor Metadata: Command Summary ---
197
+ if unique_segments:
198
+ lines.append('## Commands & Actions Summary')
199
+ lines.append('')
200
+ lines.append('Extracted from executor metadata:')
201
+ lines.append('')
202
+
203
+ for text in unique_segments:
204
+ text = text.strip()
205
+ if len(text) < 4: continue
206
+
207
+ # Skip configuration/setting lines
208
+ skip_prefixes = [
209
+ 'enable-', 'jetski-', 'antigravity_', 'teamwork_',
210
+ 'message_continue_check', 'terminal_step_check',
211
+ 'max_generator_invocations_check', 'empty_output_continuation_check',
212
+ 'force_invocation', 'no_tool_call_check', 'battle_mode_tool_args',
213
+ 'request_artifact_feedback_stop', 'idle_subagent_guard',
214
+ 'augmented_intent', 'subagent_reminder', 'running_tasks_reminder',
215
+ 'bash_command_reminder', 'message_delivery', 'read_url_content',
216
+ 'replace_file_content', 'manage_subagents', 'communication_style',
217
+ 'terminal_sandbox', 'planning_mode_artifacts', 'conversation_transcript',
218
+ 'json-hooks-enabled', 'enable-file-diff-accumulator',
219
+ 'enable-customization-skills', 'use-component-rewrite',
220
+ 'enable-markdown-agents', 'agy-customizations',
221
+ 'antigravity_guide', 'generative_ui', 'jetski-autonomous-mode',
222
+ 'enable-cost-accumulator', 'enable-url-artifacts',
223
+ 'enable-background-task-accumulator', 'enable-permissioned-github',
224
+ 'enable-skill-accumulator', 'enable-teamwork-subagent',
225
+ 'enable-owl-slash-command', 'enable-generative-hooks',
226
+ 'teamwork_preview', 'DeepInvestigator', 'user_information',
227
+ '#timeout_long_running_search_command',
228
+ 'request_artifact_feedback_stop',
229
+ ]
230
+
231
+ should_skip = False
232
+ for prefix in skip_prefixes:
233
+ if text.startswith(prefix):
234
+ should_skip = True
235
+ break
236
+ if should_skip:
237
+ continue
238
+
239
+ # System messages
240
+ if text.startswith('As IDE feedback'):
241
+ lines.append('### 💡 System Prompt')
242
+ lines.append('')
243
+ lines.append(text[:1000])
244
+ lines.append('')
245
+ elif text.startswith('planning_mode:'):
246
+ lines.append('### 📋 Planning Mode')
247
+ lines.append('')
248
+ lines.append(text[:500])
249
+ lines.append('')
250
+ elif text.startswith('gemini-'):
251
+ lines.append(f'### 🤖 Model')
252
+ lines.append('')
253
+ lines.append(text)
254
+ lines.append('')
255
+ elif text.startswith('bash_command_reminder'):
256
+ lines.append('### ⚠️ Bash Command Reminder')
257
+ lines.append('')
258
+ lines.append(text[:500])
259
+ lines.append('')
260
+ elif text.startswith('running_tasks_reminder'):
261
+ lines.append('### 📋 Running Tasks Reminder')
262
+ lines.append('')
263
+ lines.append(text[:500])
264
+ lines.append('')
265
+ elif text.startswith('unsandboxed('):
266
+ content = extract_paren_content(text, 'unsandboxed(')
267
+ if content:
268
+ lines.append('### 🔓 Unsandboxed Command')
269
+ lines.append('')
270
+ if content.startswith('cat <<') or '{' in content[:20]:
271
+ lines.append('```')
272
+ lines.append(content[:3000])
273
+ lines.append('```')
274
+ else:
275
+ lines.append(f'```\n{content}\n```')
276
+ lines.append('')
277
+ elif text.startswith('gunsandboxed('):
278
+ content = extract_paren_content(text, 'gunsandboxed(')
279
+ if content:
280
+ lines.append('### 🔫 Gunsandboxed Command')
281
+ lines.append('')
282
+ lines.append(f'```\n{content}\n```')
283
+ lines.append('')
284
+ elif text.startswith('command('):
285
+ content = extract_paren_content(text, 'command(')
286
+ if content:
287
+ lines.append('### 💬 Command')
288
+ lines.append('')
289
+ lines.append(f'```\n{content}\n```')
290
+ lines.append('')
291
+ elif text.startswith('read_file('):
292
+ content = extract_paren_content(text, 'read_file(')
293
+ if content:
294
+ lines.append('### 📄 Read File')
295
+ lines.append('')
296
+ lines.append(f'```\n{content}\n```')
297
+ lines.append('')
298
+ elif text.startswith('write_file('):
299
+ content = extract_paren_content(text, 'write_file(')
300
+ if content:
301
+ lines.append('### ✏️ Write File')
302
+ lines.append('')
303
+ lines.append(f'```\n{content}\n```')
304
+ lines.append('')
305
+ elif text.startswith('read_url('):
306
+ content = extract_paren_content(text, 'read_url(')
307
+ if content:
308
+ lines.append('### 🔗 Read URL')
309
+ lines.append('')
310
+ lines.append(f'```\n{content}\n```')
311
+ lines.append('')
312
+ elif text.startswith('run_command('):
313
+ content = extract_paren_content(text, 'run_command(')
314
+ if content:
315
+ lines.append('### 🏃 Run Command')
316
+ lines.append('')
317
+ lines.append(f'```\n{content}\n```')
318
+ lines.append('')
319
+ elif text.startswith('find_by_name('):
320
+ content = extract_paren_content(text, 'find_by_name(')
321
+ if content:
322
+ lines.append('### 🔍 Find by Name')
323
+ lines.append('')
324
+ lines.append(f'```\n{content}\n```')
325
+ lines.append('')
326
+ elif text.startswith('list_dir('):
327
+ content = extract_paren_content(text, 'list_dir(')
328
+ if content:
329
+ lines.append('### 📁 List Directory')
330
+ lines.append('')
331
+ lines.append(f'```\n{content}\n```')
332
+ lines.append('')
333
+ elif text.startswith('$') or text.startswith('!') or text.startswith('#') or text.startswith('%') or text.startswith('=') or text.startswith('>'):
334
+ # Result markers - skip
335
+ pass
336
+ elif text.startswith('mcp('):
337
+ content = extract_paren_content(text, 'mcp(')
338
+ if content:
339
+ lines.append('### 🧩 MCP Call')
340
+ lines.append('')
341
+ lines.append(f'```\n{content}\n```')
342
+ lines.append('')
343
+ elif len(text) > 20 and ('{' in text[:50] or 'EOF' in text or 'package.json' in text or 'tsconfig' in text):
344
+ lines.append('### 📝 Configuration')
345
+ lines.append('')
346
+ lines.append('```')
347
+ lines.append(text[:3000])
348
+ lines.append('```')
349
+ lines.append('')
350
+ elif len(text) > 15:
351
+ lines.append(f'### {text[:100]}')
352
+ lines.append('')
353
+
354
+ lines.append('---')
355
+ lines.append('')
356
+
357
+ # --- Tool Calls from steps table ---
358
+ tool_calls = []
359
+ assistant_msgs = []
360
+ user_msgs = []
361
+ other_steps = []
362
+
363
+ for step in steps:
364
+ step_type = step['step_type']
365
+ status = step['status']
366
+ idx = step['idx']
367
+ step_name = STEP_TYPE_NAMES.get(step_type, f'unknown({step_type})')
368
+ status_name = STATUS_NAMES.get(status, str(status))
369
+
370
+ payload_info = parse_step_payload(step['step_payload']) if step['step_payload'] else {}
371
+ meta_info = parse_metadata(step['metadata']) if step['metadata'] else {}
372
+
373
+ step_data = {
374
+ 'idx': idx, 'step_type': step_type, 'step_name': step_name,
375
+ 'status': status, 'status_name': status_name, **meta_info, **payload_info,
376
+ }
377
+
378
+ if step_type == 132: tool_calls.append(step_data)
379
+ elif step_type in (15, 23, 101): assistant_msgs.append(step_data)
380
+ elif step_type == 14: user_msgs.append(step_data)
381
+ elif step_type == 17: other_steps.append(step_data)
382
+ else: other_steps.append(step_data)
383
+
384
+ if tool_calls:
385
+ lines.append('## Tool Calls (Detailed)')
386
+ lines.append('')
387
+ lines.append(f'Total: {len(tool_calls)}')
388
+ lines.append('')
389
+
390
+ for tc in tool_calls:
391
+ idx = tc['idx']
392
+ call_id = tc.get('call_id', '')
393
+ all_text = tc.get('all_text', [])
394
+ json_data = tc.get('json', {}) or tc.get('json') or {}
395
+
396
+ tool_name = ''
397
+ for i, t in enumerate(all_text):
398
+ if t.startswith('call_') and i + 1 < len(all_text):
399
+ tool_name = all_text[i + 1]
400
+ break
401
+
402
+ lines.append(f'### Tool Call #{idx}')
403
+ lines.append('')
404
+ lines.append(f'- **Call ID**: `{call_id}`')
405
+ lines.append(f'- **Tool**: `{tool_name}`')
406
+ lines.append(f'- **Status**: {tc["status_name"]}')
407
+
408
+ if json_data:
409
+ lines.append(f'- **Action**: {json_data.get("toolAction", "")}')
410
+ lines.append(f'- **Summary**: {json_data.get("toolSummary", "")}')
411
+ lines.append(f'- **Parameters**:')
412
+ lines.append('')
413
+ lines.append('```json')
414
+ lines.append(json.dumps(json_data, indent=2))
415
+ lines.append('```')
416
+ else:
417
+ json_match = re.search(r'\{[^\}]{5,}[^\}]*\}', ' '.join(all_text))
418
+ if json_match:
419
+ try:
420
+ j = json.loads(json_match.group())
421
+ lines.append(f'- **Parameters**:')
422
+ lines.append('')
423
+ lines.append('```json')
424
+ lines.append(json.dumps(j, indent=2))
425
+ lines.append('```')
426
+ except: pass
427
+
428
+ if json_data:
429
+ for key in ['CommandLine', 'Cwd', 'Pattern', 'SearchDirectory', 'DirectoryPath', 'Excludes']:
430
+ val = json_data.get(key)
431
+ if val is not None:
432
+ if key == 'Excludes':
433
+ lines.append(f'- **{key}**: `{", ".join(val)}`')
434
+ else:
435
+ lines.append(f'- **{key}**: `{val}`')
436
+
437
+ session_info = tc.get('session_info', [])
438
+ if session_info:
439
+ lines.append(f'- **Session**: `{session_info[0]}`')
440
+ lines.append('')
441
+
442
+ lines.append('## Summary')
443
+ lines.append('')
444
+ lines.append(f'- **Total Steps**: {len(steps)}')
445
+ lines.append(f'- **User Messages**: {len(user_msgs)}')
446
+ lines.append(f'- **Assistant Messages**: {len(assistant_msgs)}')
447
+ lines.append(f'- **Tool Calls**: {len(tool_calls)}')
448
+ lines.append(f'- **System Steps**: {len(other_steps)}')
449
+ lines.append('')
450
+
451
+ markdown_content = '\n'.join(lines)
452
+
453
+ if output_path is None:
454
+ output_path = 'conversation.md'
455
+
456
+ with open(output_path, 'w', encoding='utf-8') as f:
457
+ f.write(markdown_content)
458
+
459
+ print(f'Converted {len(steps)} steps to {output_path}')
460
+ print(f' Tool calls: {len(tool_calls)}')
461
+ print(f' Assistant messages: {len(assistant_msgs)}')
462
+ print(f' User messages: {len(user_msgs)}')
463
+ print(f' Other: {len(other_steps)}')
464
+ print(f' Unique command segments: {len(unique_segments)}')
465
+
466
+ conn.close()
467
+
468
+
469
+ if __name__ == '__main__':
470
+ db_path = sys.argv[1] if len(sys.argv) > 1 else None
471
+ output_path = sys.argv[2] if len(sys.argv) > 2 else None
472
+
473
+ if db_path is None:
474
+ db_path = os.path.join(
475
+ os.path.expanduser('~'),
476
+ '.gemini', 'antigravity', 'conversations',
477
+ '094b96f4-9751-4978-b473-18144c70a431.db'
478
+ )
479
+
480
+ if not os.path.exists(db_path):
481
+ print(f'Error: Database not found at {db_path}', file=sys.stderr)
482
+ sys.exit(1)
483
+
484
+ convert_db(db_path, output_path)
package/dist/plugin.zip CHANGED
Binary file