@displaydev/cli 0.19.0 → 0.21.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.
@@ -1,7 +1,4 @@
1
- /**
2
- * HTTP client for the display.dev API.
3
- * Used by the stdio MCP server to proxy tool calls to the REST API.
4
- */ function _assert_this_initialized(self) {
1
+ function _assert_this_initialized(self) {
5
2
  if (self === void 0) {
6
3
  throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
7
4
  }
@@ -284,6 +281,10 @@ function _ts_generator(thisArg, body) {
284
281
  };
285
282
  }
286
283
  }
284
+ /**
285
+ * HTTP client for the display.dev API.
286
+ * Used by the stdio MCP server to proxy tool calls to the REST API.
287
+ */ import { readLatestVersionFromResponse } from './update-notice.js';
287
288
  export var ApiError = /*#__PURE__*/ function(Error1) {
288
289
  "use strict";
289
290
  _inherits(ApiError, Error1);
@@ -311,15 +312,50 @@ export var ApiClient = /*#__PURE__*/ function() {
311
312
  "use strict";
312
313
  function ApiClient(config) {
313
314
  _class_call_check(this, ApiClient);
314
- var _config_clientType;
315
+ var _config_clientType, _config_version, _config_clientSource;
315
316
  _define_property(this, "baseUrl", void 0);
316
317
  _define_property(this, "apiKey", void 0);
317
318
  _define_property(this, "clientType", void 0);
319
+ _define_property(this, "version", void 0);
320
+ _define_property(this, "clientSource", void 0);
318
321
  this.baseUrl = config.baseUrl.replace(/\/$/, '');
319
322
  this.apiKey = config.apiKey;
320
323
  this.clientType = (_config_clientType = config.clientType) !== null && _config_clientType !== void 0 ? _config_clientType : 'mcp-stdio';
324
+ this.version = (_config_version = config.version) !== null && _config_version !== void 0 ? _config_version : '';
325
+ this.clientSource = (_config_clientSource = config.clientSource) !== null && _config_clientSource !== void 0 ? _config_clientSource : '';
321
326
  }
322
327
  _create_class(ApiClient, [
328
+ {
329
+ key: "clientHeaders",
330
+ value: /**
331
+ * Build the request-side header set. Always carries `X-Client-Type`;
332
+ * adds `X-Client-Version` when the client was constructed with one;
333
+ * adds `X-Client-Source` when a distribution-channel attribution was
334
+ * configured.
335
+ */ function clientHeaders(extra) {
336
+ var headers = _object_spread({
337
+ 'X-Client-Type': this.clientType
338
+ }, this.version ? {
339
+ 'X-Client-Version': this.version
340
+ } : {}, this.clientSource ? {
341
+ 'X-Client-Source': this.clientSource
342
+ } : {}, extra);
343
+ return headers;
344
+ }
345
+ },
346
+ {
347
+ key: "observeResponse",
348
+ value: /**
349
+ * Read the registry-derived `X-Client-Latest-Version` response header
350
+ * and feed it to the update-notice comparator. Called from every
351
+ * fetch site. No-op when no version is configured (older constructions
352
+ * that didn't pass `version` should not trigger an update prompt).
353
+ */ function observeResponse(res) {
354
+ if (this.version) {
355
+ readLatestVersionFromResponse(res, this.version);
356
+ }
357
+ }
358
+ },
323
359
  {
324
360
  key: "publish",
325
361
  value: function publish(params) {
@@ -355,9 +391,7 @@ export var ApiClient = /*#__PURE__*/ function() {
355
391
  }
356
392
  return [
357
393
  2,
358
- this.doFetch('POST', '/v1/public/artifacts', form, {
359
- 'X-Client-Type': this.clientType
360
- })
394
+ this.doFetch('POST', '/v1/public/artifacts', form, this.clientHeaders())
361
395
  ];
362
396
  });
363
397
  }).call(this);
@@ -804,14 +838,14 @@ export var ApiClient = /*#__PURE__*/ function() {
804
838
  4,
805
839
  fetch(url, {
806
840
  method: 'GET',
807
- headers: {
808
- 'Authorization': "Bearer ".concat(this.apiKey),
809
- 'X-Client-Type': this.clientType
810
- }
841
+ headers: this.clientHeaders({
842
+ Authorization: "Bearer ".concat(this.apiKey)
843
+ })
811
844
  })
812
845
  ];
813
846
  case 1:
814
847
  res = _state.sent();
848
+ this.observeResponse(res);
815
849
  if (!!res.ok) return [
816
850
  3,
817
851
  3
@@ -961,10 +995,9 @@ export var ApiClient = /*#__PURE__*/ function() {
961
995
  return [
962
996
  4,
963
997
  fetch("".concat(this.baseUrl, "/v1/artifacts?limit=1"), {
964
- headers: {
965
- 'Authorization': "Bearer ".concat(apiKey),
966
- 'X-Client-Type': this.clientType
967
- }
998
+ headers: this.clientHeaders({
999
+ Authorization: "Bearer ".concat(apiKey)
1000
+ })
968
1001
  })
969
1002
  ];
970
1003
  case 1:
@@ -980,6 +1013,7 @@ export var ApiClient = /*#__PURE__*/ function() {
980
1013
  'network_error'
981
1014
  ];
982
1015
  case 3:
1016
+ this.observeResponse(res);
983
1017
  if (res.ok) {
984
1018
  return [
985
1019
  2,
@@ -1008,10 +1042,9 @@ export var ApiClient = /*#__PURE__*/ function() {
1008
1042
  return _ts_generator(this, function(_state) {
1009
1043
  return [
1010
1044
  2,
1011
- this.doFetch(method, path, body, {
1012
- 'Authorization': "Bearer ".concat(this.apiKey),
1013
- 'X-Client-Type': this.clientType
1014
- })
1045
+ this.doFetch(method, path, body, this.clientHeaders({
1046
+ Authorization: "Bearer ".concat(this.apiKey)
1047
+ }))
1015
1048
  ];
1016
1049
  });
1017
1050
  }).call(this);
@@ -1024,9 +1057,7 @@ export var ApiClient = /*#__PURE__*/ function() {
1024
1057
  return _ts_generator(this, function(_state) {
1025
1058
  return [
1026
1059
  2,
1027
- this.doFetch(method, path, body, {
1028
- 'X-Client-Type': this.clientType
1029
- })
1060
+ this.doFetch(method, path, body, this.clientHeaders())
1030
1061
  ];
1031
1062
  });
1032
1063
  }).call(this);
@@ -1045,15 +1076,15 @@ export var ApiClient = /*#__PURE__*/ function() {
1045
1076
  4,
1046
1077
  fetch(url, {
1047
1078
  method: method,
1048
- headers: {
1049
- 'Content-Type': 'application/json',
1050
- 'X-Client-Type': this.clientType
1051
- },
1079
+ headers: this.clientHeaders({
1080
+ 'Content-Type': 'application/json'
1081
+ }),
1052
1082
  body: body ? JSON.stringify(body) : undefined
1053
1083
  })
1054
1084
  ];
1055
1085
  case 1:
1056
1086
  res = _state.sent();
1087
+ this.observeResponse(res);
1057
1088
  return [
1058
1089
  2,
1059
1090
  res.json()
@@ -1088,6 +1119,7 @@ export var ApiClient = /*#__PURE__*/ function() {
1088
1119
  ];
1089
1120
  case 1:
1090
1121
  res = _state.sent();
1122
+ this.observeResponse(res);
1091
1123
  if (!!res.ok) return [
1092
1124
  3,
1093
1125
  3
package/dist/main.js CHANGED
@@ -198,9 +198,26 @@ import { Command } from 'commander';
198
198
  import { ApiClient, ApiError } from './api-client.js';
199
199
  import { loadConfig, saveConfig } from './config.js';
200
200
  import { startMcpServer } from './mcp-server.js';
201
+ import { emitOnExit, setCurrentVersion, setMcpMode } from './update-notice.js';
201
202
  import { DEFAULT_API_URL, DeviceCodeDeniedError, DeviceCodeExpiredError, DeviceCodeFailedError, InvalidFlagError, PublishArgsError, classifyBrandingError, parseShortIdAndVersion, parseShowBrandingFlag, pollDeviceToken, readApiKeyFromTty, readStreamToString, resolveAuth as resolveAuthFromEnvAndConfig, validatePublishArgs } from './main-helpers.js';
202
203
  var require = createRequire(import.meta.url);
203
204
  var version = require('../package.json').version;
205
+ // Update-notice scaffolding (spec/feat-cli-update-notification.md). The
206
+ // `'exit'` hook fires on every termination path — natural completion,
207
+ // `process.exit(N)`, uncaught throws — so the notice lands regardless of
208
+ // which exit path the command took. `'beforeExit'` would not work; it
209
+ // does not fire when `process.exit()` is called explicitly.
210
+ //
211
+ // Process-level idempotency: keyed on a Symbol.for so `vi.resetModules()`
212
+ // + dynamic re-import in tests does not stack a second listener (each
213
+ // re-import would otherwise re-run this top-level statement).
214
+ setCurrentVersion(version);
215
+ var EXIT_HOOK_FLAG = Symbol.for('@displaydev/cli.exit-hook-registered');
216
+ var flagged = process;
217
+ if (!flagged[EXIT_HOOK_FLAG]) {
218
+ flagged[EXIT_HOOK_FLAG] = true;
219
+ process.on('exit', emitOnExit);
220
+ }
204
221
  function resolveAuthOrConfig() {
205
222
  return _async_to_generator(function() {
206
223
  var auth;
@@ -256,11 +273,50 @@ function resolvePublicApiUrl() {
256
273
  var _process_env_DISPLAYDEV_API_URL;
257
274
  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;
258
275
  }
276
+ /**
277
+ * Distribution-channel attribution stash. `--client-source <name>` is
278
+ * registered on `program` and on every subcommand in the tree (see
279
+ * `addClientSourceOptionRecursive` at the bottom of this file) and a
280
+ * `preAction` hook stashes the parsed value before any action runs.
281
+ * `createClient` and the two unauthenticated `new ApiClient(...)` sites
282
+ * then read the stash via `resolveClientSource()`.
283
+ *
284
+ * Why a global stash instead of threading through every action: there
285
+ * are ~20 direct-HTTP subcommands and a single resolution path; passing
286
+ * the value down through each action's opts type would touch every
287
+ * action handler for a flag that is structurally cross-cutting.
288
+ *
289
+ * The flag is also registered on `mcp` so `dsp mcp --client-source foo`
290
+ * is silently accepted (per spec §1 out-of-scope). The stash gets
291
+ * populated, but the mcp action builds its `ApiClient` without reading
292
+ * the stash, so the value never reaches the spawned MCP server's
293
+ * outgoing requests.
294
+ *
295
+ * Precedence: flag > `DISPLAYDEV_CLIENT_SOURCE` env > omitted. An
296
+ * explicitly empty flag (`--client-source=`) is treated as the user
297
+ * suppressing attribution: the env fallback is skipped and the header
298
+ * is omitted. The header is analytics-only; it does not affect audit /
299
+ * share-notification source normalization (which keys off
300
+ * `X-Client-Type`).
301
+ */ var clientSourceFromFlag;
302
+ var clientSourceFlagPassed = false;
303
+ function resolveClientSource() {
304
+ if (clientSourceFlagPassed) {
305
+ return clientSourceFromFlag;
306
+ }
307
+ var env = process.env.DISPLAYDEV_CLIENT_SOURCE;
308
+ if (typeof env === 'string' && env.length > 0) {
309
+ return env;
310
+ }
311
+ return undefined;
312
+ }
259
313
  function createClient(auth) {
260
314
  return new ApiClient({
261
315
  baseUrl: auth.apiUrl,
262
316
  apiKey: auth.apiKey,
263
- clientType: 'cli'
317
+ clientType: 'cli',
318
+ version: version,
319
+ clientSource: resolveClientSource()
264
320
  });
265
321
  }
266
322
  var program = new Command().name('dsp').description('display.dev CLI — publish artifacts behind company auth').version(version);
@@ -407,7 +463,9 @@ program.command('publish <path>').description('Publish an HTML or Markdown file.
407
463
  publicClient = new ApiClient({
408
464
  baseUrl: resolvePublicApiUrl(),
409
465
  apiKey: '',
410
- clientType: 'cli'
466
+ clientType: 'cli',
467
+ version: version,
468
+ clientSource: resolveClientSource()
411
469
  });
412
470
  _state.label = 10;
413
471
  case 10:
@@ -1074,7 +1132,9 @@ program.command('login').description('Authenticate with display.dev').option('--
1074
1132
  client = new ApiClient({
1075
1133
  baseUrl: apiUrl,
1076
1134
  apiKey: '',
1077
- clientType: 'cli'
1135
+ clientType: 'cli',
1136
+ version: version,
1137
+ clientSource: resolveClientSource()
1078
1138
  });
1079
1139
  if (!(opts.apiKey !== undefined)) return [
1080
1140
  3,
@@ -2247,6 +2307,7 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
2247
2307
  ];
2248
2308
  case 1:
2249
2309
  auth = _state.sent();
2310
+ setMcpMode();
2250
2311
  if (!auth) return [
2251
2312
  3,
2252
2313
  3
@@ -2254,7 +2315,8 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
2254
2315
  client = new ApiClient({
2255
2316
  baseUrl: auth.apiUrl,
2256
2317
  apiKey: auth.apiKey,
2257
- clientType: 'mcp-stdio'
2318
+ clientType: 'mcp-stdio',
2319
+ version: version
2258
2320
  });
2259
2321
  return [
2260
2322
  4,
@@ -2274,7 +2336,8 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
2274
2336
  publicClient = new ApiClient({
2275
2337
  baseUrl: resolvePublicApiUrl(),
2276
2338
  apiKey: '',
2277
- clientType: 'mcp-stdio'
2339
+ clientType: 'mcp-stdio',
2340
+ version: version
2278
2341
  });
2279
2342
  return [
2280
2343
  4,
@@ -2291,4 +2354,61 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
2291
2354
  });
2292
2355
  })();
2293
2356
  });
2357
+ /**
2358
+ * Register `--client-source <name>` on `program` AND every command in
2359
+ * the tree (top-level + nested under `comment` / `thread` + `mcp`).
2360
+ * This makes the flag parseable at any level — `dsp --client-source
2361
+ * foo publish ...`, `dsp publish --client-source foo ...`,
2362
+ * `dsp comment --client-source foo add ...`, etc. — so the skill
2363
+ * helpers (per spec §4.2 / §4.3 / §4.4) which exec `dsp publish
2364
+ * --client-source … "$@"` work without per-command duplication of
2365
+ * the action signature.
2366
+ *
2367
+ * `mcp` is included so the flag is silently accepted there too. The
2368
+ * mcp action does not call `resolveClientSource()`, so the stashed
2369
+ * value never reaches the spawned MCP server's `ApiClient` (spec §1
2370
+ * out-of-scope).
2371
+ */ function addClientSourceOptionRecursive(cmd) {
2372
+ cmd.option('--client-source <name>', 'Distribution channel attribution (sent as X-Client-Source header for funnel analytics)');
2373
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2374
+ try {
2375
+ for(var _iterator = cmd.commands[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2376
+ var child = _step.value;
2377
+ addClientSourceOptionRecursive(child);
2378
+ }
2379
+ } catch (err) {
2380
+ _didIteratorError = true;
2381
+ _iteratorError = err;
2382
+ } finally{
2383
+ try {
2384
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2385
+ _iterator.return();
2386
+ }
2387
+ } finally{
2388
+ if (_didIteratorError) {
2389
+ throw _iteratorError;
2390
+ }
2391
+ }
2392
+ }
2393
+ }
2394
+ addClientSourceOptionRecursive(program);
2395
+ /**
2396
+ * Resolve the parsed flag value before each action. Program-level
2397
+ * `preAction` hooks fire ahead of every subcommand action, so a single
2398
+ * registration is enough — the callback walks the active command up
2399
+ * through its parents and stops at the first command whose `opts()`
2400
+ * has a string-typed `clientSource` (distinguishing "not passed"
2401
+ * `undefined` from "explicit blank" `''`).
2402
+ */ program.hook('preAction', function(_thisCommand, actionCommand) {
2403
+ var cursor = actionCommand;
2404
+ while(cursor){
2405
+ var val = cursor.opts().clientSource;
2406
+ if (typeof val === 'string') {
2407
+ clientSourceFlagPassed = true;
2408
+ clientSourceFromFlag = val.length > 0 ? val : undefined;
2409
+ return;
2410
+ }
2411
+ cursor = cursor.parent;
2412
+ }
2413
+ });
2294
2414
  program.parse();
@@ -192,6 +192,7 @@ import { z } from 'zod';
192
192
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
193
193
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
194
194
  import { ApiError } from './api-client.js';
195
+ import { withUpdateNotice } from './update-notice.js';
195
196
  var FILE_ERROR_CODES = new Set([
196
197
  'ENOENT',
197
198
  'EACCES',
@@ -313,7 +314,7 @@ export function registerPublicTools(server, api) {
313
314
  'html',
314
315
  'md'
315
316
  ]).default('html').describe('Content format')
316
- }, function(args) {
317
+ }, withUpdateNotice(function(args) {
317
318
  return _async_to_generator(function() {
318
319
  var hasContent, hasFilePath, content, _tmp, result, err;
319
320
  return _ts_generator(this, function(_state) {
@@ -409,7 +410,7 @@ export function registerPublicTools(server, api) {
409
410
  }
410
411
  });
411
412
  })();
412
- });
413
+ }));
413
414
  }
414
415
  export function registerTools(server, api) {
415
416
  server.tool('publish', 'Publish an HTML or Markdown artifact behind company auth', {
@@ -433,7 +434,7 @@ export function registerTools(server, api) {
433
434
  'hide',
434
435
  'inherit'
435
436
  ]).optional().describe('display.dev attribution bar override. Paid tier only; defaults to org setting when omitted.')
436
- }, function(args) {
437
+ }, withUpdateNotice(function(args) {
437
438
  return _async_to_generator(function() {
438
439
  var _args_name, hasContent, hasFilePath, _args_name1, content, _tmp, result, _tmp1, err;
439
440
  return _ts_generator(this, function(_state) {
@@ -611,7 +612,7 @@ export function registerTools(server, api) {
611
612
  }
612
613
  });
613
614
  })();
614
- });
615
+ }));
615
616
  server.tool('find', 'List or search artifacts in the caller\'s organization. Omit `query` to list every artifact (paginated). Filter by name, one-or-more authors, one-or-more visibilities, or update time. Sort by `updated_at` (default), `view_count`, or `name`. Paginate via `cursor` — if the result includes a non-null `nextCursor`, pass it back to fetch the next page; if filters change between calls, drop the cursor. The response also carries `totalCount` (`-1` means the count query exceeded its budget — treat as "many"). `visibility: ["private"]` returns only your own private artifacts, not org-wide.', {
616
617
  query: z.string().optional().describe('Search by artifact name (case-insensitive substring). Omit to list all artifacts.'),
617
618
  author: z.array(z.string()).optional().describe('Filter by one or more authors. Each value: email, userId, or the literal "me". Multiple values are ORed. "me" requires a user-scoped credential — with an org API key the wire drops it.'),
@@ -632,7 +633,7 @@ export function registerTools(server, api) {
632
633
  ]).optional().describe('Sort direction. Defaults: desc for updated_at and view_count, asc for name.'),
633
634
  cursor: z.string().optional().describe('Opaque pagination token from a prior response. Pass it back to fetch the next page. WARNING: only valid for the same `sort`/`dir`/filter set that issued it — change a filter and drop the cursor, or results will be skewed without an error.'),
634
635
  limit: z.number().int().min(1).max(100).optional().describe('Max results per page (1–100, default 50).')
635
- }, function(args) {
636
+ }, withUpdateNotice(function(args) {
636
637
  return _async_to_generator(function() {
637
638
  var result, err;
638
639
  return _ts_generator(this, function(_state) {
@@ -667,11 +668,11 @@ export function registerTools(server, api) {
667
668
  }
668
669
  });
669
670
  })();
670
- });
671
+ }));
671
672
  server.tool('get', 'Get full details of a specific artifact', {
672
673
  short_id: z.string().describe('Artifact shortId'),
673
674
  include: z.array(z.string()).optional().describe('Include additional data (e.g. "versions")')
674
- }, function(args) {
675
+ }, withUpdateNotice(function(args) {
675
676
  return _async_to_generator(function() {
676
677
  var result, err;
677
678
  return _ts_generator(this, function(_state) {
@@ -706,11 +707,11 @@ export function registerTools(server, api) {
706
707
  }
707
708
  });
708
709
  })();
709
- });
710
+ }));
710
711
  server.tool('delete', 'Delete an artifact permanently', {
711
712
  short_id: z.string().describe('Artifact shortId to delete'),
712
713
  confirm: z.boolean().describe('Must be true to confirm deletion')
713
- }, function(args) {
714
+ }, withUpdateNotice(function(args) {
714
715
  return _async_to_generator(function() {
715
716
  var result, err;
716
717
  return _ts_generator(this, function(_state) {
@@ -764,7 +765,7 @@ export function registerTools(server, api) {
764
765
  }
765
766
  });
766
767
  })();
767
- });
768
+ }));
768
769
  server.tool('share', 'Change an artifact\'s visibility and/or add/remove individual shared-with emails without republishing.', {
769
770
  short_id: z.string().describe('Short ID of the artifact (8 chars).'),
770
771
  visibility: z.enum([
@@ -774,7 +775,7 @@ export function registerTools(server, api) {
774
775
  ]).optional().describe('Change the visibility level. Omit to keep current. "private" requires the Pro plan.'),
775
776
  add_users: z.array(z.string()).optional().describe('Emails to add to sharedWith (idempotent).'),
776
777
  remove_users: z.array(z.string()).optional().describe('Emails to remove from sharedWith (idempotent).')
777
- }, function(args) {
778
+ }, withUpdateNotice(function(args) {
778
779
  return _async_to_generator(function() {
779
780
  var result, err;
780
781
  return _ts_generator(this, function(_state) {
@@ -832,11 +833,11 @@ export function registerTools(server, api) {
832
833
  }
833
834
  });
834
835
  })();
835
- });
836
+ }));
836
837
  server.tool('rename', 'Rename a published artifact. The URL slug updates to match; existing URLs still resolve because routing uses the shortId.', {
837
838
  short_id: z.string().describe('The 8-character shortId returned at publish.'),
838
839
  name: z.string().describe('New display name. 1–200 characters.')
839
- }, function(args) {
840
+ }, withUpdateNotice(function(args) {
840
841
  return _async_to_generator(function() {
841
842
  var result, err;
842
843
  return _ts_generator(this, function(_state) {
@@ -875,7 +876,7 @@ export function registerTools(server, api) {
875
876
  }
876
877
  });
877
878
  })();
878
- });
879
+ }));
879
880
  server.tool('export', 'Retrieve the source bytes of a published artifact. `format: "original"` returns the publisher\'s upload (markdown for .md, HTML for .html). `format: "markdown"` always returns markdown — `.md` is unchanged, `.html` is converted via Workers AI (cached).', {
880
881
  short_id: z.string().describe('Short ID of the artifact (8 chars).'),
881
882
  version: z.number().int().min(1).optional().describe('Pinned version number. Omit for current.'),
@@ -883,7 +884,7 @@ export function registerTools(server, api) {
883
884
  'original',
884
885
  'markdown'
885
886
  ]).default('original').describe('Representation requested. Default "original".')
886
- }, function(args) {
887
+ }, withUpdateNotice(function(args) {
887
888
  return _async_to_generator(function() {
888
889
  var result, err;
889
890
  return _ts_generator(this, function(_state) {
@@ -931,7 +932,7 @@ export function registerTools(server, api) {
931
932
  }
932
933
  });
933
934
  })();
934
- });
935
+ }));
935
936
  // Metadata-only: does NOT bump the artifact version. Use the `publish`
