@positronic/cli 0.0.55 → 0.0.57

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 (94) hide show
  1. package/dist/src/cli.js +142 -2
  2. package/dist/src/commands/auth.js +98 -0
  3. package/dist/src/commands/brain.js +3 -2
  4. package/dist/src/commands/helpers.js +48 -10
  5. package/dist/src/commands/project-config-manager.js +119 -0
  6. package/dist/src/commands/users.js +91 -0
  7. package/dist/src/components/agent-chat-view.js +125 -0
  8. package/dist/src/components/auth-list.js +56 -0
  9. package/dist/src/components/auth-login.js +209 -0
  10. package/dist/src/components/auth-logout.js +75 -0
  11. package/dist/src/components/auth-status.js +88 -0
  12. package/dist/src/components/brain-run.js +287 -254
  13. package/dist/src/components/brain-top-table.js +4 -0
  14. package/dist/src/components/event-detail.js +364 -0
  15. package/dist/src/components/events-view.js +379 -0
  16. package/dist/src/components/state-view.js +52 -0
  17. package/dist/src/components/top-navigator.js +80 -6
  18. package/dist/src/components/types.js +1 -0
  19. package/dist/src/components/users-create.js +293 -0
  20. package/dist/src/components/users-delete.js +294 -0
  21. package/dist/src/components/users-keys-add.js +156 -0
  22. package/dist/src/components/users-keys-list.js +119 -0
  23. package/dist/src/components/users-keys-remove.js +299 -0
  24. package/dist/src/components/users-list.js +109 -0
  25. package/dist/src/components/watch-keyboard.js +136 -0
  26. package/dist/src/components/watch-machine.js +573 -0
  27. package/dist/src/components/watch-resolver.js +3 -2
  28. package/dist/src/components/watch.js +390 -36
  29. package/dist/src/hooks/useApi.js +80 -42
  30. package/dist/src/lib/request-signer.js +208 -0
  31. package/dist/src/lib/ssh-key-utils.js +212 -0
  32. package/dist/src/utils/agent-utils.js +107 -0
  33. package/dist/types/cli.d.ts.map +1 -1
  34. package/dist/types/commands/auth.d.ts +36 -0
  35. package/dist/types/commands/auth.d.ts.map +1 -0
  36. package/dist/types/commands/brain.d.ts +2 -1
  37. package/dist/types/commands/brain.d.ts.map +1 -1
  38. package/dist/types/commands/helpers.d.ts.map +1 -1
  39. package/dist/types/commands/project-config-manager.d.ts +43 -0
  40. package/dist/types/commands/project-config-manager.d.ts.map +1 -1
  41. package/dist/types/commands/users.d.ts +33 -0
  42. package/dist/types/commands/users.d.ts.map +1 -0
  43. package/dist/types/components/agent-chat-view.d.ts +12 -0
  44. package/dist/types/components/agent-chat-view.d.ts.map +1 -0
  45. package/dist/types/components/auth-list.d.ts +7 -0
  46. package/dist/types/components/auth-list.d.ts.map +1 -0
  47. package/dist/types/components/auth-login.d.ts +9 -0
  48. package/dist/types/components/auth-login.d.ts.map +1 -0
  49. package/dist/types/components/auth-logout.d.ts +8 -0
  50. package/dist/types/components/auth-logout.d.ts.map +1 -0
  51. package/dist/types/components/auth-status.d.ts +7 -0
  52. package/dist/types/components/auth-status.d.ts.map +1 -0
  53. package/dist/types/components/brain-run.d.ts +11 -1
  54. package/dist/types/components/brain-run.d.ts.map +1 -1
  55. package/dist/types/components/brain-top-table.d.ts.map +1 -1
  56. package/dist/types/components/event-detail.d.ts +10 -0
  57. package/dist/types/components/event-detail.d.ts.map +1 -0
  58. package/dist/types/components/events-view.d.ts +13 -0
  59. package/dist/types/components/events-view.d.ts.map +1 -0
  60. package/dist/types/components/state-view.d.ts +13 -0
  61. package/dist/types/components/state-view.d.ts.map +1 -0
  62. package/dist/types/components/top-navigator.d.ts.map +1 -1
  63. package/dist/types/components/types.d.ts +11 -0
  64. package/dist/types/components/types.d.ts.map +1 -0
  65. package/dist/types/components/users-create.d.ts +6 -0
  66. package/dist/types/components/users-create.d.ts.map +1 -0
  67. package/dist/types/components/users-delete.d.ts +7 -0
  68. package/dist/types/components/users-delete.d.ts.map +1 -0
  69. package/dist/types/components/users-keys-add.d.ts +8 -0
  70. package/dist/types/components/users-keys-add.d.ts.map +1 -0
  71. package/dist/types/components/users-keys-list.d.ts +6 -0
  72. package/dist/types/components/users-keys-list.d.ts.map +1 -0
  73. package/dist/types/components/users-keys-remove.d.ts +8 -0
  74. package/dist/types/components/users-keys-remove.d.ts.map +1 -0
  75. package/dist/types/components/users-list.d.ts +2 -0
  76. package/dist/types/components/users-list.d.ts.map +1 -0
  77. package/dist/types/components/watch-keyboard.d.ts +56 -0
  78. package/dist/types/components/watch-keyboard.d.ts.map +1 -0
  79. package/dist/types/components/watch-machine.d.ts +171 -0
  80. package/dist/types/components/watch-machine.d.ts.map +1 -0
  81. package/dist/types/components/watch-resolver.d.ts +2 -1
  82. package/dist/types/components/watch-resolver.d.ts.map +1 -1
  83. package/dist/types/components/watch.d.ts +2 -1
  84. package/dist/types/components/watch.d.ts.map +1 -1
  85. package/dist/types/hooks/useApi.d.ts.map +1 -1
  86. package/dist/types/hooks/useBrainMachine.d.ts +9 -3
  87. package/dist/types/hooks/useBrainMachine.d.ts.map +1 -1
  88. package/dist/types/lib/request-signer.d.ts +51 -0
  89. package/dist/types/lib/request-signer.d.ts.map +1 -0
  90. package/dist/types/lib/ssh-key-utils.d.ts +45 -0
  91. package/dist/types/lib/ssh-key-utils.d.ts.map +1 -0
  92. package/dist/types/utils/agent-utils.d.ts +20 -0
  93. package/dist/types/utils/agent-utils.d.ts.map +1 -0
  94. package/package.json +7 -4
