nothumanallowed 6.2.1 → 6.3.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/package.json +1 -1
- package/src/cli.mjs +17 -0
- package/src/commands/chat.mjs +22 -2
- package/src/commands/ui.mjs +58 -0
- package/src/commands/voice.mjs +13 -0
- package/src/config.mjs +31 -0
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +117 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.1",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents for security, code, DevOps, data & daily ops. Per-agent memory, Telegram + Discord auto-responder, proactive intelligence daemon, voice chat, plugin system.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.mjs
CHANGED
|
@@ -414,6 +414,23 @@ function cmdConfig(args) {
|
|
|
414
414
|
console.log(` Deadlines: ${proactive.deadlines !== false ? G + 'on' : D + 'off'}${NC}`);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
const profile = config.profile;
|
|
418
|
+
if (profile) {
|
|
419
|
+
console.log(`\n ${C}User Profile${NC}`);
|
|
420
|
+
if (profile.name) console.log(` Name: ${W}${profile.name}${NC}`);
|
|
421
|
+
if (profile.email) console.log(` Email: ${D}${profile.email}${NC}`);
|
|
422
|
+
if (profile.homeAddress) console.log(` Home: ${W}${profile.homeAddress}${NC}`);
|
|
423
|
+
if (profile.workAddress) console.log(` Work: ${W}${profile.workAddress}${NC}`);
|
|
424
|
+
if (profile.city) console.log(` City: ${W}${profile.city}${NC}`);
|
|
425
|
+
if (profile.company) console.log(` Company: ${D}${profile.company}${NC}`);
|
|
426
|
+
if (profile.role) console.log(` Role: ${D}${profile.role}${NC}`);
|
|
427
|
+
if (!profile.name && !profile.homeAddress) {
|
|
428
|
+
console.log(` ${D}(not set — agents won't know your personal info)${NC}`);
|
|
429
|
+
console.log(` ${D}Set with: nha config set name "Your Name"${NC}`);
|
|
430
|
+
console.log(` ${D} nha config set home "Via Roma 1, Modena"${NC}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
417
434
|
console.log('');
|
|
418
435
|
}
|
|
419
436
|
|
package/src/commands/chat.mjs
CHANGED
|
@@ -485,7 +485,7 @@ function parseActions(text) {
|
|
|
485
485
|
|
|
486
486
|
// ── System Prompt Builder ────────────────────────────────────────────────────
|
|
487
487
|
|
|
488
|
-
function buildSystemPrompt(initialContext) {
|
|
488
|
+
function buildSystemPrompt(initialContext, config) {
|
|
489
489
|
const today = new Date().toISOString().split('T')[0];
|
|
490
490
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
491
491
|
|
|
@@ -498,6 +498,26 @@ function buildSystemPrompt(initialContext) {
|
|
|
498
498
|
`Be concise, helpful, and proactive. When presenting data, format it clearly. ` +
|
|
499
499
|
`Never output raw JSON to the user — always wrap results in natural language.`;
|
|
500
500
|
|
|
501
|
+
// Inject user profile if configured
|
|
502
|
+
const profile = config?.profile;
|
|
503
|
+
if (profile) {
|
|
504
|
+
const fields = [];
|
|
505
|
+
if (profile.name) fields.push(`Name: ${profile.name}`);
|
|
506
|
+
if (profile.email) fields.push(`Email: ${profile.email}`);
|
|
507
|
+
if (profile.phone) fields.push(`Phone: ${profile.phone}`);
|
|
508
|
+
if (profile.homeAddress) fields.push(`Home address: ${profile.homeAddress}`);
|
|
509
|
+
if (profile.workAddress) fields.push(`Work address: ${profile.workAddress}`);
|
|
510
|
+
if (profile.city) fields.push(`City: ${profile.city}`);
|
|
511
|
+
if (profile.country) fields.push(`Country: ${profile.country}`);
|
|
512
|
+
if (profile.company) fields.push(`Company: ${profile.company}`);
|
|
513
|
+
if (profile.role) fields.push(`Role: ${profile.role}`);
|
|
514
|
+
if (profile.notes) fields.push(`Notes: ${profile.notes}`);
|
|
515
|
+
|
|
516
|
+
if (fields.length > 0) {
|
|
517
|
+
prompt += `\n\n--- USER PROFILE (use this for personal references like "my home", "my city", etc.) ---\n${fields.join('\n')}`;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
501
521
|
if (initialContext) {
|
|
502
522
|
prompt += `\n\n--- CURRENT CONTEXT (fetched at session start) ---\n${initialContext}`;
|
|
503
523
|
}
|
|
@@ -695,7 +715,7 @@ export async function cmdChat(args) {
|
|
|
695
715
|
if (history.length > 0) {
|
|
696
716
|
ok(`Loaded ${Math.floor(history.length / 2)} previous conversation turns from memory.`);
|
|
697
717
|
}
|
|
698
|
-
const systemPrompt = buildSystemPrompt(initialContext);
|
|
718
|
+
const systemPrompt = buildSystemPrompt(initialContext, config);
|
|
699
719
|
|
|
700
720
|
// ── Graceful exit ───────────────────────────────────────────────────────
|
|
701
721
|
rl.on('close', () => {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -463,6 +463,26 @@ export async function cmdUI(args) {
|
|
|
463
463
|
`You help the user manage their emails, calendar, and tasks through natural conversation. ` +
|
|
464
464
|
`Be concise, helpful, and proactive. When presenting data, format it clearly. ` +
|
|
465
465
|
`Never output raw JSON to the user.`;
|
|
466
|
+
|
|
467
|
+
// Inject user profile
|
|
468
|
+
const profile = config?.profile;
|
|
469
|
+
if (profile) {
|
|
470
|
+
const fields = [];
|
|
471
|
+
if (profile.name) fields.push(`Name: ${profile.name}`);
|
|
472
|
+
if (profile.email) fields.push(`Email: ${profile.email}`);
|
|
473
|
+
if (profile.phone) fields.push(`Phone: ${profile.phone}`);
|
|
474
|
+
if (profile.homeAddress) fields.push(`Home address: ${profile.homeAddress}`);
|
|
475
|
+
if (profile.workAddress) fields.push(`Work address: ${profile.workAddress}`);
|
|
476
|
+
if (profile.city) fields.push(`City: ${profile.city}`);
|
|
477
|
+
if (profile.country) fields.push(`Country: ${profile.country}`);
|
|
478
|
+
if (profile.company) fields.push(`Company: ${profile.company}`);
|
|
479
|
+
if (profile.role) fields.push(`Role: ${profile.role}`);
|
|
480
|
+
if (profile.notes) fields.push(`Notes: ${profile.notes}`);
|
|
481
|
+
if (fields.length > 0) {
|
|
482
|
+
prompt += `\n\n--- USER PROFILE (use for "my home", "my city", "my office", etc.) ---\n${fields.join('\n')}`;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
466
486
|
return prompt;
|
|
467
487
|
})();
|
|
468
488
|
|
|
@@ -539,6 +559,44 @@ export async function cmdUI(args) {
|
|
|
539
559
|
return;
|
|
540
560
|
}
|
|
541
561
|
|
|
562
|
+
// POST /api/config — save a config value from the web UI
|
|
563
|
+
if (method === 'POST' && pathname === '/api/config') {
|
|
564
|
+
const body = await parseBody(req);
|
|
565
|
+
if (!body.key) {
|
|
566
|
+
sendJSON(res, 400, { error: 'key required' });
|
|
567
|
+
logRequest(method, pathname, 400, Date.now() - start);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const { setConfigValue, loadConfig: reloadConfig } = await import('../config.mjs');
|
|
571
|
+
const success = setConfigValue(body.key, body.value || '');
|
|
572
|
+
if (success) {
|
|
573
|
+
// Reload config in memory so the chat system picks up changes immediately
|
|
574
|
+
const newConfig = reloadConfig();
|
|
575
|
+
Object.assign(config, newConfig);
|
|
576
|
+
}
|
|
577
|
+
sendJSON(res, 200, { ok: success, key: body.key });
|
|
578
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// GET /api/config — read config values for settings UI
|
|
583
|
+
if (method === 'GET' && pathname === '/api/config') {
|
|
584
|
+
// Return non-sensitive config for the settings form
|
|
585
|
+
sendJSON(res, 200, {
|
|
586
|
+
profile: config.profile || {},
|
|
587
|
+
provider: config.llm?.provider || '',
|
|
588
|
+
model: config.llm?.model || '',
|
|
589
|
+
hasApiKey: !!config.llm?.apiKey,
|
|
590
|
+
planTime: config.ops?.planTime || '07:00',
|
|
591
|
+
summaryTime: config.ops?.summaryTime || '18:00',
|
|
592
|
+
meetingAlert: config.ops?.meetingAlertMinutes || 30,
|
|
593
|
+
hasTelegram: !!config.responder?.telegram?.token,
|
|
594
|
+
hasDiscord: !!config.responder?.discord?.token,
|
|
595
|
+
});
|
|
596
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
542
600
|
// GET /api/emails
|
|
543
601
|
if (method === 'GET' && pathname === '/api/emails') {
|
|
544
602
|
try {
|
package/src/commands/voice.mjs
CHANGED
|
@@ -747,6 +747,19 @@ export async function cmdVoice(args) {
|
|
|
747
747
|
`You help the user manage their emails, calendar, and tasks through voice commands. ` +
|
|
748
748
|
`Keep responses SHORT and SPOKEN-FRIENDLY — 2-3 sentences max. ` +
|
|
749
749
|
`Avoid markdown formatting, bullet points, numbered lists. Use natural spoken language.`;
|
|
750
|
+
|
|
751
|
+
const profile = config?.profile;
|
|
752
|
+
if (profile) {
|
|
753
|
+
const fields = [];
|
|
754
|
+
if (profile.name) fields.push(`Name: ${profile.name}`);
|
|
755
|
+
if (profile.homeAddress) fields.push(`Home: ${profile.homeAddress}`);
|
|
756
|
+
if (profile.workAddress) fields.push(`Work: ${profile.workAddress}`);
|
|
757
|
+
if (profile.city) fields.push(`City: ${profile.city}`);
|
|
758
|
+
if (fields.length > 0) {
|
|
759
|
+
prompt += `\n\n[USER PROFILE] ${fields.join('. ')}.`;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
750
763
|
return prompt;
|
|
751
764
|
})();
|
|
752
765
|
|
package/src/config.mjs
CHANGED
|
@@ -100,6 +100,20 @@ const DEFAULT_CONFIG = {
|
|
|
100
100
|
speechSynthesis: true,
|
|
101
101
|
language: '',
|
|
102
102
|
},
|
|
103
|
+
profile: {
|
|
104
|
+
name: '',
|
|
105
|
+
email: '',
|
|
106
|
+
phone: '',
|
|
107
|
+
homeAddress: '',
|
|
108
|
+
workAddress: '',
|
|
109
|
+
city: '',
|
|
110
|
+
country: '',
|
|
111
|
+
timezone: '',
|
|
112
|
+
language: '',
|
|
113
|
+
company: '',
|
|
114
|
+
role: '',
|
|
115
|
+
notes: '',
|
|
116
|
+
},
|
|
103
117
|
};
|
|
104
118
|
|
|
105
119
|
/**
|
|
@@ -228,6 +242,23 @@ export function setConfigValue(key, value) {
|
|
|
228
242
|
'proactive-meeting': 'ops.proactive.meetingPrep',
|
|
229
243
|
'proactive-patterns': 'ops.proactive.patterns',
|
|
230
244
|
'proactive-deadlines': 'ops.proactive.deadlines',
|
|
245
|
+
'name': 'profile.name',
|
|
246
|
+
'my-name': 'profile.name',
|
|
247
|
+
'email': 'profile.email',
|
|
248
|
+
'my-email': 'profile.email',
|
|
249
|
+
'phone': 'profile.phone',
|
|
250
|
+
'my-phone': 'profile.phone',
|
|
251
|
+
'home': 'profile.homeAddress',
|
|
252
|
+
'home-address': 'profile.homeAddress',
|
|
253
|
+
'work': 'profile.workAddress',
|
|
254
|
+
'work-address': 'profile.workAddress',
|
|
255
|
+
'city': 'profile.city',
|
|
256
|
+
'my-city': 'profile.city',
|
|
257
|
+
'country': 'profile.country',
|
|
258
|
+
'company': 'profile.company',
|
|
259
|
+
'my-role': 'profile.role',
|
|
260
|
+
'role': 'profile.role',
|
|
261
|
+
'profile-notes': 'profile.notes',
|
|
231
262
|
};
|
|
232
263
|
|
|
233
264
|
const resolved = aliases[key] || key;
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '6.
|
|
8
|
+
export const VERSION = '6.3.1';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -194,7 +194,7 @@ function switchView(v) {
|
|
|
194
194
|
document.querySelectorAll('.nav-item').forEach(function(el){
|
|
195
195
|
if(el.dataset.view===v){el.classList.add('nav-item--active')}else{el.classList.remove('nav-item--active')}
|
|
196
196
|
});
|
|
197
|
-
var titles = {dashboard:'Dashboard',chat:'Chat',plan:'Daily Plan',tasks:'Tasks',emails:'Emails',calendar:'Calendar',agents:'Agents'};
|
|
197
|
+
var titles = {dashboard:'Dashboard',chat:'Chat',plan:'Daily Plan',tasks:'Tasks',emails:'Emails',calendar:'Calendar',agents:'Agents',settings:'Settings'};
|
|
198
198
|
document.getElementById('headerTitle').textContent = titles[v]||v;
|
|
199
199
|
closeSidebar();
|
|
200
200
|
render();
|
|
@@ -257,6 +257,7 @@ function render(){
|
|
|
257
257
|
case 'emails':renderEmails(el);break;
|
|
258
258
|
case 'calendar':renderCalendar(el);break;
|
|
259
259
|
case 'agents':renderAgents(el);break;
|
|
260
|
+
case 'settings':renderSettings(el);break;
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
263
|
|
|
@@ -617,6 +618,117 @@ function openAgent(name,display){
|
|
|
617
618
|
function closeModal(){
|
|
618
619
|
document.getElementById('agentModal').classList.remove('modal-overlay--open');
|
|
619
620
|
}
|
|
621
|
+
|
|
622
|
+
// ---- SETTINGS ----
|
|
623
|
+
var settingsLoaded = false;
|
|
624
|
+
var settingsData = {};
|
|
625
|
+
|
|
626
|
+
function renderSettings(el) {
|
|
627
|
+
if (!settingsLoaded) {
|
|
628
|
+
el.innerHTML = '<div style="text-align:center;padding:40px"><div class="spinner"></div><div style="color:var(--dim)">Loading settings...</div></div>';
|
|
629
|
+
apiGet('/api/config').then(function(r) {
|
|
630
|
+
settingsData = r || {};
|
|
631
|
+
// Populate cache for fields
|
|
632
|
+
var cache = {};
|
|
633
|
+
var p = settingsData.profile || {};
|
|
634
|
+
if (p.name) cache['name'] = p.name;
|
|
635
|
+
if (p.email) cache['email'] = p.email;
|
|
636
|
+
if (p.phone) cache['phone'] = p.phone;
|
|
637
|
+
if (p.homeAddress) cache['home-address'] = p.homeAddress;
|
|
638
|
+
if (p.workAddress) cache['work-address'] = p.workAddress;
|
|
639
|
+
if (p.city) cache['city'] = p.city;
|
|
640
|
+
if (p.country) cache['country'] = p.country;
|
|
641
|
+
if (p.company) cache['company'] = p.company;
|
|
642
|
+
if (p.role) cache['role'] = p.role;
|
|
643
|
+
if (p.notes) cache['profile-notes'] = p.notes;
|
|
644
|
+
if (settingsData.provider) cache['provider'] = settingsData.provider;
|
|
645
|
+
if (settingsData.model) cache['model'] = settingsData.model;
|
|
646
|
+
if (settingsData.planTime) cache['plan-time'] = settingsData.planTime;
|
|
647
|
+
if (settingsData.summaryTime) cache['summary-time'] = settingsData.summaryTime;
|
|
648
|
+
if (settingsData.meetingAlert) cache['meeting-alert'] = String(settingsData.meetingAlert);
|
|
649
|
+
localStorage.setItem('nha_config_cache', JSON.stringify(cache));
|
|
650
|
+
settingsLoaded = true;
|
|
651
|
+
renderSettings(el);
|
|
652
|
+
});
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
el.innerHTML = '<div style="max-width:600px;margin:0 auto">' +
|
|
657
|
+
'<div class="card" style="margin-bottom:16px">' +
|
|
658
|
+
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:12px">User Profile</div>' +
|
|
659
|
+
'<div style="font-size:11px;color:var(--dim);margin-bottom:12px">Agents use this info when you say "my home", "my city", etc.</div>' +
|
|
660
|
+
settingsField('name', 'Name', 'Your full name') +
|
|
661
|
+
settingsField('email', 'Email', 'your@email.com') +
|
|
662
|
+
settingsField('phone', 'Phone', '+39 ...') +
|
|
663
|
+
settingsField('home-address', 'Home Address', 'Via Roma 1, Modena') +
|
|
664
|
+
settingsField('work-address', 'Work Address', 'Via Ufficio 10, Modena') +
|
|
665
|
+
settingsField('city', 'City', 'Modena') +
|
|
666
|
+
settingsField('country', 'Country', 'Italy') +
|
|
667
|
+
settingsField('company', 'Company', 'Your company') +
|
|
668
|
+
settingsField('role', 'Role', 'Your role') +
|
|
669
|
+
settingsField('profile-notes', 'Notes', 'Anything agents should know about you') +
|
|
670
|
+
'</div>' +
|
|
671
|
+
'<div class="card" style="margin-bottom:16px">' +
|
|
672
|
+
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:12px">LLM Provider</div>' +
|
|
673
|
+
settingsField('provider', 'Provider', 'anthropic / openai / gemini / deepseek / grok / mistral') +
|
|
674
|
+
settingsField('key', 'API Key', 'sk-ant-api03-...', true) +
|
|
675
|
+
settingsField('model', 'Model', 'Leave empty for default') +
|
|
676
|
+
'</div>' +
|
|
677
|
+
'<div class="card" style="margin-bottom:16px">' +
|
|
678
|
+
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:12px">Message Responder</div>' +
|
|
679
|
+
settingsField('telegram-bot-token', 'Telegram Bot Token', 'Get from @BotFather', true) +
|
|
680
|
+
settingsField('discord-bot-token', 'Discord Bot Token', 'From Discord Developer Portal', true) +
|
|
681
|
+
'</div>' +
|
|
682
|
+
'<div class="card" style="margin-bottom:16px">' +
|
|
683
|
+
'<div class="card__title" style="color:var(--green);font-size:14px;margin-bottom:12px">Daily Operations</div>' +
|
|
684
|
+
settingsField('plan-time', 'Daily Plan Time', '07:00') +
|
|
685
|
+
settingsField('summary-time', 'Summary Time', '18:00') +
|
|
686
|
+
settingsField('meeting-alert', 'Meeting Alert (minutes)', '30') +
|
|
687
|
+
'</div>' +
|
|
688
|
+
'<div style="text-align:center;margin-top:16px"><span style="color:var(--dim);font-size:10px">Changes are saved automatically when you press Enter or click outside a field.</span></div>' +
|
|
689
|
+
'</div>';
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function settingsField(key, label, placeholder, isSecret) {
|
|
693
|
+
var currentVal = '';
|
|
694
|
+
try {
|
|
695
|
+
var cfg = JSON.parse(localStorage.getItem('nha_config_cache') || '{}');
|
|
696
|
+
currentVal = cfg[key] || '';
|
|
697
|
+
} catch(e) {}
|
|
698
|
+
|
|
699
|
+
return '<div style="margin-bottom:10px">' +
|
|
700
|
+
'<label style="display:block;font-size:11px;color:var(--dim);margin-bottom:3px">' + esc(label) + '</label>' +
|
|
701
|
+
'<input type="' + (isSecret ? 'password' : 'text') + '" ' +
|
|
702
|
+
'value="' + esc(currentVal) + '" ' +
|
|
703
|
+
'placeholder="' + esc(placeholder) + '" ' +
|
|
704
|
+
'style="width:100%;padding:8px 12px;font-size:13px" ' +
|
|
705
|
+
'data-config-key="' + esc(key) + '" ' +
|
|
706
|
+
'onchange="saveSettingsField(this)" ' +
|
|
707
|
+
'onkeydown="if(event.key===\\x27Enter\\x27)this.blur()">' +
|
|
708
|
+
'</div>';
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function saveSettingsField(input) {
|
|
712
|
+
var key = input.dataset.configKey;
|
|
713
|
+
var val = input.value.trim();
|
|
714
|
+
if (!key) return;
|
|
715
|
+
apiPost('/api/config', { key: key, value: val }).then(function(r) {
|
|
716
|
+
if (r && r.ok) {
|
|
717
|
+
input.style.borderColor = 'var(--green)';
|
|
718
|
+
setTimeout(function() { input.style.borderColor = 'var(--border)'; }, 1500);
|
|
719
|
+
// Update local cache
|
|
720
|
+
try {
|
|
721
|
+
var cfg = JSON.parse(localStorage.getItem('nha_config_cache') || '{}');
|
|
722
|
+
cfg[key] = val;
|
|
723
|
+
localStorage.setItem('nha_config_cache', JSON.stringify(cfg));
|
|
724
|
+
} catch(e) {}
|
|
725
|
+
} else {
|
|
726
|
+
input.style.borderColor = 'var(--red)';
|
|
727
|
+
setTimeout(function() { input.style.borderColor = 'var(--border)'; }, 2000);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
620
732
|
var attachedFileContent = null;
|
|
621
733
|
var attachedFileName = null;
|
|
622
734
|
|
|
@@ -883,6 +995,10 @@ init();
|
|
|
883
995
|
<div class="sidebar__label">AI</div>
|
|
884
996
|
<div class="nav-item" data-view="agents" onclick="switchView('agents')"><span class="nav-item__icon">⚙</span> Agents</div>
|
|
885
997
|
</div>
|
|
998
|
+
<div class="sidebar__section">
|
|
999
|
+
<div class="sidebar__label">CONFIG</div>
|
|
1000
|
+
<div class="nav-item" data-view="settings" onclick="switchView('settings')"><span class="nav-item__icon">⚙</span> Settings</div>
|
|
1001
|
+
</div>
|
|
886
1002
|
</nav>
|
|
887
1003
|
|
|
888
1004
|
<div class="header">
|