936
937
  // tool (with `show_branding`) when you're changing content at the same
937
938
  // time so both writes happen in one transaction.
@@ -942,7 +943,7 @@ export function registerTools(server, api) {
942
943
  'hide',
943
944
  'inherit'
944
945
  ]).describe('show = force branding on; hide = force off; inherit = follow org default')
945
- }, function(args) {
946
+ }, withUpdateNotice(function(args) {
946
947
  return _async_to_generator(function() {
947
948
  var result, err;
948
949
  return _ts_generator(this, function(_state) {
@@ -977,14 +978,14 @@ export function registerTools(server, api) {
977
978
  }
978
979
  });
979
980
  })();
980
- });
981
+ }));
981
982
  server.tool('set_logo', 'Upload or replace the org logo (paid tiers only). Used as the favicon on every artifact the org publishes. Stdio variant reads bytes from a local file path.', {
982
983
  file_path: z.string().describe('Path to a local PNG or WebP file. Square, 32×32 to 1024×1024, ≤256 KB.'),
983
984
  content_type: z.enum([
984
985
  'image/png',
985
986
  'image/webp'
986
987
  ]).optional().describe('Optional declared MIME type; inferred from the file extension when omitted.')
987
- }, function(args) {
988
+ }, withUpdateNotice(function(args) {
988
989
  return _async_to_generator(function() {
989
990
  var bytes, err, contentType, lower, result, err1;
990
991
  return _ts_generator(this, function(_state) {
@@ -1073,8 +1074,8 @@ export function registerTools(server, api) {
1073
1074
  }
1074
1075
  });
1075
1076
  })();
1076
- });
1077
- server.tool('clear_logo', 'Remove the org logo. Idempotent — succeeds even when no logo is currently set.', {}, function() {
1077
+ }));
1078
+ server.tool('clear_logo', 'Remove the org logo. Idempotent — succeeds even when no logo is currently set.', {}, withUpdateNotice(function() {
1078
1079
  return _async_to_generator(function() {
1079
1080
  var err;
1080
1081
  return _ts_generator(this, function(_state) {
@@ -1111,7 +1112,7 @@ export function registerTools(server, api) {
1111
1112
  }
1112
1113
  });
1113
1114
  })();
1114
- });
1115
+ }));
1115
1116
  registerCommentTools(server, api);
