@termix-ssh/plugin-sdk 1.0.12 → 1.0.13
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 +16 -0
- package/PLUGIN-GUIDE.md +91 -59
- package/dist/backend.d.ts +404 -0
- package/dist/capabilities.js +5 -0
- package/dist/db.js +1 -0
- package/dist/desktop.d.ts +0 -243
- package/dist/frontend.d.ts +23 -2
- package/dist/host/{PluginsTab-EV4MSGY6.js → PluginsTab-OXQQ7M4I.js} +511 -178
- package/dist/host/{chunk-X4QZKO5G.js → chunk-54KCK6IZ.js} +52 -5
- package/dist/host/{chunk-XKA3DBB2.js → chunk-CALNJCHN.js} +114 -54
- package/dist/host/{chunk-RUPLD6RF.js → chunk-CN5UBTBP.js} +2 -86
- package/dist/host/{chunk-AWR6FYDO.js → chunk-ERGN4YW6.js} +5552 -5550
- package/dist/host/{chunk-VEWOYAMZ.js → chunk-T2PZWXFJ.js} +1 -1
- package/dist/host/core-classes.json +1 -1
- package/dist/host/{linked-server-B4NEG4MA.js → linked-server-5VFO4XQB.js} +1 -1
- package/dist/host/{rbac-api-OUO3LUOC.js → rbac-api-5YMBJAO5.js} +1 -1
- package/dist/host/testing-host.js +4 -4
- package/dist/host/types/src/types/onboarding.d.ts +0 -2
- package/dist/host/types/src/ui/api/audit-log-api.d.ts +0 -10
- package/dist/host/types/src/ui/api/credentials-api.d.ts +0 -22
- package/dist/host/types/src/ui/api/plugins-api.d.ts +4 -0
- package/dist/host/types/src/ui/api/settings-api.d.ts +0 -6
- package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +0 -28
- package/dist/host/types/src/ui/main-axios.d.ts +2 -2
- package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +3 -1
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +5 -6
- package/dist/host/types/src/ui/plugins/plugin-model.d.ts +20 -0
- package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +3 -0
- package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +10 -0
- package/dist/host/ui.js +14 -205
- package/dist/host/{use-permissions-6VSHHUMB.js → use-permissions-ISEDRY3J.js} +1 -1
- package/dist/manifest.d.ts +10 -1
- package/dist/manifest.js +16 -1
- package/dist/ssh-keys.d.ts +29 -0
- package/dist/ssh-keys.js +85 -0
- package/dist/testing.d.ts +32 -15
- package/dist/testing.js +218 -1
- package/package.json +5 -1
- package/schema/manifest.schema.json +10 -0
- package/dist/host/types/src/ui/sidebar/host-export-payload.d.ts +0 -30
package/dist/host/ui.js
CHANGED
|
@@ -61,8 +61,10 @@ import {
|
|
|
61
61
|
hostActionsFor,
|
|
62
62
|
hostBadgesFor,
|
|
63
63
|
hostEditorSectionList,
|
|
64
|
+
hostEditorToolbarItemsFor,
|
|
64
65
|
hostMenuItemsFor,
|
|
65
66
|
hostProtocolFlags,
|
|
67
|
+
hostStatusIndicatorsFor,
|
|
66
68
|
i18n_default,
|
|
67
69
|
installPluginHostBridge,
|
|
68
70
|
isCoreRailView,
|
|
@@ -139,8 +141,10 @@ import {
|
|
|
139
141
|
useHostBadges,
|
|
140
142
|
useHostContextMenuItems,
|
|
141
143
|
useHostEditorSections,
|
|
144
|
+
useHostEditorToolbarItems,
|
|
142
145
|
useHostProtocols,
|
|
143
146
|
useHostStatus,
|
|
147
|
+
useHostStatusIndicators,
|
|
144
148
|
useInstalledPlugins,
|
|
145
149
|
useIsMobile,
|
|
146
150
|
useKeybindingActions,
|
|
@@ -169,10 +173,10 @@ import {
|
|
|
169
173
|
useThemes,
|
|
170
174
|
verifySecondFactor,
|
|
171
175
|
withProtocolSettings
|
|
172
|
-
} from "./chunk-
|
|
176
|
+
} from "./chunk-CALNJCHN.js";
|
|
173
177
|
import {
|
|
174
178
|
usePermissions
|
|
175
|
-
} from "./chunk-
|
|
179
|
+
} from "./chunk-T2PZWXFJ.js";
|
|
176
180
|
import {
|
|
177
181
|
AddButton,
|
|
178
182
|
Card,
|
|
@@ -193,7 +197,7 @@ import {
|
|
|
193
197
|
isPrerelease,
|
|
194
198
|
reportBetaFeedbackUrl,
|
|
195
199
|
reportCoreIssueUrl
|
|
196
|
-
} from "./chunk-
|
|
200
|
+
} from "./chunk-54KCK6IZ.js";
|
|
197
201
|
import {
|
|
198
202
|
Action,
|
|
199
203
|
Activity,
|
|
@@ -383,7 +387,7 @@ import {
|
|
|
383
387
|
useSurfaceKind,
|
|
384
388
|
useUiPreference,
|
|
385
389
|
useUiPreferencesContext
|
|
386
|
-
} from "./chunk-
|
|
390
|
+
} from "./chunk-ERGN4YW6.js";
|
|
387
391
|
import {
|
|
388
392
|
CREDENTIAL_ROW_FIELD_KEYS,
|
|
389
393
|
PRESETS,
|
|
@@ -404,7 +408,6 @@ import {
|
|
|
404
408
|
assignRoleToUser,
|
|
405
409
|
authApi,
|
|
406
410
|
buildOriginWsUrl,
|
|
407
|
-
bulkImportSSHHosts,
|
|
408
411
|
bulkUpdateSSHHosts,
|
|
409
412
|
changePassword,
|
|
410
413
|
completePasswordReset,
|
|
@@ -426,11 +429,7 @@ import {
|
|
|
426
429
|
deleteRole,
|
|
427
430
|
deleteSSHHost,
|
|
428
431
|
deleteUser,
|
|
429
|
-
deployCredentialToHost,
|
|
430
432
|
duplicateCredential,
|
|
431
|
-
exportAllSSHHosts,
|
|
432
|
-
generateKeyPair,
|
|
433
|
-
generatePublicKeyFromPrivate,
|
|
434
433
|
getActiveSessions,
|
|
435
434
|
getApiKeys,
|
|
436
435
|
getBackendUrl,
|
|
@@ -480,7 +479,6 @@ import {
|
|
|
480
479
|
handleApiError,
|
|
481
480
|
hostApi,
|
|
482
481
|
hydrateLocalSharedHostAuth,
|
|
483
|
-
importSSHConfigHosts,
|
|
484
482
|
initiatePasswordReset,
|
|
485
483
|
invalidateHostsAndStatusCaches,
|
|
486
484
|
invalidateServerStatusCache,
|
|
@@ -529,7 +527,6 @@ import {
|
|
|
529
527
|
sshHostApi,
|
|
530
528
|
tabTypeForActivity,
|
|
531
529
|
unlinkExternalFromPasswordAccount,
|
|
532
|
-
updateBranding,
|
|
533
530
|
updateCredential,
|
|
534
531
|
updateExternalAutoProvision,
|
|
535
532
|
updateFolderMetadata,
|
|
@@ -547,7 +544,7 @@ import {
|
|
|
547
544
|
useTabs,
|
|
548
545
|
useTabsSafe,
|
|
549
546
|
verifyPasswordResetCode
|
|
550
|
-
} from "./chunk-
|
|
547
|
+
} from "./chunk-CN5UBTBP.js";
|
|
551
548
|
import {
|
|
552
549
|
define_import_meta_env_default,
|
|
553
550
|
init_define_import_meta_env
|
|
@@ -32199,167 +32196,6 @@ function B2({ asChild: r, children: o }, n) {
|
|
|
32199
32196
|
}
|
|
32200
32197
|
var Te = { position: "absolute", width: "1px", height: "1px", padding: "0", margin: "-1px", overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", borderWidth: "0" };
|
|
32201
32198
|
|
|
32202
|
-
// src/ui/sidebar/host-export-payload.ts
|
|
32203
|
-
init_define_import_meta_env();
|
|
32204
|
-
var FIELD_GROUP_KEYS = {
|
|
32205
|
-
connection: [
|
|
32206
|
-
"connectionType",
|
|
32207
|
-
"name",
|
|
32208
|
-
"ip",
|
|
32209
|
-
"port",
|
|
32210
|
-
"username",
|
|
32211
|
-
"folder",
|
|
32212
|
-
"domain",
|
|
32213
|
-
"security",
|
|
32214
|
-
"ignoreCert"
|
|
32215
|
-
],
|
|
32216
|
-
notes: ["notes"],
|
|
32217
|
-
tags: ["tags", "pin"],
|
|
32218
|
-
proxy: [
|
|
32219
|
-
"useSocks5",
|
|
32220
|
-
"socks5Host",
|
|
32221
|
-
"socks5Port",
|
|
32222
|
-
"socks5Username",
|
|
32223
|
-
"socks5ProxyChain"
|
|
32224
|
-
],
|
|
32225
|
-
jumpHosts: ["jumpHosts"],
|
|
32226
|
-
// Every plugin's host settings, enable switches included.
|
|
32227
|
-
featureFlags: ["pluginSettings", "forceKeyboardInteractive"],
|
|
32228
|
-
advanced: [
|
|
32229
|
-
"statusCheckEnabled",
|
|
32230
|
-
"statusCheckInterval",
|
|
32231
|
-
"terminalConfig",
|
|
32232
|
-
"sshOptions"
|
|
32233
|
-
]
|
|
32234
|
-
};
|
|
32235
|
-
var SECRET_KEYS = [
|
|
32236
|
-
"password",
|
|
32237
|
-
"key",
|
|
32238
|
-
"keyPassword",
|
|
32239
|
-
"sudoPassword",
|
|
32240
|
-
"socks5Password"
|
|
32241
|
-
];
|
|
32242
|
-
var CREDENTIAL_KEYS = [
|
|
32243
|
-
...SECRET_KEYS,
|
|
32244
|
-
"authType",
|
|
32245
|
-
"keyType",
|
|
32246
|
-
"credentialAlias",
|
|
32247
|
-
"credentialId",
|
|
32248
|
-
"overrideCredentialUsername"
|
|
32249
|
-
];
|
|
32250
|
-
var TUPLE_KEYS = ["name", "ip", "port", "username", "connectionType"];
|
|
32251
|
-
function pluginSecretKeys(summaries) {
|
|
32252
|
-
const result = {};
|
|
32253
|
-
for (const summary of summaries) {
|
|
32254
|
-
for (const field of summary.contributes?.settings?.host?.fields ?? []) {
|
|
32255
|
-
if (!field.secretKeys?.length) continue;
|
|
32256
|
-
(result[summary.id] ??= {})[field.key] = field.secretKeys;
|
|
32257
|
-
}
|
|
32258
|
-
}
|
|
32259
|
-
return result;
|
|
32260
|
-
}
|
|
32261
|
-
var NESTED_SECRET_ARRAYS = [
|
|
32262
|
-
{ container: "socks5ProxyChain", field: "password" }
|
|
32263
|
-
];
|
|
32264
|
-
function mapPluginSecrets(pluginSettings, secrets, replace2) {
|
|
32265
|
-
if (!pluginSettings || typeof pluginSettings !== "object") {
|
|
32266
|
-
return pluginSettings;
|
|
32267
|
-
}
|
|
32268
|
-
const perPlugin = {};
|
|
32269
|
-
for (const [pluginId, values] of Object.entries(
|
|
32270
|
-
pluginSettings
|
|
32271
|
-
)) {
|
|
32272
|
-
const copy = { ...values };
|
|
32273
|
-
for (const [field, keys2] of Object.entries(secrets[pluginId] ?? {})) {
|
|
32274
|
-
const blob = copy[field];
|
|
32275
|
-
if (!blob || typeof blob !== "object") continue;
|
|
32276
|
-
const record = { ...blob };
|
|
32277
|
-
for (const key of keys2) {
|
|
32278
|
-
if (key in record) record[key] = replace2(record[key]);
|
|
32279
|
-
}
|
|
32280
|
-
copy[field] = record;
|
|
32281
|
-
}
|
|
32282
|
-
perPlugin[pluginId] = copy;
|
|
32283
|
-
}
|
|
32284
|
-
return perPlugin;
|
|
32285
|
-
}
|
|
32286
|
-
function hostKey(host) {
|
|
32287
|
-
return JSON.stringify(TUPLE_KEYS.map((k3) => String(host[k3] ?? "")));
|
|
32288
|
-
}
|
|
32289
|
-
function buildExportPayload(raw, selected, groups, withCredentials, pluginSecrets = {}) {
|
|
32290
|
-
const allowed = /* @__PURE__ */ new Set([
|
|
32291
|
-
"exportId",
|
|
32292
|
-
...CREDENTIAL_KEYS,
|
|
32293
|
-
...FIELD_GROUP_KEYS.connection
|
|
32294
|
-
]);
|
|
32295
|
-
for (const group of groups) {
|
|
32296
|
-
for (const key of FIELD_GROUP_KEYS[group]) allowed.add(key);
|
|
32297
|
-
}
|
|
32298
|
-
const hosts = (raw.hosts ?? []).filter((host) => selected === null || selected.has(hostKey(host))).map((host) => {
|
|
32299
|
-
const shaped = {};
|
|
32300
|
-
for (const [key, value] of Object.entries(host)) {
|
|
32301
|
-
if (allowed.has(key)) shaped[key] = value;
|
|
32302
|
-
}
|
|
32303
|
-
if (!withCredentials) {
|
|
32304
|
-
shaped.pluginSettings = mapPluginSecrets(
|
|
32305
|
-
shaped.pluginSettings,
|
|
32306
|
-
pluginSecrets,
|
|
32307
|
-
() => null
|
|
32308
|
-
);
|
|
32309
|
-
for (const { container, field } of NESTED_SECRET_ARRAYS) {
|
|
32310
|
-
const arr = shaped[container];
|
|
32311
|
-
if (Array.isArray(arr)) {
|
|
32312
|
-
shaped[container] = arr.map(
|
|
32313
|
-
(entry) => entry && typeof entry === "object" ? { ...entry, [field]: null } : entry
|
|
32314
|
-
);
|
|
32315
|
-
}
|
|
32316
|
-
}
|
|
32317
|
-
}
|
|
32318
|
-
return shaped;
|
|
32319
|
-
});
|
|
32320
|
-
const result = { ...raw, hosts };
|
|
32321
|
-
if (raw.credentials) {
|
|
32322
|
-
const used = new Set(
|
|
32323
|
-
hosts.map((host) => host.credentialAlias).filter(Boolean)
|
|
32324
|
-
);
|
|
32325
|
-
result.credentials = raw.credentials.filter(
|
|
32326
|
-
(entry) => used.has(entry.alias)
|
|
32327
|
-
);
|
|
32328
|
-
}
|
|
32329
|
-
return result;
|
|
32330
|
-
}
|
|
32331
|
-
function maskSecrets(payload, pluginSecrets = {}) {
|
|
32332
|
-
return {
|
|
32333
|
-
...payload,
|
|
32334
|
-
hosts: payload.hosts.map((host) => {
|
|
32335
|
-
const masked = { ...host };
|
|
32336
|
-
for (const key of SECRET_KEYS) {
|
|
32337
|
-
if (masked[key] !== void 0 && masked[key] !== null && masked[key] !== "") {
|
|
32338
|
-
masked[key] = "<included>";
|
|
32339
|
-
}
|
|
32340
|
-
}
|
|
32341
|
-
masked.pluginSettings = mapPluginSecrets(
|
|
32342
|
-
masked.pluginSettings,
|
|
32343
|
-
pluginSecrets,
|
|
32344
|
-
(value) => value ? "<included>" : value
|
|
32345
|
-
);
|
|
32346
|
-
for (const { container, field } of NESTED_SECRET_ARRAYS) {
|
|
32347
|
-
const arr = masked[container];
|
|
32348
|
-
if (Array.isArray(arr)) {
|
|
32349
|
-
masked[container] = arr.map((entry) => {
|
|
32350
|
-
if (entry && typeof entry === "object") {
|
|
32351
|
-
const record = entry;
|
|
32352
|
-
if (record[field]) return { ...record, [field]: "<included>" };
|
|
32353
|
-
}
|
|
32354
|
-
return entry;
|
|
32355
|
-
});
|
|
32356
|
-
}
|
|
32357
|
-
}
|
|
32358
|
-
return masked;
|
|
32359
|
-
})
|
|
32360
|
-
};
|
|
32361
|
-
}
|
|
32362
|
-
|
|
32363
32199
|
// src/types/auth-protocols.ts
|
|
32364
32200
|
init_define_import_meta_env();
|
|
32365
32201
|
var SSH_AUTH_PROTOCOL = "ssh";
|
|
@@ -32428,25 +32264,6 @@ async function getAuditLogActions() {
|
|
|
32428
32264
|
handleApiError(error, "fetch audit log actions");
|
|
32429
32265
|
}
|
|
32430
32266
|
}
|
|
32431
|
-
async function getAuditForwarding() {
|
|
32432
|
-
try {
|
|
32433
|
-
const response = await authApi.get("/users/audit-forwarding");
|
|
32434
|
-
return response.data;
|
|
32435
|
-
} catch (error) {
|
|
32436
|
-
throw handleApiError(error, "get audit forwarding settings");
|
|
32437
|
-
}
|
|
32438
|
-
}
|
|
32439
|
-
async function updateAuditForwarding(url, token) {
|
|
32440
|
-
try {
|
|
32441
|
-
const response = await authApi.patch("/users/audit-forwarding", {
|
|
32442
|
-
url,
|
|
32443
|
-
token
|
|
32444
|
-
});
|
|
32445
|
-
return response.data;
|
|
32446
|
-
} catch (error) {
|
|
32447
|
-
throw handleApiError(error, "update audit forwarding settings");
|
|
32448
|
-
}
|
|
32449
|
-
}
|
|
32450
32267
|
|
|
32451
32268
|
// src/ui/api/host-defaults-api.ts
|
|
32452
32269
|
init_define_import_meta_env();
|
|
@@ -34226,6 +34043,7 @@ export {
|
|
|
34226
34043
|
NumberField,
|
|
34227
34044
|
OPEN_PLUGINS_EVENT,
|
|
34228
34045
|
PANEL,
|
|
34046
|
+
PRESETS,
|
|
34229
34047
|
PROMPT_BUTTON,
|
|
34230
34048
|
PROMPT_DESTRUCTIVE_BUTTON,
|
|
34231
34049
|
PROMPT_PRIMARY_BUTTON,
|
|
@@ -34323,9 +34141,7 @@ export {
|
|
|
34323
34141
|
applyUiFont,
|
|
34324
34142
|
assignRoleToUser,
|
|
34325
34143
|
beginSplitDrag,
|
|
34326
|
-
buildExportPayload,
|
|
34327
34144
|
buildOriginWsUrl,
|
|
34328
|
-
bulkImportSSHHosts,
|
|
34329
34145
|
bulkUpdateSSHHosts,
|
|
34330
34146
|
canConfigureUpdates,
|
|
34331
34147
|
canJoinSplit,
|
|
@@ -34354,22 +34170,17 @@ export {
|
|
|
34354
34170
|
deleteRole,
|
|
34355
34171
|
deleteSSHHost,
|
|
34356
34172
|
deleteUser,
|
|
34357
|
-
deployCredentialToHost,
|
|
34358
34173
|
desktopUpdates,
|
|
34359
34174
|
docsUrl,
|
|
34360
34175
|
duplicateCredential,
|
|
34361
34176
|
enabledHostProtocols,
|
|
34362
34177
|
endSplitDrag,
|
|
34363
|
-
exportAllSSHHosts,
|
|
34364
34178
|
filterPaletteItems,
|
|
34365
34179
|
findCoreHostDefault,
|
|
34366
34180
|
findMatchingKeybinding,
|
|
34367
|
-
generateKeyPair,
|
|
34368
|
-
generatePublicKeyFromPrivate,
|
|
34369
34181
|
getActiveSessions,
|
|
34370
34182
|
getAdaptiveResourceBudget,
|
|
34371
34183
|
getApiKeys,
|
|
34372
|
-
getAuditForwarding,
|
|
34373
34184
|
getAuditLogActions,
|
|
34374
34185
|
getAuditLogs,
|
|
34375
34186
|
getBasePath,
|
|
@@ -34438,11 +34249,11 @@ export {
|
|
|
34438
34249
|
hasVisibleFields,
|
|
34439
34250
|
hostActionsFor,
|
|
34440
34251
|
hostBadgesFor,
|
|
34441
|
-
|
|
34252
|
+
hostEditorToolbarItemsFor,
|
|
34442
34253
|
hostMenuItemsFor,
|
|
34443
34254
|
hostProtocolFlags,
|
|
34255
|
+
hostStatusIndicatorsFor,
|
|
34444
34256
|
hydrateLocalSharedHostAuth,
|
|
34445
|
-
importSSHConfigHosts,
|
|
34446
34257
|
invalidateHostsAndStatusCaches,
|
|
34447
34258
|
invalidateServerStatusCache,
|
|
34448
34259
|
isCoreRailView,
|
|
@@ -34467,7 +34278,6 @@ export {
|
|
|
34467
34278
|
mapCredentials,
|
|
34468
34279
|
markAdaptiveResourceUsed,
|
|
34469
34280
|
markTabSurfaceUsed,
|
|
34470
|
-
maskSecrets,
|
|
34471
34281
|
moveRailItem,
|
|
34472
34282
|
moveSplitDrag,
|
|
34473
34283
|
normalizeLanguageCode,
|
|
@@ -34478,7 +34288,6 @@ export {
|
|
|
34478
34288
|
parseServerTime,
|
|
34479
34289
|
pluginKey,
|
|
34480
34290
|
pluginName,
|
|
34481
|
-
pluginSecretKeys,
|
|
34482
34291
|
pluginWsUrl,
|
|
34483
34292
|
preloadTabSurface,
|
|
34484
34293
|
previewHostDefaultsLevel,
|
|
@@ -34551,8 +34360,6 @@ export {
|
|
|
34551
34360
|
takePendingManageRequest,
|
|
34552
34361
|
unlinkExternalFromPasswordAccount,
|
|
34553
34362
|
unlinkServer,
|
|
34554
|
-
updateAuditForwarding,
|
|
34555
|
-
updateBranding,
|
|
34556
34363
|
updateCredential,
|
|
34557
34364
|
updateExternalAutoProvision,
|
|
34558
34365
|
updateFolderMetadata,
|
|
@@ -34588,8 +34395,10 @@ export {
|
|
|
34588
34395
|
useHostBadges,
|
|
34589
34396
|
useHostContextMenuItems,
|
|
34590
34397
|
useHostEditorSections,
|
|
34398
|
+
useHostEditorToolbarItems,
|
|
34591
34399
|
useHostProtocols,
|
|
34592
34400
|
useHostStatus,
|
|
34401
|
+
useHostStatusIndicators,
|
|
34593
34402
|
useInstalledPlugins,
|
|
34594
34403
|
useIsDefaultsEditor,
|
|
34595
34404
|
useIsMobile,
|
package/dist/manifest.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsPage, pluginDoc
|
|
|
20
20
|
* "^1.2" needs 1.2 or later). engine.termix is checked against the core
|
|
21
21
|
* version by the server.
|
|
22
22
|
*/
|
|
23
|
-
export declare const PLUGIN_API_VERSION = "1.
|
|
23
|
+
export declare const PLUGIN_API_VERSION = "1.5.0";
|
|
24
24
|
/** The API major, the default engine.api for a new plugin. */
|
|
25
25
|
export declare const SUPPORTED_PLUGIN_API_VERSION: string;
|
|
26
26
|
/** Whether this build's plugin API satisfies a manifest's engine.api. */
|
|
@@ -500,7 +500,16 @@ export interface PluginManifest {
|
|
|
500
500
|
nativeDependencies?: string[];
|
|
501
501
|
/** How a linked desktop treats this plugin. Defaults to "mirror". */
|
|
502
502
|
desktop?: PluginDesktopMode;
|
|
503
|
+
/**
|
|
504
|
+
* "desktop": the plugin only loads on a desktop app's own embedded backend,
|
|
505
|
+
* never on a server. It implies desktop "local" and may declare desktop:local.
|
|
506
|
+
*/
|
|
507
|
+
runtime?: PluginRuntime;
|
|
503
508
|
}
|
|
509
|
+
export type PluginRuntime = "desktop";
|
|
510
|
+
export declare const PLUGIN_RUNTIMES: readonly PluginRuntime[];
|
|
511
|
+
/** The desktop mode a manifest resolves to. */
|
|
512
|
+
export declare function resolvePluginDesktopMode(m: Pick<PluginManifest, "desktop" | "runtime">): PluginDesktopMode;
|
|
504
513
|
export declare const DEFAULT_BACKEND_ENTRY = "dist/backend.js";
|
|
505
514
|
export declare const DEFAULT_FRONTEND_ENTRY = "dist/frontend.js";
|
|
506
515
|
export declare const DEFAULT_LOCALES_DIR = "locales";
|
package/dist/manifest.js
CHANGED
|
@@ -22,7 +22,7 @@ export { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsPage, pluginDoc
|
|
|
22
22
|
* "^1.2" needs 1.2 or later). engine.termix is checked against the core
|
|
23
23
|
* version by the server.
|
|
24
24
|
*/
|
|
25
|
-
export const PLUGIN_API_VERSION = "1.
|
|
25
|
+
export const PLUGIN_API_VERSION = "1.5.0";
|
|
26
26
|
/** The API major, the default engine.api for a new plugin. */
|
|
27
27
|
export const SUPPORTED_PLUGIN_API_VERSION = String(semver.major(PLUGIN_API_VERSION));
|
|
28
28
|
/** Whether this build's plugin API satisfies a manifest's engine.api. */
|
|
@@ -192,6 +192,11 @@ export const PLUGIN_DESKTOP_MODES = [
|
|
|
192
192
|
"local",
|
|
193
193
|
"server",
|
|
194
194
|
];
|
|
195
|
+
export const PLUGIN_RUNTIMES = ["desktop"];
|
|
196
|
+
/** The desktop mode a manifest resolves to. */
|
|
197
|
+
export function resolvePluginDesktopMode(m) {
|
|
198
|
+
return m.desktop ?? (m.runtime === "desktop" ? "local" : "mirror");
|
|
199
|
+
}
|
|
195
200
|
export const DEFAULT_BACKEND_ENTRY = "dist/backend.js";
|
|
196
201
|
export const DEFAULT_FRONTEND_ENTRY = "dist/frontend.js";
|
|
197
202
|
export const DEFAULT_LOCALES_DIR = "locales";
|
|
@@ -227,6 +232,7 @@ const ALLOWED_TOP_LEVEL = new Set([
|
|
|
227
232
|
"platforms",
|
|
228
233
|
"nativeDependencies",
|
|
229
234
|
"desktop",
|
|
235
|
+
"runtime",
|
|
230
236
|
]);
|
|
231
237
|
const ALLOWED_CONTRIBUTES = new Set([
|
|
232
238
|
"tabs",
|
|
@@ -365,6 +371,15 @@ export function validateManifest(manifest) {
|
|
|
365
371
|
!PLUGIN_DESKTOP_MODES.includes(m.desktop)) {
|
|
366
372
|
errors.push(`Field "desktop" must be one of: ${PLUGIN_DESKTOP_MODES.join(", ")}`);
|
|
367
373
|
}
|
|
374
|
+
if (m.runtime !== undefined &&
|
|
375
|
+
!PLUGIN_RUNTIMES.includes(m.runtime)) {
|
|
376
|
+
errors.push(`Field "runtime" must be one of: ${PLUGIN_RUNTIMES.join(", ")}`);
|
|
377
|
+
}
|
|
378
|
+
if (m.runtime === "desktop" &&
|
|
379
|
+
m.desktop !== undefined &&
|
|
380
|
+
m.desktop !== "local") {
|
|
381
|
+
errors.push(`A "desktop" runtime plugin must use desktop "local"`);
|
|
382
|
+
}
|
|
368
383
|
validateDependencyMap(m.dependencies, "dependencies", errors);
|
|
369
384
|
validateDependencyMap(m.optionalDependencies, "optionalDependencies", errors);
|
|
370
385
|
validateProvides(m.provides, errors);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH key helpers for plugins that make, check or deploy keys: generate a key
|
|
3
|
+
* pair, tell what a key is, derive a public key and check a pair belongs
|
|
4
|
+
* together. Pure functions over ssh2, which the host supplies. Core keeps its
|
|
5
|
+
* own copy for the connect pipeline.
|
|
6
|
+
*/
|
|
7
|
+
export type SshKeyKind = "ssh-ed25519" | "ssh-rsa" | "ecdsa-sha2-nistp256";
|
|
8
|
+
export interface GeneratedSshKeyPair {
|
|
9
|
+
privateKey: string;
|
|
10
|
+
publicKey: string;
|
|
11
|
+
keyType: SshKeyKind;
|
|
12
|
+
}
|
|
13
|
+
export interface GenerateSshKeyPairOptions {
|
|
14
|
+
/** RSA only. 2048 to 8192, default 4096. */
|
|
15
|
+
bits?: number;
|
|
16
|
+
passphrase?: string;
|
|
17
|
+
comment?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Makes a new key pair; the public half is one authorized_keys line. */
|
|
20
|
+
export declare function generateSshKeyPair(type: SshKeyKind, options?: GenerateSshKeyPairOptions): GeneratedSshKeyPair;
|
|
21
|
+
/** The key's algorithm, like "ssh-ed25519", or null when it cannot be read. */
|
|
22
|
+
export declare function detectSshKeyType(key: string, passphrase?: string): string | null;
|
|
23
|
+
/** The authorized_keys line for a private key, or null. */
|
|
24
|
+
export declare function publicKeyFromPrivate(privateKey: string, passphrase?: string): string | null;
|
|
25
|
+
/** Whether a public key line belongs to the private key. */
|
|
26
|
+
export declare function validateSshKeyPair(privateKey: string, publicKey: string, passphrase?: string): {
|
|
27
|
+
isValid: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
};
|
package/dist/ssh-keys.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH key helpers for plugins that make, check or deploy keys: generate a key
|
|
3
|
+
* pair, tell what a key is, derive a public key and check a pair belongs
|
|
4
|
+
* together. Pure functions over ssh2, which the host supplies. Core keeps its
|
|
5
|
+
* own copy for the connect pipeline.
|
|
6
|
+
*/
|
|
7
|
+
import ssh2 from "ssh2";
|
|
8
|
+
/** Makes a new key pair; the public half is one authorized_keys line. */
|
|
9
|
+
export function generateSshKeyPair(type, options = {}) {
|
|
10
|
+
const algorithm = type === "ssh-rsa" ? "rsa" : type === "ssh-ed25519" ? "ed25519" : "ecdsa";
|
|
11
|
+
if (type !== "ssh-rsa" &&
|
|
12
|
+
type !== "ssh-ed25519" &&
|
|
13
|
+
type !== "ecdsa-sha2-nistp256") {
|
|
14
|
+
throw new Error(`Unsupported key type: ${String(type)}`);
|
|
15
|
+
}
|
|
16
|
+
const generateOptions = {
|
|
17
|
+
comment: options.comment ?? "",
|
|
18
|
+
};
|
|
19
|
+
if (options.passphrase) {
|
|
20
|
+
generateOptions.passphrase = options.passphrase;
|
|
21
|
+
generateOptions.cipher = "aes256-ctr";
|
|
22
|
+
}
|
|
23
|
+
if (type === "ssh-rsa") {
|
|
24
|
+
const bits = options.bits ?? 4096;
|
|
25
|
+
if (!Number.isInteger(bits) || bits < 2048 || bits > 8192) {
|
|
26
|
+
throw new Error("RSA keys need 2048 to 8192 bits");
|
|
27
|
+
}
|
|
28
|
+
generateOptions.bits = bits;
|
|
29
|
+
}
|
|
30
|
+
if (type === "ecdsa-sha2-nistp256")
|
|
31
|
+
generateOptions.bits = 256;
|
|
32
|
+
const pair = ssh2.utils.generateKeyPairSync(algorithm, generateOptions);
|
|
33
|
+
return {
|
|
34
|
+
privateKey: pair.private,
|
|
35
|
+
publicKey: pair.public.trim(),
|
|
36
|
+
keyType: type,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parse(key, passphrase) {
|
|
40
|
+
const parsed = ssh2.utils.parseKey(key.trim(), passphrase || undefined);
|
|
41
|
+
const first = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
42
|
+
if (!first || first instanceof Error) {
|
|
43
|
+
throw first instanceof Error ? first : new Error("Not a readable SSH key");
|
|
44
|
+
}
|
|
45
|
+
return first;
|
|
46
|
+
}
|
|
47
|
+
/** The key's algorithm, like "ssh-ed25519", or null when it cannot be read. */
|
|
48
|
+
export function detectSshKeyType(key, passphrase) {
|
|
49
|
+
try {
|
|
50
|
+
return parse(key, passphrase).type;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The authorized_keys line for a private key, or null. */
|
|
57
|
+
export function publicKeyFromPrivate(privateKey, passphrase) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = parse(privateKey, passphrase);
|
|
60
|
+
return `${parsed.type} ${parsed.getPublicSSH().toString("base64")}`;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Whether a public key line belongs to the private key. */
|
|
67
|
+
export function validateSshKeyPair(privateKey, publicKey, passphrase) {
|
|
68
|
+
try {
|
|
69
|
+
const priv = parse(privateKey, passphrase);
|
|
70
|
+
const pub = parse(publicKey);
|
|
71
|
+
const same = priv.getPublicSSH().equals(pub.getPublicSSH());
|
|
72
|
+
return same
|
|
73
|
+
? { isValid: true }
|
|
74
|
+
: {
|
|
75
|
+
isValid: false,
|
|
76
|
+
error: "The public key does not match the private key",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
isValid: false,
|
|
82
|
+
error: error instanceof Error ? error.message : String(error),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/testing.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* The vitest config these run under comes from @termix-ssh/plugin-sdk/vitest-preset.
|
|
9
9
|
*/
|
|
10
|
-
import type { PluginContext } from "./backend.js";
|
|
10
|
+
import type { PluginAuditEntry, PluginContext, PluginDesktopLocal } from "./backend.js";
|
|
11
11
|
import type { HostActionContribution, TermixApp } from "./frontend.js";
|
|
12
12
|
import type { PluginManifest } from "./manifest.js";
|
|
13
13
|
import type { PluginTableDefinition } from "./db.js";
|
|
@@ -25,7 +25,7 @@ export interface FakeWsRoute {
|
|
|
25
25
|
import type { PluginLoginMethod, PluginVerifiedIdentity, PluginSecondFactor, PluginSshAuthProvider, PluginSshConnectOptions, PluginSshHost, PluginHostSummary, PluginShareableUser, PluginShareableRole, PluginSshKeyCredential, PluginSshKeyCredentialInput } from "./backend.js";
|
|
26
26
|
import type { SyncEntityRegistration } from "./backend.js";
|
|
27
27
|
import type { PluginDatabase } from "./backend.js";
|
|
28
|
-
import type { PluginExternalClientRequest, PluginProtocolTarget, PluginHostStatusEntry, PluginNotification, PluginNotificationChannel, PluginNotifyHub, PluginFetchInit, PluginBinarySpec, PluginTlsCertificateInfo, PluginTlsStatus, PluginSummary, PluginKeyboardInteractiveHandler, PluginProcessHandle, PluginProcessOptions } from "./backend.js";
|
|
28
|
+
import type { PluginExternalClientRequest, PluginProtocolTarget, PluginHostProbeTarget, PluginHostStatusEntry, PluginActivityProvider, PluginHostStatusProvider, PluginNotification, PluginNotificationChannel, PluginNotifyHub, PluginFetchInit, PluginBinarySpec, PluginTlsCertificateInfo, PluginTlsStatus, PluginSummary, PluginKeyboardInteractiveHandler, PluginProcessHandle, PluginProcessOptions } from "./backend.js";
|
|
29
29
|
/** A timer registered through ctx.schedule, run by hand with runScheduled. */
|
|
30
30
|
export interface FakeScheduledJob {
|
|
31
31
|
kind: "every" | "after";
|
|
@@ -42,6 +42,8 @@ export interface FakeContextOptions {
|
|
|
42
42
|
settings?: Record<string, unknown>;
|
|
43
43
|
/** Seed for the core settings ctx.settings.readCore can reach. */
|
|
44
44
|
coreSettings?: Record<string, string>;
|
|
45
|
+
/** What ctx.files.dataDir() answers. Defaults to /tmp/<pluginId>. */
|
|
46
|
+
dataDir?: string;
|
|
45
47
|
/** What ctx.ssh.connect and withConnection hand back as the client. */
|
|
46
48
|
sshClient?: unknown;
|
|
47
49
|
/** A real database behind ctx.db, usually createTestDb().database. */
|
|
@@ -75,8 +77,18 @@ export interface FakeContextOptions {
|
|
|
75
77
|
sshKeyCredentials?: PluginSshKeyCredential[];
|
|
76
78
|
/** What ctx.desktop.available() answers. Defaults to false. */
|
|
77
79
|
desktopAvailable?: boolean;
|
|
80
|
+
/** What ctx.desktop.local is. Calls throw when this is not given. */
|
|
81
|
+
desktopLocal?: PluginDesktopLocal;
|
|
82
|
+
/** The stored audit lines ctx.audit.list and exportBatches serve. */
|
|
83
|
+
auditEntries?: PluginAuditEntry[];
|
|
78
84
|
/** What ctx.hosts.status.get and check answer, by host id. */
|
|
79
85
|
hostStatuses?: Record<number, PluginHostStatusEntry>;
|
|
86
|
+
/** What ctx.hosts.probeTargets answers. */
|
|
87
|
+
probeTargets?: PluginHostProbeTarget[];
|
|
88
|
+
/** Host ids ctx.hosts.sessionActive answers true for. */
|
|
89
|
+
activeSessions?: number[];
|
|
90
|
+
/** What ctx.network.tcpProbe and ctx.ssh.probe answer. Defaults to false. */
|
|
91
|
+
reachable?: (target: string | number, port?: number) => boolean;
|
|
80
92
|
/** The acting user's channels, what ctx.notify.channels answers. */
|
|
81
93
|
notificationChannels?: PluginNotificationChannel[];
|
|
82
94
|
/**
|
|
@@ -164,6 +176,10 @@ export interface FakePluginContext {
|
|
|
164
176
|
}) => Promise<Record<string, string>>;
|
|
165
177
|
/** Everything the plugin registered through ctx.disposables. */
|
|
166
178
|
disposals: Array<() => void | Promise<void>>;
|
|
179
|
+
/** Providers registered through ctx.hosts.status.provide. */
|
|
180
|
+
hostStatusProviders: PluginHostStatusProvider[];
|
|
181
|
+
/** Every provider passed to ctx.hosts.activity.provide that is still registered. */
|
|
182
|
+
activityProviders: PluginActivityProvider[];
|
|
167
183
|
/** Topics emitted through ctx.events.emit, in order. */
|
|
168
184
|
emitted: Array<{
|
|
169
185
|
topic: string;
|
|
@@ -255,6 +271,8 @@ export interface FakePluginContext {
|
|
|
255
271
|
challenges: Map<string, string>;
|
|
256
272
|
renewers: number;
|
|
257
273
|
};
|
|
274
|
+
/** Calls every ctx.audit.subscribe handler, like a line core just accepted. */
|
|
275
|
+
emitAudit: (entry: PluginAuditEntry) => void;
|
|
258
276
|
/** Every ctx.audit.record entry, in order. */
|
|
259
277
|
audits: Array<{
|
|
260
278
|
action: string;
|
|
@@ -321,6 +339,8 @@ export interface MockContextOptions {
|
|
|
321
339
|
settings?: Record<string, unknown>;
|
|
322
340
|
/** Seed for the core settings readCore can reach. */
|
|
323
341
|
coreSettings?: Record<string, string>;
|
|
342
|
+
/** What ctx.files.dataDir() answers. */
|
|
343
|
+
dataDir?: string;
|
|
324
344
|
/** Acting user returned by currentActor and used by asUser. */
|
|
325
345
|
actor?: string;
|
|
326
346
|
/** What ctx.ssh.connect and withConnection hand back as the client. */
|
|
@@ -346,8 +366,18 @@ export interface MockContextOptions {
|
|
|
346
366
|
sshKeyCredentials?: PluginSshKeyCredential[];
|
|
347
367
|
/** What ctx.desktop.available() answers. */
|
|
348
368
|
desktopAvailable?: boolean;
|
|
369
|
+
/** What ctx.desktop.local is. See FakeContextOptions. */
|
|
370
|
+
desktopLocal?: PluginDesktopLocal;
|
|
371
|
+
/** See FakeContextOptions. */
|
|
372
|
+
auditEntries?: PluginAuditEntry[];
|
|
349
373
|
/** What ctx.hosts.status answers. See FakeContextOptions. */
|
|
350
374
|
hostStatuses?: Record<number, PluginHostStatusEntry>;
|
|
375
|
+
/** What ctx.hosts.probeTargets answers. */
|
|
376
|
+
probeTargets?: PluginHostProbeTarget[];
|
|
377
|
+
/** Host ids ctx.hosts.sessionActive answers true for. */
|
|
378
|
+
activeSessions?: number[];
|
|
379
|
+
/** What ctx.network.tcpProbe and ctx.ssh.probe answer. */
|
|
380
|
+
reachable?: (target: string | number, port?: number) => boolean;
|
|
351
381
|
/** The acting user's channels, what ctx.notify.channels answers. */
|
|
352
382
|
notificationChannels?: PluginNotificationChannel[];
|
|
353
383
|
/**
|
|
@@ -378,19 +408,6 @@ export interface MockPluginContext extends FakePluginContext {
|
|
|
378
408
|
/** Capability ids checked during the test, in order, denied ones included. */
|
|
379
409
|
checked: string[];
|
|
380
410
|
}
|
|
381
|
-
/**
|
|
382
|
-
* A context double that gates on capabilities the way src/backend/plugins/ctx.ts
|
|
383
|
-
* does, so a test can prove a plugin fails closed without a running server.
|
|
384
|
-
*
|
|
385
|
-
* The gates on today's ctx surface: kv:own on every ctx.kv call, db:own on
|
|
386
|
-
* ctx.db, network:serve on ctx.http and ctx.ws, events:core on emitting a
|
|
387
|
-
* topic outside the plugin's own namespace, settings:read-core on
|
|
388
|
-
* ctx.settings.readCore, ssh:connect plus credentials:use on ctx.ssh,
|
|
389
|
-
* notify:send on ctx.notify (notify:hub on serve), network:outbound on ctx.fetch, process:spawn on
|
|
390
|
-
* ctx.process, system:tls on ctx.system, plugins:read on ctx.plugins, auth:provide on ctx.auth
|
|
391
|
-
* (users:impersonate on issueUserToken), and users:read on ctx.users. Reading a plugin's own settings is deliberately
|
|
392
|
-
* ungated. As the SDK grows a member, add its gate here in the same shape.
|
|
393
|
-
*/
|
|
394
411
|
export declare function createMockCtx(options?: MockContextOptions): MockPluginContext;
|
|
395
412
|
/** A sqlite handle as createTestDb exposes it. Structural, like better-sqlite3's. */
|
|
396
413
|
export interface TestSqlite {
|