@displaydev/cli 0.6.0 → 0.7.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/dist/main.js CHANGED
@@ -1,4 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ function _array_like_to_array(arr, len) {
3
+ if (len == null || len > arr.length) len = arr.length;
4
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
5
+ return arr2;
6
+ }
7
+ function _array_without_holes(arr) {
8
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
9
+ }
2
10
  function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
3
11
  try {
4
12
  var info = gen[key](arg);
@@ -36,6 +44,23 @@ function _instanceof(left, right) {
36
44
  return left instanceof right;
37
45
  }
38
46
  }
47
+ function _iterable_to_array(iter) {
48
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
49
+ }
50
+ function _non_iterable_spread() {
51
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
52
+ }
53
+ function _to_consumable_array(arr) {
54
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
55
+ }
56
+ function _unsupported_iterable_to_array(o, minLen) {
57
+ if (!o) return;
58
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
59
+ var n = Object.prototype.toString.call(o).slice(8, -1);
60
+ if (n === "Object" && o.constructor) n = o.constructor.name;
61
+ if (n === "Map" || n === "Set") return Array.from(n);
62
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
63
+ }
39
64
  function _ts_generator(thisArg, body) {
40
65
  var f, y, t, _ = {
41
66
  label: 0,
@@ -135,49 +160,38 @@ function _ts_generator(thisArg, body) {
135
160
  };
136
161
  }
137
162
  }
138
- import { readFile, mkdir, writeFile, rename, chmod } from 'node:fs/promises';
163
+ import { readFile } from 'node:fs/promises';
139
164
  import { createInterface } from 'node:readline/promises';
140
165
  import { createRequire } from 'node:module';
141
166
  import { execFileSync } from 'node:child_process';
142
- import { homedir, platform } from 'node:os';
143
- import { join, extname } from 'node:path';
167
+ import { platform } from 'node:os';
144
168
  import { Command } from 'commander';
145
- import { ApiClient } from './api-client.js';
169
+ import { ApiClient, ApiError } from './api-client.js';
170
+ import { loadConfig, saveConfig } from './config.js';
146
171
  import { startMcpServer } from './mcp-server.js';
172
+ import { DEFAULT_API_URL, DeviceCodeDeniedError, DeviceCodeExpiredError, DeviceCodeFailedError, InvalidFlagError, PublishArgsError, classifyBrandingError, parseShortIdAndVersion, parseShowBrandingFlag, pollDeviceToken, readApiKeyFromTty, readStreamToString, resolveAuth as resolveAuthFromEnvAndConfig, validatePublishArgs } from './main-helpers.js';
147
173
  var require = createRequire(import.meta.url);
148
174
  var version = require('../package.json').version;