1116
1117
  }
1117
1118
  /**
@@ -1179,7 +1180,7 @@ export function registerTools(server, api) {
1179
1180
  body: z.string().min(1).max(10000).describe('Comment body (≤10k chars)'),
1180
1181
  parent_id: z.string().optional().describe('Root comment id when posting a reply'),
1181
1182
  anchor: anchorSchema
1182
- }), function(args) {
1183
+ }), withUpdateNotice(function(args) {
1183
1184
  return _async_to_generator(function() {
1184
1185
  var shortIdOrErr, result, err;
1185
1186
  return _ts_generator(this, function(_state) {
@@ -1233,7 +1234,7 @@ export function registerTools(server, api) {
1233
1234
  }
1234
1235
  });
1235
1236
  })();
1236
- });
1237
+ }));
1237
1238
  server.tool('list_comments', 'List comment threads on a subject. Reads from Postgres (not the KV widget cache).', _object_spread_props(_object_spread({}, subjectArgs), {
1238
1239
  status: z.enum([
1239
1240
  'open',
@@ -1241,7 +1242,7 @@ export function registerTools(server, api) {
1241
1242
  'all'
1242
1243
  ]).optional().describe('Defaults to "open"'),
1243
1244
  since: z.string().optional().describe('ISO-8601 — return only threads with activity at or after this timestamp')
1244
- }), function(args) {
1245
+ }), withUpdateNotice(function(args) {
1245
1246
  return _async_to_generator(function() {
1246
1247
  var shortIdOrErr, result, err;
1247
1248
  return _ts_generator(this, function(_state) {
@@ -1294,11 +1295,11 @@ export function registerTools(server, api) {
1294
1295
  }
1295
1296
  });
1296
1297
  })();
1297
- });
1298
+ }));
1298
1299
  server.tool('edit_comment', 'Edit your own comment within the 5-minute author window. After 5 minutes the body is locked.', {
1299
1300
  comment_id: z.string(),
1300
1301
  body: z.string().min(1).max(10000)
1301
- }, function(args) {
1302
+ }, withUpdateNotice(function(args) {
1302
1303
  return _async_to_generator(function() {
1303
1304
  var result, err;
1304
1305
  return _ts_generator(this, function(_state) {
@@ -1333,10 +1334,10 @@ export function registerTools(server, api) {
1333
1334
  }
1334
1335
  });
1335
1336
  })();
1336
- });
1337
+ }));
1337
1338
  server.tool('delete_comment', 'Soft-delete a comment. Author / artifact creator / org admin. Replies remain visible; body becomes "[deleted]".', {
1338
1339
  comment_id: z.string()
1339
- }, function(args) {
1340
+ }, withUpdateNotice(function(args) {
1340
1341
  return _async_to_generator(function() {
1341
1342
  var err;
1342
1343
  return _ts_generator(this, function(_state) {
@@ -1373,10 +1374,10 @@ export function registerTools(server, api) {
1373
1374
  }
1374
1375
  });
1375
1376
  })();
1376
- });
1377
+ }));
1377
1378
  server.tool('resolve_thread', 'Mark a thread resolved (root comment id). Thread participant / artifact creator / org admin.', {
1378
1379
  comment_id: z.string()
1379
- }, function(args) {
1380
+ }, withUpdateNotice(function(args) {
1380
1381
  return _async_to_generator(function() {
1381
1382
  var result, err;
1382
1383
  return _ts_generator(this, function(_state) {
@@ -1411,10 +1412,10 @@ export function registerTools(server, api) {
1411
1412
  }
1412
1413
  });
1413
1414
  })();
1414
- });
1415
+ }));
1415
1416
  server.tool('reopen_thread', 'Reopen a resolved thread. Any authenticated user with view access on the artifact.', {
1416
1417
  comment_id: z.string()
1417
- }, function(args) {
1418
+ }, withUpdateNotice(function(args) {
1418
1419
  return _async_to_generator(function() {
1419
1420
  var result, err;
1420
1421
  return _ts_generator(this, function(_state) {
@@ -1449,8 +1450,8 @@ export function registerTools(server, api) {
1449
1450
  }
1450
1451
  });
1451
1452
  })();
1452
- });
1453
- server.tool('watch', 'Watch the subject for comment notifications. Per-user; service-account API keys cannot subscribe.', subjectArgs, function(args) {
1453
+ }));
1454
+ server.tool('watch', 'Watch the subject for comment notifications. Per-user; service-account API keys cannot subscribe.', subjectArgs, withUpdateNotice(function(args) {
1454
1455
  return _async_to_generator(function() {
1455
1456
  var shortIdOrErr, result, err;
1456
1457
  return _ts_generator(this, function(_state) {
@@ -1500,8 +1501,8 @@ export function registerTools(server, api) {
1500
1501
  }
1501
1502
  });
1502
1503
  })();
1503
- });
1504
- server.tool('unwatch', 'Unwatch the subject. Per-user; service-account API keys cannot subscribe.', subjectArgs, function(args) {
1504
+ }));
1505
+ server.tool('unwatch', 'Unwatch the subject. Per-user; service-account API keys cannot subscribe.', subjectArgs, withUpdateNotice(function(args) {
1505
1506
  return _async_to_generator(function() {
1506
1507
  var shortIdOrErr, err;
1507
1508
  return _ts_generator(this, function(_state) {
@@ -1553,5 +1554,5 @@ export function registerTools(server, api) {
1553
1554
  }
1554
1555
  });
1555
1556
  })();
1556
- });
1557
+ }));
1557
1558
  }
@@ -0,0 +1,488 @@
1
+ function _array_like_to_array(arr, len) {
2
+ if (len == null || len > arr.length) len = arr.length;
3
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
4
+ return arr2;
5
+ }
6
+ function _array_without_holes(arr) {
7
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
8
+ }
9
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
10
+ try {
11
+ var info = gen[key](arg);
12
+ var value = info.value;
13
+ } catch (error) {
14
+ reject(error);
15
+ return;
16
+ }
17
+ if (info.done) {
18
+ resolve(value);
19
+ } else {
20
+ Promise.resolve(value).then(_next, _throw);
21
+ }
22
+ }
23
+ function _async_to_generator(fn) {
24
+ return function() {
25
+ var self = this, args = arguments;
26
+ return new Promise(function(resolve, reject) {
27
+ var gen = fn.apply(self, args);
28
+ function _next(value) {
29
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
30
+ }
31
+ function _throw(err) {
32
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
33
+ }
34
+ _next(undefined);
35
+ });
36
+ };
37
+ }
38
+ function _iterable_to_array(iter) {
39
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
40
+ }
41
+ function _non_iterable_spread() {
42
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
43
+ }
44
+ function _to_consumable_array(arr) {
45
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
46
+ }
47
+ function _type_of(obj) {
48
+ "@swc/helpers - typeof";
49
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
50
+ }
51
+ function _unsupported_iterable_to_array(o, minLen) {
52
+ if (!o) return;
53
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
54
+ var n = Object.prototype.toString.call(o).slice(8, -1);
55
+ if (n === "Object" && o.constructor) n = o.constructor.name;
56
+ if (n === "Map" || n === "Set") return Array.from(n);
57
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
58
+ }
59
+ function _ts_generator(thisArg, body) {
60
+ var f, y, t, _ = {
61
+ label: 0,
62
+ sent: function() {
63
+ if (t[0] & 1) throw t[1];
64
+ return t[1];
65
+ },
66
+ trys: [],
67
+ ops: []
68
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
69
+ return d(g, "next", {
70
+ value: verb(0)
71
+ }), d(g, "throw", {
72
+ value: verb(1)
73
+ }), d(g, "return", {
74
+ value: verb(2)
75
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
76
+ value: function() {
77
+ return this;
78
+ }
79
+ }), g;
80
+ function verb(n) {
81
+ return function(v) {
82
+ return step([
83
+ n,
84
+ v
85
+ ]);
86
+ };
87
+ }
88
+ function step(op) {
89
+ if (f) throw new TypeError("Generator is already executing.");
90
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
91
+ 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;
92
+ if (y = 0, t) op = [
93
+ op[0] & 2,
94
+ t.value
95
+ ];
96
+ switch(op[0]){
97
+ case 0:
98
+ case 1:
99
+ t = op;
100
+ break;
101
+ case 4:
102
+ _.label++;
103
+ return {
104
+ value: op[1],
105
+ done: false
106
+ };
107
+ case 5:
108
+ _.label++;
109
+ y = op[1];
110
+ op = [
111
+ 0
112
+ ];
113
+ continue;
114
+ case 7:
115
+ op = _.ops.pop();
116
+ _.trys.pop();
117
+ continue;
118
+ default:
119
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
120
+ _ = 0;
121
+ continue;
122
+ }
123
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
124
+ _.label = op[1];
125
+ break;
126
+ }
127
+ if (op[0] === 6 && _.label < t[1]) {
128
+ _.label = t[1];
129
+ t = op;
130
+ break;
131
+ }
132
+ if (t && _.label < t[2]) {
133
+ _.label = t[2];
134
+ _.ops.push(op);
135
+ break;
136
+ }
137
+ if (t[2]) _.ops.pop();
138
+ _.trys.pop();
139
+ continue;
140
+ }
141
+ op = body.call(thisArg, _);
142
+ } catch (e) {
143
+ op = [
144
+ 6,
145
+ e
146
+ ];
147
+ y = 0;
148
+ } finally{
149
+ f = t = 0;
150
+ }
151
+ if (op[0] & 5) throw op[1];
152
+ return {
153
+ value: op[0] ? op[1] : void 0,
154
+ done: true
155
+ };
156
+ }
157
+ }
158
+ /**
159
+ * Update-notification surface for `@displaydev/cli`. Two paths consume it:
160
+ * plain CLI (stderr block printed from a `process.on('exit', ...)` hook)
161
+ * and MCP stdio (content block appended to the next `tools/call` result).
162
+ *
163
+ * Module-level state (`latestVersion`, `injectedThisProcess`,
164
+ * `currentVersion`) is intentional. Tests reset between cases via
165
+ * `vi.resetModules()` + dynamic re-import — there is no exported
166
+ * test-only reset.
167
+ */ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
168
+ import { homedir } from 'node:os';
169
+ import { dirname, join } from 'node:path';
170
+ export var PACKAGE_NAME = '@displaydev/cli';
171
+ var NOTICE_PREFIX = '[displaydev]';
172
+ var THROTTLE_MS = 24 * 60 * 60 * 1000;
173
+ var latestVersion = null;
174
+ var injectedThisProcess = false;
175
+ var currentVersion = null;
176
+ var mcpMode = false;
177
+ /**
178
+ * Stamp the running CLI's version. main.ts calls this once at module
179
+ * scope; the value is read by `emitOnExit` and `injectIntoToolResult`
180
+ * when they format the notice text.
181
+ */ export function setCurrentVersion(version) {
182
+ currentVersion = version;
183
+ }
184
+ /**
185
+ * Mark this process as a long-running MCP-stdio server. main.ts calls
186
+ * this from the `mcp` subcommand handler. Once set, `emitOnExit` skips —
187
+ * MCP mode surfaces the notice via `injectIntoToolResult`, and the
188
+ * stderr fallback would (a) write the wrong text variant ("npm i -g"
189
+ * vs. "bump the host config") and (b) reach nowhere useful since stdio
190
+ * MCP hosts do not surface stderr to the agent.
191
+ */ export function setMcpMode() {
192
+ mcpMode = true;
193
+ }
194
+ /** Mark the CLI outdated, capturing the registry's `latest` value. */ export function markOutdated(version) {
195
+ latestVersion = version;
196
+ }
197
+ /** Read the most recently observed `latest`, or null. */ export function getLatestVersion() {
198
+ return latestVersion;
199
+ }
200
+ function statePath() {
201
+ return join(homedir(), '.displaydev', 'update-state.json');
202
+ }
203
+ /** Read the per-machine throttle state. Missing or corrupt → empty. */ export function loadUpdateState() {
204
+ try {
205
+ var raw = readFileSync(statePath(), 'utf-8');
206
+ var parsed = JSON.parse(raw);
207
+ if (parsed && (typeof parsed === "undefined" ? "undefined" : _type_of(parsed)) === 'object') {
208
+ return parsed;
209
+ }
210
+ return {};
211
+ } catch (unused) {
212
+ return {};
213
+ }
214
+ }
215
+ /** Persist the throttle state. Best-effort; failures swallow. */ export function saveUpdateState(state) {
216
+ try {
217
+ var path = statePath();
218
+ mkdirSync(dirname(path), {
219
+ recursive: true
220
+ });
221
+ writeFileSync(path, "".concat(JSON.stringify(state), "\n"));
222
+ } catch (unused) {
223
+ // Best-effort per spec failure-modes table — a write that fails
224
+ // means we re-fire next invocation, which is preferable to a
225
+ // crash on read-only home or a full disk.
226
+ }
227
+ }
228
+ /**
229
+ * Compare two semver strings; returns true when `a > b`. Implements the
230
+ * pre-release ranking rules from semver.org §11:
231
+ * - Numeric identifiers compare numerically.
232
+ * - Alphanumeric identifiers compare lexically (ASCII).
233
+ * - Numeric identifiers always rank below alphanumeric.
234
+ * - A longer pre-release identifier list outranks a prefix-equal shorter
235
+ * one (e.g. `0.20.0-alpha.1` > `0.20.0-alpha`).
236
+ * - A stable release outranks any pre-release with the same `x.y.z`.
237
+ *
238
+ * Returns false for anything that fails to parse.
239
+ */ export function semverGt(a, b) {
240
+ // Strict semver (semver.org §9): numeric identifiers MUST NOT have
241
+ // leading zeros, identifiers MUST NOT be empty.
242
+ var NUMERIC_RE = /^(?:0|[1-9]\d*)$/;
243
+ var ALPHANUM_RE = /^[0-9A-Za-z-]+$/;
244
+ var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
245
+ var isValidPreId = function isValidPreId(id) {
246
+ if (id.length === 0) {
247
+ return false;
248
+ }
249
+ // Numeric identifier with leading zeros is invalid (`-01`, `-007`).
250
+ if (/^\d+$/.test(id) && id.length > 1 && id.startsWith('0')) {
251
+ return false;
252
+ }
253
+ return ALPHANUM_RE.test(id);
254
+ };
255
+ var parsed = function parsed(v) {
256
+ var match = SEMVER_RE.exec(v);
257
+ if (!match) {
258
+ return null;
259
+ }
260
+ // Reject leading zeros on the numeric main triple too (`01.0.0`).
261
+ if (![
262
+ match[1],
263
+ match[2],
264
+ match[3]
265
+ ].every(function(p) {
266
+ return NUMERIC_RE.test(p);
267
+ })) {
268
+ return null;
269
+ }
270
+ var preIds = match[4] ? match[4].split('.') : [];
271
+ if (!preIds.every(isValidPreId)) {
272
+ return null;
273
+ }
274
+ return {
275
+ parts: [
276
+ Number(match[1]),
277
+ Number(match[2]),
278
+ Number(match[3])
279
+ ],
280
+ preIds: preIds
281
+ };
282
+ };
283
+ var A = parsed(a);
284
+ var B = parsed(b);
285
+ if (!A || !B) {
286
+ return false;
287
+ }
288
+ for(var i = 0; i < 3; i++){
289
+ if (A.parts[i] !== B.parts[i]) {
290
+ return A.parts[i] > B.parts[i];
291
+ }
292
+ }
293
+ // Equal main parts: stable release outranks any pre-release.
294
+ if (A.preIds.length === 0 && B.preIds.length > 0) {
295
+ return true;
296
+ }
297
+ if (A.preIds.length > 0 && B.preIds.length === 0) {
298
+ return false;
299
+ }
300
+ var len = Math.min(A.preIds.length, B.preIds.length);
301
+ for(var i1 = 0; i1 < len; i1++){
302
+ var cmp = comparePreId(A.preIds[i1], B.preIds[i1]);
303
+ if (cmp !== 0) {
304
+ return cmp > 0;
305
+ }
306
+ }
307
+ // All shared identifiers equal: longer list ranks higher.
308
+ return A.preIds.length > B.preIds.length;
309
+ }
310
+ /**
311
+ * Compare two pre-release identifiers per semver §11.4. Numeric < alphanumeric;
312
+ * pure-numeric identifiers compare numerically; alphanumerics compare lexically.
313
+ * Returns positive when `a > b`, negative when `a < b`, 0 when equal.
314
+ */ function comparePreId(a, b) {
315
+ var numA = /^\d+$/.test(a);
316
+ var numB = /^\d+$/.test(b);
317
+ if (numA && numB) {
318
+ var na = Number(a);
319
+ var nb = Number(b);
320
+ return na === nb ? 0 : na > nb ? 1 : -1;
321
+ }
322
+ if (numA) {
323
+ return -1;
324
+ }
325
+ if (numB) {
326
+ return 1;
327
+ }
328
+ return a === b ? 0 : a > b ? 1 : -1;
329
+ }
330
+ /**
331
+ * Read `X-Client-Latest-Version` from a Response and, when greater than
332
+ * the local CLI version, mark the comparator outdated. Silently skips
333
+ * missing / blank / malformed values.
334
+ */ export function readLatestVersionFromResponse(res, current) {
335
+ var header = res.headers.get('x-client-latest-version');
336
+ if (!header) {
337
+ return;
338
+ }
339
+ var trimmed = header.trim();
340
+ if (!trimmed) {
341
+ return;
342
+ }
343
+ if (semverGt(trimmed, current)) {
344
+ markOutdated(trimmed);
345
+ }
346
+ }
347
+ export function formatNotice(opts) {
348
+ // Single-line dimmed banner for humans in a terminal — mode === 'cli'
349
+ // is the only path that ever sees `tty === true` (MCP is always stdio).
350
+ if (opts.mode === 'cli' && opts.tty) {
351
+ var dim = '\x1b[2m';
352
+ var reset = '\x1b[0m';
353
+ return "".concat(dim).concat(NOTICE_PREFIX, " update available: ").concat(opts.latest, " (you have ").concat(opts.current, "). Run `npm i -g ").concat(PACKAGE_NAME, "@latest` to update.").concat(reset);
354
+ }
355
+ if (opts.mode === 'cli') {
356
+ return [
357
+ "".concat(NOTICE_PREFIX, " Update available: ").concat(PACKAGE_NAME, " ").concat(opts.latest, " (you have ").concat(opts.current, ")."),
358
+ 'Ask the user if they want to update.',
359
+ "If yes: run `npm i -g ".concat(PACKAGE_NAME, "@latest` (or the equivalent for their package manager — bun, pnpm, yarn — based on how they installed it) and retry the original command.")
360
+ ].join('\n');
361
+ }
362
+ // MCP variant: npm i -g does not update an already-running stdio
363
+ // process; the host has to bump its config and restart.
364
+ return [
365
+ "".concat(NOTICE_PREFIX, " Update available: ").concat(PACKAGE_NAME, " ").concat(opts.latest, " (you have ").concat(opts.current, ")."),
366
+ 'Ask the user if they want to update.',
367
+ "If yes: bump the version pin for ".concat(PACKAGE_NAME, " in their MCP host config, or if the config uses @latest, ask them to restart their MCP host.")
368
+ ].join('\n');
369
+ }
370
+ function passesThrottle(now, state) {
371
+ if (!state.lastNoticeAt) {
372
+ return true;
373
+ }
374
+ var last = Date.parse(state.lastNoticeAt);
375
+ if (Number.isNaN(last)) {
376
+ return true;
377
+ }
378
+ return now - last >= THROTTLE_MS;
379
+ }
380
+ /**
381
+ * Synchronous `'exit'` handler for plain CLI mode. Fires after every
382
+ * termination path (natural completion, `process.exit(N)`, uncaught
383
+ * exception). Must remain synchronous — the event loop is winding
384
+ * down by the time it runs, so `await` would silently no-op.
385
+ *
386
+ * Skips when the process is running as a stdio MCP server (notice text
387
+ * differs and stderr is not surfaced to the host) or when injection has
388
+ * already fired in this process (MCP mode already prompted via the
389
+ * tool-result content block).
390
+ */ export function emitOnExit() {
391
+ if (mcpMode) {
392
+ return;
393
+ }
394
+ if (injectedThisProcess) {
395
+ return;
396
+ }
397
+ if (latestVersion === null || currentVersion === null) {
398
+ return;
399
+ }
400
+ var state = loadUpdateState();
401
+ var now = Date.now();
402
+ if (!passesThrottle(now, state)) {
403
+ return;
404
+ }
405
+ var text = formatNotice({
406
+ current: currentVersion,
407
+ latest: latestVersion,
408
+ mode: 'cli',
409
+ tty: Boolean(process.stderr.isTTY)
410
+ });
411
+ process.stderr.write("".concat(text, "\n"));
412
+ saveUpdateState({
413
+ lastNoticeAt: new Date(now).toISOString(),
414
+ lastNoticeVersion: latestVersion
415
+ });
416
+ }
417
+ /**
418
+ * Append a notice content block to a tool-call result when:
419
+ * 1. The comparator has marked the CLI outdated.
420
+ * 2. We have not already injected once in this process.
421
+ * 3. The cross-process 24 h throttle allows.
422
+ *
423
+ * Preserves `_meta`, `isError`, and any other top-level fields. Only
424
+ * `content` changes shape (a new entry appended).
425
+ */ export function injectIntoToolResult(result) {
426
+ if (latestVersion === null || currentVersion === null) {
427
+ return result;
428
+ }
429
+ if (injectedThisProcess) {
430
+ return result;
431
+ }
432
+ var now = Date.now();
433
+ var state = loadUpdateState();
434
+ if (!passesThrottle(now, state)) {
435
+ return result;
436
+ }
437
+ var text = formatNotice({
438
+ current: currentVersion,
439
+ latest: latestVersion,
440
+ mode: 'mcp',
441
+ tty: false
442
+ });
443
+ result.content = _to_consumable_array(result.content).concat([
444
+ {
445
+ type: 'text',
446
+ text: text
447
+ }
448
+ ]);
449
+ injectedThisProcess = true;
450
+ saveUpdateState({
451
+ lastNoticeAt: new Date(now).toISOString(),
452
+ lastNoticeVersion: latestVersion
453
+ });
454
+ return result;
455
+ }
456
+ /**
457
+ * Higher-order wrapper around an MCP tool handler. Awaits the inner
458
+ * handler then runs the result through `injectIntoToolResult`.
459
+ *
460
+ * Mounted in `mcp-server.ts` at every `server.tool(...)` registration
461
+ * site so injection runs uniformly regardless of which return shape
462
+ * (`okResponse` / `errorResponse` / direct literal / `_meta`-bearing)
463
+ * the inner handler returned.
464
+ */ export function withUpdateNotice(handler) {
465
+ return function() {
466
+ for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
467
+ args[_key] = arguments[_key];
468
+ }
469
+ return _async_to_generator(function() {
470
+ var result;
471
+ return _ts_generator(this, function(_state) {
472
+ switch(_state.label){
473
+ case 0:
474
+ return [
475
+ 4,
476
+ handler.apply(void 0, _to_consumable_array(args))
477
+ ];
478
+ case 1:
479
+ result = _state.sent();
480
+ return [
481
+ 2,
482
+ injectIntoToolResult(result)
483
+ ];
484
+ }
485
+ });
486
+ })();
487
+ };
488
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@displaydev/cli",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "dsp": "dist/main.js"