dscli-agent 0.1.0__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.
- dscli/__init__.py +3 -0
- dscli/__main__.py +4 -0
- dscli/agent.py +312 -0
- dscli/attachments.py +122 -0
- dscli/browser.py +1354 -0
- dscli/cli.py +842 -0
- dscli/config.py +191 -0
- dscli/display.py +108 -0
- dscli/prompts.py +263 -0
- dscli/protocol.py +633 -0
- dscli/scraper.py +268 -0
- dscli/tools.py +674 -0
- dscli/tui.py +574 -0
- dscli/ui.py +72 -0
- dscli_agent-0.1.0.dist-info/METADATA +379 -0
- dscli_agent-0.1.0.dist-info/RECORD +20 -0
- dscli_agent-0.1.0.dist-info/WHEEL +5 -0
- dscli_agent-0.1.0.dist-info/entry_points.txt +2 -0
- dscli_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
- dscli_agent-0.1.0.dist-info/top_level.txt +1 -0
dscli/scraper.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# JavaScript evaluated inside the page. Kept as plain strings so the selectors
|
|
4
|
+
# can be changed from config without editing this file.
|
|
5
|
+
|
|
6
|
+
EXTRACT_LAST_ANSWER_JS = r"""
|
|
7
|
+
(sel) => {
|
|
8
|
+
const nodes = Array.from(document.querySelectorAll(sel));
|
|
9
|
+
const el = nodes.length ? nodes[nodes.length - 1] : null;
|
|
10
|
+
if (!el) return null;
|
|
11
|
+
|
|
12
|
+
const SKIP = new Set(['BUTTON','SVG','STYLE','SCRIPT','TEXTAREA','INPUT','NOSCRIPT']);
|
|
13
|
+
const classOf = (n) => (n.getAttribute && n.getAttribute('class')) || '';
|
|
14
|
+
const skip = (n) => {
|
|
15
|
+
if (n.nodeType !== 1) return false;
|
|
16
|
+
if (SKIP.has(n.tagName)) return true;
|
|
17
|
+
if (n.matches('[role="button"], .ds-button, .ds-icon, [aria-hidden="true"]')) return true;
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const languageOf = (pre) => {
|
|
22
|
+
const code = pre.querySelector('code');
|
|
23
|
+
const cls = code ? String(code.className.baseVal || code.className || '') : '';
|
|
24
|
+
const m = cls.match(/language-([\w.+#-]+)/);
|
|
25
|
+
if (m) return m[1];
|
|
26
|
+
let cur = pre.parentElement;
|
|
27
|
+
for (let i = 0; cur && i < 3; i++, cur = cur.parentElement) {
|
|
28
|
+
if (/code/i.test(classOf(cur))) {
|
|
29
|
+
const cand = cur.querySelector('[class*="banner"], [class*="header"], [class*="title"]');
|
|
30
|
+
if (cand) {
|
|
31
|
+
const t = (cand.innerText || '').trim().split(/\s+/)[0];
|
|
32
|
+
if (t) return t.slice(0, 20);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return '';
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const codeBlock = (pre) => {
|
|
40
|
+
const code = pre.querySelector('code') || pre;
|
|
41
|
+
const text = (code.textContent || '').replace(/\n+$/, '');
|
|
42
|
+
return '\n```' + languageOf(pre) + '\n' + text + '\n```\n';
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const walk = (node) => {
|
|
46
|
+
let out = '';
|
|
47
|
+
for (const child of node.childNodes) {
|
|
48
|
+
if (child.nodeType === 3) { out += child.textContent; continue; }
|
|
49
|
+
if (child.nodeType !== 1 || skip(child)) continue;
|
|
50
|
+
const tag = child.tagName;
|
|
51
|
+
const cls = classOf(child);
|
|
52
|
+
|
|
53
|
+
if (tag === 'BR') { out += '\n'; continue; }
|
|
54
|
+
if (tag === 'PRE') { out += codeBlock(child); continue; }
|
|
55
|
+
if (tag === 'HR') { out += '\n---\n'; continue; }
|
|
56
|
+
if (tag === 'IMG') {
|
|
57
|
+
const alt = child.getAttribute('alt') || '';
|
|
58
|
+
out += alt ? `\n || ''})\n` : '\n';
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (tag === 'A') {
|
|
62
|
+
const inner = walk(child).trim();
|
|
63
|
+
const href = child.getAttribute('href') || '';
|
|
64
|
+
let suffix = '';
|
|
65
|
+
// Append a URL only for absolute, explicitly linked URLs. Markdown also
|
|
66
|
+
// turns JSON fragments into links: DeepSeek autolinks tokens such as
|
|
67
|
+
// object.is or ".top", and parses [i](x) into a relative href. Appending
|
|
68
|
+
// those hrefs corrupts the code the model wrote.
|
|
69
|
+
const absolute = /^(?:https?:)?\/\//i.test(href);
|
|
70
|
+
if (absolute && !href.startsWith('#')) {
|
|
71
|
+
let host = '';
|
|
72
|
+
try {
|
|
73
|
+
host = new URL(href, location.href).hostname.replace(/^www\./i, '');
|
|
74
|
+
} catch (e) { host = ''; }
|
|
75
|
+
const hostLower = host.toLowerCase();
|
|
76
|
+
const bare = inner.replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
|
|
77
|
+
const hrefBare = href.replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase();
|
|
78
|
+
const autolink = !!hostLower && (
|
|
79
|
+
bare === hostLower ||
|
|
80
|
+
bare === hrefBare ||
|
|
81
|
+
(bare.charAt(0) === '.' && hostLower.endsWith(bare))
|
|
82
|
+
);
|
|
83
|
+
if (!autolink) suffix = ` (${href})`;
|
|
84
|
+
}
|
|
85
|
+
out += inner + suffix;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (/^H[1-6]$/.test(tag)) {
|
|
89
|
+
out += '\n' + '#'.repeat(Number(tag[1])) + ' ' + walk(child).trim() + '\n';
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (tag === 'LI') {
|
|
93
|
+
out += '\n- ' + walk(child).trim();
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (tag === 'TABLE') {
|
|
97
|
+
const rows = Array.from(child.querySelectorAll('tr'));
|
|
98
|
+
for (const row of rows) {
|
|
99
|
+
const cells = Array.from(row.querySelectorAll('th,td'))
|
|
100
|
+
.map((c) => (c.innerText || '').trim().replace(/\s+/g, ' '));
|
|
101
|
+
out += '\n| ' + cells.join(' | ') + ' |';
|
|
102
|
+
}
|
|
103
|
+
out += '\n';
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// DeepSeek wraps code in a banner + <pre>. Emit only the code so the
|
|
108
|
+
// "Copy" button and language label do not pollute the model's JSON.
|
|
109
|
+
if (tag !== 'PRE' && /code[-_]?block/i.test(cls) && child.querySelector('pre')) {
|
|
110
|
+
child.querySelectorAll('pre').forEach((pre) => { out += codeBlock(pre); });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (/banner|toolbar/i.test(cls) && child.closest('pre, [class*="code-block"], [class*="codeblock"]')) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const inner = walk(child);
|
|
118
|
+
const block = new Set([
|
|
119
|
+
'P','DIV','SECTION','ARTICLE','BLOCKQUOTE','UL','OL','DL','DT','DD',
|
|
120
|
+
'FIGURE','FIGCAPTION','DETAILS','SUMMARY'
|
|
121
|
+
]).has(tag);
|
|
122
|
+
out += block ? '\n' + inner + '\n' : inner;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
let text = walk(el);
|
|
128
|
+
text = text.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
129
|
+
return { text: text, count: nodes.length, htmlLength: el.innerHTML.length };
|
|
130
|
+
}
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
SNAPSHOT_JS = r"""
|
|
135
|
+
(args) => {
|
|
136
|
+
const assistantSel = args.assistant;
|
|
137
|
+
const itemSel = args.item || '[data-virtual-list-item-key]';
|
|
138
|
+
const sendSel = args.send;
|
|
139
|
+
const stopSel = args.stop;
|
|
140
|
+
const composerSel = args.composer;
|
|
141
|
+
|
|
142
|
+
const visible = (el) => {
|
|
143
|
+
if (!el) return false;
|
|
144
|
+
const style = getComputedStyle(el);
|
|
145
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
146
|
+
const r = el.getBoundingClientRect();
|
|
147
|
+
return r.width > 0 && r.height > 0;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const msgs = Array.from(document.querySelectorAll(assistantSel));
|
|
151
|
+
const last = msgs.length ? msgs[msgs.length - 1] : null;
|
|
152
|
+
let key = null;
|
|
153
|
+
let preview = '';
|
|
154
|
+
let hasActions = false;
|
|
155
|
+
if (last) {
|
|
156
|
+
const item = last.closest(itemSel);
|
|
157
|
+
key = item ? item.getAttribute('data-virtual-list-item-key') : null;
|
|
158
|
+
preview = (last.innerText || '').trim().slice(0, 160);
|
|
159
|
+
const box = item || last.parentElement;
|
|
160
|
+
hasActions = !!(box && box.querySelector(
|
|
161
|
+
'[aria-label="Read aloud"], [aria-label*="read aloud" i], [aria-label*="Read Aloud" i]'
|
|
162
|
+
));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const composer = composerSel ? document.querySelector(composerSel) : null;
|
|
166
|
+
const empty = !composer || !(composer.value || '').trim();
|
|
167
|
+
const send = sendSel ? document.querySelector(sendSel) : null;
|
|
168
|
+
const sendDisabled = !send || send.classList.contains('ds-button--disabled');
|
|
169
|
+
let stop = false;
|
|
170
|
+
if (stopSel) {
|
|
171
|
+
stop = Array.from(document.querySelectorAll(stopSel)).some(visible);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
count: msgs.length,
|
|
176
|
+
key: key,
|
|
177
|
+
preview: preview,
|
|
178
|
+
seen: last ? last.hasAttribute('data-dscli-seen') : false,
|
|
179
|
+
hasActions: hasActions,
|
|
180
|
+
composerEmpty: empty,
|
|
181
|
+
sendPresent: !!send,
|
|
182
|
+
sendDisabled: sendDisabled,
|
|
183
|
+
stop: stop,
|
|
184
|
+
url: location.href,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
MARK_JS = r"""
|
|
191
|
+
(args) => {
|
|
192
|
+
const msgs = Array.from(document.querySelectorAll(args.assistant));
|
|
193
|
+
msgs.forEach((m) => m.setAttribute('data-dscli-seen', '1'));
|
|
194
|
+
return { count: msgs.length };
|
|
195
|
+
}
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
SCROLL_TO_BOTTOM_JS = r"""
|
|
200
|
+
() => {
|
|
201
|
+
const anchor = document.querySelector('[data-virtual-list-item-key]');
|
|
202
|
+
let el = anchor;
|
|
203
|
+
while (el && el !== document.body && el !== document.documentElement) {
|
|
204
|
+
const style = getComputedStyle(el);
|
|
205
|
+
if (el.scrollHeight > el.clientHeight + 20 && /(auto|scroll)/.test(style.overflowY)) {
|
|
206
|
+
el.scrollTop = el.scrollHeight;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
el = el.parentElement;
|
|
210
|
+
}
|
|
211
|
+
window.scrollTo(0, document.body.scrollHeight);
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
TOASTS_JS = r"""
|
|
218
|
+
(sel) => {
|
|
219
|
+
const visible = (el) => {
|
|
220
|
+
const style = getComputedStyle(el);
|
|
221
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
222
|
+
const r = el.getBoundingClientRect();
|
|
223
|
+
return r.width > 0 && r.height > 0;
|
|
224
|
+
};
|
|
225
|
+
return Array.from(document.querySelectorAll(sel))
|
|
226
|
+
.filter(visible)
|
|
227
|
+
.map((n) => (n.innerText || '').trim())
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.slice(-5);
|
|
230
|
+
}
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
DIAGNOSTICS_JS = r"""
|
|
235
|
+
(args) => {
|
|
236
|
+
const present = (sel) => {
|
|
237
|
+
try { return document.querySelectorAll(sel).length; } catch (e) { return -1; }
|
|
238
|
+
};
|
|
239
|
+
const visible = (sel) => {
|
|
240
|
+
try {
|
|
241
|
+
const el = document.querySelector(sel);
|
|
242
|
+
if (!el) return false;
|
|
243
|
+
const style = getComputedStyle(el);
|
|
244
|
+
const r = el.getBoundingClientRect();
|
|
245
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && r.width > 0 && r.height > 0;
|
|
246
|
+
} catch (e) { return false; }
|
|
247
|
+
};
|
|
248
|
+
const fileInput = document.querySelector(args.fileInput);
|
|
249
|
+
const send = document.querySelector(args.send);
|
|
250
|
+
return {
|
|
251
|
+
url: location.href,
|
|
252
|
+
title: document.title,
|
|
253
|
+
composerCount: present(args.composer),
|
|
254
|
+
composerVisible: visible(args.composer),
|
|
255
|
+
sendCount: present(args.send),
|
|
256
|
+
sendVisible: visible(args.send),
|
|
257
|
+
sendDisabled: !!(send && send.classList.contains('ds-button--disabled')),
|
|
258
|
+
assistantCount: present(args.assistant),
|
|
259
|
+
fileInputCount: present(args.fileInput),
|
|
260
|
+
accept: fileInput ? (fileInput.getAttribute('accept') || '') : '',
|
|
261
|
+
cloudflareVisible: visible(args.cloudflare),
|
|
262
|
+
notificationText: Array.from(document.querySelectorAll(args.notification))
|
|
263
|
+
.map((n) => (n.innerText || '').trim()).filter(Boolean).slice(-3),
|
|
264
|
+
toggleCount: present(args.toggle),
|
|
265
|
+
textareaValueLength: (document.querySelector(args.composer) || {}).value ? document.querySelector(args.composer).value.length : 0,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
"""
|