@tensor.chat/companion 0.4.2 → 0.4.4

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
@@ -13,8 +13,13 @@ npx @tensor.chat/companion pair
13
13
  ```
14
14
 
15
15
  The command prints a code such as `AB3D-7KQ2`. In tensor.chat open
16
- **Settings → Companion devices → Pair a device** and enter it. Then keep the daemon
17
- running:
16
+ **Settings → Companion devices → Pair a device** and enter it. Once paired, a short
17
+ setup asks in the terminal what the AI may use on this computer: folders from a list of
18
+ suggestions or a folder browser, everything (not recommended), or nothing yet, and
19
+ whether it may run commands. Run it again any time with
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:
18
23
 
19
24
  ```
20
25
  npx @tensor.chat/companion run
@@ -25,9 +30,11 @@ It then appears as the folder `Devices/<name>/` in that chat's file tree.
25
30
 
26
31
  ## What the AI can do, and where
27
32
 
28
- - **Shared folders** must be approved locally on the computer first with
29
- `tensor-companion approve-folder <full-path>`. The website can request and display
30
- folders, but it cannot grant itself access. Read, Write, Edit, Glob and Grep only
33
+ - **Shared folders** must be approved locally on the computer first, with
34
+ `tensor-companion setup` or `tensor-companion approve-folder <full-path>`. The website
35
+ can request and display folders, but it cannot grant itself access. When a chat asks
36
+ for a folder the computer already approved (or one inside it), the daemon confirms
37
+ that by itself; anything else waits for `tensor-companion allow <code>`. Read, Write, Edit, Glob and Grep only
31
38
  work inside locally approved folders. Paths outside, including symlinks that point
32
39
  outside, are refused on this machine even if the server asks for them.
33
40
  - **Shell commands** stay in tensor.chat's server sandbox by default. Approve shell locally
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // tensor-companion — pair this machine with your tensor.chat account and keep it
4
- // available for the AI. Commands: pair, run, status, shell, unpair.
4
+ // available for the AI. Commands: pair, setup, run, status, shell, unpair.
5
5
 
6
6
  const os = require('os');
7
7
  const config = require('../src/config');
@@ -33,14 +33,35 @@ async function pair() {
33
33
  const cfg = await pairing.waitAndSave(server, start, { name });
34
34
  if (!cfg) throw new Error('the pairing code expired — run "npx @tensor.chat/companion pair" again');
35
35
  out(` Paired as "${cfg.name}". Config saved to ${config.configPath()}`);
36
+ if (process.stdin.isTTY && process.stdout.isTTY) await setup();
37
+ else out(' Choose what the AI may use here with: npx @tensor.chat/companion setup');
36
38
  out(' Start the daemon with: npx @tensor.chat/companion run');
37
39
  }
38
40
 
