twinclaw 1.0.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/README.md +66 -0
- package/bin/npm-twinclaw.js +17 -0
- package/bin/run-twinbot-cli.js +36 -0
- package/bin/twinbot.js +4 -0
- package/bin/twinclaw.js +4 -0
- package/dist/api/handlers/browser.js +160 -0
- package/dist/api/handlers/callback.js +80 -0
- package/dist/api/handlers/config-validate.js +19 -0
- package/dist/api/handlers/health.js +117 -0
- package/dist/api/handlers/local-state-backup.js +118 -0
- package/dist/api/handlers/persona-state.js +59 -0
- package/dist/api/handlers/skill-packages.js +94 -0
- package/dist/api/router.js +278 -0
- package/dist/api/runtime-event-producer.js +99 -0
- package/dist/api/shared.js +82 -0
- package/dist/api/websocket-hub.js +305 -0
- package/dist/config/config-loader.js +2 -0
- package/dist/config/env-schema.js +202 -0
- package/dist/config/env-validator.js +223 -0
- package/dist/config/identity-bootstrap.js +115 -0
- package/dist/config/json-config.js +344 -0
- package/dist/config/workspace.js +186 -0
- package/dist/core/channels-cli.js +77 -0
- package/dist/core/cli.js +119 -0
- package/dist/core/context-assembly.js +33 -0
- package/dist/core/doctor.js +365 -0
- package/dist/core/gateway-cli.js +323 -0
- package/dist/core/gateway.js +416 -0
- package/dist/core/heartbeat.js +54 -0
- package/dist/core/install-cli.js +320 -0
- package/dist/core/lane-executor.js +134 -0
- package/dist/core/logs-cli.js +70 -0
- package/dist/core/onboarding.js +760 -0
- package/dist/core/pairing-cli.js +78 -0
- package/dist/core/secret-vault-cli.js +204 -0
- package/dist/core/types.js +1 -0
- package/dist/index.js +404 -0
- package/dist/interfaces/dispatcher.js +214 -0
- package/dist/interfaces/telegram_handler.js +82 -0
- package/dist/interfaces/tui-dashboard.js +53 -0
- package/dist/interfaces/whatsapp_handler.js +94 -0
- package/dist/release/cli.js +97 -0
- package/dist/release/mvp-gate-cli.js +118 -0
- package/dist/release/twinbot-config-schema.js +162 -0
- package/dist/release/twinclaw-config-schema.js +162 -0
- package/dist/services/block-chunker.js +174 -0
- package/dist/services/browser-service.js +334 -0
- package/dist/services/context-lifecycle.js +314 -0
- package/dist/services/db.js +1055 -0
- package/dist/services/delivery-tracker.js +110 -0
- package/dist/services/dm-pairing.js +245 -0
- package/dist/services/embedding-service.js +125 -0
- package/dist/services/file-watcher.js +125 -0
- package/dist/services/inbound-debounce.js +92 -0
- package/dist/services/incident-manager.js +516 -0
- package/dist/services/job-scheduler.js +176 -0
- package/dist/services/local-state-backup.js +682 -0
- package/dist/services/mcp-client-adapter.js +291 -0
- package/dist/services/mcp-server-manager.js +143 -0
- package/dist/services/model-router.js +927 -0
- package/dist/services/mvp-gate.js +845 -0
- package/dist/services/orchestration-service.js +422 -0
- package/dist/services/persona-state.js +256 -0
- package/dist/services/policy-engine.js +92 -0
- package/dist/services/proactive-notifier.js +94 -0
- package/dist/services/queue-service.js +146 -0
- package/dist/services/release-pipeline.js +652 -0
- package/dist/services/runtime-budget-governor.js +415 -0
- package/dist/services/secret-vault.js +704 -0
- package/dist/services/semantic-memory.js +249 -0
- package/dist/services/skill-package-manager.js +806 -0
- package/dist/services/skill-registry.js +122 -0
- package/dist/services/streaming-output.js +75 -0
- package/dist/services/stt-service.js +39 -0
- package/dist/services/tts-service.js +44 -0
- package/dist/skills/builtin.js +250 -0
- package/dist/skills/shell.js +87 -0
- package/dist/skills/types.js +1 -0
- package/dist/types/api.js +1 -0
- package/dist/types/context-budget.js +1 -0
- package/dist/types/doctor.js +1 -0
- package/dist/types/file-watcher.js +1 -0
- package/dist/types/incident.js +1 -0
- package/dist/types/local-state-backup.js +1 -0
- package/dist/types/mcp.js +1 -0
- package/dist/types/messaging.js +1 -0
- package/dist/types/model-routing.js +1 -0
- package/dist/types/mvp-gate.js +2 -0
- package/dist/types/orchestration.js +1 -0
- package/dist/types/persona-state.js +22 -0
- package/dist/types/policy.js +1 -0
- package/dist/types/reasoning-graph.js +1 -0
- package/dist/types/release.js +1 -0
- package/dist/types/reliability.js +1 -0
- package/dist/types/runtime-budget.js +1 -0
- package/dist/types/scheduler.js +1 -0
- package/dist/types/secret-vault.js +1 -0
- package/dist/types/skill-packages.js +1 -0
- package/dist/types/websocket.js +14 -0
- package/dist/utils/logger.js +57 -0
- package/dist/utils/retry.js +61 -0
- package/dist/utils/secret-scan.js +208 -0
- package/mcp-servers.json +179 -0
- package/package.json +81 -0
- package/skill-packages.json +92 -0
- package/skill-packages.lock.json +5 -0
- package/src/skills/builtin.ts +275 -0
- package/src/skills/shell.ts +118 -0
- package/src/skills/types.ts +30 -0
- package/src/types/api.ts +252 -0
- package/src/types/blessed-contrib.d.ts +4 -0
- package/src/types/context-budget.ts +76 -0
- package/src/types/doctor.ts +29 -0
- package/src/types/file-watcher.ts +26 -0
- package/src/types/incident.ts +57 -0
- package/src/types/local-state-backup.ts +121 -0
- package/src/types/mcp.ts +106 -0
- package/src/types/messaging.ts +35 -0
- package/src/types/model-routing.ts +61 -0
- package/src/types/mvp-gate.ts +99 -0
- package/src/types/orchestration.ts +65 -0
- package/src/types/persona-state.ts +61 -0
- package/src/types/policy.ts +27 -0
- package/src/types/reasoning-graph.ts +58 -0
- package/src/types/release.ts +115 -0
- package/src/types/reliability.ts +43 -0
- package/src/types/runtime-budget.ts +85 -0
- package/src/types/scheduler.ts +47 -0
- package/src/types/secret-vault.ts +62 -0
- package/src/types/skill-packages.ts +81 -0
- package/src/types/sqlite-vec.d.ts +5 -0
- package/src/types/websocket.ts +122 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ── Close Codes ────────────────────────────────────────────────────────────────
|
|
2
|
+
/** Deterministic close codes for WebSocket control-plane lifecycle events. */
|
|
3
|
+
export const WsCloseCode = {
|
|
4
|
+
/** Authentication token missing or invalid. */
|
|
5
|
+
AuthFailed: 4001,
|
|
6
|
+
/** Client did not complete auth handshake within the required window. */
|
|
7
|
+
AuthRequired: 4002,
|
|
8
|
+
/** Subscription request contains an unrecognised topic. */
|
|
9
|
+
InvalidSubscription: 4003,
|
|
10
|
+
/** Client failed to respond to ping heartbeats (stale). */
|
|
11
|
+
StaleConnection: 4004,
|
|
12
|
+
/** Server is shutting down gracefully. */
|
|
13
|
+
ServerShutdown: 4005,
|
|
14
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getSecretVaultService } from '../services/secret-vault.js';
|
|
4
|
+
function currentDateIso() {
|
|
5
|
+
return new Date().toISOString().slice(0, 10);
|
|
6
|
+
}
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
function transcriptPath(dateIso) {
|
|
11
|
+
return path.resolve('memory', `${dateIso}.md`);
|
|
12
|
+
}
|
|
13
|
+
async function ensureTranscriptDir() {
|
|
14
|
+
await mkdir(path.resolve('memory'), { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
export function scrubSensitiveText(raw) {
|
|
17
|
+
let sanitized = raw;
|
|
18
|
+
sanitized = sanitized.replace(/(api[_-]?key|token|secret|password)\s*[:=]\s*[^\s\n]+/gi, '$1=[REDACTED]');
|
|
19
|
+
sanitized = sanitized.replace(/Bearer\s+[A-Za-z0-9._\-]+/g, 'Bearer [REDACTED]');
|
|
20
|
+
sanitized = sanitized.replace(/[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]{20,}/g, '[REDACTED_JWT]');
|
|
21
|
+
try {
|
|
22
|
+
sanitized = getSecretVaultService().redact(sanitized);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// Redaction fallback keeps logger resilient in test/mocked environments.
|
|
26
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
+
console.warn(`[Logger] Secret-vault redaction skipped: ${message}`);
|
|
28
|
+
}
|
|
29
|
+
return sanitized;
|
|
30
|
+
}
|
|
31
|
+
async function appendSection(title, body) {
|
|
32
|
+
await ensureTranscriptDir();
|
|
33
|
+
const line = `\n## ${title} @ ${nowIso()}\n\n${body}\n`;
|
|
34
|
+
await appendFile(transcriptPath(currentDateIso()), line, 'utf8');
|
|
35
|
+
}
|
|
36
|
+
export async function logThought(note) {
|
|
37
|
+
await appendSection('Thought', scrubSensitiveText(note));
|
|
38
|
+
}
|
|
39
|
+
export async function logToolCall(toolName, args, result) {
|
|
40
|
+
const payload = [
|
|
41
|
+
`- Tool: ${toolName}`,
|
|
42
|
+
`- Args: ${scrubSensitiveText(JSON.stringify(args))}`,
|
|
43
|
+
`- Result: ${scrubSensitiveText(result)}`,
|
|
44
|
+
].join('\n');
|
|
45
|
+
await appendSection('Tool Call', payload);
|
|
46
|
+
}
|
|
47
|
+
export async function logSystemCommand(command, output, exitCode) {
|
|
48
|
+
const payload = [
|
|
49
|
+
`- Command: ${scrubSensitiveText(command)}`,
|
|
50
|
+
`- ExitCode: ${exitCode}`,
|
|
51
|
+
`- Output:`,
|
|
52
|
+
'```text',
|
|
53
|
+
scrubSensitiveText(output),
|
|
54
|
+
'```',
|
|
55
|
+
].join('\n');
|
|
56
|
+
await appendSection('System Command', payload);
|
|
57
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { logThought } from './logger.js';
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
maxAttempts: 3,
|
|
4
|
+
baseDelayMs: 1000,
|
|
5
|
+
backoffFactor: 2,
|
|
6
|
+
maxDelayMs: 15_000,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Execute an async function with bounded exponential backoff retry.
|
|
10
|
+
*
|
|
11
|
+
* - Retries up to `maxAttempts` times on failure.
|
|
12
|
+
* - Delay doubles after each attempt (capped at `maxDelayMs`).
|
|
13
|
+
* - All attempts are logged for postmortem traceability.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const result = await withRetry(
|
|
18
|
+
* () => telegram.sendText(chatId, text),
|
|
19
|
+
* { maxAttempts: 3, label: 'telegram:sendText' },
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function withRetry(fn, options = {}) {
|
|
24
|
+
const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;
|
|
25
|
+
const baseDelayMs = options.baseDelayMs ?? DEFAULTS.baseDelayMs;
|
|
26
|
+
const backoffFactor = options.backoffFactor ?? DEFAULTS.backoffFactor;
|
|
27
|
+
const maxDelayMs = options.maxDelayMs ?? DEFAULTS.maxDelayMs;
|
|
28
|
+
const label = options.label ?? 'unnamed';
|
|
29
|
+
const start = Date.now();
|
|
30
|
+
let lastError = '';
|
|
31
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
32
|
+
try {
|
|
33
|
+
const value = await fn();
|
|
34
|
+
const totalDurationMs = Date.now() - start;
|
|
35
|
+
if (attempt > 1) {
|
|
36
|
+
void logThought(`[Retry] ${label} succeeded on attempt ${attempt}/${maxAttempts} (${totalDurationMs}ms).`);
|
|
37
|
+
}
|
|
38
|
+
return { ok: true, value, attempts: attempt, totalDurationMs };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
42
|
+
if (attempt < maxAttempts) {
|
|
43
|
+
const delay = Math.min(baseDelayMs * backoffFactor ** (attempt - 1), maxDelayMs);
|
|
44
|
+
void logThought(`[Retry] ${label} attempt ${attempt}/${maxAttempts} failed: ${lastError}. Retrying in ${delay}ms.`);
|
|
45
|
+
await sleep(delay);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
void logThought(`[Retry] ${label} exhausted all ${maxAttempts} attempts. Last error: ${lastError}.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: lastError,
|
|
55
|
+
attempts: maxAttempts,
|
|
56
|
+
totalDurationMs: Date.now() - start,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function sleep(ms) {
|
|
60
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const SCAN_FILE_EXTENSIONS = new Set([
|
|
4
|
+
'.ts',
|
|
5
|
+
'.tsx',
|
|
6
|
+
'.js',
|
|
7
|
+
'.cjs',
|
|
8
|
+
'.mjs',
|
|
9
|
+
'.json',
|
|
10
|
+
'.md',
|
|
11
|
+
'.yaml',
|
|
12
|
+
'.yml',
|
|
13
|
+
'.env',
|
|
14
|
+
'.txt',
|
|
15
|
+
]);
|
|
16
|
+
const EXCLUDED_PATH_SEGMENTS = new Set([
|
|
17
|
+
'.git',
|
|
18
|
+
'.github\\workflows\\node_modules',
|
|
19
|
+
'node_modules',
|
|
20
|
+
'dist',
|
|
21
|
+
'coverage',
|
|
22
|
+
'.next',
|
|
23
|
+
'.vite',
|
|
24
|
+
'.turbo',
|
|
25
|
+
'memory',
|
|
26
|
+
]);
|
|
27
|
+
const IGNORED_FILE_NAMES = new Set([
|
|
28
|
+
'package-lock.json',
|
|
29
|
+
'pnpm-lock.yaml',
|
|
30
|
+
'yarn.lock',
|
|
31
|
+
]);
|
|
32
|
+
const PLACEHOLDER_MARKERS = [
|
|
33
|
+
'<YOUR_',
|
|
34
|
+
'<REDACTED',
|
|
35
|
+
'YOUR_API_KEY',
|
|
36
|
+
'YOUR_TOKEN',
|
|
37
|
+
'EXAMPLE_',
|
|
38
|
+
'TEST_',
|
|
39
|
+
'DUMMY_',
|
|
40
|
+
'PLACEHOLDER',
|
|
41
|
+
];
|
|
42
|
+
const SECRET_PATTERNS = [
|
|
43
|
+
{
|
|
44
|
+
name: 'OpenRouter API key',
|
|
45
|
+
severity: 'high',
|
|
46
|
+
expression: /\bsk-or-v1-[A-Za-z0-9]{32,}\b/g,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Telegram bot token',
|
|
50
|
+
severity: 'high',
|
|
51
|
+
expression: /\b\d{8,10}:[A-Za-z0-9_-]{35,}\b/g,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Google API key',
|
|
55
|
+
severity: 'high',
|
|
56
|
+
expression: /\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'GitHub personal access token',
|
|
60
|
+
severity: 'high',
|
|
61
|
+
expression: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'AWS access key ID',
|
|
65
|
+
severity: 'high',
|
|
66
|
+
expression: /\bAKIA[0-9A-Z]{16}\b/g,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'Private key block',
|
|
70
|
+
severity: 'high',
|
|
71
|
+
expression: /-----BEGIN [A-Z ]+ PRIVATE KEY-----/g,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'Suspicious key assignment',
|
|
75
|
+
severity: 'medium',
|
|
76
|
+
expression: /\b(api[_-]?key|token|secret)\b\s*[:=]\s*["'][A-Za-z0-9._-]{20,}["']/gi,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
function toRelativePath(rootDir, filePath) {
|
|
80
|
+
return path.relative(rootDir, filePath).replace(/\\/g, '/');
|
|
81
|
+
}
|
|
82
|
+
function isExcludedPath(relativePath) {
|
|
83
|
+
const normalized = relativePath.toLowerCase();
|
|
84
|
+
if (normalized.length === 0) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
if (IGNORED_FILE_NAMES.has(path.basename(normalized))) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
for (const segment of EXCLUDED_PATH_SEGMENTS) {
|
|
91
|
+
const normalizedSegment = segment.replace(/\\/g, '/').toLowerCase();
|
|
92
|
+
if (normalized === normalizedSegment ||
|
|
93
|
+
normalized.startsWith(`${normalizedSegment}/`) ||
|
|
94
|
+
normalized.includes(`/${normalizedSegment}/`)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
function shouldScanFile(filePath) {
|
|
101
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
102
|
+
return SCAN_FILE_EXTENSIONS.has(extension) || path.basename(filePath).startsWith('.env');
|
|
103
|
+
}
|
|
104
|
+
function hasPlaceholderMarker(value) {
|
|
105
|
+
const upper = value.toUpperCase();
|
|
106
|
+
return PLACEHOLDER_MARKERS.some((marker) => upper.includes(marker));
|
|
107
|
+
}
|
|
108
|
+
function isIntentionalExample(line) {
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
if (trimmed.startsWith('#')) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (trimmed.includes('example.com')) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (trimmed.includes('REDACTED') || trimmed.includes('redacted')) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (hasPlaceholderMarker(trimmed)) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
function maskMatch(value) {
|
|
125
|
+
if (value.length <= 8) {
|
|
126
|
+
return '********';
|
|
127
|
+
}
|
|
128
|
+
return `${value.slice(0, 4)}…${value.slice(-4)}`;
|
|
129
|
+
}
|
|
130
|
+
function scanFile(filePath, rootDir) {
|
|
131
|
+
const hits = [];
|
|
132
|
+
const relativePath = toRelativePath(rootDir, filePath);
|
|
133
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
134
|
+
const lines = raw.split(/\r?\n/);
|
|
135
|
+
lines.forEach((line, index) => {
|
|
136
|
+
if (isIntentionalExample(line)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
140
|
+
pattern.expression.lastIndex = 0;
|
|
141
|
+
for (const match of line.matchAll(pattern.expression)) {
|
|
142
|
+
const matched = match[0];
|
|
143
|
+
if (!matched) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (hasPlaceholderMarker(matched)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
hits.push({
|
|
150
|
+
file: relativePath,
|
|
151
|
+
line: index + 1,
|
|
152
|
+
pattern: pattern.name,
|
|
153
|
+
severity: pattern.severity,
|
|
154
|
+
preview: maskMatch(matched),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
return hits;
|
|
160
|
+
}
|
|
161
|
+
function walkDirectory(rootDir, currentDir, hits) {
|
|
162
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
165
|
+
const relativePath = toRelativePath(rootDir, fullPath);
|
|
166
|
+
if (isExcludedPath(relativePath)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (entry.isDirectory()) {
|
|
170
|
+
walkDirectory(rootDir, fullPath, hits);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!entry.isFile() || !shouldScanFile(fullPath)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
hits.push(...scanFile(fullPath, rootDir));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export function runSecretScan(rootDir = process.cwd()) {
|
|
180
|
+
const absoluteRoot = path.resolve(rootDir);
|
|
181
|
+
const hits = [];
|
|
182
|
+
walkDirectory(absoluteRoot, absoluteRoot, hits);
|
|
183
|
+
const high = hits.filter((hit) => hit.severity === 'high');
|
|
184
|
+
const medium = hits.filter((hit) => hit.severity === 'medium');
|
|
185
|
+
const passed = high.length === 0;
|
|
186
|
+
if (high.length === 0 && medium.length === 0) {
|
|
187
|
+
console.log('Secret scan passed: no suspicious credentials detected.');
|
|
188
|
+
return { high, medium, passed };
|
|
189
|
+
}
|
|
190
|
+
if (high.length > 0) {
|
|
191
|
+
console.error('High-severity secret findings detected:');
|
|
192
|
+
for (const hit of high) {
|
|
193
|
+
console.error(` [HIGH] ${hit.file}:${hit.line} ${hit.pattern} (${hit.preview})`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (medium.length > 0) {
|
|
197
|
+
console.warn('Medium-severity secret findings detected:');
|
|
198
|
+
for (const hit of medium) {
|
|
199
|
+
console.warn(` [MEDIUM] ${hit.file}:${hit.line} ${hit.pattern} (${hit.preview})`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { high, medium, passed };
|
|
203
|
+
}
|
|
204
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
205
|
+
const rootArg = process.argv[2] ?? process.cwd();
|
|
206
|
+
const result = runSecretScan(rootArg);
|
|
207
|
+
process.exit(result.passed ? 0 : 1);
|
|
208
|
+
}
|
package/mcp-servers.json
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
{
|
|
2
|
+
"servers": [
|
|
3
|
+
{
|
|
4
|
+
"id": "filesystem",
|
|
5
|
+
"name": "Filesystem",
|
|
6
|
+
"description": "Read, write, and manage local files and directories securely.",
|
|
7
|
+
"transport": "stdio",
|
|
8
|
+
"command": "npx",
|
|
9
|
+
"args": [
|
|
10
|
+
"-y",
|
|
11
|
+
"@modelcontextprotocol/server-filesystem",
|
|
12
|
+
"."
|
|
13
|
+
],
|
|
14
|
+
"autoConnect": true,
|
|
15
|
+
"enabled": true
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "memory",
|
|
19
|
+
"name": "Memory (Knowledge Graph)",
|
|
20
|
+
"description": "Persistent knowledge graph for long-term factual memory and entity relationships.",
|
|
21
|
+
"transport": "stdio",
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": [
|
|
24
|
+
"-y",
|
|
25
|
+
"@modelcontextprotocol/server-memory"
|
|
26
|
+
],
|
|
27
|
+
"autoConnect": true,
|
|
28
|
+
"enabled": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "brave-search",
|
|
32
|
+
"name": "Brave Search",
|
|
33
|
+
"description": "Web and local search via Brave Search API for real-time information retrieval.",
|
|
34
|
+
"transport": "stdio",
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": [
|
|
37
|
+
"-y",
|
|
38
|
+
"@modelcontextprotocol/server-brave-search"
|
|
39
|
+
],
|
|
40
|
+
"env": {
|
|
41
|
+
"BRAVE_API_KEY": ""
|
|
42
|
+
},
|
|
43
|
+
"autoConnect": false,
|
|
44
|
+
"enabled": false
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "github",
|
|
48
|
+
"name": "GitHub",
|
|
49
|
+
"description": "Interact with GitHub repositories, issues, pull requests, and code management.",
|
|
50
|
+
"transport": "stdio",
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": [
|
|
53
|
+
"-y",
|
|
54
|
+
"@modelcontextprotocol/server-github"
|
|
55
|
+
],
|
|
56
|
+
"env": {
|
|
57
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": ""
|
|
58
|
+
},
|
|
59
|
+
"autoConnect": false,
|
|
60
|
+
"enabled": false
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "puppeteer",
|
|
64
|
+
"name": "Puppeteer (Browser Automation)",
|
|
65
|
+
"description": "Control a Chromium browser for web scraping, screenshots, and automated UI interaction.",
|
|
66
|
+
"transport": "stdio",
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": [
|
|
69
|
+
"-y",
|
|
70
|
+
"@modelcontextprotocol/server-puppeteer"
|
|
71
|
+
],
|
|
72
|
+
"autoConnect": false,
|
|
73
|
+
"enabled": true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "sequential-thinking",
|
|
77
|
+
"name": "Sequential Thinking",
|
|
78
|
+
"description": "Structured, step-by-step reasoning and problem decomposition for complex tasks.",
|
|
79
|
+
"transport": "stdio",
|
|
80
|
+
"command": "npx",
|
|
81
|
+
"args": [
|
|
82
|
+
"-y",
|
|
83
|
+
"@modelcontextprotocol/server-sequential-thinking"
|
|
84
|
+
],
|
|
85
|
+
"autoConnect": true,
|
|
86
|
+
"enabled": true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "sqlite",
|
|
90
|
+
"name": "SQLite",
|
|
91
|
+
"description": "Query and manage SQLite databases with schema inspection and analysis capabilities.",
|
|
92
|
+
"transport": "stdio",
|
|
93
|
+
"command": "npx",
|
|
94
|
+
"args": [
|
|
95
|
+
"-y",
|
|
96
|
+
"@modelcontextprotocol/server-sqlite",
|
|
97
|
+
"--db-path",
|
|
98
|
+
"./memory/twinclaw.db"
|
|
99
|
+
],
|
|
100
|
+
"autoConnect": false,
|
|
101
|
+
"enabled": true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "postgres",
|
|
105
|
+
"name": "PostgreSQL",
|
|
106
|
+
"description": "Connect to PostgreSQL databases for data querying and schema analysis.",
|
|
107
|
+
"transport": "stdio",
|
|
108
|
+
"command": "npx",
|
|
109
|
+
"args": [
|
|
110
|
+
"-y",
|
|
111
|
+
"@modelcontextprotocol/server-postgres"
|
|
112
|
+
],
|
|
113
|
+
"env": {
|
|
114
|
+
"POSTGRES_CONNECTION_STRING": ""
|
|
115
|
+
},
|
|
116
|
+
"autoConnect": false,
|
|
117
|
+
"enabled": false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "slack",
|
|
121
|
+
"name": "Slack",
|
|
122
|
+
"description": "Send messages, read channels, and manage Slack workspace interactions.",
|
|
123
|
+
"transport": "stdio",
|
|
124
|
+
"command": "npx",
|
|
125
|
+
"args": [
|
|
126
|
+
"-y",
|
|
127
|
+
"@modelcontextprotocol/server-slack"
|
|
128
|
+
],
|
|
129
|
+
"env": {
|
|
130
|
+
"SLACK_BOT_TOKEN": "",
|
|
131
|
+
"SLACK_TEAM_ID": ""
|
|
132
|
+
},
|
|
133
|
+
"autoConnect": false,
|
|
134
|
+
"enabled": false
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "google-maps",
|
|
138
|
+
"name": "Google Maps",
|
|
139
|
+
"description": "Geocoding, directions, place search, and distance calculations via Google Maps API.",
|
|
140
|
+
"transport": "stdio",
|
|
141
|
+
"command": "npx",
|
|
142
|
+
"args": [
|
|
143
|
+
"-y",
|
|
144
|
+
"@modelcontextprotocol/server-google-maps"
|
|
145
|
+
],
|
|
146
|
+
"env": {
|
|
147
|
+
"GOOGLE_MAPS_API_KEY": ""
|
|
148
|
+
},
|
|
149
|
+
"autoConnect": false,
|
|
150
|
+
"enabled": false
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"id": "fetch",
|
|
154
|
+
"name": "Fetch (HTTP)",
|
|
155
|
+
"description": "Make HTTP requests to external APIs and web resources with content extraction.",
|
|
156
|
+
"transport": "stdio",
|
|
157
|
+
"command": "npx",
|
|
158
|
+
"args": [
|
|
159
|
+
"-y",
|
|
160
|
+
"@modelcontextprotocol/server-fetch"
|
|
161
|
+
],
|
|
162
|
+
"autoConnect": true,
|
|
163
|
+
"enabled": true
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"id": "everything",
|
|
167
|
+
"name": "Everything (Test Server)",
|
|
168
|
+
"description": "Reference MCP server implementing all protocol features — for testing and debugging.",
|
|
169
|
+
"transport": "stdio",
|
|
170
|
+
"command": "npx",
|
|
171
|
+
"args": [
|
|
172
|
+
"-y",
|
|
173
|
+
"@modelcontextprotocol/server-everything"
|
|
174
|
+
],
|
|
175
|
+
"autoConnect": false,
|
|
176
|
+
"enabled": false
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "twinclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TwinClaw - Windows-first agentic AI gateway with proactive memory and multimodal hooks.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"twinclaw": "./bin/twinclaw.js",
|
|
8
|
+
"npm-twinclaw": "./bin/npm-twinclaw.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"bin/",
|
|
13
|
+
"src/types/",
|
|
14
|
+
"src/skills/",
|
|
15
|
+
"node_modules/",
|
|
16
|
+
"mcp-servers.json",
|
|
17
|
+
"skill-packages.json",
|
|
18
|
+
"skill-packages.lock.json",
|
|
19
|
+
"twinclaw.default.json"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"twinclaw": "tsx src/index.ts",
|
|
23
|
+
"dev": "tsx watch src/index.ts",
|
|
24
|
+
"start": "tsx src/index.ts",
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"check": "npm run check:types && npm run check:secrets",
|
|
27
|
+
"check:types": "tsc --noEmit",
|
|
28
|
+
"check:secrets": "tsx src/utils/secret-scan.ts",
|
|
29
|
+
"setup:hooks": "git config core.hooksPath .githooks",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:coverage": "vitest run --coverage",
|
|
32
|
+
"test:ci": "vitest run --coverage --reporter json --outputFile=test-results.json",
|
|
33
|
+
"test:release": "vitest run tests/harness/release-pipeline.spec.ts",
|
|
34
|
+
"release:preflight": "tsx src/release/cli.ts preflight",
|
|
35
|
+
"release:prepare": "tsx src/release/cli.ts prepare",
|
|
36
|
+
"release:rollback": "tsx src/release/cli.ts rollback",
|
|
37
|
+
"release:drill": "tsx src/release/cli.ts drill",
|
|
38
|
+
"mvp:gate": "tsx src/release/mvp-gate-cli.ts",
|
|
39
|
+
"mvp:gate:local": "tsx src/release/mvp-gate-cli.ts --skip-health"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [],
|
|
42
|
+
"author": "",
|
|
43
|
+
"license": "ISC",
|
|
44
|
+
"type": "module",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@elevenlabs/elevenlabs-js": "^2.36.0",
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
48
|
+
"@types/node-telegram-bot-api": "^0.64.13",
|
|
49
|
+
"better-sqlite3": "^12.6.2",
|
|
50
|
+
"blessed": "^0.1.81",
|
|
51
|
+
"blessed-contrib": "^1.0.11",
|
|
52
|
+
"chokidar": "^5.0.0",
|
|
53
|
+
"express": "^5.2.1",
|
|
54
|
+
"groq-sdk": "^0.37.0",
|
|
55
|
+
"node-cron": "^4.2.1",
|
|
56
|
+
"node-telegram-bot-api": "^0.63.0",
|
|
57
|
+
"node-windows": "^1.0.0-beta.8",
|
|
58
|
+
"playwright": "^1.58.2",
|
|
59
|
+
"playwright-core": "^1.58.2",
|
|
60
|
+
"qrcode-terminal": "^0.12.0",
|
|
61
|
+
"sqlite-vec": "^0.1.7-alpha.2",
|
|
62
|
+
"ts-node": "^10.9.2",
|
|
63
|
+
"tsx": "^4.21.0",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"whatsapp-web.js": "^1.17.1",
|
|
66
|
+
"ws": "^8.19.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
70
|
+
"@types/blessed": "^0.1.27",
|
|
71
|
+
"@types/express": "^5.0.6",
|
|
72
|
+
"@types/node": "^25.3.0",
|
|
73
|
+
"@types/node-windows": "^0.1.6",
|
|
74
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
75
|
+
"@types/supertest": "^6.0.3",
|
|
76
|
+
"@types/ws": "^8.18.1",
|
|
77
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
78
|
+
"supertest": "^7.2.2",
|
|
79
|
+
"vitest": "^4.0.18"
|
|
80
|
+
}
|
|
81
|
+
}
|