tiger-agent 0.2.3 → 0.2.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/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="./IMG_5745.jpg" alt="Tiger bot" width="900" />
3
+ </p>
4
+
1
5
  # 🐯 Tiger Agent
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/tiger-agent.svg)](https://www.npmjs.com/package/tiger-agent)
@@ -10,13 +14,20 @@ Made by **AI Research Group, Department of Civil Engineering, KMUTT**
10
14
 
11
15
  ---
12
16
 
13
- ## 🆕 What's New — v0.2.0
17
+ ## 🆕 What's New — v0.2.4
18
+
19
+ - **ClawHub skill install fixed** — `clawhub_install` and `clawhub_search` now work correctly when installed via `npm install -g`
20
+ - **No required API keys** — `tiger onboard` skips providers with no key; any single provider is enough to start
21
+ - **`/limit` Telegram command** — set per-provider daily token limits from chat without restarting
22
+ - **Soul & ownskill refresh fixed** — 24-hour regeneration timer now uses DB timestamps, not file mtime, so reflection appends no longer block the update cycle
23
+
24
+ ### v0.2.0
14
25
 
15
26
  - **npm global install** — `npm install -g tiger-agent`, no git clone needed
16
27
  - **Multi-provider LLM** — 5 providers (Kimi, Z.ai, MiniMax, Claude, Moonshot) with auto-fallback
17
28
  - **Daily token limits** — per-provider limits with automatic switching at UTC midnight
18
29
  - **`tiger` CLI** — unified command: `tiger onboard`, `tiger start`, `tiger telegram`, `tiger stop`
19
- - **Telegram `/api` & `/tokens`** — switch providers and monitor usage from chat
30
+ - **Telegram `/api`, `/tokens`, `/limit`** — manage providers and usage from chat
20
31
  - **Encrypted secrets** — optional at-rest encryption for API keys
21
32
 
22
33
  ---
@@ -259,6 +270,27 @@ Without it, Tiger falls back to cosine similarity in Python — slower but fully
259
270
  | **Skills** | `list_skills`, `load_skill`, `clawhub_search`, `clawhub_install` |
260
271
  | **Orchestration** | `run_sub_agents` |
261
272
 
273
+ ### ClawHub Skills
274
+
275
+ Tiger can search and install skills from [ClawHub](https://clawhub.dev) — a community registry of reusable agent skills. The `clawhub` CLI is bundled with Tiger, no separate install needed.
276
+
277
+ Enable skill install in `~/.tiger/.env`:
278
+
279
+ ```env
280
+ ALLOW_SKILL_INSTALL=true
281
+ ```
282
+
283
+ Then just ask Tiger in chat:
284
+
285
+ ```
286
+ Search for a web search skill on clawhub
287
+ Install the web-search skill
288
+ ```
289
+
290
+ Skills are installed to `~/.tiger/skills/` and loaded automatically on demand.
291
+
292
+ > **Note:** `ALLOW_SKILL_INSTALL=true` must be set during `tiger onboard` or added manually to `~/.tiger/.env`.
293
+
262
294
  ---
263
295
 
264
296
  ## 🔒 Security
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiger-agent",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Cognitive AI agent with persistent memory, multi-provider LLM, and Telegram bot",
5
5
  "type": "commonjs",
6
6
  "main": "src/cli.js",
@@ -0,0 +1,39 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function getLegacyRootMirrorPath(filePath) {
5
+ const canonical = path.resolve(filePath);
6
+ const candidate = path.resolve(process.cwd(), path.basename(canonical));
7
+
8
+ if (candidate === canonical) return null;
9
+ if (!fs.existsSync(candidate)) return null;
10
+
11
+ return candidate;
12
+ }
13
+
14
+ function syncLegacyRootMirror(filePath) {
15
+ const canonical = path.resolve(filePath);
16
+ const legacy = getLegacyRootMirrorPath(canonical);
17
+ if (!legacy) return;
18
+
19
+ const content = fs.readFileSync(canonical, 'utf8');
20
+ fs.writeFileSync(legacy, content, 'utf8');
21
+ }
22
+
23
+ function writeContextFile(filePath, content) {
24
+ const canonical = path.resolve(filePath);
25
+ fs.writeFileSync(canonical, content, 'utf8');
26
+ syncLegacyRootMirror(canonical);
27
+ }
28
+
29
+ function appendContextFile(filePath, content) {
30
+ const canonical = path.resolve(filePath);
31
+ fs.appendFileSync(canonical, content, 'utf8');
32
+ syncLegacyRootMirror(canonical);
33
+ }
34
+
35
+ module.exports = {
36
+ writeContextFile,
37
+ appendContextFile,
38
+ syncLegacyRootMirror
39
+ };
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { dataDir } = require('../config');
4
4
  const { ensureDir } = require('../utils');
5
+ const { writeContextFile, syncLegacyRootMirror } = require('./contextFileMirrors');
5
6
 
6
7
  const files = ['soul.md', 'human.md', 'human2.md', 'ownskill.md'];
7
8
 
@@ -10,8 +11,10 @@ function ensureContextFiles() {
10
11
  for (const name of files) {
11
12
  const full = path.join(dataDir, name);
12
13
  if (!fs.existsSync(full)) {
13
- fs.writeFileSync(full, `# ${name.replace('.md', '')}\n\n`, 'utf8');
14
+ writeContextFile(full, `# ${name.replace('.md', '')}\n\n`);
15
+ continue;
14
16
  }
17
+ syncLegacyRootMirror(full);
15
18
  }
16
19
  }
17
20
 
@@ -12,6 +12,7 @@ const {
12
12
  memoryIngestMinChars
13
13
  } = require('../config');
14
14
  const { loadContextFiles } = require('./contextFiles');
15
+ const { writeContextFile, appendContextFile } = require('./contextFileMirrors');
15
16
  const { tools, callTool } = require('./toolbox');
16
17
  const {
17
18
  ensureConversation,
@@ -98,7 +99,7 @@ async function maybeUpdateHumanFile(userText, assistantText) {
98
99
  if (!append) return;
99
100
 
100
101
  const block = `\n## Update ${new Date().toISOString()}\n${append}\n`;
101
- fs.appendFileSync(path.resolve(human2.full), block, 'utf8');
102
+ appendContextFile(path.resolve(human2.full), block);
102
103
  }
103
104
 
104
105
  function buildSystemPrompt(contextText, memoriesText) {
@@ -173,7 +174,7 @@ async function maybeUpdateOwnSkillSummary(conversationIdValue) {
173
174
 
174
175
  const next = String(message.content || '').trim();
175
176
  if (!next) return;
176
- fs.writeFileSync(path.resolve(ownSkillPath), `${next}\n`, 'utf8');
177
+ writeContextFile(path.resolve(ownSkillPath), `${next}\n`);
177
178
  setMeta(OWNSKILL_META_KEY, Date.now());
178
179
  }
179
180
 
@@ -209,7 +210,7 @@ async function maybeUpdateSoulSummary(conversationIdValue) {
209
210
 
210
211
  const next = String(message.content || '').trim();
211
212
  if (!next) return;
212
- fs.writeFileSync(path.resolve(soulPath), `${next}\n`, 'utf8');
213
+ writeContextFile(path.resolve(soulPath), `${next}\n`);
213
214
  setMeta(SOUL_META_KEY, Date.now());
214
215
  }
215
216
 
@@ -1,8 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { chatCompletion, embedText } = require('../llmClient');
4
- const { dataDir, embeddingsEnabled } = require('../config');
4
+ const { dataDir, embeddingsEnabled, reflectionUpdateHours } = require('../config');
5
5
  const { addMemory, getMeta, setMeta, getMessagesSince, getRecentMessagesAll } = require('./db');
6
+ const { writeContextFile } = require('./contextFileMirrors');
6
7
 
7
8
  const REFLECTION_META_KEY = 'memory_reflection_last_run_ts';
8
9
  const MAX_MESSAGE_SCAN = 600;
@@ -67,7 +68,7 @@ function appendTimestampedBullets(filePath, heading, bullets, stamp) {
67
68
  const withHeading = ensureHeading(existing, heading);
68
69
  const lines = bullets.map((line) => `- [${stamp}] ${line}`).join('\n');
69
70
  const next = `${withHeading.trimEnd()}\n${lines}\n`;
70
- fs.writeFileSync(full, next, 'utf8');
71
+ writeContextFile(full, next);
71
72
  }
72
73
 
73
74
  function appendHuman2Update(filePath, payload, stampIso) {
@@ -80,7 +81,7 @@ function appendHuman2Update(filePath, payload, stampIso) {
80
81
  for (const w of payload.successfulWorkflows) lines.push(`- Workflow: ${w}`);
81
82
  if (!lines.length) return;
82
83
  const block = `\n## Update ${stampIso}\n${lines.join('\n')}\n`;
83
- fs.writeFileSync(full, `${existing.trimEnd()}\n${block}`, 'utf8');
84
+ writeContextFile(full, `${existing.trimEnd()}${block}`);
84
85
  }
85
86
 
86
87
  async function generateReflection(rows, sinceIso, untilIso) {
@@ -123,9 +124,20 @@ async function generateReflection(rows, sinceIso, untilIso) {
123
124
  };
124
125
  }
125
126
 
127
+ function shouldRunReflectionCycle(lastRunTs) {
128
+ if (!lastRunTs) return true;
129
+ const hoursPassed = (nowTs() - lastRunTs) / (60 * 60 * 1000);
130
+ return hoursPassed >= reflectionUpdateHours;
131
+ }
132
+
126
133
  async function maybeRunReflectionCycle({ force = false } = {}) {
127
134
  const startedAt = nowTs();
128
135
  const lastRunTs = Number(getMeta(REFLECTION_META_KEY, 0) || 0);
136
+
137
+ if (!force && !shouldRunReflectionCycle(lastRunTs)) {
138
+ return { ok: true, skipped: true, reason: 'not_yet_due' };
139
+ }
140
+
129
141
  const rows = lastRunTs
130
142
  ? getMessagesSince(lastRunTs, MAX_MESSAGE_SCAN)
131
143
  : getRecentMessagesAll(Math.min(MAX_MESSAGE_SCAN, 240));
@@ -185,6 +197,7 @@ async function maybeRunReflectionCycle({ force = false } = {}) {
185
197
  }
186
198
 
187
199
  setMeta(REFLECTION_META_KEY, startedAt);
200
+ console.log(`[ReflectionCycle] Completed at ${stampIso} (${rows.length} messages processed)`);
188
201
  return { ok: true, skipped: false, at: stampIso };
189
202
  }
190
203
 
@@ -8,14 +8,30 @@ function startReflectionScheduler() {
8
8
 
9
9
  const everyMs = Math.max(1, Number(reflectionUpdateHours || 12)) * 60 * 60 * 1000;
10
10
 
11
+ console.log(`[ReflectionScheduler] Starting with interval: ${reflectionUpdateHours || 12} hours (${everyMs}ms)`);
12
+
11
13
  // Kick one asynchronous check on startup.
12
- maybeRunReflectionCycle().catch(() => {});
14
+ maybeRunReflectionCycle().catch((err) => {
15
+ console.error('[ReflectionScheduler] Startup check failed:', err.message);
16
+ });
13
17
 
14
18
  intervalHandle = setInterval(() => {
15
- maybeRunReflectionCycle().catch(() => {});
19
+ console.log('[ReflectionScheduler] Running scheduled reflection cycle...');
20
+ maybeRunReflectionCycle().catch((err) => {
21
+ console.error('[ReflectionScheduler] Scheduled cycle failed:', err.message);
22
+ });
16
23
  }, everyMs);
17
24
  }
18
25
 
26
+ function stopReflectionScheduler() {
27
+ if (intervalHandle) {
28
+ clearInterval(intervalHandle);
29
+ intervalHandle = null;
30
+ console.log('[ReflectionScheduler] Stopped');
31
+ }
32
+ }
33
+
19
34
  module.exports = {
20
- startReflectionScheduler
35
+ startReflectionScheduler,
36
+ stopReflectionScheduler
21
37
  };