@termix-ssh/plugin-sdk 1.0.12 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/PLUGIN-GUIDE.md +106 -59
  3. package/dist/backend.d.ts +534 -1
  4. package/dist/backend.js +7 -0
  5. package/dist/capabilities.js +7 -0
  6. package/dist/db.js +1 -0
  7. package/dist/desktop.d.ts +0 -243
  8. package/dist/frontend.d.ts +23 -2
  9. package/dist/host/{PluginsTab-EV4MSGY6.js → PluginsTab-S33WIIGC.js} +511 -178
  10. package/dist/host/{chunk-AWR6FYDO.js → chunk-2IV3VX7F.js} +5552 -5550
  11. package/dist/host/{chunk-RUPLD6RF.js → chunk-3L45NNT5.js} +29 -84
  12. package/dist/host/{chunk-VEWOYAMZ.js → chunk-57DJU2ZA.js} +1 -1
  13. package/dist/host/{chunk-XKA3DBB2.js → chunk-V44FCJBQ.js} +135 -54
  14. package/dist/host/{chunk-X4QZKO5G.js → chunk-VFTB2RDP.js} +54 -5
  15. package/dist/host/core-classes.json +1 -1
  16. package/dist/host/{linked-server-B4NEG4MA.js → linked-server-SCJH7DSL.js} +1 -1
  17. package/dist/host/{rbac-api-OUO3LUOC.js → rbac-api-KA5IPQQL.js} +1 -1
  18. package/dist/host/testing-host.js +4 -4
  19. package/dist/host/types/src/types/index.d.ts +10 -0
  20. package/dist/host/types/src/types/onboarding.d.ts +0 -2
  21. package/dist/host/types/src/types/ui-types.d.ts +3 -1
  22. package/dist/host/types/src/ui/api/audit-log-api.d.ts +0 -10
  23. package/dist/host/types/src/ui/api/auth-methods-api.d.ts +2 -0
  24. package/dist/host/types/src/ui/api/credentials-api.d.ts +0 -22
  25. package/dist/host/types/src/ui/api/plugins-api.d.ts +4 -0
  26. package/dist/host/types/src/ui/api/settings-api.d.ts +6 -6
  27. package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +6 -28
  28. package/dist/host/types/src/ui/main-axios.d.ts +3 -3
  29. package/dist/host/types/src/ui/onboarding/onboarding-registry.d.ts +3 -1
  30. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +6 -7
  31. package/dist/host/types/src/ui/plugins/plugin-model.d.ts +20 -0
  32. package/dist/host/types/src/ui/plugins/use-plugins-manager.d.ts +3 -0
  33. package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +4 -1
  34. package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +10 -0
  35. package/dist/host/ui.js +36 -210
  36. package/dist/host/{use-permissions-6VSHHUMB.js → use-permissions-LLN6TLAL.js} +1 -1
  37. package/dist/manifest.d.ts +10 -1
  38. package/dist/manifest.js +16 -1
  39. package/dist/ssh-keys.d.ts +29 -0
  40. package/dist/ssh-keys.js +85 -0
  41. package/dist/testing.d.ts +52 -15
  42. package/dist/testing.js +279 -2
  43. package/dist/vitest-preset.js +23 -0
  44. package/package.json +5 -1
  45. package/schema/manifest.schema.json +12 -0
  46. package/dist/host/types/src/ui/sidebar/host-export-payload.d.ts +0 -30
package/dist/testing.js CHANGED
@@ -100,7 +100,7 @@ function emptyHostRecord(id, userId) {
100
100
  };
101
101
  }
