termux-dev 1.6.0 → 1.8.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/README.md +214 -166
- package/assets/banner.svg +23 -28
- package/assets/preview.png +0 -0
- package/assets/preview.svg +101 -10
- package/dist/cli/banner.js +3 -4
- package/dist/cli/clipboard-write.js +55 -0
- package/dist/cli/clipboard.js +182 -106
- package/dist/cli/colors.js +67 -0
- package/dist/cli/confirm.js +50 -0
- package/dist/cli/doctor.js +4 -4
- package/dist/cli/effort.js +80 -0
- package/dist/cli/export.js +3 -3
- package/dist/cli/files.js +19 -5
- package/dist/cli/finder.js +241 -0
- package/dist/cli/headless.js +23 -11
- package/dist/cli/index.js +1557 -744
- package/dist/cli/keys.js +69 -0
- package/dist/cli/liveblock.js +45 -0
- package/dist/cli/log.js +19 -0
- package/dist/cli/logo.js +30 -0
- package/dist/cli/markdown.js +174 -37
- package/dist/cli/mcp-panel.js +432 -0
- package/dist/cli/permission.js +84 -0
- package/dist/cli/picker.js +143 -0
- package/dist/cli/prompt.js +256 -56
- package/dist/cli/resume.js +15 -0
- package/dist/cli/rewind.js +51 -0
- package/dist/cli/runtime_errors.js +6 -0
- package/dist/cli/server.js +85 -24
- package/dist/cli/settings.js +307 -0
- package/dist/cli/statusline.js +1 -1
- package/dist/cli/theme.js +126 -108
- package/dist/cli/transcript.js +134 -0
- package/dist/cli/typeahead.js +371 -0
- package/dist/cli/ui.js +1022 -0
- package/dist/cli/updater.js +23 -21
- package/dist/cli/usage-panel.js +242 -0
- package/dist/cli/welcome.js +56 -0
- package/dist/cli/width.js +178 -0
- package/dist/core/capabilities.js +433 -0
- package/dist/core/catalog-util.js +27 -0
- package/dist/core/checkpoint.js +90 -0
- package/dist/core/context-stats.js +29 -0
- package/dist/core/cost.js +191 -0
- package/dist/core/formats.js +199 -0
- package/dist/core/history.js +51 -1
- package/dist/core/hooks.js +79 -0
- package/dist/core/i18n.js +42 -0
- package/dist/core/local-config.js +69 -0
- package/dist/core/locales/de.js +265 -0
- package/dist/core/locales/en.js +276 -0
- package/dist/core/locales/es.js +265 -0
- package/dist/core/locales/fr.js +265 -0
- package/dist/core/locales/ja.js +265 -0
- package/dist/core/locales/pt.js +265 -0
- package/dist/core/locales/ru.js +265 -0
- package/dist/core/locales/uk.js +265 -0
- package/dist/core/locales/zh.js +265 -0
- package/dist/core/loop.js +125 -49
- package/dist/core/memory.js +39 -12
- package/dist/core/models.js +33 -20
- package/dist/core/network.js +12 -11
- package/dist/core/notify.js +54 -0
- package/dist/core/planfile.js +43 -0
- package/dist/core/price-catalog.json +1 -0
- package/dist/core/prices.js +249 -0
- package/dist/core/reasoning-catalog.json +1 -0
- package/dist/core/reasoning.js +393 -0
- package/dist/core/redact.js +41 -0
- package/dist/core/secure-file.js +22 -0
- package/dist/core/session.js +16 -5
- package/dist/core/snapshot.js +14 -2
- package/dist/core/title.js +61 -0
- package/dist/core/types.js +1 -1
- package/dist/core/usage.js +43 -7
- package/dist/core/vision-catalog.json +1 -0
- package/dist/core/vision.js +123 -0
- package/dist/mcp/client.js +36 -5
- package/dist/mcp/http-client.js +174 -0
- package/dist/mcp/manager.js +330 -93
- package/dist/permissions/guard.js +385 -117
- package/dist/prompts/builder.js +46 -38
- package/dist/providers/anthropic.js +517 -0
- package/dist/providers/index.js +18 -1
- package/dist/providers/openai.js +138 -125
- package/dist/providers/shared.js +116 -0
- package/dist/providers/toolguard.js +101 -0
- package/dist/tools/bash.js +141 -51
- package/dist/tools/diagnostics.js +76 -48
- package/dist/tools/fs.js +77 -5
- package/dist/tools/index.js +2 -1
- package/dist/tools/mcp.js +72 -0
- package/dist/tools/packages.js +16 -1
- package/dist/tools/plan.js +8 -8
- package/dist/tools/questions.js +50 -54
- package/dist/tools/server.js +6 -0
- package/dist/tools/todo.js +20 -18
- package/dist/tools/web.js +143 -8
- package/package.json +1 -1
package/dist/core/models.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { catalogContextWindow } from './vision.js';
|
|
4
5
|
const FALLBACK_LIMITS = {
|
|
5
6
|
'claude-3.5-sonnet': 200000,
|
|
6
7
|
'claude-3-opus': 200000,
|
|
@@ -38,47 +39,59 @@ function loadLimits() {
|
|
|
38
39
|
return limitsCache;
|
|
39
40
|
}
|
|
40
41
|
export function getModelContextLimit(modelName) {
|
|
42
|
+
return getModelContextInfo(modelName).limit;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The context window of a model and how sure we are. The models.dev catalogue is asked first
|
|
46
|
+
* (it knows the maker's own number for thousands of models and is refreshed with /effort refresh),
|
|
47
|
+
* then devx's table, then rules by name, and only then a default of 128k.
|
|
48
|
+
*/
|
|
49
|
+
export function getModelContextInfo(modelName) {
|
|
50
|
+
const fromCatalogue = catalogContextWindow(modelName || '');
|
|
51
|
+
if (fromCatalogue)
|
|
52
|
+
return { limit: fromCatalogue, source: 'catalogue' };
|
|
53
|
+
const rule = (limit) => ({ limit, source: 'rule' });
|
|
41
54
|
const cache = loadLimits();
|
|
42
55
|
const clean = (modelName || '').trim().toLowerCase();
|
|
43
56
|
const short = clean.split('/').pop() || clean;
|
|
44
57
|
if (cache[clean])
|
|
45
|
-
return cache[clean];
|
|
58
|
+
return { limit: cache[clean], source: 'table' };
|
|
46
59
|
if (cache[short])
|
|
47
|
-
return cache[short];
|
|
60
|
+
return { limit: cache[short], source: 'table' };
|
|
48
61
|
const baseName = short.replace(/-\d{4,8}$/, '').replace(/:latest$/, '');
|
|
49
62
|
if (cache[baseName])
|
|
50
|
-
return cache[baseName];
|
|
63
|
+
return { limit: cache[baseName], source: 'table' };
|
|
51
64
|
if (clean.includes('kimi-k2') || clean.includes('kimi'))
|
|
52
|
-
return 256000;
|
|
65
|
+
return rule(256000);
|
|
53
66
|
if (clean.includes('ox-alpha') || clean.includes('stealth'))
|
|
54
|
-
return 1048576;
|
|
67
|
+
return rule(1048576);
|
|
55
68
|
if (clean.includes('gemini-3') || clean.includes('gemini-2') || clean.includes('gemini-1.5') || clean.includes('gemini'))
|
|
56
|
-
return 1048576;
|
|
69
|
+
return rule(1048576);
|
|
57
70
|
if (clean.includes('claude-opus-4') || clean.includes('claude-4'))
|
|
58
|
-
return 1000000;
|
|
71
|
+
return rule(1000000);
|
|
59
72
|
if (clean.includes('claude-3-5') || clean.includes('claude-3.5') || clean.includes('claude-3-7') || clean.includes('claude-3.7') || clean.includes('claude'))
|
|
60
|
-
return 200000;
|
|
73
|
+
return rule(200000);
|
|
61
74
|
if (clean.includes('gpt-5'))
|
|
62
|
-
return 1050000;
|
|
75
|
+
return rule(1050000);
|
|
63
76
|
if (clean.includes('gpt-4o') || clean.includes('gpt-4-turbo') || clean.includes('o1') || clean.includes('o3'))
|
|
64
|
-
return 128000;
|
|
77
|
+
return rule(128000);
|
|
65
78
|
if (clean.includes('deepseek-v4') || clean.includes('deepseek-v3') || clean.includes('deepseek-r1') || clean.includes('deepseek'))
|
|
66
|
-
return 128000;
|
|
79
|
+
return rule(128000);
|
|
67
80
|
if (clean.includes('qwen3.5') || clean.includes('qwen-3.5'))
|
|
68
|
-
return 262144;
|
|
81
|
+
return rule(262144);
|
|
69
82
|
if (clean.includes('qwen2.5') || clean.includes('qwen-2.5') || clean.includes('qwen'))
|
|
70
|
-
return 131072;
|
|
83
|
+
return rule(131072);
|
|
71
84
|
if (clean.includes('glm-5') || clean.includes('glm-4.7') || clean.includes('glm-4.6'))
|
|
72
|
-
return 200000;
|
|
85
|
+
return rule(200000);
|
|
73
86
|
if (clean.includes('glm-4'))
|
|
74
|
-
return 128000;
|
|
87
|
+
return rule(128000);
|
|
75
88
|
if (clean.includes('minimax-m1') || clean.includes('minimax'))
|
|
76
|
-
return 1000000;
|
|
89
|
+
return rule(1000000);
|
|
77
90
|
if (clean.includes('doubao'))
|
|
78
|
-
return 256000;
|
|
91
|
+
return rule(256000);
|
|
79
92
|
if (clean.includes('llama-3.3') || clean.includes('llama-3.1') || clean.includes('llama-3'))
|
|
80
|
-
return 128000;
|
|
93
|
+
return rule(128000);
|
|
81
94
|
if (clean.includes('mistral-large') || clean.includes('codestral'))
|
|
82
|
-
return 128000;
|
|
83
|
-
return 128000;
|
|
95
|
+
return rule(128000);
|
|
96
|
+
return { limit: 128000, source: 'default' };
|
|
84
97
|
}
|
package/dist/core/network.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Network Resilience & Error Utilities for devx
|
|
3
3
|
*/
|
|
4
|
+
import { t } from './i18n.js';
|
|
4
5
|
export function isNetworkOrRetryableError(err) {
|
|
5
6
|
if (!err)
|
|
6
7
|
return false;
|
|
@@ -70,45 +71,45 @@ export function isNetworkOrRetryableError(err) {
|
|
|
70
71
|
}
|
|
71
72
|
export function getHumanReadableNetworkError(err) {
|
|
72
73
|
if (!err)
|
|
73
|
-
return '
|
|
74
|
+
return t('net.unknown');
|
|
74
75
|
const msg = (err.message || String(err)).toLowerCase();
|
|
75
76
|
const code = (err.code || err.cause?.code || '').toLowerCase();
|
|
76
77
|
const status = err.status || err.statusCode || 0;
|
|
77
78
|
if (status === 429 || msg.includes('429') || msg.includes('rate limit')) {
|
|
78
|
-
return '
|
|
79
|
+
return t('net.rateLimit');
|
|
79
80
|
}
|
|
80
81
|
if (status === 502 || msg.includes('502') || msg.includes('bad gateway')) {
|
|
81
|
-
return '
|
|
82
|
+
return t('net.badGateway');
|
|
82
83
|
}
|
|
83
84
|
if (status === 503 || msg.includes('503') || msg.includes('service unavailable')) {
|
|
84
|
-
return '
|
|
85
|
+
return t('net.unavailable');
|
|
85
86
|
}
|
|
86
87
|
if (status === 504 || msg.includes('504') || msg.includes('gateway timeout')) {
|
|
87
|
-
return '
|
|
88
|
+
return t('net.gatewayTimeout');
|
|
88
89
|
}
|
|
89
90
|
if (code.includes('enotfound') || msg.includes('enotfound') || code.includes('eai_again')) {
|
|
90
|
-
return '
|
|
91
|
+
return t('net.offline');
|
|
91
92
|
}
|
|
92
93
|
if (code.includes('econnreset') ||
|
|
93
94
|
msg.includes('econnreset') ||
|
|
94
95
|
msg.includes('connection reset') ||
|
|
95
96
|
msg.includes('socket hang up') ||
|
|
96
97
|
msg.includes('terminated')) {
|
|
97
|
-
return '
|
|
98
|
+
return t('net.reset');
|
|
98
99
|
}
|
|
99
100
|
if (code.includes('etimedout') ||
|
|
100
101
|
msg.includes('timeout') ||
|
|
101
102
|
msg.includes('time out') ||
|
|
102
103
|
code.includes('und_err_connect_timeout')) {
|
|
103
|
-
return '
|
|
104
|
+
return t('net.timeout');
|
|
104
105
|
}
|
|
105
106
|
if (code.includes('econnrefused') || msg.includes('connection refused')) {
|
|
106
|
-
return '
|
|
107
|
+
return t('net.refused');
|
|
107
108
|
}
|
|
108
109
|
if (msg.includes('fetch failed')) {
|
|
109
|
-
return '
|
|
110
|
+
return t('net.fetchFailed');
|
|
110
111
|
}
|
|
111
|
-
return err.message || '
|
|
112
|
+
return err.message || t('net.generic');
|
|
112
113
|
}
|
|
113
114
|
export function waitDelay(ms, signal) {
|
|
114
115
|
return new Promise((resolve) => {
|
package/dist/core/notify.js
CHANGED
|
@@ -52,3 +52,57 @@ export function notifyDevice(title, message, options = { vibrate: true, sound: t
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
// ───────────────────────── desktop notification when the terminal is not in front ─────────────────────────
|
|
56
|
+
/** The start of an answer as plain text for a notification: no markdown, code or links, cut at a word. */
|
|
57
|
+
export function notificationBody(markdown, max = 160) {
|
|
58
|
+
let text = String(markdown ?? '')
|
|
59
|
+
.replace(/```[\s\S]*?(```|$)/g, ' ')
|
|
60
|
+
.replace(/`([^`]*)`/g, '$1')
|
|
61
|
+
.replace(/!?\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
62
|
+
.replace(/^\s{0,3}(#{1,6}|>|[-*+]|\d+[.)])\s+/gm, '')
|
|
63
|
+
.replace(/[*_~]{1,3}([^*_~\n]+)[*_~]{1,3}/g, '$1')
|
|
64
|
+
.replace(/[\x00-\x1f\x7f]/g, ' ')
|
|
65
|
+
.replace(/\s+/g, ' ')
|
|
66
|
+
.trim();
|
|
67
|
+
if (Array.from(text).length <= max)
|
|
68
|
+
return text;
|
|
69
|
+
const cut = Array.from(text).slice(0, max).join('');
|
|
70
|
+
text = cut.replace(/\s+\S*$/, '') || cut;
|
|
71
|
+
return text.replace(/[\s,;:.\-]+$/, '') + '…';
|
|
72
|
+
}
|
|
73
|
+
/** kitty's own notification protocol (OSC 99): a title, then the body of the same notification. */
|
|
74
|
+
export function kittyNotificationSequence(title, body) {
|
|
75
|
+
const clean = (s) => s.replace(/[\x00-\x1f\x7f]/g, ' ');
|
|
76
|
+
return `\x1b]99;i=devx:d=0;${clean(title)}\x1b\\\x1b]99;i=devx:p=body;${clean(body)}\x1b\\`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Shows a notification on the desktop: through kitty's protocol in kitty, `notify-send` on Linux,
|
|
80
|
+
* `osascript` on macOS. Returns which way was used, or 'none'. Never throws.
|
|
81
|
+
*/
|
|
82
|
+
export function sendDesktopNotification(title, body, io = {}) {
|
|
83
|
+
const platform = io.platform ?? process.platform;
|
|
84
|
+
const env = io.env ?? process.env;
|
|
85
|
+
const write = io.write ?? ((text) => process.stdout.write(text));
|
|
86
|
+
const run = io.run ?? ((command, args) => {
|
|
87
|
+
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
88
|
+
child.on('error', () => { });
|
|
89
|
+
child.unref();
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
if (env.KITTY_WINDOW_ID) {
|
|
93
|
+
write(kittyNotificationSequence(title, body));
|
|
94
|
+
return 'kitty';
|
|
95
|
+
}
|
|
96
|
+
if (platform === 'linux') {
|
|
97
|
+
run('notify-send', ['--app-name=devx', title, body]);
|
|
98
|
+
return 'notify-send';
|
|
99
|
+
}
|
|
100
|
+
if (platform === 'darwin') {
|
|
101
|
+
const q = (s) => `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
102
|
+
run('osascript', ['-e', `display notification ${q(body)} with title ${q(title)}`]);
|
|
103
|
+
return 'osascript';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch { }
|
|
107
|
+
return 'none';
|
|
108
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fsSync from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const planFile = (cwd) => path.join(cwd, '.devx', 'plan.md');
|
|
5
|
+
export const PLAN_FRESH_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
export const PLAN_PROMPT_MAX_CHARS = 3000;
|
|
7
|
+
export function planToMarkdown(plan, when = new Date()) {
|
|
8
|
+
const stamp = when.toISOString().slice(0, 16).replace('T', ' ');
|
|
9
|
+
const lines = ['# Plan', '', `_Approved ${stamp}_`, '', plan.summary.trim(), ''];
|
|
10
|
+
const files = (plan.files ?? []).filter(Boolean);
|
|
11
|
+
if (files.length > 0)
|
|
12
|
+
lines.push('## Files', '', ...files.map((f) => `- ${f}`), '');
|
|
13
|
+
const steps = (plan.steps ?? []).filter(Boolean);
|
|
14
|
+
if (steps.length > 0)
|
|
15
|
+
lines.push('## Steps', '', ...steps.map((s, i) => `${i + 1}. ${s}`), '');
|
|
16
|
+
return lines.join('\n');
|
|
17
|
+
}
|
|
18
|
+
/** Writes the plan (structured data, or the text of a plan the model wrote in its answer). Returns the path. */
|
|
19
|
+
export async function savePlanFile(cwd, plan, when = new Date()) {
|
|
20
|
+
const markdown = typeof plan === 'string'
|
|
21
|
+
? `# Plan\n\n_Approved ${when.toISOString().slice(0, 16).replace('T', ' ')}_\n\n${plan.trim()}\n`
|
|
22
|
+
: planToMarkdown(plan, when);
|
|
23
|
+
const file = planFile(cwd);
|
|
24
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
25
|
+
await fs.writeFile(file, markdown, 'utf8');
|
|
26
|
+
return file;
|
|
27
|
+
}
|
|
28
|
+
/** The plan for the system prompt: only if it is recent, cut to a size that does not eat the context. */
|
|
29
|
+
export function loadPlanForPrompt(cwd, now = Date.now()) {
|
|
30
|
+
const file = planFile(cwd);
|
|
31
|
+
try {
|
|
32
|
+
const stat = fsSync.statSync(file);
|
|
33
|
+
if (now - stat.mtimeMs > PLAN_FRESH_MS)
|
|
34
|
+
return '';
|
|
35
|
+
const text = fsSync.readFileSync(file, 'utf8').trim();
|
|
36
|
+
if (text.length <= PLAN_PROMPT_MAX_CHARS)
|
|
37
|
+
return text;
|
|
38
|
+
return text.slice(0, PLAN_PROMPT_MAX_CHARS).replace(/\n[^\n]*$/, '') + '\n… (the rest is in .devx/plan.md)';
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|