@displaydev/cli 0.18.0 → 0.20.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
@@ -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;
@@ -260,7 +277,8 @@ function createClient(auth) {
260
277
  return new ApiClient({
261
278
  baseUrl: auth.apiUrl,
262
279
  apiKey: auth.apiKey,
263
- clientType: 'cli'
280
+ clientType: 'cli',
281
+ version: version
264
282
  });
265
283
  }
266
284
  var program = new Command().name('dsp').description('display.dev CLI — publish artifacts behind company auth').version(version);
@@ -407,7 +425,8 @@ program.command('publish <path>').description('Publish an HTML or Markdown file.
407
425
  publicClient = new ApiClient({
408
426
  baseUrl: resolvePublicApiUrl(),
409
427
  apiKey: '',
410
- clientType: 'cli'
428
+ clientType: 'cli',
429
+ version: version
411
430
  });
412
431
  _state.label = 10;
413
432
  case 10:
@@ -1074,7 +1093,8 @@ program.command('login').description('Authenticate with display.dev').option('--
1074
1093
  client = new ApiClient({
1075
1094
  baseUrl: apiUrl,
1076
1095
  apiKey: '',
1077
- clientType: 'cli'
1096
+ clientType: 'cli',
1097
+ version: version
1078
1098
  });
1079
1099
  if (!(opts.apiKey !== undefined)) return [
1080
1100
  3,
@@ -1703,6 +1723,537 @@ program.command('clear-logo').description('Remove the org logo (idempotent — s
1703
1723
  });
1704
1724
  })();
1705
1725
  });