102
102
  function applyHostInput(record, input) {
103
- const { tags, pluginSettings: _settings, forceKeyboardInteractive: _fki, ...rest } = input;
103
+ const { tags, pluginSettings: _settings, pluginEnable: _pluginEnable, forceKeyboardInteractive: _fki, password: _password, key: _key, keyPassword: _keyPassword, keyType: _keyType, sudoPassword: _sudoPassword, protocolAuth: _protocolAuth, ...rest } = input;
104
104
  const next = { ...record };
105
105
  for (const [key, value] of Object.entries(rest)) {
106
106
  if (value !== undefined)
@@ -124,6 +124,9 @@ export function createFakeContext(options = {}) {
124
124
  const pluginId = options.pluginId ?? "test-plugin";
125
125
  const disposals = [];
126
126
  const emitted = [];
127
+ const hostStatusProviders = [];
128
+ const activityProviders = [];
129
+ const hostLockProviders = [];
127
130
  const kv = new Map();
128
131
  const tables = [];
129
132
  const syncEntities = [];
@@ -136,6 +139,7 @@ export function createFakeContext(options = {}) {
136
139
  const settingsValidators = new Set();
137
140
  const sshConnections = [];
138
141
  const hostShares = [];
142
+ const hostShareSets = [];
139
143
  const desktopWindows = [];
140
144
  const externalClientLaunches = [];
141
145
  const credentialReads = [];
@@ -158,6 +162,7 @@ export function createFakeContext(options = {}) {
158
162
  };
159
163
  const secretStore = new Map();
160
164
  const audits = [];
165
+ const auditSubscribers = new Set();
161
166
  const activities = [];
162
167
  const trackedSessions = [];
163
168
  const interactions = [];
@@ -195,6 +200,7 @@ export function createFakeContext(options = {}) {
195
200
  revokedSessions: [],
196
201
  loginFailures: new Map(),
197
202
  userTokens: [],
203
+ policies: [],
198
204
  };
199
205
  const sshClient = options.sshClient ?? {};
200
206
  let actor = options.actor;
@@ -261,7 +267,7 @@ export function createFakeContext(options = {}) {
261
267
  files: {
262
268
  // A fake path; a test that needs a real folder uses the OS temp dir
263
269
  // itself, not this double.
264
- dataDir: async () => `/tmp/${pluginId}`,
270
+ dataDir: async () => options.dataDir ?? `/tmp/${pluginId}`,
265
271
  },
266
272
  db: options.db
267
273
  ? {
@@ -461,6 +467,27 @@ export function createFakeContext(options = {}) {
461
467
  },
462
468
  disposables,
463
469
  hosts: {
470
+ importMany: async (rows) => {
471
+ const created = rows.map(() => nextHostId++);
472
+ return {
473
+ success: created.length,
474
+ updated: 0,
475
+ skipped: 0,
476
+ failed: 0,
477
+ errors: [],
478
+ createdHostIds: created,
479
+ };
480
+ },
481
+ importSshConfig: async () => ({
482
+ success: 0,
483
+ updated: 0,
484
+ skipped: 0,
485
+ failed: 0,
486
+ errors: [],
487
+ createdHostIds: [],
488
+ }),
489
+ exportMany: async () => ({ hosts: [] }),
490
+ registerImportNormalizer: () => () => { },
464
491
  list: async () => [...hostsById.values()],
465
492
  get: async (hostId) => hostsById.get(hostId) ?? null,
466
493
  checkAccess: async (hostId) => {
@@ -502,6 +529,10 @@ export function createFakeContext(options = {}) {
502
529
  hostShares.push({ hostId, targets, permissionLevel, durationHours });
503
530
  return { hostId, shared: true };
504
531
  },
532
+ setShares: async (hostId, grants) => {
533
+ hostShareSets.push({ hostId, grants: [...grants] });
534
+ return { hostId, shared: true };
535
+ },
505
536
  listUsers: async () => options.shareableUsers ?? [],
506
537
  listRoles: async () => options.shareableRoles ?? [],
507
538
  trackSession: (hostId) => {
@@ -519,7 +550,37 @@ export function createFakeContext(options = {}) {
519
550
  recordActivity: async (hostId, type, hostName) => {
520
551
  activities.push({ hostId, type, hostName });
521
552
  },
553
+ probeTargets: async (filter) => (options.probeTargets ?? []).filter((target) => !filter?.hostIds || filter.hostIds.includes(target.id)),
554
+ sessionActive: (hostId) => options.activeSessions?.includes(hostId) ?? false,
555
+ activity: {
556
+ provide: (provider) => {
557
+ activityProviders.push(provider);
558
+ return () => {
559
+ const at = activityProviders.indexOf(provider);
560
+ if (at >= 0)
561
+ activityProviders.splice(at, 1);
562
+ };
563
+ },
564
+ },
565
+ locks: {
566
+ provide: (provider) => {
567
+ hostLockProviders.push(provider);
568
+ return () => {
569
+ const at = hostLockProviders.indexOf(provider);
570
+ if (at >= 0)
571
+ hostLockProviders.splice(at, 1);
572
+ };
573
+ },
574
+ },
522
575
  status: {
576
+ provide: (provider) => {
577
+ hostStatusProviders.push(provider);
578
+ return () => {
579
+ const at = hostStatusProviders.indexOf(provider);
580
+ if (at >= 0)
581
+ hostStatusProviders.splice(at, 1);
582
+ };
583
+ },
523
584
  get: async (hostId) => hostStatuses.get(hostId) ?? null,
524
585
  check: async (hostId) => hostStatuses.get(hostId) ?? null,
525
586
  reportLogin: (hostId, outcome) => {
@@ -543,11 +604,36 @@ export function createFakeContext(options = {}) {
543
604
  after: (delayMs, fn) => schedule("after", delayMs, fn),
544
605
  },
545
606
  audit: {
607
+ subscribe: (handler) => {
608
+ auditSubscribers.add(handler);
609
+ return () => {
610
+ auditSubscribers.delete(handler);
611
+ };
612
+ },
613
+ list: async (listOptions) => {
614
+ const matching = filterAuditEntries(options.auditEntries ?? [], listOptions?.filters).reverse();
615
+ const offset = listOptions?.offset ?? 0;
616
+ return {
617
+ entries: matching.slice(offset, offset + (listOptions?.limit ?? 50)),
618
+ total: matching.length,
619
+ };
620
+ },
621
+ exportBatches: async function* (filters, batchSize) {
622
+ const matching = filterAuditEntries(options.auditEntries ?? [], filters);
623
+ const size = batchSize ?? 500;
624
+ for (let at = 0; at < matching.length; at += size) {
625
+ yield matching.slice(at, at + size);
626
+ }
627
+ },
546
628
  record: async (entry) => {
547
629
  audits.push({ ...entry });
548
630
  },
549
631
  },
632
+ network: {
633
+ tcpProbe: async (host, port) => options.reachable?.(host, port) ?? false,
634
+ },
550
635
  ssh: {
636
+ probe: async (hostId, probeOptions) => options.reachable?.(hostId, probeOptions?.port) ?? false,
551
637
  connect: async (host, connectOptions) => {
552
638
  sshConnections.push({ host, options: connectOptions });
553
639
  return {
@@ -559,6 +645,16 @@ export function createFakeContext(options = {}) {
559
645
  dispose: () => { },
560
646
  };
561
647
  },
648
+ exec: async (host) => {
649
+ sshConnections.push({ host });
650
+ return {
651
+ stdout: "",
652
+ stderr: "",
653
+ code: 0,
654
+ truncated: false,
655
+ timedOut: false,
656
+ };
657
+ },
562
658
  withConnection: async (host, connectOptions, fn) => {
563
659
  sshConnections.push({ host, pool: connectOptions.pool });
564
660
  return fn(sshClient);
@@ -682,6 +778,23 @@ export function createFakeContext(options = {}) {
682
778
  expiresAt: new Date(Date.now() + ttl * 1000).toISOString(),
683
779
  };
684
780
  },
781
+ policy: {
782
+ provide: (provider) => {
783
+ auth.policies.push(provider);
784
+ return () => {
785
+ const at = auth.policies.indexOf(provider);
786
+ if (at >= 0)
787
+ auth.policies.splice(at, 1);
788
+ };
789
+ },
790
+ },
791
+ listSecondFactors: () => [
792
+ ...auth.secondFactors.map((factor) => ({
793
+ id: factor.id,
794
+ pluginId,
795
+ })),
796
+ ...(options.secondFactors ?? []),
797
+ ],
685
798
  },
686
799
  users: {
687
800
  getLocale: async (userId) => options.locales?.[userId] ?? null,
@@ -696,8 +809,10 @@ export function createFakeContext(options = {}) {
696
809
  return { success: true };
697
810
  },
698
811
  available: () => options.desktopAvailable ?? false,
812
+ local: options.desktopLocal ?? unavailableDesktopLocal(),
699
813
  },
700
814
  credentials: {
815
+ list: async () => [...(options.credentialSummaries ?? [])],
701
816
  listSshKeys: async () => [
702
817
  ...(options.sshKeyCredentials ?? []),
703
818
  ...createdSshKeys.map((key) => ({
@@ -716,6 +831,12 @@ export function createFakeContext(options = {}) {
716
831
  credentialReads.push({ hostId, protocol });
717
832
  return options.protocolTargets?.[`${hostId}:${protocol}`] ?? null;
718
833
  },
834
+ publicKey: async (credentialId) => (options.sshKeyCredentials ?? []).find((k) => k.id === credentialId)
835
+ ?.publicKey ?? null,
836
+ resolveKeyRefs: async (key, passphrase) => ({
837
+ key,
838
+ passphrase: passphrase ?? undefined,
839
+ }),
719
840
  registerSecretResolver: (scheme, resolve) => {
720
841
  auth.secretResolvers.set(scheme, resolve);
721
842
  disposals.push(() => {
@@ -863,6 +984,9 @@ export function createFakeContext(options = {}) {
863
984
  return errors;
864
985
  },
865
986
  disposals,
987
+ hostStatusProviders,
988
+ activityProviders,
989
+ hostLockProviders,
866
990
  emitted,
867
991
  kv,
868
992
  tables,
@@ -873,6 +997,7 @@ export function createFakeContext(options = {}) {
873
997
  coreSettings,
874
998
  sshConnections,
875
999
  hostShares,
1000
+ hostShareSets,
876
1001
  auth,
877
1002
  desktopWindows,
878
1003
  externalClientLaunches,
@@ -886,6 +1011,10 @@ export function createFakeContext(options = {}) {
886
1011
  tls,
887
1012
  secretStore,
888
1013
  audits,
1014
+ emitAudit: (entry) => {
1015
+ for (const handler of auditSubscribers)
1016
+ handler(entry);
1017
+ },
889
1018
  activities,
890
1019
  trackedSessions,
891
1020
  interactions,
@@ -930,6 +1059,49 @@ export function createFakeContext(options = {}) {
930
1059
  * (users:impersonate on issueUserToken), and users:read on ctx.users. Reading a plugin's own settings is deliberately
931
1060
  * ungated. As the SDK grows a member, add its gate here in the same shape.
932
1061
  */
1062
+ function filterAuditEntries(entries, filters) {
1063
+ return entries.filter((entry) => (filters?.userId === undefined || entry.userId === filters.userId) &&
1064
+ (filters?.action === undefined || entry.action === filters.action) &&
1065
+ (filters?.resourceType === undefined ||
1066
+ entry.resourceType === filters.resourceType) &&
1067
+ (filters?.success === undefined || entry.success === filters.success) &&
1068
+ (filters?.startDate === undefined ||
1069
+ entry.timestamp >= filters.startDate) &&
1070
+ (filters?.endDate === undefined || entry.timestamp <= filters.endDate));
1071
+ }
1072
+ function gateDesktopLocal(target, check) {
1073
+ const wrap = (value) => {
1074
+ if (typeof value === "function") {
1075
+ return (...args) => {
1076
+ check();
1077
+ return value(...args);
1078
+ };
1079
+ }
1080
+ if (value && typeof value === "object") {
1081
+ return new Proxy(value, {
1082
+ get: (obj, key) => wrap(obj[key]),
1083
+ });
1084
+ }
1085
+ return value;
1086
+ };
1087
+ return new Proxy(target, {
1088
+ get: (obj, key) => key === "available"
1089
+ ? obj.available
1090
+ : wrap(obj[key]),
1091
+ });
1092
+ }
1093
+ function unavailableDesktopLocal() {
1094
+ const fail = () => {
1095
+ throw new Error("ctx.desktop.local needs the desktopLocal test option");
1096
+ };
1097
+ const deep = () => new Proxy(() => fail(), { get: () => deep(), apply: () => fail() });
1098
+ return {
1099
+ available: () => false,
1100
+ info: fail,
1101
+ fs: deep(),
1102
+ pty: deep(),
1103
+ };
1104
+ }
933
1105
  export function createMockCtx(options = {}) {
934
1106
  const granted = new Set(options.capabilities ?? []);
935
1107
  const checked = [];
@@ -938,6 +1110,7 @@ export function createMockCtx(options = {}) {
938
1110
  actor: options.actor,
939
1111
  settings: options.settings,
940
1112
  coreSettings: options.coreSettings,
1113
+ dataDir: options.dataDir,
941
1114
  sshClient: options.sshClient,
942
1115
  db: options.db,
943
1116
  router: options.router,
@@ -949,8 +1122,14 @@ export function createMockCtx(options = {}) {
949
1122
  services: options.services,
950
1123
  protocolTargets: options.protocolTargets,
951
1124
  sshKeyCredentials: options.sshKeyCredentials,
1125
+ credentialSummaries: options.credentialSummaries,
952
1126
  desktopAvailable: options.desktopAvailable,
1127
+ desktopLocal: options.desktopLocal,
1128
+ auditEntries: options.auditEntries,
953
1129
  hostStatuses: options.hostStatuses,
1130
+ probeTargets: options.probeTargets,
1131
+ activeSessions: options.activeSessions,
1132
+ reachable: options.reachable,
954
1133
  notificationChannels: options.notificationChannels,
955
1134
  fetch: options.fetch,
956
1135
  process: options.process,
@@ -961,6 +1140,7 @@ export function createMockCtx(options = {}) {
961
1140
  baseUrl: options.baseUrl,
962
1141
  linkedUsers: options.linkedUsers,
963
1142
  loginAttemptLimit: options.loginAttemptLimit,
1143
+ secondFactors: options.secondFactors,
964
1144
  locales: options.locales,
965
1145
  manifest: {
966
1146
  capabilities: options.capabilities ?? [],
@@ -1077,6 +1257,24 @@ export function createMockCtx(options = {}) {
1077
1257
  },
1078
1258
  },
1079
1259
  hosts: {
1260
+ importMany: async (rows, importOptions) => {
1261
+ require("hosts:write");
1262
+ if (importOptions?.credentials?.length)
1263
+ require("credentials:write");
1264
+ return ctx.hosts.importMany(rows, importOptions);
1265
+ },
1266
+ importSshConfig: async (content, importOptions) => {
1267
+ require("hosts:write");
1268
+ return ctx.hosts.importSshConfig(content, importOptions);
1269
+ },
1270
+ exportMany: async (exportOptions) => {
1271
+ require("hosts:export");
1272
+ return ctx.hosts.exportMany(exportOptions);
1273
+ },
1274
+ registerImportNormalizer: (normalizer) => {
1275
+ require("hosts:write");
1276
+ return ctx.hosts.registerImportNormalizer(normalizer);
1277
+ },
1080
1278
  list: async () => {
1081
1279
  require("hosts:read");
1082
1280
  return ctx.hosts.list();
@@ -1109,6 +1307,10 @@ export function createMockCtx(options = {}) {
1109
1307
  require("hosts:write");
1110
1308
  return ctx.hosts.share(hostId, targets, permissionLevel, durationHours);
1111
1309
  },
1310
+ setShares: async (hostId, grants) => {
1311
+ require("hosts:write");
1312
+ return ctx.hosts.setShares(hostId, grants);
1313
+ },
1112
1314
  listUsers: async () => {
1113
1315
  require("hosts:write");
1114
1316
  return ctx.hosts.listUsers();
@@ -1125,7 +1327,32 @@ export function createMockCtx(options = {}) {
1125
1327
  require("hosts:read");
1126
1328
  return ctx.hosts.recordActivity(hostId, type, hostName);
1127
1329
  },
1330
+ probeTargets: async (filter) => {
1331
+ require("hosts:read");
1332
+ return ctx.hosts.probeTargets(filter);
1333
+ },
1334
+ sessionActive: (hostId) => {
1335
+ require("hosts:read");
1336
+ return ctx.hosts.sessionActive(hostId);
1337
+ },
1338
+ activity: {
1339
+ provide: (provider) => {
1340
+ require("hosts:activity-provide");
1341
+ return ctx.hosts.activity.provide(provider);
1342
+ },
1343
+ },
1344
+ locks: {
1345
+ provide: (provider) => {
1346
+ require("hosts:lock");
1347
+ return ctx.hosts.locks.provide(provider);
1348
+ },
1349
+ },
1128
1350
  status: {
1351
+ provide: (provider) => {
1352
+ require("hosts:read");
1353
+ require("hosts:status-provide");
1354
+ return ctx.hosts.status.provide(provider);
1355
+ },
1129
1356
  get: async (hostId) => {
1130
1357
  require("hosts:read");
1131
1358
  return ctx.hosts.status.get(hostId);
@@ -1146,11 +1373,20 @@ export function createMockCtx(options = {}) {
1146
1373
  },
1147
1374
  ssh: {
1148
1375
  ...ctx.ssh,
1376
+ probe: async (hostId, probeOptions) => {
1377
+ require("ssh:connect");
1378
+ return ctx.ssh.probe(hostId, probeOptions);
1379
+ },
1149
1380
  connect: async (host, connectOptions) => {
1150
1381
  require("ssh:connect");
1151
1382
  require("credentials:use");
1152
1383
  return ctx.ssh.connect(host, connectOptions);
1153
1384
  },
1385
+ exec: async (host, command, execOptions) => {
1386
+ require("ssh:connect");
1387
+ require("credentials:use");
1388
+ return ctx.ssh.exec(host, command, execOptions);
1389
+ },
1154
1390
  withConnection: async (host, connectOptions, fn) => {
1155
1391
  require("ssh:connect");
1156
1392
  require("credentials:use");
@@ -1236,6 +1472,13 @@ export function createMockCtx(options = {}) {
1236
1472
  require("users:impersonate");
1237
1473
  return ctx.auth.issueUserToken(userId, tokenOptions);
1238
1474
  },
1475
+ policy: {
1476
+ provide: (provider) => {
1477
+ require("auth:policy");
1478
+ return ctx.auth.policy.provide(provider);
1479
+ },
1480
+ },
1481
+ listSecondFactors: () => ctx.auth.listSecondFactors(),
1239
1482
  },
1240
1483
  users: {
1241
1484
  getLocale: async (userId) => {
@@ -1243,6 +1486,21 @@ export function createMockCtx(options = {}) {
1243
1486
  return ctx.users.getLocale(userId);
1244
1487
  },
1245
1488
  },
1489
+ audit: {
1490
+ record: (entry) => ctx.audit.record(entry),
1491
+ subscribe: (handler) => {
1492
+ require("audit:read");
1493
+ return ctx.audit.subscribe(handler);
1494
+ },
1495
+ list: async (listOptions) => {
1496
+ require("audit:read");
1497
+ return ctx.audit.list(listOptions);
1498
+ },
1499
+ exportBatches: (filters, batchSize) => {
1500
+ require("audit:read");
1501
+ return ctx.audit.exportBatches(filters, batchSize);
1502
+ },
1503
+ },
1246
1504
  desktop: {
1247
1505
  openIsolatedWindow: async (request) => {
1248
1506
  require("desktop:window");
@@ -1253,8 +1511,13 @@ export function createMockCtx(options = {}) {
1253
1511
  return ctx.desktop.launchExternalClient(request);
1254
1512
  },
1255
1513
  available: () => ctx.desktop.available(),
1514
+ local: gateDesktopLocal(ctx.desktop.local, () => require("desktop:local")),
1256
1515
  },
1257
1516
  credentials: {
1517
+ list: async () => {
1518
+ require("credentials:use");
1519
+ return ctx.credentials.list();
1520
+ },
1258
1521
  listSshKeys: async () => {
1259
1522
  require("credentials:use");
1260
1523
  return ctx.credentials.listSshKeys();
@@ -1271,6 +1534,14 @@ export function createMockCtx(options = {}) {
1271
1534
  }
1272
1535
  return ctx.credentials.resolveHostProtocol(hostId, protocol);
1273
1536
  },
1537
+ publicKey: async (credentialId) => {
1538
+ require("credentials:use");
1539
+ return ctx.credentials.publicKey(credentialId);
1540
+ },
1541
+ resolveKeyRefs: async (key, passphrase) => {
1542
+ require("credentials:read");
1543
+ return ctx.credentials.resolveKeyRefs(key, passphrase);
1544
+ },
1274
1545
  registerSecretResolver: (scheme, resolve) => {
1275
1546
  require("auth:provide");
1276
1547
  ctx.credentials.registerSecretResolver(scheme, resolve);
@@ -1294,6 +1565,12 @@ export function createMockCtx(options = {}) {
1294
1565
  require("network:outbound");
1295
1566
  return ctx.fetch(url, init);
1296
1567
  },
1568
+ network: {
1569
+ tcpProbe: async (host, port, probeOptions) => {
1570
+ require("network:probe");
1571
+ return ctx.network.tcpProbe(host, port, probeOptions);
1572
+ },
1573
+ },
1297
1574
  process: {
1298
1575
  run: async (file, args, runOptions) => {
1299
1576
  require("process:spawn");
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * Two projects: tests/backend runs in node, tests/frontend in jsdom.
10
10
  */
11
+ import { createRequire } from "node:module";
11
12
  import fs from "node:fs";
12
13
  import path from "node:path";
13
14
  import { fileURLToPath } from "node:url";
@@ -84,6 +85,28 @@ export function pluginVitestConfig(configUrl, options = {}) {
84
85
  // Vite must be allowed to read it, and React must come from the plugin.
85
86
  const sdkDir = fs.realpathSync(fileURLToPath(new URL("..", import.meta.url)));
86
87
  const server = { fs: { allow: [pluginDir, sdkDir] } };
88
+ // dedupe alone is not enough: react-dom or the testing library loaded from
89
+ // the linked SDK's own node_modules pull in a second React, and hooks then
90
+ // see no renderer.
91
+ if (!sdkDir.startsWith(pluginDir)) {
92
+ const pluginRequire = createRequire(path.join(pluginDir, "package.json"));
93
+ for (const name of [
94
+ "react",
95
+ "react-dom",
96
+ "@testing-library/react",
97
+ "react-i18next",
98
+ "i18next",
99
+ ]) {
100
+ if (alias[name])
101
+ continue;
102
+ try {
103
+ alias[name] = path.dirname(pluginRequire.resolve(`${name}/package.json`));
104
+ }
105
+ catch {
106
+ // Not installed in the plugin: leave it to normal resolution.
107
+ }
108
+ }
109
+ }
87
110
  const resolve = {
88
111
  alias,
89
112
  dedupe: ["react", "react-dom", "react-i18next", "i18next"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@termix-ssh/plugin-sdk",
3
- "version": "1.0.12",
3
+ "version": "1.0.15",
4
4
  "description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -85,6 +85,10 @@
85
85
  "types": "./dist/ssh-certs.d.ts",
86
86
  "default": "./dist/ssh-certs.js"
87
87
  },
88
+ "./ssh-keys": {
89
+ "types": "./dist/ssh-keys.d.ts",
90
+ "default": "./dist/ssh-keys.js"
91
+ },
88
92
  "./tsconfig.plugin.json": "./tsconfig.plugin.json",
89
93
  "./schema/manifest.schema.json": "./schema/manifest.schema.json",
90
94
  "./ui": {
@@ -151,9 +151,11 @@
151
151
  "process:spawn",
152
152
  "users:write",
153
153
  "auth:provide",
154
+ "auth:policy",
154
155
  "system:tls",
155
156
  "device:serial",
156
157
  "hosts:write",
158
+ "hosts:export",
157
159
  "credentials:use",
158
160
  "credentials:write",
159
161
  "network:outbound",
@@ -163,7 +165,12 @@
163
165
  "events:core",
164
166
  "notify:send",
165
167
  "audit:read",
168
+ "network:probe",
169
+ "hosts:status-provide",
170
+ "hosts:activity-provide",
171
+ "hosts:lock",
166
172
  "desktop:window",
173
+ "desktop:local",
167
174
  "users:impersonate",
168
175
  "db:core-refs",
169
176
  "hosts:read",
@@ -1261,6 +1268,11 @@
1261
1268
  "enum": ["mirror", "local", "server"],
1262
1269
  "description": "How a desktop linked to a server treats this plugin: mirror follows the server, local is decided on each desktop, server never runs on a linked desktop. Defaults to mirror."
1263
1270
  },
1271
+ "runtime": {
1272
+ "type": "string",
1273
+ "enum": ["desktop"],
1274
+ "description": "desktop: the plugin only loads on a desktop app's own embedded backend, never on a server. It implies desktop local and may declare desktop:local."
1275
+ },
1264
1276
  "nativeDependencies": {
1265
1277
  "type": "array",
1266
1278
  "minItems": 1,
@@ -1,30 +0,0 @@
1
- export type FieldGroup = "connection" | "notes" | "tags" | "proxy" | "jumpHosts" | "featureFlags" | "advanced";
2
- export interface ExportPayload {
3
- version?: string;
4
- exportedAt?: string;
5
- credentials?: Record<string, unknown>[];
6
- hosts: Record<string, unknown>[];
7
- }
8
- export declare const SECRET_KEYS: string[];
9
- /**
10
- * Keys inside a plugin's JSON host settings that hold secrets, per plugin and
11
- * field, from each manifest's `secretKeys`.
12
- */
13
- export type PluginSecretKeys = Record<string, Record<string, string[]>>;
14
- /** Reads the secret keys the plugins declare in their host settings. */
15
- export declare function pluginSecretKeys(summaries: Array<{
16
- id: string;
17
- contributes?: {
18
- settings?: {
19
- host?: {
20
- fields?: Array<{
21
- key: string;
22
- secretKeys?: string[];
23
- }>;
24
- };
25
- };
26
- } | null;
27
- }>): PluginSecretKeys;
28
- export declare function hostKey(host: Record<string, unknown>): string;
29
- export declare function buildExportPayload(raw: ExportPayload, selected: Set<string> | null, groups: Set<FieldGroup>, withCredentials: boolean, pluginSecrets?: PluginSecretKeys): ExportPayload;
30
- export declare function maskSecrets(payload: ExportPayload, pluginSecrets?: PluginSecretKeys): ExportPayload;