onkol 0.5.2 → 0.5.5
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 +3 -4
- package/dist/plugin/discord-client.js +35 -27
- package/package.json +1 -1
- package/scripts/dissolve-worker.sh +8 -2
- package/src/plugin/discord-client.ts +35 -27
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
5
|
import { program } from 'commander';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import { mkdirSync, writeFileSync, readFileSync, copyFileSync, existsSync } from 'fs';
|
|
7
|
+
import { mkdirSync, writeFileSync, readFileSync, copyFileSync, existsSync, unlinkSync } from 'fs';
|
|
8
8
|
import { resolve } from 'path';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
10
|
import { runSetupPrompts } from './prompts.js';
|
|
@@ -34,7 +34,6 @@ function saveCheckpoint(homeDir, checkpoint) {
|
|
|
34
34
|
function clearCheckpoint(homeDir) {
|
|
35
35
|
const p = resolve(homeDir, '.onkol-setup-checkpoint.json');
|
|
36
36
|
if (existsSync(p)) {
|
|
37
|
-
const { unlinkSync } = require('fs');
|
|
38
37
|
unlinkSync(p);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
@@ -570,8 +569,8 @@ program
|
|
|
570
569
|
console.log(chalk.cyan('[1/3] Updating files from npm package...'));
|
|
571
570
|
try {
|
|
572
571
|
// 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, '
|
|
572
|
+
// __dirname is dist/cli/, so pkgRoot is two levels up (dist/cli -> dist -> package root)
|
|
573
|
+
const pkgRoot = resolve(__dirname, '../..');
|
|
575
574
|
const { readdirSync, chmodSync } = await import('fs');
|
|
576
575
|
// Try src/plugin first (has .ts files), then dist/plugin (.js files)
|
|
577
576
|
let pluginUpdated = false;
|
|
@@ -2,9 +2,11 @@ import { Client, GatewayIntentBits } from 'discord.js';
|
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
|
-
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg']);
|
|
6
5
|
const ATTACHMENT_DIR = join(tmpdir(), 'onkol-attachments');
|
|
7
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'];
|
|
8
10
|
export function shouldForwardMessage(messageChannelId, authorId, isBot, targetChannelId, allowedUsers) {
|
|
9
11
|
if (isBot)
|
|
10
12
|
return false;
|
|
@@ -14,38 +16,37 @@ export function shouldForwardMessage(messageChannelId, authorId, isBot, targetCh
|
|
|
14
16
|
return false;
|
|
15
17
|
return true;
|
|
16
18
|
}
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const name = (a.name || '').toLowerCase();
|
|
24
|
-
return IMAGE_EXTENSIONS.has(name.slice(name.lastIndexOf('.')));
|
|
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
25
|
}
|
|
26
|
-
//
|
|
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.
|
|
27
28
|
async function resolveAttachments(message) {
|
|
28
29
|
let content = message.content;
|
|
29
30
|
for (const attachment of message.attachments.values()) {
|
|
30
31
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const res = await fetch(attachment.url);
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
continue;
|
|
35
|
+
if (shouldInline(attachment)) {
|
|
36
|
+
// Inline small text files directly into the message
|
|
37
|
+
const text = await res.text();
|
|
38
|
+
const label = attachment.name ? `[${attachment.name}]` : '';
|
|
39
|
+
content = content ? `${content}\n\n${label}\n${text}` : `${label}\n${text}`;
|
|
37
40
|
}
|
|
38
|
-
else
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.error(`[discord-filtered] Downloaded image: ${filepath} (${buffer.length} bytes)`);
|
|
48
|
-
}
|
|
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)})`);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
catch (err) {
|
|
@@ -54,6 +55,13 @@ async function resolveAttachments(message) {
|
|
|
54
55
|
}
|
|
55
56
|
return content;
|
|
56
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
|
+
}
|
|
57
65
|
export function createDiscordClient(config, onMessage) {
|
|
58
66
|
const client = new Client({
|
|
59
67
|
intents: [
|
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"
|
|
@@ -3,10 +3,13 @@ import { mkdirSync, writeFileSync } from 'fs'
|
|
|
3
3
|
import { join } from 'path'
|
|
4
4
|
import { tmpdir } from 'os'
|
|
5
5
|
|
|
6
|
-
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg'])
|
|
7
6
|
const ATTACHMENT_DIR = join(tmpdir(), 'onkol-attachments')
|
|
8
7
|
mkdirSync(ATTACHMENT_DIR, { recursive: true })
|
|
9
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']
|
|
12
|
+
|
|
10
13
|
export interface DiscordClientConfig {
|
|
11
14
|
botToken: string
|
|
12
15
|
channelId: string
|
|
@@ -26,39 +29,38 @@ export function shouldForwardMessage(
|
|
|
26
29
|
return true
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const name = (a.name || '').toLowerCase()
|
|
36
|
-
return IMAGE_EXTENSIONS.has(name.slice(name.lastIndexOf('.')))
|
|
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
|
|
37
38
|
}
|
|
38
39
|
|
|
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.
|
|
40
42
|
async function resolveAttachments(message: Message): Promise<string> {
|
|
41
43
|
let content = message.content
|
|
42
44
|
|
|
43
45
|
for (const attachment of message.attachments.values()) {
|
|
44
46
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
47
|
+
const res = await fetch(attachment.url)
|
|
48
|
+
if (!res.ok) continue
|
|
49
|
+
|
|
50
|
+
if (shouldInline(attachment)) {
|
|
51
|
+
// Inline small text files directly into the message
|
|
52
|
+
const text = await res.text()
|
|
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)})`)
|
|
62
64
|
}
|
|
63
65
|
} catch (err) {
|
|
64
66
|
console.error(`[discord-filtered] Failed to fetch attachment ${attachment.name}: ${err}`)
|
|
@@ -68,6 +70,12 @@ async function resolveAttachments(message: Message): Promise<string> {
|
|
|
68
70
|
return content
|
|
69
71
|
}
|
|
70
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
|
+
|
|
71
79
|
export function createDiscordClient(
|
|
72
80
|
config: DiscordClientConfig,
|
|
73
81
|
onMessage: (content: string, message: Message) => void
|