janito 1.7.0__py3-none-any.whl → 1.8.0__py3-none-any.whl
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.
- janito/__init__.py +1 -1
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -2
- janito/agent/conversation.py +70 -27
- janito/agent/conversation_api.py +104 -4
- janito/agent/conversation_exceptions.py +6 -0
- janito/agent/conversation_tool_calls.py +17 -3
- janito/agent/event.py +24 -0
- janito/agent/event_dispatcher.py +24 -0
- janito/agent/event_handler_protocol.py +5 -0
- janito/agent/event_system.py +15 -0
- janito/agent/message_handler.py +4 -1
- janito/agent/message_handler_protocol.py +5 -0
- janito/agent/openai_client.py +5 -8
- janito/agent/openai_schema_generator.py +23 -4
- janito/agent/profile_manager.py +15 -83
- janito/agent/queued_message_handler.py +22 -3
- janito/agent/rich_message_handler.py +66 -72
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
- janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
- janito/agent/test_handler_protocols.py +47 -0
- janito/agent/tests/__init__.py +1 -0
- janito/agent/tool_base.py +1 -1
- janito/agent/tool_executor.py +109 -0
- janito/agent/tool_registry.py +3 -75
- janito/agent/tool_use_tracker.py +46 -0
- janito/agent/tools/__init__.py +8 -9
- janito/agent/tools/ask_user.py +19 -11
- janito/agent/tools/create_directory.py +43 -28
- janito/agent/tools/create_file.py +60 -29
- janito/agent/tools/dir_walk_utils.py +16 -0
- janito/agent/tools/fetch_url.py +10 -11
- janito/agent/tools/find_files.py +49 -32
- janito/agent/tools/get_lines.py +54 -18
- janito/agent/tools/memory.py +32 -52
- janito/agent/tools/move_file.py +72 -23
- janito/agent/tools/outline_file/__init__.py +85 -0
- janito/agent/tools/outline_file/formatting.py +20 -0
- janito/agent/tools/outline_file/markdown_outline.py +14 -0
- janito/agent/tools/outline_file/python_outline.py +71 -0
- janito/agent/tools/present_choices.py +62 -0
- janito/agent/tools/present_choices_test.py +18 -0
- janito/agent/tools/remove_directory.py +31 -26
- janito/agent/tools/remove_file.py +31 -13
- janito/agent/tools/replace_text_in_file.py +135 -36
- janito/agent/tools/run_bash_command.py +47 -50
- janito/agent/tools/run_powershell_command.py +52 -36
- janito/agent/tools/run_python_command.py +49 -29
- janito/agent/tools/search_outline.py +17 -0
- janito/agent/tools/search_text.py +208 -0
- janito/agent/tools/tools_utils.py +47 -4
- janito/agent/tools/utils.py +14 -15
- janito/agent/tools/validate_file_syntax.py +163 -0
- janito/cli/arg_parser.py +36 -4
- janito/cli/logging_setup.py +7 -2
- janito/cli/main.py +96 -2
- janito/cli/runner/_termweb_log_utils.py +17 -0
- janito/cli/runner/cli_main.py +119 -77
- janito/cli/runner/config.py +2 -2
- janito/cli/termweb_starter.py +73 -0
- janito/cli_chat_shell/chat_loop.py +42 -7
- janito/cli_chat_shell/chat_state.py +1 -1
- janito/cli_chat_shell/chat_ui.py +0 -1
- janito/cli_chat_shell/commands/__init__.py +15 -6
- janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
- janito/cli_chat_shell/commands/lang.py +16 -0
- janito/cli_chat_shell/commands/prompt.py +42 -0
- janito/cli_chat_shell/commands/session_control.py +36 -1
- janito/cli_chat_shell/commands/termweb_log.py +86 -0
- janito/cli_chat_shell/commands/utility.py +5 -2
- janito/cli_chat_shell/commands/verbose.py +29 -0
- janito/cli_chat_shell/session_manager.py +9 -1
- janito/cli_chat_shell/shell_command_completer.py +20 -0
- janito/cli_chat_shell/ui.py +110 -99
- janito/i18n/__init__.py +35 -0
- janito/i18n/messages.py +23 -0
- janito/i18n/pt.py +46 -0
- janito/rich_utils.py +43 -43
- janito/termweb/app.py +95 -0
- janito/termweb/static/editor.html +238 -0
- janito/termweb/static/editor.html.bak +238 -0
- janito/termweb/static/explorer.html.bak +59 -0
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +55 -0
- janito/termweb/static/index.html.bak +55 -0
- janito/termweb/static/index.html.bak.bak +175 -0
- janito/termweb/static/landing.html.bak +36 -0
- janito/termweb/static/termicon.svg +1 -0
- janito/termweb/static/termweb.css +235 -0
- janito/termweb/static/termweb.css.bak +286 -0
- janito/termweb/static/termweb.js +187 -0
- janito/termweb/static/termweb.js.bak +187 -0
- janito/termweb/static/termweb.js.bak.bak +157 -0
- janito/termweb/static/termweb_quickopen.js +135 -0
- janito/termweb/static/termweb_quickopen.js.bak +125 -0
- janito/web/app.py +4 -4
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/METADATA +58 -25
- janito-1.8.0.dist-info/RECORD +127 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
- janito/agent/templates/profiles/system_prompt_template_base.toml +0 -76
- janito/agent/templates/profiles/system_prompt_template_default.toml +0 -3
- janito/agent/templates/profiles/system_prompt_template_technical.toml +0 -13
- janito/agent/tests/test_prompt_toml.py +0 -61
- janito/agent/tool_registry_core.py +0 -2
- janito/agent/tools/get_file_outline.py +0 -146
- janito/agent/tools/py_compile_file.py +0 -40
- janito/agent/tools/replace_file.py +0 -51
- janito/agent/tools/search_files.py +0 -65
- janito/cli/runner/scan.py +0 -57
- janito/cli_chat_shell/commands/system.py +0 -73
- janito-1.7.0.dist-info/RECORD +0 -89
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
// Directory/File browser logic for explorer with right-side preview and URL sync (using /path)
|
2
|
+
let explorerView = localStorage.getItem('explorerView') || 'list';
|
3
|
+
let currentExplorerPath = '.';
|
4
|
+
|
5
|
+
function getParentPath(path) {
|
6
|
+
// Normalize slashes
|
7
|
+
path = (path || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
8
|
+
if (!path || path === '.' || path === '') return '.';
|
9
|
+
const parts = path.split('/');
|
10
|
+
if (parts.length <= 1) return '.';
|
11
|
+
parts.pop();
|
12
|
+
const parent = parts.join('/');
|
13
|
+
return parent === '' ? '.' : parent;
|
14
|
+
}
|
15
|
+
|
16
|
+
|
17
|
+
function setExplorerView(view) {
|
18
|
+
explorerView = view;
|
19
|
+
localStorage.setItem('explorerView', view);
|
20
|
+
document.getElementById('view-list').classList.toggle('active', view === 'list');
|
21
|
+
document.getElementById('view-icons').classList.toggle('active', view === 'icons');
|
22
|
+
}
|
23
|
+
|
24
|
+
function normalizeExplorerPath(path) {
|
25
|
+
if (!path || path === '/' || path === '' || path === '.') return '.';
|
26
|
+
return path.replace(/^\/+|\/+$/g, '');
|
27
|
+
}
|
28
|
+
|
29
|
+
function updateExplorerUrl(path, push=true) {
|
30
|
+
let url = '/';
|
31
|
+
if (path && path !== '.' && path !== '/') {
|
32
|
+
url = '/' + path.replace(/^\/+|\/+$/g, '');
|
33
|
+
}
|
34
|
+
if (push) {
|
35
|
+
console.log('[DEBUG] updateExplorerUrl: pushing url', url, 'for path', path);
|
36
|
+
window.history.pushState({ explorerPath: path }, '', url);
|
37
|
+
} else {
|
38
|
+
console.log('[DEBUG] updateExplorerUrl: not pushing, url would be', url, 'for path', path);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
function renderExplorer(path, pushUrl=true) {
|
43
|
+
currentExplorerPath = normalizeExplorerPath(path);
|
44
|
+
console.log('[DEBUG] renderExplorer: path', path, 'normalized to', currentExplorerPath, 'pushUrl:', pushUrl);
|
45
|
+
fetch(`/api/explorer/${encodeURIComponent(currentExplorerPath)}`)
|
46
|
+
.then(resp => resp.json())
|
47
|
+
.then(data => {
|
48
|
+
const main = document.getElementById('explorer-main');
|
49
|
+
if (!main) return;
|
50
|
+
if (data.error) {
|
51
|
+
main.innerHTML = `<div class='error'>${data.error}</div>`;
|
52
|
+
return;
|
53
|
+
}
|
54
|
+
if (data.type === 'dir') {
|
55
|
+
let html = `<h3>Directory: ${data.path}</h3>`;
|
56
|
+
if (explorerView === 'list') {
|
57
|
+
html += `<ul class='explorer-list'>`;
|
58
|
+
if (data.path !== '.') {
|
59
|
+
const parent = getParentPath(data.path);
|
60
|
+
html += `<li><a href='#' data-path='${parent}' class='explorer-link'>(.. parent)</a></li>`;
|
61
|
+
}
|
62
|
+
for (const entry of data.entries) {
|
63
|
+
const entryPath = data.path === '.' ? entry.name : data.path + '/' + entry.name;
|
64
|
+
if (entry.is_dir) {
|
65
|
+
html += `<li><a href='#' data-path='${entryPath}' class='explorer-link'>📁 ${entry.name}</a></li>`;
|
66
|
+
} else {
|
67
|
+
html += `<li><a href='#' data-path='${entryPath}' class='explorer-link file-link'>📄 ${entry.name}</a></li>`;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
html += '</ul>';
|
71
|
+
} else {
|
72
|
+
html += `<div class='explorer-icons'>`;
|
73
|
+
if (data.path !== '.') {
|
74
|
+
const parent = getParentPath(data.path);
|
75
|
+
html += `<div class='explorer-icon'><a href='#' data-path='${parent}' class='explorer-link' title='Parent'>(..)</a></div>`;
|
76
|
+
}
|
77
|
+
for (const entry of data.entries) {
|
78
|
+
const entryPath = data.path === '.' ? entry.name : data.path + '/' + entry.name;
|
79
|
+
if (entry.is_dir) {
|
80
|
+
html += `<div class='explorer-icon'><a href='#' data-path='${entryPath}' class='explorer-link' title='${entry.name}'>📁<br>${entry.name}</a></div>`;
|
81
|
+
} else {
|
82
|
+
html += `<div class='explorer-icon'><a href='#' data-path='${entryPath}' class='explorer-link file-link' title='${entry.name}'>📄<br>${entry.name}</a></div>`;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
html += '</div>';
|
86
|
+
}
|
87
|
+
main.innerHTML = html;
|
88
|
+
// Clear preview panel when changing directories
|
89
|
+
const preview = document.getElementById('explorer-preview');
|
90
|
+
if (preview) preview.innerHTML = '';
|
91
|
+
if (pushUrl) updateExplorerUrl(currentExplorerPath, true);
|
92
|
+
}
|
93
|
+
// Attach click handlers
|
94
|
+
document.querySelectorAll('.explorer-link').forEach(link => {
|
95
|
+
link.onclick = function(e) {
|
96
|
+
e.preventDefault();
|
97
|
+
const p = this.getAttribute('data-path');
|
98
|
+
// If file, show preview; if dir, update explorer
|
99
|
+
if (this.classList.contains('file-link')) {
|
100
|
+
const preview = document.getElementById('explorer-preview');
|
101
|
+
if (preview) {
|
102
|
+
preview.innerHTML = `<div class='spinner' style='display:inline-block;vertical-align:middle;'></div> <span style='vertical-align:middle;'>Loading file...</span>`;
|
103
|
+
}
|
104
|
+
fetch(`/api/explorer/${encodeURIComponent(p)}`)
|
105
|
+
.then(resp => resp.json())
|
106
|
+
.then(fileData => {
|
107
|
+
if (preview && fileData.type === 'file') {
|
108
|
+
preview.innerHTML = `<h3>File: ${fileData.path}</h3><pre class='explorer-file'>${escapeHtml(fileData.content)}</pre>`;
|
109
|
+
}
|
110
|
+
});
|
111
|
+
} else {
|
112
|
+
console.log('[DEBUG] explorer-link click: navigating to dir', p);
|
113
|
+
renderExplorer(p, true);
|
114
|
+
}
|
115
|
+
};
|
116
|
+
});
|
117
|
+
});
|
118
|
+
}
|
119
|
+
|
120
|
+
function escapeHtml(text) {
|
121
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
122
|
+
return text.replace(/[&<>"']'/g, m => map[m]);
|
123
|
+
}
|
124
|
+
|
125
|
+
function getExplorerPathFromLocation() {
|
126
|
+
let path = window.location.pathname;
|
127
|
+
// Remove leading slash
|
128
|
+
if (path.startsWith('/')) path = path.slice(1);
|
129
|
+
// If empty, root, or dot, treat as '.'
|
130
|
+
if (!path || path === '' || path === '.' || path === '/') return '.';
|
131
|
+
// Remove trailing slashes
|
132
|
+
path = path.replace(/\/+$/g, '');
|
133
|
+
console.log('[DEBUG] getExplorerPathFromLocation: extracted', path, 'from', window.location.pathname);
|
134
|
+
return path;
|
135
|
+
}
|
136
|
+
|
137
|
+
document.addEventListener('DOMContentLoaded', function() {
|
138
|
+
const initialPath = getExplorerPathFromLocation();
|
139
|
+
console.log('[DEBUG] DOMContentLoaded: initialPath', initialPath);
|
140
|
+
currentExplorerPath = initialPath; // Ensure currentExplorerPath matches URL
|
141
|
+
setExplorerView(localStorage.getItem('explorerView') || 'list');
|
142
|
+
renderExplorer(initialPath, false);
|
143
|
+
document.getElementById('view-list').onclick = function() {
|
144
|
+
setExplorerView('list');
|
145
|
+
renderExplorer(currentExplorerPath, false);
|
146
|
+
};
|
147
|
+
document.getElementById('view-icons').onclick = function() {
|
148
|
+
setExplorerView('icons');
|
149
|
+
renderExplorer(currentExplorerPath, false);
|
150
|
+
};
|
151
|
+
});
|
152
|
+
|
153
|
+
window.addEventListener('popstate', function(event) {
|
154
|
+
const path = (event.state && event.state.explorerPath) || getExplorerPathFromLocation();
|
155
|
+
console.log('[DEBUG] popstate: path', path, 'event.state:', event.state);
|
156
|
+
renderExplorer(path, false);
|
157
|
+
});
|
@@ -0,0 +1,135 @@
|
|
1
|
+
// Quick Open (Ctrl+P) modal logic for TermWeb
|
2
|
+
(function() {
|
3
|
+
let allFiles = [];
|
4
|
+
let modal = document.getElementById('quickopen-modal');
|
5
|
+
let input = document.getElementById('quickopen-input');
|
6
|
+
let results = document.getElementById('quickopen-results');
|
7
|
+
let selectedIdx = -1;
|
8
|
+
let loaded = false;
|
9
|
+
|
10
|
+
// Recursively fetch all file paths from /api/explorer/
|
11
|
+
async function fetchAllFiles(path = '.') {
|
12
|
+
let files = [];
|
13
|
+
try {
|
14
|
+
let resp = await fetch(`/api/explorer/${encodeURIComponent(path)}`);
|
15
|
+
let data = await resp.json();
|
16
|
+
if (data.type === 'dir' && data.entries) {
|
17
|
+
for (const entry of data.entries) {
|
18
|
+
const entryPath = path === '.' ? entry.name : path + '/' + entry.name;
|
19
|
+
if (entry.is_dir) {
|
20
|
+
files = files.concat(await fetchAllFiles(entryPath));
|
21
|
+
} else {
|
22
|
+
files.push(entryPath);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
} catch (e) { console.error('QuickOpen fetch error', e); }
|
27
|
+
return files;
|
28
|
+
}
|
29
|
+
|
30
|
+
async function ensureLoaded() {
|
31
|
+
if (!loaded) {
|
32
|
+
allFiles = await fetchAllFiles('.');
|
33
|
+
loaded = true;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
function showModal() {
|
38
|
+
modal.style.display = 'flex';
|
39
|
+
input.value = '';
|
40
|
+
results.innerHTML = '';
|
41
|
+
selectedIdx = -1;
|
42
|
+
setTimeout(() => input.focus(), 50);
|
43
|
+
}
|
44
|
+
function hideModal() {
|
45
|
+
modal.style.display = 'none';
|
46
|
+
}
|
47
|
+
|
48
|
+
function renderResults(filtered) {
|
49
|
+
results.innerHTML = '';
|
50
|
+
filtered.slice(0, 15).forEach((file, idx) => {
|
51
|
+
let li = document.createElement('li');
|
52
|
+
li.textContent = file;
|
53
|
+
li.tabIndex = -1;
|
54
|
+
li.className = 'quickopen-result' + (idx === selectedIdx ? ' selected' : '');
|
55
|
+
li.onclick = () => openFile(file);
|
56
|
+
results.appendChild(li);
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
// Improved openFile: poll for file link and click when ready
|
61
|
+
function openFile(path) {
|
62
|
+
hideModal();
|
63
|
+
window.renderExplorer && window.renderExplorer(getParentPath(path), false);
|
64
|
+
const maxAttempts = 20; // 20 x 100ms = 2s max
|
65
|
+
let attempts = 0;
|
66
|
+
function tryClickFile() {
|
67
|
+
let fileLinks = document.querySelectorAll(`a.file-link[data-path='${path}']`);
|
68
|
+
if (fileLinks.length) {
|
69
|
+
fileLinks[0].click();
|
70
|
+
// Optionally, add a highlight class
|
71
|
+
fileLinks[0].classList.add('quickopen-selected');
|
72
|
+
} else if (attempts < maxAttempts) {
|
73
|
+
attempts++;
|
74
|
+
setTimeout(tryClickFile, 100);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
tryClickFile();
|
78
|
+
}
|
79
|
+
|
80
|
+
input.addEventListener('input', async function() {
|
81
|
+
await ensureLoaded();
|
82
|
+
let q = input.value.trim().toLowerCase();
|
83
|
+
let filtered = allFiles.filter(f => f.toLowerCase().includes(q));
|
84
|
+
selectedIdx = -1;
|
85
|
+
renderResults(filtered);
|
86
|
+
});
|
87
|
+
input.addEventListener('keydown', function(e) {
|
88
|
+
let items = results.querySelectorAll('li');
|
89
|
+
if (e.key === 'ArrowDown') {
|
90
|
+
selectedIdx = Math.min(selectedIdx + 1, items.length - 1);
|
91
|
+
renderResults(Array.from(items).map(li => li.textContent));
|
92
|
+
e.preventDefault();
|
93
|
+
} else if (e.key === 'ArrowUp') {
|
94
|
+
selectedIdx = Math.max(selectedIdx - 1, 0);
|
95
|
+
renderResults(Array.from(items).map(li => li.textContent));
|
96
|
+
e.preventDefault();
|
97
|
+
} else if (e.key === 'Enter') {
|
98
|
+
if (selectedIdx >= 0 && items[selectedIdx]) {
|
99
|
+
openFile(items[selectedIdx].textContent);
|
100
|
+
} else if (items.length === 1) {
|
101
|
+
openFile(items[0].textContent);
|
102
|
+
}
|
103
|
+
} else if (e.key === 'Escape') {
|
104
|
+
hideModal();
|
105
|
+
}
|
106
|
+
});
|
107
|
+
|
108
|
+
document.addEventListener('keydown', function(e) {
|
109
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'p' && !e.shiftKey && !e.altKey) {
|
110
|
+
showModal();
|
111
|
+
e.preventDefault();
|
112
|
+
} else if (e.key === 'Escape' && modal.style.display === 'flex') {
|
113
|
+
hideModal();
|
114
|
+
}
|
115
|
+
});
|
116
|
+
modal.addEventListener('mousedown', function(e) {
|
117
|
+
if (e.target === modal) hideModal();
|
118
|
+
});
|
119
|
+
|
120
|
+
// Helper: get parent path
|
121
|
+
function getParentPath(path) {
|
122
|
+
path = (path || '').replace(/\\/g, '/').replace(/^\/+/g, '').replace(/\/+$/g, '');
|
123
|
+
if (!path || path === '.' || path === '') return '.';
|
124
|
+
const parts = path.split('/');
|
125
|
+
if (parts.length <= 1) return '.';
|
126
|
+
parts.pop();
|
127
|
+
const parent = parts.join('/');
|
128
|
+
return parent === '' ? '.' : parent;
|
129
|
+
}
|
130
|
+
|
131
|
+
// Style for selected result and quickopen-selected file link
|
132
|
+
let style = document.createElement('style');
|
133
|
+
style.textContent = `.quickopen-result.selected { background:#333; color:#8be9fd; } .quickopen-result { padding:0.3em 0.7em; cursor:pointer; border-radius:0.3em; } .quickopen-result:hover { background:#222; } a.file-link.quickopen-selected { background:#444 !important; color:#8be9fd !important; border-radius:0.3em; }`;
|
134
|
+
document.head.appendChild(style);
|
135
|
+
})();
|
@@ -0,0 +1,125 @@
|
|
1
|
+
// Quick Open (Ctrl+P) modal logic for TermWeb
|
2
|
+
(function() {
|
3
|
+
let allFiles = [];
|
4
|
+
let modal = document.getElementById('quickopen-modal');
|
5
|
+
let input = document.getElementById('quickopen-input');
|
6
|
+
let results = document.getElementById('quickopen-results');
|
7
|
+
let selectedIdx = -1;
|
8
|
+
let loaded = false;
|
9
|
+
|
10
|
+
// Recursively fetch all file paths from /api/explorer/
|
11
|
+
async function fetchAllFiles(path = '.') {
|
12
|
+
let files = [];
|
13
|
+
try {
|
14
|
+
let resp = await fetch(`/api/explorer/${encodeURIComponent(path)}`);
|
15
|
+
let data = await resp.json();
|
16
|
+
if (data.type === 'dir' && data.entries) {
|
17
|
+
for (const entry of data.entries) {
|
18
|
+
const entryPath = path === '.' ? entry.name : path + '/' + entry.name;
|
19
|
+
if (entry.is_dir) {
|
20
|
+
files = files.concat(await fetchAllFiles(entryPath));
|
21
|
+
} else {
|
22
|
+
files.push(entryPath);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
} catch (e) { console.error('QuickOpen fetch error', e); }
|
27
|
+
return files;
|
28
|
+
}
|
29
|
+
|
30
|
+
async function ensureLoaded() {
|
31
|
+
if (!loaded) {
|
32
|
+
allFiles = await fetchAllFiles('.');
|
33
|
+
loaded = true;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
function showModal() {
|
38
|
+
modal.style.display = 'flex';
|
39
|
+
input.value = '';
|
40
|
+
results.innerHTML = '';
|
41
|
+
selectedIdx = -1;
|
42
|
+
setTimeout(() => input.focus(), 50);
|
43
|
+
}
|
44
|
+
function hideModal() {
|
45
|
+
modal.style.display = 'none';
|
46
|
+
}
|
47
|
+
|
48
|
+
function renderResults(filtered) {
|
49
|
+
results.innerHTML = '';
|
50
|
+
filtered.slice(0, 15).forEach((file, idx) => {
|
51
|
+
let li = document.createElement('li');
|
52
|
+
li.textContent = file;
|
53
|
+
li.tabIndex = -1;
|
54
|
+
li.className = 'quickopen-result' + (idx === selectedIdx ? ' selected' : '');
|
55
|
+
li.onclick = () => openFile(file);
|
56
|
+
results.appendChild(li);
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
function openFile(path) {
|
61
|
+
hideModal();
|
62
|
+
// Simulate clicking the file in explorer
|
63
|
+
window.renderExplorer && window.renderExplorer(getParentPath(path), false);
|
64
|
+
setTimeout(() => {
|
65
|
+
let fileLinks = document.querySelectorAll(`a.file-link[data-path='${path}']`);
|
66
|
+
if (fileLinks.length) fileLinks[0].click();
|
67
|
+
}, 250);
|
68
|
+
}
|
69
|
+
|
70
|
+
input.addEventListener('input', async function() {
|
71
|
+
await ensureLoaded();
|
72
|
+
let q = input.value.trim().toLowerCase();
|
73
|
+
let filtered = allFiles.filter(f => f.toLowerCase().includes(q));
|
74
|
+
selectedIdx = -1;
|
75
|
+
renderResults(filtered);
|
76
|
+
});
|
77
|
+
input.addEventListener('keydown', function(e) {
|
78
|
+
let items = results.querySelectorAll('li');
|
79
|
+
if (e.key === 'ArrowDown') {
|
80
|
+
selectedIdx = Math.min(selectedIdx + 1, items.length - 1);
|
81
|
+
renderResults(Array.from(items).map(li => li.textContent));
|
82
|
+
e.preventDefault();
|
83
|
+
} else if (e.key === 'ArrowUp') {
|
84
|
+
selectedIdx = Math.max(selectedIdx - 1, 0);
|
85
|
+
renderResults(Array.from(items).map(li => li.textContent));
|
86
|
+
e.preventDefault();
|
87
|
+
} else if (e.key === 'Enter') {
|
88
|
+
if (selectedIdx >= 0 && items[selectedIdx]) {
|
89
|
+
openFile(items[selectedIdx].textContent);
|
90
|
+
} else if (items.length === 1) {
|
91
|
+
openFile(items[0].textContent);
|
92
|
+
}
|
93
|
+
} else if (e.key === 'Escape') {
|
94
|
+
hideModal();
|
95
|
+
}
|
96
|
+
});
|
97
|
+
|
98
|
+
document.addEventListener('keydown', function(e) {
|
99
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'p' && !e.shiftKey && !e.altKey) {
|
100
|
+
showModal();
|
101
|
+
e.preventDefault();
|
102
|
+
} else if (e.key === 'Escape' && modal.style.display === 'flex') {
|
103
|
+
hideModal();
|
104
|
+
}
|
105
|
+
});
|
106
|
+
modal.addEventListener('mousedown', function(e) {
|
107
|
+
if (e.target === modal) hideModal();
|
108
|
+
});
|
109
|
+
|
110
|
+
// Helper: get parent path
|
111
|
+
function getParentPath(path) {
|
112
|
+
path = (path || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
113
|
+
if (!path || path === '.' || path === '') return '.';
|
114
|
+
const parts = path.split('/');
|
115
|
+
if (parts.length <= 1) return '.';
|
116
|
+
parts.pop();
|
117
|
+
const parent = parts.join('/');
|
118
|
+
return parent === '' ? '.' : parent;
|
119
|
+
}
|
120
|
+
|
121
|
+
// Style for selected result
|
122
|
+
let style = document.createElement('style');
|
123
|
+
style.textContent = `.quickopen-result.selected { background:#333; color:#8be9fd; } .quickopen-result { padding:0.3em 0.7em; cursor:pointer; border-radius:0.3em; } .quickopen-result:hover { background:#222; }`;
|
124
|
+
document.head.appendChild(style);
|
125
|
+
})();
|
janito/web/app.py
CHANGED
@@ -15,7 +15,7 @@ import threading
|
|
15
15
|
import traceback
|
16
16
|
import sys
|
17
17
|
|
18
|
-
from janito.agent.runtime_config import unified_config
|
18
|
+
from janito.agent.runtime_config import unified_config, runtime_config
|
19
19
|
|
20
20
|
# Render system prompt from config
|
21
21
|
role = unified_config.get("role", "software engineer")
|
@@ -27,16 +27,16 @@ else:
|
|
27
27
|
api_key=unified_config.get("api_key"),
|
28
28
|
model=unified_config.get("model"),
|
29
29
|
role=role,
|
30
|
-
|
30
|
+
profile_name="base",
|
31
31
|
interaction_mode=unified_config.get("interaction_mode", "prompt"),
|
32
|
-
verbose_tools=
|
32
|
+
verbose_tools=runtime_config.get("verbose_tools", False),
|
33
33
|
base_url=unified_config.get("base_url", None),
|
34
34
|
azure_openai_api_version=unified_config.get(
|
35
35
|
"azure_openai_api_version", "2023-05-15"
|
36
36
|
),
|
37
37
|
use_azure_openai=unified_config.get("use_azure_openai", False),
|
38
38
|
)
|
39
|
-
system_prompt_template = profile_manager.
|
39
|
+
system_prompt_template = profile_manager.system_prompt_template
|
40
40
|
|
41
41
|
app = Flask(
|
42
42
|
__name__,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: janito
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.8.0
|
4
4
|
Summary: Natural Language Coding Agent,
|
5
5
|
Author-email: João Pinto <joao.pinto@gmail.com>
|
6
6
|
License-Expression: MIT
|
@@ -36,12 +36,45 @@ Janito is an AI-powered assistant for the command line and web that interprets n
|
|
36
36
|
|
37
37
|
For a technical overview, see the [Architecture Guide](docs/reference/architecture.md).
|
38
38
|
|
39
|
+
## 🧪 Experimental Feature: Liteweb File Viewer
|
40
|
+
|
41
|
+
Janito now includes an experimental lightweight web file viewer for use with the CLI chat shell. This feature allows you to click on file paths in the terminal (when using a Rich-compatible terminal) and instantly preview file contents in your browser—no full IDE required!
|
42
|
+
|
43
|
+
### How to Use
|
44
|
+
|
45
|
+
- Start the CLI chat shell with the `--termweb` flag:
|
46
|
+
```bash
|
47
|
+
janito --termweb
|
48
|
+
```
|
49
|
+
- By default, the viewer runs at http://localhost:8088 (or the next available port up to 8100).
|
50
|
+
- To specify a port, use `--termweb-port 8090`.
|
51
|
+
- File paths in CLI output become clickable links that open the file in your browser.
|
52
|
+
|
53
|
+
**Note:** This feature is experimental. It is intended for quick file previews and review, not for editing or production use. Feedback is welcome!
|
54
|
+
|
55
|
+
### Why is this useful?
|
56
|
+
- Enables instant file previews from the CLI without a full IDE.
|
57
|
+
- Works with all Janito file tools and outputs that display file paths.
|
58
|
+
- Uses the Rich library’s link markup for clickable terminal links.
|
59
|
+
|
60
|
+
---
|
61
|
+
|
39
62
|
## 📖 Full Documentation & Overview
|
40
63
|
- For structured and in-depth guides, visit the [Janito Documentation Site](https://docs.janito.dev).
|
41
64
|
- For a high-level, user-friendly overview, see [janito.dev](https://janito.dev).
|
42
65
|
|
43
66
|
---
|
44
67
|
|
68
|
+
## Listing Available Tools
|
69
|
+
|
70
|
+
To list all registered tools on the command line, use the option:
|
71
|
+
|
72
|
+
```sh
|
73
|
+
janito --list-tools
|
74
|
+
```
|
75
|
+
|
76
|
+
This will display a colorful table with the name, description, and parameters of each available tool.
|
77
|
+
|
45
78
|
## ⚡ Quick Start
|
46
79
|
|
47
80
|
## 🖥️ Supported Human Interfaces
|
@@ -89,7 +122,7 @@ janito --web
|
|
89
122
|
- 📁 **File & Directory Management:** Navigate, create, move, or remove files and folders.
|
90
123
|
- 🧠 **Context-Aware:** Understands your project structure for precise edits.
|
91
124
|
- 💬 **Interactive User Prompts:** Asks for clarification when needed.
|
92
|
-
-
|
125
|
+
- 🛠️ **Extensible Tooling:** Built-in tools for file operations, shell commands, directory and file management, Python code execution and validation, text replacement, and more.
|
93
126
|
- See [janito/agent/tools/README.md](janito/agent/tools/README.md) for the full list of built-in tools and their usage details. For the message handler model, see [docs/MESSAGE_HANDLER_MODEL.md](docs/MESSAGE_HANDLER_MODEL.md).
|
94
127
|
- 🌐 **Web Interface (In Development):** Simple web UI for streaming responses and tool progress.
|
95
128
|
|
@@ -101,7 +134,7 @@ janito --web
|
|
101
134
|
|
102
135
|
### Contributing & Developer Guide
|
103
136
|
|
104
|
-
If you want to extend Janito or add new tools, see the [Developer Guide](docs/README_DEV.md) for system_prompt_template, tool registration requirements, and code
|
137
|
+
If you want to extend Janito or add new tools, see the [Developer Guide](docs/README_DEV.md) for system_prompt_template, tool registration requirements, and code profile guidelines. For the full list of built-in tools and their usage, see the [Tools Reference](janito/agent/tools/README.md).
|
105
138
|
|
106
139
|
|
107
140
|
|
@@ -138,12 +171,12 @@ For details on using models hosted on Azure OpenAI, see [docs/AZURE_OPENAI.md](d
|
|
138
171
|
|
139
172
|
---
|
140
173
|
|
141
|
-
##
|
174
|
+
## 🧑💻 System Prompt & Role
|
142
175
|
|
143
|
-
Janito operates using a system prompt template that defines its behavior, communication
|
176
|
+
Janito operates using a system prompt template that defines its behavior, communication profile, and capabilities. By default, Janito assumes the role of a "software engineer"—this means its responses and actions are tailored to the expectations and best practices of professional software engineering.
|
144
177
|
|
145
178
|
- **Role:** You can customize the agent's role (e.g., "data scientist", "DevOps engineer") using the `--role` flag or config. The default is `software engineer`.
|
146
|
-
- **System Prompt Template:** The system prompt is rendered from a Jinja2 template (see `janito/agent/templates/
|
179
|
+
- **System Prompt Template:** The system prompt is rendered from a Jinja2 template (see `janito/agent/templates/prompt_prompt_template.j2`). This template governs how the agent interprets system_prompt_template, interacts with files, and communicates with users.
|
147
180
|
- **Customization & Precedence:** Advanced users can override the system prompt with the `--system` flag (raw string), or point to a custom file using `--system-file`. The precedence is: `--system-file` > `--system`/config > default template.
|
148
181
|
|
149
182
|
The default template ensures the agent:
|
@@ -152,7 +185,7 @@ The default template ensures the agent:
|
|
152
185
|
- Provides concise plans before taking action
|
153
186
|
- Documents any changes made
|
154
187
|
|
155
|
-
For more details or to customize the prompt, see the template file at `janito/agent/templates/
|
188
|
+
For more details or to customize the prompt, see the template file at `janito/agent/templates/prompt_prompt_template.j2` and the architecture overview in [docs/reference/architecture.md](docs/reference/architecture.md).
|
156
189
|
|
157
190
|
---
|
158
191
|
|
@@ -184,18 +217,18 @@ Vanilla mode is ideal for:
|
|
184
217
|
|
185
218
|
> Note: Vanilla mode is a runtime switch and does not change the Agent API or class signatures. It is controlled via CLI/config only.
|
186
219
|
|
187
|
-
##
|
220
|
+
## 👨💻 AgentProfileManager: Profile, Role, and Prompt Management
|
188
221
|
|
189
|
-
Janito now uses a dedicated `AgentProfileManager` class to manage user profiles, roles, interaction
|
190
|
-
- Stores the current role (e.g., "software engineer") and interaction
|
191
|
-
- Renders the system prompt from the appropriate template based on interaction
|
222
|
+
Janito now uses a dedicated `AgentProfileManager` class to manage user profiles, roles, interaction profiles, and system prompt selection. This manager:
|
223
|
+
- Stores the current role (e.g., "software engineer") and interaction profile (e.g., "default", "technical").
|
224
|
+
- Renders the system prompt from the appropriate template based on interaction profile.
|
192
225
|
- Instantiates and manages the low-level LLM Agent, passing the correct prompt.
|
193
|
-
- Provides methods to update the role, interaction
|
226
|
+
- Provides methods to update the role, interaction profile, and refresh the prompt at runtime.
|
194
227
|
|
195
228
|
### Multiple System Prompt Templates
|
196
|
-
- The system prompt template is now selected based on the interaction
|
229
|
+
- The system prompt template is now selected based on the interaction profile (e.g., `default` or `technical`).
|
197
230
|
- Templates are located in `janito/agent/templates/` (see `system_prompt_template.j2` and `system_prompt_template_technical.j2`).
|
198
|
-
- You can switch interaction
|
231
|
+
- You can switch interaction profiles at runtime using the profile manager, enabling different agent behaviors for different user needs.
|
199
232
|
|
200
233
|
This separation ensures that the LLM Agent remains focused on language model interaction and tool execution, while all profile, role, and prompt logic is managed at a higher level.
|
201
234
|
|
@@ -203,45 +236,45 @@ See `janito/agent/profile_manager.py` for implementation details.
|
|
203
236
|
|
204
237
|
### Agent Interaction Style
|
205
238
|
|
206
|
-
You can control the agent's behavior and prompt
|
239
|
+
You can control the agent's behavior and prompt profile globally or per-project using the `profile` config key. See [Prompt Profiles Guide](docs/guides/prompt_profiles.md) for all available styles and combinations.
|
207
240
|
- `default`: Concise, general-purpose agent (default)
|
208
241
|
- `technical`: Strict, workflow-oriented for technical/developer use
|
209
242
|
|
210
243
|
Set globally:
|
211
244
|
```bash
|
212
|
-
janito --set-global-config
|
245
|
+
janito --set-global-config profile=technical
|
213
246
|
```
|
214
247
|
Or per-project (in your project root):
|
215
248
|
```bash
|
216
|
-
janito --set-local-config
|
249
|
+
janito --set-local-config profile=technical
|
217
250
|
```
|
218
251
|
|
219
252
|
You can also override for a session with the CLI flag:
|
220
253
|
```bash
|
221
|
-
janito --
|
254
|
+
janito --profile technical
|
222
255
|
```
|
223
256
|
|
224
257
|
See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for full details.
|
225
258
|
|
226
|
-
##
|
259
|
+
## 🧑💻 Combinatorial Style System
|
227
260
|
|
228
|
-
Janito now supports combinatorial
|
261
|
+
Janito now supports combinatorial profiles for system prompts, allowing you to combine a main profile (such as `default` or `technical`) with one or more feature extensions (such as `commit_all`).
|
229
262
|
|
230
|
-
- **Main
|
231
|
-
- **Feature extensions:** Optional features that override or extend the main
|
263
|
+
- **Main profile:** The base agent behavior and workflow (e.g., `default`, `technical`).
|
264
|
+
- **Feature extensions:** Optional features that override or extend the main profile (e.g., `commit_all`).
|
232
265
|
- **Syntax:** Use a hyphen to combine, e.g., `technical-commit_all`.
|
233
266
|
|
234
267
|
**How it works:**
|
235
|
-
- The main
|
268
|
+
- The main profile template is loaded first.
|
236
269
|
- Each feature extension template is layered on top, overriding or extending specific blocks in the main template.
|
237
270
|
- Feature templates must use `{% extends parent_template %}` for dynamic inheritance.
|
238
271
|
|
239
272
|
**Example usage:**
|
240
273
|
```bash
|
241
|
-
janito --
|
274
|
+
janito --profile technical-commit_all
|
242
275
|
```
|
243
276
|
|
244
|
-
This will apply the `technical`
|
277
|
+
This will apply the `technical` profile with the `commit_all` feature enabled in the agent's system prompt.
|
245
278
|
|
246
279
|
See `janito/render_prompt.py` and `janito/agent/templates/` for implementation details and to create your own feature extensions.
|
247
280
|
|