@tensor.chat/companion 0.4.3 → 0.4.6

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 CHANGED
@@ -17,7 +17,9 @@ The command prints a code such as `AB3D-7KQ2`. In tensor.chat open
17
17
  setup asks in the terminal what the AI may use on this computer: folders from a list of
18
18
  suggestions or a folder browser, everything (not recommended), or nothing yet, and
19
19
  whether it may run commands. Run it again any time with
20
- `npx @tensor.chat/companion setup`. Then keep the daemon running:
20
+ `npx @tensor.chat/companion setup`. It speaks the computer's language (`LANG`) if that is
21
+ one of de, en, es, fr, hi, pt, ru or zh, English otherwise; `--lang` picks another.
22
+ Then keep the daemon running:
21
23
 
22
24
  ```
23
25
  npx @tensor.chat/companion run
@@ -70,11 +70,11 @@ function run() {
70
70
  });
71
71
  daemon.events.on('call', (c) => out(`[${stamp()}] ${c.tool}: ${summarize(c.args)}`));
72
72
  daemon.events.on('approval', (a) => out(`[${stamp()}] approval requested for ${a.tool}: ${a.summary || ''} — answer it in the chat`));
73
- daemon.events.on('unauthorized', () => { out('the server rejected this device — pair again with "npx @tensor.chat/companion pair"'); process.exit(2); });
74
- daemon.events.on('removed', () => { out('this device was removed in the account settings — exiting'); process.exit(3); });
73
+ daemon.events.on('unauthorized', async () => { out('the server rejected this device — pair again with "npx @tensor.chat/companion pair"'); await daemon.stop(); process.exit(2); });
74
+ daemon.events.on('removed', async () => { out('this device was removed in the account settings — exiting'); await daemon.stop(); process.exit(3); });
75
75
  daemon.events.on('local_approval_request', (a) => out(`[${stamp()}] local approval requested: run npx @tensor.chat/companion allow ${a.code}`));
76
76
  daemon.start();
77
- const stop = () => { daemon.stop(); setTimeout(() => process.exit(0), 200); };
77
+ const stop = async () => { await daemon.stop(); process.exit(0); };
78
78
  process.on('SIGINT', stop);
79
79
  process.on('SIGTERM', stop);
80
80
  }
@@ -165,7 +165,7 @@ function help() {
165
165
  out(`tensor-companion ${VERSION}
166
166
 
167
167
  tensor-companion pair [--server URL] [--name NAME] pair this machine with your account
168
- tensor-companion setup [--lang en|de] choose what the AI may use on this computer
168
+ tensor-companion setup [--lang LANG] choose what the AI may use on this computer
169
169
  tensor-companion run stay connected and run tools for the AI
170
170
  tensor-companion status show the current pairing
171
171
  tensor-companion shell <path|auto> choose the shell Bash commands run in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensor.chat/companion",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "description": "Lets your tensor.chat AI use this machine: files, shell and internet through your own connection.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tensor.chat",
package/src/daemon.js CHANGED
@@ -10,19 +10,13 @@ const EventEmitter = require('events');
10
10
  const WebSocket = require('ws');
11
11
  const { Roots } = require('./paths');
12
12
  const bash = require('./tools/bash');
13
- const files = require('./tools/files');
14
- const { fetchUrl } = require('./tools/fetch');
15
- const { extractTouched } = require('./tools/touched');
16
- const { outsideFolders } = require('./tools/outside');
17
- const { Jobs } = require('./tools/jobs');
13
+ const { ToolRunner } = require('./toolRunner');
18
14
  const { createStallWatch } = require('./stalls');
19
15
 
20
16
  const VERSION = require('../package.json').version;
21
17
  const RECONNECT_MIN_MS = 1000;
22
18
  const RECONNECT_MAX_MS = 60000;
23
19
  const MAX_CONCURRENT_CALLS = 4;
24
- const BACKGROUND_FIRST_LOOK_MS = 1500; // a background start still reports the first lines
25
- const FOREGROUND_DEFAULT_MS = 600000;
26
20
  const SLOW_SPAWN_MS = 2000;
27
21
 
28
22
  // What a call works on, for the log: the command, the path or the address
@@ -108,7 +102,7 @@ function authorizeRequestedRoots(requestedRoots, labels, approvedRoots) {
108
102
  return { roots, labels: keptLabels, rejected };
109
103
  }
110
104
 
111
- function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShell, approver = null, slowSpawnMs = SLOW_SPAWN_MS } = {}) {
105
+ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShell, approver = null, slowSpawnMs = SLOW_SPAWN_MS, toolWorkerPath } = {}) {
112
106
  if (!serverUrl || !token) throw new Error('serverUrl and token are required');
113
107
  const emitter = new EventEmitter();
114
108
  const say = typeof log === 'function' ? log : () => {};
@@ -116,7 +110,8 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
116
110
  const localApprovals = typeof approvedRoots === 'function' ? approvedRoots : null;
117
111
  const localShellApproval = typeof approvedShell === 'function' ? approvedShell : null;
118
112
  const running = new Map(); // call id → AbortController
119
- const jobs = new Jobs();
113
+ const tools = new ToolRunner({ shell, slowSpawnMs, workerPath: toolWorkerPath });
114
+ tools.on('log', say);
120
115
  const unsentExits = []; // job endings the server has not heard yet (offline at the time)
121
116
  const state = { connected: false, deviceId: null, name: null, mode: null, roots: [], labels: [], reconnectMs: RECONNECT_MIN_MS, stopped: false, ws: null, timer: null, connectedAt: null, lastPingAt: null };
122
117
  const stalls = createStallWatch({ say });
@@ -160,79 +155,6 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
160
155
  emitter.emit('config', { roots: [...roots.status(), ...authorized.rejected], mode: state.mode, name: state.name });
161
156
  }
