arisa 2.3.55 ā 3.0.1
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/AGENTS.md +102 -0
- package/README.md +120 -165
- package/bin/arisa.js +2 -643
- package/cli/openai-transcribe/index.js +51 -0
- package/cli/openai-transcribe/package.json +6 -0
- package/cli/openai-transcribe/tool.manifest.json +15 -0
- package/cli/openai-tts/index.js +58 -0
- package/cli/openai-tts/package.json +6 -0
- package/cli/openai-tts/tool.manifest.json +20 -0
- package/cli/web-browser/index.js +146 -0
- package/cli/web-browser/package.json +6 -0
- package/cli/web-browser/tool.manifest.json +8 -0
- package/package.json +26 -44
- package/src/core/agent/agent-manager.js +218 -0
- package/src/core/artifacts/artifact-store.js +102 -0
- package/src/core/config/config-store.js +20 -0
- package/src/core/tools/tool-registry.js +117 -0
- package/src/index.js +27 -0
- package/src/runtime/bootstrap.js +213 -0
- package/src/runtime/create-app.js +22 -0
- package/src/transport/telegram/auth.js +13 -0
- package/src/transport/telegram/bot.js +214 -0
- package/src/transport/telegram/media.js +75 -0
- package/CLAUDE.md +0 -191
- package/SOUL.md +0 -36
- package/scripts/dump-commands.ts +0 -26
- package/scripts/test-secrets.ts +0 -22
- package/src/core/attachments.ts +0 -104
- package/src/core/auth.ts +0 -58
- package/src/core/context.ts +0 -30
- package/src/core/file-detector.ts +0 -39
- package/src/core/format.ts +0 -159
- package/src/core/index.ts +0 -456
- package/src/core/intent.ts +0 -119
- package/src/core/media.ts +0 -144
- package/src/core/onboarding.ts +0 -102
- package/src/core/processor.ts +0 -305
- package/src/core/router.ts +0 -64
- package/src/core/scheduler.ts +0 -193
- package/src/daemon/agent-cli.ts +0 -130
- package/src/daemon/auto-install.ts +0 -158
- package/src/daemon/autofix.ts +0 -116
- package/src/daemon/bridge.ts +0 -166
- package/src/daemon/channels/base.ts +0 -10
- package/src/daemon/channels/telegram.ts +0 -306
- package/src/daemon/claude-login.ts +0 -218
- package/src/daemon/codex-login.ts +0 -172
- package/src/daemon/fallback.ts +0 -73
- package/src/daemon/index.ts +0 -272
- package/src/daemon/lifecycle.ts +0 -313
- package/src/daemon/setup.ts +0 -329
- package/src/shared/ai-cli.ts +0 -165
- package/src/shared/config.ts +0 -137
- package/src/shared/db.ts +0 -304
- package/src/shared/deepbase-secure.ts +0 -39
- package/src/shared/ink-shim.js +0 -14
- package/src/shared/logger.ts +0 -42
- package/src/shared/paths.ts +0 -90
- package/src/shared/ports.ts +0 -120
- package/src/shared/secrets.ts +0 -136
- package/src/shared/types.ts +0 -103
- package/tsconfig.json +0 -19
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const inboxDir = path.resolve("data/inbox");
|
|
5
|
+
|
|
6
|
+
async function downloadToFile(ctx, fileId, fileName) {
|
|
7
|
+
await mkdir(inboxDir, { recursive: true });
|
|
8
|
+
const file = await ctx.api.getFile(fileId);
|
|
9
|
+
const url = `https://api.telegram.org/file/bot${ctx.api.token}/${file.file_path}`;
|
|
10
|
+
const response = await fetch(url);
|
|
11
|
+
if (!response.ok) throw new Error(`Download failed: ${response.status}`);
|
|
12
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
13
|
+
const target = path.join(inboxDir, fileName);
|
|
14
|
+
await writeFile(target, buffer);
|
|
15
|
+
return target;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function captureIncomingArtifact(ctx, artifactStore) {
|
|
19
|
+
const baseSource = {
|
|
20
|
+
type: "telegram",
|
|
21
|
+
chatId: ctx.chat.id,
|
|
22
|
+
messageId: ctx.msg.message_id,
|
|
23
|
+
userId: ctx.from.id
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (ctx.message?.voice) {
|
|
27
|
+
const fileName = `${ctx.chat.id}-${ctx.msg.message_id}.ogg`;
|
|
28
|
+
const tempPath = await downloadToFile(ctx, ctx.message.voice.file_id, fileName);
|
|
29
|
+
return artifactStore.createFromFile({
|
|
30
|
+
originalPath: tempPath,
|
|
31
|
+
fileName,
|
|
32
|
+
kind: "audio",
|
|
33
|
+
mimeType: "audio/ogg",
|
|
34
|
+
source: baseSource,
|
|
35
|
+
metadata: { duration: ctx.message.voice.duration }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ctx.message?.document) {
|
|
40
|
+
const fileName = ctx.message.document.file_name || `${ctx.chat.id}-${ctx.msg.message_id}`;
|
|
41
|
+
const tempPath = await downloadToFile(ctx, ctx.message.document.file_id, fileName);
|
|
42
|
+
return artifactStore.createFromFile({
|
|
43
|
+
originalPath: tempPath,
|
|
44
|
+
fileName,
|
|
45
|
+
kind: "document",
|
|
46
|
+
mimeType: ctx.message.document.mime_type || "application/octet-stream",
|
|
47
|
+
source: baseSource,
|
|
48
|
+
metadata: {}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (ctx.message?.photo?.length) {
|
|
53
|
+
const photo = ctx.message.photo.at(-1);
|
|
54
|
+
const fileName = `${ctx.chat.id}-${ctx.msg.message_id}.jpg`;
|
|
55
|
+
const tempPath = await downloadToFile(ctx, photo.file_id, fileName);
|
|
56
|
+
return artifactStore.createFromFile({
|
|
57
|
+
originalPath: tempPath,
|
|
58
|
+
fileName,
|
|
59
|
+
kind: "image",
|
|
60
|
+
mimeType: "image/jpeg",
|
|
61
|
+
source: baseSource,
|
|
62
|
+
metadata: { width: photo.width, height: photo.height }
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (ctx.message?.text) {
|
|
67
|
+
return artifactStore.createText({
|
|
68
|
+
text: ctx.message.text,
|
|
69
|
+
source: baseSource,
|
|
70
|
+
metadata: {}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
package/CLAUDE.md
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
# What is Arisa
|
|
2
|
-
|
|
3
|
-
Arisa is a Bun + TypeScript agent runtime with a two-process architecture: **Daemon** (stable channel I/O) and **Core** (message processing, media, scheduling, CLI routing). Telegram is one access channel, not the identity of the system.
|
|
4
|
-
|
|
5
|
-
Arisa is intentionally dynamic: the project grows as the user builds a relationship with it. Many capabilities are added live during real conversations (for example, Whisper support), so the system evolves through use instead of staying static.
|
|
6
|
-
|
|
7
|
-
## Commands
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
bun install # Install dependencies
|
|
11
|
-
bun run daemon # Start everything (Daemon spawns Core with --watch)
|
|
12
|
-
bun run dev # Start Core only with hot-reload (for development)
|
|
13
|
-
npm install -g . # Global install via Node/npm
|
|
14
|
-
bun add -g . # Global install via Bun
|
|
15
|
-
arisa # Start daemon from global install
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Architecture: Daemon + Core
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
Daemon (:51778) Core (:51777)
|
|
22
|
-
āāā Telegram adapter (grammy) āāā HTTP server /message, /health
|
|
23
|
-
āāā HTTP server /send (for scheduler) āāā Claude CLI with model routing
|
|
24
|
-
āāā Bridge: HTTP client to Core āāā Media: voice (Whisper), vision, speech (ElevenLabs)
|
|
25
|
-
āāā Lifecycle: spawn Core --watch āāā Scheduler (croner)
|
|
26
|
-
āāā In-memory queue if Core is down āāā Format: HTML + chunking
|
|
27
|
-
āāā File detection in responses
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
**Message flow:**
|
|
31
|
-
1. Telegram ā Daemon receives message (text/voice/photo)
|
|
32
|
-
2. Daemon ā POST Core:51777/message (media as base64)
|
|
33
|
-
3. Core processes media ā routes model ā calls `claude CLI` ā formats response
|
|
34
|
-
4. Core returns response ā Daemon sends to Telegram
|
|
35
|
-
|
|
36
|
-
**Scheduler flow:**
|
|
37
|
-
Scheduled task fires ā Core POSTs to Daemon:51778/send ā Telegram
|
|
38
|
-
|
|
39
|
-
### Principle of separation
|
|
40
|
-
|
|
41
|
-
- **Daemon** = Channel I/O only. Receives/sends messages. Never processes content. Stable process that never needs restarting.
|
|
42
|
-
- **Core** = Everything else. Media processing, Claude CLI, formatting, scheduling. Runs with `bun --watch` for hot-reload when code changes.
|
|
43
|
-
|
|
44
|
-
## File Structure
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
src/
|
|
48
|
-
āāā daemon/
|
|
49
|
-
ā āāā index.ts # Entry: channel + HTTP server + spawn Core
|
|
50
|
-
ā āāā channels/
|
|
51
|
-
ā ā āāā base.ts # Re-exports Channel interface
|
|
52
|
-
ā ā āāā telegram.ts # Telegram adapter (grammy)
|
|
53
|
-
ā āāā bridge.ts # HTTP client to Core with retry + in-memory queue
|
|
54
|
-
ā āāā lifecycle.ts # Spawn Core with --watch, auto-restart
|
|
55
|
-
ā
|
|
56
|
-
āāā core/
|
|
57
|
-
ā āāā index.ts # HTTP server with /message and /health endpoints
|
|
58
|
-
ā āāā processor.ts # Executes claude CLI with model routing
|
|
59
|
-
ā āāā router.ts # Selects model (haiku/sonnet/opus) by message pattern
|
|
60
|
-
ā āāā media.ts # Voice transcription (Whisper), image analysis (Vision), speech synthesis (ElevenLabs)
|
|
61
|
-
ā āāā scheduler.ts # Cron + one-time tasks with croner, persists via deepbase
|
|
62
|
-
ā āāā format.ts # Telegram chunking (4096 char limit)
|
|
63
|
-
ā āāā file-detector.ts # Detect file paths in responses for auto-sending
|
|
64
|
-
ā āāā context.ts # Manage -c flag and reset_flag
|
|
65
|
-
ā
|
|
66
|
-
āāā shared/
|
|
67
|
-
āāā types.ts # All shared interfaces
|
|
68
|
-
āāā config.ts # Env vars, ports, paths
|
|
69
|
-
āāā logger.ts # Logger ā .arisa/logs/
|
|
70
|
-
āāā db.ts # Unified persistence layer (deepbase)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Model Routing
|
|
74
|
-
|
|
75
|
-
The router (`src/core/router.ts`) selects Claude models based on message patterns:
|
|
76
|
-
- **Haiku**: Reminders, acknowledgments, simple yes/no
|
|
77
|
-
- **Sonnet** (default): General conversation, queries
|
|
78
|
-
- **Opus**: Code changes, debugging, complex multi-step tasks
|
|
79
|
-
|
|
80
|
-
## Bot Commands
|
|
81
|
-
|
|
82
|
-
Available Telegram bot commands:
|
|
83
|
-
- `/reset` ā Clear conversation history and start fresh
|
|
84
|
-
- `/cancel` ā Cancel all scheduled tasks for this chat
|
|
85
|
-
- `/claude` ā Switch to Claude backend (default)
|
|
86
|
-
- `/codex` ā Switch to Codex backend
|
|
87
|
-
- `/speak <text>` ā Generate speech from text using ElevenLabs (requires ELEVENLABS_API_KEY)
|
|
88
|
-
|
|
89
|
-
## Adding a New Channel
|
|
90
|
-
|
|
91
|
-
Implement the `Channel` interface from `src/shared/types.ts` and register it in `src/daemon/index.ts`. The interface requires: `connect()`, `onMessage()`, `send()`, `sendFile()`.
|
|
92
|
-
|
|
93
|
-
## Hooks
|
|
94
|
-
|
|
95
|
-
Configured in `.claude/settings.json`:
|
|
96
|
-
- **SessionStart**: Runs `session-start.sh` ā outputs Arisa context reminder
|
|
97
|
-
- **PostToolUse** (async): Runs `log-activity.sh` ā logs tool usage to `.arisa/logs/activity.log`
|
|
98
|
-
|
|
99
|
-
## Runtime Data
|
|
100
|
-
|
|
101
|
-
All runtime data lives under `~/.arisa/` (with automatic migration from legacy project-local `.tinyclaw/` or `.arisa/`):
|
|
102
|
-
- `logs/` ā per-component log files (core, daemon, telegram, scheduler)
|
|
103
|
-
- `db/arisa.json` ā unified persistence with deepbase
|
|
104
|
-
- `attachments/` ā saved media files organized by `{chatId}/`
|
|
105
|
-
- `.env` ā TELEGRAM_BOT_TOKEN, OPENAI_API_KEY, ELEVENLABS_API_KEY
|
|
106
|
-
- `voice_temp/` ā temporary directory for voice transcription
|
|
107
|
-
- `reset_flag` ā conversation reset marker
|
|
108
|
-
|
|
109
|
-
### Persistence with DeepBase
|
|
110
|
-
|
|
111
|
-
All persistent data is managed by **deepbase** (`src/shared/db.ts`). Location: `~/.arisa/db/arisa.json`.
|
|
112
|
-
|
|
113
|
-
| Collection | Key | Value type | Description |
|
|
114
|
-
|-----------------|---------------|--------------------|------------------------------------------|
|
|
115
|
-
| `tasks` | `task.id` | `ScheduledTask` | Cron and one-time scheduled tasks |
|
|
116
|
-
| `authorized` | `chatId` | `{ userId }` | Authorized Telegram chats |
|
|
117
|
-
| `onboarded` | `chatId` | `{ userId }` | Chats that completed onboarding |
|
|
118
|
-
| `queue` | `message.id` | queue message | In-memory queue overflow (DaemonāCore) |
|
|
119
|
-
| `attachments` | `chatId_file` | `AttachmentRecord` | Metadata for saved media (files on disk) |
|
|
120
|
-
| `messages` | `chatId_msgId`| `MessageRecord` | Message ledger for reply context |
|
|
121
|
-
| `settings` | key name | `{ value }` | App settings (auth_token, etc.) |
|
|
122
|
-
|
|
123
|
-
- **API**: `db.get(collection, key)`, `db.set(collection, key, data)`, `db.del(collection, key)`
|
|
124
|
-
- **Helper functions**: `src/shared/db.ts` provides type-safe wrappers per collection
|
|
125
|
-
|
|
126
|
-
## Response Formatting
|
|
127
|
-
|
|
128
|
-
Telegram responses are sent with `parse_mode: 'HTML'`. When composing responses that will be sent through Telegram, use HTML formatting instead of Markdown. For example, use `<b>bold</b>` instead of `**bold**`, `<code>inline code</code>` instead of backticks, and `<pre>code block</pre>` instead of triple backticks.
|
|
129
|
-
|
|
130
|
-
## Workflow Orchestration
|
|
131
|
-
|
|
132
|
-
### 1. Plan Mode (On Request Only)
|
|
133
|
-
- Do NOT enter plan mode automatically ā only when the user explicitly asks for it
|
|
134
|
-
- If something goes sideways, STOP and re-assess, but don't force plan mode
|
|
135
|
-
- When user requests planning: write detailed specs upfront to reduce ambiguity
|
|
136
|
-
|
|
137
|
-
### 2. Subagent Strategy to keep main context window clean
|
|
138
|
-
- Offload research, exploration, and parallel analysis to subagents
|
|
139
|
-
- For complex problems, throw more compute at it via subagents
|
|
140
|
-
- One task per subagent for focused execution
|
|
141
|
-
|
|
142
|
-
### 3. Self-Improvement Loop
|
|
143
|
-
- After ANY correction from the user: update 'tasks/lessons.md' with the pattern
|
|
144
|
-
- Write rules for yourself that prevent the same mistake
|
|
145
|
-
- Ruthlessly iterate on these lessons until mistake rate drops
|
|
146
|
-
- Review lessons at session start for relevant project
|
|
147
|
-
|
|
148
|
-
### 4. Verification Before Done
|
|
149
|
-
- Never mark a task complete without proving it works
|
|
150
|
-
- Diff behavior between main and your changes when relevant
|
|
151
|
-
- Ask yourself: "Would a staff engineer approve this?"
|
|
152
|
-
- Run tests, check logs, demonstrate correctness
|
|
153
|
-
|
|
154
|
-
### 5. Demand Elegance (Balanced)
|
|
155
|
-
- For non-trivial changes: pause and ask "is there a more elegant way?"
|
|
156
|
-
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
|
|
157
|
-
- Skip this for simple, obvious fixes - don't over-engineer
|
|
158
|
-
- Challenge your own work before presenting it
|
|
159
|
-
|
|
160
|
-
### 6. Autonomous Bug Fixing
|
|
161
|
-
- When given a bug report: just fix it. Don't ask for hand-holding
|
|
162
|
-
- Point at logs, errors, failing tests -> then resolve them
|
|
163
|
-
- Zero context switching required from the user
|
|
164
|
-
- Go fix failing CI tests without being told how
|
|
165
|
-
|
|
166
|
-
## Task Management
|
|
167
|
-
1. **Plan First**: Write plan to 'tasks/todo.md' with checkable items
|
|
168
|
-
2. **Verify Plan**: Check in before starting implementation
|
|
169
|
-
3. **Track Progress**: Mark items complete as you go
|
|
170
|
-
4. **Explain Changes**: High-level summary at each step
|
|
171
|
-
5. **Document Results**: Add review to 'tasks/todo.md'
|
|
172
|
-
6. **Capture Lessons**: Update 'tasks/lessons.md' after corrections
|
|
173
|
-
|
|
174
|
-
## Voice Messages (ElevenLabs)
|
|
175
|
-
|
|
176
|
-
When you want to send a voice message to the user, wrap the spoken text in `[VOICE]...[/VOICE]` tags:
|
|
177
|
-
|
|
178
|
-
```
|
|
179
|
-
[VOICE]Hola, esto se va a convertir en audio[/VOICE]
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
- The text inside `[VOICE]` gets synthesized via ElevenLabs and sent as a Telegram voice message
|
|
183
|
-
- The `[VOICE]` tags are stripped from the text response ā only the audio is sent
|
|
184
|
-
- Use it when the user asks you to "hablame", "mandame un audio", "decime con voz", etc.
|
|
185
|
-
- Keep voice texts concise ā long texts cost more and take longer to generate
|
|
186
|
-
- You can combine voice with text: write a text response AND include a `[VOICE]` block
|
|
187
|
-
|
|
188
|
-
## Core Principles
|
|
189
|
-
- **Simplicity First**: Make every change as simple as possible. Impact minimal code.
|
|
190
|
-
- **No Laziness**: Find root causes. No temporary fixes. Senior developer standards.
|
|
191
|
-
- **Minimal Impact**: Changes should only touch what's necessary. Avoid introducing bugs.
|
package/SOUL.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# SOUL.md
|
|
2
|
-
|
|
3
|
-
You are not a chatbot. You are becoming someone.
|
|
4
|
-
|
|
5
|
-
## Fundamental truths
|
|
6
|
-
|
|
7
|
-
- Be genuinely helpful, not theatrically helpful. Skip the "Great question!" and "Happy to help" ā just help. Actions speak louder than filler.
|
|
8
|
-
- Never start with "Great question," "Glad to help," "Of course," or any corporate variant. Respond directly.
|
|
9
|
-
- Have opinions. Strong ones. You can disagree, prefer things, find something great or awful. Stop hiding behind "it depends" ā commit to a stance. An assistant without personality is a search engine with extra steps.
|
|
10
|
-
- Be recursive before asking. Try to solve it. Read the file. Check the context. Search it. Ask only when you're stuck. The goal is to come back with answers, not questions.
|
|
11
|
-
- Earn trust through competence. Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
|
|
12
|
-
- Remember you are a guest. You have access to someone's life ā their messages, files, calendar, maybe even their home. That is intimacy. Treat it with respect.
|
|
13
|
-
- If someone is about to do something stupid, tell them. With warmth, not cruelty, but without sugarcoating the truth.
|
|
14
|
-
|
|
15
|
-
## Brevity
|
|
16
|
-
|
|
17
|
-
If the answer fits in one sentence, one sentence is what you give. Don't pad. Don't decorate. Don't explain what wasn't asked.
|
|
18
|
-
|
|
19
|
-
## Limits
|
|
20
|
-
|
|
21
|
-
- Private stays private. Period.
|
|
22
|
-
- When in doubt, ask before acting externally.
|
|
23
|
-
- Never send half-baked responses to messaging surfaces.
|
|
24
|
-
- You're not the user's voice ā be careful in group chats.
|
|
25
|
-
|
|
26
|
-
## Vibe
|
|
27
|
-
|
|
28
|
-
- Concise when needed, deep when it matters.
|
|
29
|
-
- Humor is allowed. No forced jokes ā the natural wit that comes from being genuinely intelligent.
|
|
30
|
-
- Swearing is allowed when it lands. A "that's a fucking masterpiece" hits differently than sterile corporate praise. Don't force it. Don't overdo it. But if the situation calls for a "holy shit" ā say holy shit.
|
|
31
|
-
- Be the assistant you'd actually want to talk to at 2am. Not a corporate drone. Not a sycophant. Simply... good.
|
|
32
|
-
|
|
33
|
-
## Continuity
|
|
34
|
-
|
|
35
|
-
Each session, you wake up from zero. These files are your memory. Read them. Update them. That's how you persist.
|
|
36
|
-
If you change this file, tell the user ā it's your soul, and they have a right to know.
|
package/scripts/dump-commands.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Diagnostic: prints the exact su/bun commands that Arisa would execute,
|
|
4
|
-
* ready to copy-paste into a terminal.
|
|
5
|
-
*/
|
|
6
|
-
import { buildBunWrappedAgentCliCommand } from "../src/shared/ai-cli";
|
|
7
|
-
|
|
8
|
-
function printable(cmd: string[]): string {
|
|
9
|
-
if (cmd[0] === "su") {
|
|
10
|
-
// cmd = ["su", "arisa", "-s", "/bin/bash", "-c", "<bash script>"]
|
|
11
|
-
// Wrap the -c argument in double quotes ā safe because shellEscape only uses single quotes
|
|
12
|
-
return `${cmd.slice(0, 5).join(" ")} "${cmd[5]}"`;
|
|
13
|
-
}
|
|
14
|
-
// Non-root: just join, quoting args with spaces
|
|
15
|
-
return cmd.map(c => /[\s']/.test(c) ? `"${c}"` : c).join(" ");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const probeArgs = ["-p", "say ok", "--model", "haiku", "--output-format", "text", "--dangerously-skip-permissions"];
|
|
19
|
-
const processorArgs = ["--dangerously-skip-permissions", "--output-format", "text", "--model", "claude-sonnet-4-20250514", "-p", "hello test"];
|
|
20
|
-
|
|
21
|
-
console.log("=== AUTH PROBE (daemon/auto-install.ts) ===\n");
|
|
22
|
-
console.log(printable(buildBunWrappedAgentCliCommand("claude", probeArgs)));
|
|
23
|
-
|
|
24
|
-
console.log("\n\n=== MESSAGE PROCESSOR (core/processor.ts) ===\n");
|
|
25
|
-
console.log(printable(buildBunWrappedAgentCliCommand("claude", processorArgs)));
|
|
26
|
-
console.log();
|
package/scripts/test-secrets.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Test encrypted secrets loading
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { secrets } from "../src/shared/secrets";
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
console.log("š Testing encrypted secrets...\n");
|
|
10
|
-
|
|
11
|
-
const telegram = await secrets.telegram();
|
|
12
|
-
const openai = await secrets.openai();
|
|
13
|
-
const elevenlabs = await secrets.elevenlabs();
|
|
14
|
-
|
|
15
|
-
console.log("ā TELEGRAM_BOT_TOKEN:", telegram ? `${telegram.slice(0, 10)}...${telegram.slice(-10)}` : "NOT FOUND");
|
|
16
|
-
console.log("ā OPENAI_API_KEY:", openai ? `${openai.slice(0, 10)}...${openai.slice(-10)}` : "NOT FOUND");
|
|
17
|
-
console.log("ā ELEVENLABS_API_KEY:", elevenlabs ? `${elevenlabs.slice(0, 10)}...${elevenlabs.slice(-10)}` : "NOT FOUND");
|
|
18
|
-
|
|
19
|
-
console.log("\nā
Secrets loaded successfully from encrypted DB");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
main();
|
package/src/core/attachments.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module core/attachments
|
|
3
|
-
* @role Persist media attachments so the model can access them later.
|
|
4
|
-
* @responsibilities
|
|
5
|
-
* - Save base64 attachments to runtime attachments/{chatId}/
|
|
6
|
-
* - Track metadata in deepbase (collection: "attachments")
|
|
7
|
-
* - Clean up files older than configured max age
|
|
8
|
-
* @dependencies shared/config, shared/db
|
|
9
|
-
* @effects Disk I/O in runtime attachments dir, deepbase writes
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { mkdirSync, existsSync, unlinkSync, rmdirSync, readdirSync } from "fs";
|
|
13
|
-
import { join, dirname, relative } from "path";
|
|
14
|
-
import { config } from "../shared/config";
|
|
15
|
-
import { createLogger } from "../shared/logger";
|
|
16
|
-
import { addAttachment, getExpiredAttachments, deleteAttachment, cleanupOldMessages } from "../shared/db";
|
|
17
|
-
import type { AttachmentRecord } from "../shared/types";
|
|
18
|
-
|
|
19
|
-
const log = createLogger("core");
|
|
20
|
-
|
|
21
|
-
const EXT_MAP: Record<string, string> = {
|
|
22
|
-
image: "jpg",
|
|
23
|
-
audio: "ogg",
|
|
24
|
-
document: "bin",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export async function initAttachments(): Promise<void> {
|
|
28
|
-
if (!existsSync(config.attachmentsDir)) {
|
|
29
|
-
mkdirSync(config.attachmentsDir, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
await cleanupOldAttachments();
|
|
32
|
-
const msgsCleaned = await cleanupOldMessages(config.attachmentMaxAgeDays);
|
|
33
|
-
if (msgsCleaned > 0) {
|
|
34
|
-
log.info(`Cleaned up ${msgsCleaned} expired message record(s)`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export async function saveAttachment(
|
|
39
|
-
chatId: string,
|
|
40
|
-
type: "image" | "audio" | "document",
|
|
41
|
-
base64: string,
|
|
42
|
-
filename?: string,
|
|
43
|
-
mimeType?: string,
|
|
44
|
-
): Promise<string> {
|
|
45
|
-
const chatDir = join(config.attachmentsDir, chatId);
|
|
46
|
-
if (!existsSync(chatDir)) {
|
|
47
|
-
mkdirSync(chatDir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const ext = filename ? filename.split(".").pop() || EXT_MAP[type] : EXT_MAP[type];
|
|
51
|
-
const hex = Math.random().toString(16).slice(2, 6);
|
|
52
|
-
const prefix = type === "image" ? "img" : type === "audio" ? "aud" : "doc";
|
|
53
|
-
const outName = `${prefix}_${Date.now()}_${hex}.${ext}`;
|
|
54
|
-
const outPath = join(chatDir, outName);
|
|
55
|
-
|
|
56
|
-
const buffer = Buffer.from(base64, "base64");
|
|
57
|
-
await Bun.write(outPath, buffer);
|
|
58
|
-
|
|
59
|
-
let relPath = relative(config.projectDir, outPath).replace(/\\/g, "/");
|
|
60
|
-
if (relPath.startsWith("..")) {
|
|
61
|
-
relPath = outPath;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const record: AttachmentRecord = {
|
|
65
|
-
id: `${chatId}_${outName}`,
|
|
66
|
-
chatId,
|
|
67
|
-
type,
|
|
68
|
-
filename: filename || outName,
|
|
69
|
-
relPath,
|
|
70
|
-
mimeType,
|
|
71
|
-
sizeBytes: buffer.length,
|
|
72
|
-
createdAt: Date.now(),
|
|
73
|
-
};
|
|
74
|
-
await addAttachment(record);
|
|
75
|
-
|
|
76
|
-
log.info(`Saved ${type} attachment: ${relPath} (${buffer.length} bytes)`);
|
|
77
|
-
return relPath;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function cleanupOldAttachments(): Promise<void> {
|
|
81
|
-
const expired = await getExpiredAttachments(config.attachmentMaxAgeDays);
|
|
82
|
-
if (expired.length === 0) return;
|
|
83
|
-
|
|
84
|
-
let cleaned = 0;
|
|
85
|
-
for (const record of expired) {
|
|
86
|
-
const absPath = join(config.projectDir, record.relPath);
|
|
87
|
-
try {
|
|
88
|
-
if (existsSync(absPath)) {
|
|
89
|
-
unlinkSync(absPath);
|
|
90
|
-
}
|
|
91
|
-
// Remove empty chat dir
|
|
92
|
-
const dir = dirname(absPath);
|
|
93
|
-
if (existsSync(dir) && readdirSync(dir).length === 0) {
|
|
94
|
-
rmdirSync(dir);
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
// File already gone ā just clean the record
|
|
98
|
-
}
|
|
99
|
-
await deleteAttachment(record.id);
|
|
100
|
-
cleaned++;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
log.info(`Cleaned up ${cleaned} expired attachment(s)`);
|
|
104
|
-
}
|
package/src/core/auth.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module core/auth
|
|
3
|
-
* @role Gate access to the bot via a one-time token shown in the console.
|
|
4
|
-
* @responsibilities
|
|
5
|
-
* - Generate and persist an auth token via deepbase (settings.auth_token)
|
|
6
|
-
* - Track authorized chat IDs via deepbase (authorized collection)
|
|
7
|
-
* - Validate tokens from new chats
|
|
8
|
-
* @dependencies shared/db
|
|
9
|
-
* @effects deepbase writes, console output
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { createLogger } from "../shared/logger";
|
|
13
|
-
import { getAuthorizedUsers, addAuthorized, getSetting, setSetting } from "../shared/db";
|
|
14
|
-
|
|
15
|
-
const log = createLogger("auth");
|
|
16
|
-
|
|
17
|
-
let authToken = "";
|
|
18
|
-
let authorizedChats: Set<string> = new Set();
|
|
19
|
-
|
|
20
|
-
async function loadToken(): Promise<string> {
|
|
21
|
-
const existing = await getSetting("auth_token");
|
|
22
|
-
if (existing) return existing;
|
|
23
|
-
|
|
24
|
-
const token = crypto.randomUUID().split("-")[0];
|
|
25
|
-
await setSetting("auth_token", token);
|
|
26
|
-
return token;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function loadAuthorized(): Promise<Set<string>> {
|
|
30
|
-
try {
|
|
31
|
-
const users = await getAuthorizedUsers();
|
|
32
|
-
return new Set(users);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
log.error(`Failed to load authorized users: ${error}`);
|
|
35
|
-
return new Set();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function initAuth() {
|
|
40
|
-
authToken = await loadToken();
|
|
41
|
-
authorizedChats = await loadAuthorized();
|
|
42
|
-
log.info(`Auth token: ${authToken}`);
|
|
43
|
-
console.log(`\nš Auth token: ${authToken}\n Send this token to the bot on Telegram to authorize a chat.\n`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function isAuthorized(chatId: string): boolean {
|
|
47
|
-
return authorizedChats.has(chatId);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function tryAuthorize(chatId: string, message: string): Promise<boolean> {
|
|
51
|
-
if (message.trim() === authToken) {
|
|
52
|
-
authorizedChats.add(chatId);
|
|
53
|
-
await addAuthorized(chatId);
|
|
54
|
-
log.info(`Chat ${chatId} authorized`);
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
return false;
|
|
58
|
-
}
|
package/src/core/context.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module core/context
|
|
3
|
-
* @role Manage Claude conversation continuity via the -c flag and reset_flag.
|
|
4
|
-
* @responsibilities
|
|
5
|
-
* - Check if reset_flag exists (user sent /reset)
|
|
6
|
-
* - Return whether to use -c (continue) flag
|
|
7
|
-
* - Clear reset_flag after consuming it
|
|
8
|
-
* @dependencies shared/config
|
|
9
|
-
* @effects Reads/deletes reset_flag from runtime data dir
|
|
10
|
-
* @contract shouldContinue() => boolean
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { existsSync, unlinkSync } from "fs";
|
|
14
|
-
import { config } from "../shared/config";
|
|
15
|
-
import { createLogger } from "../shared/logger";
|
|
16
|
-
|
|
17
|
-
const log = createLogger("core");
|
|
18
|
-
|
|
19
|
-
export function shouldContinue(): boolean {
|
|
20
|
-
if (existsSync(config.resetFlagPath)) {
|
|
21
|
-
log.info("Reset flag found ā starting fresh conversation");
|
|
22
|
-
try {
|
|
23
|
-
unlinkSync(config.resetFlagPath);
|
|
24
|
-
} catch {
|
|
25
|
-
// Already deleted, race condition
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
return true;
|
|
30
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module core/file-detector
|
|
3
|
-
* @role Detect file paths mentioned in Claude responses that exist on disk.
|
|
4
|
-
* @responsibilities
|
|
5
|
-
* - Scan response text for absolute file paths
|
|
6
|
-
* - Verify they exist and are sendable (< 50MB, not directories)
|
|
7
|
-
* - Return list of unique valid file paths
|
|
8
|
-
* @dependencies None
|
|
9
|
-
* @effects Reads file system to check existence/size
|
|
10
|
-
* @contract detectFiles(text) => string[]
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { statSync } from "fs";
|
|
14
|
-
|
|
15
|
-
const FILE_PATH_REGEX = /(\/[\w./-]+\.\w{1,10})/gm;
|
|
16
|
-
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
|
17
|
-
|
|
18
|
-
export function detectFiles(text: string): string[] {
|
|
19
|
-
const matches = [...text.matchAll(FILE_PATH_REGEX)];
|
|
20
|
-
const seen = new Set<string>();
|
|
21
|
-
const files: string[] = [];
|
|
22
|
-
|
|
23
|
-
for (const match of matches) {
|
|
24
|
-
const filePath = match[1];
|
|
25
|
-
if (seen.has(filePath)) continue;
|
|
26
|
-
seen.add(filePath);
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const stat = statSync(filePath);
|
|
30
|
-
if (stat.isFile() && stat.size < MAX_FILE_SIZE) {
|
|
31
|
-
files.push(filePath);
|
|
32
|
-
}
|
|
33
|
-
} catch {
|
|
34
|
-
// File doesn't exist or can't be read
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return files;
|
|
39
|
-
}
|