@@ -246,9 +246,9 @@ export function useApiGet(endpoint, options) {
246
246
  case 0:
247
247
  _state.trys.push([
248
248
  0,
249
- 6,
250
249
  7,
251
- 8
250
+ 8,
251
+ 9
252
252
  ]);
253
253
  setLoading(true);
254
254
  setError(null);
@@ -273,27 +273,41 @@ export function useApiGet(endpoint, options) {
273
273
  setData(result);
274
274
  return [
275
275
  3,
276
- 5
276
+ 6
277
277
  ];
278
278
  case 3:
279
+ if (!(response.status === 401)) return [
280
+ 3,
281
+ 4
282
+ ];
283
+ setError({
284
+ title: 'Authentication Required',
285
+ message: 'Your request could not be authenticated.',
286
+ details: "Run 'px auth login' to configure your SSH key, or check that your key is registered on the server."
287
+ });
288
+ return [
289
+ 3,
290
+ 6
291
+ ];
292
+ case 4:
279
293
  return [
280
294
  4,
281
295
  response.text()
282
296
  ];
283
- case 4:
297
+ case 5:
284
298
  errorText = _state.sent();
285
299
  setError({
286
300
  title: 'Server Error',
287
301
  message: "Error fetching ".concat(endpoint, ": ").concat(response.status, " ").concat(response.statusText),
288
302
  details: "Server response: ".concat(errorText)
289
303
  });
290
- _state.label = 5;
291
- case 5:
304
+ _state.label = 6;
305
+ case 6:
292
306
  return [
293
307
  3,
294
- 8
308
+ 9
295
309
  ];
296
- case 6:
310
+ case 7:
297
311
  err = _state.sent();
298
312
  baseError = getConnectionErrorMessage();
299
313
  errorDetails = err.message;
@@ -305,14 +319,14 @@ export function useApiGet(endpoint, options) {
305
319
  }));