162
157
 
163
- async function runTool(tool, args, signal, timeoutMs, callRoots = roots) {
164
- const a = args && typeof args === 'object' ? args : {};
165
- switch (tool) {
166
- case 'bash': {
167
- const cwdRes = a.cwd ? callRoots.resolve(a.cwd) : null;
168
- if (cwdRes && cwdRes.error) return { ok: false, text: `(cannot run there — ${cwdRes.error})` };
169
- const cwd = cwdRes ? cwdRes.abs : callRoots.primary();
170
- if (!cwd) return { ok: false, text: '(no shared folder is available on this device — the user has to add one in the device settings)' };
171
- // Starting a process holds this whole process until Windows has it running. A
172
- // scanner that inspects the program first can make that last long enough for
173
- // the server to stop hearing from this computer.
174
- const spawnAt = Date.now();
175
- const job = jobs.start(String(a.command || ''), { cwd, shell });
176
- const spawnMs = Date.now() - spawnAt;
177
- if (spawnMs >= slowSpawnMs) say(`starting the shell took ${Math.round(spawnMs / 1000)} s`);
178
- // A cancelled call (turn aborted, server timeout) takes its foreground job with
179
- // it; a background job belongs to the chat, not to the call, and lives on.
180
- const onAbort = () => { if (!a.background) jobs.kill(job.id); };
181
- if (signal) signal.addEventListener('abort', onAbort, { once: true });
182
- // The server cancels a call at timeoutMs; answering a little earlier keeps the job
183
- // alive as a background job instead of letting the cancel kill it.
184
- const limit = timeoutMs || FOREGROUND_DEFAULT_MS;
185
- const waitMs = a.background ? BACKGROUND_FIRST_LOOK_MS : Math.max(1000, limit - Math.min(5000, Math.floor(limit / 4)));
186
- const ended = await jobs.wait(job, waitMs);
187
- if (signal) signal.removeEventListener('abort', onAbort);
188
- if (ended) {
189
- const r = jobs.result(job);
190
- r.touched = extractTouched(callRoots, a.command, r.stdout, r.stderr);
191
- delete r.stdout;
192
- delete r.stderr;
193
- jobs.poll(job.id); // the one-shot result already carried every line
194
- return r;
195
- }
196
- // Still running: it becomes (or already was) a background job.
197
- const p = jobs.poll(job.id);
198
- return { ok: true, running: true, jobId: job.id, text: p.text, exitCode: null, truncated: false, timedOut: false, touched: [] };
199
- }
200
- case 'job': {
201
- const job = jobs.get(a.jobId);
202
- if (!job) return { ok: false, text: '(no such job on this device — it may have been forgotten after many newer ones)' };
203
- if (a.action === 'kill') {
204
- jobs.kill(job.id);
205
- await jobs.wait(job, 3000);
206
- }
207
- const p = jobs.poll(job.id);
208
- return { ok: true, jobId: job.id, running: p.status === 'running', status: p.status, exitCode: p.exitCode, jobMs: p.ms, text: p.text, skipped: p.skipped };
209
- }
210
- case 'read': return files.read(callRoots, a.path, { lines: a.lines });
211
- case 'write': return files.write(callRoots, a.path, a.content, { overwrite: a.overwrite === true });
212
- case 'edit': return files.edit(callRoots, a.path, a.edits);
213
- case 'glob': return files.glob(callRoots, a.pattern);
214
- // The server asks before it judges a shell command: what of it lies outside
215
- // the shared folders, read with this machine's own variables. Nothing runs.
216
- case 'outside': {
217
- const at = a.cwd ? callRoots.resolve(a.cwd) : null;
218
- return outsideFolders(callRoots, a.command, { cwd: at && !at.error ? at.abs : callRoots.primary() || os.homedir() });
219
- }
220
- case 'grep': return files.grep(callRoots, a.command);
221
- case 'ls': return files.ls(callRoots, a.path, { max: a.max });
222
- case 'fetchfile': return files.fetchfile(callRoots, a.path, { maxBytes: a.maxBytes });
223
- case 'fetchpart': return files.fetchpart(callRoots, a.path, { offset: a.offset, length: a.length });
224
- // Das eingehaengte Laufwerk schreibt ueber diese vier zurueck. Sie stehen der KI
225
- // NICHT offen - sie kommen nur aus dem Adapter, der das Laufwerk bedient.
226
- case 'writepart': return files.writepart(callRoots, a.path, { offset: a.offset, base64: a.base64, truncate: a.truncate });
227
- case 'mkdirpath': return files.mkdirpath(callRoots, a.path);
228
- case 'removepath': return await files.removepath(callRoots, a.path, { endgueltig: a.endgueltig === true });
229
- case 'renamepath': return files.renamepath(callRoots, a.from, a.to);
230
- case 'statpath': return files.statpath(callRoots, a.path);
231
- case 'fetch': return fetchUrl(a.url, { signal, raw: a.raw === true });
232
- default: return { ok: false, text: `(unknown tool "${tool}" on the companion device)` };
233
- }
234
- }
235
-
236
158
  async function onCall(msg) {
237
159
  const id = String(msg.id);
238
160
  const tool = String(msg.tool || '');
@@ -258,7 +180,7 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
258
180
  const scoped = authorizeRequestedRoots(msg.roots, msg.labels, approved);
259
181
  callRoots = new Roots(scoped.roots, scoped.labels);
260
182
  }
