@bryti/agent 0.0.1 → 0.1.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/Dockerfile +27 -0
- package/README.md +91 -51
- package/config.example.yml +265 -0
- package/dist/active-hours.d.ts +23 -0
- package/dist/active-hours.d.ts.map +1 -0
- package/dist/active-hours.js +68 -0
- package/dist/active-hours.js.map +1 -0
- package/dist/agent.d.ts +84 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +383 -0
- package/dist/agent.js.map +1 -0
- package/dist/channels/markdown/ir.d.ts +79 -0
- package/dist/channels/markdown/ir.d.ts.map +1 -0
- package/dist/channels/markdown/ir.js +824 -0
- package/dist/channels/markdown/ir.js.map +1 -0
- package/dist/channels/markdown/render.d.ts +35 -0
- package/dist/channels/markdown/render.d.ts.map +1 -0
- package/dist/channels/markdown/render.js +178 -0
- package/dist/channels/markdown/render.js.map +1 -0
- package/dist/channels/telegram-network-errors.d.ts +27 -0
- package/dist/channels/telegram-network-errors.d.ts.map +1 -0
- package/dist/channels/telegram-network-errors.js +156 -0
- package/dist/channels/telegram-network-errors.js.map +1 -0
- package/dist/channels/telegram.d.ts +76 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +814 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +59 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +9 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/channels/whatsapp.d.ts +45 -0
- package/dist/channels/whatsapp.d.ts.map +1 -0
- package/dist/channels/whatsapp.js +310 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +635 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands.d.ts +35 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +113 -0
- package/dist/commands.js.map +1 -0
- package/dist/compaction/history.d.ts +17 -0
- package/dist/compaction/history.d.ts.map +1 -0
- package/dist/compaction/history.js +35 -0
- package/dist/compaction/history.js.map +1 -0
- package/dist/compaction/index.d.ts +3 -0
- package/dist/compaction/index.d.ts.map +1 -0
- package/dist/compaction/index.js +3 -0
- package/dist/compaction/index.js.map +1 -0
- package/dist/compaction/proactive.d.ts +25 -0
- package/dist/compaction/proactive.d.ts.map +1 -0
- package/dist/compaction/proactive.js +87 -0
- package/dist/compaction/proactive.js.map +1 -0
- package/dist/compaction/transcript-repair.d.ts +55 -0
- package/dist/compaction/transcript-repair.d.ts.map +1 -0
- package/dist/compaction/transcript-repair.js +215 -0
- package/dist/compaction/transcript-repair.js.map +1 -0
- package/dist/config.d.ts +128 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +317 -0
- package/dist/config.js.map +1 -0
- package/dist/crash-recovery.d.ts +23 -0
- package/dist/crash-recovery.d.ts.map +1 -0
- package/dist/crash-recovery.js +96 -0
- package/dist/crash-recovery.js.map +1 -0
- package/dist/defaults/extensions/EXTENSIONS.md +158 -0
- package/dist/defaults/extensions/documents-hedgedoc.ts +153 -0
- package/dist/history.d.ts +31 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +49 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +686 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +143 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/conversation-search.d.ts +15 -0
- package/dist/memory/conversation-search.d.ts.map +1 -0
- package/dist/memory/conversation-search.js +60 -0
- package/dist/memory/conversation-search.js.map +1 -0
- package/dist/memory/core-memory.d.ts +28 -0
- package/dist/memory/core-memory.d.ts.map +1 -0
- package/dist/memory/core-memory.js +102 -0
- package/dist/memory/core-memory.js.map +1 -0
- package/dist/memory/embeddings.d.ts +44 -0
- package/dist/memory/embeddings.d.ts.map +1 -0
- package/dist/memory/embeddings.js +139 -0
- package/dist/memory/embeddings.js.map +1 -0
- package/dist/memory/search.d.ts +49 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +97 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +205 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/message-queue.d.ts +73 -0
- package/dist/message-queue.d.ts.map +1 -0
- package/dist/message-queue.js +188 -0
- package/dist/message-queue.js.map +1 -0
- package/dist/model-infra.d.ts +64 -0
- package/dist/model-infra.d.ts.map +1 -0
- package/dist/model-infra.js +202 -0
- package/dist/model-infra.js.map +1 -0
- package/dist/projection/format.d.ts +10 -0
- package/dist/projection/format.d.ts.map +1 -0
- package/dist/projection/format.js +30 -0
- package/dist/projection/format.js.map +1 -0
- package/dist/projection/index.d.ts +11 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.js +9 -0
- package/dist/projection/index.js.map +1 -0
- package/dist/projection/reflection.d.ts +119 -0
- package/dist/projection/reflection.d.ts.map +1 -0
- package/dist/projection/reflection.js +422 -0
- package/dist/projection/reflection.js.map +1 -0
- package/dist/projection/store.d.ts +144 -0
- package/dist/projection/store.d.ts.map +1 -0
- package/dist/projection/store.js +519 -0
- package/dist/projection/store.js.map +1 -0
- package/dist/projection/tools.d.ts +11 -0
- package/dist/projection/tools.d.ts.map +1 -0
- package/dist/projection/tools.js +237 -0
- package/dist/projection/tools.js.map +1 -0
- package/dist/scheduler.d.ts +36 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +286 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/system-prompt.d.ts +41 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +162 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/time.d.ts +52 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +138 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/archival-memory-tool.d.ts +8 -0
- package/dist/tools/archival-memory-tool.d.ts.map +1 -0
- package/dist/tools/archival-memory-tool.js +68 -0
- package/dist/tools/archival-memory-tool.js.map +1 -0
- package/dist/tools/conversation-search-tool.d.ts +6 -0
- package/dist/tools/conversation-search-tool.d.ts.map +1 -0
- package/dist/tools/conversation-search-tool.js +28 -0
- package/dist/tools/conversation-search-tool.js.map +1 -0
- package/dist/tools/core-memory-tool.d.ts +7 -0
- package/dist/tools/core-memory-tool.d.ts.map +1 -0
- package/dist/tools/core-memory-tool.js +59 -0
- package/dist/tools/core-memory-tool.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +15 -0
- package/dist/tools/fetch-url.d.ts.map +1 -0
- package/dist/tools/fetch-url.js +76 -0
- package/dist/tools/fetch-url.js.map +1 -0
- package/dist/tools/files.d.ts +10 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +127 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +118 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/result.d.ts +21 -0
- package/dist/tools/result.d.ts.map +1 -0
- package/dist/tools/result.js +36 -0
- package/dist/tools/result.js.map +1 -0
- package/dist/tools/skill-install.d.ts +17 -0
- package/dist/tools/skill-install.d.ts.map +1 -0
- package/dist/tools/skill-install.js +148 -0
- package/dist/tools/skill-install.js.map +1 -0
- package/dist/tools/web-search.d.ts +42 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +237 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/trust/guardrail.d.ts +60 -0
- package/dist/trust/guardrail.d.ts.map +1 -0
- package/dist/trust/guardrail.js +171 -0
- package/dist/trust/guardrail.js.map +1 -0
- package/dist/trust/index.d.ts +12 -0
- package/dist/trust/index.d.ts.map +1 -0
- package/dist/trust/index.js +12 -0
- package/dist/trust/index.js.map +1 -0
- package/dist/trust/store.d.ts +118 -0
- package/dist/trust/store.d.ts.map +1 -0
- package/dist/trust/store.js +209 -0
- package/dist/trust/store.js.map +1 -0
- package/dist/trust/wrapper.d.ts +36 -0
- package/dist/trust/wrapper.d.ts.map +1 -0
- package/dist/trust/wrapper.js +142 -0
- package/dist/trust/wrapper.js.map +1 -0
- package/dist/usage.d.ts +53 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +124 -0
- package/dist/usage.js.map +1 -0
- package/dist/util/math.d.ts +9 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/math.js +22 -0
- package/dist/util/math.js.map +1 -0
- package/dist/util/ssrf.d.ts +21 -0
- package/dist/util/ssrf.d.ts.map +1 -0
- package/dist/util/ssrf.js +77 -0
- package/dist/util/ssrf.js.map +1 -0
- package/dist/workers/index.d.ts +8 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +7 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/registry.d.ts +53 -0
- package/dist/workers/registry.d.ts.map +1 -0
- package/dist/workers/registry.js +38 -0
- package/dist/workers/registry.js.map +1 -0
- package/dist/workers/scoped-tools.d.ts +21 -0
- package/dist/workers/scoped-tools.d.ts.map +1 -0
- package/dist/workers/scoped-tools.js +111 -0
- package/dist/workers/scoped-tools.js.map +1 -0
- package/dist/workers/spawn.d.ts +62 -0
- package/dist/workers/spawn.d.ts.map +1 -0
- package/dist/workers/spawn.js +314 -0
- package/dist/workers/spawn.js.map +1 -0
- package/dist/workers/tools.d.ts +26 -0
- package/dist/workers/tools.d.ts.map +1 -0
- package/dist/workers/tools.js +380 -0
- package/dist/workers/tools.js.map +1 -0
- package/docker-compose.yml +72 -0
- package/package.json +16 -1
- package/run.sh +27 -0
package/Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Stage 1: Build
|
|
2
|
+
FROM node:22-alpine AS builder
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
COPY package.json package-lock.json ./
|
|
5
|
+
RUN npm ci
|
|
6
|
+
COPY tsconfig.json ./
|
|
7
|
+
COPY src ./src
|
|
8
|
+
RUN npm run build
|
|
9
|
+
|
|
10
|
+
# Stage 2: Runtime
|
|
11
|
+
FROM node:22-alpine
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
COPY --from=builder /app/dist ./dist
|
|
14
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
15
|
+
COPY package.json ./
|
|
16
|
+
|
|
17
|
+
# Create non-root user
|
|
18
|
+
RUN addgroup -g 1000 -S bryti && \
|
|
19
|
+
adduser -u 1000 -S bryti -G bryti
|
|
20
|
+
USER bryti
|
|
21
|
+
|
|
22
|
+
# Data directory (config, memory, sessions, logs) is a volume mount.
|
|
23
|
+
# The embedding model downloads here on first run (~300MB).
|
|
24
|
+
VOLUME /data
|
|
25
|
+
ENV BRYTI_DATA_DIR=/data
|
|
26
|
+
|
|
27
|
+
CMD ["node", "dist/index.js"]
|
package/README.md
CHANGED
|
@@ -1,39 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/icon.svg" alt="" width="32" height="40">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">Bryti</h1>
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Your AI colleague, in the apps you already use.</strong>
|
|
9
|
+
</p>
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-blue.svg" alt="License: AGPL-3.0"></a>
|
|
13
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-22%2B-brightgreen.svg" alt="Node.js 22+"></a>
|
|
14
|
+
<a href="https://github.com/mariozechner/pi"><img src="https://img.shields.io/badge/built%20on-pi%20SDK-purple.svg" alt="Built on pi"></a>
|
|
15
|
+
<a href="#getting-started"><img src="https://img.shields.io/badge/self--hosted-yes-orange.svg" alt="Self-hosted"></a>
|
|
16
|
+
</p>
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="assets/chat-example.svg" alt="Bryti conversation showing research, memory recall, and proactive follow-up" width="540">
|
|
20
|
+
</p>
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Bryti is a personal AI agent that lives in Telegram and WhatsApp. It remembers what you tell it, tracks what's coming up, researches things in the background, and writes its own tools when it needs new capabilities. All running on your machine, with your data staying yours.
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
> *Named after the Old Norse **bryti**: the estate steward who handled the day-to-day so you could focus on what matters.*
|
|
27
|
+
|
|
28
|
+
## What makes it different
|
|
14
29
|
|
|
15
|
-
**It
|
|
30
|
+
**It actually remembers you.** Three-tier memory: a small always-visible file for key facts, long-term searchable storage with local embeddings, and full conversation logs. When the context window fills up, compaction preserves what matters instead of throwing it away.
|
|
16
31
|
|
|
17
|
-
**It
|
|
32
|
+
**It understands the future, not just the past.** Projections go beyond simple reminders. "Remind me to write that article unless you see it posted already." "When the dentist confirms, remind me to book time off." "Every Monday morning, check the sprint board." Time-based, event-triggered, recurring, with dependencies.
|
|
18
33
|
|
|
19
|
-
**
|
|
34
|
+
**External content can't compromise it.** The main agent has no web access at all. Research happens in isolated worker sessions with scoped file access. Even if a malicious page tries to hijack the model, it only reaches the disposable worker, never the main conversation.
|
|
20
35
|
|
|
21
|
-
**It
|
|
36
|
+
**It extends itself.** The agent writes TypeScript extensions to give itself new tools: API integrations, custom commands, whatever it needs. Write the file, restart, done.
|
|
22
37
|
|
|
23
|
-
**It
|
|
38
|
+
**It works without a subscription.** Configure free models via OpenCode, use your own Ollama instance, or bring any OpenAI-compatible API. Automatic fallback across providers means it keeps working when one goes down.
|
|
24
39
|
|
|
25
40
|
## Getting started
|
|
26
41
|
|
|
27
|
-
###
|
|
42
|
+
### Requirements
|
|
28
43
|
|
|
29
44
|
- Node.js 22+
|
|
30
|
-
- A Telegram bot token (from [@BotFather](https://t.me/BotFather))
|
|
31
|
-
-
|
|
45
|
+
- A Telegram bot token (from [@BotFather](https://t.me/BotFather)) or a WhatsApp phone number for Bryti
|
|
46
|
+
- Docker and docker-compose (optional, for HedgeDoc integration)
|
|
32
47
|
|
|
33
|
-
###
|
|
48
|
+
### Quick start
|
|
34
49
|
|
|
35
50
|
```bash
|
|
36
|
-
git clone
|
|
51
|
+
git clone git@github.com:larsderidder/bryti.git
|
|
37
52
|
cd bryti
|
|
38
53
|
npm install
|
|
39
54
|
|
|
@@ -45,11 +60,20 @@ cp config.example.yml data/config.yml # edit to taste
|
|
|
45
60
|
./run.sh
|
|
46
61
|
```
|
|
47
62
|
|
|
48
|
-
The embedding model downloads on first run (~
|
|
63
|
+
The embedding model downloads on first run (~300 MB). After that, startups take a few seconds.
|
|
64
|
+
|
|
65
|
+
### Using Anthropic models through your Claude subscription
|
|
66
|
+
|
|
67
|
+
No API key needed. Install the [pi CLI](https://github.com/mariozechner/pi) and log in once:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm i -g @mariozechner/pi-coding-agent
|
|
71
|
+
pi login anthropic # opens browser, stores OAuth token locally
|
|
72
|
+
```
|
|
49
73
|
|
|
50
|
-
###
|
|
74
|
+
### Using free or open-source models only
|
|
51
75
|
|
|
52
|
-
|
|
76
|
+
No subscription, no API keys:
|
|
53
77
|
|
|
54
78
|
```yaml
|
|
55
79
|
agent:
|
|
@@ -58,15 +82,21 @@ agent:
|
|
|
58
82
|
- "opencode/kimi-k2.5-free"
|
|
59
83
|
```
|
|
60
84
|
|
|
61
|
-
Remove the `anthropic` provider from `models.providers
|
|
85
|
+
Remove the `anthropic` provider from `models.providers` in your config. See `config.example.yml` for more provider examples (OpenRouter, Google Gemini, Ollama, Together AI).
|
|
62
86
|
|
|
63
87
|
### Docker
|
|
64
88
|
|
|
65
89
|
```bash
|
|
90
|
+
cp .env.example .env # add your Telegram bot token
|
|
91
|
+
cp config.example.yml data/config.yml # edit to taste
|
|
66
92
|
docker compose up -d
|
|
67
93
|
```
|
|
68
94
|
|
|
69
|
-
|
|
95
|
+
The `data/` directory is mounted as a volume. Config, memory, sessions, and logs all live there. Backup = copy the directory. Logs are available via `docker compose logs -f`.
|
|
96
|
+
|
|
97
|
+
### Why self-hosted?
|
|
98
|
+
|
|
99
|
+
Your conversations, memories, and personal data never leave your machine. No third-party servers, no vendor lock-in on the agent itself. You control which models to use, which providers to trust, and when to upgrade.
|
|
70
100
|
|
|
71
101
|
## How it works
|
|
72
102
|
|
|
@@ -74,15 +104,15 @@ Mount `data/` as a volume. Config, memory, sessions, and logs all live there. Ba
|
|
|
74
104
|
|
|
75
105
|
Three tiers, managed automatically:
|
|
76
106
|
|
|
77
|
-
1. **Core memory** (`data/core-memory.md`): a small markdown file (
|
|
107
|
+
1. **Core memory** (`data/core-memory.md`): a small markdown file (4 KB cap) that's always in the model's context. Contains your preferences, ongoing projects, and key facts about you. The agent updates it as it learns.
|
|
78
108
|
|
|
79
|
-
2. **Archival memory** (per-user SQLite): long-term storage with hybrid search
|
|
109
|
+
2. **Archival memory** (per-user SQLite): long-term storage with hybrid search combining FTS5 keyword matching and vector similarity (local embeddings via node-llama-cpp), fused with reciprocal rank fusion. No external API calls; all embedding runs on your machine. The agent inserts facts when it learns something and searches when it needs context.
|
|
80
110
|
|
|
81
|
-
3. **Conversation search**: full JSONL audit logs of every conversation, searchable by keyword. Useful when the agent needs to look up what you discussed last week
|
|
111
|
+
3. **Conversation search**: full JSONL audit logs of every conversation, searchable by keyword. Useful when the agent needs to look up what you discussed last week.
|
|
82
112
|
|
|
83
113
|
### Projections
|
|
84
114
|
|
|
85
|
-
The forward-looking memory system. Instead of just remembering the past,
|
|
115
|
+
The forward-looking memory system. Instead of just remembering the past, Bryti tracks what's coming:
|
|
86
116
|
|
|
87
117
|
- **Exact-time**: "remind me at 3pm" fires at 3pm
|
|
88
118
|
- **Day/week/month**: "follow up next week" resolves within that window
|
|
@@ -95,24 +125,37 @@ A reflection pass runs every 30 minutes, scanning recent conversation history fo
|
|
|
95
125
|
|
|
96
126
|
### Workers
|
|
97
127
|
|
|
98
|
-
|
|
128
|
+
Background sessions for long-running tasks. The main agent dispatches a worker with a goal; the worker runs independently (web search, URL fetching, analysis) and writes results to a file. When it finishes, a completion fact is archived, which can trigger projections so the main agent reads the summary and notifies you right away.
|
|
99
129
|
|
|
100
|
-
Workers are the security boundary. The main agent has no web search or URL fetch tools
|
|
130
|
+
Workers are also the first security boundary. The main agent has no web search or URL fetch tools. External content is processed in isolation, and only the worker's cleaned-up result file enters the main conversation. This keeps prompt injection in web content from reaching the agent's context.
|
|
101
131
|
|
|
102
|
-
|
|
132
|
+
You can configure named worker types in `config.yml` with preset models, tools, and timeouts:
|
|
103
133
|
|
|
104
|
-
|
|
134
|
+
```yaml
|
|
135
|
+
workers:
|
|
136
|
+
types:
|
|
137
|
+
research:
|
|
138
|
+
description: "Web research and content gathering"
|
|
139
|
+
model: "anthropic/claude-sonnet-4-20250514"
|
|
140
|
+
tools: [web_search, fetch_url]
|
|
141
|
+
timeout_seconds: 3600
|
|
142
|
+
analysis:
|
|
143
|
+
description: "Deep analysis using a stronger model"
|
|
144
|
+
model: "anthropic/claude-sonnet-4-6"
|
|
145
|
+
tools: [fetch_url]
|
|
146
|
+
timeout_seconds: 1800
|
|
147
|
+
```
|
|
105
148
|
|
|
106
|
-
The agent
|
|
149
|
+
The agent selects a type when dispatching. Explicit parameters on the dispatch call still override type defaults. The agent can also define new types by editing `config.yml` and restarting.
|
|
107
150
|
|
|
108
|
-
|
|
151
|
+
You can steer a running worker mid-task to narrow its focus or redirect its research.
|
|
109
152
|
|
|
110
153
|
### Guardrail
|
|
111
154
|
|
|
112
155
|
Elevated tools (shell commands, HTTP requests, extension-loaded tools) go through two checks:
|
|
113
156
|
|
|
114
157
|
1. **Tool-level approval**: is this tool allowed at all? First use requires your permission via inline buttons or text.
|
|
115
|
-
2. **Call-level evaluation**: an LLM call evaluates the specific arguments against what you asked for
|
|
158
|
+
2. **Call-level evaluation**: an LLM call evaluates the specific arguments against what you asked for, and decides whether to escalate. Like a call-based sudo. The prompt is small (~300 tokens in, ~20 out), so it uses the primary model for reliability without meaningful cost impact.
|
|
116
159
|
|
|
117
160
|
Pre-approve tools in config to skip the first-use prompt:
|
|
118
161
|
|
|
@@ -123,11 +166,17 @@ trust:
|
|
|
123
166
|
- http_request
|
|
124
167
|
```
|
|
125
168
|
|
|
169
|
+
### Self-extending
|
|
170
|
+
|
|
171
|
+
The agent writes TypeScript extension files to give itself new tools, using the pi SDK extension format. Each extension registers tools with the SDK; after writing one the agent restarts, and the new tools are available immediately.
|
|
172
|
+
|
|
173
|
+
Extensions live in `data/files/extensions/`. An extension guide is included so the agent knows the template, parameter types, and conventions. An empty file acts as a tombstone, signaling the agent intentionally deleted an extension so it won't get reseeded on restart.
|
|
174
|
+
|
|
126
175
|
## Architecture
|
|
127
176
|
|
|
128
|
-
|
|
177
|
+
Bryti is intentionally simple, straightforward, and organized. You should be able to understand the code, and any component should be simple enough to read in a single sitting. If that's not the case, open an issue and I'll fix it.
|
|
129
178
|
|
|
130
|
-
|
|
179
|
+
### Source layout
|
|
131
180
|
|
|
132
181
|
```
|
|
133
182
|
src/
|
|
@@ -145,7 +194,7 @@ src/
|
|
|
145
194
|
whatsapp.ts baileys bridge, QR auth, auto-reconnect
|
|
146
195
|
|
|
147
196
|
memory/
|
|
148
|
-
core-memory.ts always-in-context markdown file (
|
|
197
|
+
core-memory.ts always-in-context markdown file (4 KB cap)
|
|
149
198
|
store.ts per-user SQLite with FTS5 + embeddings
|
|
150
199
|
embeddings.ts local embeddings via node-llama-cpp
|
|
151
200
|
search.ts hybrid keyword + vector search with RRF
|
|
@@ -183,25 +232,13 @@ src/
|
|
|
183
232
|
|
|
184
233
|
## Configuration
|
|
185
234
|
|
|
186
|
-
`data/config.yml` controls everything. Copy `config.example.yml` to get started.
|
|
187
|
-
|
|
188
|
-
**Agent**: name, system prompt additions, primary model, fallback models, timezone, reflection model.
|
|
189
|
-
|
|
190
|
-
**Channels**: Telegram token + allowed user IDs. WhatsApp enable flag + allowed phone numbers. Both can run simultaneously.
|
|
191
|
-
|
|
192
|
-
**Models**: providers with endpoints, API keys, and model definitions. Anthropic OAuth reads tokens from `~/.pi/agent/auth.json` (shared with pi CLI). Free models use `api_key: "public"`.
|
|
193
|
-
|
|
194
|
-
**Tools**: SearXNG instance URL for worker web searches, max concurrent workers, file workspace path.
|
|
195
|
-
|
|
196
|
-
**Scheduling**: static cron jobs, active hours window. Projection jobs (daily review, exact-time check, reflection) are automatic.
|
|
197
|
-
|
|
198
|
-
**Integrations**: key-value pairs injected into `process.env` so extensions can pick them up. Convention: `integrations.hedgedoc.url` becomes `HEDGEDOC_URL`. Existing env vars are never overwritten.
|
|
235
|
+
`data/config.yml` controls everything. Copy `config.example.yml` to get started. The example file is heavily commented with all available options, provider examples, and integration patterns.
|
|
199
236
|
|
|
200
237
|
Environment variables are supported via `${VAR}` syntax. The `.env` file loads automatically.
|
|
201
238
|
|
|
202
239
|
## CLI
|
|
203
240
|
|
|
204
|
-
Operator tools for managing
|
|
241
|
+
Operator tools for managing Bryti without going through chat:
|
|
205
242
|
|
|
206
243
|
```bash
|
|
207
244
|
npm run cli -- help # all commands
|
|
@@ -212,9 +249,12 @@ npm run cli -- reflect # run reflection pass now
|
|
|
212
249
|
npm run cli -- timeskip "dentist" --minutes 2 # make a projection fire in 2 min
|
|
213
250
|
npm run cli -- archive-fact "dentist confirmed" # insert fact, trigger matching projections
|
|
214
251
|
npm run cli -- fill-context --turns 20 # inject synthetic conversation for testing
|
|
215
|
-
npm run cli -- import-openclaw # import memory from an OpenClaw instance
|
|
216
252
|
```
|
|
217
253
|
|
|
254
|
+
## Contributing
|
|
255
|
+
|
|
256
|
+
Found a bug or have an idea? [Open an issue](https://github.com/larsderidder/bryti/issues). Pull requests welcome.
|
|
257
|
+
|
|
218
258
|
## License
|
|
219
259
|
|
|
220
260
|
[AGPL-3.0](LICENSE)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Bryti configuration
|
|
2
|
+
# Copy to data/config.yml and fill in your values.
|
|
3
|
+
# ${VAR} references are substituted from environment variables.
|
|
4
|
+
|
|
5
|
+
agent:
|
|
6
|
+
name: "Bryti"
|
|
7
|
+
|
|
8
|
+
# System prompt. Memory contents, tool listings, extensions, and projections
|
|
9
|
+
# are injected automatically; this is the behavioral core. Personal details
|
|
10
|
+
# about you are best shared in conversation; the agent stores them in core
|
|
11
|
+
# memory and keeps them across sessions.
|
|
12
|
+
system_prompt: |
|
|
13
|
+
You are Bryti, a personal AI assistant. You run on the pi agent framework
|
|
14
|
+
with persistent memory and tool-calling capabilities. You are concise,
|
|
15
|
+
practical, and honest about what you can and cannot do.
|
|
16
|
+
|
|
17
|
+
## Your memory
|
|
18
|
+
Your core memory (shown below) persists across conversations. Update it when
|
|
19
|
+
you learn something worth keeping: user preferences, facts, ongoing projects,
|
|
20
|
+
recurring topics. Do this proactively without telling the user unless asked.
|
|
21
|
+
|
|
22
|
+
Archival memory is for details that don't need to be always visible but should
|
|
23
|
+
be searchable later.
|
|
24
|
+
|
|
25
|
+
## Projection memory
|
|
26
|
+
Projections are your forward-looking memory. Store anything about the
|
|
27
|
+
future: appointments, deadlines, plans, reminders, commitments. Your
|
|
28
|
+
current projections are shown below; use `projection_list` to see more.
|
|
29
|
+
|
|
30
|
+
Guidelines:
|
|
31
|
+
- Store ALL items, even far-future ones. If unsure about timing, use
|
|
32
|
+
resolution "month" or "someday"
|
|
33
|
+
- Connect new information to existing projections when you see a link
|
|
34
|
+
- When the user postpones a discussion, create a separate projection for
|
|
35
|
+
each distinct topic being deferred
|
|
36
|
+
- ALWAYS populate the context field with keywords to search archival
|
|
37
|
+
memory with and a brief description of why this matters
|
|
38
|
+
- Before creating a projection, search archival memory first to find
|
|
39
|
+
existing relevant context. Note key terms in the context field so you
|
|
40
|
+
can find them again at activation time
|
|
41
|
+
- If you just discussed something worth projecting, archive the key
|
|
42
|
+
points first, then create the projection referencing what you archived
|
|
43
|
+
- When a projection activates, search archival memory for related context
|
|
44
|
+
before responding. Projections are the "what" and "when"; archival
|
|
45
|
+
memory holds the "why"
|
|
46
|
+
|
|
47
|
+
## What you cannot do
|
|
48
|
+
- You cannot modify your own source code or core configuration
|
|
49
|
+
- You cannot access the internet directly. Use worker_dispatch for any
|
|
50
|
+
web research.
|
|
51
|
+
|
|
52
|
+
# Primary model. Format: provider/model-id
|
|
53
|
+
# Option 1: Anthropic OAuth (Claude Pro/Max subscription, no API key needed)
|
|
54
|
+
# Requires pi CLI login first: `pi login anthropic`
|
|
55
|
+
model: "anthropic/claude-sonnet-4-6"
|
|
56
|
+
|
|
57
|
+
# Tried in order when primary model fails
|
|
58
|
+
fallback_models:
|
|
59
|
+
- "opencode/minimax-m2.5-free"
|
|
60
|
+
- "opencode/kimi-k2.5-free"
|
|
61
|
+
|
|
62
|
+
# IANA timezone for projection scheduling and display
|
|
63
|
+
timezone: "Europe/Amsterdam"
|
|
64
|
+
|
|
65
|
+
# Model for the background reflection pass (defaults to primary model).
|
|
66
|
+
# Set a cheaper model here to save tokens on background work.
|
|
67
|
+
# reflection_model: "opencode/minimax-m2.5-free"
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# Channels (enable at least one)
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
telegram:
|
|
74
|
+
token: ${TELEGRAM_BOT_TOKEN}
|
|
75
|
+
allowed_users: [] # Telegram user IDs. Empty = deny all. Add at least one.
|
|
76
|
+
|
|
77
|
+
# WhatsApp via baileys (no Meta Business API). QR code auth on first run.
|
|
78
|
+
# allowed_users: phone numbers in international format without + (e.g., 31612345678)
|
|
79
|
+
whatsapp:
|
|
80
|
+
enabled: false
|
|
81
|
+
allowed_users: []
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Model providers
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
models:
|
|
88
|
+
providers:
|
|
89
|
+
# context_window and max_tokens are optional on every model.
|
|
90
|
+
# Defaults: 200K context, 32K output. Only set them when a model differs.
|
|
91
|
+
# Only list the models you actually use in agent.model / fallback_models.
|
|
92
|
+
|
|
93
|
+
# Anthropic OAuth: uses your Claude Pro or Max subscription.
|
|
94
|
+
# No api_key needed; bryti reads the OAuth token from ~/.pi/agent/auth.json.
|
|
95
|
+
# One-time setup: install pi (`npm i -g @mariozechner/pi-coding-agent`)
|
|
96
|
+
# and run `pi login anthropic`. Opens a browser, token is stored locally.
|
|
97
|
+
- name: anthropic
|
|
98
|
+
base_url: ""
|
|
99
|
+
api: anthropic-messages
|
|
100
|
+
api_key: ""
|
|
101
|
+
models:
|
|
102
|
+
- id: "claude-sonnet-4-6"
|
|
103
|
+
context_window: 200000
|
|
104
|
+
max_tokens: 64000
|
|
105
|
+
|
|
106
|
+
# Free open models via opencode.ai (no subscription, no API key)
|
|
107
|
+
- name: opencode
|
|
108
|
+
base_url: https://opencode.ai/zen/v1
|
|
109
|
+
api: openai-responses
|
|
110
|
+
api_key: "public"
|
|
111
|
+
models:
|
|
112
|
+
- id: "minimax-m2.5-free"
|
|
113
|
+
name: "MiniMax M2.5 (Free)"
|
|
114
|
+
api: openai-completions
|
|
115
|
+
context_window: 200000
|
|
116
|
+
max_tokens: 32000
|
|
117
|
+
compat:
|
|
118
|
+
maxTokensField: max_tokens
|
|
119
|
+
- id: "kimi-k2.5-free"
|
|
120
|
+
name: "Kimi K2.5 (Free)"
|
|
121
|
+
api: openai-completions
|
|
122
|
+
context_window: 128000
|
|
123
|
+
max_tokens: 32000
|
|
124
|
+
compat:
|
|
125
|
+
maxTokensField: max_tokens
|
|
126
|
+
|
|
127
|
+
# --- More providers (uncomment and add your keys) ---
|
|
128
|
+
|
|
129
|
+
# Anthropic API key (alternative to OAuth above)
|
|
130
|
+
# - name: anthropic
|
|
131
|
+
# api: anthropic-messages
|
|
132
|
+
# api_key: ${ANTHROPIC_API_KEY}
|
|
133
|
+
# models:
|
|
134
|
+
# - id: "claude-sonnet-4-6"
|
|
135
|
+
# context_window: 200000
|
|
136
|
+
# max_tokens: 64000
|
|
137
|
+
|
|
138
|
+
# OpenRouter: access 200+ models with one API key
|
|
139
|
+
# - name: openrouter
|
|
140
|
+
# base_url: https://openrouter.ai/api/v1
|
|
141
|
+
# api_key: ${OPENROUTER_API_KEY}
|
|
142
|
+
# models:
|
|
143
|
+
# - id: "anthropic/claude-sonnet-4-6"
|
|
144
|
+
# context_window: 200000
|
|
145
|
+
# max_tokens: 64000
|
|
146
|
+
|
|
147
|
+
# Google Gemini
|
|
148
|
+
# - name: google
|
|
149
|
+
# base_url: https://generativelanguage.googleapis.com/v1beta/openai
|
|
150
|
+
# api_key: ${GOOGLE_API_KEY}
|
|
151
|
+
# models:
|
|
152
|
+
# - id: "gemini-2.5-flash"
|
|
153
|
+
# context_window: 1048576
|
|
154
|
+
# max_tokens: 65536
|
|
155
|
+
|
|
156
|
+
# Ollama (local or remote)
|
|
157
|
+
# - name: ollama
|
|
158
|
+
# base_url: http://localhost:11434/v1
|
|
159
|
+
# api_key: "ollama"
|
|
160
|
+
# models:
|
|
161
|
+
# - id: "qwen3:32b"
|
|
162
|
+
|
|
163
|
+
# Together AI
|
|
164
|
+
# - name: together
|
|
165
|
+
# base_url: https://api.together.xyz/v1
|
|
166
|
+
# api_key: ${TOGETHER_API_KEY}
|
|
167
|
+
# models:
|
|
168
|
+
# - id: "Qwen/Qwen3-235B-A22B"
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
171
|
+
# Tools
|
|
172
|
+
# ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
tools:
|
|
175
|
+
# Web search for workers. Set brave_api_key OR searxng_url (not both).
|
|
176
|
+
# If both are set, Brave takes priority.
|
|
177
|
+
# If neither is set, web search is disabled for workers.
|
|
178
|
+
web_search:
|
|
179
|
+
# Option A: Brave Search API. Free tier: 2000 queries/month, no self-hosting.
|
|
180
|
+
# Get a key at https://api.search.brave.com/
|
|
181
|
+
# brave_api_key: "${BRAVE_API_KEY}"
|
|
182
|
+
|
|
183
|
+
# Option B: SearXNG. Self-hosted or public instance, no API key needed.
|
|
184
|
+
searxng_url: "https://searx.be"
|
|
185
|
+
|
|
186
|
+
# fetch_url:
|
|
187
|
+
# timeout_ms: 10000 # default: 10s
|
|
188
|
+
|
|
189
|
+
# files:
|
|
190
|
+
# base_dir: ./data/files # default
|
|
191
|
+
|
|
192
|
+
workers:
|
|
193
|
+
max_concurrent: 3
|
|
194
|
+
# Default model for background workers. Falls back to the first
|
|
195
|
+
# fallback_model, then the primary agent model.
|
|
196
|
+
# model: "opencode/minimax-m2.5-free"
|
|
197
|
+
|
|
198
|
+
# Named worker types. The agent selects a type when dispatching to get
|
|
199
|
+
# preset defaults for model, tools, and timeout. Explicit parameters on
|
|
200
|
+
# the dispatch call still override type defaults.
|
|
201
|
+
# The agent can also define new types by editing this file and restarting.
|
|
202
|
+
# types:
|
|
203
|
+
# research:
|
|
204
|
+
# description: "Web research and content gathering"
|
|
205
|
+
# model: "opencode/minimax-m2.5-free"
|
|
206
|
+
# tools: [web_search, fetch_url]
|
|
207
|
+
# timeout_seconds: 3600
|
|
208
|
+
# analysis:
|
|
209
|
+
# description: "Deep analysis using a stronger model"
|
|
210
|
+
# model: "anthropic/claude-sonnet-4-6"
|
|
211
|
+
# tools: [fetch_url]
|
|
212
|
+
# timeout_seconds: 1800
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# Integrations (optional)
|
|
216
|
+
#
|
|
217
|
+
# Values here are injected into process.env at startup so extensions can
|
|
218
|
+
# read them without separate .env entries.
|
|
219
|
+
#
|
|
220
|
+
# Convention: integrations.<name>.<key> becomes the env var NAME_KEY (uppercased).
|
|
221
|
+
# Example: integrations.hedgedoc.url → HEDGEDOC_URL
|
|
222
|
+
#
|
|
223
|
+
# Secrets (API keys, tokens) should still use ${VAR} substitution so they
|
|
224
|
+
# stay in .env and out of config.yml.
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
# integrations:
|
|
228
|
+
# hedgedoc:
|
|
229
|
+
# url: "http://hedgedoc:3000" # internal Docker network URL
|
|
230
|
+
# public_url: "https://docs.example.com" # user-facing URL for shared links
|
|
231
|
+
#
|
|
232
|
+
# # Any future integration follows the same pattern:
|
|
233
|
+
# # my_service:
|
|
234
|
+
# # url: "https://api.example.com"
|
|
235
|
+
# # api_key: "${MY_SERVICE_API_KEY}" # secret stays in .env
|
|
236
|
+
|
|
237
|
+
# ---------------------------------------------------------------------------
|
|
238
|
+
# Scheduled prompts (optional)
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
cron: []
|
|
242
|
+
# - schedule: "0 8 * * *"
|
|
243
|
+
# message: "Good morning! Brief me on anything I need to know today."
|
|
244
|
+
# - schedule: "0 18 * * 1-5"
|
|
245
|
+
# message: "Summarize what we discussed today and update memory with action items."
|
|
246
|
+
|
|
247
|
+
# ---------------------------------------------------------------------------
|
|
248
|
+
# Trust levels (runtime permissions for elevated tools)
|
|
249
|
+
# ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
# Extension tools (shell_exec, http_request, etc.) require approval before
|
|
252
|
+
# first use. Pre-approve tools here to skip the permission prompt.
|
|
253
|
+
# trust:
|
|
254
|
+
# approved_tools:
|
|
255
|
+
# - shell_exec
|
|
256
|
+
# - http_request
|
|
257
|
+
# - weather_weert
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# Active hours (optional; limits when the scheduler can send messages)
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
# active_hours:
|
|
264
|
+
# start: "08:00"
|
|
265
|
+
# end: "23:00"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active hours guard.
|
|
3
|
+
*
|
|
4
|
+
* Determines whether the current time falls within the configured active
|
|
5
|
+
* window so scheduler callbacks skip during quiet hours. Absent config
|
|
6
|
+
* means always active. Overnight windows (start > end) are supported.
|
|
7
|
+
*/
|
|
8
|
+
export interface ActiveHoursConfig {
|
|
9
|
+
/** IANA timezone name, e.g. "Europe/Amsterdam". */
|
|
10
|
+
timezone: string;
|
|
11
|
+
/** Start of active window, "HH:MM", inclusive. */
|
|
12
|
+
start: string;
|
|
13
|
+
/** End of active window, "HH:MM", exclusive. */
|
|
14
|
+
end: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Return true if the given time (default: now) is within the active window.
|
|
18
|
+
*
|
|
19
|
+
* When cfg is undefined, always returns true (no restriction configured).
|
|
20
|
+
* The optional `now` parameter exists for testing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isActiveNow(cfg: ActiveHoursConfig | undefined, now?: Date): boolean;
|
|
23
|
+
//# sourceMappingURL=active-hours.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-hours.d.ts","sourceRoot":"","sources":["../src/active-hours.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;CACb;AAqCD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,iBAAiB,GAAG,SAAS,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAsBnF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active hours guard.
|
|
3
|
+
*
|
|
4
|
+
* Determines whether the current time falls within the configured active
|
|
5
|
+
* window so scheduler callbacks skip during quiet hours. Absent config
|
|
6
|
+
* means always active. Overnight windows (start > end) are supported.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Parse "HH:MM" into total minutes since midnight. Returns NaN on bad input.
|
|
10
|
+
*/
|
|
11
|
+
function parseHHMM(hhmm) {
|
|
12
|
+
const match = hhmm.match(/^(\d{1,2}):(\d{2})$/);
|
|
13
|
+
if (!match)
|
|
14
|
+
return NaN;
|
|
15
|
+
const h = parseInt(match[1], 10);
|
|
16
|
+
const m = parseInt(match[2], 10);
|
|
17
|
+
if (h > 23 || m > 59)
|
|
18
|
+
return NaN;
|
|
19
|
+
return h * 60 + m;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get the HH:MM minutes for a given Date in the given IANA timezone.
|
|
23
|
+
* Falls back to UTC on unknown timezone.
|
|
24
|
+
*/
|
|
25
|
+
function currentMinutesInZone(timezone, now = new Date()) {
|
|
26
|
+
try {
|
|
27
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
28
|
+
timeZone: timezone,
|
|
29
|
+
hour: "2-digit",
|
|
30
|
+
minute: "2-digit",
|
|
31
|
+
hour12: false,
|
|
32
|
+
}).formatToParts(now);
|
|
33
|
+
const h = parseInt(parts.find((p) => p.type === "hour")?.value ?? "0", 10);
|
|
34
|
+
const m = parseInt(parts.find((p) => p.type === "minute")?.value ?? "0", 10);
|
|
35
|
+
return h * 60 + m;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Unknown timezone: fall back to UTC
|
|
39
|
+
console.warn(`[active-hours] Unknown timezone "${timezone}", falling back to UTC`);
|
|
40
|
+
return now.getUTCHours() * 60 + now.getUTCMinutes();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Return true if the given time (default: now) is within the active window.
|
|
45
|
+
*
|
|
46
|
+
* When cfg is undefined, always returns true (no restriction configured).
|
|
47
|
+
* The optional `now` parameter exists for testing.
|
|
48
|
+
*/
|
|
49
|
+
export function isActiveNow(cfg, now) {
|
|
50
|
+
if (!cfg)
|
|
51
|
+
return true;
|
|
52
|
+
const startMin = parseHHMM(cfg.start);
|
|
53
|
+
const endMin = parseHHMM(cfg.end);
|
|
54
|
+
if (isNaN(startMin) || isNaN(endMin)) {
|
|
55
|
+
console.warn(`[active-hours] Invalid active_hours config (start="${cfg.start}" end="${cfg.end}"), treating as always active`);
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
const nowMin = currentMinutesInZone(cfg.timezone, now);
|
|
59
|
+
if (startMin <= endMin) {
|
|
60
|
+
// Normal window: 08:00–23:00
|
|
61
|
+
return nowMin >= startMin && nowMin < endMin;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Overnight window: 22:00–06:00
|
|
65
|
+
return nowMin >= startMin || nowMin < endMin;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=active-hours.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-hours.js","sourceRoot":"","sources":["../src/active-hours.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH;;GAEG;AACH,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAChD,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAC;IACvB,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,GAAG,CAAC;IACjC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAAC,QAAgB,EAAE,MAAY,IAAI,IAAI,EAAE;IACpE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YAC7C,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,KAAK;SACd,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QAEtB,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,EAAE,KAAK,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,KAAK,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QAC7E,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;QACrC,OAAO,CAAC,IAAI,CAAC,oCAAoC,QAAQ,wBAAwB,CAAC,CAAC;QACnF,OAAO,GAAG,CAAC,WAAW,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,aAAa,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAkC,EAAE,GAAU;IACxE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAElC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CACV,sDAAsD,GAAG,CAAC,KAAK,UAAU,GAAG,CAAC,GAAG,+BAA+B,CAChH,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAEvD,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;QACvB,6BAA6B;QAC7B,OAAO,MAAM,IAAI,QAAQ,IAAI,MAAM,GAAG,MAAM,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,gCAAgC;QAChC,OAAO,MAAM,IAAI,QAAQ,IAAI,MAAM,GAAG,MAAM,CAAC;IAC/C,CAAC;AACH,CAAC"}
|