@lamalibre/portlama-agent 1.0.8 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamalibre/portlama-agent",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Tunnel agent for Portlama — manages Chisel tunnel client as a system service",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -451,6 +451,179 @@ export async function deleteSiteFile(panelUrlOrConfig, p12Path, p12Password, sit
451
451
  }
452
452
  }
453
453
 
454
+ // ---------------------------------------------------------------------------
455
+ // Ticket system API functions
456
+ // ---------------------------------------------------------------------------
457
+
458
+ // --- Ticket API functions ---
459
+ // These use config-object only (no dual positional args). The ticket system is new
460
+ // and only called from code that already uses config objects, so the legacy positional
461
+ // calling convention used by older functions above is not needed here.
462
+
463
+ /**
464
+ * Fetch pending tickets from the agent's inbox.
465
+ * @param {object} config - Agent config object
466
+ * @returns {Promise<{ tickets: Array }>}
467
+ */
468
+ export async function fetchTicketInbox(config) {
469
+ const url = `${config.panelUrl}/api/tickets/inbox`;
470
+ try {
471
+ const { stdout } = await curlAuthenticated(config, [url]);
472
+ return JSON.parse(stdout);
473
+ } catch (err) {
474
+ throw new Error(`Failed to fetch ticket inbox. Details: ${err.stderr || err.message}`);
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Validate a ticket from the inbox.
480
+ * @param {object} config - Agent config object
481
+ * @param {string} ticketId - The ticket ID to validate
482
+ * @returns {Promise<object>}
483
+ */
484
+ export async function validateTicket(config, ticketId) {
485
+ const url = `${config.panelUrl}/api/tickets/validate`;
486
+ const curlArgs = [
487
+ '-X', 'POST',
488
+ '-H', 'Content-Type: application/json',
489
+ '-d', JSON.stringify({ ticketId }),
490
+ url,
491
+ ];
492
+ try {
493
+ const { stdout } = await curlAuthenticated(config, curlArgs);
494
+ return JSON.parse(stdout);
495
+ } catch (err) {
496
+ throw new Error(`Failed to validate ticket. Details: ${err.stderr || err.message}`);
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Register an instance for a ticket scope.
502
+ * @param {object} config - Agent config object
503
+ * @param {string} scope - The scope capability (e.g. "shell:connect")
504
+ * @param {object} transport - Transport configuration
505
+ * @returns {Promise<object>}
506
+ */
507
+ export async function registerTicketInstance(config, scope, transport) {
508
+ const url = `${config.panelUrl}/api/tickets/instances`;
509
+ const curlArgs = [
510
+ '-X', 'POST',
511
+ '-H', 'Content-Type: application/json',
512
+ '-d', JSON.stringify({ scope, transport }),
513
+ url,
514
+ ];
515
+ try {
516
+ const { stdout } = await curlAuthenticated(config, curlArgs);
517
+ return JSON.parse(stdout);
518
+ } catch (err) {
519
+ throw new Error(`Failed to register ticket instance. Details: ${err.stderr || err.message}`);
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Send an instance heartbeat.
525
+ * @param {object} config - Agent config object
526
+ * @param {string} instanceId - The instance ID
527
+ * @returns {Promise<object>}
528
+ */
529
+ export async function sendInstanceHeartbeat(config, instanceId) {
530
+ const url = `${config.panelUrl}/api/tickets/instances/${encodeURIComponent(instanceId)}/heartbeat`;
531
+ const curlArgs = ['-X', 'POST', url];
532
+ try {
533
+ const { stdout } = await curlAuthenticated(config, curlArgs);
534
+ return JSON.parse(stdout);
535
+ } catch (err) {
536
+ throw new Error(`Failed to send instance heartbeat. Details: ${err.stderr || err.message}`);
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Request a ticket for a target agent.
542
+ * @param {object} config - Agent config object
543
+ * @param {string} scope - Capability scope
544
+ * @param {string} instanceId - The instance ID
545
+ * @param {string} target - Target agent label
546
+ * @returns {Promise<object>}
547
+ */
548
+ export async function requestTicket(config, scope, instanceId, target) {
549
+ const url = `${config.panelUrl}/api/tickets`;
550
+ const curlArgs = [
551
+ '-X', 'POST',
552
+ '-H', 'Content-Type: application/json',
553
+ '-d', JSON.stringify({ scope, instanceId, target }),
554
+ url,
555
+ ];
556
+ try {
557
+ const { stdout } = await curlAuthenticated(config, curlArgs);
558
+ return JSON.parse(stdout);
559
+ } catch (err) {
560
+ throw new Error(`Failed to request ticket. Details: ${err.stderr || err.message}`);
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Report a session creation to the panel.
566
+ * @param {object} config - Agent config object
567
+ * @param {string} ticketId - The ticket ID
568
+ * @param {string} sessionId - The session ID
569
+ * @returns {Promise<object>}
570
+ */
571
+ export async function reportSessionCreation(config, ticketId, sessionId) {
572
+ const url = `${config.panelUrl}/api/tickets/sessions`;
573
+ const curlArgs = [
574
+ '-X', 'POST',
575
+ '-H', 'Content-Type: application/json',
576
+ '-d', JSON.stringify({ ticketId, sessionId }),
577
+ url,
578
+ ];
579
+ try {
580
+ const { stdout } = await curlAuthenticated(config, curlArgs);
581
+ return JSON.parse(stdout);
582
+ } catch (err) {
583
+ throw new Error(`Failed to report session creation. Details: ${err.stderr || err.message}`);
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Send a session heartbeat to the panel for re-validation.
589
+ * @param {object} config - Agent config object
590
+ * @param {string} sessionId - The session ID
591
+ * @returns {Promise<{ authorized: boolean, reason?: string }>}
592
+ */
593
+ export async function sendSessionHeartbeat(config, sessionId) {
594
+ const url = `${config.panelUrl}/api/tickets/sessions/${encodeURIComponent(sessionId)}/heartbeat`;
595
+ const curlArgs = ['-X', 'POST', url];
596
+ try {
597
+ const { stdout } = await curlAuthenticated(config, curlArgs);
598
+ return JSON.parse(stdout);
599
+ } catch (err) {
600
+ throw new Error(`Failed to send session heartbeat. Details: ${err.stderr || err.message}`);
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Update session status on the panel.
606
+ * @param {object} config - Agent config object
607
+ * @param {string} sessionId - The session ID
608
+ * @param {string} status - New status: 'active' or 'grace'
609
+ * @returns {Promise<object>}
610
+ */
611
+ export async function updateSessionStatus(config, sessionId, status) {
612
+ const url = `${config.panelUrl}/api/tickets/sessions/${encodeURIComponent(sessionId)}`;
613
+ const curlArgs = [
614
+ '-X', 'PATCH',
615
+ '-H', 'Content-Type: application/json',
616
+ '-d', JSON.stringify({ status, lastActivityAt: new Date().toISOString() }),
617
+ url,
618
+ ];
619
+ try {
620
+ const { stdout } = await curlAuthenticated(config, curlArgs);
621
+ return JSON.parse(stdout);
622
+ } catch (err) {
623
+ throw new Error(`Failed to update session status. Details: ${err.stderr || err.message}`);
624
+ }
625
+ }
626
+
454
627
  /**
455
628
  * Execute an unauthenticated curl POST to a panel URL.
456
629
  * Used for the enrollment endpoint which doesn't require mTLS.