tiger-agent 0.2.0
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/.env.example +22 -0
- package/.env.secrets.example +14 -0
- package/LICENSE +22 -0
- package/README.md +284 -0
- package/bin/tiger.js +96 -0
- package/package.json +58 -0
- package/scripts/audit.sh +54 -0
- package/scripts/backup.sh +42 -0
- package/scripts/cryptoEnv.js +57 -0
- package/scripts/decrypt-env.js +34 -0
- package/scripts/encrypt-env.js +34 -0
- package/scripts/migrate-vector-db.js +44 -0
- package/scripts/onboard.js +319 -0
- package/scripts/scan-secrets.sh +87 -0
- package/scripts/setup.js +302 -0
- package/scripts/sqlite_memory.py +297 -0
- package/scripts/sqlite_vec_setup.py +112 -0
- package/src/agent/contextFiles.js +30 -0
- package/src/agent/db.js +349 -0
- package/src/agent/mainAgent.js +406 -0
- package/src/agent/reflectionAgent.js +193 -0
- package/src/agent/reflectionScheduler.js +21 -0
- package/src/agent/skills.js +169 -0
- package/src/agent/subAgent.js +39 -0
- package/src/agent/toolbox.js +291 -0
- package/src/apiProviders.js +217 -0
- package/src/cli.js +187 -0
- package/src/config.js +141 -0
- package/src/kimiClient.js +88 -0
- package/src/llmClient.js +147 -0
- package/src/telegram/bot.js +182 -0
- package/src/telegram/supervisor.js +84 -0
- package/src/tokenManager.js +223 -0
- package/src/utils.js +30 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { encryptString } = require('./cryptoEnv');
|
|
6
|
+
|
|
7
|
+
function arg(name, def = '') {
|
|
8
|
+
const idx = process.argv.indexOf(name);
|
|
9
|
+
if (idx === -1) return def;
|
|
10
|
+
return process.argv[idx + 1] || def;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const inPath = arg('--in', '.env.secrets');
|
|
14
|
+
const outPath = arg('--out', '.env.secrets.enc');
|
|
15
|
+
const passphrase = process.env.SECRETS_PASSPHRASE || '';
|
|
16
|
+
|
|
17
|
+
if (!passphrase) {
|
|
18
|
+
console.error('Missing SECRETS_PASSPHRASE env var');
|
|
19
|
+
process.exit(2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const absIn = path.resolve(process.cwd(), inPath);
|
|
23
|
+
const absOut = path.resolve(process.cwd(), outPath);
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(absIn)) {
|
|
26
|
+
console.error(`Input file not found: ${absIn}`);
|
|
27
|
+
process.exit(2);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const plaintext = fs.readFileSync(absIn, 'utf8');
|
|
31
|
+
const payload = encryptString(plaintext, passphrase);
|
|
32
|
+
fs.writeFileSync(absOut, JSON.stringify(payload, null, 2) + '\n', { mode: 0o600 });
|
|
33
|
+
|
|
34
|
+
console.log(`Wrote encrypted secrets to ${outPath}`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const out = {
|
|
7
|
+
from: '/tmp/tiger_memory.db',
|
|
8
|
+
to: './db/memory.sqlite'
|
|
9
|
+
};
|
|
10
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
11
|
+
const token = argv[i];
|
|
12
|
+
if (token === '--from' && argv[i + 1]) {
|
|
13
|
+
out.from = argv[i + 1];
|
|
14
|
+
i += 1;
|
|
15
|
+
} else if (token === '--to' && argv[i + 1]) {
|
|
16
|
+
out.to = argv[i + 1];
|
|
17
|
+
i += 1;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function main() {
|
|
24
|
+
const args = parseArgs(process.argv.slice(2));
|
|
25
|
+
const from = path.resolve(args.from);
|
|
26
|
+
const to = path.resolve(args.to);
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(from)) {
|
|
29
|
+
process.stderr.write(`Source DB not found: ${from}\n`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
34
|
+
fs.copyFileSync(from, to);
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
ok: true,
|
|
38
|
+
from,
|
|
39
|
+
to
|
|
40
|
+
}) + '\n'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
main();
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* tiger onboard [--install-daemon]
|
|
6
|
+
*
|
|
7
|
+
* Interactive first-run setup. Writes ~/.tiger/.env and optionally installs
|
|
8
|
+
* a systemd (Linux) or launchd (macOS) daemon so the Telegram bot starts
|
|
9
|
+
* automatically on boot.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
const { execSync, execFileSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const TIGER_HOME = process.env.TIGER_HOME || path.join(os.homedir(), '.tiger');
|
|
19
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
20
|
+
const ENV_PATH = path.join(TIGER_HOME, '.env');
|
|
21
|
+
const installDaemon = process.argv.includes('--install-daemon');
|
|
22
|
+
|
|
23
|
+
// ─── Readline helpers ─────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
26
|
+
const ask = (prompt) => new Promise((res) => rl.question(prompt, res));
|
|
27
|
+
|
|
28
|
+
async function askHidden(prompt) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const stdin = process.stdin;
|
|
31
|
+
const stdout = process.stdout;
|
|
32
|
+
if (!stdin.isTTY) { resolve(''); return; }
|
|
33
|
+
stdout.write(prompt);
|
|
34
|
+
stdin.setRawMode(true);
|
|
35
|
+
stdin.resume();
|
|
36
|
+
let buf = '';
|
|
37
|
+
function onData(chunk) {
|
|
38
|
+
for (const ch of chunk.toString('utf8')) {
|
|
39
|
+
if (ch === '\r' || ch === '\n') { stdout.write('\n'); cleanup(); resolve(buf); return; }
|
|
40
|
+
if (ch === '\u0003') { cleanup(); reject(new Error('Aborted')); return; }
|
|
41
|
+
if (ch === '\u007f') { if (buf.length) { buf = buf.slice(0, -1); stdout.write('\b \b'); } continue; }
|
|
42
|
+
buf += ch; stdout.write('*');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function cleanup() { stdin.off('data', onData); stdin.setRawMode(false); stdin.pause(); }
|
|
46
|
+
stdin.on('data', onData);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function yn(s, def) {
|
|
51
|
+
const t = String(s || '').trim().toLowerCase();
|
|
52
|
+
if (!t) return def;
|
|
53
|
+
return ['y', 'yes', '1', 'true'].includes(t);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function envLine(k, v) {
|
|
57
|
+
const s = String(v == null ? '' : v);
|
|
58
|
+
if (!s) return `${k}=`;
|
|
59
|
+
if (/[\s#"']/.test(s)) return `${k}="${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
60
|
+
return `${k}=${s}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Daemon helpers ───────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
function nodeBin() {
|
|
66
|
+
return process.execPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function cliScript() {
|
|
70
|
+
return path.join(PKG_ROOT, 'src', 'cli.js');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function installSystemd() {
|
|
74
|
+
const svcDir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
75
|
+
fs.mkdirSync(svcDir, { recursive: true });
|
|
76
|
+
const svcPath = path.join(svcDir, 'tiger.service');
|
|
77
|
+
const logPath = path.join(TIGER_HOME, 'logs', 'telegram.log');
|
|
78
|
+
|
|
79
|
+
const unit = `[Unit]
|
|
80
|
+
Description=Tiger AI Agent - Telegram Bot
|
|
81
|
+
After=network-online.target
|
|
82
|
+
Wants=network-online.target
|
|
83
|
+
|
|
84
|
+
[Service]
|
|
85
|
+
Type=simple
|
|
86
|
+
ExecStart=${nodeBin()} ${cliScript()} --telegram --worker
|
|
87
|
+
WorkingDirectory=${TIGER_HOME}
|
|
88
|
+
Environment=TIGER_HOME=${TIGER_HOME}
|
|
89
|
+
Restart=always
|
|
90
|
+
RestartSec=5
|
|
91
|
+
StandardOutput=append:${logPath}
|
|
92
|
+
StandardError=append:${logPath}
|
|
93
|
+
|
|
94
|
+
[Install]
|
|
95
|
+
WantedBy=default.target
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
fs.writeFileSync(svcPath, unit, 'utf8');
|
|
99
|
+
console.log(` Wrote ${svcPath}`);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
execSync('systemctl --user daemon-reload', { stdio: 'ignore' });
|
|
103
|
+
execSync('systemctl --user enable tiger', { stdio: 'ignore' });
|
|
104
|
+
execSync('systemctl --user start tiger', { stdio: 'ignore' });
|
|
105
|
+
console.log(' Daemon enabled and started via systemd --user');
|
|
106
|
+
console.log(' Manage with: systemctl --user {start|stop|restart|status} tiger');
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.log(' Wrote unit file but could not start systemd (no DBUS?). Run manually:');
|
|
109
|
+
console.log(' systemctl --user daemon-reload && systemctl --user enable --now tiger');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function installLaunchd() {
|
|
114
|
+
const agentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
115
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
116
|
+
const plistPath = path.join(agentsDir, 'com.tiger.agent.plist');
|
|
117
|
+
const logPath = path.join(TIGER_HOME, 'logs', 'telegram.log');
|
|
118
|
+
|
|
119
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
120
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
121
|
+
<plist version="1.0">
|
|
122
|
+
<dict>
|
|
123
|
+
<key>Label</key><string>com.tiger.agent</string>
|
|
124
|
+
<key>ProgramArguments</key>
|
|
125
|
+
<array>
|
|
126
|
+
<string>${nodeBin()}</string>
|
|
127
|
+
<string>${cliScript()}</string>
|
|
128
|
+
<string>--telegram</string>
|
|
129
|
+
<string>--worker</string>
|
|
130
|
+
</array>
|
|
131
|
+
<key>WorkingDirectory</key><string>${TIGER_HOME}</string>
|
|
132
|
+
<key>EnvironmentVariables</key>
|
|
133
|
+
<dict><key>TIGER_HOME</key><string>${TIGER_HOME}</string></dict>
|
|
134
|
+
<key>RunAtLoad</key><true/>
|
|
135
|
+
<key>KeepAlive</key><true/>
|
|
136
|
+
<key>StandardOutPath</key><string>${logPath}</string>
|
|
137
|
+
<key>StandardErrorPath</key><string>${logPath}</string>
|
|
138
|
+
</dict>
|
|
139
|
+
</plist>
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(plistPath, plist, 'utf8');
|
|
143
|
+
console.log(` Wrote ${plistPath}`);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
execSync(`launchctl load -w "${plistPath}"`, { stdio: 'ignore' });
|
|
147
|
+
console.log(' Daemon loaded via launchctl');
|
|
148
|
+
console.log(` Manage with: launchctl {load|unload} ~/Library/LaunchAgents/com.tiger.agent.plist`);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
console.log(' Wrote plist but launchctl load failed. Run manually:');
|
|
151
|
+
console.log(` launchctl load -w "${plistPath}"`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function installDaemonForPlatform() {
|
|
156
|
+
const plat = process.platform;
|
|
157
|
+
if (plat === 'linux') {
|
|
158
|
+
installSystemd();
|
|
159
|
+
} else if (plat === 'darwin') {
|
|
160
|
+
installLaunchd();
|
|
161
|
+
} else {
|
|
162
|
+
console.log(` Daemon auto-install is not supported on ${plat}.`);
|
|
163
|
+
console.log(' Start manually: tiger telegram --background');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
(async function main() {
|
|
170
|
+
console.log(`
|
|
171
|
+
╔══════════════════════════════════════════╗
|
|
172
|
+
║ 🐯 Tiger Agent — First-time Setup ║
|
|
173
|
+
╚══════════════════════════════════════════╝
|
|
174
|
+
Config will be saved to: ${TIGER_HOME}
|
|
175
|
+
`);
|
|
176
|
+
|
|
177
|
+
// Warn if existing config
|
|
178
|
+
if (fs.existsSync(ENV_PATH)) {
|
|
179
|
+
const ow = await ask('Existing config found. Overwrite? (y/N): ');
|
|
180
|
+
if (!yn(ow, false)) { console.log('Cancelled.'); rl.close(); return; }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Active provider ────────────────────────────────────────────────────────
|
|
184
|
+
console.log('\nAvailable providers: kimi, zai (Zhipu GLM-5), minimax, claude, moonshot');
|
|
185
|
+
const activeProv = (await ask('Active provider (zai): ')).trim() || 'zai';
|
|
186
|
+
const provOrder = (await ask(`Provider fallback order (${activeProv},claude,kimi,minimax,moonshot): `)).trim()
|
|
187
|
+
|| `${activeProv},claude,kimi,minimax,moonshot`;
|
|
188
|
+
|
|
189
|
+
// ── API keys ───────────────────────────────────────────────────────────────
|
|
190
|
+
console.log('\nEnter API keys (press Enter to skip a provider):');
|
|
191
|
+
|
|
192
|
+
const kimiKey = (await askHidden(' KIMI_CODE_API_KEY : ')).trim();
|
|
193
|
+
const moonshotKey= (await askHidden(' MOONSHOT_API_KEY : ')).trim();
|
|
194
|
+
const zaiKey = (await askHidden(' ZAI_API_KEY : ')).trim();
|
|
195
|
+
const minimaxKey = (await askHidden(' MINIMAX_API_KEY : ')).trim();
|
|
196
|
+
const claudeKey = (await askHidden(' CLAUDE_API_KEY : ')).trim();
|
|
197
|
+
|
|
198
|
+
// ── Telegram ───────────────────────────────────────────────────────────────
|
|
199
|
+
console.log('');
|
|
200
|
+
const tgToken = (await askHidden(' TELEGRAM_BOT_TOKEN : ')).trim();
|
|
201
|
+
|
|
202
|
+
// ── Token limits ───────────────────────────────────────────────────────────
|
|
203
|
+
console.log('\nDaily token limits per provider (0 = unlimited, auto-switch on breach):');
|
|
204
|
+
const kimiLimit = (await ask(' KIMI_TOKEN_LIMIT (100000): ')).trim() || '100000';
|
|
205
|
+
const moonshotLimit= (await ask(' MOONSHOT_TOKEN_LIMIT(100000): ')).trim() || '100000';
|
|
206
|
+
const zaiLimit = (await ask(' ZAI_TOKEN_LIMIT (100000): ')).trim() || '100000';
|
|
207
|
+
const minimaxLimit = (await ask(' MINIMAX_TOKEN_LIMIT (100000): ')).trim() || '100000';
|
|
208
|
+
const claudeLimit = (await ask(' CLAUDE_TOKEN_LIMIT (500000): ')).trim() || '500000';
|
|
209
|
+
|
|
210
|
+
// ── Misc ───────────────────────────────────────────────────────────────────
|
|
211
|
+
const allowShell = yn(await ask('\nEnable shell tool? (y/N): '), false);
|
|
212
|
+
const allowSkill = yn(await ask('Enable skill install? (y/N): '), false);
|
|
213
|
+
|
|
214
|
+
// ── Write .env ─────────────────────────────────────────────────────────────
|
|
215
|
+
const lines = [
|
|
216
|
+
'# Tiger Agent config — generated by `tiger onboard`',
|
|
217
|
+
'',
|
|
218
|
+
'# ── Legacy Kimi compat (used if ACTIVE_PROVIDER=kimi)',
|
|
219
|
+
envLine('KIMI_PROVIDER', 'code'),
|
|
220
|
+
envLine('KIMI_CODE_API_KEY', kimiKey),
|
|
221
|
+
envLine('KIMI_BASE_URL', 'https://api.kimi.com/coding/v1'),
|
|
222
|
+
envLine('KIMI_CHAT_MODEL', 'kimi-coding/k2p5'),
|
|
223
|
+
envLine('KIMI_EMBED_MODEL', ''),
|
|
224
|
+
envLine('KIMI_USER_AGENT', 'KimiCLI/0.77'),
|
|
225
|
+
envLine('KIMI_ENABLE_EMBEDDINGS', 'false'),
|
|
226
|
+
envLine('KIMI_TIMEOUT_MS', '30000'),
|
|
227
|
+
'',
|
|
228
|
+
'# ── Multi-provider',
|
|
229
|
+
envLine('ACTIVE_PROVIDER', activeProv),
|
|
230
|
+
envLine('PROVIDER_ORDER', provOrder),
|
|
231
|
+
'',
|
|
232
|
+
'# ── Z.ai (Zhipu GLM)',
|
|
233
|
+
envLine('ZAI_API_KEY', zaiKey),
|
|
234
|
+
envLine('ZAI_BASE_URL', 'https://open.bigmodel.cn/api/paas/v4'),
|
|
235
|
+
envLine('ZAI_MODEL', 'glm-5'),
|
|
236
|
+
envLine('ZAI_TIMEOUT_MS', '30000'),
|
|
237
|
+
'',
|
|
238
|
+
'# ── MiniMax',
|
|
239
|
+
envLine('MINIMAX_API_KEY', minimaxKey),
|
|
240
|
+
envLine('MINIMAX_BASE_URL', 'https://api.minimax.chat/v1'),
|
|
241
|
+
envLine('MINIMAX_MODEL', 'abab6.5s-chat'),
|
|
242
|
+
envLine('MINIMAX_TIMEOUT_MS', '30000'),
|
|
243
|
+
'',
|
|
244
|
+
'# ── Claude (Anthropic)',
|
|
245
|
+
envLine('CLAUDE_API_KEY', claudeKey),
|
|
246
|
+
envLine('CLAUDE_MODEL', 'claude-sonnet-4-6'),
|
|
247
|
+
envLine('CLAUDE_TIMEOUT_MS', '60000'),
|
|
248
|
+
'',
|
|
249
|
+
'# ── Moonshot',
|
|
250
|
+
envLine('MOONSHOT_API_KEY', moonshotKey),
|
|
251
|
+
envLine('MOONSHOT_BASE_URL', 'https://api.moonshot.cn/v1'),
|
|
252
|
+
envLine('MOONSHOT_MODEL', 'kimi-k1'),
|
|
253
|
+
'',
|
|
254
|
+
'# ── Token limits (daily, 0 = unlimited)',
|
|
255
|
+
envLine('KIMI_TOKEN_LIMIT', kimiLimit),
|
|
256
|
+
envLine('MOONSHOT_TOKEN_LIMIT', moonshotLimit),
|
|
257
|
+
envLine('ZAI_TOKEN_LIMIT', zaiLimit),
|
|
258
|
+
envLine('MINIMAX_TOKEN_LIMIT', minimaxLimit),
|
|
259
|
+
envLine('CLAUDE_TOKEN_LIMIT', claudeLimit),
|
|
260
|
+
'',
|
|
261
|
+
'# ── Telegram',
|
|
262
|
+
envLine('TELEGRAM_BOT_TOKEN', tgToken),
|
|
263
|
+
'',
|
|
264
|
+
'# ── Permissions',
|
|
265
|
+
envLine('ALLOW_SHELL', allowShell ? 'true' : 'false'),
|
|
266
|
+
envLine('ALLOW_SKILL_INSTALL', allowSkill ? 'true' : 'false'),
|
|
267
|
+
'',
|
|
268
|
+
'# ── Paths (relative to TIGER_HOME)',
|
|
269
|
+
'DATA_DIR=./data',
|
|
270
|
+
'DB_PATH=./db/agent.json',
|
|
271
|
+
'VECTOR_DB_PATH=./db/memory.sqlite',
|
|
272
|
+
'SQLITE_VEC_EXTENSION=',
|
|
273
|
+
'',
|
|
274
|
+
'# ── Memory',
|
|
275
|
+
'MAX_MESSAGES=200',
|
|
276
|
+
'RECENT_MESSAGES=40',
|
|
277
|
+
'OWN_SKILL_UPDATE_HOURS=24',
|
|
278
|
+
'SOUL_UPDATE_HOURS=24',
|
|
279
|
+
'REFLECTION_UPDATE_HOURS=12',
|
|
280
|
+
'MEMORY_INGEST_EVERY_TURNS=2',
|
|
281
|
+
'MEMORY_INGEST_MIN_CHARS=140',
|
|
282
|
+
''
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
fs.writeFileSync(ENV_PATH, lines.join('\n'), { mode: 0o600 });
|
|
286
|
+
console.log(`\n✅ Config written to ${ENV_PATH}`);
|
|
287
|
+
|
|
288
|
+
// ── Daemon install ─────────────────────────────────────────────────────────
|
|
289
|
+
let wantDaemon = installDaemon;
|
|
290
|
+
if (!wantDaemon) {
|
|
291
|
+
wantDaemon = yn(await ask('\nInstall system daemon (auto-start Telegram bot on boot)? (y/N): '), false);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (wantDaemon) {
|
|
295
|
+
if (!tgToken) {
|
|
296
|
+
console.log('⚠️ No Telegram token set — skipping daemon install.');
|
|
297
|
+
} else {
|
|
298
|
+
console.log('\nInstalling daemon...');
|
|
299
|
+
installDaemonForPlatform();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log(`
|
|
304
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
305
|
+
Setup complete! 🐯
|
|
306
|
+
|
|
307
|
+
Start CLI: tiger start
|
|
308
|
+
Start Telegram: tiger telegram
|
|
309
|
+
Background daemon: tiger telegram --background
|
|
310
|
+
Switch provider: /api claude (in Telegram chat)
|
|
311
|
+
Token usage: /tokens (in Telegram chat)
|
|
312
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
|
|
313
|
+
|
|
314
|
+
rl.close();
|
|
315
|
+
})().catch((err) => {
|
|
316
|
+
console.error(`\nOnboard failed: ${err.message}`);
|
|
317
|
+
rl.close();
|
|
318
|
+
process.exit(1);
|
|
319
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Simple staged-content secret scanner.
|
|
5
|
+
# Blocks commits/pushes if it finds likely credentials.
|
|
6
|
+
|
|
7
|
+
# Ensure we run from the repo root even if invoked from elsewhere.
|
|
8
|
+
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
9
|
+
if [[ -n "${ROOT:-}" ]]; then
|
|
10
|
+
cd "$ROOT"
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Files to skip (by path regex) even if staged.
|
|
14
|
+
SKIP_RE='(^node_modules/|^package-lock\.json$|^\.env\.example$)'
|
|
15
|
+
|
|
16
|
+
# Read staged file list (added/modified/copied/renamed).
|
|
17
|
+
mapfile -d '' FILES < <(git diff --cached --name-only -z --diff-filter=ACMR)
|
|
18
|
+
|
|
19
|
+
if [[ ${#FILES[@]} -eq 0 ]]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Regexes for common secret formats.
|
|
24
|
+
# Keep these relatively specific to reduce false positives.
|
|
25
|
+
PATTERNS=(
|
|
26
|
+
'AKIA[0-9A-Z]{16}'
|
|
27
|
+
'ASIA[0-9A-Z]{16}'
|
|
28
|
+
'ghp_[A-Za-z0-9]{20,}'
|
|
29
|
+
'github_pat_[A-Za-z0-9_]{20,}'
|
|
30
|
+
'xox[baprs]-[A-Za-z0-9-]{10,}'
|
|
31
|
+
'-----BEGIN (RSA|OPENSSH|EC) PRIVATE KEY-----'
|
|
32
|
+
'ssh-rsa [A-Za-z0-9+/]{100,}={0,3}'
|
|
33
|
+
'[0-9]{8,10}:[A-Za-z0-9_-]{30,}' # Telegram bot token shape
|
|
34
|
+
'sk-[A-Za-z0-9]{20,}'
|
|
35
|
+
'AIza[0-9A-Za-z\-_]{30,}'
|
|
36
|
+
'(?i)authorization:\s*bearer\s+[A-Za-z0-9._\-+/=]{20,}'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Env-var style (only for common credential variable names).
|
|
40
|
+
# This avoids false positives from code/docs that merely mention "token" etc.
|
|
41
|
+
ENV_KEYS='(?i)(MOONSHOT_API_KEY|KIMI_CODE_API_KEY|KIMI_API_KEY|TELEGRAM_BOT_TOKEN|OPENAI_API_KEY|GITHUB_TOKEN|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|NPM_TOKEN)'
|
|
42
|
+
ENV_RE="${ENV_KEYS}[[:space:]]*=[[:space:]]*['\"]?[^#\r\n\s'\"]{12,}"
|
|
43
|
+
|
|
44
|
+
FOUND=0
|
|
45
|
+
|
|
46
|
+
for f in "${FILES[@]}"; do
|
|
47
|
+
if [[ "$f" =~ $SKIP_RE ]]; then
|
|
48
|
+
continue
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Get staged content. If it's binary or missing, skip gracefully.
|
|
52
|
+
if ! content=$(git show ":$f" 2>/dev/null); then
|
|
53
|
+
continue
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
for re in "${PATTERNS[@]}"; do
|
|
57
|
+
if printf '%s' "$content" | grep -Pq -- "$re"; then
|
|
58
|
+
echo "[secret-scan] Potential secret detected in staged file: $f" >&2
|
|
59
|
+
FOUND=1
|
|
60
|
+
break
|
|
61
|
+
fi
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
if [[ $FOUND -eq 0 ]]; then
|
|
65
|
+
if printf '%s' "$content" | grep -Pq -- "$ENV_RE"; then
|
|
66
|
+
echo "[secret-scan] Suspicious key/value secret detected in staged file: $f" >&2
|
|
67
|
+
FOUND=1
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
if [[ $FOUND -ne 0 ]]; then
|
|
72
|
+
# Don't spam; one file is enough to stop.
|
|
73
|
+
break
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
if [[ $FOUND -ne 0 ]]; then
|
|
79
|
+
cat >&2 <<'MSG'
|
|
80
|
+
[secret-scan] Commit/push blocked.
|
|
81
|
+
[secret-scan] Fix: remove the secret from the commit, or move it to .env (which is gitignored).
|
|
82
|
+
[secret-scan] If you already pushed a secret: rotate/revoke it immediately.
|
|
83
|
+
MSG
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
exit 0
|