41
+ // What this computer lets the AI use, chosen in a guided menu
42
+ async function setup() {
43
+ const { runSetup } = require('../src/setup/wizard');
44
+ const { translator, pickLanguage } = require('../src/setup/i18n');
45
+ const t = translator(pickLanguage(process.env, opt('--lang')));
46
+ if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error(t('done.noTty'));
47
+ let user = '';
48
+ try { user = os.userInfo().username; } catch (_) {}
49
+ const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
50
+ const r = await runSetup({ user, device: config.load().name || os.hostname(), isRoot, lang: opt('--lang') });
51
+ out('');
52
+ if (!r.saved) { out(` ${t('done.unchanged')}`); return; }
53
+ out(` ✔ ${t('done.saved')}`);
54
+ out(` ${t('review.folders')}: ${r.everything ? t('everything.summary') : (r.roots.length ? r.roots.join(', ') : t('review.noFolders'))}`);
55
+ out(` ${t('review.commands')}: ${r.shellUntil ? t('review.cmdUntil', { date: t.date(r.shellUntil) }) : t('review.cmdNo')}`);
56
+ if (r.roots.length) out(` ${t('done.web')}`);
57
+ out('');
58
+ }
59
+
39
60
  function run() {
40
61
  const cfg = config.load();
41
62
  if (!cfg.token || !cfg.serverUrl) throw new Error('not paired yet — run "npx @tensor.chat/companion pair" first');
42
63
  const stamp = () => new Date().toISOString().slice(11, 19);
43
- const daemon = createDaemon({ serverUrl: cfg.serverUrl, token: cfg.token, shell: cfg.shell, approvedRoots: () => config.approvedRoots(), approvedShell: () => config.shellApproved(), log: (m) => out(`[${stamp()}] ${m}`) });
64
+ const daemon = createDaemon({ serverUrl: cfg.serverUrl, token: cfg.token, shell: cfg.shell, approvedRoots: () => config.approvedRoots(), approvedShell: () => config.shellApproved(), approver: 'terminal', log: (m) => out(`[${stamp()}] ${m}`) });
44
65
  daemon.events.on('welcome', (w) => out(`[${stamp()}] ready as "${w.name || cfg.name}" (device ${w.deviceId})`));
45
66
  daemon.events.on('config', (c) => {
46
67
  const bad = c.roots.filter((r) => !r.ok);
@@ -144,6 +165,7 @@ function help() {
144
165
  out(`tensor-companion ${VERSION}
145
166
 
146
167
  tensor-companion pair [--server URL] [--name NAME] pair this machine with your account
168
+ tensor-companion setup [--lang LANG] choose what the AI may use on this computer
147
169
  tensor-companion run stay connected and run tools for the AI
148
170
  tensor-companion status show the current pairing
149
171
  tensor-companion shell <path|auto> choose the shell Bash commands run in
@@ -159,6 +181,7 @@ function help() {
159
181
  (async () => {
160
182
  try {
161
183
  if (cmd === 'pair') await pair();
184
+ else if (cmd === 'setup') await setup();
162
185
  else if (cmd === 'run') run();
163
186
  else if (cmd === 'status') status();
164
187
  else if (cmd === 'shell') shell();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tensor.chat/companion",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
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
@@ -48,7 +48,9 @@ function isRoot() {
48
48
  try { return typeof process.getuid === 'function' && process.getuid() === 0; } catch (_) { return false; }
49
49
  }
50
50
 
51
- function systemInfo(shell, localAuthority = false) {
51
+ // approver: where this computer confirms what the website asks for - 'app' (a dialog of
52
+ // the desktop app) or 'terminal' (a command run on it, often over SSH)
53
+ function systemInfo(shell, localAuthority = false, approver = null) {
52
54
  let username = null;
53
55
  try { username = os.userInfo().username; } catch (_) {}
54
56
  return {
@@ -65,18 +67,29 @@ function systemInfo(shell, localAuthority = false) {
65
67
  shellKind: bash.shellFor(shell).kind,
66
68
  cwd: process.cwd(),
67
69
  localAuthority: localAuthority ? 1 : null,
70
+ approver: approver === 'app' || approver === 'terminal' ? approver : null,
68
71
  };
69
72
  }
70
73
 
74
+ // Whether a real path lies in (or is) one of the folders this computer approved
75
+ function insideApproved(real, approvedRoots) {
76
+ const norm = (p) => process.platform === 'win32' ? p.toLowerCase() : p;
77
+ return (Array.isArray(approvedRoots) ? approvedRoots : []).some((r) => {
78
+ const rel = path.relative(norm(path.resolve(String(r))), norm(real));
79
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
80
+ });
81
+ }
82
+
83
+ // A request the computer already allows: a folder inside one it approved, or the
84
+ // shell while its approval runs
85
+ function coveredLocally(request, approvedRoots, shellApproved) {
86
+ if (request.kind === 'shell') return !!shellApproved;
87
+ try { return insideApproved(fs.realpathSync.native(path.resolve(String(request.path || ''))), approvedRoots); } catch (_) { return false; }
88
+ }
89
+
71
90
  function authorizeRequestedRoots(requestedRoots, labels, approvedRoots) {
72
91
  const requested = Array.isArray(requestedRoots) ? requestedRoots.map(String) : [];
73
92
  const names = Array.isArray(labels) ? labels.map(String) : [];
74
- const approved = (Array.isArray(approvedRoots) ? approvedRoots : []).map((r) => path.resolve(String(r)));
75
- const norm = (p) => process.platform === 'win32' ? p.toLowerCase() : p;
76
- const inside = (candidate, allowed) => {
77
- const rel = path.relative(norm(allowed), norm(candidate));
78
- return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
79
- };
80
93
  const roots = [];
81
94
  const keptLabels = [];
82
95
  const rejected = [];
@@ -87,7 +100,7 @@ function authorizeRequestedRoots(requestedRoots, labels, approvedRoots) {
87
100
  rejected.push({ path: abs, ok: false, error: 'folder does not exist on this computer' });
88
101
  return;
89
102
  }
90
- if (approved.some((allow) => inside(real, allow))) {
103
+ if (insideApproved(real, approvedRoots)) {
91
104
  roots.push(real);
92
105
  keptLabels.push(names[i] || '');
93
106
  } else rejected.push({ path: abs, ok: false, error: 'not approved on this computer' });
@@ -95,7 +108,7 @@ function authorizeRequestedRoots(requestedRoots, labels, approvedRoots) {
95
108
  return { roots, labels: keptLabels, rejected };
96
109
  }
97
110
 
98
- function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShell, slowSpawnMs = SLOW_SPAWN_MS } = {}) {
111
+ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShell, approver = null, slowSpawnMs = SLOW_SPAWN_MS } = {}) {
99
112
  if (!serverUrl || !token) throw new Error('serverUrl and token are required');
100
113
  const emitter = new EventEmitter();
101
114
  const say = typeof log === 'function' ? log : () => {};
@@ -113,6 +126,26 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
113
126
  try { state.ws.send(JSON.stringify(msg)); return true; } catch (_) { return false; }
114
127
  }
115
128
 
129
+ // What this computer already allows needs nobody at it: the daemon says yes itself.
130
+ // Everything else waits for a person there (the desktop app's dialog, or "allow").
131
+ async function confirmIfCovered(msg) {
132
+ const what = msg.kind === 'shell' ? 'shell access' : `folder ${msg.path}`;
133
+ if (!localApprovals || !coveredLocally(msg, localApprovals(), localShellApproval && localShellApproval())) {
134
+ emitter.emit('local_approval_request', msg);
135
+ return;
136
+ }
137
+ try {
138
+ const url = `${serverUrl.replace(/\/+$/, '')}/api/companion/local-approvals/${encodeURIComponent(String(msg.code))}/complete`;
139
+ const res = await fetch(url, { method: 'POST', headers: { authorization: `Bearer ${token}` } });
140
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
141
+ say(`${what} confirmed: already approved on this computer`);
142
+ emitter.emit('local_approval_confirmed', msg);
143
+ } catch (e) {
144
+ say(`${what} is approved on this computer, but confirming it failed (${e.message})`);
145
+ emitter.emit('local_approval_request', msg);
146
+ }
147
+ }
148
+
116
149
  function applyConfig(msg) {
117
150
  const requested = Array.isArray(msg.roots) ? msg.roots.map(String) : [];
118
151
  const labels = Array.isArray(msg.labels) ? msg.labels.map(String) : [];
@@ -264,7 +297,7 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
264
297
  case 'cancel': { const c = running.get(String(msg.id)); if (c) c.abort(); break; }
265
298
  case 'approval': emitter.emit('approval', msg); break;
266
299
  case 'approval_resolved': emitter.emit('approval_resolved', msg); break;
267
- case 'local_approval_request': emitter.emit('local_approval_request', msg); break;
300
+ case 'local_approval_request': confirmIfCovered(msg); break;
268
301
  default: break;
269
302
  }
270
303
  }
@@ -304,7 +337,7 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
304
337
  state.connectedAt = Date.now();
305
338
  state.lastPingAt = null;
306
339
  state.reconnectMs = RECONNECT_MIN_MS;
307
- send(systemInfo(shell, !!localApprovals && !!localShellApproval));
340
+ send(systemInfo(shell, !!localApprovals && !!localShellApproval, approver));
308
341
  flushExits();
309
342
  say(`connected to ${serverUrl}`);
310
343
  emitter.emit('connected');
@@ -0,0 +1,36 @@
1
+ 'use strict';
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. The site
4
+ // speaks the same eight.
5
+
6
+ const STRINGS = {
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'),
15
+ };
16
+
17
+ function pickLanguage(env = process.env, override) {
18
+ let raw = override || env.TENSOR_COMPANION_LANG || env.LC_ALL || env.LC_MESSAGES || env.LANG;
19
+ if (!raw) { try { raw = Intl.DateTimeFormat().resolvedOptions().locale; } catch (_) { raw = ''; } }
20
+ const code = String(raw || '').slice(0, 2).toLowerCase();
21
+ return Object.prototype.hasOwnProperty.call(STRINGS, code) ? code : 'en';
22
+ }
23
+
24
+ function translator(lang) {
25
+ const table = STRINGS[lang] || STRINGS.en;
26
+ const t = (key, vars) => {
27
+ const s = Object.prototype.hasOwnProperty.call(table, key) ? table[key] : STRINGS.en[key];
28
+ if (typeof s !== 'string') return s;
29
+ return s.replace(/\{(\w+)\}/g, (m, k) => (vars && vars[k] != null ? String(vars[k]) : m));
30
+ };
31
+ t.lang = STRINGS[lang] ? lang : 'en';
32
+ t.date = (iso) => { try { return new Intl.DateTimeFormat(t.lang, { dateStyle: 'medium' }).format(new Date(iso)); } catch (_) { return String(iso).slice(0, 10); } };
33
+ return t;
34
+ }
35
+
36
+ module.exports = { pickLanguage, translator, STRINGS };
@@ -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
+ };
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+ // The setup's words in French.   is the no-break space French sets before
3
+ // ? : ! and between a number and its word.
4
+
5
+ module.exports = {
6
+ setup: 'configuration',
7
+ steps: ['Quoi', 'Dossiers', 'Commandes', 'Enregistrer'],
8
+ 'start.title': 'Que peut utiliser l’IA sur cet ordinateur ?',
9
+ 'start.folders': 'Choisir des dossiers', 'start.foldersNote': 'recommandé',
10
+ 'start.nothing': 'Rien pour l’instant', 'start.nothingNote': 'une discussion demande quand elle a besoin d’un dossier',
11
+ 'start.everything': 'Tout', 'start.everythingNote': 'déconseillé',
12
+ 'start.help': 'L’IA lit, modifie et supprime des fichiers uniquement là où vous l’autorisez ici.',
13
+ 'start.now': 'Approuvé actuellement :',
14
+ 'start.later': 'Modifiable à tout moment :',
15
+ 'places.title': 'Quels dossiers l’IA peut-elle utiliser ?',
16
+ 'places.loading': 'Recherche de dossiers…',
17
+ 'places.none': 'Aucune suggestion sur cet ordinateur.',
18
+ 'places.browse': 'Parcourir tous les dossiers…', 'places.type': 'Saisir un chemin…', 'places.continue': 'Continuer',
19
+ selected: 'Sélection : {n}',
20
+ 'note.git': '{n} projets Git', 'note.git1': '1 projet Git', 'note.project': 'projet Git',
21
+ 'note.web': 'sites web', 'note.srv': 'données du serveur', 'note.opt': 'programmes installés',
22
+ 'note.etc': 'réglages du système', 'note.log': 'journaux', 'note.home': 'tout votre dossier personnel',
23
+ 'note.readOnly': 'lecture seule pour vous', 'note.added': 'ajouté par vous', 'note.earlier': 'déjà approuvé',
24
+ 'note.inside': 'dans {path}',
25
+ 'help.home': 'Tout ce qui se trouve dans {home}, y compris les réglages cachés et les clés comme ~/.ssh.',
26
+ 'help.etc': 'Réglages de tout le système. Une mauvaise modification peut arrêter le serveur.',
27
+ 'help.log': 'Journaux du système et de ses services. Utiles pour trouver des erreurs.',
28
+ 'help.web': 'Fichiers des sites web hébergés sur ce serveur.',
29
+ 'help.srv': 'Données des services de ce serveur.',
30
+ 'help.opt': 'Programmes installés en dehors du gestionnaire de paquets.',
31
+ 'help.code': 'Vos projets de code.',
32
+ 'help.readOnly': 'Vous pouvez lire ici, mais pas écrire. L’IA peut faire de même, pas plus.',
33
+ 'help.browse': 'Choisissez n’importe quel dossier de cet ordinateur, comme dans un gestionnaire de fichiers.',
34
+ 'help.type': 'Saisissez un chemin complet. Tab le complète.',
35
+ 'help.continue': 'Ensuite : les commandes.',
36
+ 'browse.title': 'Parcourir', 'browse.thisPc': 'Ce PC',
37
+ 'browse.loading': 'chargement…', 'browse.empty': 'aucun dossier', 'browse.noAccess': 'pas d’accès', 'browse.link': 'lien',
38
+ 'browse.blocked': '{path} se trouve dans {parent}, qui est déjà sélectionné.',
39
+ 'browse.hidden': 'Les dossiers cachés sont affichés.', 'browse.hiddenOff': 'Les dossiers cachés sont masqués.',
40
+ 'type.title': 'Saisir le chemin d’un dossier', 'type.label': 'Chemin',
41
+ 'type.notFound': 'Le dossier {path} n’existe pas.',
42
+ 'everything.title': 'Tout partager ?',
43
+ 'everything.body': 'L’IA peut lire, modifier et supprimer tous les fichiers auxquels {user} a accès sur cet ordinateur.',
44
+ 'everything.root': 'Vous êtes root : cela couvre tous les fichiers du système, y compris le système lui-même.',
45
+ 'everything.free': 'L’IA peut ainsi travailler librement sur ce serveur. Déconseillé.',
46
+ 'everything.no': 'Non, revenir', 'everything.yes': 'Oui, tout partager',
47
+ 'everything.summary': 'Tout sur cet ordinateur',
48
+ 'shell.title': 'L’IA peut-elle exécuter des commandes ici ?',
49
+ 'shell.body': 'Les commandes s’exécutent en tant que {user}, avec un accès Internet complet. Elles ne se limitent pas aux dossiers partagés.',
50
+ 'shell.keep': 'Laisser ainsi', 'shell.keepNote': 'jusqu’au {date}',
51
+ 'shell.no': 'Pas maintenant', 'shell.noNote': 'une discussion demande d’abord quand elle en a besoin',
52
+ 'shell.off': 'Désactiver', 'shell.offNote': 'une discussion demande d’abord quand elle en a besoin',
53
+ 'shell.d7': 'Oui, pour 7 jours', 'shell.d30': 'Oui, pour 30 jours', 'shell.d365': 'Oui, pour un an',
54
+ 'review.title': 'Vérifier et enregistrer',
55
+ 'review.folders': 'Dossiers', 'review.commands': 'Commandes',
56
+ 'review.noFolders': 'aucun — une discussion demande quand elle en a besoin',
57
+ 'review.removed': 'approbation retirée',
58
+ 'review.cmdNo': 'non approuvées — une discussion demande d’abord',
59
+ 'review.cmdUntil': 'approuvées jusqu’au {date}',
60
+ 'review.save': 'Enregistrer', 'review.back': 'Retour', 'review.quit': 'Quitter sans enregistrer',
61
+ 'review.where': 'Enregistré uniquement sur cet ordinateur, dans {file}',
62
+ keyNames: { space: 'espace', enter: 'entrée', esc: 'échap', tab: 'tab', backspace: 'retour arrière' },
63
+ keys: { move: 'déplacer', select: 'valider', toggle: 'cocher', back: 'retour', quit: 'quitter', open: 'ouvrir', close: 'fermer', openClose: 'ouvrir/fermer', up: 'dossier parent', hidden: 'cachés', done: 'terminer', tab: 'compléter', add: 'ajouter' },
64
+ 'done.saved': 'Enregistré sur cet ordinateur.',
65
+ 'done.unchanged': 'Aucune modification.',
66
+ 'done.web': 'Quand une discussion sur tensor.chat partage l’un de ces dossiers, cet ordinateur le confirme de lui-même.',
67
+ 'done.run': 'Démarrez le Companion avec :',
68
+ 'done.noTty': 'La configuration nécessite un terminal interactif. Sans terminal, utilisez approve-folder et approve-shell.',
69
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+ // The setup's words in Hindi.
3
+
4
+ module.exports = {
5
+ setup: 'सेटअप',
6
+ steps: ['क्या', 'फ़ोल्डर', 'कमांड', 'सहेजें'],
7
+ 'start.title': 'AI इस कंप्यूटर पर क्या इस्तेमाल कर सकता है?',
8
+ 'start.folders': 'फ़ोल्डर चुनें', 'start.foldersNote': 'अनुशंसित',
9
+ 'start.nothing': 'अभी कुछ नहीं', 'start.nothingNote': 'फ़ोल्डर की ज़रूरत होने पर चैट पूछती है',
10
+ 'start.everything': 'सब कुछ', 'start.everythingNote': 'अनुशंसित नहीं',
11
+ 'start.help': 'AI फ़ाइलें केवल वहीं पढ़ता, बदलता और हटाता है जहाँ आप यहाँ अनुमति देते हैं।',
12
+ 'start.now': 'अभी स्वीकृत:',
13
+ 'start.later': 'इसे कभी भी बदलें:',
14
+ 'places.title': 'AI कौन-से फ़ोल्डर इस्तेमाल कर सकता है?',
15
+ 'places.loading': 'फ़ोल्डर खोजे जा रहे हैं…',
16
+ 'places.none': 'इस कंप्यूटर पर कोई सुझाव नहीं।',
17
+ 'places.browse': 'सभी फ़ोल्डर ब्राउज़ करें…', 'places.type': 'पथ लिखें…', 'places.continue': 'जारी रखें',
18
+ selected: 'चयनित: {n}',
19
+ 'note.git': '{n} Git प्रोजेक्ट', 'note.git1': '1 Git प्रोजेक्ट', 'note.project': 'Git प्रोजेक्ट',
20
+ 'note.web': 'वेबसाइटें', 'note.srv': 'सर्वर डेटा', 'note.opt': 'इंस्टॉल किए गए प्रोग्राम',
21
+ 'note.etc': 'सिस्टम सेटिंग्स', 'note.log': 'लॉग', 'note.home': 'आपका पूरा होम फ़ोल्डर',
22
+ 'note.readOnly': 'आपके लिए केवल पढ़ने योग्य', 'note.added': 'आपने जोड़ा', 'note.earlier': 'पहले से स्वीकृत',
23
+ 'note.inside': '{path} के अंदर',
24
+ 'help.home': '{home} में सब कुछ, छिपी सेटिंग्स और ~/.ssh जैसी कुंजियाँ भी।',
25
+ 'help.etc': 'पूरे सिस्टम की सेटिंग्स। वहाँ एक गलत बदलाव सर्वर को रोक सकता है।',
26
+ 'help.log': 'सिस्टम और उसकी सेवाओं के लॉग। गड़बड़ी ढूँढने में काम आते हैं।',
27
+ 'help.web': 'उन वेबसाइटों की फ़ाइलें जिन्हें यह सर्वर दिखाता है।',
28
+ 'help.srv': 'इस सर्वर की सेवाओं का डेटा।',
29
+ 'help.opt': 'पैकेज मैनेजर के बाहर इंस्टॉल किए गए प्रोग्राम।',
30
+ 'help.code': 'आपके कोड प्रोजेक्ट।',
31
+ 'help.readOnly': 'आप यहाँ पढ़ सकते हैं, लिख नहीं सकते। AI भी इतना ही कर सकता है, इससे ज़्यादा नहीं।',
32
+ 'help.browse': 'इस कंप्यूटर पर कोई भी फ़ोल्डर चुनें, जैसे किसी फ़ाइल मैनेजर में।',
33
+ 'help.type': 'पूरा पथ लिखें। Tab उसे पूरा करता है।',
34
+ 'help.continue': 'आगे: कमांड।',
35
+ 'browse.title': 'ब्राउज़ करें', 'browse.thisPc': 'यह पीसी',
36
+ 'browse.loading': 'लोड हो रहा है…', 'browse.empty': 'कोई फ़ोल्डर नहीं', 'browse.noAccess': 'पहुँच नहीं', 'browse.link': 'लिंक',
37
+ 'browse.blocked': '{path}, {parent} के अंदर है, और वह पहले से चुना हुआ है।',
38
+ 'browse.hidden': 'छिपे फ़ोल्डर दिखाए जा रहे हैं।', 'browse.hiddenOff': 'छिपे फ़ोल्डर नहीं दिखाए जा रहे।',
39
+ 'type.title': 'फ़ोल्डर का पथ लिखें', 'type.label': 'पथ',
40
+ 'type.notFound': '{path} नाम का कोई फ़ोल्डर नहीं है।',
41
+ 'everything.title': 'सब कुछ साझा करें?',
42
+ 'everything.body': 'AI इस कंप्यूटर पर हर वह फ़ाइल पढ़, बदल और हटा सकता है जिस तक {user} पहुँच सकता है।',
43
+ 'everything.root': 'आप root हैं: यानी सिस्टम की हर फ़ाइल, खुद सिस्टम भी।',
44
+ 'everything.free': 'इससे AI इस सर्वर पर खुलकर काम कर सकता है। अनुशंसित नहीं।',
45
+ 'everything.no': 'नहीं, वापस', 'everything.yes': 'हाँ, सब कुछ साझा करें',
46
+ 'everything.summary': 'इस कंप्यूटर पर सब कुछ',
47
+ 'shell.title': 'क्या AI यहाँ कमांड चला सकता है?',
48
+ 'shell.body': 'कमांड {user} के रूप में, पूरी इंटरनेट पहुँच के साथ चलते हैं। वे साझा फ़ोल्डरों तक सीमित नहीं हैं।',
49
+ 'shell.keep': 'जैसा है वैसा रखें', 'shell.keepNote': '{date} तक',
50
+ 'shell.no': 'अभी नहीं', 'shell.noNote': 'ज़रूरत होने पर चैट पहले पूछती है',
51
+ 'shell.off': 'बंद करें', 'shell.offNote': 'ज़रूरत होने पर चैट पहले पूछती है',
52
+ 'shell.d7': 'हाँ, 7 दिनों के लिए', 'shell.d30': 'हाँ, 30 दिनों के लिए', 'shell.d365': 'हाँ, एक साल के लिए',
53
+ 'review.title': 'जाँचें और सहेजें',
54
+ 'review.folders': 'फ़ोल्डर', 'review.commands': 'कमांड',
55
+ 'review.noFolders': 'कोई नहीं — ज़रूरत होने पर चैट पूछती है',
56
+ 'review.removed': 'स्वीकृति हटाई गई',
57
+ 'review.cmdNo': 'स्वीकृत नहीं — चैट पहले पूछती है',
58
+ 'review.cmdUntil': '{date} तक स्वीकृत',
59
+ 'review.save': 'सहेजें', 'review.back': 'वापस', 'review.quit': 'बिना सहेजे बाहर निकलें',
60
+ 'review.where': 'केवल इस कंप्यूटर पर सहेजा जाता है, {file} में',
61
+ keyNames: { space: 'स्पेस', enter: 'Enter', esc: 'Esc', tab: 'Tab', backspace: 'Backspace' },
62
+ keys: { move: 'चलें', select: 'चुनें', toggle: 'टिक करें', back: 'वापस', quit: 'बाहर निकलें', open: 'खोलें', close: 'बंद करें', openClose: 'खोलें/बंद करें', up: 'ऊपर का फ़ोल्डर', hidden: 'छिपे', done: 'हो गया', tab: 'पूरा करें', add: 'जोड़ें' },
63
+ 'done.saved': 'इस कंप्यूटर पर सहेजा गया।',
64
+ 'done.unchanged': 'कुछ नहीं बदला।',
65
+ 'done.web': 'जब tensor.chat पर कोई चैट इनमें से कोई फ़ोल्डर साझा करती है, तो यह कंप्यूटर खुद उसकी पुष्टि कर देता है।',
66
+ 'done.run': 'Companion को इससे शुरू करें:',
67
+ 'done.noTty': 'सेटअप के लिए इंटरैक्टिव टर्मिनल चाहिए। उसके बिना approve-folder और approve-shell इस्तेमाल करें।',
68
+ };