natureco-cli 5.62.0 → 5.64.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/CHANGELOG.md +34 -1
- package/bin/natureco.js +10 -3
- package/package.json +8 -5
- package/scripts/benchmark-startup.js +26 -0
- package/src/commands/backup.js +3 -3
- package/src/commands/chat.js +17 -17
- package/src/commands/code.js +49 -14
- package/src/commands/code_v5.js +15 -1
- package/src/commands/gateway-server.js +128 -24
- package/src/commands/git.js +2 -2
- package/src/commands/pairing.js +2 -22
- package/src/commands/repl.js +118 -112
- package/src/tools/agentic-runner.js +41 -33
- package/src/tools/memory_write.js +16 -12
- package/src/tools/structural_patch.js +25 -0
- package/src/tools/workflow.js +10 -2
- package/src/utils/agent-core.js +51 -0
- package/src/utils/agent-workspace.js +24 -0
- package/src/utils/api.js +12 -8
- package/src/utils/channel-sdk.js +212 -0
- package/src/utils/code-intelligence.js +69 -0
- package/src/utils/coding-session.js +54 -0
- package/src/utils/delivery-store.js +34 -0
- package/src/utils/i18n.js +7 -1
- package/src/utils/json-schema.js +43 -0
- package/src/utils/lsp-client.js +129 -0
- package/src/utils/memory-record.js +49 -0
- package/src/utils/pairing-store.js +55 -0
- package/src/utils/pattern-detector.js +13 -3
- package/src/utils/plugin-registry.js +3 -3
- package/src/utils/process-errors.js +14 -7
- package/src/utils/runtime-health.js +28 -0
- package/src/utils/secret-store.js +90 -0
- package/src/utils/secure-sync.js +63 -0
- package/src/utils/skill-lifecycle.js +59 -0
- package/src/utils/structural-patch.js +68 -0
- package/src/utils/sub-agent.js +13 -2
- package/src/utils/test-failure-analyzer.js +64 -0
- package/src/utils/tool-execution-gateway.js +56 -0
- package/src/utils/tool-manifest.js +31 -0
- package/src/utils/tool-path-policy.js +49 -0
- package/src/utils/tool-result.js +17 -0
- package/src/utils/tool-runner.js +81 -53
- package/src/utils/tools.js +30 -42
|
@@ -2,13 +2,75 @@ const chalk = require('chalk');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
|
-
const { spawn, execSync } = require('child_process');
|
|
5
|
+
const { spawn, execSync, execFileSync } = require('child_process');
|
|
6
6
|
const pino = require('pino');
|
|
7
7
|
const { loadBaileys } = require('../utils/baileys');
|
|
8
8
|
const { ApiError } = require('../utils/errors');
|
|
9
|
+
const { ensurePendingPairing, isPaired } = require('../utils/pairing-store');
|
|
10
|
+
const { ChannelAdapter, ChannelDeliveryManager } = require('../utils/channel-sdk');
|
|
11
|
+
const { DeliveryStore } = require('../utils/delivery-store');
|
|
9
12
|
|
|
10
13
|
const PID_FILE = path.join(os.homedir(), '.natureco', 'gateway.pid');
|
|
11
14
|
const LOG_FILE = path.join(os.homedir(), '.natureco', 'gateway.log');
|
|
15
|
+
const gatewayStartedAt = Date.now();
|
|
16
|
+
const gatewayDeliveryManager = new ChannelDeliveryManager({ store: new DeliveryStore() });
|
|
17
|
+
|
|
18
|
+
async function buildGatewayHealth(config = {}, manager = gatewayDeliveryManager) {
|
|
19
|
+
const configuredChannels = ['whatsapp', 'telegram', 'signal', 'discord', 'slack', 'irc', 'mattermost', 'imessage', 'sms']
|
|
20
|
+
.filter(name => Object.keys(config).some(key => key.toLowerCase().startsWith(name) && !!config[key]));
|
|
21
|
+
const adapterHealth = await manager.health();
|
|
22
|
+
const channelStates = Object.fromEntries(configuredChannels.map(name => {
|
|
23
|
+
const adapter = adapterHealth.find(item => item.channel === name);
|
|
24
|
+
return [name, adapter || { channel: name, state: 'configured', ok: null }];
|
|
25
|
+
}));
|
|
26
|
+
const metrics = manager.snapshotMetrics();
|
|
27
|
+
return {
|
|
28
|
+
ok: metrics.failed === 0 || metrics.delivered > 0,
|
|
29
|
+
status: metrics.failed > 0 ? 'degraded' : 'healthy',
|
|
30
|
+
uptimeSeconds: Math.floor((Date.now() - gatewayStartedAt) / 1000),
|
|
31
|
+
pid: process.pid,
|
|
32
|
+
channels: channelStates,
|
|
33
|
+
delivery: { ...metrics, deadLetters: manager.deadLetters.length },
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function registerGatewayDeliveryAdapters(config, manager = gatewayDeliveryManager) {
|
|
39
|
+
const register = (name, send, health) => {
|
|
40
|
+
if (manager.adapters.has(name)) return;
|
|
41
|
+
manager.register(new ChannelAdapter({ name, send, health }));
|
|
42
|
+
};
|
|
43
|
+
register('whatsapp', async item => {
|
|
44
|
+
if (!global.whatsappSock) throw new Error('WhatsApp not connected');
|
|
45
|
+
const jid = `${String(item.target).replace(/[^\d]/g, '')}@s.whatsapp.net`;
|
|
46
|
+
return global.whatsappSock.sendMessage(jid, { text: String(item.payload.message) });
|
|
47
|
+
}, async () => ({ ok: !!global.whatsappSock }));
|
|
48
|
+
register('telegram', async item => {
|
|
49
|
+
if (!global.telegramBot) throw new Error('Telegram not connected');
|
|
50
|
+
return global.telegramBot.sendMessage(String(item.target), String(item.payload.message));
|
|
51
|
+
}, async () => ({ ok: !!global.telegramBot }));
|
|
52
|
+
register('signal', item => sendSignalMessage(config, item.target, item.payload.message), async () => ({ ok: !!global.signalProvider || !!config.signalHttpUrl }));
|
|
53
|
+
register('irc', async item => {
|
|
54
|
+
if (!global.ircClient?.isReady()) throw new Error('IRC not connected');
|
|
55
|
+
return sendIrcMessage(global.ircClient, item.target, item.payload.message);
|
|
56
|
+
}, async () => ({ ok: !!global.ircClient?.isReady() }));
|
|
57
|
+
register('mattermost', item => sendMattermostMessage(config, item.target, item.payload.message), async () => ({ ok: !!global.mattermostProvider }));
|
|
58
|
+
register('imessage', async item => {
|
|
59
|
+
if (!global.imessageProvider) throw new Error('iMessage not connected');
|
|
60
|
+
return sendImessage(global.imessageProvider.imsgPath, item.target, item.payload.message);
|
|
61
|
+
}, async () => ({ ok: !!global.imessageProvider }));
|
|
62
|
+
register('sms', item => sendSmsMessage(config, item.target, item.payload.message), async () => ({ ok: !!global.smsProvider }));
|
|
63
|
+
register('discord', async item => {
|
|
64
|
+
if (!global.discordClient) throw new Error('Discord not connected');
|
|
65
|
+
const channel = await global.discordClient.channels.fetch(String(item.target));
|
|
66
|
+
return channel.send(String(item.payload.message));
|
|
67
|
+
}, async () => ({ ok: !!global.discordClient }));
|
|
68
|
+
register('slack', async item => {
|
|
69
|
+
if (!global.slackClient) throw new Error('Slack not connected');
|
|
70
|
+
return global.slackClient.chat.postMessage({ channel: String(item.target), text: String(item.payload.message) });
|
|
71
|
+
}, async () => ({ ok: !!global.slackClient }));
|
|
72
|
+
return manager;
|
|
73
|
+
}
|
|
12
74
|
|
|
13
75
|
// `https` is used in the webhook delivery path (~line 570). The require was
|
|
14
76
|
// missing; the module only worked because Node's CommonJS cache happens to
|
|
@@ -408,9 +470,14 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
408
470
|
// Log incoming number
|
|
409
471
|
log('whatsapp', `Incoming from: +${sender}, allowed: ${JSON.stringify(allowedNumbers)}`, 'gray');
|
|
410
472
|
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
|
|
473
|
+
// Pairing is the default. The owner's own LID conversation is trusted;
|
|
474
|
+
// every other sender must be allowlisted or explicitly paired.
|
|
475
|
+
const ownConversation = msg.key.fromMe && isLID;
|
|
476
|
+
const gate = ownConversation
|
|
477
|
+
? { allowed: true, trusted: true, reason: 'owner' }
|
|
478
|
+
: channelGate(config, 'whatsapp', sender);
|
|
479
|
+
if (!gate.allowed) {
|
|
480
|
+
log('whatsapp', `blocked message from +${sender} (${gate.reason})`, 'yellow');
|
|
414
481
|
continue;
|
|
415
482
|
}
|
|
416
483
|
|
|
@@ -437,8 +504,7 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
437
504
|
try {
|
|
438
505
|
// v5.47 TEK BEYIN: allow-list'teki gonderen (veya sahibin kendi cihazi) =
|
|
439
506
|
// guvenilir → terminaldekiyle ayni ajan. Aksi halde hafizasiz hafif yol.
|
|
440
|
-
const trusted =
|
|
441
|
-
(allowedNumbers.length > 0 && allowedNumbers.some(n => numberMatches(n, sender)));
|
|
507
|
+
const trusted = gate.trusted;
|
|
442
508
|
let reply = '';
|
|
443
509
|
|
|
444
510
|
if (trusted) {
|
|
@@ -516,8 +582,11 @@ async function startDiscordProvider(config) {
|
|
|
516
582
|
client.on(Events.MessageCreate, async (message) => {
|
|
517
583
|
if (message.author.bot) return;
|
|
518
584
|
const chatId = message.channel.id;
|
|
519
|
-
const
|
|
520
|
-
if (
|
|
585
|
+
const gate = channelGate(config, 'discord', String(message.author.id || chatId));
|
|
586
|
+
if (!gate.allowed) {
|
|
587
|
+
log('discord', `blocked message from ${message.author.id} (${gate.reason})`, 'yellow');
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
521
590
|
|
|
522
591
|
try {
|
|
523
592
|
const response = await callProviderForGateway(config, message.content, {
|
|
@@ -546,13 +615,19 @@ async function startDiscordProvider(config) {
|
|
|
546
615
|
// yetkisiz/yabancı gönderene kişisel hafıza sızabiliyordu. channelGate: (1) allow-list
|
|
547
616
|
// kuruluysa yetkisiz göndereni ENGELLE; (2) allow-list kurulu DEĞİLSE yanıt ver ama
|
|
548
617
|
// kişisel hafızayı ENJEKTE ETME (trusted=false) — böylece anonim kanaldan hafıza sızmaz.
|
|
549
|
-
function channelGate(config, channel, senderId) {
|
|
618
|
+
function channelGate(config, channel, senderId, pairing = { isPaired, ensurePendingPairing }) {
|
|
550
619
|
const allow = config[`${channel}AllowedChats`] || config[`${channel}AllowedNumbers`] || config[`${channel}AllowedUsers`] || [];
|
|
551
|
-
|
|
552
|
-
|
|
620
|
+
const allowlisted = Array.isArray(allow) && allow.map(String).includes(String(senderId));
|
|
621
|
+
if (allowlisted) return { allowed: true, trusted: true, reason: 'allowlist' };
|
|
622
|
+
|
|
623
|
+
const policy = config[`${channel}DmPolicy`] || 'pairing';
|
|
624
|
+
if (policy === 'open') return { allowed: true, trusted: false, reason: 'open' };
|
|
625
|
+
if (policy === 'disabled' || policy === 'allowlist') {
|
|
626
|
+
return { allowed: false, trusted: false, reason: policy };
|
|
553
627
|
}
|
|
554
|
-
|
|
555
|
-
|
|
628
|
+
if (pairing.isPaired(channel, senderId)) return { allowed: true, trusted: true, reason: 'paired' };
|
|
629
|
+
const pending = pairing.ensurePendingPairing(channel, senderId);
|
|
630
|
+
return { allowed: false, trusted: false, reason: 'pairing-required', pairingId: pending.id };
|
|
556
631
|
}
|
|
557
632
|
|
|
558
633
|
async function startSlackProvider(config) {
|
|
@@ -1500,7 +1575,7 @@ function startImessagePollingFallback(imsgPath, config) {
|
|
|
1500
1575
|
try {
|
|
1501
1576
|
const args = ['history', '--format', 'json', '--limit', '10'];
|
|
1502
1577
|
if (lastRowId) args.push('--since-rowid', String(lastRowId));
|
|
1503
|
-
const result =
|
|
1578
|
+
const result = execFileSync(imsgPath, args, {
|
|
1504
1579
|
encoding: 'utf-8', timeout: 10000, stdio: 'pipe',
|
|
1505
1580
|
});
|
|
1506
1581
|
const lines = result.trim().split('\n').filter(Boolean);
|
|
@@ -1519,6 +1594,13 @@ function startImessagePollingFallback(imsgPath, config) {
|
|
|
1519
1594
|
log('imessage', 'polling started (15s interval, history fallback)', 'green');
|
|
1520
1595
|
}
|
|
1521
1596
|
|
|
1597
|
+
function sendImessage(imsgPath, target, text) {
|
|
1598
|
+
return execFileSync(imsgPath, ['send', '--to', String(target), '--text', String(text)], {
|
|
1599
|
+
timeout: 15000,
|
|
1600
|
+
stdio: 'pipe',
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1522
1604
|
function findImsgBin() {
|
|
1523
1605
|
const config = require('../utils/config').getConfig();
|
|
1524
1606
|
if (config.imessageCliPath && fs.existsSync(config.imessageCliPath)) return config.imessageCliPath;
|
|
@@ -1601,9 +1683,7 @@ async function processImessageMessage(msg, config) {
|
|
|
1601
1683
|
}
|
|
1602
1684
|
|
|
1603
1685
|
if (reply) {
|
|
1604
|
-
|
|
1605
|
-
timeout: 15000, stdio: 'pipe',
|
|
1606
|
-
});
|
|
1686
|
+
sendImessage(global.imessageProvider.imsgPath, sender, reply);
|
|
1607
1687
|
log('imessage', `Reply sent to ${sender} (${reply.length} chars)`, 'green');
|
|
1608
1688
|
|
|
1609
1689
|
// v5.6.40: Track outgoing message for loop prevention
|
|
@@ -1805,7 +1885,7 @@ function startHttpServer() {
|
|
|
1805
1885
|
const server = http.createServer(async (req, res) => {
|
|
1806
1886
|
// CORS headers
|
|
1807
1887
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
1808
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
1888
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
1809
1889
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
1810
1890
|
|
|
1811
1891
|
if (req.method === 'OPTIONS') {
|
|
@@ -1813,6 +1893,14 @@ function startHttpServer() {
|
|
|
1813
1893
|
res.end();
|
|
1814
1894
|
return;
|
|
1815
1895
|
}
|
|
1896
|
+
|
|
1897
|
+
if (req.method === 'GET' && (req.url === '/health' || req.url === '/metrics')) {
|
|
1898
|
+
const { getConfig } = require('../utils/config');
|
|
1899
|
+
const snapshot = await buildGatewayHealth(getConfig(), gatewayDeliveryManager);
|
|
1900
|
+
res.writeHead(snapshot.status === 'healthy' ? 200 : 503, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
1901
|
+
res.end(JSON.stringify(req.url === '/health' ? snapshot : snapshot.delivery));
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1816
1904
|
|
|
1817
1905
|
if (req.method === 'POST' && req.url === '/webhooks/sms') {
|
|
1818
1906
|
// Twilio SMS webhook handler
|
|
@@ -1857,6 +1945,23 @@ function startHttpServer() {
|
|
|
1857
1945
|
res.end(JSON.stringify({ error: 'Missing required fields: channel, target, message' }));
|
|
1858
1946
|
return;
|
|
1859
1947
|
}
|
|
1948
|
+
|
|
1949
|
+
const cfg = require('../utils/config').getConfig();
|
|
1950
|
+
registerGatewayDeliveryAdapters(cfg, gatewayDeliveryManager);
|
|
1951
|
+
if (gatewayDeliveryManager.adapters.has(channel)) {
|
|
1952
|
+
const queued = gatewayDeliveryManager.enqueue(channel, target, { message }, { idempotencyKey: req.headers['idempotency-key'] });
|
|
1953
|
+
if (queued.duplicate) {
|
|
1954
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1955
|
+
res.end(JSON.stringify({ success: true, duplicate: true, deliveryId: queued.id }));
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
const [delivery] = await gatewayDeliveryManager.drain();
|
|
1959
|
+
res.writeHead(delivery.ok ? 200 : 503, { 'Content-Type': 'application/json' });
|
|
1960
|
+
res.end(JSON.stringify(delivery.ok
|
|
1961
|
+
? { success: true, channel, target, deliveryId: delivery.id, attempts: delivery.attempts }
|
|
1962
|
+
: { success: false, channel, target, deliveryId: delivery.id, error: delivery.error }));
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1860
1965
|
|
|
1861
1966
|
if (channel === 'whatsapp') {
|
|
1862
1967
|
if (!global.whatsappSock) {
|
|
@@ -1938,9 +2043,7 @@ function startHttpServer() {
|
|
|
1938
2043
|
return;
|
|
1939
2044
|
}
|
|
1940
2045
|
try {
|
|
1941
|
-
|
|
1942
|
-
timeout: 15000, stdio: 'pipe',
|
|
1943
|
-
});
|
|
2046
|
+
sendImessage(global.imessageProvider.imsgPath, target, message);
|
|
1944
2047
|
log('http', `iMessage sent to ${target}`, 'green');
|
|
1945
2048
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1946
2049
|
res.end(JSON.stringify({ success: true, channel: 'imessage', target }));
|
|
@@ -2133,9 +2236,7 @@ function startCronJobs(config) {
|
|
|
2133
2236
|
log('cron', 'iMessage not connected, skipping', 'red');
|
|
2134
2237
|
return;
|
|
2135
2238
|
}
|
|
2136
|
-
|
|
2137
|
-
timeout: 15000, stdio: 'pipe',
|
|
2138
|
-
});
|
|
2239
|
+
sendImessage(global.imessageProvider.imsgPath, cronJob.target, reply);
|
|
2139
2240
|
log('cron', `Sent to iMessage: ${cronJob.target}`, 'green');
|
|
2140
2241
|
} else if (cronJob.action === 'sms') {
|
|
2141
2242
|
if (!global.smsProvider) {
|
|
@@ -2267,3 +2368,6 @@ if (require.main === module || process.argv.includes('--gateway-worker')) {
|
|
|
2267
2368
|
module.exports = gatewayServer;
|
|
2268
2369
|
// v5.43: test için — kanal gönderen doğrulaması + hafıza izolasyonu (Madde 7)
|
|
2269
2370
|
module.exports.channelGate = channelGate;
|
|
2371
|
+
module.exports.buildGatewayHealth = buildGatewayHealth;
|
|
2372
|
+
module.exports.gatewayDeliveryManager = gatewayDeliveryManager;
|
|
2373
|
+
module.exports.registerGatewayDeliveryAdapters = registerGatewayDeliveryAdapters;
|
package/src/commands/git.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
1
|
+
const { execSync, execFileSync } = require('child_process');
|
|
2
2
|
const inquirer = require('../utils/inquirer-wrapper');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const { getApiKey } = require('../utils/config');
|
|
@@ -111,7 +111,7 @@ async function gitCommit() {
|
|
|
111
111
|
|
|
112
112
|
if (answer.confirm) {
|
|
113
113
|
try {
|
|
114
|
-
|
|
114
|
+
execFileSync('git', ['commit', '-m', commitMessage], { stdio: 'inherit' });
|
|
115
115
|
console.log(chalk.green('\n✅ Committed successfully\n'));
|
|
116
116
|
} catch (err) {
|
|
117
117
|
console.log(chalk.red('\n❌ Commit failed\n'));
|
package/src/commands/pairing.js
CHANGED
|
@@ -1,25 +1,5 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
|
|
6
|
-
const PAIRINGS_FILE = path.join(os.homedir(), '.natureco', 'pairings.json');
|
|
7
|
-
|
|
8
|
-
function loadPairings() {
|
|
9
|
-
if (!fs.existsSync(PAIRINGS_FILE)) return [];
|
|
10
|
-
try { return JSON.parse(fs.readFileSync(PAIRINGS_FILE, 'utf8')); }
|
|
11
|
-
catch { return []; }
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function savePairings(pairings) {
|
|
15
|
-
const dir = path.dirname(PAIRINGS_FILE);
|
|
16
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
-
fs.writeFileSync(PAIRINGS_FILE, JSON.stringify(pairings, null, 2), 'utf8');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function genId() {
|
|
21
|
-
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8).toUpperCase();
|
|
22
|
-
}
|
|
2
|
+
const { loadPairings, savePairings, genCode } = require('../utils/pairing-store');
|
|
23
3
|
|
|
24
4
|
function pairing(args) {
|
|
25
5
|
const [action, ...params] = args || [];
|
|
@@ -84,7 +64,7 @@ function cmdReject(id) {
|
|
|
84
64
|
}
|
|
85
65
|
|
|
86
66
|
function cmdGenerate() {
|
|
87
|
-
const code =
|
|
67
|
+
const code = genCode();
|
|
88
68
|
const pairings = loadPairings();
|
|
89
69
|
|
|
90
70
|
const entry = {
|