306
320
  return [
307
321
  3,
308
- 8
322
+ 9
309
323
  ];
310
- case 7:
324
+ case 8:
311
325
  setLoading(false);
312
326
  return [
313
327
  7
314
328
  ];
315
- case 8:
329
+ case 9:
316
330
  return [
317
331
  2
318
332
  ];
@@ -336,15 +350,15 @@ export function useApiPost(endpoint, defaultOptions) {
336
350
  var _useState2 = _sliced_to_array(useState(null), 2), error = _useState2[0], setError = _useState2[1];
337
351
  var execute = useCallback(function(body, options) {
338
352
  return _async_to_generator(function() {
339
- var response, result, errorText, errorObj, err, baseError, errorDetails, errorObj1;
353
+ var response, result, errorObj, errorText, errorObj1, err, baseError, errorDetails, errorObj2;
340
354
  return _ts_generator(this, function(_state) {
341
355
  switch(_state.label){
342
356
  case 0:
343
357
  _state.trys.push([
344
358
  0,
345
- 6,
346
359
  7,
347
- 8
360
+ 8,
361
+ 9
348
362
  ]);
349
363
  setLoading(true);
350
364
  setError(null);
@@ -358,7 +372,7 @@ export function useApiPost(endpoint, defaultOptions) {
358
372
  ];
359
373
  case 1:
360
374
  response = _state.sent();
361
- if (!(response.status === 200 || response.status === 201)) return [
375
+ if (!(response.status === 200 || response.status === 201 || response.status === 202)) return [
362
376
  3,
363
377
  3
364
378
  ];
@@ -374,25 +388,37 @@ export function useApiPost(endpoint, defaultOptions) {
374
388
  result
375
389
  ];
376
390
  case 3:
391
+ if (!(response.status === 401)) return [
392
+ 3,
393
+ 4
394
+ ];
395
+ errorObj = {
396
+ title: 'Authentication Required',
397
+ message: 'Your request could not be authenticated.',
398
+ details: "Run 'px auth login' to configure your SSH key, or check that your key is registered on the server."
399
+ };
400
+ setError(errorObj);
401
+ throw errorObj;
402
+ case 4:
377
403
  return [
378
404
  4,
379
405
  response.text()
380
406
  ];
381
- case 4:
407
+ case 5:
382
408
  errorText = _state.sent();
383
- errorObj = {
409
+ errorObj1 = {
384
410
  title: 'Server Error',
385
411
  message: "Error posting to ".concat(endpoint, ": ").concat(response.status, " ").concat(response.statusText),
386
412
  details: "Server response: ".concat(errorText)
387
413
  };
388
- setError(errorObj);
389
- throw errorObj;
390
- case 5:
414
+ setError(errorObj1);
415
+ throw errorObj1;
416
+ case 6:
391
417
  return [
392
418
  3,
393
- 8
419
+ 9
394
420
  ];
395
- case 6:
421
+ case 7:
396
422
  err = _state.sent();
397
423
  // If it's already our error object, don't wrap it again
398
424
  if (err.title && err.message) {
@@ -404,17 +430,17 @@ export function useApiPost(endpoint, defaultOptions) {
404
430
  if (err.code === 'ECONNREFUSED') {
405
431
  errorDetails = 'Connection refused. The server might not be running or is listening on a different port.';
406
432
  }
407
- errorObj1 = _object_spread_props(_object_spread({}, baseError), {
433
+ errorObj2 = _object_spread_props(_object_spread({}, baseError), {
408
434
  details: "".concat(baseError.details, " ").concat(errorDetails)
409
435
  });
410
- setError(errorObj1);
411
- throw errorObj1;
412
- case 7:
436
+ setError(errorObj2);
437
+ throw errorObj2;
438
+ case 8:
413
439
  setLoading(false);
414
440
  return [
415
441
  7
416
442
  ];
417
- case 8:
443
+ case 9:
418
444
  return [
419
445
  2
420
446
  ];
@@ -437,15 +463,15 @@ export function useApiDelete(resourceType) {
437
463
  var _useState1 = _sliced_to_array(useState(null), 2), error = _useState1[0], setError = _useState1[1];
438
464
  var execute = useCallback(function(endpoint, options) {
439
465
  return _async_to_generator(function() {
440
- var response, errorText, errorObj, err, baseError, errorDetails, errorObj1;
466
+ var response, errorObj, errorText, errorObj1, err, baseError, errorDetails, errorObj2;
441
467
  return _ts_generator(this, function(_state) {
442
468
  switch(_state.label){
443
469
  case 0:
444
470
  _state.trys.push([
445
471
  0,
446
- 5,
447
472
  6,
448
- 7
473
+ 7,
474
+ 8
449
475
  ]);
450
476
  setLoading(true);
451
477
  setError(null);
@@ -466,25 +492,37 @@ export function useApiDelete(resourceType) {
466
492
  true
467
493
  ];
468
494
  case 2:
495
+ if (!(response.status === 401)) return [
496
+ 3,
497
+ 3
498
+ ];
499
+ errorObj = {
500
+ title: 'Authentication Required',
501
+ message: 'Your request could not be authenticated.',
502
+ details: "Run 'px auth login' to configure your SSH key, or check that your key is registered on the server."
503
+ };
504
+ setError(errorObj);
505
+ throw errorObj;
506
+ case 3:
469
507
  return [
470
508
  4,
471
509
  response.text()
472
510
  ];
473
- case 3:
511
+ case 4:
474
512
  errorText = _state.sent();
475
- errorObj = {
513
+ errorObj1 = {
476
514
  title: 'Server Error',
477
515
  message: "Error deleting ".concat(resourceType, ": ").concat(response.status, " ").concat(response.statusText),
478
516
  details: "Server response: ".concat(errorText)
479
517
  };
480
- setError(errorObj);
481
- throw errorObj;
482
- case 4:
518
+ setError(errorObj1);
519
+ throw errorObj1;
520
+ case 5:
483
521
  return [
484
522
  3,
485
- 7
523
+ 8
486
524
  ];
487
- case 5:
525
+ case 6:
488
526
  err = _state.sent();
489
527
  // If it's already our error object, don't wrap it again
490
528
  if (err.title && err.message) {
@@ -496,17 +534,17 @@ export function useApiDelete(resourceType) {
496
534
  if (err.code === 'ECONNREFUSED') {
497
535
  errorDetails = 'Connection refused. The server might not be running or is listening on a different port.';
498
536
  }
499
- errorObj1 = _object_spread_props(_object_spread({}, baseError), {
537
+ errorObj2 = _object_spread_props(_object_spread({}, baseError), {
500
538
  details: "".concat(baseError.details, " ").concat(errorDetails)
501
539
  });
502
- setError(errorObj1);
503
- throw errorObj1;
504
- case 6:
540
+ setError(errorObj2);
541
+ throw errorObj2;
542
+ case 7:
505
543
  setLoading(false);
506
544
  return [
507
545
  7
508
546
  ];
509
- case 7:
547
+ case 8:
510
548
  return [
511
549
  2
512
550
  ];
@@ -0,0 +1,208 @@
1
+ function _class_call_check(instance, Constructor) {
2
+ if (!(instance instanceof Constructor)) {
3
+ throw new TypeError("Cannot call a class as a function");
4
+ }
5
+ }
6
+ function _defineProperties(target, props) {
7
+ for(var i = 0; i < props.length; i++){
8
+ var descriptor = props[i];
9
+ descriptor.enumerable = descriptor.enumerable || false;
10
+ descriptor.configurable = true;
11
+ if ("value" in descriptor) descriptor.writable = true;
12
+ Object.defineProperty(target, descriptor.key, descriptor);
13
+ }
14
+ }
15
+ function _create_class(Constructor, protoProps, staticProps) {
16
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
17
+ if (staticProps) _defineProperties(Constructor, staticProps);
18
+ return Constructor;
19
+ }
20
+ function _define_property(obj, key, value) {
21
+ if (key in obj) {
22
+ Object.defineProperty(obj, key, {
23
+ value: value,
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true
27
+ });
28
+ } else {
29
+ obj[key] = value;
30
+ }
31
+ return obj;
32
+ }
33
+ function _instanceof(left, right) {
34
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
35
+ return !!right[Symbol.hasInstance](left);
36
+ } else {
37
+ return left instanceof right;
38
+ }
39
+ }
40
+ import { loadPrivateKey, getPrivateKeyFingerprint, signWithPrivateKey, resolvePrivateKeyPath } from './ssh-key-utils.js';
41
+ import { existsSync } from 'fs';
42
+ import { ProjectConfigManager } from '../commands/project-config-manager.js';
43
+ /**
44
+ * Request signer for RFC 9421 HTTP Message Signatures
45
+ */ export var RequestSigner = /*#__PURE__*/ function() {
46
+ "use strict";
47
+ function RequestSigner() {
48
+ _class_call_check(this, RequestSigner);
49
+ _define_property(this, "privateKey", null);
50
+ _define_property(this, "fingerprint", null);
51
+ _define_property(this, "initialized", false);
52
+ _define_property(this, "initError", null);
53
+ this.initialize();
54
+ }
55
+ _create_class(RequestSigner, [
56
+ {
57
+ key: "initialize",
58
+ value: function initialize() {
59
+ try {
60
+ // Get configured path from project config manager
61
+ var configManager = new ProjectConfigManager();
62
+ var configuredPath = configManager.getPrivateKeyPath();
63
+ var keyPath = resolvePrivateKeyPath(configuredPath);
64
+ if (!existsSync(keyPath)) {
65
+ this.initError = new Error("Private key not found at ".concat(keyPath, ". Run 'px auth login' to configure your SSH key, or set POSITRONIC_PRIVATE_KEY environment variable."));
66
+ return;
67
+ }
68
+ this.privateKey = loadPrivateKey(keyPath);
69
+ this.fingerprint = getPrivateKeyFingerprint(this.privateKey);
70
+ this.initialized = true;
71
+ } catch (error) {
72
+ this.initError = _instanceof(error, Error) ? error : new Error('Failed to initialize request signer');
73
+ }
74
+ }
75
+ },
76
+ {
77
+ /**
78
+ * Check if the signer is ready to sign requests
79
+ */ key: "isReady",
80
+ value: function isReady() {
81
+ return this.initialized && this.privateKey !== null;
82
+ }
83
+ },
84
+ {
85
+ /**
86
+ * Get the error that occurred during initialization, if any
87
+ */ key: "getError",
88
+ value: function getError() {
89
+ return this.initError;
90
+ }
91
+ },
92
+ {
93
+ /**
94
+ * Get the fingerprint of the loaded private key
95
+ */ key: "getFingerprint",
96
+ value: function getFingerprint() {
97
+ return this.fingerprint;
98
+ }
99
+ },
100
+ {
101
+ /**
102
+ * Sign an HTTP request and return the signature headers
103
+ */ key: "signRequest",
104
+ value: function signRequest(method, url) {
105
+ var headers = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
106
+ if (!this.privateKey || !this.fingerprint) {
107
+ throw new Error('Request signer not initialized');
108
+ }
109
+ var parsedUrl = new URL(url);
110
+ var created = Math.floor(Date.now() / 1000);
111
+ // Build the signature base
112
+ var coveredComponents = [
113
+ '"@method"',
114
+ '"@path"',
115
+ '"@authority"'
116
+ ];
117
+ // Add content-type if present
118
+ if (headers['Content-Type'] || headers['content-type']) {
119
+ coveredComponents.push('"content-type"');
120
+ }
121
+ // Create the signature base string
122
+ var signatureBaseLines = [];
123
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
124
+ try {
125
+ for(var _iterator = coveredComponents[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
126
+ var component = _step.value;
127
+ var componentName = component.replace(/"/g, '');
128
+ if (componentName === '@method') {
129
+ signatureBaseLines.push('"@method": '.concat(method.toUpperCase()));
130
+ } else if (componentName === '@path') {
131
+ signatureBaseLines.push('"@path": '.concat(parsedUrl.pathname));
132
+ } else if (componentName === '@authority') {
133
+ signatureBaseLines.push('"@authority": '.concat(parsedUrl.host));
134
+ } else {
135
+ // Regular header
136
+ var headerValue = headers[componentName] || headers[componentName.toLowerCase()] || headers[componentName.charAt(0).toUpperCase() + componentName.slice(1)];
137
+ if (headerValue) {
138
+ signatureBaseLines.push('"'.concat(componentName.toLowerCase(), '": ').concat(headerValue));
139
+ }
140
+ }
141
+ }
142
+ } catch (err) {
143
+ _didIteratorError = true;
144
+ _iteratorError = err;
145
+ } finally{
146
+ try {
147
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
148
+ _iterator.return();
149
+ }
150
+ } finally{
151
+ if (_didIteratorError) {
152
+ throw _iteratorError;
153
+ }
154
+ }
155
+ }
156
+ // Create the signature-params line
157
+ var signatureParams = "(".concat(coveredComponents.join(' '), ");created=").concat(created, ';keyid="').concat(this.fingerprint, '"');
158
+ signatureBaseLines.push('"@signature-params": '.concat(signatureParams));
159
+ var signatureBase = signatureBaseLines.join('\n');
160
+ // Sign the base
161
+ var signatureBytes = signWithPrivateKey(this.privateKey, signatureBase);
162
+ var signatureValue = signatureBytes.toString('base64');
163
+ return {
164
+ Signature: "sig1=:".concat(signatureValue, ":"),
165
+ 'Signature-Input': "sig1=".concat(signatureParams)
166
+ };
167
+ }
168
+ }
169
+ ]);
170
+ return RequestSigner;
171
+ }();
172
+ // Singleton instance
173
+ var signerInstance = null;
174
+ /**
175
+ * Get the singleton request signer instance
176
+ */ export function getRequestSigner() {
177
+ if (!signerInstance) {
178
+ signerInstance = new RequestSigner();
179
+ }
180
+ return signerInstance;
181
+ }
182
+ /**
183
+ * Reset the request signer singleton
184
+ * Call this after auth config changes to force reinitialization with new key
185
+ */ export function resetRequestSigner() {
186
+ signerInstance = null;
187
+ }
188
+ /**
189
+ * Check if request signing is available
190
+ */ export function isSigningAvailable() {
191
+ return getRequestSigner().isReady();
192
+ }
193
+ /**
194
+ * Sign an HTTP request if signing is available
195
+ * Returns the additional headers to add, or empty object if signing is not available
196
+ */ export function maybeSignRequest(method, url) {
197
+ var headers = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {};
198
+ var signer = getRequestSigner();
199
+ if (!signer.isReady()) {
200
+ return {};
201
+ }
202
+ try {
203
+ return signer.signRequest(method, url, headers);
204
+ } catch (error) {
205
+ console.error('Warning: Failed to sign request:', error);
206
+ return {};
207
+ }
208
+ }
@@ -0,0 +1,212 @@
1
+ import sshpk from 'sshpk';
2
+ import { readFileSync, readdirSync, existsSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+ import { createPublicKey } from 'crypto';
6
+ /**
7
+ * Discover available SSH keys in the ~/.ssh directory
8
+ * Scans for common key files and returns metadata about each key
9
+ */ export function discoverSSHKeys() {
10
+ var sshDir = join(homedir(), '.ssh');
11
+ if (!existsSync(sshDir)) {
12
+ return [];
13
+ }
14
+ var discoveredKeys = [];
15
+ var processedPaths = new Set();
16
+ // Common private key filenames to look for
17
+ var commonKeyNames = [
18
+ 'id_rsa',
19
+ 'id_ed25519',
20
+ 'id_ecdsa',
21
+ 'id_dsa'
22
+ ];
23
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
24
+ try {
25
+ // First, check common key names
26
+ for(var _iterator = commonKeyNames[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
27
+ var keyName = _step.value;
28
+ var privateKeyPath = join(sshDir, keyName);
29
+ var publicKeyPath = join(sshDir, "".concat(keyName, ".pub"));
30
+ if (existsSync(privateKeyPath) && !processedPaths.has(privateKeyPath)) {
31
+ var keyInfo = tryLoadKeyInfo(privateKeyPath, publicKeyPath);
32
+ if (keyInfo) {
33
+ discoveredKeys.push(keyInfo);
34
+ processedPaths.add(privateKeyPath);
35
+ }
36
+ }
37
+ }
38
+ } catch (err) {
39
+ _didIteratorError = true;
40
+ _iteratorError = err;
41
+ } finally{
42
+ try {
43
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
44
+ _iterator.return();
45
+ }
46
+ } finally{
47
+ if (_didIteratorError) {
48
+ throw _iteratorError;
49
+ }
50
+ }
51
+ }
52
+ // Also scan for any .pub files and infer private key path
53
+ try {
54
+ var files = readdirSync(sshDir);
55
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
56
+ try {
57
+ for(var _iterator1 = files[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
58
+ var file = _step1.value;
59
+ if (file.endsWith('.pub')) {
60
+ var privateKeyName = file.slice(0, -4); // Remove .pub
61
+ var privateKeyPath1 = join(sshDir, privateKeyName);
62
+ var publicKeyPath1 = join(sshDir, file);
63
+ if (existsSync(privateKeyPath1) && !processedPaths.has(privateKeyPath1)) {
64
+ var keyInfo1 = tryLoadKeyInfo(privateKeyPath1, publicKeyPath1);
65
+ if (keyInfo1) {
66
+ discoveredKeys.push(keyInfo1);
67
+ processedPaths.add(privateKeyPath1);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ } catch (err) {
73
+ _didIteratorError1 = true;
74
+ _iteratorError1 = err;
75
+ } finally{
76
+ try {
77
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
78
+ _iterator1.return();
79
+ }
80
+ } finally{
81
+ if (_didIteratorError1) {
82
+ throw _iteratorError1;
83
+ }
84
+ }
85
+ }
86
+ } catch (e) {
87
+ // If we can't read the directory, just return what we have
88
+ }
89
+ return discoveredKeys;
90
+ }
91
+ /**
92
+ * Try to load key info from a private/public key pair
93
+ */ function tryLoadKeyInfo(privateKeyPath, publicKeyPath) {
94
+ try {
95
+ // Try to read the public key to get fingerprint and algorithm
96
+ if (existsSync(publicKeyPath)) {
97
+ var pubContent = readFileSync(publicKeyPath, 'utf-8').trim();
98
+ var sshKey = sshpk.parseKey(pubContent, 'auto');
99
+ var fingerprint = sshKey.fingerprint('sha256').toString();
100
+ // Extract comment from public key (usually the third part after algorithm and key data)
101
+ var parts = pubContent.split(' ');
102
+ var comment = parts.length > 2 ? parts.slice(2).join(' ') : undefined;
103
+ return {
104
+ path: privateKeyPath,
105
+ fingerprint: fingerprint,
106
+ algorithm: sshKey.type.toUpperCase(),
107
+ comment: comment
108
+ };
109
+ }
110
+ // If no public key, try to derive info from private key
111
+ var privateContent = readFileSync(privateKeyPath, 'utf-8');
112
+ var privateKey = sshpk.parsePrivateKey(privateContent, 'auto');
113
+ var publicKey = privateKey.toPublic();
114
+ var fingerprint1 = publicKey.fingerprint('sha256').toString();
115
+ return {
116
+ path: privateKeyPath,
117
+ fingerprint: fingerprint1,
118
+ algorithm: privateKey.type.toUpperCase()
119
+ };
120
+ } catch (e) {
121
+ // Key couldn't be parsed, skip it
122
+ return null;
123
+ }
124
+ }
125
+ /**
126
+ * Convert an SSH public key file to JWK format
127
+ * Uses sshpk for SSH parsing and Node.js crypto for PEM → JWK conversion
128
+ */ export function convertSSHPubKeyToJWK(pubKeyPath) {
129
+ var content = readFileSync(pubKeyPath, 'utf-8').trim();
130
+ var sshKey = sshpk.parseKey(content, 'auto');
131
+ // Get the fingerprint using SHA256 (sshpk's job)
132
+ var fingerprint = sshKey.fingerprint('sha256').toString();
133
+ // Convert SSH → PEM → JWK using Node.js crypto (battle-tested)
134
+ var pem = sshKey.toString('pem');
135
+ var keyObject = createPublicKey(pem);
136
+ var jwk = keyObject.export({
137
+ format: 'jwk'
138
+ });
139
+ return {
140
+ jwk: jwk,
141
+ fingerprint: fingerprint,
142
+ algorithm: sshKey.type
143
+ };
144
+ }
145
+ /**
146
+ * Load an SSH private key from a file path or environment variable
147
+ */ export function loadPrivateKey(pathOrEnv) {
148
+ var keyPath;
149
+ if (pathOrEnv) {
150
+ // If it starts with ~ or /, treat as a path
151
+ if (pathOrEnv.startsWith('~') || pathOrEnv.startsWith('/')) {
152
+ keyPath = pathOrEnv.startsWith('~') ? join(homedir(), pathOrEnv.slice(1)) : pathOrEnv;
153
+ } else {
154
+ // Otherwise, check if it's an env var or direct path
155
+ keyPath = pathOrEnv;
156
+ }
157
+ } else {
158
+ // Default to ~/.ssh/id_rsa
159
+ keyPath = join(homedir(), '.ssh', 'id_rsa');
160
+ }
161
+ // Expand ~ if present
162
+ if (keyPath.startsWith('~')) {
163
+ keyPath = join(homedir(), keyPath.slice(1));
164
+ }
165
+ var content = readFileSync(keyPath, 'utf-8');
166
+ return sshpk.parsePrivateKey(content, 'auto');
167
+ }
168
+ /**
169
+ * Get the fingerprint of a private key (from its public component)
170
+ */ export function getPrivateKeyFingerprint(privateKey) {
171
+ var publicKey = privateKey.toPublic();
172
+ return publicKey.fingerprint('sha256').toString();
173
+ }
174
+ /**
175
+ * Sign data with an SSH private key
176
+ */ export function signWithPrivateKey(privateKey, data) {
177
+ var dataBuffer = typeof data === 'string' ? Buffer.from(data) : data;
178
+ var signer = privateKey.createSign('sha256');
179
+ signer.update(dataBuffer);
180
+ var signature = signer.sign();
181
+ return signature.toBuffer('raw');
182
+ }
183
+ /**
184
+ * Resolve the private key path from environment, config, or default
185
+ * @param configuredPath - Optional configured path from ProjectConfigManager
186
+ */ export function resolvePrivateKeyPath(configuredPath) {
187
+ // Priority 1: Environment variable (highest)
188
+ var envPath = process.env.POSITRONIC_PRIVATE_KEY;
189
+ if (envPath) {
190
+ if (envPath.startsWith('~')) {
191
+ return join(homedir(), envPath.slice(1));
192
+ }
193
+ return envPath;
194
+ }
195
+ // Priority 2: Configured path from config manager
196
+ if (configuredPath) {
197
+ if (configuredPath.startsWith('~')) {
198
+ return join(homedir(), configuredPath.slice(1));
199
+ }
200
+ return configuredPath;
201
+ }
202
+ // Priority 3: Default fallback
203
+ return join(homedir(), '.ssh', 'id_rsa');
204
+ }
205
+ /**
206
+ * Expand a path that may contain ~ to the full path
207
+ */ export function expandPath(keyPath) {
208
+ if (keyPath.startsWith('~')) {
209
+ return join(homedir(), keyPath.slice(1));
210
+ }
211
+ return keyPath;
212
+ }