@positronic/cli 0.0.58 → 0.0.60

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.
@@ -0,0 +1,579 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
2
+ try {
3
+ var info = gen[key](arg);
4
+ var value = info.value;
5
+ } catch (error) {
6
+ reject(error);
7
+ return;
8
+ }
9
+ if (info.done) {
10
+ resolve(value);
11
+ } else {
12
+ Promise.resolve(value).then(_next, _throw);
13
+ }
14
+ }
15
+ function _async_to_generator(fn) {
16
+ return function() {
17
+ var self = this, args = arguments;
18
+ return new Promise(function(resolve, reject) {
19
+ var gen = fn.apply(self, args);
20
+ function _next(value) {
21
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
22
+ }
23
+ function _throw(err) {
24
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
25
+ }
26
+ _next(undefined);
27
+ });
28
+ };
29
+ }
30
+ function _class_call_check(instance, Constructor) {
31
+ if (!(instance instanceof Constructor)) {
32
+ throw new TypeError("Cannot call a class as a function");
33
+ }
34
+ }
35
+ function _defineProperties(target, props) {
36
+ for(var i = 0; i < props.length; i++){
37
+ var descriptor = props[i];
38
+ descriptor.enumerable = descriptor.enumerable || false;
39
+ descriptor.configurable = true;
40
+ if ("value" in descriptor) descriptor.writable = true;
41
+ Object.defineProperty(target, descriptor.key, descriptor);
42
+ }
43
+ }
44
+ function _create_class(Constructor, protoProps, staticProps) {
45
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
46
+ if (staticProps) _defineProperties(Constructor, staticProps);
47
+ return Constructor;
48
+ }
49
+ function _define_property(obj, key, value) {
50
+ if (key in obj) {
51
+ Object.defineProperty(obj, key, {
52
+ value: value,
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true
56
+ });
57
+ } else {
58
+ obj[key] = value;
59
+ }
60
+ return obj;
61
+ }
62
+ function _instanceof(left, right) {
63
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
64
+ return !!right[Symbol.hasInstance](left);
65
+ } else {
66
+ return left instanceof right;
67
+ }
68
+ }
69
+ function _ts_generator(thisArg, body) {
70
+ var f, y, t, _ = {
71
+ label: 0,
72
+ sent: function() {
73
+ if (t[0] & 1) throw t[1];
74
+ return t[1];
75
+ },
76
+ trys: [],
77
+ ops: []
78
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
79
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
80
+ return this;
81
+ }), g;
82
+ function verb(n) {
83
+ return function(v) {
84
+ return step([
85
+ n,
86
+ v
87
+ ]);
88
+ };
89
+ }
90
+ function step(op) {
91
+ if (f) throw new TypeError("Generator is already executing.");
92
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
93
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
94
+ if (y = 0, t) op = [
95
+ op[0] & 2,
96
+ t.value
97
+ ];
98
+ switch(op[0]){
99
+ case 0:
100
+ case 1:
101
+ t = op;
102
+ break;
103
+ case 4:
104
+ _.label++;
105
+ return {
106
+ value: op[1],
107
+ done: false
108
+ };
109
+ case 5:
110
+ _.label++;
111
+ y = op[1];
112
+ op = [
113
+ 0
114
+ ];
115
+ continue;
116
+ case 7:
117
+ op = _.ops.pop();
118
+ _.trys.pop();
119
+ continue;
120
+ default:
121
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
122
+ _ = 0;
123
+ continue;
124
+ }
125
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
126
+ _.label = op[1];
127
+ break;
128
+ }
129
+ if (op[0] === 6 && _.label < t[1]) {
130
+ _.label = t[1];
131
+ t = op;
132
+ break;
133
+ }
134
+ if (t && _.label < t[2]) {
135
+ _.label = t[2];
136
+ _.ops.push(op);
137
+ break;
138
+ }
139
+ if (t[2]) _.ops.pop();
140
+ _.trys.pop();
141
+ continue;
142
+ }
143
+ op = body.call(thisArg, _);
144
+ } catch (e) {
145
+ op = [
146
+ 6,
147
+ e
148
+ ];
149
+ y = 0;
150
+ } finally{
151
+ f = t = 0;
152
+ }
153
+ if (op[0] & 5) throw op[1];
154
+ return {
155
+ value: op[0] ? op[1] : void 0,
156
+ done: true
157
+ };
158
+ }
159
+ }
160
+ import { SignJWT, importPKCS8, base64url } from 'jose';
161
+ import { existsSync } from 'fs';
162
+ import { createPrivateKey } from 'crypto';
163
+ import { loadPrivateKey, getPrivateKeyFingerprint, getPublicKeyFingerprint, resolvePrivateKeyPath } from './ssh-key-utils.js';
164
+ import { ProjectConfigManager } from '../commands/project-config-manager.js';
165
+ import { AgentSigner } from './ssh-agent-signer.js';
166
+ /**
167
+ * Check if an error indicates an encrypted key
168
+ */ function isEncryptedKeyError(error) {
169
+ if (_instanceof(error, Error) && error.name === 'KeyEncryptedError') {
170
+ return true;
171
+ }
172
+ return false;
173
+ }
174
+ /**
175
+ * JWT Auth Provider for authenticating API requests
176
+ * Uses SSH private keys to sign short-lived JWTs
177
+ * Falls back to ssh-agent for encrypted keys
178
+ */ export var JwtAuthProvider = /*#__PURE__*/ function() {
179
+ "use strict";
180
+ function JwtAuthProvider() {
181
+ _class_call_check(this, JwtAuthProvider);
182
+ _define_property(this, "privateKey", null);
183
+ _define_property(this, "fingerprint", null);
184
+ _define_property(this, "initialized", false);
185
+ _define_property(this, "initError", null);
186
+ // Agent fallback support
187
+ _define_property(this, "encryptedKeyPath", null);
188
+ _define_property(this, "agentSigner", null);
189
+ _define_property(this, "agentKey", null);
190
+ _define_property(this, "useAgent", false);
191
+ this.initialize();
192
+ }
193
+ _create_class(JwtAuthProvider, [
194
+ {
195
+ key: "initialize",
196
+ value: function initialize() {
197
+ try {
198
+ // Get configured path from project config manager
199
+ var configManager = new ProjectConfigManager();
200
+ var configuredPath = configManager.getPrivateKeyPath();
201
+ var keyPath = resolvePrivateKeyPath(configuredPath);
202
+ if (!existsSync(keyPath)) {
203
+ 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."));
204
+ return;
205
+ }
206
+ this.privateKey = loadPrivateKey(keyPath);
207
+ this.fingerprint = getPrivateKeyFingerprint(this.privateKey);
208
+ this.initialized = true;
209
+ } catch (error) {
210
+ if (isEncryptedKeyError(error)) {
211
+ // Store the path for agent fallback - we'll try the agent in createToken()
212
+ var configManager1 = new ProjectConfigManager();
213
+ var configuredPath1 = configManager1.getPrivateKeyPath();
214
+ this.encryptedKeyPath = resolvePrivateKeyPath(configuredPath1);
215
+ this.initError = _instanceof(error, Error) ? error : new Error('Key is encrypted');
216
+ } else {
217
+ this.initError = _instanceof(error, Error) ? error : new Error('Failed to initialize JWT auth provider');
218
+ }
219
+ }
220
+ }
221
+ },
222
+ {
223
+ /**
224
+ * Check if the provider is ready to create JWTs
225
+ * Returns true if we have a direct key OR if we have an encrypted key
226
+ * that might work with agent fallback
227
+ */ key: "isReady",
228
+ value: function isReady() {
229
+ // Direct key is loaded and ready
230
+ if (this.initialized && this.privateKey !== null) {
231
+ return true;
232
+ }
233
+ // Encrypted key might work with agent fallback
234
+ if (this.encryptedKeyPath !== null) {
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+ },
240
+ {
241
+ /**
242
+ * Check if we have an encrypted key that requires agent fallback
243
+ */ key: "hasEncryptedKey",
244
+ value: function hasEncryptedKey() {
245
+ return this.encryptedKeyPath !== null;
246
+ }
247
+ },
248
+ {
249
+ /**
250
+ * Get the error that occurred during initialization, if any
251
+ */ key: "getError",
252
+ value: function getError() {
253
+ return this.initError;
254
+ }
255
+ },
256
+ {
257
+ /**
258
+ * Get the fingerprint of the loaded private key
259
+ */ key: "getFingerprint",
260
+ value: function getFingerprint() {
261
+ return this.fingerprint;
262
+ }
263
+ },
264
+ {
265
+ key: "getAlgorithm",
266
+ value: /**
267
+ * Map SSH key type to JWT algorithm
268
+ */ function getAlgorithm() {
269
+ if (!this.privateKey) {
270
+ throw new Error('Private key not loaded');
271
+ }
272
+ return this.getAlgorithmForKeyType(this.privateKey.type, this.privateKey.curve);
273
+ }
274
+ },
275
+ {
276
+ key: "getAlgorithmForKeyType",
277
+ value: /**
278
+ * Map SSH key type string to JWT algorithm
279
+ */ function getAlgorithmForKeyType(keyType, curve) {
280
+ if (keyType === 'rsa') {
281
+ return 'RS256';
282
+ } else if (keyType === 'ecdsa') {
283
+ // ECDSA curve determines algorithm
284
+ if (curve === 'nistp256') {
285
+ return 'ES256';
286
+ } else if (curve === 'nistp384') {
287
+ return 'ES384';
288
+ } else if (curve === 'nistp521') {
289
+ return 'ES512';
290
+ }
291
+ // Default to ES256 for unknown curves
292
+ return 'ES256';
293
+ } else if (keyType === 'ed25519') {
294
+ return 'EdDSA';
295
+ }
296
+ throw new Error("Unsupported key type: ".concat(keyType));
297
+ }
298
+ },
299
+ {
300
+ key: "getPkcs8Pem",
301
+ value: /**
302
+ * Convert the SSH private key to PKCS8 PEM format
303
+ * Ed25519 keys need special handling because sshpk's PKCS8 output
304
+ * is not compatible with Node.js/OpenSSL
305
+ */ function getPkcs8Pem() {
306
+ if (!this.privateKey) {
307
+ throw new Error('Private key not loaded');
308
+ }
309
+ if (this.privateKey.type === 'ed25519') {
310
+ // For Ed25519, sshpk's PKCS8 output includes the public key in a format
311
+ // that Node.js/OpenSSL doesn't understand. Instead, we construct a JWK
312
+ // from the raw key parts and let Node's crypto handle the conversion.
313
+ // sshpk stores Ed25519 key data in 'k' (seed) and 'A' (public) parts
314
+ var parts = this.privateKey.part;
315
+ var seed = parts.k.data;
316
+ var publicKey = parts.A.data;
317
+ // Construct JWK and let Node's crypto convert to PKCS8
318
+ var jwk = {
319
+ kty: 'OKP',
320
+ crv: 'Ed25519',
321
+ d: seed.toString('base64url'),
322
+ x: publicKey.toString('base64url')
323
+ };
324
+ var keyObj = createPrivateKey({
325
+ key: jwk,
326
+ format: 'jwk'
327
+ });
328
+ return keyObj.export({
329
+ type: 'pkcs8',
330
+ format: 'pem'
331
+ });
332
+ }
333
+ // For RSA and ECDSA, sshpk's PKCS8 output works fine
334
+ return this.privateKey.toString('pkcs8');
335
+ }
336
+ },
337
+ {
338
+ key: "createToken",
339
+ value: /**
340
+ * Create a short-lived JWT for authentication
341
+ */ function createToken() {
342
+ return _async_to_generator(function() {
343
+ return _ts_generator(this, function(_state) {
344
+ switch(_state.label){
345
+ case 0:
346
+ // If we have a direct private key, use the standard jose signing path
347
+ if (this.privateKey && this.fingerprint) {
348
+ return [
349
+ 2,
350
+ this.createTokenDirect()
351
+ ];
352
+ }
353
+ if (!this.encryptedKeyPath) return [
354
+ 3,
355
+ 2
356
+ ];
357
+ return [
358
+ 4,
359
+ this.tryAgentFallback()
360
+ ];
361
+ case 1:
362
+ _state.sent();
363
+ _state.label = 2;
364
+ case 2:
365
+ // If agent fallback succeeded, use agent signing
366
+ if (this.useAgent && this.agentSigner && this.agentKey && this.fingerprint) {
367
+ return [
368
+ 2,
369
+ this.createTokenWithAgent()
370
+ ];
371
+ }
372
+ // No authentication method available
373
+ throw this.initError || new Error('JWT auth provider not initialized');
374
+ }
375
+ });
376
+ }).call(this);
377
+ }
378
+ },
379
+ {
380
+ key: "createTokenDirect",
381
+ value: /**
382
+ * Create JWT using direct private key (jose library)
383
+ */ function createTokenDirect() {
384
+ return _async_to_generator(function() {
385
+ var algorithm, pkcs8Pem, joseKey, jwt;
386
+ return _ts_generator(this, function(_state) {
387
+ switch(_state.label){
388
+ case 0:
389
+ if (!this.privateKey || !this.fingerprint) {
390
+ throw new Error('Private key not loaded');
391
+ }
392
+ algorithm = this.getAlgorithm();
393
+ // Convert SSH private key to PKCS8 PEM format
394
+ pkcs8Pem = this.getPkcs8Pem();
395
+ return [
396
+ 4,
397
+ importPKCS8(pkcs8Pem, algorithm)
398
+ ];
399
+ case 1:
400
+ joseKey = _state.sent();
401
+ return [
402
+ 4,
403
+ new SignJWT({}).setProtectedHeader({
404
+ alg: algorithm
405
+ }).setSubject(this.fingerprint).setIssuedAt().setExpirationTime('30s').sign(joseKey)
406
+ ];
407
+ case 2:
408
+ jwt = _state.sent();
409
+ return [
410
+ 2,
411
+ jwt
412
+ ];
413
+ }
414
+ });
415
+ }).call(this);
416
+ }
417
+ },
418
+ {
419
+ key: "tryAgentFallback",
420
+ value: /**
421
+ * Try to use ssh-agent for signing when private key is encrypted
422
+ */ function tryAgentFallback() {
423
+ return _async_to_generator(function() {
424
+ var pubKeyPath, fingerprint, agent, agentKey;
425
+ return _ts_generator(this, function(_state) {
426
+ switch(_state.label){
427
+ case 0:
428
+ if (!this.encryptedKeyPath) {
429
+ return [
430
+ 2
431
+ ];
432
+ }
433
+ // Get fingerprint from public key file
434
+ pubKeyPath = this.encryptedKeyPath + '.pub';
435
+ if (!existsSync(pubKeyPath)) {
436
+ throw new Error("Key is encrypted and public key file not found at ".concat(pubKeyPath, ".\n") + "Cannot determine key fingerprint for ssh-agent lookup.");
437
+ }
438
+ fingerprint = getPublicKeyFingerprint(pubKeyPath);
439
+ agent = new AgentSigner();
440
+ if (!agent.isAvailable()) {
441
+ throw new Error("Key is encrypted and ssh-agent is not running.\n" + "Start ssh-agent or use an unencrypted key.");
442
+ }
443
+ return [
444
+ 4,
445
+ agent.hasKey(fingerprint)
446
+ ];
447
+ case 1:
448
+ agentKey = _state.sent();
449
+ if (!agentKey) {
450
+ throw new Error("Key is encrypted and not loaded in ssh-agent.\n" + "Run: ssh-add ".concat(this.encryptedKeyPath));
451
+ }
452
+ this.agentSigner = agent;
453
+ this.agentKey = agentKey;
454
+ this.fingerprint = fingerprint;
455
+ this.useAgent = true;
456
+ this.initError = null;
457
+ this.initialized = true;
458
+ return [
459
+ 2
460
+ ];
461
+ }
462
+ });
463
+ }).call(this);
464
+ }
465
+ },
466
+ {
467
+ key: "createTokenWithAgent",
468
+ value: /**
469
+ * Create JWT using ssh-agent for signing
470
+ * Manually constructs the JWT since jose expects to do signing itself
471
+ */ function createTokenWithAgent() {
472
+ return _async_to_generator(function() {
473
+ var keyType, curve, algorithm, header, encodedHeader, now, payload, encodedPayload, signingInput, signature, signatureBytes, encodedSignature;
474
+ return _ts_generator(this, function(_state) {
475
+ switch(_state.label){
476
+ case 0:
477
+ if (!this.agentSigner || !this.agentKey || !this.fingerprint) {
478
+ throw new Error('Agent signing not configured');
479
+ }
480
+ // Get algorithm from agent key type
481
+ keyType = this.agentKey.type;
482
+ curve = this.agentKey.curve;
483
+ algorithm = this.getAlgorithmForKeyType(keyType, curve);
484
+ // Build JWT header
485
+ header = {
486
+ alg: algorithm
487
+ };
488
+ encodedHeader = base64url.encode(JSON.stringify(header));
489
+ // Build JWT payload
490
+ now = Math.floor(Date.now() / 1000);
491
+ payload = {
492
+ sub: this.fingerprint,
493
+ iat: now,
494
+ exp: now + 30
495
+ };
496
+ encodedPayload = base64url.encode(JSON.stringify(payload));
497
+ // Create signing input
498
+ signingInput = "".concat(encodedHeader, ".").concat(encodedPayload);
499
+ return [
500
+ 4,
501
+ this.agentSigner.sign(this.agentKey, Buffer.from(signingInput))
502
+ ];
503
+ case 1:
504
+ signature = _state.sent();
505
+ // Convert sshpk.Signature to raw bytes for JWT
506
+ // sshpk's toBuffer() gives us the raw signature bytes
507
+ signatureBytes = signature.toBuffer('raw');
508
+ encodedSignature = base64url.encode(signatureBytes);
509
+ return [
510
+ 2,
511
+ "".concat(signingInput, ".").concat(encodedSignature)
512
+ ];
513
+ }
514
+ });
515
+ }).call(this);
516
+ }
517
+ }
518
+ ]);
519
+ return JwtAuthProvider;
520
+ }();
521
+ // Singleton instance
522
+ var providerInstance = null;
523
+ /**
524
+ * Get the singleton JWT auth provider instance
525
+ */ export function getJwtAuthProvider() {
526
+ if (!providerInstance) {
527
+ providerInstance = new JwtAuthProvider();
528
+ }
529
+ return providerInstance;
530
+ }
531
+ /**
532
+ * Reset the JWT auth provider singleton
533
+ * Call this after auth config changes to force reinitialization with new key
534
+ */ export function resetJwtAuthProvider() {
535
+ providerInstance = null;
536
+ }
537
+ /**
538
+ * Check if JWT auth is available
539
+ */ export function isAuthAvailable() {
540
+ return getJwtAuthProvider().isReady();
541
+ }
542
+ /**
543
+ * Get the Authorization header if auth is available
544
+ * Throws if there's an auth configuration error
545
+ * Returns empty object with warning if no key is configured
546
+ */ export function getAuthHeader() {
547
+ return _async_to_generator(function() {
548
+ var provider, error, token;
549
+ return _ts_generator(this, function(_state) {
550
+ switch(_state.label){
551
+ case 0:
552
+ provider = getJwtAuthProvider();
553
+ if (!provider.isReady()) {
554
+ error = provider.getError();
555
+ if (error) {
556
+ throw error;
557
+ }
558
+ console.warn('Warning: No SSH key configured for authentication. Run "px auth login" to configure.');
559
+ return [
560
+ 2,
561
+ {}
562
+ ];
563
+ }
564
+ return [
565
+ 4,
566
+ provider.createToken()
567
+ ];
568
+ case 1:
569
+ token = _state.sent();
570
+ return [
571
+ 2,
572
+ {
573
+ Authorization: "Bearer ".concat(token)
574
+ }
575
+ ];
576
+ }
577
+ });
578
+ })();
579
+ }