@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.
- package/dist/src/cli.js +142 -2
- package/dist/src/commands/auth.js +98 -0
- package/dist/src/commands/brain.js +3 -2
- package/dist/src/commands/helpers.js +48 -10
- package/dist/src/commands/project-config-manager.js +119 -0
- package/dist/src/commands/users.js +91 -0
- package/dist/src/components/agent-chat-view.js +125 -0
- package/dist/src/components/auth-list.js +56 -0
- package/dist/src/components/auth-login.js +209 -0
- package/dist/src/components/auth-logout.js +75 -0
- package/dist/src/components/auth-status.js +88 -0
- package/dist/src/components/brain-run.js +287 -254
- package/dist/src/components/brain-top-table.js +4 -0
- package/dist/src/components/event-detail.js +364 -0
- package/dist/src/components/events-view.js +379 -0
- package/dist/src/components/state-view.js +52 -0
- package/dist/src/components/top-navigator.js +80 -6
- package/dist/src/components/types.js +1 -0
- package/dist/src/components/users-create.js +293 -0
- package/dist/src/components/users-delete.js +294 -0
- package/dist/src/components/users-keys-add.js +156 -0
- package/dist/src/components/users-keys-list.js +119 -0
- package/dist/src/components/users-keys-remove.js +299 -0
- package/dist/src/components/users-list.js +109 -0
- package/dist/src/components/watch-keyboard.js +136 -0
- package/dist/src/components/watch-machine.js +573 -0
- package/dist/src/components/watch-resolver.js +3 -2
- package/dist/src/components/watch.js +390 -36
- package/dist/src/hooks/useApi.js +80 -42
- package/dist/src/lib/request-signer.js +208 -0
- package/dist/src/lib/ssh-key-utils.js +212 -0
- package/dist/src/utils/agent-utils.js +107 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/commands/auth.d.ts +36 -0
- package/dist/types/commands/auth.d.ts.map +1 -0
- package/dist/types/commands/brain.d.ts +2 -1
- package/dist/types/commands/brain.d.ts.map +1 -1
- package/dist/types/commands/helpers.d.ts.map +1 -1
- package/dist/types/commands/project-config-manager.d.ts +43 -0
- package/dist/types/commands/project-config-manager.d.ts.map +1 -1
- package/dist/types/commands/users.d.ts +33 -0
- package/dist/types/commands/users.d.ts.map +1 -0
- package/dist/types/components/agent-chat-view.d.ts +12 -0
- package/dist/types/components/agent-chat-view.d.ts.map +1 -0
- package/dist/types/components/auth-list.d.ts +7 -0
- package/dist/types/components/auth-list.d.ts.map +1 -0
- package/dist/types/components/auth-login.d.ts +9 -0
- package/dist/types/components/auth-login.d.ts.map +1 -0
- package/dist/types/components/auth-logout.d.ts +8 -0
- package/dist/types/components/auth-logout.d.ts.map +1 -0
- package/dist/types/components/auth-status.d.ts +7 -0
- package/dist/types/components/auth-status.d.ts.map +1 -0
- package/dist/types/components/brain-run.d.ts +11 -1
- package/dist/types/components/brain-run.d.ts.map +1 -1
- package/dist/types/components/brain-top-table.d.ts.map +1 -1
- package/dist/types/components/event-detail.d.ts +10 -0
- package/dist/types/components/event-detail.d.ts.map +1 -0
- package/dist/types/components/events-view.d.ts +13 -0
- package/dist/types/components/events-view.d.ts.map +1 -0
- package/dist/types/components/state-view.d.ts +13 -0
- package/dist/types/components/state-view.d.ts.map +1 -0
- package/dist/types/components/top-navigator.d.ts.map +1 -1
- package/dist/types/components/types.d.ts +11 -0
- package/dist/types/components/types.d.ts.map +1 -0
- package/dist/types/components/users-create.d.ts +6 -0
- package/dist/types/components/users-create.d.ts.map +1 -0
- package/dist/types/components/users-delete.d.ts +7 -0
- package/dist/types/components/users-delete.d.ts.map +1 -0
- package/dist/types/components/users-keys-add.d.ts +8 -0
- package/dist/types/components/users-keys-add.d.ts.map +1 -0
- package/dist/types/components/users-keys-list.d.ts +6 -0
- package/dist/types/components/users-keys-list.d.ts.map +1 -0
- package/dist/types/components/users-keys-remove.d.ts +8 -0
- package/dist/types/components/users-keys-remove.d.ts.map +1 -0
- package/dist/types/components/users-list.d.ts +2 -0
- package/dist/types/components/users-list.d.ts.map +1 -0
- package/dist/types/components/watch-keyboard.d.ts +56 -0
- package/dist/types/components/watch-keyboard.d.ts.map +1 -0
- package/dist/types/components/watch-machine.d.ts +171 -0
- package/dist/types/components/watch-machine.d.ts.map +1 -0
- package/dist/types/components/watch-resolver.d.ts +2 -1
- package/dist/types/components/watch-resolver.d.ts.map +1 -1
- package/dist/types/components/watch.d.ts +2 -1
- package/dist/types/components/watch.d.ts.map +1 -1
- package/dist/types/hooks/useApi.d.ts.map +1 -1
- package/dist/types/hooks/useBrainMachine.d.ts +9 -3
- package/dist/types/hooks/useBrainMachine.d.ts.map +1 -1
- package/dist/types/lib/request-signer.d.ts +51 -0
- package/dist/types/lib/request-signer.d.ts.map +1 -0
- package/dist/types/lib/ssh-key-utils.d.ts +45 -0
- package/dist/types/lib/ssh-key-utils.d.ts.map +1 -0
- package/dist/types/utils/agent-utils.d.ts +20 -0
- package/dist/types/utils/agent-utils.d.ts.map +1 -0
- package/package.json +7 -4
package/dist/src/hooks/useApi.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
291
|
-
case
|
|
304
|
+
_state.label = 6;
|
|
305
|
+
case 6:
|
|
292
306
|
return [
|
|
293
307
|
3,
|
|
294
|
-
|
|
308
|
+
9
|
|
295
309
|
];
|
|
296
|
-
case
|
|
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
|
-
|
|
322
|
+
9
|
|
309
323
|
];
|
|
310
|
-
case
|
|
324
|
+
case 8:
|
|
311
325
|
setLoading(false);
|
|
312
326
|
return [
|
|
313
327
|
7
|
|
314
328
|
];
|
|
315
|
-
case
|
|
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,
|
|
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
|
|
407
|
+
case 5:
|
|
382
408
|
errorText = _state.sent();
|
|
383
|
-
|
|
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(
|
|
389
|
-
throw
|
|
390
|
-
case
|
|
414
|
+
setError(errorObj1);
|
|
415
|
+
throw errorObj1;
|
|
416
|
+
case 6:
|
|
391
417
|
return [
|
|
392
418
|
3,
|
|
393
|
-
|
|
419
|
+
9
|
|
394
420
|
];
|
|
395
|
-
case
|
|
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
|
-
|
|
433
|
+
errorObj2 = _object_spread_props(_object_spread({}, baseError), {
|
|
408
434
|
details: "".concat(baseError.details, " ").concat(errorDetails)
|
|
409
435
|
});
|
|
410
|
-
setError(
|
|
411
|
-
throw
|
|
412
|
-
case
|
|
436
|
+
setError(errorObj2);
|
|
437
|
+
throw errorObj2;
|
|
438
|
+
case 8:
|
|
413
439
|
setLoading(false);
|
|
414
440
|
return [
|
|
415
441
|
7
|
|
416
442
|
];
|
|
417
|
-
case
|
|
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,
|
|
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
|
|
511
|
+
case 4:
|
|
474
512
|
errorText = _state.sent();
|
|
475
|
-
|
|
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(
|
|
481
|
-
throw
|
|
482
|
-
case
|
|
518
|
+
setError(errorObj1);
|
|
519
|
+
throw errorObj1;
|
|
520
|
+
case 5:
|
|
483
521
|
return [
|
|
484
522
|
3,
|
|
485
|
-
|
|
523
|
+
8
|
|
486
524
|
];
|
|
487
|
-
case
|
|
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
|
-
|
|
537
|
+
errorObj2 = _object_spread_props(_object_spread({}, baseError), {
|
|
500
538
|
details: "".concat(baseError.details, " ").concat(errorDetails)
|
|
501
539
|
});
|
|
502
|
-
setError(
|
|
503
|
-
throw
|
|
504
|
-
case
|
|
540
|
+
setError(errorObj2);
|
|
541
|
+
throw errorObj2;
|
|
542
|
+
case 7:
|
|
505
543
|
setLoading(false);
|
|
506
544
|
return [
|
|
507
545
|
7
|
|
508
546
|
];
|
|
509
|
-
case
|
|
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
|
+
}
|