@displaydev/cli 0.1.0 → 0.2.0

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/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @displaydev/cli
2
+
3
+ CLI for [display.dev](https://display.dev) — publish HTML and Markdown behind company auth.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @displaydev/cli
9
+ ```
10
+
11
+ ## Authenticate
12
+
13
+ ```bash
14
+ # Interactive login via email OTP
15
+ dsp login
16
+
17
+ # Or set an API key (from the dashboard)
18
+ export DISPLAYDEV_API_KEY=sk_live_...
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ # Publish a file
25
+ dsp publish ./report.html --name "Q1 Report"
26
+
27
+ # Update an existing artifact
28
+ dsp publish ./report.html --id abc123
29
+
30
+ # Search artifacts
31
+ dsp find
32
+ dsp find "quarterly" --by alice@acme.com
33
+
34
+ # Get artifact details
35
+ dsp get abc123
36
+
37
+ # Delete an artifact
38
+ dsp delete abc123 --confirm
39
+
40
+ # Start MCP server (for Claude Desktop / Claude Code)
41
+ dsp mcp
42
+ ```
43
+
44
+ ## MCP
45
+
46
+ The CLI doubles as an MCP server over stdio. Add to your MCP client config:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "display": {
52
+ "command": "dsp",
53
+ "args": ["mcp"],
54
+ "env": {
55
+ "DISPLAYDEV_API_KEY": "sk_live_..."
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
@@ -356,6 +356,113 @@ export var ApiClient = /*#__PURE__*/ function() {
356
356
  }).call(this);
357
357
  }
358
358
  },
359
+ {
360
+ key: "authCheck",
361
+ value: function authCheck(email) {
362
+ return _async_to_generator(function() {
363
+ return _ts_generator(this, function(_state) {
364
+ return [
365
+ 2,
366
+ this.requestNoAuth('POST', '/cli/auth-check', {
367
+ email: email
368
+ })
369
+ ];
370
+ });
371
+ }).call(this);
372
+ }
373
+ },
374
+ {
375
+ key: "requestDeviceCode",
376
+ value: function requestDeviceCode(clientId) {
377
+ return _async_to_generator(function() {
378
+ return _ts_generator(this, function(_state) {
379
+ return [
380
+ 2,
381
+ this.requestNoAuth('POST', '/api/auth/device/code', {
382
+ client_id: clientId
383
+ })
384
+ ];
385
+ });
386
+ }).call(this);
387
+ }
388
+ },
389
+ {
390
+ key: "pollDeviceToken",
391
+ value: function pollDeviceToken(deviceCode, clientId) {
392
+ return _async_to_generator(function() {
393
+ return _ts_generator(this, function(_state) {
394
+ return [
395
+ 2,
396
+ this.requestNoAuthRaw('POST', '/api/auth/device/token', {
397
+ device_code: deviceCode,
398
+ client_id: clientId,
399
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
400
+ })
401
+ ];
402
+ });
403
+ }).call(this);
404
+ }
405
+ },
406
+ {
407
+ key: "validateApiKey",
408
+ value: /**
409
+ * Validate an API key against the API.
410
+ * Returns 'valid', 'invalid' (401/403), or 'network_error'.
411
+ */ function validateApiKey(apiKey) {
412
+ return _async_to_generator(function() {
413
+ var res, unused;
414
+ return _ts_generator(this, function(_state) {
415
+ switch(_state.label){
416
+ case 0:
417
+ _state.trys.push([
418
+ 0,
419
+ 2,
420
+ ,
421
+ 3
422
+ ]);
423
+ return [
424
+ 4,
425
+ fetch("".concat(this.baseUrl, "/v1/artifacts?limit=1"), {
426
+ headers: {
427
+ 'Authorization': "Bearer ".concat(apiKey),
428
+ 'X-Client-Type': this.clientType
429
+ }
430
+ })
431
+ ];
432
+ case 1:
433
+ res = _state.sent();
434
+ return [
435
+ 3,
436
+ 3
437
+ ];
438
+ case 2:
439
+ unused = _state.sent();
440
+ return [
441
+ 2,
442
+ 'network_error'
443
+ ];
444
+ case 3:
445
+ if (res.ok) {
446
+ return [
447
+ 2,
448
+ 'valid'
449
+ ];
450
+ }
451
+ if (res.status === 401 || res.status === 403) {
452
+ return [
453
+ 2,
454
+ 'invalid'
455
+ ];
456
+ }
457
+ return [
458
+ 2,
459
+ 'network_error'
460
+ ];
461
+ }
462
+ });
463
+ }).call(this);
464
+ }
465
+ },
359
466
  {
360
467
  key: "request",
361
468
  value: function request(method, path, body) {
@@ -387,6 +494,37 @@ export var ApiClient = /*#__PURE__*/ function() {
387
494
  }).call(this);
388
495
  }
389
496
  },
497
+ {
498
+ key: "requestNoAuthRaw",
499
+ value: /** Like requestNoAuth but returns parsed JSON without throwing on non-2xx (for polling endpoints). */ function requestNoAuthRaw(method, path, body) {
500
+ return _async_to_generator(function() {
501
+ var url, res;
502
+ return _ts_generator(this, function(_state) {
503
+ switch(_state.label){
504
+ case 0:
505
+ url = "".concat(this.baseUrl).concat(path);
506
+ return [
507
+ 4,
508
+ fetch(url, {
509
+ method: method,
510
+ headers: {
511
+ 'Content-Type': 'application/json',
512
+ 'X-Client-Type': this.clientType
513
+ },
514
+ body: body ? JSON.stringify(body) : undefined
515
+ })
516
+ ];
517
+ case 1:
518
+ res = _state.sent();
519
+ return [
520
+ 2,
521
+ res.json()
522
+ ];
523
+ }
524
+ });
525
+ }).call(this);
526
+ }
527
+ },
390
528
  {
391
529
  key: "doFetch",
392
530
  value: function doFetch(method, path, body, headers) {
package/dist/main.js CHANGED
@@ -28,6 +28,14 @@ function _async_to_generator(fn) {
28
28
  });
29
29
  };
30
30
  }
31
+ function _instanceof(left, right) {
32
+ "@swc/helpers - instanceof";
33
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
34
+ return !!right[Symbol.hasInstance](left);
35
+ } else {
36
+ return left instanceof right;
37
+ }
38
+ }
31
39
  function _ts_generator(thisArg, body) {
32
40
  var f, y, t, _ = {
33
41
  label: 0,
@@ -129,13 +137,14 @@ function _ts_generator(thisArg, body) {
129
137
  }
130
138
  import { readFile, mkdir, writeFile, rename, chmod } from 'node:fs/promises';
131
139
  import { createInterface } from 'node:readline/promises';
132
- import { homedir } from 'node:os';
140
+ import { execFileSync } from 'node:child_process';
141
+ import { homedir, platform } from 'node:os';
133
142
  import { join, extname } from 'node:path';
134
143
  import { Command } from 'commander';
135
144
  import { ApiClient } from './api-client.js';
136
145
  import { startMcpServer } from './mcp-server.js';
137
146
  var DEFAULT_API_URL = 'https://api.display.dev';
138
- var CONFIG_DIR = join(homedir(), '.display');
147
+ var CONFIG_DIR = join(homedir(), '.displaydev');
139
148
  var CONFIG_PATH = join(CONFIG_DIR, 'config.json');
140
149
  function loadConfig() {
141
150
  return _async_to_generator(function() {
@@ -216,12 +225,12 @@ function saveConfig(config) {
216
225
  })();
217
226
  }
218
227
  function resolveAuth() {
219
- var apiKey = process.env.DISPLAY_API_KEY;
228
+ var apiKey = process.env.DISPLAYDEV_API_KEY;
220
229
  if (apiKey) {
221
- var _process_env_DISPLAY_API_URL;
230
+ var _process_env_DISPLAYDEV_API_URL;
222
231
  return {
223
232
  apiKey: apiKey,
224
- apiUrl: (_process_env_DISPLAY_API_URL = process.env.DISPLAY_API_URL) !== null && _process_env_DISPLAY_API_URL !== void 0 ? _process_env_DISPLAY_API_URL : DEFAULT_API_URL
233
+ apiUrl: (_process_env_DISPLAYDEV_API_URL = process.env.DISPLAYDEV_API_URL) !== null && _process_env_DISPLAYDEV_API_URL !== void 0 ? _process_env_DISPLAYDEV_API_URL : DEFAULT_API_URL
225
234
  };
226
235
  }
227
236
  return null;
@@ -255,7 +264,7 @@ function resolveAuthOrConfig() {
255
264
  }
256
265
  ];
257
266
  }
258
- console.error('Not authenticated. Set DISPLAY_API_KEY or run: dsp login');
267
+ console.error('Not authenticated. Set DISPLAYDEV_API_KEY or run: dsp login');
259
268
  process.exit(1);
260
269
  return [
261
270
  2
@@ -471,55 +480,258 @@ program.command('delete <shortId>').description('Delete an artifact permanently'
471
480
  });
472
481
  })();
473
482
  });
483
+ function openBrowser(url) {
484
+ try {
485
+ var os = platform();
486
+ if (os === 'darwin') {
487
+ execFileSync('open', [
488
+ url
489
+ ]);
490
+ } else if (os === 'win32') {
491
+ execFileSync('cmd', [
492
+ '/c',
493
+ 'start',
494
+ '',
495
+ url
496
+ ]);
497
+ } else {
498
+ execFileSync('xdg-open', [
499
+ url
500
+ ]);
501
+ }
502
+ } catch (unused) {
503
+ // Browser opening failed (no display, SSH session, etc.)
504
+ // URL is already printed — user can open it manually
505
+ }
506
+ }
507
+ function sleep(ms) {
508
+ return new Promise(function(resolve) {
509
+ return setTimeout(resolve, ms);
510
+ });
511
+ }
474
512
  // --- login ---
475
- program.command('login').description('Authenticate with display.dev via email OTP').option('--email <email>', 'Email address').action(function(opts) {
513
+ program.command('login').description('Authenticate with display.dev').option('--email <email>', 'Email address').option('--api-key [key]', 'Authenticate with an API key').action(function(opts) {
476
514
  return _async_to_generator(function() {
477
- var _process_env_DISPLAY_API_URL, apiUrl, client, rl, email, code, result;
515
+ var _process_env_DISPLAYDEV_API_URL, apiUrl, client, key, rl, validation, rl1, email, method, check, unused, err, msg, code, result, unused1, deviceResult, unused2, device_code, verification_uri_complete, initialInterval, interval, tokenResult, unused3, _$err;
478
516
  return _ts_generator(this, function(_state) {
479
517
  switch(_state.label){
480
518
  case 0:
481
- apiUrl = (_process_env_DISPLAY_API_URL = process.env.DISPLAY_API_URL) !== null && _process_env_DISPLAY_API_URL !== void 0 ? _process_env_DISPLAY_API_URL : DEFAULT_API_URL;
519
+ apiUrl = (_process_env_DISPLAYDEV_API_URL = process.env.DISPLAYDEV_API_URL) !== null && _process_env_DISPLAYDEV_API_URL !== void 0 ? _process_env_DISPLAYDEV_API_URL : DEFAULT_API_URL;
482
520
  client = new ApiClient({
483
521
  baseUrl: apiUrl,
484
522
  apiKey: '',
485
523
  clientType: 'cli'
486
524
  });
525
+ if (!(opts.apiKey !== undefined)) return [
526
+ 3,
527
+ 8
528
+ ];
529
+ if (!(typeof opts.apiKey === 'string')) return [
530
+ 3,
531
+ 1
532
+ ];
533
+ key = opts.apiKey;
534
+ return [
535
+ 3,
536
+ 5
537
+ ];
538
+ case 1:
539
+ if (!process.stdin.isTTY) return [
540
+ 3,
541
+ 3
542
+ ];
543
+ // Mask input for interactive API key entry
544
+ process.stdout.write('API key: ');
545
+ return [
546
+ 4,
547
+ new Promise(function(resolve) {
548
+ var input = '';
549
+ process.stdin.setRawMode(true);
550
+ process.stdin.resume();
551
+ process.stdin.setEncoding('utf8');
552
+ var cleanup = function cleanup() {
553
+ process.stdin.setRawMode(false);
554
+ process.stdin.pause();
555
+ process.stdin.removeListener('data', onData);
556
+ };
557
+ var onData = function onData(ch) {
558
+ if (ch === '\r' || ch === '\n') {
559
+ cleanup();
560
+ process.stdout.write('\n');
561
+ resolve(input);
562
+ } else if (ch === '\u0003') {
563
+ cleanup();
564
+ process.stdout.write('\n');
565
+ process.exit(1);
566
+ } else if (ch === '\u007F' || ch === '\b') {
567
+ if (input.length > 0) {
568
+ input = input.slice(0, -1);
569
+ }
570
+ } else {
571
+ input += ch;
572
+ }
573
+ };
574
+ process.stdin.on('data', onData);
575
+ })
576
+ ];
577
+ case 2:
578
+ key = _state.sent();
579
+ return [
580
+ 3,
581
+ 5
582
+ ];
583
+ case 3:
584
+ // Non-TTY: read from stdin (piped input)
487
585
  rl = createInterface({
488
586
  input: process.stdin,
489
587
  output: process.stdout
490
588
  });
589
+ return [
590
+ 4,
591
+ rl.question('API key: ')
592
+ ];
593
+ case 4:
594
+ key = _state.sent();
595
+ rl.close();
596
+ _state.label = 5;
597
+ case 5:
598
+ key = key.trim();
599
+ if (!key) {
600
+ console.error('API key cannot be empty.');
601
+ process.exit(1);
602
+ }
603
+ return [
604
+ 4,
605
+ client.validateApiKey(key)
606
+ ];
607
+ case 6:
608
+ validation = _state.sent();
609
+ if (validation === 'invalid') {
610
+ console.error('Invalid API key. Check the key and try again.');
611
+ process.exit(1);
612
+ }
613
+ if (validation === 'network_error') {
614
+ // Network issue — save anyway, validate on first use
615
+ }
616
+ return [
617
+ 4,
618
+ saveConfig({
619
+ token: key,
620
+ apiUrl: apiUrl
621
+ })
622
+ ];
623
+ case 7:
624
+ _state.sent();
625
+ console.log('Authenticated.');
626
+ return [
627
+ 2
628
+ ];
629
+ case 8:
630
+ // --- Mode 1 & 2: Email-based login ---
631
+ rl1 = createInterface({
632
+ input: process.stdin,
633
+ output: process.stdout
634
+ });
491
635
  email = opts.email;
492
636
  if (!!email) return [
493
637
  3,
494
- 2
638
+ 10
495
639
  ];
496
640
  return [
497
641
  4,
498
- rl.question('Email: ')
642
+ rl1.question('Email: ')
499
643
  ];
500
- case 1:
644
+ case 9:
501
645
  email = _state.sent();
502
- _state.label = 2;
503
- case 2:
646
+ _state.label = 10;
647
+ case 10:
648
+ email = email.trim();
649
+ // Check auth method
650
+ method = 'otp';
651
+ _state.label = 11;
652
+ case 11:
653
+ _state.trys.push([
654
+ 11,
655
+ 13,
656
+ ,
657
+ 14
658
+ ]);
659
+ return [
660
+ 4,
661
+ client.authCheck(email)
662
+ ];
663
+ case 12:
664
+ check = _state.sent();
665
+ method = check.method;
666
+ return [
667
+ 3,
668
+ 14
669
+ ];
670
+ case 13:
671
+ unused = _state.sent();
672
+ return [
673
+ 3,
674
+ 14
675
+ ];
676
+ case 14:
677
+ if (!(method === 'otp')) return [
678
+ 3,
679
+ 25
680
+ ];
681
+ _state.label = 15;
682
+ case 15:
683
+ _state.trys.push([
684
+ 15,
685
+ 17,
686
+ ,
687
+ 18
688
+ ]);
504
689
  return [
505
690
  4,
506
691
  client.sendOtp(email)
507
692
  ];
508
- case 3:
693
+ case 16:
509
694
  _state.sent();
695
+ return [
696
+ 3,
697
+ 18
698
+ ];
699
+ case 17:
700
+ err = _state.sent();
701
+ rl1.close();
702
+ msg = _instanceof(err, Error) ? err.message : 'Something went wrong';
703
+ if (msg.includes('requires SSO')) {
704
+ console.error('Your organization requires SSO. Run dsp login again.');
705
+ } else {
706
+ console.error("Something went wrong. Check your connection and try again.");
707
+ }
708
+ process.exit(1);
709
+ return [
710
+ 3,
711
+ 18
712
+ ];
713
+ case 18:
510
714
  console.log("Code sent to ".concat(email));
511
715
  return [
512
716
  4,
513
- rl.question('Enter the 6-digit code: ')
717
+ rl1.question('Enter the 6-digit code: ')
514
718
  ];
515
- case 4:
719
+ case 19:
516
720
  code = _state.sent();
517
- rl.close();
721
+ rl1.close();
722
+ _state.label = 20;
723
+ case 20:
724
+ _state.trys.push([
725
+ 20,
726
+ 23,
727
+ ,
728
+ 24
729
+ ]);
518
730
  return [
519
731
  4,
520
732
  client.verifyOtp(email, code.trim())
521
733
  ];
522
- case 5:
734
+ case 21:
523
735
  result = _state.sent();
524
736
  return [
525
737
  4,
@@ -528,9 +740,156 @@ program.command('login').description('Authenticate with display.dev via email OT
528
740
  apiUrl: apiUrl
529
741
  })
530
742
  ];
531
- case 6:
743
+ case 22:
532
744
  _state.sent();
533
745
  console.log("Logged in as ".concat(email));
746
+ return [
747
+ 3,
748
+ 24
749
+ ];
750
+ case 23:
751
+ unused1 = _state.sent();
752
+ console.error('Invalid or expired code. Try again.');
753
+ process.exit(1);
754
+ return [
755
+ 3,
756
+ 24
757
+ ];
758
+ case 24:
759
+ return [
760
+ 3,
761
+ 38
762
+ ];
763
+ case 25:
764
+ // --- Mode 2: SSO device flow ---
765
+ rl1.close();
766
+ console.log('Your organization requires SSO. Opening browser...');
767
+ _state.label = 26;
768
+ case 26:
769
+ _state.trys.push([
770
+ 26,
771
+ 28,
772
+ ,
773
+ 29
774
+ ]);
775
+ return [
776
+ 4,
777
+ client.requestDeviceCode('dsp-cli')
778
+ ];
779
+ case 27:
780
+ deviceResult = _state.sent();
781
+ return [
782
+ 3,
783
+ 29
784
+ ];
785
+ case 28:
786
+ unused2 = _state.sent();
787
+ console.error('Something went wrong. Check your connection and try again.');
788
+ process.exit(1);
789
+ return [
790
+ 3,
791
+ 29
792
+ ];
793
+ case 29:
794
+ device_code = deviceResult.device_code, verification_uri_complete = deviceResult.verification_uri_complete, initialInterval = deviceResult.interval;
795
+ console.log();
796
+ console.log(" ".concat(verification_uri_complete));
797
+ console.log();
798
+ openBrowser(verification_uri_complete);
799
+ // Poll for token
800
+ interval = (initialInterval || 5) * 1000;
801
+ process.stdout.write('Waiting for authentication...');
802
+ _state.label = 30;
803
+ case 30:
804
+ if (!true) return [
805
+ 3,
806
+ 38
807
+ ];
808
+ return [
809
+ 4,
810
+ sleep(interval)
811
+ ];
812
+ case 31:
813
+ _state.sent();
814
+ tokenResult = void 0;
815
+ _state.label = 32;
816
+ case 32:
817
+ _state.trys.push([
818
+ 32,
819
+ 34,
820
+ ,
821
+ 35
822
+ ]);
823
+ return [
824
+ 4,
825
+ client.pollDeviceToken(device_code, 'dsp-cli')
826
+ ];
827
+ case 33:
828
+ tokenResult = _state.sent();
829
+ return [
830
+ 3,
831
+ 35
832
+ ];
833
+ case 34:
834
+ unused3 = _state.sent();
835
+ // Transient network error — retry on next interval
836
+ return [
837
+ 3,
838
+ 30
839
+ ];
840
+ case 35:
841
+ if (!('access_token' in tokenResult)) return [
842
+ 3,
843
+ 37
844
+ ];
845
+ console.log(' done');
846
+ return [
847
+ 4,
848
+ saveConfig({
849
+ token: tokenResult.access_token,
850
+ apiUrl: apiUrl
851
+ })
852
+ ];
853
+ case 36:
854
+ _state.sent();
855
+ console.log("Logged in as ".concat(email));
856
+ return [
857
+ 2
858
+ ];
859
+ case 37:
860
+ _$err = tokenResult;
861
+ if (_$err.error === 'authorization_pending') {
862
+ return [
863
+ 3,
864
+ 30
865
+ ];
866
+ }
867
+ if (_$err.error === 'slow_down') {
868
+ interval += 5000;
869
+ return [
870
+ 3,
871
+ 30
872
+ ];
873
+ }
874
+ if (_$err.error === 'access_denied') {
875
+ console.log();
876
+ console.error('Authentication denied. Run dsp login to try again.');
877
+ process.exit(1);
878
+ }
879
+ if (_$err.error === 'expired_token') {
880
+ console.log();
881
+ console.error('Code expired. Run dsp login to try again.');
882
+ process.exit(1);
883
+ }
884
+ // Unknown error
885
+ console.log();
886
+ console.error(_$err.error_description || 'Authentication failed. Run dsp login to try again.');
887
+ process.exit(1);
888
+ return [
889
+ 3,
890
+ 30
891
+ ];
892
+ case 38:
534
893
  return [
535
894
  2
536
895
  ];
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@displaydev/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
- "dsp": "./dist/main.js"
6
+ "dsp": "dist/main.js"
7
7
  },
8
8
  "files": [
9
9
  "dist",