1726
+ // --- comment add ---
1727
+ // `dsp comment add --artifact <shortId> --body "..." --anchor-json '<json>' [--parent <id>]`
1728
+ // Spec §3 maps to POST /v1/artifacts/:shortId/comments. Root posts
1729
+ // require a full anchor descriptor (textQuote prefix + exact + suffix
1730
+ // plus cssPath); replies inherit the root's anchor and must omit it.
1731
+ //
1732
+ // `--anchor-json` is intentionally the only anchor input. A bare-text
1733
+ // shortcut would have to fabricate prefix/suffix/cssPath, and the
1734
+ // underlying `dom-anchor-text-quote` resolver binds to the FIRST exact
1735
+ // match in the scope. Without prefix/suffix the resolver cannot
1736
+ // disambiguate duplicates and the comment can silently attach to the
1737
+ // wrong occurrence — see widget's anchor algorithm at
1738
+ // webapp/src/components/comments/anchor.ts:54. Forcing the caller to
1739
+ // supply the full descriptor (the agent has the source content; humans
1740
+ // can copy from the widget's selection JSON) keeps the bind reliable.
1741
+ program.command('comment').description('Comment-related subcommands. Run `dsp comment <cmd> --help` for each.').command('add').description('Post a root comment or reply on an artifact.').option('--artifact <shortId>', 'Artifact shortId (subject_type=artifact)').option('--kb-page <kbName...>', 'KB-page subject — pass two values: kb name then file path (subject_type=kb_page; not yet supported). Variadic (commander) — anything after `--kb-page` up to the next flag is consumed as the (kbName, filePath) tuple.').requiredOption('--body <text>', 'Comment body (≤10k chars)').option('--anchor-json <json>', 'Full anchor object as JSON: {"textQuote":{"prefix":"","exact":"...","suffix":""},"cssPath":"..."}. Required for root posts; omit for replies.').option('--parent <commentId>', 'Reply to this root comment id; omit for a root post').action(function(opts) {
1742
+ return _async_to_generator(function() {
1743
+ var subject, anchor, auth, client, result, err;
1744
+ return _ts_generator(this, function(_state) {
1745
+ switch(_state.label){
1746
+ case 0:
1747
+ subject = resolveSubjectFlags(opts);
1748
+ if (subject.kind === 'invalid') {
1749
+ console.error(subject.error);
1750
+ process.exit(1);
1751
+ }
1752
+ if (subject.kind === 'kb_page') {
1753
+ console.error('subject_type_not_implemented: KB-page subjects are not yet supported.');
1754
+ process.exit(1);
1755
+ }
1756
+ if (!opts.parent && !opts.anchorJson) {
1757
+ console.error('Root comments require --anchor-json. Use --parent <commentId> for a reply.');
1758
+ process.exit(1);
1759
+ }
1760
+ if (opts.parent && opts.anchorJson) {
1761
+ console.error('Replies inherit the root anchor — omit --anchor-json when --parent is set.');
1762
+ process.exit(1);
1763
+ }
1764
+ if (opts.anchorJson) {
1765
+ try {
1766
+ anchor = JSON.parse(opts.anchorJson);
1767
+ } catch (unused) {
1768
+ console.error('--anchor-json is not valid JSON.');
1769
+ process.exit(1);
1770
+ }
1771
+ }
1772
+ return [
1773
+ 4,
1774
+ resolveAuthOrConfig()
1775
+ ];
1776
+ case 1:
1777
+ auth = _state.sent();
1778
+ client = createClient(auth);
1779
+ _state.label = 2;
1780
+ case 2:
1781
+ _state.trys.push([
1782
+ 2,
1783
+ 4,
1784
+ ,
1785
+ 5
1786
+ ]);
1787
+ return [
1788
+ 4,
1789
+ client.addComment(subject.shortId, {
1790
+ body: opts.body,
1791
+ anchor: anchor,
1792
+ parentId: opts.parent
1793
+ })
1794
+ ];
1795
+ case 3:
1796
+ result = _state.sent();
1797
+ console.log(JSON.stringify(result, null, 2));
1798
+ return [
1799
+ 3,
1800
+ 5
1801
+ ];
1802
+ case 4:
1803
+ err = _state.sent();
1804
+ handleCommentApiError(err);
1805
+ return [
1806
+ 3,
1807
+ 5
1808
+ ];
1809
+ case 5:
1810
+ return [
1811
+ 2
1812
+ ];
1813
+ }
1814
+ });
1815
+ })();
1816
+ });
1817
+ // --- comment list ---
1818
+ program.commands.find(function(c) {
1819
+ return c.name() === 'comment';
1820
+ }).command('list').description('List comment threads on a subject.').option('--artifact <shortId>', 'Artifact shortId (subject_type=artifact)').option('--kb-page <kbName...>', 'KB-page subject — pass two values: kb name then file path (subject_type=kb_page; not yet supported). Variadic (commander) — anything after `--kb-page` up to the next flag is consumed as the (kbName, filePath) tuple.').option('--status <state>', 'open | resolved | all (default: open)').option('--since <iso>', 'ISO-8601 timestamp — filter to threads with activity at or after this point').action(function(opts) {
1821
+ return _async_to_generator(function() {
1822
+ var subject, auth, client, result, err;
1823
+ return _ts_generator(this, function(_state) {
1824
+ switch(_state.label){
1825
+ case 0:
1826
+ subject = resolveSubjectFlags(opts);
1827
+ if (subject.kind === 'invalid') {
1828
+ console.error(subject.error);
1829
+ process.exit(1);
1830
+ }
1831
+ if (subject.kind === 'kb_page') {
1832
+ console.error('subject_type_not_implemented: KB-page subjects are not yet supported.');
1833
+ process.exit(1);
1834
+ }
1835
+ if (opts.status && ![
1836
+ 'open',
1837
+ 'resolved',
1838
+ 'all'
1839
+ ].includes(opts.status)) {
1840
+ console.error("Invalid --status: ".concat(opts.status, ". Use open, resolved, or all."));
1841
+ process.exit(1);
1842
+ }
1843
+ return [
1844
+ 4,
1845
+ resolveAuthOrConfig()
1846
+ ];
1847
+ case 1:
1848
+ auth = _state.sent();
1849
+ client = createClient(auth);
1850
+ _state.label = 2;
1851
+ case 2:
1852
+ _state.trys.push([
1853
+ 2,
1854
+ 4,
1855
+ ,
1856
+ 5
1857
+ ]);
1858
+ return [
1859
+ 4,
1860
+ client.listComments(subject.shortId, {
1861
+ status: opts.status,
1862
+ since: opts.since
1863
+ })
1864
+ ];
1865
+ case 3:
1866
+ result = _state.sent();
1867
+ console.log(JSON.stringify(result, null, 2));
1868
+ return [
1869
+ 3,
1870
+ 5
1871
+ ];
1872
+ case 4:
1873
+ err = _state.sent();
1874
+ handleCommentApiError(err);
1875
+ return [
1876
+ 3,
1877
+ 5
1878
+ ];
1879
+ case 5:
1880
+ return [
1881
+ 2
1882
+ ];
1883
+ }
1884
+ });
1885
+ })();
1886
+ });
1887
+ // --- comment edit ---
1888
+ program.commands.find(function(c) {
1889
+ return c.name() === 'comment';
1890
+ }).command('edit <commentId>').description('Edit your own comment within the 5-minute author window.').requiredOption('--body <text>', 'New comment body').action(function(commentId, opts) {
1891
+ return _async_to_generator(function() {
1892
+ var auth, client, result, err;
1893
+ return _ts_generator(this, function(_state) {
1894
+ switch(_state.label){
1895
+ case 0:
1896
+ return [
1897
+ 4,
1898
+ resolveAuthOrConfig()
1899
+ ];
1900
+ case 1:
1901
+ auth = _state.sent();
1902
+ client = createClient(auth);
1903
+ _state.label = 2;
1904
+ case 2:
1905
+ _state.trys.push([
1906
+ 2,
1907
+ 4,
1908
+ ,
1909
+ 5
1910
+ ]);
1911
+ return [
1912
+ 4,
1913
+ client.editComment(commentId, opts.body)
1914
+ ];
1915
+ case 3:
1916
+ result = _state.sent();
1917
+ console.log(JSON.stringify(result, null, 2));
1918
+ return [
1919
+ 3,
1920
+ 5
1921
+ ];
1922
+ case 4:
1923
+ err = _state.sent();
1924
+ handleCommentApiError(err);
1925
+ return [
1926
+ 3,
1927
+ 5
1928
+ ];
1929
+ case 5:
1930
+ return [
1931
+ 2
1932
+ ];
1933
+ }
1934
+ });
1935
+ })();
1936
+ });
1937
+ // --- comment delete ---
1938
+ program.commands.find(function(c) {
1939
+ return c.name() === 'comment';
1940
+ }).command('delete <commentId>').description('Soft-delete a comment. Author / artifact creator / org admin.').action(function(commentId) {
1941
+ return _async_to_generator(function() {
1942
+ var auth, client, err;
1943
+ return _ts_generator(this, function(_state) {
1944
+ switch(_state.label){
1945
+ case 0:
1946
+ return [
1947
+ 4,
1948
+ resolveAuthOrConfig()
1949
+ ];
1950
+ case 1:
1951
+ auth = _state.sent();
1952
+ client = createClient(auth);
1953
+ _state.label = 2;
1954
+ case 2:
1955
+ _state.trys.push([
1956
+ 2,
1957
+ 4,
1958
+ ,
1959
+ 5
1960
+ ]);
1961
+ return [
1962
+ 4,
1963
+ client.deleteComment(commentId)
1964
+ ];
1965
+ case 3:
1966
+ _state.sent();
1967
+ console.log('Comment deleted.');
1968
+ return [
1969
+ 3,
1970
+ 5
1971
+ ];
1972
+ case 4:
1973
+ err = _state.sent();
1974
+ handleCommentApiError(err);
1975
+ return [
1976
+ 3,
1977
+ 5
1978
+ ];
1979
+ case 5:
1980
+ return [
1981
+ 2
1982
+ ];
1983
+ }
1984
+ });
1985
+ })();
1986
+ });
1987
+ // --- thread resolve / reopen ---
1988
+ var threadCmd = program.command('thread').description('Thread-level operations (resolve, reopen).');
1989
+ threadCmd.command('resolve <rootCommentId>').description('Mark a thread resolved. Thread participant / artifact creator / org admin.').action(function(commentId) {
1990
+ return _async_to_generator(function() {
1991
+ var auth, client, result, err;
1992
+ return _ts_generator(this, function(_state) {
1993
+ switch(_state.label){
1994
+ case 0:
1995
+ return [
1996
+ 4,
1997
+ resolveAuthOrConfig()
1998
+ ];
1999
+ case 1:
2000
+ auth = _state.sent();
2001
+ client = createClient(auth);
2002
+ _state.label = 2;
2003
+ case 2:
2004
+ _state.trys.push([
2005
+ 2,
2006
+ 4,
2007
+ ,
2008
+ 5
2009
+ ]);
2010
+ return [
2011
+ 4,
2012
+ client.resolveThread(commentId)
2013
+ ];
2014
+ case 3:
2015
+ result = _state.sent();
2016
+ console.log(JSON.stringify(result, null, 2));
2017
+ return [
2018
+ 3,
2019
+ 5
2020
+ ];
2021
+ case 4:
2022
+ err = _state.sent();
2023
+ handleCommentApiError(err);
2024
+ return [
2025
+ 3,
2026
+ 5
2027
+ ];
2028
+ case 5:
2029
+ return [
2030
+ 2
2031
+ ];
2032
+ }
2033
+ });
2034
+ })();
2035
+ });
2036
+ threadCmd.command('reopen <rootCommentId>').description('Reopen a resolved thread. Any authenticated user with view access.').action(function(commentId) {
2037
+ return _async_to_generator(function() {
2038
+ var auth, client, result, err;
2039
+ return _ts_generator(this, function(_state) {
2040
+ switch(_state.label){
2041
+ case 0:
2042
+ return [
2043
+ 4,
2044
+ resolveAuthOrConfig()
2045
+ ];
2046
+ case 1:
2047
+ auth = _state.sent();
2048
+ client = createClient(auth);
2049
+ _state.label = 2;
2050
+ case 2:
2051
+ _state.trys.push([
2052
+ 2,
2053
+ 4,
2054
+ ,
2055
+ 5
2056
+ ]);
2057
+ return [
2058
+ 4,
2059
+ client.reopenThread(commentId)
2060
+ ];
2061
+ case 3:
2062
+ result = _state.sent();
2063
+ console.log(JSON.stringify(result, null, 2));
2064
+ return [
2065
+ 3,
2066
+ 5
2067
+ ];
2068
+ case 4:
2069
+ err = _state.sent();
2070
+ handleCommentApiError(err);
2071
+ return [
2072
+ 3,
2073
+ 5
2074
+ ];
2075
+ case 5:
2076
+ return [
2077
+ 2
2078
+ ];
2079
+ }
2080
+ });
2081
+ })();
2082
+ });
2083
+ // --- watch / unwatch ---
2084
+ program.command('watch').description('Watch a subject for comment notifications.').option('--artifact <shortId>', 'Artifact shortId (subject_type=artifact)').option('--kb-page <kbName...>', 'KB-page subject — pass two values: kb name then file path (subject_type=kb_page; not yet supported). Variadic (commander) — anything after `--kb-page` up to the next flag is consumed as the (kbName, filePath) tuple.').action(function(opts) {
2085
+ return _async_to_generator(function() {
2086
+ var subject, auth, client, result, err;
2087
+ return _ts_generator(this, function(_state) {
2088
+ switch(_state.label){
2089
+ case 0:
2090
+ subject = resolveSubjectFlags(opts);
2091
+ if (subject.kind === 'invalid') {
2092
+ console.error(subject.error);
2093
+ process.exit(1);
2094
+ }
2095
+ if (subject.kind === 'kb_page') {
2096
+ console.error('subject_type_not_implemented: KB-page subjects are not yet supported.');
2097
+ process.exit(1);
2098
+ }
2099
+ return [
2100
+ 4,
2101
+ resolveAuthOrConfig()
2102
+ ];
2103
+ case 1:
2104
+ auth = _state.sent();
2105
+ client = createClient(auth);
2106
+ _state.label = 2;
2107
+ case 2:
2108
+ _state.trys.push([
2109
+ 2,
2110
+ 4,
2111
+ ,
2112
+ 5
2113
+ ]);
2114
+ return [
2115
+ 4,
2116
+ client.watchArtifact(subject.shortId)
2117
+ ];
2118
+ case 3:
2119
+ result = _state.sent();
2120
+ console.log(JSON.stringify(result, null, 2));
2121
+ return [
2122
+ 3,
2123
+ 5
2124
+ ];
2125
+ case 4:
2126
+ err = _state.sent();
2127
+ handleCommentApiError(err);
2128
+ return [
2129
+ 3,
2130
+ 5
2131
+ ];
2132
+ case 5:
2133
+ return [
2134
+ 2
2135
+ ];
2136
+ }
2137
+ });
2138
+ })();
2139
+ });
2140
+ program.command('unwatch').description('Unwatch a subject.').option('--artifact <shortId>', 'Artifact shortId (subject_type=artifact)').option('--kb-page <kbName...>', 'KB-page subject — pass two values: kb name then file path (subject_type=kb_page; not yet supported). Variadic (commander) — anything after `--kb-page` up to the next flag is consumed as the (kbName, filePath) tuple.').action(function(opts) {
2141
+ return _async_to_generator(function() {
2142
+ var subject, auth, client, err;
2143
+ return _ts_generator(this, function(_state) {
2144
+ switch(_state.label){
2145
+ case 0:
2146
+ subject = resolveSubjectFlags(opts);
2147
+ if (subject.kind === 'invalid') {
2148
+ console.error(subject.error);
2149
+ process.exit(1);
2150
+ }
2151
+ if (subject.kind === 'kb_page') {
2152
+ console.error('subject_type_not_implemented: KB-page subjects are not yet supported.');
2153
+ process.exit(1);
2154
+ }
2155
+ return [
2156
+ 4,
2157
+ resolveAuthOrConfig()
2158
+ ];
2159
+ case 1:
2160
+ auth = _state.sent();
2161
+ client = createClient(auth);
2162
+ _state.label = 2;
2163
+ case 2:
2164
+ _state.trys.push([
2165
+ 2,
2166
+ 4,
2167
+ ,
2168
+ 5
2169
+ ]);
2170
+ return [
2171
+ 4,
2172
+ client.unwatchArtifact(subject.shortId)
2173
+ ];
2174
+ case 3:
2175
+ _state.sent();
2176
+ console.log('Unwatched.');
2177
+ return [
2178
+ 3,
2179
+ 5
2180
+ ];
2181
+ case 4:
2182
+ err = _state.sent();
2183
+ handleCommentApiError(err);
2184
+ return [
2185
+ 3,
2186
+ 5
2187
+ ];
2188
+ case 5:
2189
+ return [
2190
+ 2
2191
+ ];
2192
+ }
2193
+ });
2194
+ })();
2195
+ });
2196
+ function resolveSubjectFlags(opts) {
2197
+ var hasArtifact = Boolean(opts.artifact);
2198
+ var kbPage = opts.kbPage;
2199
+ var hasKbPage = Array.isArray(kbPage) && kbPage.length > 0;
2200
+ if (hasArtifact && hasKbPage) {
2201
+ return {
2202
+ kind: 'invalid',
2203
+ error: 'Pass either --artifact or --kb-page, not both.'
2204
+ };
2205
+ }
2206
+ if (!hasArtifact && !hasKbPage) {
2207
+ return {
2208
+ kind: 'invalid',
2209
+ error: 'Pass --artifact <shortId> or --kb-page <kbName> <filePath>.'
2210
+ };
2211
+ }
2212
+ if (hasArtifact) {
2213
+ return {
2214
+ kind: 'artifact',
2215
+ shortId: opts.artifact
2216
+ };
2217
+ }
2218
+ if (kbPage.length !== 2) {
2219
+ return {
2220
+ kind: 'invalid',
2221
+ error: '--kb-page requires exactly two values: <kbName> <filePath>.'
2222
+ };
2223
+ }
2224
+ return {
2225
+ kind: 'kb_page',
2226
+ kbName: kbPage[0],
2227
+ filePath: kbPage[1],
2228
+ shortId: ''
2229
+ };
2230
+ }
2231
+ /**
2232
+ * Common error mapper for the comment / watch / thread subcommands.
2233
+ * Mirrors the ApiError handling on share/rename so 401 prompts a
2234
+ * re-login, 403 surfaces the API's reason verbatim, 404 shows a
2235
+ * concrete "not found", and 410 (signed-token failures from the
2236
+ * email-link routes) prints the API's message. Other errors fall
2237
+ * through to a generic stringify.
2238
+ */ function handleCommentApiError(err) {
2239
+ if (_instanceof(err, ApiError)) {
2240
+ if (err.status === 401) {
2241
+ console.error('Your credential is no longer valid. Run: dsp login (or rotate DISPLAYDEV_API_KEY).');
2242
+ process.exit(1);
2243
+ }
2244
+ if (err.status === 404) {
2245
+ console.error(err.message);
2246
+ process.exit(4);
2247
+ }
2248
+ if (err.status === 403 || err.status === 400 || err.status === 410 || err.status === 429) {
2249
+ console.error(err.message);
2250
+ process.exit(1);
2251
+ }
2252
+ }
2253
+ var msg = _instanceof(err, Error) ? err.message : String(err);
2254
+ console.error(msg);
2255
+ process.exit(1);
2256
+ }
1706
2257
  // --- mcp ---
1707
2258
  program.command('mcp').description('Start MCP server over stdin/stdout').action(function() {
1708
2259
  return _async_to_generator(function() {
@@ -1716,6 +2267,7 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
1716
2267
  ];
1717
2268
  case 1:
1718
2269
  auth = _state.sent();
2270
+ setMcpMode();
1719
2271
  if (!auth) return [
1720
2272
  3,
1721
2273
  3
@@ -1723,7 +2275,8 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
1723
2275
  client = new ApiClient({
1724
2276
  baseUrl: auth.apiUrl,
1725
2277
  apiKey: auth.apiKey,
1726
- clientType: 'mcp-stdio'
2278
+ clientType: 'mcp-stdio',
2279
+ version: version
1727
2280
  });
1728
2281
  return [
1729
2282
  4,
@@ -1743,7 +2296,8 @@ program.command('mcp').description('Start MCP server over stdin/stdout').action(
1743
2296
  publicClient = new ApiClient({
1744
2297
  baseUrl: resolvePublicApiUrl(),
1745
2298
  apiKey: '',
1746
- clientType: 'mcp-stdio'
2299
+ clientType: 'mcp-stdio',
2300
+ version: version
1747
2301
  });
1748
2302
  return [
1749
2303
  4,