tabby-profile-bridge 0.2.2 → 0.2.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.2.3 — npm listing and screenshots
4
+ - Publish documentation refresh with two real Tabby import/export screenshots.
5
+ - Use absolute GitHub-hosted screenshot URLs so images render in the npm README.
6
+ - Include English and Portuguese READMEs in the npm package.
7
+ - No functional change to SSH/Telnet import/export.
8
+
3
9
  ## v0.2.2 — Bilingual interface and documentation
4
10
  - English-default UI with Portuguese translation following Tabby's effective locale.
5
11
  - Live updates for pt-BR and pt-PT, including automatic Tabby language selection.
package/README.md CHANGED
@@ -4,7 +4,35 @@
4
4
 
5
5
  Import and export SSH/Telnet profiles across Tabby, CSV, and MobaXterm while preserving nested folders.
6
6
 
7
- **by gugahartmann** · [Português (Brasil)](README.pt-BR.md)
7
+ **by gugahartmann** · [Português (Brasil)](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/README.pt-BR.md)
8
+ ## Latest release — v0.2.3
9
+
10
+ **Profile Bridge is now available as a bilingual SSH/Telnet session exchange plugin for Tabby**, with CSV and MobaXterm `.mxtsessions` support.
11
+
12
+ | | Feature | What it does |
13
+ | --- | --- | --- |
14
+ | ↔ | **Two-way exchange** | Export from Tabby and import the exported file back into Tabby or MobaXterm |
15
+ | 📁 | **Nested folder preservation** | Keep regional, location and device groups; optionally place an import/export inside a parent folder |
16
+ | 🔍 | **Review before importing** | Inspect name, IP/hostname, SSH port and username, then choose Keep, Add or Replace |
17
+ | 👤 | **Bulk username changes** | Update legacy or colleague-specific usernames before committing imported profiles |
18
+ | 🌐 | **EN / PT-BR** | Follow Tabby's language automatically; English is the fallback |
19
+ | 🔐 | **Privacy by default** | Opt in to usernames; passwords, private keys and key paths are not exported |
20
+
21
+ **Real-world check:** a Tabby → MobaXterm → Tabby exchange has been manually validated with the plugin. MobaXterm-specific advanced settings may still require manual review.
22
+
23
+ [**Download v0.2.3**](https://github.com/gugahartmann/Tabby-Profile-Bridge/releases/tag/v0.2.3) · [Release notes](docs/releases/v0.2.3.md) · [Community announcement](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/docs/TABBY_COMMUNITY_ANNOUNCEMENT.md)
24
+
25
+
26
+
27
+ ### Screenshots
28
+
29
+ **Export — choose exactly which groups and profiles to share**
30
+
31
+ ![Tabby Profile Bridge export: selected SSH profiles, nested folders, usernames and MobaXterm format](https://raw.githubusercontent.com/gugahartmann/Tabby-Profile-Bridge/main/docs/screenshots/tabby-export.png)
32
+
33
+ **Import — review profiles, edit usernames and decide how to handle existing connections**
34
+
35
+ ![Tabby Profile Bridge import: MobaXterm sessions and per-profile conflict review](https://raw.githubusercontent.com/gugahartmann/Tabby-Profile-Bridge/main/docs/screenshots/tabby-import.png)
8
36
 
9
37
  ## Features
10
38
 
@@ -20,7 +48,7 @@ Import and export SSH/Telnet profiles across Tabby, CSV, and MobaXterm while pre
20
48
 
21
49
  ## Install
22
50
 
23
- After the package is published on npm, find **tabby-profile-bridge** in Tabby → Settings → Plugins. You can also obtain an installable .tgz from [GitHub Releases](https://github.com/gugahartmann/Tabby-Profile-Bridge/releases), using a plugin installation workflow supported by your Tabby version.
51
+ When available in the npm registry, find **tabby-profile-bridge** in Tabby → Settings → Plugins. You can also obtain an installable .tgz from [GitHub Releases](https://github.com/gugahartmann/Tabby-Profile-Bridge/releases), using a plugin installation workflow supported by your Tabby version.
24
52
 
25
53
  Open **Settings → Profile Bridge** after enabling the plugin.
26
54
 
@@ -62,9 +90,9 @@ Import limit: **5 MB/file**, **10,000 profiles/file**, **32 folder levels**. Mob
62
90
 
63
91
  ## Documentation
64
92
 
65
- - [User Guide (English)](docs/USER_GUIDE.md)
66
- - [Guia do Usuário (Português do Brasil)](docs/GUIA_USUARIO.pt-BR.md)
67
- - [Changelog](CHANGELOG.md)
93
+ - [User Guide (English)](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/docs/USER_GUIDE.md)
94
+ - [Guia do Usuário (Português do Brasil)](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/docs/GUIA_USUARIO.pt-BR.md)
95
+ - [Changelog](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/CHANGELOG.md)
68
96
 
69
97
  ## Roadmap
70
98
 
package/README.pt-BR.md CHANGED
@@ -4,7 +4,35 @@
4
4
 
5
5
  Importe e exporte perfis SSH/Telnet entre o Tabby, arquivos CSV e o MobaXterm, preservando a hierarquia das pastas.
6
6
 
7
- **by gugahartmann** · [English](README.md)
7
+ **by gugahartmann** · [English](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/README.md)
8
+ ## Versão atual — v0.2.3
9
+
10
+ **O Profile Bridge permite trocar perfis SSH/Telnet entre o Tabby e outros terminais**, com suporte a CSV e arquivos `.mxtsessions` do MobaXterm.
11
+
12
+ | | Recurso | Como funciona |
13
+ | --- | --- | --- |
14
+ | ↔ | **Importação e exportação** | Exporte sessões do Tabby e importe o arquivo de volta para o Tabby ou para o MobaXterm |
15
+ | 📁 | **Hierarquia de pastas** | Preserve regiões, cidades e POPs, com opção de usar uma pasta principal para cada importação/exportação |
16
+ | 🔍 | **Prévia de conflitos** | Revise nome, IP/hostname, porta SSH e usuário antes de escolher Manter, Adicionar ou Substituir |
17
+ | 👤 | **Alteração em massa** | Substitua usuários antigos ou específicos de outros colegas antes de importar |
18
+ | 🌐 | **EN / PT-BR** | Interface no idioma selecionado no Tabby, com inglês como padrão |
19
+ | 🔐 | **Privacidade** | Exportação de usuários sob confirmação; sem senhas, chaves privadas ou caminhos de chaves |
20
+
21
+ **Validação prática:** o fluxo Tabby → MobaXterm → Tabby já foi testado manualmente com o plugin. Recursos avançados específicos do MobaXterm ainda podem exigir conferência individual.
22
+
23
+ [**Baixar v0.2.3**](https://github.com/gugahartmann/Tabby-Profile-Bridge/releases/tag/v0.2.3) · [Notas da versão](docs/releases/v0.2.3.md) · [Texto de divulgação](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/docs/TABBY_COMMUNITY_ANNOUNCEMENT.md)
24
+
25
+
26
+
27
+ ### Capturas de tela
28
+
29
+ **Exportação — escolha os grupos e perfis que deseja compartilhar**
30
+
31
+ ![Exportação no Tabby Profile Bridge: grupos, perfis SSH, usuários e formato MobaXterm](https://raw.githubusercontent.com/gugahartmann/Tabby-Profile-Bridge/main/docs/screenshots/tabby-export.png)
32
+
33
+ **Importação — revise os perfis, altere usuários e decida como tratar as conexões existentes**
34
+
35
+ ![Importação no Tabby Profile Bridge: sessões do MobaXterm e revisão de conflitos](https://raw.githubusercontent.com/gugahartmann/Tabby-Profile-Bridge/main/docs/screenshots/tabby-import.png)
8
36
 
9
37
  ## Funcionalidades
10
38
 
@@ -20,7 +48,7 @@ Importe e exporte perfis SSH/Telnet entre o Tabby, arquivos CSV e o MobaXterm, p
20
48
 
21
49
  ## Instalação
22
50
 
23
- Após a publicação no npm, procure **tabby-profile-bridge** em Tabby → Configurações → Plugins. Também é possível obter o pacote .tgz nas [releases do GitHub](https://github.com/gugahartmann/Tabby-Profile-Bridge/releases) e usar um procedimento de instalação de plugins compatível com sua versão do Tabby.
51
+ Quando estiver disponível no registro npm, procure **tabby-profile-bridge** em Tabby → Configurações → Plugins. Também é possível obter o pacote .tgz nas [releases do GitHub](https://github.com/gugahartmann/Tabby-Profile-Bridge/releases) e usar um procedimento de instalação de plugins compatível com sua versão do Tabby.
24
52
 
25
53
  Após ativar o plugin, abra **Configurações → Profile Bridge**.
26
54
 
@@ -62,9 +90,9 @@ Limites: **5 MB/arquivo**, **10.000 perfis/arquivo**, **32 níveis de pasta**. O
62
90
 
63
91
  ## Documentação
64
92
 
65
- - [Guia do Usuário (português)](docs/GUIA_USUARIO.pt-BR.md)
66
- - [User Guide (English)](docs/USER_GUIDE.md)
67
- - [Histórico de versões](CHANGELOG.md)
93
+ - [Guia do Usuário (português)](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/docs/GUIA_USUARIO.pt-BR.md)
94
+ - [User Guide (English)](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/docs/USER_GUIDE.md)
95
+ - [Histórico de versões](https://github.com/gugahartmann/Tabby-Profile-Bridge/blob/main/CHANGELOG.md)
68
96
 
69
97
  ## Próximas integrações
70
98
 
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- (()=>{var e={919(e,t,o){var r=o(784);e.exports=(r.default||r).apply(r,[])},784(e,t,o){o(766),e.exports=function(e){return""+'<div class="p-3"><div class="d-flex justify-content-between align-items-start mb-2"><h2 class="mb-0">Tabby Profile Bridge</h2><span class="small text-muted text-end">by gugahartmann · v{{version}}</span></div><p class="text-muted">{{t(\'description\')}}</p><div class="btn-group mb-3"><button class="btn" [class.btn-primary]="tab===&quot;export&quot;" [class.btn-outline-secondary]="tab!==&quot;export&quot;" (click)="tab=&quot;export&quot;">{{t(\'export\')}}</button><button class="btn" [class.btn-primary]="tab===&quot;import&quot;" [class.btn-outline-secondary]="tab!==&quot;import&quot;" (click)="tab=&quot;import&quot;">{{t(\'import\')}}</button></div><div class="alert alert-danger" *ngIf="error">{{error}}</div><div class="alert alert-success" *ngIf="info">{{info}}</div><div *ngIf="tab===&quot;export&quot;"><div class="d-flex justify-content-between align-items-center mb-2"><h4>{{t(\'selectFolders\')}}</h4><div><button class="btn btn-sm btn-outline-secondary me-2" (click)="selectAll(true)">{{t(\'selectAll\')}}</button><button class="btn btn-sm btn-outline-secondary" (click)="selectAll(false)">{{t(\'clear\')}}</button></div></div><input class="form-control mb-2" type="search" [attr.placeholder]="t(\'searchPlaceholder\')" [(ngModel)]="search"><div class="border rounded p-2 mb-3" style="max-height:320px;overflow-y:auto"><ng-container *ngFor="let node of visible()"><div class="d-flex align-items-center mb-1" *ngIf="matches(node.name)||!search"><button class="btn btn-sm btn-link p-0 me-1" *ngIf="node.children.length" (click)="node.expanded=!node.expanded">{{node.expanded ? \'▾\' : \'▸\'}}</button><span class="me-2" *ngIf="!node.children.length">📁</span><input class="form-check-input me-2" type="checkbox" [checked]="node.selected" [indeterminate]="node.partial" (change)="toggleGroup(node,$any($event.target).checked)"><strong>{{node.name}}</strong><small class="text-muted ms-2">({{node.count}})</small></div><ng-container *ngIf="node.expanded"><div class="d-flex align-items-center mb-1" *ngFor="let profile of node.profiles" [style.paddingLeft.px]="(node.level+1)*20+13"><ng-container *ngIf="matches(profile.name)||matches(profile.options?.host||&quot;&quot;)"><input class="form-check-input me-2" type="checkbox" [checked]="chosenIds.has(profile.id)" (change)="toggleProfile(profile,$any($event.target).checked)"><span class="me-2">🖥</span><span>{{profile.name}}</span><small class="text-muted ms-2">{{profile.type}} · {{profile.options?.host}}:{{profile.options?.port}}</small></ng-container></div></ng-container></ng-container></div><h4>{{t(\'exportUsers\')}}</h4><p class="text-muted">{{t(\'userOptIn\')}}</p><div class="border rounded p-2 mb-3" style="max-height:190px;overflow-y:auto"><div class="text-muted" *ngIf="!users.length">{{t(\'selectToListUsers\')}}</div><div class="form-check" *ngFor="let item of users"><input class="form-check-input" type="checkbox" [checked]="sharedUsers.has(item.user)" (change)="toggleUser(item.user,$any($event.target).checked)"><label class="form-check-label">{{item.user}} — {{item.count}} {{t(\'sessions\')}}</label></div></div><h4>{{t(\'exportFolder\')}}</h4><div class="form-check mb-2"><input class="form-check-input" type="radio" name="exportMode" value="root" [(ngModel)]="exportMode" id="exp-root"><label class="form-check-label" for="exp-root">{{t(\'preserveHierarchy\')}}</label></div><div class="form-check mb-2"><input class="form-check-input" type="radio" name="exportMode" value="folder" [(ngModel)]="exportMode" id="exp-folder"><label class="form-check-label" for="exp-folder">{{t(\'wrapExport\')}}</label></div><input class="form-control mb-3" *ngIf="exportMode===&quot;folder&quot;" [(ngModel)]="exportFolder" [attr.placeholder]="t(\'folderPlaceholder\')" maxlength="256"><div class="d-flex gap-2 align-items-center mb-3"><label class="form-label mb-0" for="bridge-format">{{t(\'format\')}}</label><select class="form-select" style="max-width:220px" id="bridge-format" [(ngModel)]="format"><option *ngFor="let adapter of supportedFormats" [value]="adapter.id">{{adapter.label}}</option></select></div><p class="small text-muted" *ngIf="format===&quot;csv&quot;">{{t(\'csvHelp\')}}</p><p class="small text-muted" *ngIf="format===&quot;mxtsessions&quot;">{{t(\'mobaHelp\')}}</p><button class="btn btn-primary" [disabled]="!selected.length||busy" (click)="exportFile()">{{t(\'exportProfiles\', {count: selected.length})}}</button></div><div *ngIf="tab===&quot;import&quot;"><h4>{{t(\'addFiles\')}}</h4><p class="text-muted">{{t(\'importDescription\')}}</p><input class="form-control mb-3" type="file" multiple [attr.accept]="acceptedExtensions" (change)="onFiles($event)"><div class="card mb-3" *ngFor="let file of files"><div class="card-body"><div class="d-flex justify-content-between"><h5>{{file.name}} — {{file.preview.length}} {{t(\'sessions\')}}</h5><button class="btn btn-sm btn-outline-danger" (click)="removeFile(file)">{{t(\'remove\')}}</button></div><div class="form-check"><input class="form-check-input" type="radio" [name]="&quot;mode-&quot;+file.name" value="root" [(ngModel)]="file.mode" (change)="refreshPreview(file)"><label class="form-check-label">{{t(\'importRoot\')}}</label></div><div class="form-check"><input class="form-check-input" type="radio" [name]="&quot;mode-&quot;+file.name" value="folder" [(ngModel)]="file.mode" (change)="refreshPreview(file)"><label class="form-check-label">{{t(\'importFolder\')}}</label></div><input class="form-control mt-2" *ngIf="file.mode===&quot;folder&quot;" [(ngModel)]="file.folder" (ngModelChange)="refreshPreview(file)" maxlength="256" [attr.placeholder]="t(\'parentFolderPlaceholder\')"><div class="small text-muted mt-2">{{t(\'matchingNames\', {count:countMatches(file)})}}</div><div class="alert alert-warning mt-2" *ngIf="file.warnings.length">{{t(\'mobaWarning\', {count:file.warnings.length, first:file.warnings[0]})}}</div></div></div><div *ngIf="files.length"><h4>{{t(\'bulkUser\')}}</h4><div class="row g-2 mb-3"><div class="col-md-4"><label class="form-label">{{t(\'originalUser\')}}</label><select class="form-select" [(ngModel)]="bulkFrom"><option value="*">{{t(\'allUsers\')}}</option><option *ngFor="let name of importedUsers" [value]="name">{{name || t(\'emptyUser\')}}</option></select></div><div class="col-md-4"><label class="form-label">{{t(\'newUser\')}}</label><input class="form-control" [(ngModel)]="bulkTo" maxlength="255" [attr.placeholder]="t(\'newUserPlaceholder\')"></div><div class="col-md-4 d-flex align-items-end"><button class="btn btn-outline-primary w-100" (click)="applyBulkUser()">{{t(\'applyUser\')}}</button></div></div><div class="d-flex justify-content-between align-items-center mb-2"><h4>{{t(\'reviewProfiles\', {count:importedCount})}}</h4><div class="btn-group"><button class="btn btn-sm btn-outline-secondary" (click)="setAllActions(&quot;skip&quot;)">{{t(\'skipAll\')}}</button><button class="btn btn-sm btn-outline-secondary" (click)="setAllActions(&quot;create&quot;)">{{t(\'createAll\')}}</button></div></div><div class="border rounded mb-3" style="max-height:370px;overflow:auto"><table class="table table-sm table-striped mb-0"><thead><tr><th>{{t(\'session\')}}</th><th>{{t(\'hostPort\')}}</th><th>{{t(\'username\')}}</th><th>{{t(\'match\')}}</th><th>{{t(\'action\')}}</th></tr></thead><tbody><ng-container *ngFor="let file of files"><tr *ngFor="let row of file.preview"><td><strong>{{row.name}}</strong><div class="small text-muted">{{row.path.join(\' / \') || t(\'root\')}}</div></td><td><span>{{row.options.host}}:{{row.options.port}}</span><div class="small text-muted">{{row.type}}</div></td><td><input class="form-control form-control-sm" [ngModel]="row.editedUser===undefined?row.options.user:row.editedUser" (ngModelChange)="row.editedUser=$event" maxlength="255"></td><td><span class="text-warning" *ngIf="row.matchedId">{{t(\'sameName\')}}</span><span class="text-muted" *ngIf="!row.matchedId&amp;&amp;row.sameEndpointId">{{t(\'sameEndpoint\')}}</span><span class="text-muted" *ngIf="!row.matchedId&amp;&amp;!row.sameEndpointId">{{t(\'new\')}}</span></td><td><select class="form-select form-select-sm" [(ngModel)]="row.action"><option value="skip">{{t(\'skip\')}}</option><option value="create">{{t(\'create\')}}</option><option *ngIf="row.matchedId" value="replace">{{t(\'replace\')}}</option></select></td></tr></ng-container></tbody></table></div><p class="small text-muted">{{t(\'replaceWarning\')}}</p><button class="btn btn-primary" [disabled]="!importCount||busy" (click)="performImport()">{{busy ? t(\'importing\') : t(\'applyChanges\', {count:importCount})}}</button></div></div></div>'}},766(e,t,o){"use strict";var r=Object.prototype.hasOwnProperty;function s(e,t){return Array.isArray(e)?function(e,t){for(var o,r="",n="",i=Array.isArray(t),a=0;a<e.length;a++)(o=s(e[a]))&&(i&&t[a]&&(o=l(o)),r=r+n+o,n=" ");return r}(e,t):e&&"object"==typeof e?function(e){var t="",o="";for(var s in e)s&&e[s]&&r.call(e,s)&&(t=t+o+s,o=" ");return t}(e):e||""}function n(e){if(!e)return"";if("object"==typeof e){var t="";for(var o in e)r.call(e,o)&&(t=t+o+":"+e[o]+";");return t}return e+""}function i(e,t,o,r){if(!1===t||null==t||!t&&("class"===e||"style"===e))return"";if(!0===t)return" "+(r?e:e+'="'+e+'"');var s=typeof t;return"object"!==s&&"function"!==s||"function"!=typeof t.toJSON||(t=t.toJSON()),"string"==typeof t||(t=JSON.stringify(t),o||-1===t.indexOf('"'))?(o&&(t=l(t))," "+e+'="'+t+'"'):" "+e+"='"+t.replace(/'/g,"&#39;")+"'"}t.merge=function e(t,o){if(1===arguments.length){for(var r=t[0],s=1;s<t.length;s++)r=e(r,t[s]);return r}for(var i in o)if("class"===i){var a=t[i]||[];t[i]=(Array.isArray(a)?a:[a]).concat(o[i]||[])}else if("style"===i){a=(a=n(t[i]))&&";"!==a[a.length-1]?a+";":a;var l=n(o[i]);l=l&&";"!==l[l.length-1]?l+";":l,t[i]=a+l}else t[i]=o[i];return t},t.classes=s,t.style=n,t.attr=i,t.attrs=function(e,t){var o="";for(var a in e)if(r.call(e,a)){var l=e[a];if("class"===a){o=i(a,l=s(l),!1,t)+o;continue}"style"===a&&(l=n(l)),o+=i(a,l,!1,t)}return o};var a=/["&<>]/;function l(e){var t=""+e,o=a.exec(t);if(!o)return e;var r,s,n,i="";for(r=o.index,s=0;r<t.length;r++){switch(t.charCodeAt(r)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}s!==r&&(i+=t.substring(s,r)),s=r+1,i+=n}return s!==r?i+t.substring(s,r):i}t.escape=l,t.rethrow=function e(t,r,s,n){if(!(t instanceof Error))throw t;if(!("undefined"==typeof window&&r||n))throw t.message+=" on line "+s,t;var i,a,l,c;try{n=n||o(21).readFileSync(r,{encoding:"utf8"}),i=3,a=n.split("\n"),l=Math.max(s-i,0),c=Math.min(a.length,s+i)}catch(o){return t.message+=" - could not read from "+r+" ("+o.message+")",void e(t,null,s)}i=a.slice(l,c).map(function(e,t){var o=t+l+1;return(o==s?" > ":" ")+o+"| "+e}).join("\n"),t.path=r;try{t.message=(r||"Pug")+":"+s+"\n"+i+"\n\n"+t.message}catch(e){}throw t}},353(e,t,o){"use strict";var r=this&&this.__decorate||function(e,t,o,r){var s,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(i=(n<3?s(i):n>3?s(t,o,i):s(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i},s=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(t,"__esModule",{value:!0}),t.BridgeComponent=void 0;const n=o(430),i=o(368),a=o(56),l=o(979),c=o(646);let p=class{constructor(e,t,r){this.profilesService=e,this.config=t,this.locale=r,this.tab="export",this.format="csv",this.supportedFormats=l.FORMATS,this.acceptedExtensions=l.FORMATS.map(e=>e.extension).join(","),this.profiles=[],this.groups=[],this.nodes=[],this.chosenIds=new Set,this.users=[],this.sharedUsers=new Set,this.search="",this.exportMode="root",this.exportFolder="Tabby Export",this.files=[],this.bulkFrom="*",this.bulkTo="",this.error="",this.info="",this.busy=!1,this.version=o(330).rE,this.language="en-US",this.language=this.locale.getLocale(),this.localeSubscription=this.locale.localeChanged$.subscribe(e=>{this.language=e})}t(e,t={}){return c.translate(this.language,e,t)}ngOnDestroy(){this.localeSubscription&&this.localeSubscription.unsubscribe()}async ngOnInit(){await this.reload()}async reload(){this.error="";const e=await this.profilesService.getProfiles({includeBuiltin:!1,clone:!0});this.profiles=e.filter(e=>!("ssh"!==e.type&&"telnet"!==e.type||e.isTemplate||e.isBuiltin)).map(e=>{const t=this.profilesService.getConfigProxyForProfile(e);return{...e,options:{...t.options}}}),this.groups=this.profilesService.getSyncProfileGroups();const t=new Set(this.groups.map(e=>e.id)),o=new Map;for(const e of this.groups){const r=t.has(e.parentGroupId)?e.parentGroupId:"";o.set(r,[...o.get(r)||[],e])}const r=(e,t,s)=>(o.get(e)||[]).filter(e=>!s.has(e.id)).map(e=>{const o=r(e.id,t+1,new Set([...s,e.id])),n=this.profiles.filter(t=>t.group===e.id);return{id:e.id,name:e.name,level:t,count:n.length+o.reduce((e,t)=>e+t.count,0),expanded:t<2,selected:!1,partial:!1,children:o,profiles:n}});this.nodes=r("",0,new Set);const s=this.profiles.filter(e=>!e.group||!t.has(e.group));s.length&&this.nodes.push({id:"__root__",name:this.t("ungrouped"),level:0,count:s.length,expanded:!0,selected:!1,partial:!1,children:[],profiles:s}),this.chosenIds.clear(),this.sharedUsers.clear(),this.refresh()}visible(){const e=[],t=o=>{e.push(o),o.expanded&&o.children.forEach(t)};return this.nodes.forEach(t),e}toggleGroup(e,t){const o=e=>{e.profiles.forEach(e=>t?this.chosenIds.add(e.id):this.chosenIds.delete(e.id)),e.children.forEach(o)};o(e),this.refresh()}toggleProfile(e,t){t?this.chosenIds.add(e.id):this.chosenIds.delete(e.id),this.refresh()}selectAll(e){this.chosenIds=new Set(e?this.profiles.map(e=>e.id):[]),this.refresh()}refresh(){const e=t=>{t.children.forEach(e);const o=t.profiles.filter(e=>this.chosenIds.has(e.id)).length;t.selected=o===t.profiles.length&&t.children.every(e=>e.selected)&&t.profiles.length+t.children.length>0,t.partial=!t.selected&&(o>0||t.children.some(e=>e.selected||e.partial))};this.nodes.forEach(e),this.users=a.usersOf(this.selected);const t=new Set(this.users.map(e=>e.user));for(const e of this.sharedUsers)t.has(e)||this.sharedUsers.delete(e)}get selected(){return this.profiles.filter(e=>this.chosenIds.has(e.id))}toggleUser(e,t){t?this.sharedUsers.add(e):this.sharedUsers.delete(e)}matches(e){return!this.search||e.toLowerCase().includes(this.search.toLowerCase())}download(e,t,o){const r=new Blob([t],{type:o}),s=URL.createObjectURL(r),n=document.createElement("a");n.href=s,n.download=e,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(s),1e3)}async exportFile(){this.error="",this.info="";try{const e="folder"===this.exportMode?this.exportFolder.trim():"";if(e&&(e.includes("/")||e.includes("\\")))throw Error(this.t("invalidFolder"));const t=a.rowsForExport(this.profiles,this.groups,this.chosenIds,this.sharedUsers,e),o=l.getFormat(this.format),r=o.encode(t);this.download("tabby-profile-bridge"+o.extension,r,o.mimeType),this.info=this.t("exportedSuccess",{count:t.length})}catch(e){this.error=String(e.message||e)}}async onFiles(e){this.error="",this.info="";const t=e.target,o=Array.from(t.files||[]);try{for(const e of o){if(e.size>a.MAX_BYTES)throw Error(this.t("tooLarge",{file:e.name}));const t=l.detectFormat(e.name),o=t.binary?new Uint8Array(await e.arrayBuffer()):await e.text(),r=t.decode(o),s=r.rows,n={name:e.name,mode:"root",folder:e.name.replace(/\.[^.]+$/,""),rows:s,preview:[],warnings:r.warnings};this.files.push(n),this.refreshPreview(n)}}catch(e){this.error=String(e.message||e)}t.value=""}refreshPreview(e){e.preview=a.makePreview(e.rows,this.profiles,this.groups,"folder"===e.mode?e.folder.trim():"")}removeFile(e){this.files=this.files.filter(t=>t!==e)}countMatches(e){return e.preview.filter(e=>Boolean(e.matchedId)).length}get importCount(){return this.files.reduce((e,t)=>e+t.preview.filter(e=>"skip"!==e.action).length,0)}get importedCount(){return this.files.reduce((e,t)=>e+t.preview.length,0)}applyBulkUser(){if(this.error="",this.bulkTo.length>255)this.error=this.t("tooLongUser");else for(const e of this.files)for(const t of e.preview)"skip"===t.action||"*"!==this.bulkFrom&&t.options.user!==this.bulkFrom||(t.editedUser=this.bulkTo)}get importedUsers(){const e=new Set;return this.files.forEach(t=>t.preview.forEach(t=>e.add(t.options.user||""))),[...e].sort()}setAllActions(e){for(const t of this.files)for(const o of t.preview)o.action="replace"!==e||o.matchedId?e:"create"}async performImport(){if(!this.busy){this.error="",this.info="",this.busy=!0;try{const e=this.files.flatMap(e=>e.preview.filter(e=>"skip"!==e.action));if(!e.length)throw Error(this.t("selectImport"));const t=()=>Math.random().toString(36).slice(2)+Date.now().toString(36),o=a.buildPlan(e,this.groups,t),r=[...this.config.store.profiles],s=[...this.config.store.groups];try{const e=new Map(o.replaces.map(e=>[e.id,e]));this.config.store.profiles=this.config.store.profiles.map(t=>e.get(t.id)||t),this.config.store.profiles.push(...o.creates),this.config.store.groups.push(...o.groups),await this.config.save()}catch(e){this.config.store.profiles=r,this.config.store.groups=s;try{await this.config.save()}catch(e){}throw e}this.info=this.t("importedSuccess",{created:o.creates.length,replaced:o.replaces.length,groups:o.groups.length}),this.files=[],await this.reload()}catch(e){this.error=String(e.message||e)}finally{this.busy=!1}}}};p=r([(0,n.Component)({template:o(919)}),s("design:paramtypes",[i.ProfilesService,i.ConfigService,i.LocaleService])],p),t.BridgeComponent=p},729(e,t,o){"use strict";var r=this&&this.__decorate||function(e,t,o,r){var s,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(i=(n<3?s(i):n>3?s(t,o,i):s(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};Object.defineProperty(t,"__esModule",{value:!0});const s=o(430),n=o(804),i=o(440),a=o(643),l=o(262),c=o(977),p=o(353);let d=class{};d=r([(0,s.NgModule)({imports:[n.CommonModule,i.FormsModule,a.NgbModule],declarations:[p.BridgeComponent],providers:[{provide:l.SettingsTabProvider,useClass:c.BridgeSettingsProvider,multi:!0}]})],d),t.default=d},977(e,t,o){"use strict";var r=this&&this.__decorate||function(e,t,o,r){var s,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(i=(n<3?s(i):n>3?s(t,o,i):s(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};Object.defineProperty(t,"__esModule",{value:!0}),t.BridgeSettingsProvider=void 0;const s=o(430),n=o(262),i=o(353);let a=class extends n.SettingsTabProvider{constructor(){super(...arguments),this.id="profile-bridge",this.title="Profile Bridge",this.icon="exchange-alt",this.weight=210}getComponentType(){return i.BridgeComponent}};a=r([(0,s.Injectable)()],a),t.BridgeSettingsProvider=a},56(e){"use strict";const t=["group1","group2","other_groups","profile","protocol","hostname","port","username","auth","algorithms","settings"],o=1e4,r=5242880,s=["hmac","kex","cipher","serverHostKey","compression"],n=["x11","skipBanner","agentForward","keepaliveInterval","keepaliveCountMax","readyTimeout","term","reuseSession","warnOnClose"],i=new Set(["ssh","telnet"]);function a(e){throw new Error(e)}function l(e,t){e||a(t)}function c(e,t=512){return"string"==typeof e&&e.length<=t}function p(e){return!!e&&"object"==typeof e&&!Array.isArray(e)&&Object.getPrototypeOf(e)===Object.prototype}function d(e){return e.startsWith("'")&&/^[=+@\- \t\r\n]*[=+@\-]/.test(e.slice(1))?e.slice(1):e}function u(e){if(!e)return[];const t=[];let o="",r=!1;for(const s of e)r?(o+=s,r=!1):"\\"===s?r=!0:"/"===s?(t.push(o.trim()),o=""):o+=s;return r&&(o+="\\"),t.push(o.trim()),l(t.every(Boolean),"Empty intermediate folder"),t}function h(e){return e.map(e=>e.replace(/\\/g,"\\\\").replace(/\//g,"\\/")).join(" / ")}function m(e,t){const o=(r=e.protocol,String(r||"ssh").toLowerCase().trim());var r;l(i.has(o),"Row "+t+": unsupported protocol"),l(c(e.profile)&&!!e.profile.trim(),"Row "+t+": missing profile name"),l(c(e.hostname,255)&&!!e.hostname.trim(),"Row "+t+": missing hostname"),l(c(e.username||"",255),"Row "+t+": invalid username"),l(/^\d{1,5}$/.test(String(e.port||""))&&+e.port>=1&&+e.port<=65535,"Row "+t+": invalid port");const d=[e.group1,e.group2,...u(e.other_groups)].filter(Boolean);l(d.length<=32&&d.every(e=>c(e,256)),"Row "+t+": invalid folder depth/name");let h={};if(e.algorithms){try{h=JSON.parse(e.algorithms)}catch{a("Row "+t+": invalid algorithms JSON")}l(p(h),"Row "+t+": invalid algorithms object");for(const[e,o]of Object.entries(h))l(s.includes(e)&&Array.isArray(o)&&o.length<=64&&o.every(e=>c(e,128)&&/^[a-zA-Z0-9_.@,+-]+$/.test(e)),"Row "+t+": invalid SSH algorithms")}let m={};if(e.settings){try{m=JSON.parse(e.settings)}catch{a("Row "+t+": invalid settings JSON")}l(p(m),"Row "+t+": invalid settings");for(const[e,o]of Object.entries(m))l(n.includes(e),"Row "+t+": unsupported setting "+e),["term"].includes(e)?l(c(o,80),"Invalid terminal"):["keepaliveInterval","keepaliveCountMax","readyTimeout"].includes(e)?l(null===o||Number.isInteger(o)&&o>=0&&o<=2147483647,"Invalid timeout"):l(null===o||"boolean"==typeof o,"Invalid boolean option")}return l(["","password","publicKey","agent","keyboardInteractive"].includes(e.auth||""),"Row "+t+": unsupported authentication"),{path:d,name:e.profile,type:o,options:{host:e.hostname,port:Number(e.port),user:e.username||"",...e.auth?{auth:e.auth}:{},...Object.keys(h).length?{algorithms:h}:{},...m}}}function f(e,t){const o=new Map(t.map(e=>[e.id,e])),r=[],s=new Set;for(;e&&o.has(e);){l(!s.has(e),"Circular group detected"),s.add(e);const t=o.get(e);l(c(t.name,256),"Invalid group name"),r.unshift(t.name),e=t.parentGroupId,l(r.length<=32,"Group depth exceeds 32 levels")}return r}function g(e,t){return[e.type,...t,e.name].map(e=>String(e||"").toLowerCase()).join("")}function v(e){const t=e.options||{};return[e.type,t.host||"",t.port||("ssh"===e.type?22:23),t.user||""].map(e=>String(e).toLowerCase()).join("")}e.exports={MAX_BYTES:r,MAX_ROWS:o,readCSV:function(e){const t=function(e){l(c(e,r),"CSV file is too large"),e=e.replace(/^\uFEFF/,"");const t=[];let o=[],s="",n=!1,i=!1;for(let r=0;r<e.length;r++){const a=e[r];n?'"'===a&&'"'===e[r+1]?(s+='"',r++):'"'===a?(n=!1,i=!0):s+=a:'"'!==a||s||i?","===a?(o.push(d(s)),s="",i=!1):"\n"===a||"\r"===a?("\r"===a&&"\n"===e[r+1]&&r++,o.push(d(s)),o.some(e=>""!==e)&&t.push(o),o=[],s="",i=!1):(l(!i||/\s/.test(a),"Unexpected text after CSV quote"),s+=a):n=!0,l(s.length<=1e4,"CSV cell is too long")}return l(!n,"Unclosed CSV quote"),(s||o.length)&&(o.push(d(s)),t.push(o)),t}(e);l(t.length>=1,"CSV file is empty");const s=t.shift().map(e=>e.trim().toLowerCase()),n={name:"profile",host:"hostname",user:"username",folder1:"group1",folder2:"group2",group3:"other_groups"},i=s.map(e=>n[e]||e);l(new Set(i).size===i.length,"Duplicate CSV headers");for(const e of["profile","hostname","port"])l(i.includes(e),"Required CSV column missing: "+e);return l(t.length<=o,"Too many sessions in CSV"),t.map((e,t)=>{l(e.length<=i.length,"Row "+(t+2)+": unexpected columns");const o={};return i.forEach((t,r)=>o[t]=e[r]||""),m(o,t+2)})},stringifyCSV:function(e){return"\ufeff"+[t.join(","),...e.map(e=>t.map(t=>{return o=function(e){return e=String(e??""),/^[=+@\- \t\r\n]*[=+@\-]/.test(e)?"'"+e:e}(o=e[t]),/[",\r\n]/.test(o)?'"'+o.replace(/"/g,'""')+'"':o;var o}).join(","))].join("\r\n")+"\r\n"},rowsForExport:function(e,t,r,a,d=""){const u=e.filter(e=>r.has(e.id)&&i.has(e.type));l(u.length&&u.length<=o,"Select 1 to 10000 connections");const g=u.map(e=>{const o=e.options||{};l(c(e.name)&&!!e.name.trim(),"Invalid name"),l(c(o.host,255)&&!!o.host.trim(),"Missing hostname: "+e.name);const r=[...d?[d]:[],...f(e.group,t)],i=function(e){const t=e.options||{},o={};if(p(t.algorithms))for(const e of s){const r=t.algorithms[e];Array.isArray(r)&&r.length<=64&&r.every(e=>c(e,128)&&/^[a-zA-Z0-9_.@,+-]+$/.test(e))&&r.length&&(o[e]=r)}const r={};for(const e of n){const o=t[e];void 0!==o&&("term"===e&&c(o,80)||["keepaliveInterval","keepaliveCountMax","readyTimeout"].includes(e)&&(null===o||Number.isInteger(o)&&o>=0&&o<=2147483647)?r[e]=o:null!==o&&"boolean"!=typeof o||(r[e]=o))}return{algorithms:o,settings:r}}(e);return{group1:r[0]||"",group2:r[1]||"",other_groups:h(r.slice(2)),profile:e.name,protocol:e.type,hostname:o.host,port:String(o.port||("ssh"===e.type?22:23)),username:a.has(o.user)?o.user:"",auth:["password","publicKey","agent","keyboardInteractive"].includes(o.auth)?o.auth:"",algorithms:Object.keys(i.algorithms).length?JSON.stringify(i.algorithms):"",settings:Object.keys(i.settings).length?JSON.stringify(i.settings):""}});return g.forEach((e,t)=>m(e,t+2)),g},usersOf:function(e){const t=new Map;return e.forEach(e=>{const o=e.options?.user;o&&t.set(o,(t.get(o)||0)+1)}),[...t].sort((e,t)=>e[0].localeCompare(t[0])).map(([e,t])=>({user:e,count:t}))},makePreview:function(e,t,o,r=""){return l(c(r,256),"Invalid wrapper folder"),e.map((e,s)=>{const n=[...r?[r]:[],...e.path],i=t.find(t=>g(t,f(t.group,o))===g(e,n)),a=t.find(t=>v(t)===v(e));return{...e,index:s,path:n,matchedId:i?.id||"",sameEndpointId:a?.id||"",action:i?"skip":"create"}})},buildPlan:function(e,t,o){l(Array.isArray(e)&&"function"==typeof o,"Invalid plan");const r=t.map(e=>({...e})),s=[],n=[],i=[],a=new Set;function p(e,t){return r.find(o=>o.name===e&&(o.parentGroupId||"")===(t||""))}for(const t of e){if("skip"===t.action)continue;l("create"===t.action||"replace"===t.action,"Invalid action"),l(!a.has(t.index),"Duplicate import row"),a.add(t.index);let e="";for(const n of t.path){let t=p(n,e);t||(t={id:o(),name:n,...e?{parentGroupId:e}:{}},r.push(t),s.push(t)),e=t.id}const d={type:t.type,name:t.name,group:e,options:{...t.options,...void 0!==t.editedUser?{user:t.editedUser}:{}}};l(c(d.options.user||"",255),"Invalid edited username"),"replace"===t.action?(l(t.matchedId,"Cannot replace without an exact match"),i.push({...d,id:t.matchedId})):n.push({...d,id:t.type+":custom:bridge:"+o()})}return{groups:s,creates:n,replaces:i}},validateRow:m,joinOther:h}},979(e,t,o){"use strict";const r=o(56),s=o(684),n=Object.freeze([{id:"csv",label:"CSV (.csv)",extension:".csv",mimeType:"text/csv;charset=utf-8",binary:!1,encode:e=>r.stringifyCSV(e),decode:e=>({rows:r.readCSV(e),warnings:[]})},{id:"mxtsessions",label:"MobaXterm (.mxtsessions)",extension:".mxtsessions",mimeType:"application/octet-stream",binary:!0,encode:e=>s.writeMoba(e),decode:e=>s.readMoba(e)}]);e.exports={FORMATS:n,getFormat:function(e){const t=n.find(t=>t.id===e);if(!t)throw new Error("Unsupported import/export format: "+e);return t},detectFormat:function(e){const t=String(e||"").toLowerCase(),o=n.find(e=>t.endsWith(e.extension));if(!o)throw new Error("Unsupported file format: "+e);return o}}},646(e){"use strict";const t={description:{en:"Import, export, and convert SSH/Telnet profiles between terminal clients. Passwords, login scripts, private keys, and local key paths are never exported.",pt:"Importe, exporte e converta perfis SSH/Telnet entre clientes de terminal. Senhas, scripts de login, chaves privadas e caminhos locais de chaves nunca são exportados."},export:{en:"Export",pt:"Exportar"},import:{en:"Import",pt:"Importar"},selectFolders:{en:"Select folders and sessions",pt:"Selecione pastas e sessões"},selectAll:{en:"Select all",pt:"Selecionar tudo"},clear:{en:"Clear",pt:"Limpar"},searchPlaceholder:{en:"Filter by name (selection is preserved)",pt:"Filtrar por nome (a seleção é preservada)"},ungrouped:{en:"Ungrouped",pt:"Sem grupo"},exportUsers:{en:"Export usernames",pt:"Exportar usuários"},userOptIn:{en:"Usernames are excluded by default. Explicitly select the shared service accounts to include.",pt:"Usuários são omitidos por padrão. Selecione explicitamente as contas compartilhadas que deseja incluir."},selectToListUsers:{en:"Select sessions to see their usernames.",pt:"Selecione sessões para listar os usuários."},sessions:{en:"sessions",pt:"sessões"},exportFolder:{en:"Export destination folder",pt:"Pasta de destino da exportação"},preserveHierarchy:{en:"Preserve hierarchy at root",pt:"Preservar hierarquia na raiz"},wrapExport:{en:"Place selected sessions inside one top-level folder",pt:"Colocar as sessões selecionadas dentro de uma pasta principal"},folderPlaceholder:{en:"Top-level folder name",pt:"Nome da pasta principal"},format:{en:"Format",pt:"Formato"},csvHelp:{en:"Algorithms and safe terminal options are stored as JSON strings in CSV cells. Group colors and icons are not exported.",pt:"Algoritmos e opções seguras de terminal são armazenados como texto JSON nas células do CSV. Cores e ícones dos grupos não são exportados."},mobaHelp:{en:"MobaXterm export preserves names, hierarchy, hosts, ports, usernames, and basic X11. Unsupported advanced options, colors, icons, and secrets are omitted.",pt:"A exportação MobaXterm preserva nomes, hierarquia, hosts, portas, usuários e X11 básico. Opções avançadas incompatíveis, cores, ícones e segredos são omitidos."},exportProfiles:{en:"Export {count} profiles",pt:"Exportar {count} perfis"},addFiles:{en:"Add CSV or MobaXterm files",pt:"Adicionar arquivos CSV ou MobaXterm"},importDescription:{en:"Import each file at the root or inside its own parent folder. Review conflicts before applying; existing profiles are never replaced automatically.",pt:"Importe cada arquivo na raiz ou dentro de sua própria pasta principal. Revise os conflitos antes de aplicar; perfis existentes nunca são substituídos automaticamente."},remove:{en:"Remove",pt:"Remover"},importRoot:{en:"Import at Tabby root (keep source hierarchy)",pt:"Importar na raiz do Tabby (manter hierarquia de origem)"},importFolder:{en:"Import inside one parent folder",pt:"Importar dentro de uma pasta principal"},parentFolderPlaceholder:{en:"Parent folder name",pt:"Nome da pasta principal"},matchingNames:{en:"{count} matching names in destination",pt:"{count} nomes coincidentes no destino"},mobaWarning:{en:"{count} MobaXterm entries/settings were not converted. First: {first}",pt:"{count} entradas/configurações do MobaXterm não foram convertidas. Primeira: {first}"},bulkUser:{en:"Bulk username conversion",pt:"Alteração de usuário em massa"},originalUser:{en:"Original username",pt:"Usuário original"},allUsers:{en:"All imported usernames",pt:"Todos os usuários importados"},emptyUser:{en:"(empty)",pt:"(vazio)"},newUser:{en:"New username",pt:"Novo usuário"},newUserPlaceholder:{en:"New SSH username",pt:"Novo usuário SSH"},applyUser:{en:"Apply to selected imports",pt:"Aplicar às importações selecionadas"},reviewProfiles:{en:"Review {count} imported profiles",pt:"Revisar {count} perfis importados"},skipAll:{en:"Skip all",pt:"Ignorar todos"},createAll:{en:"Create all",pt:"Criar todos"},session:{en:"Session",pt:"Sessão"},hostPort:{en:"Host : port",pt:"Host : porta"},username:{en:"Username",pt:"Usuário"},match:{en:"Existing match",pt:"Correspondência existente"},action:{en:"Action",pt:"Ação"},root:{en:"(root)",pt:"(raiz)"},sameName:{en:"Same name / folder",pt:"Mesmo nome / pasta"},sameEndpoint:{en:"Same endpoint elsewhere",pt:"Mesmo destino em outra pasta"},new:{en:"New",pt:"Novo"},skip:{en:"Keep / skip",pt:"Manter / ignorar"},create:{en:"Add as new",pt:"Adicionar como novo"},replace:{en:"Replace existing",pt:"Substituir existente"},replaceWarning:{en:"Replacing a profile removes its connection-specific credentials. Back up your Tabby configuration first. Imported files are never executed.",pt:"Substituir um perfil remove suas credenciais específicas. Faça backup da configuração do Tabby antes. Arquivos importados nunca são executados."},applyChanges:{en:"Apply {count} selected changes",pt:"Aplicar {count} alterações selecionadas"},importing:{en:"Importing...",pt:"Importando..."},exportedSuccess:{en:"Exported {count} sessions without passwords or private keys.",pt:"Exportadas {count} sessões sem senhas ou chaves privadas."},importedSuccess:{en:"Imported {created} new sessions, replaced {replaced}, created {groups} folders.",pt:"Importadas {created} sessões novas, substituídas {replaced} e criadas {groups} pastas."},invalidFolder:{en:"Container folder cannot contain slashes.",pt:"O nome da pasta principal não pode conter barras."},tooLarge:{en:"File exceeds 5 MB: {file}",pt:"O arquivo excede 5 MB: {file}"},tooLongUser:{en:"Username is too long.",pt:"O nome de usuário é muito longo."},selectImport:{en:"Select at least one profile to import.",pt:"Selecione pelo menos um perfil para importar."},errorPrefix:{en:"Operation failed: {error}",pt:"Falha na operação: {error}"}};e.exports={translate:function(e,o,r={}){const s=t[o];return(s?s[function(e){return"string"==typeof e&&/^pt(?:-|$)/i.test(e)?"pt":"en"}(e)]:o).replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g,(e,t)=>Object.prototype.hasOwnProperty.call(r,t)?String(r[t]):e)}}},684(e,t,o){"use strict";const r=o(56),s="0%localhost%22%%%-1%-1%%%%%0%0%0%%%-1%0%0%0%%1080%%0%0%1%%0%%%%0%-1%-1%0".split("%"),n="MobaFont%10%0%0%-1%15%236,236,236%30,30,30%180,180,192%0%-1%0%%xterm%-1%0%_Std_Colors_0_%80%24%0%0%-1%<none>%%0%0%-1%-1".split("%"),i=new Map([[8364,128],[8218,130],[402,131],[8222,132],[8230,133],[8224,134],[8225,135],[710,136],[8240,137],[352,138],[8249,139],[338,140],[381,142],[8216,145],[8217,146],[8220,147],[8221,148],[8226,149],[8211,150],[8212,151],[732,152],[8482,153],[353,154],[8250,155],[339,156],[382,158],[376,159]]);function a(e,t){if("string"!=typeof e||!e.trim()||/[\r\n=#%\\]/.test(e))throw Error("Invalid MobaXterm "+t+": rename to remove newlines, =, #, %, or backslash");return e}function l(e){if(!Array.isArray(e)||e.length>32)throw Error("Invalid MobaXterm group depth");for(const t of e)a(t,"folder");return e.join("\\")}function c(e){if(a(e.name,"session"),/[%#\r\n]/.test(e.options.host)||/[%#\r\n]/.test(e.options.user||""))throw Error("Unsupported MobaXterm delimiter in host/username");const t=e.options;if("telnet"===e.type)return e.name+"=#98#1%"+t.host+"%"+t.port+"%"+(t.user||"")+"%";if("ssh"!==e.type)throw Error("Unsupported MobaXterm protocol");const o=[...s],r=[...n];return o[1]=t.host,o[2]=String(t.port),o[3]=t.user||"",o[5]=t.x11?"-1":"0",o[12]=o[3]?"0":"-1",o[14]="",o[7]="",o[8]="",o[9]="",o[10]="",o[15]="",o[19]="0",o[20]="",o[22]="",o[26]="",void 0!==t.agentForward&&(o[34]=t.agentForward?"-1":"0"),["xterm","xterm-256color","vt100","vt220","vt400","xterm-r6"].includes(t.term)&&(r[13]=t.term),e.name+"=#109#"+o.join("%")+"#"+r.join("%")+"#0# #-1"}e.exports={readMoba:function(e){const t="string"==typeof e?e:(o=e,new TextDecoder("windows-1252",{fatal:!1}).decode(o));var o;if(t.length>r.MAX_BYTES)throw Error("MobaXterm file exceeds 5 MB");const s=[],n=[];let i=[],a=!1,l=!1;for(const e of t.replace(/^\uFEFF/,"").split(/\r?\n/)){const t=e.trim();if(!t||t.startsWith(";")||t.startsWith("#"))continue;if(/^\[/.test(t)){a=/^\[Bookmarks(?:_\d+)?\]$/i.test(t),a&&(l=!0,i=[]);continue}if(!a)continue;if(/^SubRep=/i.test(t)){const e=t.slice(t.indexOf("=")+1);if(i=e?e.split("\\"):[],i.length>32)throw Error("MobaXterm has more than 32 group levels");continue}if(/^(ImgNum|Expanded)=/i.test(t))continue;const o=t.indexOf("=");if(o<=0)continue;const c=t.slice(0,o).trim(),p=t.slice(o+1).trim(),d=/#(109|98)#([01])%/.exec(p);if(!d){n.push("Unsupported session type ignored: "+c);continue}const u="109"===d[1]&&"0"===d[2]?"ssh":"98"===d[1]&&"1"===d[2]?"telnet":null;if(!u){n.push("Unsupported session type ignored: "+c);continue}const h=p.slice(d.index).split("#")[2].split("%"),m={group1:i[0]||"",group2:i[1]||"",other_groups:r.joinOther(i.slice(2)),profile:c,protocol:u,hostname:h[1]||"",port:h[2]||String("ssh"===u?22:23),username:"<default>"===h[3]?"":h[3]||"",auth:"",algorithms:"",settings:""};if("ssh"===u){const e={};h.length>5&&(e.x11="-1"===h[5]),Object.keys(e).length&&(m.settings=JSON.stringify(e)),(h[7]||h[8]||h[14]||h[20]||h[26])&&n.push("Advanced settings not transferred: "+c)}try{s.push(r.validateRow(m,s.length+1))}catch(e){n.push("Ignored invalid session "+c+": "+String(e.message||e))}if(s.length>r.MAX_ROWS)throw Error("Too many MobaXterm sessions")}if(!l)throw Error("No MobaXterm [Bookmarks] sections found");return{rows:s,warnings:n}},writeMoba:function(e){if(!Array.isArray(e)||!e.length||e.length>r.MAX_ROWS)throw Error("Select 1 to 10000 profiles");const t=[],o=new Map;function s(e){const r=e.join("");if(!o.has(r)){const s={path:[...e],sessions:[]};t.push(s),o.set(r,s)}return o.get(r)}s([]);for(const t of e){const e=Array.isArray(t.path)?t.path:r.validateRow(t,1).path;l(e);for(let t=1;t<=e.length;t++)s(e.slice(0,t));const o=Array.isArray(t.path)?t:r.validateRow(t,1);s(e).sessions.push(c(o))}return function(e){const t=[];for(const o of e){const e=o.codePointAt(0);if(e<=127||e>=160&&e<=255)t.push(e);else{if(!i.has(e))throw Error("MobaXterm Windows-1252 cannot encode character U+"+e.toString(16).toUpperCase()+". Rename the affected group/session.");t.push(i.get(e))}}return new Uint8Array(t)}(t.map((e,t)=>[t?"[Bookmarks_"+t+"]":"[Bookmarks]","SubRep="+e.path.join("\\"),"ImgNum="+(t?"41":"42"),...e.sessions].join("\r\n")).join("\r\n")+"\r\n")}}},804(e){"use strict";e.exports=require("@angular/common")},430(e){"use strict";e.exports=require("@angular/core")},440(e){"use strict";e.exports=require("@angular/forms")},643(e){"use strict";e.exports=require("@ng-bootstrap/ng-bootstrap")},21(e){"use strict";e.exports=require("fs")},368(e){"use strict";e.exports=require("tabby-core")},262(e){"use strict";e.exports=require("tabby-settings")},330(e){"use strict";e.exports={rE:"0.2.2"}}};const t={};let o=function o(r){const s=t[r];if(void 0!==s)return s.exports;const n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,o),n.exports}(729);module.exports=o})();
1
+ (()=>{var e={919(e,t,o){var r=o(784);e.exports=(r.default||r).apply(r,[])},784(e,t,o){o(766),e.exports=function(e){return""+'<div class="p-3"><div class="d-flex justify-content-between align-items-start mb-2"><h2 class="mb-0">Tabby Profile Bridge</h2><span class="small text-muted text-end">by gugahartmann · v{{version}}</span></div><p class="text-muted">{{t(\'description\')}}</p><div class="btn-group mb-3"><button class="btn" [class.btn-primary]="tab===&quot;export&quot;" [class.btn-outline-secondary]="tab!==&quot;export&quot;" (click)="tab=&quot;export&quot;">{{t(\'export\')}}</button><button class="btn" [class.btn-primary]="tab===&quot;import&quot;" [class.btn-outline-secondary]="tab!==&quot;import&quot;" (click)="tab=&quot;import&quot;">{{t(\'import\')}}</button></div><div class="alert alert-danger" *ngIf="error">{{error}}</div><div class="alert alert-success" *ngIf="info">{{info}}</div><div *ngIf="tab===&quot;export&quot;"><div class="d-flex justify-content-between align-items-center mb-2"><h4>{{t(\'selectFolders\')}}</h4><div><button class="btn btn-sm btn-outline-secondary me-2" (click)="selectAll(true)">{{t(\'selectAll\')}}</button><button class="btn btn-sm btn-outline-secondary" (click)="selectAll(false)">{{t(\'clear\')}}</button></div></div><input class="form-control mb-2" type="search" [attr.placeholder]="t(\'searchPlaceholder\')" [(ngModel)]="search"><div class="border rounded p-2 mb-3" style="max-height:320px;overflow-y:auto"><ng-container *ngFor="let node of visible()"><div class="d-flex align-items-center mb-1" *ngIf="matches(node.name)||!search"><button class="btn btn-sm btn-link p-0 me-1" *ngIf="node.children.length" (click)="node.expanded=!node.expanded">{{node.expanded ? \'▾\' : \'▸\'}}</button><span class="me-2" *ngIf="!node.children.length">📁</span><input class="form-check-input me-2" type="checkbox" [checked]="node.selected" [indeterminate]="node.partial" (change)="toggleGroup(node,$any($event.target).checked)"><strong>{{node.name}}</strong><small class="text-muted ms-2">({{node.count}})</small></div><ng-container *ngIf="node.expanded"><div class="d-flex align-items-center mb-1" *ngFor="let profile of node.profiles" [style.paddingLeft.px]="(node.level+1)*20+13"><ng-container *ngIf="matches(profile.name)||matches(profile.options?.host||&quot;&quot;)"><input class="form-check-input me-2" type="checkbox" [checked]="chosenIds.has(profile.id)" (change)="toggleProfile(profile,$any($event.target).checked)"><span class="me-2">🖥</span><span>{{profile.name}}</span><small class="text-muted ms-2">{{profile.type}} · {{profile.options?.host}}:{{profile.options?.port}}</small></ng-container></div></ng-container></ng-container></div><h4>{{t(\'exportUsers\')}}</h4><p class="text-muted">{{t(\'userOptIn\')}}</p><div class="border rounded p-2 mb-3" style="max-height:190px;overflow-y:auto"><div class="text-muted" *ngIf="!users.length">{{t(\'selectToListUsers\')}}</div><div class="form-check" *ngFor="let item of users"><input class="form-check-input" type="checkbox" [checked]="sharedUsers.has(item.user)" (change)="toggleUser(item.user,$any($event.target).checked)"><label class="form-check-label">{{item.user}} — {{item.count}} {{t(\'sessions\')}}</label></div></div><h4>{{t(\'exportFolder\')}}</h4><div class="form-check mb-2"><input class="form-check-input" type="radio" name="exportMode" value="root" [(ngModel)]="exportMode" id="exp-root"><label class="form-check-label" for="exp-root">{{t(\'preserveHierarchy\')}}</label></div><div class="form-check mb-2"><input class="form-check-input" type="radio" name="exportMode" value="folder" [(ngModel)]="exportMode" id="exp-folder"><label class="form-check-label" for="exp-folder">{{t(\'wrapExport\')}}</label></div><input class="form-control mb-3" *ngIf="exportMode===&quot;folder&quot;" [(ngModel)]="exportFolder" [attr.placeholder]="t(\'folderPlaceholder\')" maxlength="256"><div class="d-flex gap-2 align-items-center mb-3"><label class="form-label mb-0" for="bridge-format">{{t(\'format\')}}</label><select class="form-select" style="max-width:220px" id="bridge-format" [(ngModel)]="format"><option *ngFor="let adapter of supportedFormats" [value]="adapter.id">{{adapter.label}}</option></select></div><p class="small text-muted" *ngIf="format===&quot;csv&quot;">{{t(\'csvHelp\')}}</p><p class="small text-muted" *ngIf="format===&quot;mxtsessions&quot;">{{t(\'mobaHelp\')}}</p><button class="btn btn-primary" [disabled]="!selected.length||busy" (click)="exportFile()">{{t(\'exportProfiles\', {count: selected.length})}}</button></div><div *ngIf="tab===&quot;import&quot;"><h4>{{t(\'addFiles\')}}</h4><p class="text-muted">{{t(\'importDescription\')}}</p><input class="form-control mb-3" type="file" multiple [attr.accept]="acceptedExtensions" (change)="onFiles($event)"><div class="card mb-3" *ngFor="let file of files"><div class="card-body"><div class="d-flex justify-content-between"><h5>{{file.name}} — {{file.preview.length}} {{t(\'sessions\')}}</h5><button class="btn btn-sm btn-outline-danger" (click)="removeFile(file)">{{t(\'remove\')}}</button></div><div class="form-check"><input class="form-check-input" type="radio" [name]="&quot;mode-&quot;+file.name" value="root" [(ngModel)]="file.mode" (change)="refreshPreview(file)"><label class="form-check-label">{{t(\'importRoot\')}}</label></div><div class="form-check"><input class="form-check-input" type="radio" [name]="&quot;mode-&quot;+file.name" value="folder" [(ngModel)]="file.mode" (change)="refreshPreview(file)"><label class="form-check-label">{{t(\'importFolder\')}}</label></div><input class="form-control mt-2" *ngIf="file.mode===&quot;folder&quot;" [(ngModel)]="file.folder" (ngModelChange)="refreshPreview(file)" maxlength="256" [attr.placeholder]="t(\'parentFolderPlaceholder\')"><div class="small text-muted mt-2">{{t(\'matchingNames\', {count:countMatches(file)})}}</div><div class="alert alert-warning mt-2" *ngIf="file.warnings.length">{{t(\'mobaWarning\', {count:file.warnings.length, first:file.warnings[0]})}}</div></div></div><div *ngIf="files.length"><h4>{{t(\'bulkUser\')}}</h4><div class="row g-2 mb-3"><div class="col-md-4"><label class="form-label">{{t(\'originalUser\')}}</label><select class="form-select" [(ngModel)]="bulkFrom"><option value="*">{{t(\'allUsers\')}}</option><option *ngFor="let name of importedUsers" [value]="name">{{name || t(\'emptyUser\')}}</option></select></div><div class="col-md-4"><label class="form-label">{{t(\'newUser\')}}</label><input class="form-control" [(ngModel)]="bulkTo" maxlength="255" [attr.placeholder]="t(\'newUserPlaceholder\')"></div><div class="col-md-4 d-flex align-items-end"><button class="btn btn-outline-primary w-100" (click)="applyBulkUser()">{{t(\'applyUser\')}}</button></div></div><div class="d-flex justify-content-between align-items-center mb-2"><h4>{{t(\'reviewProfiles\', {count:importedCount})}}</h4><div class="btn-group"><button class="btn btn-sm btn-outline-secondary" (click)="setAllActions(&quot;skip&quot;)">{{t(\'skipAll\')}}</button><button class="btn btn-sm btn-outline-secondary" (click)="setAllActions(&quot;create&quot;)">{{t(\'createAll\')}}</button></div></div><div class="border rounded mb-3" style="max-height:370px;overflow:auto"><table class="table table-sm table-striped mb-0"><thead><tr><th>{{t(\'session\')}}</th><th>{{t(\'hostPort\')}}</th><th>{{t(\'username\')}}</th><th>{{t(\'match\')}}</th><th>{{t(\'action\')}}</th></tr></thead><tbody><ng-container *ngFor="let file of files"><tr *ngFor="let row of file.preview"><td><strong>{{row.name}}</strong><div class="small text-muted">{{row.path.join(\' / \') || t(\'root\')}}</div></td><td><span>{{row.options.host}}:{{row.options.port}}</span><div class="small text-muted">{{row.type}}</div></td><td><input class="form-control form-control-sm" [ngModel]="row.editedUser===undefined?row.options.user:row.editedUser" (ngModelChange)="row.editedUser=$event" maxlength="255"></td><td><span class="text-warning" *ngIf="row.matchedId">{{t(\'sameName\')}}</span><span class="text-muted" *ngIf="!row.matchedId&amp;&amp;row.sameEndpointId">{{t(\'sameEndpoint\')}}</span><span class="text-muted" *ngIf="!row.matchedId&amp;&amp;!row.sameEndpointId">{{t(\'new\')}}</span></td><td><select class="form-select form-select-sm" [(ngModel)]="row.action"><option value="skip">{{t(\'skip\')}}</option><option value="create">{{t(\'create\')}}</option><option *ngIf="row.matchedId" value="replace">{{t(\'replace\')}}</option></select></td></tr></ng-container></tbody></table></div><p class="small text-muted">{{t(\'replaceWarning\')}}</p><button class="btn btn-primary" [disabled]="!importCount||busy" (click)="performImport()">{{busy ? t(\'importing\') : t(\'applyChanges\', {count:importCount})}}</button></div></div></div>'}},766(e,t,o){"use strict";var r=Object.prototype.hasOwnProperty;function s(e,t){return Array.isArray(e)?function(e,t){for(var o,r="",n="",i=Array.isArray(t),a=0;a<e.length;a++)(o=s(e[a]))&&(i&&t[a]&&(o=l(o)),r=r+n+o,n=" ");return r}(e,t):e&&"object"==typeof e?function(e){var t="",o="";for(var s in e)s&&e[s]&&r.call(e,s)&&(t=t+o+s,o=" ");return t}(e):e||""}function n(e){if(!e)return"";if("object"==typeof e){var t="";for(var o in e)r.call(e,o)&&(t=t+o+":"+e[o]+";");return t}return e+""}function i(e,t,o,r){if(!1===t||null==t||!t&&("class"===e||"style"===e))return"";if(!0===t)return" "+(r?e:e+'="'+e+'"');var s=typeof t;return"object"!==s&&"function"!==s||"function"!=typeof t.toJSON||(t=t.toJSON()),"string"==typeof t||(t=JSON.stringify(t),o||-1===t.indexOf('"'))?(o&&(t=l(t))," "+e+'="'+t+'"'):" "+e+"='"+t.replace(/'/g,"&#39;")+"'"}t.merge=function e(t,o){if(1===arguments.length){for(var r=t[0],s=1;s<t.length;s++)r=e(r,t[s]);return r}for(var i in o)if("class"===i){var a=t[i]||[];t[i]=(Array.isArray(a)?a:[a]).concat(o[i]||[])}else if("style"===i){a=(a=n(t[i]))&&";"!==a[a.length-1]?a+";":a;var l=n(o[i]);l=l&&";"!==l[l.length-1]?l+";":l,t[i]=a+l}else t[i]=o[i];return t},t.classes=s,t.style=n,t.attr=i,t.attrs=function(e,t){var o="";for(var a in e)if(r.call(e,a)){var l=e[a];if("class"===a){o=i(a,l=s(l),!1,t)+o;continue}"style"===a&&(l=n(l)),o+=i(a,l,!1,t)}return o};var a=/["&<>]/;function l(e){var t=""+e,o=a.exec(t);if(!o)return e;var r,s,n,i="";for(r=o.index,s=0;r<t.length;r++){switch(t.charCodeAt(r)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}s!==r&&(i+=t.substring(s,r)),s=r+1,i+=n}return s!==r?i+t.substring(s,r):i}t.escape=l,t.rethrow=function e(t,r,s,n){if(!(t instanceof Error))throw t;if(!("undefined"==typeof window&&r||n))throw t.message+=" on line "+s,t;var i,a,l,c;try{n=n||o(21).readFileSync(r,{encoding:"utf8"}),i=3,a=n.split("\n"),l=Math.max(s-i,0),c=Math.min(a.length,s+i)}catch(o){return t.message+=" - could not read from "+r+" ("+o.message+")",void e(t,null,s)}i=a.slice(l,c).map(function(e,t){var o=t+l+1;return(o==s?" > ":" ")+o+"| "+e}).join("\n"),t.path=r;try{t.message=(r||"Pug")+":"+s+"\n"+i+"\n\n"+t.message}catch(e){}throw t}},353(e,t,o){"use strict";var r=this&&this.__decorate||function(e,t,o,r){var s,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(i=(n<3?s(i):n>3?s(t,o,i):s(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i},s=this&&this.__metadata||function(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)};Object.defineProperty(t,"__esModule",{value:!0}),t.BridgeComponent=void 0;const n=o(430),i=o(368),a=o(56),l=o(979),c=o(646);let p=class{constructor(e,t,r){this.profilesService=e,this.config=t,this.locale=r,this.tab="export",this.format="csv",this.supportedFormats=l.FORMATS,this.acceptedExtensions=l.FORMATS.map(e=>e.extension).join(","),this.profiles=[],this.groups=[],this.nodes=[],this.chosenIds=new Set,this.users=[],this.sharedUsers=new Set,this.search="",this.exportMode="root",this.exportFolder="Tabby Export",this.files=[],this.bulkFrom="*",this.bulkTo="",this.error="",this.info="",this.busy=!1,this.version=o(330).rE,this.language="en-US",this.language=this.locale.getLocale(),this.localeSubscription=this.locale.localeChanged$.subscribe(e=>{this.language=e})}t(e,t={}){return c.translate(this.language,e,t)}ngOnDestroy(){this.localeSubscription&&this.localeSubscription.unsubscribe()}async ngOnInit(){await this.reload()}async reload(){this.error="";const e=await this.profilesService.getProfiles({includeBuiltin:!1,clone:!0});this.profiles=e.filter(e=>!("ssh"!==e.type&&"telnet"!==e.type||e.isTemplate||e.isBuiltin)).map(e=>{const t=this.profilesService.getConfigProxyForProfile(e);return{...e,options:{...t.options}}}),this.groups=this.profilesService.getSyncProfileGroups();const t=new Set(this.groups.map(e=>e.id)),o=new Map;for(const e of this.groups){const r=t.has(e.parentGroupId)?e.parentGroupId:"";o.set(r,[...o.get(r)||[],e])}const r=(e,t,s)=>(o.get(e)||[]).filter(e=>!s.has(e.id)).map(e=>{const o=r(e.id,t+1,new Set([...s,e.id])),n=this.profiles.filter(t=>t.group===e.id);return{id:e.id,name:e.name,level:t,count:n.length+o.reduce((e,t)=>e+t.count,0),expanded:t<2,selected:!1,partial:!1,children:o,profiles:n}});this.nodes=r("",0,new Set);const s=this.profiles.filter(e=>!e.group||!t.has(e.group));s.length&&this.nodes.push({id:"__root__",name:this.t("ungrouped"),level:0,count:s.length,expanded:!0,selected:!1,partial:!1,children:[],profiles:s}),this.chosenIds.clear(),this.sharedUsers.clear(),this.refresh()}visible(){const e=[],t=o=>{e.push(o),o.expanded&&o.children.forEach(t)};return this.nodes.forEach(t),e}toggleGroup(e,t){const o=e=>{e.profiles.forEach(e=>t?this.chosenIds.add(e.id):this.chosenIds.delete(e.id)),e.children.forEach(o)};o(e),this.refresh()}toggleProfile(e,t){t?this.chosenIds.add(e.id):this.chosenIds.delete(e.id),this.refresh()}selectAll(e){this.chosenIds=new Set(e?this.profiles.map(e=>e.id):[]),this.refresh()}refresh(){const e=t=>{t.children.forEach(e);const o=t.profiles.filter(e=>this.chosenIds.has(e.id)).length;t.selected=o===t.profiles.length&&t.children.every(e=>e.selected)&&t.profiles.length+t.children.length>0,t.partial=!t.selected&&(o>0||t.children.some(e=>e.selected||e.partial))};this.nodes.forEach(e),this.users=a.usersOf(this.selected);const t=new Set(this.users.map(e=>e.user));for(const e of this.sharedUsers)t.has(e)||this.sharedUsers.delete(e)}get selected(){return this.profiles.filter(e=>this.chosenIds.has(e.id))}toggleUser(e,t){t?this.sharedUsers.add(e):this.sharedUsers.delete(e)}matches(e){return!this.search||e.toLowerCase().includes(this.search.toLowerCase())}download(e,t,o){const r=new Blob([t],{type:o}),s=URL.createObjectURL(r),n=document.createElement("a");n.href=s,n.download=e,document.body.appendChild(n),n.click(),n.remove(),setTimeout(()=>URL.revokeObjectURL(s),1e3)}async exportFile(){this.error="",this.info="";try{const e="folder"===this.exportMode?this.exportFolder.trim():"";if(e&&(e.includes("/")||e.includes("\\")))throw Error(this.t("invalidFolder"));const t=a.rowsForExport(this.profiles,this.groups,this.chosenIds,this.sharedUsers,e),o=l.getFormat(this.format),r=o.encode(t);this.download("tabby-profile-bridge"+o.extension,r,o.mimeType),this.info=this.t("exportedSuccess",{count:t.length})}catch(e){this.error=String(e.message||e)}}async onFiles(e){this.error="",this.info="";const t=e.target,o=Array.from(t.files||[]);try{for(const e of o){if(e.size>a.MAX_BYTES)throw Error(this.t("tooLarge",{file:e.name}));const t=l.detectFormat(e.name),o=t.binary?new Uint8Array(await e.arrayBuffer()):await e.text(),r=t.decode(o),s=r.rows,n={name:e.name,mode:"root",folder:e.name.replace(/\.[^.]+$/,""),rows:s,preview:[],warnings:r.warnings};this.files.push(n),this.refreshPreview(n)}}catch(e){this.error=String(e.message||e)}t.value=""}refreshPreview(e){e.preview=a.makePreview(e.rows,this.profiles,this.groups,"folder"===e.mode?e.folder.trim():"")}removeFile(e){this.files=this.files.filter(t=>t!==e)}countMatches(e){return e.preview.filter(e=>Boolean(e.matchedId)).length}get importCount(){return this.files.reduce((e,t)=>e+t.preview.filter(e=>"skip"!==e.action).length,0)}get importedCount(){return this.files.reduce((e,t)=>e+t.preview.length,0)}applyBulkUser(){if(this.error="",this.bulkTo.length>255)this.error=this.t("tooLongUser");else for(const e of this.files)for(const t of e.preview)"skip"===t.action||"*"!==this.bulkFrom&&t.options.user!==this.bulkFrom||(t.editedUser=this.bulkTo)}get importedUsers(){const e=new Set;return this.files.forEach(t=>t.preview.forEach(t=>e.add(t.options.user||""))),[...e].sort()}setAllActions(e){for(const t of this.files)for(const o of t.preview)o.action="replace"!==e||o.matchedId?e:"create"}async performImport(){if(!this.busy){this.error="",this.info="",this.busy=!0;try{const e=this.files.flatMap(e=>e.preview.filter(e=>"skip"!==e.action));if(!e.length)throw Error(this.t("selectImport"));const t=()=>Math.random().toString(36).slice(2)+Date.now().toString(36),o=a.buildPlan(e,this.groups,t),r=[...this.config.store.profiles],s=[...this.config.store.groups];try{const e=new Map(o.replaces.map(e=>[e.id,e]));this.config.store.profiles=this.config.store.profiles.map(t=>e.get(t.id)||t),this.config.store.profiles.push(...o.creates),this.config.store.groups.push(...o.groups),await this.config.save()}catch(e){this.config.store.profiles=r,this.config.store.groups=s;try{await this.config.save()}catch(e){}throw e}this.info=this.t("importedSuccess",{created:o.creates.length,replaced:o.replaces.length,groups:o.groups.length}),this.files=[],await this.reload()}catch(e){this.error=String(e.message||e)}finally{this.busy=!1}}}};p=r([(0,n.Component)({template:o(919)}),s("design:paramtypes",[i.ProfilesService,i.ConfigService,i.LocaleService])],p),t.BridgeComponent=p},729(e,t,o){"use strict";var r=this&&this.__decorate||function(e,t,o,r){var s,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(i=(n<3?s(i):n>3?s(t,o,i):s(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};Object.defineProperty(t,"__esModule",{value:!0});const s=o(430),n=o(804),i=o(440),a=o(643),l=o(262),c=o(977),p=o(353);let d=class{};d=r([(0,s.NgModule)({imports:[n.CommonModule,i.FormsModule,a.NgbModule],declarations:[p.BridgeComponent],providers:[{provide:l.SettingsTabProvider,useClass:c.BridgeSettingsProvider,multi:!0}]})],d),t.default=d},977(e,t,o){"use strict";var r=this&&this.__decorate||function(e,t,o,r){var s,n=arguments.length,i=n<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,o,r);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(i=(n<3?s(i):n>3?s(t,o,i):s(t,o))||i);return n>3&&i&&Object.defineProperty(t,o,i),i};Object.defineProperty(t,"__esModule",{value:!0}),t.BridgeSettingsProvider=void 0;const s=o(430),n=o(262),i=o(353);let a=class extends n.SettingsTabProvider{constructor(){super(...arguments),this.id="profile-bridge",this.title="Profile Bridge",this.icon="exchange-alt",this.weight=210}getComponentType(){return i.BridgeComponent}};a=r([(0,s.Injectable)()],a),t.BridgeSettingsProvider=a},56(e){"use strict";const t=["group1","group2","other_groups","profile","protocol","hostname","port","username","auth","algorithms","settings"],o=1e4,r=5242880,s=["hmac","kex","cipher","serverHostKey","compression"],n=["x11","skipBanner","agentForward","keepaliveInterval","keepaliveCountMax","readyTimeout","term","reuseSession","warnOnClose"],i=new Set(["ssh","telnet"]);function a(e){throw new Error(e)}function l(e,t){e||a(t)}function c(e,t=512){return"string"==typeof e&&e.length<=t}function p(e){return!!e&&"object"==typeof e&&!Array.isArray(e)&&Object.getPrototypeOf(e)===Object.prototype}function d(e){return e.startsWith("'")&&/^[=+@\- \t\r\n]*[=+@\-]/.test(e.slice(1))?e.slice(1):e}function u(e){if(!e)return[];const t=[];let o="",r=!1;for(const s of e)r?(o+=s,r=!1):"\\"===s?r=!0:"/"===s?(t.push(o.trim()),o=""):o+=s;return r&&(o+="\\"),t.push(o.trim()),l(t.every(Boolean),"Empty intermediate folder"),t}function h(e){return e.map(e=>e.replace(/\\/g,"\\\\").replace(/\//g,"\\/")).join(" / ")}function m(e,t){const o=(r=e.protocol,String(r||"ssh").toLowerCase().trim());var r;l(i.has(o),"Row "+t+": unsupported protocol"),l(c(e.profile)&&!!e.profile.trim(),"Row "+t+": missing profile name"),l(c(e.hostname,255)&&!!e.hostname.trim(),"Row "+t+": missing hostname"),l(c(e.username||"",255),"Row "+t+": invalid username"),l(/^\d{1,5}$/.test(String(e.port||""))&&+e.port>=1&&+e.port<=65535,"Row "+t+": invalid port");const d=[e.group1,e.group2,...u(e.other_groups)].filter(Boolean);l(d.length<=32&&d.every(e=>c(e,256)),"Row "+t+": invalid folder depth/name");let h={};if(e.algorithms){try{h=JSON.parse(e.algorithms)}catch{a("Row "+t+": invalid algorithms JSON")}l(p(h),"Row "+t+": invalid algorithms object");for(const[e,o]of Object.entries(h))l(s.includes(e)&&Array.isArray(o)&&o.length<=64&&o.every(e=>c(e,128)&&/^[a-zA-Z0-9_.@,+-]+$/.test(e)),"Row "+t+": invalid SSH algorithms")}let m={};if(e.settings){try{m=JSON.parse(e.settings)}catch{a("Row "+t+": invalid settings JSON")}l(p(m),"Row "+t+": invalid settings");for(const[e,o]of Object.entries(m))l(n.includes(e),"Row "+t+": unsupported setting "+e),["term"].includes(e)?l(c(o,80),"Invalid terminal"):["keepaliveInterval","keepaliveCountMax","readyTimeout"].includes(e)?l(null===o||Number.isInteger(o)&&o>=0&&o<=2147483647,"Invalid timeout"):l(null===o||"boolean"==typeof o,"Invalid boolean option")}return l(["","password","publicKey","agent","keyboardInteractive"].includes(e.auth||""),"Row "+t+": unsupported authentication"),{path:d,name:e.profile,type:o,options:{host:e.hostname,port:Number(e.port),user:e.username||"",...e.auth?{auth:e.auth}:{},...Object.keys(h).length?{algorithms:h}:{},...m}}}function f(e,t){const o=new Map(t.map(e=>[e.id,e])),r=[],s=new Set;for(;e&&o.has(e);){l(!s.has(e),"Circular group detected"),s.add(e);const t=o.get(e);l(c(t.name,256),"Invalid group name"),r.unshift(t.name),e=t.parentGroupId,l(r.length<=32,"Group depth exceeds 32 levels")}return r}function g(e,t){return[e.type,...t,e.name].map(e=>String(e||"").toLowerCase()).join("")}function v(e){const t=e.options||{};return[e.type,t.host||"",t.port||("ssh"===e.type?22:23),t.user||""].map(e=>String(e).toLowerCase()).join("")}e.exports={MAX_BYTES:r,MAX_ROWS:o,readCSV:function(e){const t=function(e){l(c(e,r),"CSV file is too large"),e=e.replace(/^\uFEFF/,"");const t=[];let o=[],s="",n=!1,i=!1;for(let r=0;r<e.length;r++){const a=e[r];n?'"'===a&&'"'===e[r+1]?(s+='"',r++):'"'===a?(n=!1,i=!0):s+=a:'"'!==a||s||i?","===a?(o.push(d(s)),s="",i=!1):"\n"===a||"\r"===a?("\r"===a&&"\n"===e[r+1]&&r++,o.push(d(s)),o.some(e=>""!==e)&&t.push(o),o=[],s="",i=!1):(l(!i||/\s/.test(a),"Unexpected text after CSV quote"),s+=a):n=!0,l(s.length<=1e4,"CSV cell is too long")}return l(!n,"Unclosed CSV quote"),(s||o.length)&&(o.push(d(s)),t.push(o)),t}(e);l(t.length>=1,"CSV file is empty");const s=t.shift().map(e=>e.trim().toLowerCase()),n={name:"profile",host:"hostname",user:"username",folder1:"group1",folder2:"group2",group3:"other_groups"},i=s.map(e=>n[e]||e);l(new Set(i).size===i.length,"Duplicate CSV headers");for(const e of["profile","hostname","port"])l(i.includes(e),"Required CSV column missing: "+e);return l(t.length<=o,"Too many sessions in CSV"),t.map((e,t)=>{l(e.length<=i.length,"Row "+(t+2)+": unexpected columns");const o={};return i.forEach((t,r)=>o[t]=e[r]||""),m(o,t+2)})},stringifyCSV:function(e){return"\ufeff"+[t.join(","),...e.map(e=>t.map(t=>{return o=function(e){return e=String(e??""),/^[=+@\- \t\r\n]*[=+@\-]/.test(e)?"'"+e:e}(o=e[t]),/[",\r\n]/.test(o)?'"'+o.replace(/"/g,'""')+'"':o;var o}).join(","))].join("\r\n")+"\r\n"},rowsForExport:function(e,t,r,a,d=""){const u=e.filter(e=>r.has(e.id)&&i.has(e.type));l(u.length&&u.length<=o,"Select 1 to 10000 connections");const g=u.map(e=>{const o=e.options||{};l(c(e.name)&&!!e.name.trim(),"Invalid name"),l(c(o.host,255)&&!!o.host.trim(),"Missing hostname: "+e.name);const r=[...d?[d]:[],...f(e.group,t)],i=function(e){const t=e.options||{},o={};if(p(t.algorithms))for(const e of s){const r=t.algorithms[e];Array.isArray(r)&&r.length<=64&&r.every(e=>c(e,128)&&/^[a-zA-Z0-9_.@,+-]+$/.test(e))&&r.length&&(o[e]=r)}const r={};for(const e of n){const o=t[e];void 0!==o&&("term"===e&&c(o,80)||["keepaliveInterval","keepaliveCountMax","readyTimeout"].includes(e)&&(null===o||Number.isInteger(o)&&o>=0&&o<=2147483647)?r[e]=o:null!==o&&"boolean"!=typeof o||(r[e]=o))}return{algorithms:o,settings:r}}(e);return{group1:r[0]||"",group2:r[1]||"",other_groups:h(r.slice(2)),profile:e.name,protocol:e.type,hostname:o.host,port:String(o.port||("ssh"===e.type?22:23)),username:a.has(o.user)?o.user:"",auth:["password","publicKey","agent","keyboardInteractive"].includes(o.auth)?o.auth:"",algorithms:Object.keys(i.algorithms).length?JSON.stringify(i.algorithms):"",settings:Object.keys(i.settings).length?JSON.stringify(i.settings):""}});return g.forEach((e,t)=>m(e,t+2)),g},usersOf:function(e){const t=new Map;return e.forEach(e=>{const o=e.options?.user;o&&t.set(o,(t.get(o)||0)+1)}),[...t].sort((e,t)=>e[0].localeCompare(t[0])).map(([e,t])=>({user:e,count:t}))},makePreview:function(e,t,o,r=""){return l(c(r,256),"Invalid wrapper folder"),e.map((e,s)=>{const n=[...r?[r]:[],...e.path],i=t.find(t=>g(t,f(t.group,o))===g(e,n)),a=t.find(t=>v(t)===v(e));return{...e,index:s,path:n,matchedId:i?.id||"",sameEndpointId:a?.id||"",action:i?"skip":"create"}})},buildPlan:function(e,t,o){l(Array.isArray(e)&&"function"==typeof o,"Invalid plan");const r=t.map(e=>({...e})),s=[],n=[],i=[],a=new Set;function p(e,t){return r.find(o=>o.name===e&&(o.parentGroupId||"")===(t||""))}for(const t of e){if("skip"===t.action)continue;l("create"===t.action||"replace"===t.action,"Invalid action"),l(!a.has(t.index),"Duplicate import row"),a.add(t.index);let e="";for(const n of t.path){let t=p(n,e);t||(t={id:o(),name:n,...e?{parentGroupId:e}:{}},r.push(t),s.push(t)),e=t.id}const d={type:t.type,name:t.name,group:e,options:{...t.options,...void 0!==t.editedUser?{user:t.editedUser}:{}}};l(c(d.options.user||"",255),"Invalid edited username"),"replace"===t.action?(l(t.matchedId,"Cannot replace without an exact match"),i.push({...d,id:t.matchedId})):n.push({...d,id:t.type+":custom:bridge:"+o()})}return{groups:s,creates:n,replaces:i}},validateRow:m,joinOther:h}},979(e,t,o){"use strict";const r=o(56),s=o(684),n=Object.freeze([{id:"csv",label:"CSV (.csv)",extension:".csv",mimeType:"text/csv;charset=utf-8",binary:!1,encode:e=>r.stringifyCSV(e),decode:e=>({rows:r.readCSV(e),warnings:[]})},{id:"mxtsessions",label:"MobaXterm (.mxtsessions)",extension:".mxtsessions",mimeType:"application/octet-stream",binary:!0,encode:e=>s.writeMoba(e),decode:e=>s.readMoba(e)}]);e.exports={FORMATS:n,getFormat:function(e){const t=n.find(t=>t.id===e);if(!t)throw new Error("Unsupported import/export format: "+e);return t},detectFormat:function(e){const t=String(e||"").toLowerCase(),o=n.find(e=>t.endsWith(e.extension));if(!o)throw new Error("Unsupported file format: "+e);return o}}},646(e){"use strict";const t={description:{en:"Import, export, and convert SSH/Telnet profiles between terminal clients. Passwords, login scripts, private keys, and local key paths are never exported.",pt:"Importe, exporte e converta perfis SSH/Telnet entre clientes de terminal. Senhas, scripts de login, chaves privadas e caminhos locais de chaves nunca são exportados."},export:{en:"Export",pt:"Exportar"},import:{en:"Import",pt:"Importar"},selectFolders:{en:"Select folders and sessions",pt:"Selecione pastas e sessões"},selectAll:{en:"Select all",pt:"Selecionar tudo"},clear:{en:"Clear",pt:"Limpar"},searchPlaceholder:{en:"Filter by name (selection is preserved)",pt:"Filtrar por nome (a seleção é preservada)"},ungrouped:{en:"Ungrouped",pt:"Sem grupo"},exportUsers:{en:"Export usernames",pt:"Exportar usuários"},userOptIn:{en:"Usernames are excluded by default. Explicitly select the shared service accounts to include.",pt:"Usuários são omitidos por padrão. Selecione explicitamente as contas compartilhadas que deseja incluir."},selectToListUsers:{en:"Select sessions to see their usernames.",pt:"Selecione sessões para listar os usuários."},sessions:{en:"sessions",pt:"sessões"},exportFolder:{en:"Export destination folder",pt:"Pasta de destino da exportação"},preserveHierarchy:{en:"Preserve hierarchy at root",pt:"Preservar hierarquia na raiz"},wrapExport:{en:"Place selected sessions inside one top-level folder",pt:"Colocar as sessões selecionadas dentro de uma pasta principal"},folderPlaceholder:{en:"Top-level folder name",pt:"Nome da pasta principal"},format:{en:"Format",pt:"Formato"},csvHelp:{en:"Algorithms and safe terminal options are stored as JSON strings in CSV cells. Group colors and icons are not exported.",pt:"Algoritmos e opções seguras de terminal são armazenados como texto JSON nas células do CSV. Cores e ícones dos grupos não são exportados."},mobaHelp:{en:"MobaXterm export preserves names, hierarchy, hosts, ports, usernames, and basic X11. Unsupported advanced options, colors, icons, and secrets are omitted.",pt:"A exportação MobaXterm preserva nomes, hierarquia, hosts, portas, usuários e X11 básico. Opções avançadas incompatíveis, cores, ícones e segredos são omitidos."},exportProfiles:{en:"Export {count} profiles",pt:"Exportar {count} perfis"},addFiles:{en:"Add CSV or MobaXterm files",pt:"Adicionar arquivos CSV ou MobaXterm"},importDescription:{en:"Import each file at the root or inside its own parent folder. Review conflicts before applying; existing profiles are never replaced automatically.",pt:"Importe cada arquivo na raiz ou dentro de sua própria pasta principal. Revise os conflitos antes de aplicar; perfis existentes nunca são substituídos automaticamente."},remove:{en:"Remove",pt:"Remover"},importRoot:{en:"Import at Tabby root (keep source hierarchy)",pt:"Importar na raiz do Tabby (manter hierarquia de origem)"},importFolder:{en:"Import inside one parent folder",pt:"Importar dentro de uma pasta principal"},parentFolderPlaceholder:{en:"Parent folder name",pt:"Nome da pasta principal"},matchingNames:{en:"{count} matching names in destination",pt:"{count} nomes coincidentes no destino"},mobaWarning:{en:"{count} MobaXterm entries/settings were not converted. First: {first}",pt:"{count} entradas/configurações do MobaXterm não foram convertidas. Primeira: {first}"},bulkUser:{en:"Bulk username conversion",pt:"Alteração de usuário em massa"},originalUser:{en:"Original username",pt:"Usuário original"},allUsers:{en:"All imported usernames",pt:"Todos os usuários importados"},emptyUser:{en:"(empty)",pt:"(vazio)"},newUser:{en:"New username",pt:"Novo usuário"},newUserPlaceholder:{en:"New SSH username",pt:"Novo usuário SSH"},applyUser:{en:"Apply to selected imports",pt:"Aplicar às importações selecionadas"},reviewProfiles:{en:"Review {count} imported profiles",pt:"Revisar {count} perfis importados"},skipAll:{en:"Skip all",pt:"Ignorar todos"},createAll:{en:"Create all",pt:"Criar todos"},session:{en:"Session",pt:"Sessão"},hostPort:{en:"Host : port",pt:"Host : porta"},username:{en:"Username",pt:"Usuário"},match:{en:"Existing match",pt:"Correspondência existente"},action:{en:"Action",pt:"Ação"},root:{en:"(root)",pt:"(raiz)"},sameName:{en:"Same name / folder",pt:"Mesmo nome / pasta"},sameEndpoint:{en:"Same endpoint elsewhere",pt:"Mesmo destino em outra pasta"},new:{en:"New",pt:"Novo"},skip:{en:"Keep / skip",pt:"Manter / ignorar"},create:{en:"Add as new",pt:"Adicionar como novo"},replace:{en:"Replace existing",pt:"Substituir existente"},replaceWarning:{en:"Replacing a profile removes its connection-specific credentials. Back up your Tabby configuration first. Imported files are never executed.",pt:"Substituir um perfil remove suas credenciais específicas. Faça backup da configuração do Tabby antes. Arquivos importados nunca são executados."},applyChanges:{en:"Apply {count} selected changes",pt:"Aplicar {count} alterações selecionadas"},importing:{en:"Importing...",pt:"Importando..."},exportedSuccess:{en:"Exported {count} sessions without passwords or private keys.",pt:"Exportadas {count} sessões sem senhas ou chaves privadas."},importedSuccess:{en:"Imported {created} new sessions, replaced {replaced}, created {groups} folders.",pt:"Importadas {created} sessões novas, substituídas {replaced} e criadas {groups} pastas."},invalidFolder:{en:"Container folder cannot contain slashes.",pt:"O nome da pasta principal não pode conter barras."},tooLarge:{en:"File exceeds 5 MB: {file}",pt:"O arquivo excede 5 MB: {file}"},tooLongUser:{en:"Username is too long.",pt:"O nome de usuário é muito longo."},selectImport:{en:"Select at least one profile to import.",pt:"Selecione pelo menos um perfil para importar."},errorPrefix:{en:"Operation failed: {error}",pt:"Falha na operação: {error}"}};e.exports={translate:function(e,o,r={}){const s=t[o];return(s?s[function(e){return"string"==typeof e&&/^pt(?:-|$)/i.test(e)?"pt":"en"}(e)]:o).replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g,(e,t)=>Object.prototype.hasOwnProperty.call(r,t)?String(r[t]):e)}}},684(e,t,o){"use strict";const r=o(56),s="0%localhost%22%%%-1%-1%%%%%0%0%0%%%-1%0%0%0%%1080%%0%0%1%%0%%%%0%-1%-1%0".split("%"),n="MobaFont%10%0%0%-1%15%236,236,236%30,30,30%180,180,192%0%-1%0%%xterm%-1%0%_Std_Colors_0_%80%24%0%0%-1%<none>%%0%0%-1%-1".split("%"),i=new Map([[8364,128],[8218,130],[402,131],[8222,132],[8230,133],[8224,134],[8225,135],[710,136],[8240,137],[352,138],[8249,139],[338,140],[381,142],[8216,145],[8217,146],[8220,147],[8221,148],[8226,149],[8211,150],[8212,151],[732,152],[8482,153],[353,154],[8250,155],[339,156],[382,158],[376,159]]);function a(e,t){if("string"!=typeof e||!e.trim()||/[\r\n=#%\\]/.test(e))throw Error("Invalid MobaXterm "+t+": rename to remove newlines, =, #, %, or backslash");return e}function l(e){if(!Array.isArray(e)||e.length>32)throw Error("Invalid MobaXterm group depth");for(const t of e)a(t,"folder");return e.join("\\")}function c(e){if(a(e.name,"session"),/[%#\r\n]/.test(e.options.host)||/[%#\r\n]/.test(e.options.user||""))throw Error("Unsupported MobaXterm delimiter in host/username");const t=e.options;if("telnet"===e.type)return e.name+"=#98#1%"+t.host+"%"+t.port+"%"+(t.user||"")+"%";if("ssh"!==e.type)throw Error("Unsupported MobaXterm protocol");const o=[...s],r=[...n];return o[1]=t.host,o[2]=String(t.port),o[3]=t.user||"",o[5]=t.x11?"-1":"0",o[12]=o[3]?"0":"-1",o[14]="",o[7]="",o[8]="",o[9]="",o[10]="",o[15]="",o[19]="0",o[20]="",o[22]="",o[26]="",void 0!==t.agentForward&&(o[34]=t.agentForward?"-1":"0"),["xterm","xterm-256color","vt100","vt220","vt400","xterm-r6"].includes(t.term)&&(r[13]=t.term),e.name+"=#109#"+o.join("%")+"#"+r.join("%")+"#0# #-1"}e.exports={readMoba:function(e){const t="string"==typeof e?e:(o=e,new TextDecoder("windows-1252",{fatal:!1}).decode(o));var o;if(t.length>r.MAX_BYTES)throw Error("MobaXterm file exceeds 5 MB");const s=[],n=[];let i=[],a=!1,l=!1;for(const e of t.replace(/^\uFEFF/,"").split(/\r?\n/)){const t=e.trim();if(!t||t.startsWith(";")||t.startsWith("#"))continue;if(/^\[/.test(t)){a=/^\[Bookmarks(?:_\d+)?\]$/i.test(t),a&&(l=!0,i=[]);continue}if(!a)continue;if(/^SubRep=/i.test(t)){const e=t.slice(t.indexOf("=")+1);if(i=e?e.split("\\"):[],i.length>32)throw Error("MobaXterm has more than 32 group levels");continue}if(/^(ImgNum|Expanded)=/i.test(t))continue;const o=t.indexOf("=");if(o<=0)continue;const c=t.slice(0,o).trim(),p=t.slice(o+1).trim(),d=/#(109|98)#([01])%/.exec(p);if(!d){n.push("Unsupported session type ignored: "+c);continue}const u="109"===d[1]&&"0"===d[2]?"ssh":"98"===d[1]&&"1"===d[2]?"telnet":null;if(!u){n.push("Unsupported session type ignored: "+c);continue}const h=p.slice(d.index).split("#")[2].split("%"),m={group1:i[0]||"",group2:i[1]||"",other_groups:r.joinOther(i.slice(2)),profile:c,protocol:u,hostname:h[1]||"",port:h[2]||String("ssh"===u?22:23),username:"<default>"===h[3]?"":h[3]||"",auth:"",algorithms:"",settings:""};if("ssh"===u){const e={};h.length>5&&(e.x11="-1"===h[5]),Object.keys(e).length&&(m.settings=JSON.stringify(e)),(h[7]||h[8]||h[14]||h[20]||h[26])&&n.push("Advanced settings not transferred: "+c)}try{s.push(r.validateRow(m,s.length+1))}catch(e){n.push("Ignored invalid session "+c+": "+String(e.message||e))}if(s.length>r.MAX_ROWS)throw Error("Too many MobaXterm sessions")}if(!l)throw Error("No MobaXterm [Bookmarks] sections found");return{rows:s,warnings:n}},writeMoba:function(e){if(!Array.isArray(e)||!e.length||e.length>r.MAX_ROWS)throw Error("Select 1 to 10000 profiles");const t=[],o=new Map;function s(e){const r=e.join("");if(!o.has(r)){const s={path:[...e],sessions:[]};t.push(s),o.set(r,s)}return o.get(r)}s([]);for(const t of e){const e=Array.isArray(t.path)?t.path:r.validateRow(t,1).path;l(e);for(let t=1;t<=e.length;t++)s(e.slice(0,t));const o=Array.isArray(t.path)?t:r.validateRow(t,1);s(e).sessions.push(c(o))}return function(e){const t=[];for(const o of e){const e=o.codePointAt(0);if(e<=127||e>=160&&e<=255)t.push(e);else{if(!i.has(e))throw Error("MobaXterm Windows-1252 cannot encode character U+"+e.toString(16).toUpperCase()+". Rename the affected group/session.");t.push(i.get(e))}}return new Uint8Array(t)}(t.map((e,t)=>[t?"[Bookmarks_"+t+"]":"[Bookmarks]","SubRep="+e.path.join("\\"),"ImgNum="+(t?"41":"42"),...e.sessions].join("\r\n")).join("\r\n")+"\r\n")}}},804(e){"use strict";e.exports=require("@angular/common")},430(e){"use strict";e.exports=require("@angular/core")},440(e){"use strict";e.exports=require("@angular/forms")},643(e){"use strict";e.exports=require("@ng-bootstrap/ng-bootstrap")},21(e){"use strict";e.exports=require("fs")},368(e){"use strict";e.exports=require("tabby-core")},262(e){"use strict";e.exports=require("tabby-settings")},330(e){"use strict";e.exports={rE:"0.2.3"}}};const t={};let o=function o(r){const s=t[r];if(void 0!==s)return s.exports;const n=t[r]={exports:{}};return e[r].call(n.exports,n,n.exports,o),n.exports}(729);module.exports=o})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabby-profile-bridge",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Import and export SSH/Telnet profiles between Tabby, CSV and MobaXterm while preserving nested groups",
5
5
  "keywords": [
6
6
  "tabby-plugin",
@@ -26,6 +26,7 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "README.md",
29
+ "README.pt-BR.md",
29
30
  "LICENSE",
30
31
  "CHANGELOG.md"
31
32
  ],