termux-dev 1.5.0 → 1.8.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.
Files changed (98) hide show
  1. package/README.md +215 -163
  2. package/assets/banner.svg +23 -28
  3. package/assets/preview.png +0 -0
  4. package/assets/preview.svg +94 -0
  5. package/dist/cli/banner.js +7 -3
  6. package/dist/cli/clipboard-write.js +55 -0
  7. package/dist/cli/clipboard.js +182 -106
  8. package/dist/cli/colors.js +67 -0
  9. package/dist/cli/confirm.js +50 -0
  10. package/dist/cli/doctor.js +4 -4
  11. package/dist/cli/effort.js +80 -0
  12. package/dist/cli/export.js +3 -3
  13. package/dist/cli/files.js +19 -5
  14. package/dist/cli/headless.js +29 -14
  15. package/dist/cli/index.js +1395 -474
  16. package/dist/cli/keys.js +69 -0
  17. package/dist/cli/liveblock.js +45 -0
  18. package/dist/cli/log.js +19 -0
  19. package/dist/cli/markdown.js +158 -36
  20. package/dist/cli/mcp-panel.js +432 -0
  21. package/dist/cli/permission.js +84 -0
  22. package/dist/cli/picker.js +135 -0
  23. package/dist/cli/prompt.js +338 -121
  24. package/dist/cli/prompt_palette.js +6 -0
  25. package/dist/cli/resume.js +15 -0
  26. package/dist/cli/rewind.js +51 -0
  27. package/dist/cli/runtime_errors.js +6 -0
  28. package/dist/cli/server.js +85 -24
  29. package/dist/cli/settings.js +307 -0
  30. package/dist/cli/statusline.js +27 -1
  31. package/dist/cli/theme.js +126 -108
  32. package/dist/cli/transcript.js +114 -0
  33. package/dist/cli/typeahead.js +371 -0
  34. package/dist/cli/ui.js +1021 -0
  35. package/dist/cli/updater.js +23 -21
  36. package/dist/cli/usage-panel.js +242 -0
  37. package/dist/cli/width.js +178 -0
  38. package/dist/core/capabilities.js +433 -0
  39. package/dist/core/catalog-util.js +27 -0
  40. package/dist/core/checkpoint.js +90 -0
  41. package/dist/core/context-stats.js +29 -0
  42. package/dist/core/cost.js +191 -0
  43. package/dist/core/formats.js +199 -0
  44. package/dist/core/history.js +51 -1
  45. package/dist/core/hooks.js +79 -0
  46. package/dist/core/i18n.js +42 -0
  47. package/dist/core/local-config.js +69 -0
  48. package/dist/core/locales/de.js +210 -0
  49. package/dist/core/locales/en.js +221 -0
  50. package/dist/core/locales/es.js +210 -0
  51. package/dist/core/locales/fr.js +210 -0
  52. package/dist/core/locales/ja.js +210 -0
  53. package/dist/core/locales/pt.js +210 -0
  54. package/dist/core/locales/ru.js +210 -0
  55. package/dist/core/locales/uk.js +210 -0
  56. package/dist/core/locales/zh.js +210 -0
  57. package/dist/core/loop.js +125 -49
  58. package/dist/core/memory.js +39 -12
  59. package/dist/core/models.js +33 -20
  60. package/dist/core/network.js +12 -11
  61. package/dist/core/notify.js +54 -0
  62. package/dist/core/planfile.js +43 -0
  63. package/dist/core/price-catalog.json +1 -0
  64. package/dist/core/prices.js +249 -0
  65. package/dist/core/reasoning-catalog.json +1 -0
  66. package/dist/core/reasoning.js +393 -0
  67. package/dist/core/redact.js +41 -0
  68. package/dist/core/secure-file.js +22 -0
  69. package/dist/core/session.js +16 -5
  70. package/dist/core/snapshot.js +14 -2
  71. package/dist/core/title.js +61 -0
  72. package/dist/core/types.js +1 -1
  73. package/dist/core/usage.js +43 -7
  74. package/dist/core/vision-catalog.json +1 -0
  75. package/dist/core/vision.js +123 -0
  76. package/dist/mcp/client.js +36 -5
  77. package/dist/mcp/http-client.js +174 -0
  78. package/dist/mcp/manager.js +330 -93
  79. package/dist/permissions/guard.js +385 -117
  80. package/dist/prompts/builder.js +46 -38
  81. package/dist/providers/anthropic.js +517 -0
  82. package/dist/providers/index.js +18 -1
  83. package/dist/providers/openai.js +138 -125
  84. package/dist/providers/shared.js +116 -0
  85. package/dist/providers/toolguard.js +101 -0
  86. package/dist/tools/bash.js +141 -51
  87. package/dist/tools/diagnostics.js +76 -48
  88. package/dist/tools/fs.js +77 -5
  89. package/dist/tools/index.js +2 -1
  90. package/dist/tools/mcp.js +72 -0
  91. package/dist/tools/packages.js +16 -1
  92. package/dist/tools/plan.js +8 -8
  93. package/dist/tools/questions.js +50 -54
  94. package/dist/tools/server.js +6 -0
  95. package/dist/tools/todo.js +20 -18
  96. package/dist/tools/web.js +143 -8
  97. package/package.json +4 -2
  98. package/assets/preview_work1.png +0 -0
@@ -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 'Неизвестная ошибка сети (Network error)';
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 'Превышен лимит запросов API (Rate limit / 429)';
79
+ return t('net.rateLimit');
79
80
  }
80
81
  if (status === 502 || msg.includes('502') || msg.includes('bad gateway')) {
81
- return 'Шлюз провайдера временно недоступен (Bad Gateway / 502)';
82
+ return t('net.badGateway');
82
83
  }
83
84
  if (status === 503 || msg.includes('503') || msg.includes('service unavailable')) {
84
- return 'Сервер провайдера перегружен (Service Unavailable / 503)';
85
+ return t('net.unavailable');
85
86
  }
86
87
  if (status === 504 || msg.includes('504') || msg.includes('gateway timeout')) {
87
- return 'Таймаут шлюза провайдера (Gateway Timeout / 504)';
88
+ return t('net.gatewayTimeout');
88
89
  }
89
90
  if (code.includes('enotfound') || msg.includes('enotfound') || code.includes('eai_again')) {
90
- return 'Нет подключения к сети или DNS (Host not found / Offline)';
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 'Обрыв соединения с сервером (Connection reset / Socket closed)';
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 'Таймаут сетевого соединения (Network timeout)';
104
+ return t('net.timeout');
104
105
  }
105
106
  if (code.includes('econnrefused') || msg.includes('connection refused')) {
106
- return 'Сервер отклонил подключение (Connection refused)';
107
+ return t('net.refused');
107
108
  }
108
109
  if (msg.includes('fetch failed')) {
109
- return 'Сбой сетевого соединения (Fetch failed / Connection dropped)';
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) => {
@@ -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
+ }