onkol 0.5.1 → 0.5.4
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/dist/cli/index.js
CHANGED
|
@@ -570,8 +570,8 @@ program
|
|
|
570
570
|
console.log(chalk.cyan('[1/3] Updating files from npm package...'));
|
|
571
571
|
try {
|
|
572
572
|
// Find where this CLI is running from — that's the latest package
|
|
573
|
-
// __dirname is dist/cli/, so pkgRoot is
|
|
574
|
-
const pkgRoot = resolve(__dirname, '
|
|
573
|
+
// __dirname is dist/cli/, so pkgRoot is two levels up (dist/cli -> dist -> package root)
|
|
574
|
+
const pkgRoot = resolve(__dirname, '../..');
|
|
575
575
|
const { readdirSync, chmodSync } = await import('fs');
|
|
576
576
|
// Try src/plugin first (has .ts files), then dist/plugin (.js files)
|
|
577
577
|
let pluginUpdated = false;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { Client, GatewayIntentBits } from 'discord.js';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
const ATTACHMENT_DIR = join(tmpdir(), 'onkol-attachments');
|
|
6
|
+
mkdirSync(ATTACHMENT_DIR, { recursive: true });
|
|
7
|
+
// Small text files get inlined into the message content
|
|
8
|
+
const INLINE_MAX_BYTES = 10_000;
|
|
9
|
+
const INLINE_CONTENT_TYPES = ['text/', 'application/json', 'application/xml', 'application/csv'];
|
|
2
10
|
export function shouldForwardMessage(messageChannelId, authorId, isBot, targetChannelId, allowedUsers) {
|
|
3
11
|
if (isBot)
|
|
4
12
|
return false;
|
|
@@ -8,17 +16,37 @@ export function shouldForwardMessage(messageChannelId, authorId, isBot, targetCh
|
|
|
8
16
|
return false;
|
|
9
17
|
return true;
|
|
10
18
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
function shouldInline(a) {
|
|
20
|
+
if (a.name?.endsWith('.txt') || a.name?.endsWith('.csv') || a.name?.endsWith('.json') || a.name?.endsWith('.md')) {
|
|
21
|
+
return (a.size || 0) <= INLINE_MAX_BYTES;
|
|
22
|
+
}
|
|
23
|
+
const ct = a.contentType || '';
|
|
24
|
+
return INLINE_CONTENT_TYPES.some(t => ct.startsWith(t)) && (a.size || 0) <= INLINE_MAX_BYTES;
|
|
25
|
+
}
|
|
26
|
+
// Download all attachments: small text gets inlined, everything else saved to disk.
|
|
27
|
+
// Claude Code's Read tool handles images, PDFs, CSVs, notebooks, etc. natively.
|
|
28
|
+
async function resolveAttachments(message) {
|
|
14
29
|
let content = message.content;
|
|
15
|
-
const
|
|
16
|
-
for (const attachment of textAttachments.values()) {
|
|
30
|
+
for (const attachment of message.attachments.values()) {
|
|
17
31
|
try {
|
|
18
32
|
const res = await fetch(attachment.url);
|
|
19
|
-
if (res.ok)
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
continue;
|
|
35
|
+
if (shouldInline(attachment)) {
|
|
36
|
+
// Inline small text files directly into the message
|
|
20
37
|
const text = await res.text();
|
|
21
|
-
|
|
38
|
+
const label = attachment.name ? `[${attachment.name}]` : '';
|
|
39
|
+
content = content ? `${content}\n\n${label}\n${text}` : `${label}\n${text}`;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Save to disk — Claude Code can Read images, PDFs, CSVs, etc.
|
|
43
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
44
|
+
const filename = `${message.id}-${attachment.name || 'file'}`;
|
|
45
|
+
const filepath = join(ATTACHMENT_DIR, filename);
|
|
46
|
+
writeFileSync(filepath, buffer);
|
|
47
|
+
const note = `[User sent a file: ${attachment.name || 'file'} (${formatSize(buffer.length)}). Saved to ${filepath} — use the Read tool to view it.]`;
|
|
48
|
+
content = content ? `${content}\n\n${note}` : note;
|
|
49
|
+
console.error(`[discord-filtered] Downloaded: ${filepath} (${formatSize(buffer.length)})`);
|
|
22
50
|
}
|
|
23
51
|
}
|
|
24
52
|
catch (err) {
|
|
@@ -27,6 +55,13 @@ async function resolveTextAttachments(message) {
|
|
|
27
55
|
}
|
|
28
56
|
return content;
|
|
29
57
|
}
|
|
58
|
+
function formatSize(bytes) {
|
|
59
|
+
if (bytes < 1024)
|
|
60
|
+
return `${bytes}B`;
|
|
61
|
+
if (bytes < 1024 * 1024)
|
|
62
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
63
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
64
|
+
}
|
|
30
65
|
export function createDiscordClient(config, onMessage) {
|
|
31
66
|
const client = new Client({
|
|
32
67
|
intents: [
|
|
@@ -37,7 +72,7 @@ export function createDiscordClient(config, onMessage) {
|
|
|
37
72
|
});
|
|
38
73
|
client.on('messageCreate', async (message) => {
|
|
39
74
|
if (shouldForwardMessage(message.channel.id, message.author.id, message.author.bot, config.channelId, config.allowedUsers)) {
|
|
40
|
-
const content = await
|
|
75
|
+
const content = await resolveAttachments(message);
|
|
41
76
|
if (content) {
|
|
42
77
|
onMessage(content, message);
|
|
43
78
|
}
|
package/package.json
CHANGED
|
@@ -64,8 +64,14 @@ if [ -n "$CHANNEL_ID" ] && [ "$CHANNEL_ID" != "null" ]; then
|
|
|
64
64
|
echo "Discord channel deleted."
|
|
65
65
|
fi
|
|
66
66
|
|
|
67
|
-
# Archive worker directory
|
|
68
|
-
|
|
67
|
+
# Archive worker directory (append counter if archive already exists from same day)
|
|
68
|
+
ARCHIVE_BASE="$ONKOL_DIR/workers/.archive/${DATE}-${WORKER_NAME}"
|
|
69
|
+
ARCHIVE_DIR="$ARCHIVE_BASE"
|
|
70
|
+
COUNTER=1
|
|
71
|
+
while [ -d "$ARCHIVE_DIR" ]; do
|
|
72
|
+
ARCHIVE_DIR="${ARCHIVE_BASE}-${COUNTER}"
|
|
73
|
+
COUNTER=$((COUNTER + 1))
|
|
74
|
+
done
|
|
69
75
|
mkdir -p "$ONKOL_DIR/workers/.archive"
|
|
70
76
|
mv "$WORKER_DIR" "$ARCHIVE_DIR"
|
|
71
77
|
echo "Worker directory archived to $ARCHIVE_DIR"
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { Client, GatewayIntentBits, type Message, type Attachment } from 'discord.js'
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { tmpdir } from 'os'
|
|
5
|
+
|
|
6
|
+
const ATTACHMENT_DIR = join(tmpdir(), 'onkol-attachments')
|
|
7
|
+
mkdirSync(ATTACHMENT_DIR, { recursive: true })
|
|
8
|
+
|
|
9
|
+
// Small text files get inlined into the message content
|
|
10
|
+
const INLINE_MAX_BYTES = 10_000
|
|
11
|
+
const INLINE_CONTENT_TYPES = ['text/', 'application/json', 'application/xml', 'application/csv']
|
|
2
12
|
|
|
3
13
|
export interface DiscordClientConfig {
|
|
4
14
|
botToken: string
|
|
@@ -19,27 +29,53 @@ export function shouldForwardMessage(
|
|
|
19
29
|
return true
|
|
20
30
|
}
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
function shouldInline(a: Attachment): boolean {
|
|
33
|
+
if (a.name?.endsWith('.txt') || a.name?.endsWith('.csv') || a.name?.endsWith('.json') || a.name?.endsWith('.md')) {
|
|
34
|
+
return (a.size || 0) <= INLINE_MAX_BYTES
|
|
35
|
+
}
|
|
36
|
+
const ct = a.contentType || ''
|
|
37
|
+
return INLINE_CONTENT_TYPES.some(t => ct.startsWith(t)) && (a.size || 0) <= INLINE_MAX_BYTES
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Download all attachments: small text gets inlined, everything else saved to disk.
|
|
41
|
+
// Claude Code's Read tool handles images, PDFs, CSVs, notebooks, etc. natively.
|
|
42
|
+
async function resolveAttachments(message: Message): Promise<string> {
|
|
25
43
|
let content = message.content
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
for (const attachment of textAttachments.values()) {
|
|
44
|
+
|
|
45
|
+
for (const attachment of message.attachments.values()) {
|
|
30
46
|
try {
|
|
31
47
|
const res = await fetch(attachment.url)
|
|
32
|
-
if (res.ok)
|
|
48
|
+
if (!res.ok) continue
|
|
49
|
+
|
|
50
|
+
if (shouldInline(attachment)) {
|
|
51
|
+
// Inline small text files directly into the message
|
|
33
52
|
const text = await res.text()
|
|
34
|
-
|
|
53
|
+
const label = attachment.name ? `[${attachment.name}]` : ''
|
|
54
|
+
content = content ? `${content}\n\n${label}\n${text}` : `${label}\n${text}`
|
|
55
|
+
} else {
|
|
56
|
+
// Save to disk — Claude Code can Read images, PDFs, CSVs, etc.
|
|
57
|
+
const buffer = Buffer.from(await res.arrayBuffer())
|
|
58
|
+
const filename = `${message.id}-${attachment.name || 'file'}`
|
|
59
|
+
const filepath = join(ATTACHMENT_DIR, filename)
|
|
60
|
+
writeFileSync(filepath, buffer)
|
|
61
|
+
const note = `[User sent a file: ${attachment.name || 'file'} (${formatSize(buffer.length)}). Saved to ${filepath} — use the Read tool to view it.]`
|
|
62
|
+
content = content ? `${content}\n\n${note}` : note
|
|
63
|
+
console.error(`[discord-filtered] Downloaded: ${filepath} (${formatSize(buffer.length)})`)
|
|
35
64
|
}
|
|
36
65
|
} catch (err) {
|
|
37
66
|
console.error(`[discord-filtered] Failed to fetch attachment ${attachment.name}: ${err}`)
|
|
38
67
|
}
|
|
39
68
|
}
|
|
69
|
+
|
|
40
70
|
return content
|
|
41
71
|
}
|
|
42
72
|
|
|
73
|
+
function formatSize(bytes: number): string {
|
|
74
|
+
if (bytes < 1024) return `${bytes}B`
|
|
75
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`
|
|
76
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`
|
|
77
|
+
}
|
|
78
|
+
|
|
43
79
|
export function createDiscordClient(
|
|
44
80
|
config: DiscordClientConfig,
|
|
45
81
|
onMessage: (content: string, message: Message) => void
|
|
@@ -62,7 +98,7 @@ export function createDiscordClient(
|
|
|
62
98
|
config.allowedUsers
|
|
63
99
|
)
|
|
64
100
|
) {
|
|
65
|
-
const content = await
|
|
101
|
+
const content = await resolveAttachments(message)
|
|
66
102
|
if (content) {
|
|
67
103
|
onMessage(content, message)
|
|
68
104
|
}
|