261
- r = await runTool(tool, msg.args, ctrl.signal, Number(msg.timeoutMs) || undefined, callRoots);
183
+ r = await tools.run(tool, msg.args, ctrl.signal, Number(msg.timeoutMs) || undefined, callRoots);
262
184
  } catch (e) {
263
185
  r = { ok: false, text: `(tool error on the companion device: ${e && e.message ? e.message : e})` };
264
186
  }
@@ -275,7 +197,7 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
275
197
  unsentExits.shift();
276
198
  }
277
199
  }
278
- jobs.on('exit', (r) => {
200
+ tools.on('exit', (r) => {
279
201
  const m = { t: 'job_exit', jobId: r.jobId, status: r.status, exitCode: r.exitCode, ms: r.ms, tail: r.tail };
280
202
  unsentExits.push(m);
281
203
  flushExits();
@@ -376,11 +298,11 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
376
298
  stalls.stop();
377
299
  if (state.timer) { clearTimeout(state.timer); state.timer = null; }
378
300
  for (const c of running.values()) c.abort();
379
- jobs.killAll();
301
+ const stopped = tools.stop();
380
302
  if (state.ws) { try { state.ws.close(1000, 'daemon stopped'); } catch (_) {} try { state.ws.terminate(); } catch (_) {} state.ws = null; }
303
+ return stopped;
381
304
  },
382
305
  replyApproval(approvalId, decision) { return send({ t: 'approval_reply', approvalId, decision }); },
383
- jobs,
384
306
  };
385
307
  }
386
308
 
package/src/setup/i18n.js CHANGED
@@ -1,138 +1,17 @@
1
1
  'use strict';
2
2
  // The setup's words, in the language the computer is set to (LC_ALL, LC_MESSAGES,
3
- // LANG - an SSH session usually brings the caller's), English otherwise.
3
+ // LANG - an SSH session usually brings the caller's), English otherwise. The site
4
+ // speaks the same eight.
4
5
 
5
6
  const STRINGS = {
6
- en: {
7
- setup: 'setup',
8
- steps: ['What', 'Folders', 'Commands', 'Save'],
9
- 'start.title': 'What may the AI use on this computer?',
10
- 'start.folders': 'Choose folders', 'start.foldersNote': 'recommended',
11
- 'start.nothing': 'Nothing yet', 'start.nothingNote': 'a chat asks when it needs a folder',
12
- 'start.everything': 'Everything', 'start.everythingNote': 'not recommended',
13
- 'start.help': 'The AI reads, changes and deletes files only where you allow it here.',
14
- 'start.now': 'Approved now:',
15
- 'start.later': 'Change it any time:',
16
- 'places.title': 'Which folders may the AI use?',
17
- 'places.loading': 'Looking for folders…',
18
- 'places.none': 'No suggestions on this computer.',
19
- 'places.browse': 'Browse all folders…', 'places.type': 'Type a path…', 'places.continue': 'Continue',
20
- selected: '{n} selected',
21
- 'note.git': '{n} Git projects', 'note.git1': '1 Git project', 'note.project': 'Git project',
22
- 'note.web': 'websites', 'note.srv': 'server data', 'note.opt': 'installed programs',
23
- 'note.etc': 'system settings', 'note.log': 'logs', 'note.home': 'your whole home folder',
24
- 'note.readOnly': 'read-only for you', 'note.added': 'added by you', 'note.earlier': 'approved earlier',
25
- 'note.inside': 'inside {path}',
26
- 'help.home': 'Everything in {home}, also hidden settings and keys such as ~/.ssh.',
27
- 'help.etc': 'Settings of the whole system. A wrong change there can stop the server.',
28
- 'help.log': 'Log files of the system and its services. Good for finding errors.',
29
- 'help.web': 'Files of the websites this server delivers.',
30
- 'help.srv': 'Data of the services on this server.',
31
- 'help.opt': 'Programs installed outside the package manager.',
32
- 'help.code': 'Your code projects.',
33
- 'help.readOnly': 'You can read here but not write. The AI can do the same, no more.',
34
- 'help.browse': 'Pick any folder on this computer, like in a file manager.',
35
- 'help.type': 'Enter a full path. Tab completes it.',
36
- 'help.continue': 'Next: commands.',
37
- 'browse.title': 'Browse', 'browse.thisPc': 'This PC',
38
- 'browse.loading': 'loading…', 'browse.empty': 'no folders', 'browse.noAccess': 'no access', 'browse.link': 'link',
39
- 'browse.blocked': '{path} is inside {parent}, which is already selected.',
40
- 'browse.hidden': 'Hidden folders are shown.', 'browse.hiddenOff': 'Hidden folders are hidden.',
41
- 'type.title': 'Type a folder path', 'type.label': 'Path',
42
- 'type.notFound': 'There is no folder {path}.',
43
- 'everything.title': 'Share everything?',
44
- 'everything.body': 'The AI can read, change and delete every file that {user} can reach on this computer.',
45
- 'everything.root': 'You are root: that is every file on the system, the system itself included.',
46
- 'everything.free': 'It lets the AI work freely on this server. Not recommended.',
47
- 'everything.no': 'No, go back', 'everything.yes': 'Yes, share everything',
48
- 'everything.summary': 'Everything on this computer',
49
- 'shell.title': 'May the AI run commands here?',
50
- 'shell.body': 'Commands run as {user}, with full internet access. They are not limited to the shared folders.',
51
- 'shell.keep': 'Keep as it is', 'shell.keepNote': 'until {date}',
52
- 'shell.no': 'Not now', 'shell.noNote': 'a chat asks first when it needs them',
53
- 'shell.off': 'Turn off', 'shell.offNote': 'a chat asks first when it needs them',
54
- 'shell.d7': 'Yes, for 7 days', 'shell.d30': 'Yes, for 30 days', 'shell.d365': 'Yes, for a year',
55
- 'review.title': 'Check and save',
56
- 'review.folders': 'Folders', 'review.commands': 'Commands',
57
- 'review.noFolders': 'none — a chat asks when it needs one',
58
- 'review.removed': 'approval removed',
59
- 'review.cmdNo': 'not approved — a chat asks first',
60
- 'review.cmdUntil': 'approved until {date}',
61
- 'review.save': 'Save', 'review.back': 'Back', 'review.quit': 'Quit without saving',
62
- 'review.where': 'Saved only on this computer, in {file}',
63
- keyNames: { space: 'space', enter: 'enter', esc: 'esc', tab: 'tab', backspace: 'backspace' },
64
- keys: { move: 'move', select: 'select', toggle: 'select', back: 'back', quit: 'quit', open: 'open', close: 'close', openClose: 'open/close', up: 'folder up', hidden: 'hidden', done: 'done', tab: 'complete', add: 'add' },
65
- 'done.saved': 'Saved on this computer.',
66
- 'done.unchanged': 'Nothing changed.',
67
- 'done.web': 'When a chat on tensor.chat shares one of these folders, this computer confirms it by itself.',
68
- 'done.run': 'Start the companion with:',
69
- 'done.noTty': 'The setup needs an interactive terminal. Without one, use approve-folder and approve-shell.',
70
- },
71
- de: {
72
- setup: 'Einrichtung',
73
- steps: ['Was', 'Ordner', 'Befehle', 'Speichern'],
74
- 'start.title': 'Was darf die KI auf diesem Computer nutzen?',
75
- 'start.folders': 'Ordner auswählen', 'start.foldersNote': 'empfohlen',
76
- 'start.nothing': 'Noch nichts', 'start.nothingNote': 'ein Chat fragt, wenn er einen Ordner braucht',
77
- 'start.everything': 'Alles', 'start.everythingNote': 'nicht empfohlen',
78
- 'start.help': 'Die KI liest, ändert und löscht Dateien nur dort, wo du es hier erlaubst.',
79
- 'start.now': 'Jetzt freigegeben:',
80
- 'start.later': 'Jederzeit änderbar:',
81
- 'places.title': 'Welche Ordner darf die KI nutzen?',
82
- 'places.loading': 'Suche Ordner…',
83
- 'places.none': 'Keine Vorschläge auf diesem Computer.',
84
- 'places.browse': 'Alle Ordner durchsuchen…', 'places.type': 'Pfad eingeben…', 'places.continue': 'Weiter',
85
- selected: '{n} ausgewählt',
86
- 'note.git': '{n} Git-Projekte', 'note.git1': '1 Git-Projekt', 'note.project': 'Git-Projekt',
87
- 'note.web': 'Webseiten', 'note.srv': 'Serverdaten', 'note.opt': 'installierte Programme',
88
- 'note.etc': 'Systemeinstellungen', 'note.log': 'Protokolle', 'note.home': 'dein ganzer Home-Ordner',
89
- 'note.readOnly': 'für dich nur lesbar', 'note.added': 'von dir hinzugefügt', 'note.earlier': 'schon freigegeben',
90
- 'note.inside': 'liegt in {path}',
91
- 'help.home': 'Alles in {home}, auch versteckte Einstellungen und Schlüssel wie ~/.ssh.',
92
- 'help.etc': 'Einstellungen des ganzen Systems. Eine falsche Änderung dort kann den Server lahmlegen.',
93
- 'help.log': 'Protokolle des Systems und seiner Dienste. Gut für die Fehlersuche.',
94
- 'help.web': 'Dateien der Webseiten, die dieser Server ausliefert.',
95
- 'help.srv': 'Daten der Dienste auf diesem Server.',
96
- 'help.opt': 'Programme, die neben der Paketverwaltung installiert sind.',
97
- 'help.code': 'Deine Code-Projekte.',
98
- 'help.readOnly': 'Du kannst hier lesen, aber nicht schreiben. Die KI kann dasselbe, nicht mehr.',
99
- 'help.browse': 'Wähle einen beliebigen Ordner auf diesem Computer, wie in einem Dateimanager.',
100
- 'help.type': 'Gib einen vollständigen Pfad ein. Tab ergänzt ihn.',
101
- 'help.continue': 'Weiter zu den Befehlen.',
102
- 'browse.title': 'Durchsuchen', 'browse.thisPc': 'Dieser PC',
103
- 'browse.loading': 'lädt…', 'browse.empty': 'keine Ordner', 'browse.noAccess': 'kein Zugriff', 'browse.link': 'Verknüpfung',
104
- 'browse.blocked': '{path} liegt in {parent}, und der ist schon ausgewählt.',
105
- 'browse.hidden': 'Versteckte Ordner werden angezeigt.', 'browse.hiddenOff': 'Versteckte Ordner sind ausgeblendet.',
106
- 'type.title': 'Ordnerpfad eingeben', 'type.label': 'Pfad',
107
- 'type.notFound': 'Den Ordner {path} gibt es nicht.',
108
- 'everything.title': 'Alles freigeben?',
109
- 'everything.body': 'Die KI kann jede Datei lesen, ändern und löschen, an die {user} auf diesem Computer herankommt.',
110
- 'everything.root': 'Du bist root: Das ist jede Datei des Systems, das System selbst eingeschlossen.',
111
- 'everything.free': 'So kann die KI frei auf diesem Server arbeiten. Nicht empfohlen.',
112
- 'everything.no': 'Nein, zurück', 'everything.yes': 'Ja, alles freigeben',
113
- 'everything.summary': 'Alles auf diesem Computer',
114
- 'shell.title': 'Darf die KI hier Befehle ausführen?',
115
- 'shell.body': 'Befehle laufen als {user}, mit vollem Internetzugang. Sie sind nicht auf die freigegebenen Ordner beschränkt.',
116
- 'shell.keep': 'So lassen', 'shell.keepNote': 'bis {date}',
117
- 'shell.no': 'Jetzt nicht', 'shell.noNote': 'ein Chat fragt vorher, wenn er sie braucht',
118
- 'shell.off': 'Ausschalten', 'shell.offNote': 'ein Chat fragt vorher, wenn er sie braucht',
119
- 'shell.d7': 'Ja, für 7 Tage', 'shell.d30': 'Ja, für 30 Tage', 'shell.d365': 'Ja, für ein Jahr',
120
- 'review.title': 'Prüfen und speichern',
121
- 'review.folders': 'Ordner', 'review.commands': 'Befehle',
122
- 'review.noFolders': 'keine – ein Chat fragt, wenn er einen braucht',
123
- 'review.removed': 'Freigabe entfernt',
124
- 'review.cmdNo': 'nicht freigegeben – ein Chat fragt vorher',
125
- 'review.cmdUntil': 'freigegeben bis {date}',
126
- 'review.save': 'Speichern', 'review.back': 'Zurück', 'review.quit': 'Beenden ohne zu speichern',
127
- 'review.where': 'Wird nur auf diesem Computer gespeichert, in {file}',
128
- keyNames: { space: 'Leertaste', enter: 'Enter', esc: 'Esc', tab: 'Tab', backspace: 'Rücktaste' },
129
- keys: { move: 'wählen', select: 'bestätigen', toggle: 'auswählen', back: 'zurück', quit: 'beenden', open: 'öffnen', close: 'schließen', openClose: 'öffnen/schließen', up: 'Ordner hoch', hidden: 'versteckte', done: 'fertig', tab: 'ergänzen', add: 'hinzufügen' },
130
- 'done.saved': 'Auf diesem Computer gespeichert.',
131
- 'done.unchanged': 'Nichts geändert.',
132
- 'done.web': 'Gibt ein Chat auf tensor.chat einen dieser Ordner frei, bestätigt dieser Computer das selbst.',
133
- 'done.run': 'Den Companion starten mit:',
134
- 'done.noTty': 'Die Einrichtung braucht ein interaktives Terminal. Ohne eines gehen approve-folder und approve-shell.',
135
- },
7
+ en: require('./lang/en'),
8
+ de: require('./lang/de'),
9
+ es: require('./lang/es'),
10
+ fr: require('./lang/fr'),
11
+ hi: require('./lang/hi'),
12
+ pt: require('./lang/pt'),
13
+ ru: require('./lang/ru'),
14
+ zh: require('./lang/zh'),
136
15
  };
137
16
 
138
17
  function pickLanguage(env = process.env, override) {
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+ // The setup's words in German.
3
+
4
+ module.exports = {
5
+ setup: 'Einrichtung',
6
+ steps: ['Was', 'Ordner', 'Befehle', 'Speichern'],
7
+ 'start.title': 'Was darf die KI auf diesem Computer nutzen?',
8
+ 'start.folders': 'Ordner auswählen', 'start.foldersNote': 'empfohlen',
9
+ 'start.nothing': 'Noch nichts', 'start.nothingNote': 'ein Chat fragt, wenn er einen Ordner braucht',
10
+ 'start.everything': 'Alles', 'start.everythingNote': 'nicht empfohlen',
11
+ 'start.help': 'Die KI liest, ändert und löscht Dateien nur dort, wo du es hier erlaubst.',
12
+ 'start.now': 'Jetzt freigegeben:',
13
+ 'start.later': 'Jederzeit änderbar:',
14
+ 'places.title': 'Welche Ordner darf die KI nutzen?',
15
+ 'places.loading': 'Suche Ordner…',
16
+ 'places.none': 'Keine Vorschläge auf diesem Computer.',
17
+ 'places.browse': 'Alle Ordner durchsuchen…', 'places.type': 'Pfad eingeben…', 'places.continue': 'Weiter',
18
+ selected: '{n} ausgewählt',
19
+ 'note.git': '{n} Git-Projekte', 'note.git1': '1 Git-Projekt', 'note.project': 'Git-Projekt',
20
+ 'note.web': 'Webseiten', 'note.srv': 'Serverdaten', 'note.opt': 'installierte Programme',
21
+ 'note.etc': 'Systemeinstellungen', 'note.log': 'Protokolle', 'note.home': 'dein ganzer Home-Ordner',
22
+ 'note.readOnly': 'für dich nur lesbar', 'note.added': 'von dir hinzugefügt', 'note.earlier': 'schon freigegeben',
23
+ 'note.inside': 'liegt in {path}',
24
+ 'help.home': 'Alles in {home}, auch versteckte Einstellungen und Schlüssel wie ~/.ssh.',
25
+ 'help.etc': 'Einstellungen des ganzen Systems. Eine falsche Änderung dort kann den Server lahmlegen.',
26
+ 'help.log': 'Protokolle des Systems und seiner Dienste. Gut für die Fehlersuche.',
27
+ 'help.web': 'Dateien der Webseiten, die dieser Server ausliefert.',
28
+ 'help.srv': 'Daten der Dienste auf diesem Server.',
29
+ 'help.opt': 'Programme, die neben der Paketverwaltung installiert sind.',
30
+ 'help.code': 'Deine Code-Projekte.',
31
+ 'help.readOnly': 'Du kannst hier lesen, aber nicht schreiben. Die KI kann dasselbe, nicht mehr.',
32
+ 'help.browse': 'Wähle einen beliebigen Ordner auf diesem Computer, wie in einem Dateimanager.',
33
+ 'help.type': 'Gib einen vollständigen Pfad ein. Tab ergänzt ihn.',
34
+ 'help.continue': 'Weiter zu den Befehlen.',
35
+ 'browse.title': 'Durchsuchen', 'browse.thisPc': 'Dieser PC',
36
+ 'browse.loading': 'lädt…', 'browse.empty': 'keine Ordner', 'browse.noAccess': 'kein Zugriff', 'browse.link': 'Verknüpfung',
37
+ 'browse.blocked': '{path} liegt in {parent}, und der ist schon ausgewählt.',
38
+ 'browse.hidden': 'Versteckte Ordner werden angezeigt.', 'browse.hiddenOff': 'Versteckte Ordner sind ausgeblendet.',
39
+ 'type.title': 'Ordnerpfad eingeben', 'type.label': 'Pfad',
40
+ 'type.notFound': 'Den Ordner {path} gibt es nicht.',
41
+ 'everything.title': 'Alles freigeben?',
42
+ 'everything.body': 'Die KI kann jede Datei lesen, ändern und löschen, an die {user} auf diesem Computer herankommt.',
43
+ 'everything.root': 'Du bist root: Das ist jede Datei des Systems, das System selbst eingeschlossen.',
44
+ 'everything.free': 'So kann die KI frei auf diesem Server arbeiten. Nicht empfohlen.',
45
+ 'everything.no': 'Nein, zurück', 'everything.yes': 'Ja, alles freigeben',
46
+ 'everything.summary': 'Alles auf diesem Computer',
47
+ 'shell.title': 'Darf die KI hier Befehle ausführen?',
48
+ 'shell.body': 'Befehle laufen als {user}, mit vollem Internetzugang. Sie sind nicht auf die freigegebenen Ordner beschränkt.',
49
+ 'shell.keep': 'So lassen', 'shell.keepNote': 'bis {date}',
50
+ 'shell.no': 'Jetzt nicht', 'shell.noNote': 'ein Chat fragt vorher, wenn er sie braucht',
51
+ 'shell.off': 'Ausschalten', 'shell.offNote': 'ein Chat fragt vorher, wenn er sie braucht',
52
+ 'shell.d7': 'Ja, für 7 Tage', 'shell.d30': 'Ja, für 30 Tage', 'shell.d365': 'Ja, für ein Jahr',
53
+ 'review.title': 'Prüfen und speichern',
54
+ 'review.folders': 'Ordner', 'review.commands': 'Befehle',
55
+ 'review.noFolders': 'keine – ein Chat fragt, wenn er einen braucht',
56
+ 'review.removed': 'Freigabe entfernt',
57
+ 'review.cmdNo': 'nicht freigegeben – ein Chat fragt vorher',
58
+ 'review.cmdUntil': 'freigegeben bis {date}',
59
+ 'review.save': 'Speichern', 'review.back': 'Zurück', 'review.quit': 'Beenden ohne zu speichern',
60
+ 'review.where': 'Wird nur auf diesem Computer gespeichert, in {file}',
61
+ keyNames: { space: 'Leertaste', enter: 'Enter', esc: 'Esc', tab: 'Tab', backspace: 'Rücktaste' },
62
+ keys: { move: 'wählen', select: 'bestätigen', toggle: 'auswählen', back: 'zurück', quit: 'beenden', open: 'öffnen', close: 'schließen', openClose: 'öffnen/schließen', up: 'Ordner hoch', hidden: 'versteckte', done: 'fertig', tab: 'ergänzen', add: 'hinzufügen' },
63
+ 'done.saved': 'Auf diesem Computer gespeichert.',
64
+ 'done.unchanged': 'Nichts geändert.',
65
+ 'done.web': 'Gibt ein Chat auf tensor.chat einen dieser Ordner frei, bestätigt dieser Computer das selbst.',
66
+ 'done.run': 'Den Companion starten mit:',
67
+ 'done.noTty': 'Die Einrichtung braucht ein interaktives Terminal. Ohne eines gehen approve-folder und approve-shell.',
68
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+ // The setup's words in English.
3
+
4
+ module.exports = {
5
+ setup: 'setup',
6
+ steps: ['What', 'Folders', 'Commands', 'Save'],
7
+ 'start.title': 'What may the AI use on this computer?',
8
+ 'start.folders': 'Choose folders', 'start.foldersNote': 'recommended',
9
+ 'start.nothing': 'Nothing yet', 'start.nothingNote': 'a chat asks when it needs a folder',
10
+ 'start.everything': 'Everything', 'start.everythingNote': 'not recommended',
11
+ 'start.help': 'The AI reads, changes and deletes files only where you allow it here.',
12
+ 'start.now': 'Approved now:',
13
+ 'start.later': 'Change it any time:',
14
+ 'places.title': 'Which folders may the AI use?',
15
+ 'places.loading': 'Looking for folders…',
16
+ 'places.none': 'No suggestions on this computer.',
17
+ 'places.browse': 'Browse all folders…', 'places.type': 'Type a path…', 'places.continue': 'Continue',
18
+ selected: '{n} selected',
19
+ 'note.git': '{n} Git projects', 'note.git1': '1 Git project', 'note.project': 'Git project',
20
+ 'note.web': 'websites', 'note.srv': 'server data', 'note.opt': 'installed programs',
21
+ 'note.etc': 'system settings', 'note.log': 'logs', 'note.home': 'your whole home folder',
22
+ 'note.readOnly': 'read-only for you', 'note.added': 'added by you', 'note.earlier': 'approved earlier',
23
+ 'note.inside': 'inside {path}',
24
+ 'help.home': 'Everything in {home}, also hidden settings and keys such as ~/.ssh.',
25
+ 'help.etc': 'Settings of the whole system. A wrong change there can stop the server.',
26
+ 'help.log': 'Log files of the system and its services. Good for finding errors.',
27
+ 'help.web': 'Files of the websites this server delivers.',
28
+ 'help.srv': 'Data of the services on this server.',
29
+ 'help.opt': 'Programs installed outside the package manager.',
30
+ 'help.code': 'Your code projects.',
31
+ 'help.readOnly': 'You can read here but not write. The AI can do the same, no more.',
32
+ 'help.browse': 'Pick any folder on this computer, like in a file manager.',
33
+ 'help.type': 'Enter a full path. Tab completes it.',
34
+ 'help.continue': 'Next: commands.',
35
+ 'browse.title': 'Browse', 'browse.thisPc': 'This PC',
36
+ 'browse.loading': 'loading…', 'browse.empty': 'no folders', 'browse.noAccess': 'no access', 'browse.link': 'link',
37
+ 'browse.blocked': '{path} is inside {parent}, which is already selected.',
38
+ 'browse.hidden': 'Hidden folders are shown.', 'browse.hiddenOff': 'Hidden folders are hidden.',
39
+ 'type.title': 'Type a folder path', 'type.label': 'Path',
40
+ 'type.notFound': 'There is no folder {path}.',
41
+ 'everything.title': 'Share everything?',
42
+ 'everything.body': 'The AI can read, change and delete every file that {user} can reach on this computer.',
43
+ 'everything.root': 'You are root: that is every file on the system, the system itself included.',
44
+ 'everything.free': 'It lets the AI work freely on this server. Not recommended.',
45
+ 'everything.no': 'No, go back', 'everything.yes': 'Yes, share everything',
46
+ 'everything.summary': 'Everything on this computer',
47
+ 'shell.title': 'May the AI run commands here?',
48
+ 'shell.body': 'Commands run as {user}, with full internet access. They are not limited to the shared folders.',
49
+ 'shell.keep': 'Keep as it is', 'shell.keepNote': 'until {date}',
50
+ 'shell.no': 'Not now', 'shell.noNote': 'a chat asks first when it needs them',
51
+ 'shell.off': 'Turn off', 'shell.offNote': 'a chat asks first when it needs them',
52
+ 'shell.d7': 'Yes, for 7 days', 'shell.d30': 'Yes, for 30 days', 'shell.d365': 'Yes, for a year',
53
+ 'review.title': 'Check and save',
54
+ 'review.folders': 'Folders', 'review.commands': 'Commands',
55
+ 'review.noFolders': 'none — a chat asks when it needs one',
56
+ 'review.removed': 'approval removed',
57
+ 'review.cmdNo': 'not approved — a chat asks first',
58
+ 'review.cmdUntil': 'approved until {date}',
59
+ 'review.save': 'Save', 'review.back': 'Back', 'review.quit': 'Quit without saving',
60
+ 'review.where': 'Saved only on this computer, in {file}',
61
+ keyNames: { space: 'space', enter: 'enter', esc: 'esc', tab: 'tab', backspace: 'backspace' },
62
+ keys: { move: 'move', select: 'select', toggle: 'select', back: 'back', quit: 'quit', open: 'open', close: 'close', openClose: 'open/close', up: 'folder up', hidden: 'hidden', done: 'done', tab: 'complete', add: 'add' },
63
+ 'done.saved': 'Saved on this computer.',
64
+ 'done.unchanged': 'Nothing changed.',
65
+ 'done.web': 'When a chat on tensor.chat shares one of these folders, this computer confirms it by itself.',
66
+ 'done.run': 'Start the companion with:',
67
+ 'done.noTty': 'The setup needs an interactive terminal. Without one, use approve-folder and approve-shell.',
68
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+ // The setup's words in Spanish.
3
+
4
+ module.exports = {
5
+ setup: 'configuración',
6
+ steps: ['Qué', 'Carpetas', 'Comandos', 'Guardar'],
7
+ 'start.title': '¿Qué puede usar la IA en este ordenador?',
8
+ 'start.folders': 'Elegir carpetas', 'start.foldersNote': 'recomendado',
9
+ 'start.nothing': 'Nada por ahora', 'start.nothingNote': 'un chat pregunta cuando necesita una carpeta',
10
+ 'start.everything': 'Todo', 'start.everythingNote': 'no recomendado',
11
+ 'start.help': 'La IA lee, modifica y borra archivos solo donde tú lo permitas aquí.',
12
+ 'start.now': 'Aprobado ahora:',
13
+ 'start.later': 'Puedes cambiarlo cuando quieras:',
14
+ 'places.title': '¿Qué carpetas puede usar la IA?',
15
+ 'places.loading': 'Buscando carpetas…',
16
+ 'places.none': 'No hay sugerencias en este ordenador.',
17
+ 'places.browse': 'Explorar todas las carpetas…', 'places.type': 'Escribir una ruta…', 'places.continue': 'Continuar',
18
+ selected: 'Selección: {n}',
19
+ 'note.git': '{n} proyectos Git', 'note.git1': '1 proyecto Git', 'note.project': 'proyecto Git',
20
+ 'note.web': 'sitios web', 'note.srv': 'datos del servidor', 'note.opt': 'programas instalados',
21
+ 'note.etc': 'ajustes del sistema', 'note.log': 'registros', 'note.home': 'toda tu carpeta personal',
22
+ 'note.readOnly': 'solo lectura para ti', 'note.added': 'añadida por ti', 'note.earlier': 'aprobada antes',
23
+ 'note.inside': 'dentro de {path}',
24
+ 'help.home': 'Todo lo que hay en {home}, también ajustes ocultos y claves como ~/.ssh.',
25
+ 'help.etc': 'Ajustes de todo el sistema. Un cambio erróneo ahí puede dejar el servidor sin funcionar.',
26
+ 'help.log': 'Registros del sistema y de sus servicios. Útiles para encontrar errores.',
27
+ 'help.web': 'Archivos de los sitios web que sirve este servidor.',
28
+ 'help.srv': 'Datos de los servicios de este servidor.',
29
+ 'help.opt': 'Programas instalados fuera del gestor de paquetes.',
30
+ 'help.code': 'Tus proyectos de código.',
31
+ 'help.readOnly': 'Aquí puedes leer, pero no escribir. La IA puede hacer lo mismo, nada más.',
32
+ 'help.browse': 'Elige cualquier carpeta de este ordenador, como en un explorador de archivos.',
33
+ 'help.type': 'Escribe una ruta completa. Tab la completa.',
34
+ 'help.continue': 'Siguiente: comandos.',
35
+ 'browse.title': 'Explorar', 'browse.thisPc': 'Este equipo',
36
+ 'browse.loading': 'cargando…', 'browse.empty': 'sin carpetas', 'browse.noAccess': 'sin acceso', 'browse.link': 'enlace',
37
+ 'browse.blocked': '{path} está dentro de {parent}, que ya está seleccionada.',
38
+ 'browse.hidden': 'Se muestran las carpetas ocultas.', 'browse.hiddenOff': 'Las carpetas ocultas no se muestran.',
39
+ 'type.title': 'Escribe la ruta de una carpeta', 'type.label': 'Ruta',
40
+ 'type.notFound': 'No existe la carpeta {path}.',
41
+ 'everything.title': '¿Compartir todo?',
42
+ 'everything.body': 'La IA puede leer, modificar y borrar cualquier archivo al que {user} tenga acceso en este ordenador.',
43
+ 'everything.root': 'Eres root: eso incluye cada archivo del sistema, también el propio sistema.',
44
+ 'everything.free': 'Así la IA puede trabajar libremente en este servidor. No recomendado.',
45
+ 'everything.no': 'No, volver', 'everything.yes': 'Sí, compartir todo',
46
+ 'everything.summary': 'Todo en este ordenador',
47
+ 'shell.title': '¿Puede la IA ejecutar comandos aquí?',
48
+ 'shell.body': 'Los comandos se ejecutan como {user}, con acceso completo a Internet. No se limitan a las carpetas compartidas.',
49
+ 'shell.keep': 'Dejarlo así', 'shell.keepNote': 'hasta el {date}',
50
+ 'shell.no': 'Ahora no', 'shell.noNote': 'un chat pregunta antes cuando los necesita',
51
+ 'shell.off': 'Desactivar', 'shell.offNote': 'un chat pregunta antes cuando los necesita',
52
+ 'shell.d7': 'Sí, durante 7 días', 'shell.d30': 'Sí, durante 30 días', 'shell.d365': 'Sí, durante un año',
53
+ 'review.title': 'Revisar y guardar',
54
+ 'review.folders': 'Carpetas', 'review.commands': 'Comandos',
55
+ 'review.noFolders': 'ninguna — un chat pregunta cuando necesita una',
56
+ 'review.removed': 'aprobación retirada',
57
+ 'review.cmdNo': 'no aprobados — un chat pregunta antes',
58
+ 'review.cmdUntil': 'aprobados hasta el {date}',
59
+ 'review.save': 'Guardar', 'review.back': 'Atrás', 'review.quit': 'Salir sin guardar',
60
+ 'review.where': 'Se guarda solo en este ordenador, en {file}',
61
+ keyNames: { space: 'espacio', enter: 'enter', esc: 'esc', tab: 'tab', backspace: 'retroceso' },
62
+ keys: { move: 'mover', select: 'elegir', toggle: 'marcar', back: 'atrás', quit: 'salir', open: 'abrir', close: 'cerrar', openClose: 'abrir/cerrar', up: 'carpeta superior', hidden: 'ocultas', done: 'listo', tab: 'completar', add: 'añadir' },
63
+ 'done.saved': 'Guardado en este ordenador.',
64
+ 'done.unchanged': 'No se ha cambiado nada.',
65
+ 'done.web': 'Cuando un chat de tensor.chat comparta una de estas carpetas, este ordenador lo confirmará por sí solo.',
66
+ 'done.run': 'Inicia el Companion con:',
67
+ 'done.noTty': 'La configuración necesita un terminal interactivo. Sin él, usa approve-folder y approve-shell.',
68
+ };