149
- var DEFAULT_API_URL = 'https://api.display.dev';
150
- var CONFIG_DIR = join(homedir(), '.displaydev');
151
- var CONFIG_PATH = join(CONFIG_DIR, 'config.json');
152
- function loadConfig() {
175
+ function resolveAuthOrConfig() {
153
176
  return _async_to_generator(function() {
154
- var raw, unused;
177
+ var auth;
155
178
  return _ts_generator(this, function(_state) {
156
179
  switch(_state.label){
157
180
  case 0:
158
- _state.trys.push([
159
- 0,
160
- 2,
161
- ,
162
- 3
163
- ]);
164
181
  return [
165
182
  4,
166
- readFile(CONFIG_PATH, 'utf-8')
183
+ resolveAuthOrConfigOptional()
167
184
  ];
168
185
  case 1:
169
- raw = _state.sent();
170
- return [
171
- 2,
172
- JSON.parse(raw)
173
- ];
174
- case 2:
175
- unused = _state.sent();
176
- return [
177
- 2,
178
- null
179
- ];
180
- case 3:
186
+ auth = _state.sent();
187
+ if (auth) {
188
+ return [
189
+ 2,
190
+ auth
191
+ ];
192
+ }
193
+ console.error('Not authenticated. Set DISPLAYDEV_API_KEY or run: dsp login');
194
+ process.exit(1);
181
195
  return [
182
196
  2
183
197
  ];
@@ -185,97 +199,34 @@ function loadConfig() {
185
199
  });
186
200
  })();
187
201
  }
188
- function saveConfig(config) {
202
+ /**
203
+ * Like `resolveAuthOrConfig()` but returns null instead of exiting when
204
+ * no credential is present. Used by flows that have a fallback (public
205
+ * publish) or a different behaviour without auth (MCP public-only tools).
206
+ */ function resolveAuthOrConfigOptional() {
189
207
  return _async_to_generator(function() {
190
- var tmpPath;
208
+ var config;
191
209
  return _ts_generator(this, function(_state) {
192
210
  switch(_state.label){
193
211
  case 0:
194
- return [
195
- 4,
196
- mkdir(CONFIG_DIR, {
197
- recursive: true
198
- })
199
- ];
200
- case 1:
201
- _state.sent();
202
- tmpPath = "".concat(CONFIG_PATH, ".tmp");
203
- return [
204
- 4,
205
- writeFile(tmpPath, "".concat(JSON.stringify(config, null, 2), "\n"), {
206
- mode: 384
207
- })
208
- ];
209
- case 2:
210
- _state.sent();
211
- return [
212
- 4,
213
- rename(tmpPath, CONFIG_PATH)
214
- ];
215
- case 3:
216
- _state.sent();
217
- return [
218
- 4,
219
- chmod(CONFIG_PATH, 384)
220
- ];
221
- case 4:
222
- _state.sent();
223
- return [
224
- 2
225
- ];
226
- }
227
- });
228
- })();
229
- }
230
- function resolveAuth() {
231
- var apiKey = process.env.DISPLAYDEV_API_KEY;
232
- if (apiKey) {
233
- var _process_env_DISPLAYDEV_API_URL;
234
- return {
235
- apiKey: apiKey,
236
- 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
237
- };
238
- }
239
- return null;
240
- }
241
- function resolveAuthOrConfig() {
242
- return _async_to_generator(function() {
243
- var env, config, _config_apiUrl;
244
- return _ts_generator(this, function(_state) {
245
- switch(_state.label){
246
- case 0:
247
- env = resolveAuth();
248
- if (env) {
249
- return [
250
- 2,
251
- env
252
- ];
253
- }
254
212
  return [
255
213
  4,
256
214
  loadConfig()
257
215
  ];
258
216
  case 1:
259
217
  config = _state.sent();
260
- if (config === null || config === void 0 ? void 0 : config.token) {
261
- ;
262
- return [
263
- 2,
264
- {
265
- apiKey: config.token,
266
- apiUrl: (_config_apiUrl = config.apiUrl) !== null && _config_apiUrl !== void 0 ? _config_apiUrl : DEFAULT_API_URL
267
- }
268
- ];
269
- }
270
- console.error('Not authenticated. Set DISPLAYDEV_API_KEY or run: dsp login');
271
- process.exit(1);
272
218
  return [
273
- 2
219
+ 2,
220
+ resolveAuthFromEnvAndConfig(process.env, config)
274
221
  ];
275
222
  }
276
223
  });
277
224
  })();
278
225
  }
226
+ function resolvePublicApiUrl() {
227
+ var _process_env_DISPLAYDEV_API_URL;
228
+ return (_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;
229
+ }
279
230
  function createClient(auth) {
280
231
  return new ApiClient({
281
232
  baseUrl: auth.apiUrl,
@@ -287,82 +238,225 @@ var program = new Command().name('dsp').description('display.dev CLI — publish
287
238
  function collectEmails(value, prev) {
288
239
  return prev.concat(value);
289
240
  }
290
- var SHOW_BRANDING_VALUES = new Set([
291
- 'show',
292
- 'hide',
293
- 'inherit'
294
- ]);
295
- function parseShowBrandingFlag(raw) {
296
- if (!raw) {
297
- return undefined;
298
- }
299
- var normalized = raw.toLowerCase();
300
- if (!SHOW_BRANDING_VALUES.has(normalized)) {
301
- console.error("Invalid --show-branding: ".concat(raw, ". Use show, hide, or inherit."));
302
- process.exit(1);
241
+ function parseShowBrandingOrExit(raw) {
242
+ try {
243
+ return parseShowBrandingFlag(raw);
244
+ } catch (err) {
245
+ if (_instanceof(err, InvalidFlagError)) {
246
+ console.error("Invalid ".concat(err.flag, ": ").concat(err.value, ". Use show, hide, or inherit."));
247
+ process.exit(1);
248
+ }
249
+ throw err;
303
250
  }
304
- return normalized;
305
251
  }
306
252
  // --- publish ---
307
- program.command('publish <path>').description('Publish an HTML or Markdown file').option('--name <name>', 'Artifact display name').option('--id <shortId>', 'Update existing artifact (new version)').option('--public', 'Make artifact publicly accessible').option('--company', 'Restrict artifact to company auth (default for new artifacts)').option('--share <email>', 'Share with guest email (repeatable)', collectEmails, []).option('--clear-shares', 'Remove all guest email shares (update only)').option('--theme <theme>', 'Markdown theme (default: github)').option('--show-branding <mode>', 'display.dev bar override: show, hide, or inherit (paid plans only)').action(function(filePath, opts) {
253
+ program.command('publish <path>').description('Publish an HTML or Markdown file. Use "-" to read from stdin (requires --id).').option('--name <name>', 'Artifact display name').option('--id <shortId>', 'Update existing artifact (new version)').option('--public', 'Make artifact publicly accessible (alias for --visibility public)').option('--company', 'Restrict artifact to company auth (alias for --visibility company)').option('--visibility <level>', 'public | company | private. "private" requires the Teams plan.').option('--share <email>', 'Add an email to sharedWith (repeatable; alias for --share-with)', collectEmails, []).option('--share-with <emails>', 'Comma-separated email list for sharedWith', function(val) {
254
+ return val.split(',').map(function(e) {
255
+ return e.trim();
256
+ }).filter(Boolean);
257
+ }).option('--clear-shares', 'Remove all sharedWith entries (update only)').option('--theme <theme>', 'Markdown theme (default: github)').option('--show-branding <mode>', 'display.dev bar override: show, hide, or inherit (paid plans only)').action(function(filePath, opts) {
308
258
  return _async_to_generator(function() {
309
- var showBranding, auth, client, ext, content, format, visibility, share, result, err, msg, verb;
259
+ var _opts_shareWith, showBranding, auth, fromStdin, format, content, inferClient, detail, err, msg, publicClient, publicResult, err1, msg1, client, visibility, mergedShare, share, result, err2, msg2, verb;
310
260
  return _ts_generator(this, function(_state) {
311
261
  switch(_state.label){
312
262
  case 0:
313
- if (!opts.id && !opts.name) {
314
- console.error('--name is required for new artifacts. Use --id to update an existing one.');
315
- process.exit(1);
263
+ showBranding = parseShowBrandingOrExit(opts.showBranding);
264
+ return [
265
+ 4,
266
+ resolveAuthOrConfigOptional()
267
+ ];
268
+ case 1:
269
+ auth = _state.sent();
270
+ fromStdin = filePath === '-';
271
+ if (fromStdin) {
272
+ // stdin path is for re-publishing existing artifacts (`dsp export | dsp
273
+ // publish --id <id> -`). Path-specific gates run first so the user gets
274
+ // a clear error before we touch the network or stdin.
275
+ if (!auth) {
276
+ console.error('Reading from stdin (-) requires auth. Run: dsp login');
277
+ process.exit(1);
278
+ }
279
+ if (!opts.id) {
280
+ console.error('Reading from stdin (-) requires --id <shortId>.');
281
+ process.exit(2);
282
+ }
283
+ if (process.stdin.isTTY) {
284
+ console.error('cannot read source from a TTY; pipe a file or use a path');
285
+ process.exit(2);
286
+ }
316
287
  }
317
- if (opts.public && opts.company) {
318
- console.error('Use --public or --company, not both.');
319
- process.exit(1);
288
+ try {
289
+ validatePublishArgs({
290
+ name: opts.name,
291
+ id: opts.id,
292
+ public: opts.public,
293
+ company: opts.company,
294
+ visibility: opts.visibility,
295
+ share: opts.share,
296
+ shareWith: opts.shareWith,
297
+ clearShares: opts.clearShares,
298
+ showBranding: opts.showBranding,
299
+ filePath: filePath,
300
+ authenticated: auth !== null,
301
+ fromStdin: fromStdin
302
+ });
303
+ } catch (err) {
304
+ if (_instanceof(err, PublishArgsError)) {
305
+ console.error(err.message);
306
+ process.exit(1);
307
+ }
308
+ throw err;
320
309
  }
321
- if (opts.clearShares && opts.share.length > 0) {
322
- console.error('Use --share or --clear-shares, not both.');
310
+ if (!fromStdin) return [
311
+ 3,
312
+ 7
313
+ ];
314
+ // The artifact's format is fixed at create — infer it via GET so the
315
+ // round-trip preserves it without making the caller pass --format.
316
+ inferClient = createClient(auth);
317
+ _state.label = 2;
318
+ case 2:
319
+ _state.trys.push([
320
+ 2,
321
+ 4,
322
+ ,
323
+ 5
324
+ ]);
325
+ return [
326
+ 4,
327
+ inferClient.get(opts.id)
328
+ ];
329
+ case 3:
330
+ detail = _state.sent();
331
+ format = detail.format === 'md' ? 'md' : 'html';
332
+ return [
333
+ 3,
334
+ 5
335
+ ];
336
+ case 4:
337
+ err = _state.sent();
338
+ if (_instanceof(err, ApiError) && (err.status === 401 || err.status === 403)) {
339
+ console.error('Your credential is no longer valid. Run: dsp login (or rotate DISPLAYDEV_API_KEY).');
323
340
  process.exit(1);
324
341
  }
325
- if (opts.clearShares && !opts.id) {
326
- console.error('--clear-shares only applies when updating (--id).');
327
- process.exit(1);
342
+ if (_instanceof(err, ApiError) && err.status === 404) {
343
+ console.error("Artifact not found: ".concat(opts.id));
344
+ process.exit(4);
328
345
  }
329
- showBranding = parseShowBrandingFlag(opts.showBranding);
346
+ msg = _instanceof(err, Error) ? err.message : String(err);
347
+ console.error(msg);
348
+ process.exit(1);
349
+ return [
350
+ 3,
351
+ 5
352
+ ];
353
+ case 5:
330
354
  return [
331
355
  4,
332
- resolveAuthOrConfig()
356
+ readStreamToString(process.stdin)
333
357
  ];
334
- case 1:
335
- auth = _state.sent();
336
- client = createClient(auth);
337
- ext = extname(filePath).toLowerCase();
338
- if (ext !== '.html' && ext !== '.md') {
339
- console.error("Unsupported file type: ".concat(ext, " (expected .html or .md)"));
340
- process.exit(1);
341
- }
358
+ case 6:
359
+ content = _state.sent();
360
+ return [
361
+ 3,
362
+ 9
363
+ ];
364
+ case 7:
365
+ format = filePath.toLowerCase().endsWith('.md') ? 'md' : 'html';
342
366
  return [
343
367
  4,
344
368
  readFile(filePath, 'utf-8')
345
369
  ];
346
- case 2:
370
+ case 8:
347
371
  content = _state.sent();
348
- format = ext === '.md' ? 'md' : 'html';
349
- if (opts.public) {
372
+ _state.label = 9;
373
+ case 9:
374
+ if (!!auth) return [
375
+ 3,
376
+ 14
377
+ ];
378
+ publicClient = new ApiClient({
379
+ baseUrl: resolvePublicApiUrl(),
380
+ apiKey: '',
381
+ clientType: 'cli'
382
+ });
383
+ _state.label = 10;
384
+ case 10:
385
+ _state.trys.push([
386
+ 10,
387
+ 12,
388
+ ,
389
+ 13
390
+ ]);
391
+ return [
392
+ 4,
393
+ publicClient.publishPublic({
394
+ content: content,
395
+ name: opts.name,
396
+ format: format
397
+ })
398
+ ];
399
+ case 11:
400
+ publicResult = _state.sent();
401
+ return [
402
+ 3,
403
+ 13
404
+ ];
405
+ case 12:
406
+ err1 = _state.sent();
407
+ msg1 = _instanceof(err1, Error) ? err1.message : String(err1);
408
+ console.error(msg1);
409
+ process.exit(1);
410
+ return [
411
+ 3,
412
+ 13
413
+ ];
414
+ case 13:
415
+ // Full response as JSON on stdout so `dsp publish … | jq -r .previewUrl`
416
+ // works alongside the human-readable block on stderr.
417
+ console.log(JSON.stringify(publicResult));
418
+ process.stderr.write([
419
+ '',
420
+ "Published anonymously — nobody owns this artifact yet.",
421
+ " Preview: ".concat(publicResult.previewUrl),
422
+ " Claim: ".concat(publicResult.claimUrl),
423
+ " Expires: ".concat(publicResult.expiresAt),
424
+ "Sign in at the claim URL to move it into your display.dev organization.",
425
+ ''
426
+ ].join('\n'));
427
+ return [
428
+ 2
429
+ ];
430
+ case 14:
431
+ // --- Authenticated path — `--id` is update, otherwise create. ---
432
+ client = createClient(auth);
433
+ if (opts.visibility) {
434
+ if (opts.visibility !== 'public' && opts.visibility !== 'company' && opts.visibility !== 'private') {
435
+ console.error("Invalid --visibility: ".concat(opts.visibility, ". Use public, company, or private."));
436
+ process.exit(1);
437
+ }
438
+ visibility = opts.visibility;
439
+ } else if (opts.public) {
350
440
  visibility = 'public';
351
441
  } else if (opts.company) {
352
442
  visibility = 'company';
353
443
  }
354
- share = opts.share.length > 0 ? opts.share : undefined;
355
- _state.label = 3;
356
- case 3:
444
+ // --share-with (csv) and --share (repeatable) both contribute to the
445
+ // sharedWith list. Concatenating rather than making them mutually
446
+ // exclusive keeps CLI composition simple for scripts that want to mix.
447
+ mergedShare = _to_consumable_array((_opts_shareWith = opts.shareWith) !== null && _opts_shareWith !== void 0 ? _opts_shareWith : []).concat(_to_consumable_array(opts.share));
448
+ share = mergedShare.length > 0 ? mergedShare : undefined;
449
+ _state.label = 15;
450
+ case 15:
357
451
  _state.trys.push([
358
- 3,
359
- 8,
452
+ 15,
453
+ 20,
360
454
  ,
361
- 9
455
+ 21
362
456
  ]);
363
457
  if (!opts.id) return [
364
458
  3,
365
- 5
459
+ 17
366
460
  ];
367
461
  return [
368
462
  4,
@@ -377,13 +471,13 @@ program.command('publish <path>').description('Publish an HTML or Markdown file'
377
471
  showBranding: showBranding
378
472
  })
379
473
  ];
380
- case 4:
474
+ case 16:
381
475
  result = _state.sent();
382
476
  return [
383
477
  3,
384
- 7
478
+ 19
385
479
  ];
386
- case 5:
480
+ case 17:
387
481
  return [
388
482
  4,
389
483
  client.publish({
@@ -396,28 +490,34 @@ program.command('publish <path>').description('Publish an HTML or Markdown file'
396
490
  showBranding: showBranding
397
491
  })
398
492
  ];
399
- case 6:
493
+ case 18:
400
494
  result = _state.sent();
401
- _state.label = 7;
402
- case 7:
495
+ _state.label = 19;
496
+ case 19:
403
497
  return [
404
498
  3,
405
- 9
499
+ 21
406
500
  ];
407
- case 8:
408
- err = _state.sent();
409
- msg = _instanceof(err, Error) ? err.message : String(err);
410
- if (/paid plan|upgrade/i.test(msg)) {
501
+ case 20:
502
+ err2 = _state.sent();
503
+ // Fail closed on an expired / revoked credential: do NOT fall through
504
+ // to the public endpoint, since the user intended a workspace publish.
505
+ if (_instanceof(err2, ApiError) && (err2.status === 401 || err2.status === 403)) {
506
+ console.error('Your credential is no longer valid. Run: dsp login (or rotate DISPLAYDEV_API_KEY).');
507
+ process.exit(1);
508
+ }
509
+ msg2 = _instanceof(err2, Error) ? err2.message : String(err2);
510
+ if (classifyBrandingError(msg2) === 'paid-plan') {
411
511
  console.error('Error: Hiding display.dev branding requires a paid plan. See https://display.dev/billing');
412
512
  } else {
413
- console.error(msg);
513
+ console.error(msg2);
414
514
  }
415
515
  process.exit(1);
416
516
  return [
417
517
  3,
418
- 9
518
+ 21
419
519
  ];
420
- case 9:
520
+ case 21:
421
521
  console.log(result.url);
422
522
  verb = opts.id ? 'Updated' : 'Published';
423
523
  console.log("".concat(verb, " ").concat(result.name, " (").concat(result.shortId, ") v").concat(result.version));
@@ -428,6 +528,96 @@ program.command('publish <path>').description('Publish an HTML or Markdown file'
428
528
  });
429
529
  })();
430
530
  });
531
+ // --- share ---
532
+ program.command('share <shortId>').description('Change an artifact\'s visibility and/or add/remove shared-with emails.').option('--visibility <level>', 'public | company | private. "private" requires the Teams plan.').option('--add-users <emails>', 'Comma-separated emails to add to sharedWith', function(val) {
533
+ return val.split(',').map(function(e) {
534
+ return e.trim();
535
+ }).filter(Boolean);
536
+ }).option('--remove-users <emails>', 'Comma-separated emails to remove from sharedWith', function(val) {
537
+ return val.split(',').map(function(e) {
538
+ return e.trim();
539
+ }).filter(Boolean);
540
+ }).action(function(shortId, opts) {
541
+ return _async_to_generator(function() {
542
+ var auth, client, result, err, msg;
543
+ return _ts_generator(this, function(_state) {
544
+ switch(_state.label){
545
+ case 0:
546
+ if (opts.visibility === undefined && (!opts.addUsers || opts.addUsers.length === 0) && (!opts.removeUsers || opts.removeUsers.length === 0)) {
547
+ console.error('Provide at least one of --visibility, --add-users, --remove-users.');
548
+ process.exit(1);
549
+ }
550
+ if (opts.visibility !== undefined && opts.visibility !== 'public' && opts.visibility !== 'company' && opts.visibility !== 'private') {
551
+ console.error("Invalid --visibility: ".concat(opts.visibility, ". Use public, company, or private."));
552
+ process.exit(1);
553
+ }
554
+ return [
555
+ 4,
556
+ resolveAuthOrConfig()
557
+ ];
558
+ case 1:
559
+ auth = _state.sent();
560
+ client = createClient(auth);
561
+ _state.label = 2;
562
+ case 2:
563
+ _state.trys.push([
564
+ 2,
565
+ 4,
566
+ ,
567
+ 5
568
+ ]);
569
+ return [
570
+ 4,
571
+ client.share(shortId, {
572
+ visibility: opts.visibility,
573
+ addUsers: opts.addUsers,
574
+ removeUsers: opts.removeUsers
575
+ })
576
+ ];
577
+ case 3:
578
+ result = _state.sent();
579
+ console.log(JSON.stringify(result, null, 2));
580
+ return [
581
+ 3,
582
+ 5
583
+ ];
584
+ case 4:
585
+ err = _state.sent();
586
+ if (_instanceof(err, ApiError) && err.status === 402) {
587
+ console.error('Error: Private visibility requires the Teams plan. Upgrade at https://display.dev/pricing');
588
+ process.exit(1);
589
+ }
590
+ if (_instanceof(err, ApiError) && err.status === 404) {
591
+ console.error("Artifact not found: ".concat(shortId));
592
+ process.exit(4);
593
+ }
594
+ if (_instanceof(err, ApiError) && err.status === 401) {
595
+ // 401 is always "credential expired / revoked" — sessions + api keys
596
+ // both return it on an invalid secret. 403 is authorization ("you're
597
+ // signed in but not allowed"), which we surface with the API's own
598
+ // message below.
599
+ console.error('Your credential is no longer valid. Run: dsp login (or rotate DISPLAYDEV_API_KEY).');
600
+ process.exit(1);
601
+ }
602
+ if (_instanceof(err, ApiError) && (err.status === 400 || err.status === 403)) {
603
+ console.error(err.message);
604
+ process.exit(1);
605
+ }
606
+ msg = _instanceof(err, Error) ? err.message : String(err);
607
+ console.error(msg);
608
+ process.exit(1);
609
+ return [
610
+ 3,
611
+ 5
612
+ ];
613
+ case 5:
614
+ return [
615
+ 2
616
+ ];
617
+ }
618
+ });
619
+ })();
620
+ });
431
621
  // --- find ---
432
622
  program.command('find [query]').description('Search for artifacts').option('--by <email>', 'Filter by publisher email').option('--mine', 'Show only artifacts you published').option('--since <date>', 'Filter by date (ISO format)').option('--sort <col>', 'Sort column: updated_at, view_count, name', 'updated_at').option('--dir <direction>', 'Sort direction: asc or desc').option('--limit <n>', 'Max rows to fetch (1-100)', function(v) {
433
623
  return parseInt(v, 10);
@@ -531,6 +721,76 @@ program.command('get <shortId>').description('Get artifact details').option('--i
531
721
  });
532
722
  })();
533
723
  });
724
+ // --- export ---
725
+ program.command('export <shortId>').description('Print the published source bytes to stdout. Pin a version with @<n>.').action(function(shortIdArg) {
726
+ return _async_to_generator(function() {
727
+ var parsed, auth, client, result, err, msg;
728
+ return _ts_generator(this, function(_state) {
729
+ switch(_state.label){
730
+ case 0:
731
+ parsed = parseShortIdAndVersion(shortIdArg);
732
+ if (!parsed) {
733
+ console.error('Invalid argument. Expected <shortId> or <shortId>@<version> (version must be a positive integer).');
734
+ process.exit(2);
735
+ }
736
+ return [
737
+ 4,
738
+ resolveAuthOrConfig()
739
+ ];
740
+ case 1:
741
+ auth = _state.sent();
742
+ client = createClient(auth);
743
+ _state.label = 2;
744
+ case 2:
745
+ _state.trys.push([
746
+ 2,
747
+ 4,
748
+ ,
749
+ 5
750
+ ]);
751
+ return [
752
+ 4,
753
+ client.exportSource(parsed.shortId, parsed.version)
754
+ ];
755
+ case 3:
756
+ result = _state.sent();
757
+ return [
758
+ 3,
759
+ 5
760
+ ];
761
+ case 4:
762
+ err = _state.sent();
763
+ if (_instanceof(err, ApiError)) {
764
+ if (err.status === 404) {
765
+ console.error('Artifact not found');
766
+ process.exit(4);
767
+ }
768
+ if (err.status === 403) {
769
+ console.error('Forbidden');
770
+ process.exit(4);
771
+ }
772
+ if (err.status === 401) {
773
+ console.error('Your credential is no longer valid. Run: dsp login (or rotate DISPLAYDEV_API_KEY).');
774
+ process.exit(1);
775
+ }
776
+ }
777
+ msg = _instanceof(err, Error) ? err.message : String(err);
778
+ console.error(msg);
779
+ process.exit(1);
780
+ return [
781
+ 3,
782
+ 5
783
+ ];
784
+ case 5:
785
+ // Binary-clean: write Buffer directly, no console.log (which appends \n).
786
+ process.stdout.write(result.data);
787
+ return [
788
+ 2
789
+ ];
790
+ }
791
+ });
792
+ })();
793
+ });
534
794
  // --- delete ---
535
795
  program.command('delete <shortId>').description('Delete an artifact permanently').option('--confirm', 'Confirm deletion').action(function(shortId, opts) {
536
796
  return _async_to_generator(function() {
@@ -564,6 +824,9 @@ program.command('delete <shortId>').description('Delete an artifact permanently'
564
824
  })();
565
825
  });
566
826
  function openBrowser(url) {
827
+ if (process.env.DISPLAYDEV_SKIP_BROWSER) {
828
+ return;
829
+ }
567
830
  try {
568
831
  var os = platform();
569
832
  if (os === 'darwin') {
@@ -587,15 +850,10 @@ function openBrowser(url) {
587
850
  // URL is already printed — user can open it manually
588
851
  }
589
852
  }
590
- function sleep(ms) {
591
- return new Promise(function(resolve) {
592
- return setTimeout(resolve, ms);
593
- });
594
- }
595
853
  // --- login ---
596
854
  program.command('login').description('Authenticate with display.dev').option('--email <email>', 'Email address').option('--code <code>', 'OTP code (verify step; pair with --email)').option('--api-key [key]', 'Authenticate with an API key').option('--json', 'Machine-readable output (structured status, no prose)').action(function(opts) {
597
855
  return _async_to_generator(function() {
598
- var _process_env_DISPLAYDEV_API_URL, emit, emitErr, apiUrl, client, key, rl, validation, email, rl1, method, check, unused, err, msg, code, rl2, result, unused1, deviceResult, unused2, device_code, verification_uri_complete, initialInterval, interval, tokenResult, unused3, _$err;
856
+ var _process_env_DISPLAYDEV_API_URL, emit, emitErr, apiUrl, client, key, unused, rl, validation, email, rl1, method, check, unused1, err, msg, code, rl2, result, unused2, deviceResult, unused3, device_code, verification_uri_complete, initialInterval, expires_in, token, err1;
599
857
  return _ts_generator(this, function(_state) {
600
858
  switch(_state.label){
601
859
  case 0:
@@ -623,7 +881,7 @@ program.command('login').description('Authenticate with display.dev').option('--
623
881
  });
624
882
  if (!(opts.apiKey !== undefined)) return [
625
883
  3,
626
- 8
884
+ 11
627
885
  ];
628
886
  if (!(typeof opts.apiKey === 'string')) return [
629
887
  3,
@@ -632,54 +890,46 @@ program.command('login').description('Authenticate with display.dev').option('--
632
890
  key = opts.apiKey;
633
891
  return [
634
892
  3,
635
- 5
893
+ 8
636
894
  ];
637
895
  case 1:
638
896
  if (!process.stdin.isTTY) return [
639
897
  3,
640
- 3
898
+ 6
641
899
  ];
642
- // Mask input for interactive API key entry
900
+ // Raw-mode input suppresses terminal echo.
643
901
  process.stdout.write('API key: ');
902
+ _state.label = 2;
903
+ case 2:
904
+ _state.trys.push([
905
+ 2,
906
+ 4,
907
+ ,
908
+ 5
909
+ ]);
644
910
  return [
645
911
  4,
646
- new Promise(function(resolve) {
647
- var input = '';
648
- process.stdin.setRawMode(true);
649
- process.stdin.resume();
650
- process.stdin.setEncoding('utf8');
651
- var cleanup = function cleanup() {
652
- process.stdin.setRawMode(false);
653
- process.stdin.pause();
654
- process.stdin.removeListener('data', onData);
655
- };
656
- var onData = function onData(ch) {
657
- if (ch === '\r' || ch === '\n') {
658
- cleanup();
659
- process.stdout.write('\n');
660
- resolve(input);
661
- } else if (ch === '\u0003') {
662
- cleanup();
663
- process.stdout.write('\n');
664
- process.exit(1);
665
- } else if (ch === '\u007F' || ch === '\b') {
666
- if (input.length > 0) {
667
- input = input.slice(0, -1);
668
- }
669
- } else {
670
- input += ch;
671
- }
672
- };
673
- process.stdin.on('data', onData);
674
- })
912
+ readApiKeyFromTty(process.stdin, process.stdout)
675
913
  ];
676
- case 2:
914
+ case 3:
677
915
  key = _state.sent();
678
916
  return [
679
917
  3,
680
918
  5
681
919
  ];
682
- case 3:
920
+ case 4:
921
+ unused = _state.sent();
922
+ process.exit(1);
923
+ return [
924
+ 3,
925
+ 5
926
+ ];
927
+ case 5:
928
+ return [
929
+ 3,
930
+ 8
931
+ ];
932
+ case 6:
683
933
  // Non-TTY: read from stdin (piped input)
684
934
  rl = createInterface({
685
935
  input: process.stdin,
@@ -689,11 +939,11 @@ program.command('login').description('Authenticate with display.dev').option('--
689
939
  4,
690
940
  rl.question('API key: ')
691
941
  ];
692
- case 4:
942
+ case 7:
693
943
  key = _state.sent();
694
944
  rl.close();
695
- _state.label = 5;
696
- case 5:
945
+ _state.label = 8;
946
+ case 8:
697
947
  key = key.trim();
698
948
  if (!key) {
699
949
  console.error('API key cannot be empty.');
@@ -703,7 +953,7 @@ program.command('login').description('Authenticate with display.dev').option('--
703
953
  4,
704
954
  client.validateApiKey(key)
705
955
  ];
706
- case 6:
956
+ case 9:
707
957
  validation = _state.sent();
708
958
  if (validation === 'invalid') {
709
959
  emitErr('Invalid API key. Check the key and try again.', {
@@ -722,7 +972,7 @@ program.command('login').description('Authenticate with display.dev').option('--
722
972
  apiUrl: apiUrl
723
973
  })
724
974
  ];
725
- case 7:
975
+ case 10:
726
976
  _state.sent();
727
977
  emit('Authenticated.', {
728
978
  status: 'authenticated',
@@ -731,12 +981,12 @@ program.command('login').description('Authenticate with display.dev').option('--
731
981
  return [
732
982
  2
733
983
  ];
734
- case 8:
984
+ case 11:
735
985
  // --- Mode 1 & 2: Email-based login ---
736
986
  email = opts.email;
737
987
  if (!!email) return [
738
988
  3,
739
- 10
989
+ 13
740
990
  ];
741
991
  if (!process.stdin.isTTY) {
742
992
  console.error('Email is required. Use --email <email> for non-interactive mode.');
@@ -750,67 +1000,67 @@ program.command('login').description('Authenticate with display.dev').option('--
750
1000
  4,
751
1001
  rl1.question('Email: ')
752
1002
  ];
753
- case 9:
1003
+ case 12:
754
1004
  email = _state.sent();
755
1005
  rl1.close();
756
- _state.label = 10;
757
- case 10:
1006
+ _state.label = 13;
1007
+ case 13:
758
1008
  email = email.trim();
759
1009
  // Check auth method
760
1010
  method = 'otp';
761
- _state.label = 11;
762
- case 11:
1011
+ _state.label = 14;
1012
+ case 14:
763
1013
  _state.trys.push([
764
- 11,
765
- 13,
1014
+ 14,
1015
+ 16,
766
1016
  ,
767
- 14
1017
+ 17
768
1018
  ]);
769
1019
  return [
770
1020
  4,
771
1021
  client.authCheck(email)
772
1022
  ];
773
- case 12:
1023
+ case 15:
774
1024
  check = _state.sent();
775
1025
  method = check.method;
776
1026
  return [
777
1027
  3,
778
- 14
1028
+ 17
779
1029
  ];
780
- case 13:
781
- unused = _state.sent();
1030
+ case 16:
1031
+ unused1 = _state.sent();
782
1032
  return [
783
1033
  3,
784
- 14
1034
+ 17
785
1035
  ];
786
- case 14:
1036
+ case 17:
787
1037
  if (!(method === 'otp')) return [
788
1038
  3,
789
- 27
1039
+ 30
790
1040
  ];
791
1041
  if (!!opts.code) return [
792
1042
  3,
793
- 19
1043
+ 22
794
1044
  ];
795
- _state.label = 15;
796
- case 15:
1045
+ _state.label = 18;
1046
+ case 18:
797
1047
  _state.trys.push([
798
- 15,
799
- 17,
1048
+ 18,
1049
+ 20,
800
1050
  ,
801
- 18
1051
+ 21
802
1052
  ]);
803
1053
  return [
804
1054
  4,
805
1055
  client.sendOtp(email)
806
1056
  ];
807
- case 16:
1057
+ case 19:
808
1058
  _state.sent();
809
1059
  return [
810
1060
  3,
811
- 18
1061
+ 21
812
1062
  ];
813
- case 17:
1063
+ case 20:
814
1064
  err = _state.sent();
815
1065
  msg = _instanceof(err, Error) ? err.message : 'Something went wrong';
816
1066
  if (msg.includes('requires SSO')) {
@@ -827,9 +1077,9 @@ program.command('login').description('Authenticate with display.dev').option('--
827
1077
  process.exit(1);
828
1078
  return [
829
1079
  3,
830
- 18
1080
+ 21
831
1081
  ];
832
- case 18:
1082
+ case 21:
833
1083
  if (!process.stdin.isTTY) {
834
1084
  // Non-interactive: exit after sending. The caller (typically an
835
1085
  // agent) will collect the OTP from the user and re-invoke with --code.
@@ -859,18 +1109,18 @@ program.command('login').description('Authenticate with display.dev').option('--
859
1109
  if (!opts.json) {
860
1110
  console.log("Code sent to ".concat(email));
861
1111
  }
862
- _state.label = 19;
863
- case 19:
1112
+ _state.label = 22;
1113
+ case 22:
864
1114
  if (!opts.code) return [
865
1115
  3,
866
- 20
1116
+ 23
867
1117
  ];
868
1118
  code = opts.code;
869
1119
  return [
870
1120
  3,
871
- 22
1121
+ 25
872
1122
  ];
873
- case 20:
1123
+ case 23:
874
1124
  rl2 = createInterface({
875
1125
  input: process.stdin,
876
1126
  output: process.stdout
@@ -879,22 +1129,22 @@ program.command('login').description('Authenticate with display.dev').option('--
879
1129
  4,
880
1130
  rl2.question('Enter the 6-digit code: ')
881
1131
  ];
882
- case 21:
1132
+ case 24:
883
1133
  code = _state.sent();
884
1134
  rl2.close();
885
- _state.label = 22;
886
- case 22:
1135
+ _state.label = 25;
1136
+ case 25:
887
1137
  _state.trys.push([
888
- 22,
889
1138
  25,
1139
+ 28,
890
1140
  ,
891
- 26
1141
+ 29
892
1142
  ]);
893
1143
  return [
894
1144
  4,
895
1145
  client.verifyOtp(email, code.trim())
896
1146
  ];
897
- case 23:
1147
+ case 26:
898
1148
  result = _state.sent();
899
1149
  return [
900
1150
  4,
@@ -903,7 +1153,7 @@ program.command('login').description('Authenticate with display.dev').option('--
903
1153
  apiUrl: apiUrl
904
1154
  })
905
1155
  ];
906
- case 24:
1156
+ case 27:
907
1157
  _state.sent();
908
1158
  emit("Logged in as ".concat(email), {
909
1159
  status: 'authenticated',
@@ -912,10 +1162,10 @@ program.command('login').description('Authenticate with display.dev').option('--
912
1162
  });
913
1163
  return [
914
1164
  3,
915
- 26
1165
+ 29
916
1166
  ];
917
- case 25:
918
- unused1 = _state.sent();
1167
+ case 28:
1168
+ unused2 = _state.sent();
919
1169
  emitErr('Invalid or expired code. Try again.', {
920
1170
  status: 'error',
921
1171
  error: 'invalid_or_expired_code'
@@ -923,142 +1173,98 @@ program.command('login').description('Authenticate with display.dev').option('--
923
1173
  process.exit(1);
924
1174
  return [
925
1175
  3,
926
- 26
1176
+ 29
927
1177
  ];
928
- case 26:
1178
+ case 29:
929
1179
  return [
930
1180
  3,
931
- 40
1181
+ 39
932
1182
  ];
933
- case 27:
1183
+ case 30:
934
1184
  // --- Mode 2: SSO device flow ---
935
1185
  console.log('Your organization requires SSO. Opening browser...');
936
- _state.label = 28;
937
- case 28:
1186
+ _state.label = 31;
1187
+ case 31:
938
1188
  _state.trys.push([
939
- 28,
940
- 30,
1189
+ 31,
1190
+ 33,
941
1191
  ,
942
- 31
1192
+ 34
943
1193
  ]);
944
1194
  return [
945
1195
  4,
946
1196
  client.requestDeviceCode('dsp-cli')
947
1197
  ];
948
- case 29:
1198
+ case 32:
949
1199
  deviceResult = _state.sent();
950
1200
  return [
951
1201
  3,
952
- 31
1202
+ 34
953
1203
  ];
954
- case 30:
955
- unused2 = _state.sent();
1204
+ case 33:
1205
+ unused3 = _state.sent();
956
1206
  console.error('Something went wrong. Check your connection and try again.');
957
1207
  process.exit(1);
958
1208
  return [
959
1209
  3,
960
- 31
1210
+ 34
961
1211
  ];
962
- case 31:
963
- device_code = deviceResult.device_code, verification_uri_complete = deviceResult.verification_uri_complete, initialInterval = deviceResult.interval;
1212
+ case 34:
1213
+ device_code = deviceResult.device_code, verification_uri_complete = deviceResult.verification_uri_complete, initialInterval = deviceResult.interval, expires_in = deviceResult.expires_in;
964
1214
  console.log();
965
1215
  console.log(" ".concat(verification_uri_complete));
966
1216
  console.log();
967
1217
  openBrowser(verification_uri_complete);
968
- // Poll for token
969
- interval = (initialInterval || 5) * 1000;
970
1218
  process.stdout.write('Waiting for authentication...');
971
- _state.label = 32;
972
- case 32:
973
- if (!true) return [
974
- 3,
975
- 40
976
- ];
977
- return [
978
- 4,
979
- sleep(interval)
980
- ];
981
- case 33:
982
- _state.sent();
983
- tokenResult = void 0;
984
- _state.label = 34;
985
- case 34:
1219
+ _state.label = 35;
1220
+ case 35:
986
1221
  _state.trys.push([
987
- 34,
988
- 36,
1222
+ 35,
1223
+ 38,
989
1224
  ,
990
- 37
1225
+ 39
991
1226
  ]);
992
1227
  return [
993
1228
  4,
994
- client.pollDeviceToken(device_code, 'dsp-cli')
995
- ];
996
- case 35:
997
- tokenResult = _state.sent();
998
- return [
999
- 3,
1000
- 37
1229
+ pollDeviceToken(client, device_code, 'dsp-cli', {
1230
+ intervalMs: (initialInterval || 5) * 1000,
1231
+ expiresAt: Date.now() + (expires_in || 600) * 1000
1232
+ })
1001
1233
  ];
1002
1234
  case 36:
1003
- unused3 = _state.sent();
1004
- // Transient network error — retry on next interval
1005
- return [
1006
- 3,
1007
- 32
1008
- ];
1009
- case 37:
1010
- if (!('access_token' in tokenResult)) return [
1011
- 3,
1012
- 39
1013
- ];
1235
+ token = _state.sent();
1014
1236
  console.log(' done');
1015
1237
  return [
1016
1238
  4,
1017
1239
  saveConfig({
1018
- token: tokenResult.access_token,
1240
+ token: token,
1019
1241
  apiUrl: apiUrl
1020
1242
  })
1021
1243
  ];
1022
- case 38:
1244
+ case 37:
1023
1245
  _state.sent();
1024
1246
  console.log("Logged in as ".concat(email));
1025
1247
  return [
1026
1248
  2
1027
1249
  ];
1028
- case 39:
1029
- _$err = tokenResult;
1030
- if (_$err.error === 'authorization_pending') {
1031
- return [
1032
- 3,
1033
- 32
1034
- ];
1035
- }
1036
- if (_$err.error === 'slow_down') {
1037
- interval += 5000;
1038
- return [
1039
- 3,
1040
- 32
1041
- ];
1042
- }
1043
- if (_$err.error === 'access_denied') {
1044
- console.log();
1250
+ case 38:
1251
+ err1 = _state.sent();
1252
+ console.log();
1253
+ if (_instanceof(err1, DeviceCodeDeniedError)) {
1045
1254
  console.error('Authentication denied. Run dsp login to try again.');
1046
- process.exit(1);
1047
- }
1048
- if (_$err.error === 'expired_token') {
1049
- console.log();
1255
+ } else if (_instanceof(err1, DeviceCodeExpiredError)) {
1050
1256
  console.error('Code expired. Run dsp login to try again.');
1051
- process.exit(1);
1257
+ } else if (_instanceof(err1, DeviceCodeFailedError)) {
1258
+ console.error(err1.message || 'Authentication failed. Run dsp login to try again.');
1259
+ } else {
1260
+ console.error('Authentication failed. Run dsp login to try again.');
1052
1261
  }
1053
- // Unknown error
1054
- console.log();
1055
- console.error(_$err.error_description || 'Authentication failed. Run dsp login to try again.');
1056
1262
  process.exit(1);
1057
1263
  return [
1058
1264
  3,
1059
- 32
1265
+ 39
1060
1266
  ];
1061
- case 40:
1267
+ case 39:
1062
1268
  return [
1063
1269
  2
1064
1270
  ];
@@ -1073,7 +1279,7 @@ program.command('branding <shortId> <mode>').description('Override display.dev b
1073
1279
  return _ts_generator(this, function(_state) {
1074
1280
  switch(_state.label){
1075
1281
  case 0:
1076
- mode = parseShowBrandingFlag(modeRaw);
1282
+ mode = parseShowBrandingOrExit(modeRaw);
1077
1283
  if (!mode) {
1078
1284
  console.error('mode is required');
1079
1285
  process.exit(1);
@@ -1107,7 +1313,7 @@ program.command('branding <shortId> <mode>').description('Override display.dev b
1107
1313
  case 4:
1108
1314
  err = _state.sent();
1109
1315
  msg = _instanceof(err, Error) ? err.message : String(err);
1110
- if (/paid plan|upgrade/i.test(msg)) {
1316
+ if (classifyBrandingError(msg) === 'paid-plan') {
1111
1317
  console.error('Error: Hiding display.dev branding requires a paid plan. See https://display.dev/billing');
1112
1318
  } else {
1113
1319
  console.error(msg);
@@ -1128,16 +1334,20 @@ program.command('branding <shortId> <mode>').description('Override display.dev b
1128
1334
  // --- mcp ---
1129
1335
  program.command('mcp').description('Start MCP server over stdin/stdout').action(function() {
1130
1336
  return _async_to_generator(function() {
1131
- var auth, client;
1337
+ var auth, client, publicClient;
1132
1338
  return _ts_generator(this, function(_state) {
1133
1339
  switch(_state.label){
1134
1340
  case 0:
1135
1341
  return [
1136
1342
  4,
1137
- resolveAuthOrConfig()
1343
+ resolveAuthOrConfigOptional()
1138
1344
  ];
1139
1345
  case 1:
1140
1346
  auth = _state.sent();
1347
+ if (!auth) return [
1348
+ 3,
1349
+ 3
1350
+ ];
1141
1351
  client = new ApiClient({
1142
1352
  baseUrl: auth.apiUrl,
1143
1353
  apiKey: auth.apiKey,
@@ -1152,6 +1362,28 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
1152
1362
  return [
1153
1363
  2
1154
1364
  ];
1365
+ case 3:
1366
+ // Unauthenticated: expose only `publish`, which maps to the public
1367
+ // claimable endpoint and returns a claim URL the user visits to own
1368
+ // the artifact. Workspace-bound tools are not registered — they have
1369
+ // no meaningful behaviour without auth and listing them would invite
1370
+ // agents to call them and error out.
1371
+ publicClient = new ApiClient({
1372
+ baseUrl: resolvePublicApiUrl(),
1373
+ apiKey: '',
1374
+ clientType: 'mcp-stdio'
1375
+ });
1376
+ return [
1377
+ 4,
1378
+ startMcpServer(publicClient, {
1379
+ mode: 'public'
1380
+ })
1381
+ ];
1382
+ case 4:
1383
+ _state.sent();
1384
+ return [
1385
+ 2
1386
+ ];
1155
1387
  }
1156
1388
  });
1157
1389
  })();