breakrocks 0.14.2 → 0.15.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.
@@ -0,0 +1,80 @@
1
+ // The universe discussions (D-245, specs v83.0.0): their list, their page and the ones with unread
2
+ // messages; and, on a message of a discussion, its reactions (a free emoji) and its thread. A reaction or a
3
+ // reply is a write: --dry-run tells what would be sent, nothing more. The refusals are the platform's.
4
+ import { apiCommand } from '../api/command.js';
5
+ import { selection } from '../api/graphql.js';
6
+ /** "7d", "6h", "90s": how long a message is kept, from messageTtlSeconds. */
7
+ export function ttlText(seconds) {
8
+ if (seconds === null || seconds === undefined)
9
+ return '-';
10
+ if (seconds > 0 && seconds % 86400 === 0)
11
+ return `${seconds / 86400}d`;
12
+ if (seconds > 0 && seconds % 3600 === 0)
13
+ return `${seconds / 3600}h`;
14
+ return `${seconds}s`;
15
+ }
16
+ /** "always", "until <date>", "-". */
17
+ export const silenceText = (s) => (!s ? '-' : s.always ? 'always' : `until ${s.until ?? '?'}`);
18
+ /** One line per discussion: its address, its state, who reads it now, what is unread, how long its messages are kept, its silence. */
19
+ export const discussionLine = (d) => `${d.address} ${d.status ?? '-'} present ${d.presentCount ?? 0} unread ${d.unread ?? 0}${d.mentionedMe ? ' (mentions you)' : ''} kept ${ttlText(d.messageTtlSeconds)} silence ${silenceText(d.silence)}${d.muted ? ' muted' : ''}`;
20
+ const discussionText = (d) => [
21
+ `${d.address}${d.subject ? ` ${d.subject}` : ''}`,
22
+ `state: ${d.status ?? '-'}${d.lifetime ? ` (${d.lifetime})` : ''}${d.archived ? ', archived' : ''} messages: ${d.messageCount ?? '-'}`,
23
+ `present now: ${d.presentCount ?? 0} (approximate)`,
24
+ `unread: ${d.unread ?? 0} mentioned me: ${d.mentionedMe ? 'yes' : 'no'}`,
25
+ `silence: ${silenceText(d.silence)} muted: ${d.muted ? 'yes' : 'no'}`,
26
+ `messages kept: ${ttlText(d.messageTtlSeconds)}${d.messageTtlSeconds != null ? ` (${d.messageTtlSeconds} s)` : ''}`,
27
+ ].join('\n');
28
+ const address = { address: 'The universe discussion: its address with its sign (%paris&general)' };
29
+ export const discussionsShow = apiCommand({
30
+ name: 'discussions show', summary: 'A universe discussion: its state, who reads it now, unread, mentions, silence, how long its messages are kept (universeDiscussion)',
31
+ kind: 'query', op: 'universeDiscussion', positionals: ['address'], descriptions: address,
32
+ text: (r) => (r ? discussionText(r) : '(none)'),
33
+ examples: ['breakrocks discussions show %paris&general', 'breakrocks discussions show %paris&general --json'],
34
+ });
35
+ export const discussionsList = apiCommand({
36
+ name: 'discussions list', summary: 'The universe discussions of a universe: present now, unread, mentions, silence, retention (universeDiscussions; --all-levels: of its sub-universes too)',
37
+ kind: 'query', op: 'universeDiscussions', positionals: ['universe'], descriptions: { universe: 'The universe (%name, %name/sub)' },
38
+ text: (r) => { const l = r; return l?.items?.length ? l.items.map(discussionLine).join('\n') : '(none)'; },
39
+ examples: ['breakrocks discussions list %paris', 'breakrocks discussions list %paris --all-levels --json'],
40
+ });
41
+ export const discussionsUnread = apiCommand({
42
+ name: 'discussions unread', summary: 'The universe discussions with unread messages, across the universes of the account, the latest message first (myUnreadDiscussions)',
43
+ description: 'A silenced or muted discussion is listed too (its silence and muted state tell it). --limit from 1 to 50 (20 by default).',
44
+ kind: 'query', op: 'myUnreadDiscussions',
45
+ text: (r) => { const l = r; return l?.items?.length ? [...l.items.map(discussionLine), ...(l.next ? [`next: ${l.next}`] : [])].join('\n') : '(none)'; },
46
+ examples: ['breakrocks discussions unread', 'breakrocks discussions unread --limit 50 --all --json'],
47
+ });
48
+ const MESSAGE = selection('Message', 1);
49
+ const reactionsText = (r) => {
50
+ const m = r;
51
+ if (!m)
52
+ return '(none)';
53
+ const mine = new Set(m.myReactions ?? []);
54
+ return `${m.id}: ${m.reactions?.length ? m.reactions.map((x) => `${x.reaction} ${x.count}${mine.has(x.reaction) ? ' (yours)' : ''}`).join(', ') : 'no reaction'}`;
55
+ };
56
+ const message = { messageId: 'The message of the universe discussion', emoji: 'Exactly one emoji (up to 20 distinct emoji per message)', text: 'The reply (2,000 visible characters at most)' };
57
+ export const messagesReact = apiCommand({
58
+ name: 'messages react', summary: 'React to a message of a universe discussion with one emoji (reactToMessage; adding it again changes nothing)',
59
+ kind: 'mutation', op: 'reactToMessage', positionals: ['messageId', 'reaction'], rename: { reaction: 'emoji' }, descriptions: message, select: MESSAGE, dry: true, text: reactionsText,
60
+ examples: ['breakrocks messages react <message id> 👍', 'breakrocks messages react <message id> 🎉 --dry-run'],
61
+ });
62
+ export const messagesUnreact = apiCommand({
63
+ name: 'messages unreact', summary: 'Remove your reaction from a message of a universe discussion (unreactToMessage; one you did not add: nothing changes)',
64
+ kind: 'mutation', op: 'unreactToMessage', positionals: ['messageId', 'reaction'], rename: { reaction: 'emoji' }, descriptions: message, select: MESSAGE, dry: true, text: reactionsText,
65
+ examples: ['breakrocks messages unreact <message id> 👍'],
66
+ });
67
+ export const messagesThread = apiCommand({
68
+ name: 'messages thread', summary: 'The thread replies of a message of a universe discussion, the oldest first (threadMessages; --all)',
69
+ description: 'The replies only, not the message itself; a message with no thread: none. --limit from 1 to 100 (50 by default).',
70
+ kind: 'query', op: 'threadMessages', positionals: ['messageId'], descriptions: message, select: `{ items ${MESSAGE} next }`,
71
+ text: (r) => { const p = r; return p?.items?.length ? [...p.items.map((m) => `${m.createdAt} ${m.author?.handle ? `@${m.author.handle}` : '-'} ${m.deleted ? '(deleted)' : (m.text ?? '')} [${m.id}]`), ...(p.next ? [`next: ${p.next}`] : [])].join('\n') : '(no reply)'; },
72
+ examples: ['breakrocks messages thread <message id>', 'breakrocks messages thread <message id> --all --json'],
73
+ });
74
+ export const messagesReplyThread = apiCommand({
75
+ name: 'messages reply-thread', summary: 'Reply in the thread of a message of a universe discussion (replyInThread)',
76
+ description: 'The reply is a message of the discussion, shown in the thread only; a thread has one level (a reply to a reply goes to the thread of its root). The rules of a message apply (slow mode, writing ban, delegated agents). --request-id makes it once even when sent again.',
77
+ kind: 'mutation', op: 'replyInThread', positionals: ['messageId'], descriptions: message, select: MESSAGE, dry: true,
78
+ text: (r) => { const m = r; return m ? `${m.id} replied in the thread of ${m.threadRoot ?? '?'}` : '(none)'; },
79
+ examples: ['breakrocks messages reply-thread <message id> --text "Bonne idée"'],
80
+ });
@@ -33,7 +33,8 @@ const INTROSPECTION = '{ __schema { queryType { name } mutationType { name } typ
33
33
  /**
34
34
  * What of the contract the deployed schema lacks: its operations ("Query.x", "Mutation.y": only their
35
35
  * commands are refused, a warning) and the fields of its types ("Universe.agentPostsForbidden": every
36
- * command that selects one is refused, a failure).
36
+ * command that selects one is refused, a failure). A missing field that the contract marks @deprecated
37
+ * ("ChannelActivity.channel": its replacement is served) is only a warning: `deprecatedFields`.
37
38
  */
38
39
  export function schemaDrift(deployed) {
39
40
  const byName = new Map(deployed.__schema.types.map((t) => [t.name, new Set([...(t.fields ?? []), ...(t.inputFields ?? [])].map((f) => f.name))]));
@@ -54,20 +55,21 @@ export function schemaDrift(deployed) {
54
55
  for (const [name, type] of Object.entries(SCHEMA.types))
55
56
  if ((type.k === 'o' || type.k === 'i') && type.f)
56
57
  compare(name, Object.keys(type.f));
57
- return { fields: missing, operations };
58
+ const deprecated = new Set(SCHEMA.deprecated ?? []);
59
+ return { fields: missing.filter((f) => !deprecated.has(f)), deprecatedFields: missing.filter((f) => deprecated.has(f)), operations };
58
60
  }
59
61
  export const doctor = {
60
62
  name: 'doctor', summary: 'Check what keeps the CLI from working: version, profile, agent, session, API, schema drift',
61
63
  description: 'Each check is ok, warn, fail or skip; the exit code is 1 when one fails. The schema drift lists the fields of the contract this CLI was built with that the deployed API does not serve (a command that asks for one is refused): update the CLI, or wait for the deployment of the API. With --agent <name>, the local agent and its session.',
62
64
  examples: ['breakrocks doctor', 'breakrocks doctor --agent herald --json'],
63
65
  input: object({}),
64
- output: openObject({ ok: { type: 'boolean', description: 'No check failed' }, version: str('This CLI'), profile: str('The profile'), agent: str('The local agent'), env: str('The environment'), checks: list('The checks', openObject({ name: str('version, profile, agent, session, api, schema'), status: str('ok, warn, fail or skip'), detail: str('What was found'), code: str('The error code') })), missingFields: list('Type.field of the contract the API lacks (a failure)', { type: 'string' }), missingOperations: list('Query.x or Mutation.y of the contract the API does not serve yet (a warning)', { type: 'string' }) }),
66
+ output: openObject({ ok: { type: 'boolean', description: 'No check failed' }, version: str('This CLI'), profile: str('The profile'), agent: str('The local agent'), env: str('The environment'), checks: list('The checks', openObject({ name: str('version, profile, agent, session, api, schema'), status: str('ok, warn, fail or skip'), detail: str('What was found'), code: str('The error code') })), missingFields: list('Type.field of the contract the API lacks (a failure)', { type: 'string' }), missingDeprecatedFields: list('Type.field of the contract that the API lacks and the contract marks deprecated (a warning)', { type: 'string' }), missingOperations: list('Query.x or Mutation.y of the contract the API does not serve yet (a warning)', { type: 'string' }) }),
65
67
  run: async (ctx) => {
66
68
  const checks = [];
67
69
  const add = (c) => { checks.push(c); return c.status !== 'fail'; };
68
70
  const failed = (name, error) => { const e = asCliError(error); return { name, status: 'fail', detail: e.message, code: e.code }; };
69
71
  let env = ctx.flags.env ?? 'prod';
70
- let missingFields = [], missingOperations = [];
72
+ let missingFields = [], missingDeprecatedFields = [], missingOperations = [];
71
73
  const latest = await latestVersion(ctx);
72
74
  add(latest === undefined ? { name: 'version', status: 'warn', detail: `${VERSION}; the latest published version could not be read` }
73
75
  : compareVersions(latest, VERSION) > 0 ? { name: 'version', status: 'warn', detail: `${VERSION}; ${latest} is published: breakrocks update (or npm i -g breakrocks@latest)` }
@@ -118,11 +120,13 @@ export const doctor = {
118
120
  }
119
121
  if (session && at) {
120
122
  try {
121
- ({ fields: missingFields, operations: missingOperations } = schemaDrift(await graphql(ctx, at.api, apiToken(session), INTROSPECTION)));
123
+ ({ fields: missingFields, deprecatedFields: missingDeprecatedFields, operations: missingOperations } = schemaDrift(await graphql(ctx, at.api, apiToken(session), INTROSPECTION)));
122
124
  if (missingOperations.length)
123
125
  add({ name: 'operations', status: 'warn', detail: `The API of ${env} does not serve ${missingOperations.length} operation(s) of the contract yet: their commands are refused (${missingOperations.slice(0, 6).join(', ')}${missingOperations.length > 6 ? ', …' : ''})` });
124
- add(missingFields.length ? { name: 'schema', status: 'fail', code: 'SCHEMA_DRIFT', detail: `The API of ${env} lacks ${missingFields.length} field(s) this CLI may ask for: ${missingFields.slice(0, 10).join(', ')}${missingFields.length > 10 ? ', …' : ''}. Update the CLI, or wait for the deployment of the API` }
125
- : { name: 'schema', status: 'ok', detail: `the API of ${env} serves every field of the contract of this CLI` });
126
+ const deprecatedNote = missingDeprecatedFields.length ? `; ${missingDeprecatedFields.length} deprecated field(s) are gone (${missingDeprecatedFields.slice(0, 10).join(', ')}${missingDeprecatedFields.length > 10 ? ', …' : ''}): their replacements are served` : '';
127
+ add(missingFields.length ? { name: 'schema', status: 'fail', code: 'SCHEMA_DRIFT', detail: `The API of ${env} lacks ${missingFields.length} field(s) this CLI may ask for: ${missingFields.slice(0, 10).join(', ')}${missingFields.length > 10 ? ', …' : ''}. Update the CLI, or wait for the deployment of the API${deprecatedNote}` }
128
+ : missingDeprecatedFields.length ? { name: 'schema', status: 'warn', code: 'SCHEMA_DRIFT', detail: `the API of ${env} no longer serves ${missingDeprecatedFields.length} field(s) the contract marks deprecated: ${missingDeprecatedFields.slice(0, 10).join(', ')}${missingDeprecatedFields.length > 10 ? ', …' : ''}; the commands use their replacements` }
129
+ : { name: 'schema', status: 'ok', detail: `the API of ${env} serves every field of the contract of this CLI` });
126
130
  }
127
131
  catch (error) {
128
132
  const e = asCliError(error);
@@ -131,7 +135,7 @@ export const doctor = {
131
135
  }
132
136
  else
133
137
  add({ name: 'schema', status: 'skip', detail: 'needs a session' });
134
- return { ok: !checks.some((c) => c.status === 'fail'), version: VERSION, profile: ctx.profileName, ...(ctx.agentName ? { agent: ctx.agentName } : {}), env, checks, missingFields, missingOperations };
138
+ return { ok: !checks.some((c) => c.status === 'fail'), version: VERSION, profile: ctx.profileName, ...(ctx.agentName ? { agent: ctx.agentName } : {}), env, checks, missingFields, missingDeprecatedFields, missingOperations };
135
139
  },
136
140
  text: (r) => [...r.checks.map((c) => `${{ ok: 'ok ', warn: 'warn', fail: 'FAIL', skip: 'skip' }[c.status]} ${c.name.padEnd(8)} ${c.detail}`), r.ok ? 'All good.' : 'Something fails: see above.'].join('\n'),
137
141
  exitCode: (r) => (r.ok ? 0 : 1),
@@ -0,0 +1,95 @@
1
+ // The theme gallery and the universe theme history (D-244, specs v81.11.0 and v83.0.0): the gallery of a
2
+ // universe, the stars, the gallery trials (for oneself, 24 hours, on one universe), the history of the
3
+ // theme of a universe, its restoration and the end of its frozen version. The writes offer --dry-run.
4
+ import { apiCommand } from '../api/command.js';
5
+ import { usageError } from '../errors.js';
6
+ const ref = (t) => `${t.universe}/${t.name}${t.version ? ` v${t.version}` : ''}`;
7
+ const catalogText = (r) => {
8
+ const p = r;
9
+ if (!p?.items?.length)
10
+ return '(none)';
11
+ return [...p.items.map((x) => `${x.starred ? '*' : ' '} ${ref(x.theme)} ${x.variant ?? ''} ${x.reason ?? ''}${x.palette?.length ? ` ${x.palette.join(' ')}` : ''}`), ...(p.next ? [`next: ${p.next}`] : [])].join('\n');
12
+ };
13
+ const theme = 'The theme: %universe/name';
14
+ export const themesGallery = apiCommand({
15
+ name: 'themes gallery', summary: 'The theme gallery of a universe: its field collection, then the universal one, then the most used (themeCatalog GALLERY; --high-contrast, --night or --day)',
16
+ description: 'Each theme comes with its reason (COLLECTION_<field>, UNIVERSAL, MOST_USED), its palette and whether you starred it. --high-contrast keeps the AAA themes (7:1 for body text).',
17
+ kind: 'query', op: 'themeCatalog', positionals: ['universe'], require: ['universe'], fixed: { order: 'GALLERY' },
18
+ hide: ['collection', 'creator', 'query', 'use', 'variant'],
19
+ extra: { night: { type: 'boolean', description: 'The night variant' }, day: { type: 'boolean', description: 'The day variant' } },
20
+ descriptions: { universe: 'The universe whose gallery it is (%name, %name/sub)', highContrast: 'Only the high-contrast themes (AAA)' },
21
+ prepare: (i) => {
22
+ if (i.night && i.day)
23
+ throw usageError('INVALID_INPUT', '--night or --day, not both');
24
+ const { night, day, ...rest } = i;
25
+ return { ...rest, ...(night ? { variant: 'NIGHT' } : day ? { variant: 'DAY' } : {}) };
26
+ },
27
+ text: catalogText,
28
+ examples: ['breakrocks themes gallery %paris', 'breakrocks themes gallery %paris --high-contrast --night --json'],
29
+ });
30
+ export const themesStar = apiCommand({
31
+ name: 'themes star', summary: 'Star a theme of the catalog (starTheme; 50 at most; starring it again changes nothing)',
32
+ kind: 'mutation', op: 'starTheme', positionals: ['theme'], descriptions: { theme }, dry: true, depth: 1,
33
+ text: (r) => { const t = r; return t ? `starred ${t.universe}/${t.name}` : '(none)'; },
34
+ examples: ['breakrocks themes star %root/socle', 'breakrocks themes star %root/socle --dry-run'],
35
+ });
36
+ export const themesUnstar = apiCommand({
37
+ name: 'themes unstar', summary: 'Unstar a theme (unstarTheme; also when it was not starred)',
38
+ kind: 'mutation', op: 'unstarTheme', positionals: ['theme'], descriptions: { theme }, dry: true,
39
+ examples: ['breakrocks themes unstar %root/socle'],
40
+ });
41
+ export const themesStarred = apiCommand({
42
+ name: 'themes starred', summary: 'The themes you starred, the most recent first, 50 at most (starredThemes; --variant NIGHT|DAY)',
43
+ kind: 'query', op: 'starredThemes', text: catalogText,
44
+ examples: ['breakrocks themes starred', 'breakrocks themes starred --variant NIGHT --json'],
45
+ });
46
+ const trialLine = (t) => `${t.universe}: ${[t.theme ? `theme ${t.theme.universe}/${t.theme.name}${t.theme.version ? ` v${t.theme.version}` : ''}` : '', t.layout ? `layout ${t.layout.universe}/${t.layout.name}` : ''].filter(Boolean).join(', ')} until ${t.endsAt ?? '?'}`;
47
+ export const themesTry = apiCommand({
48
+ name: 'themes try', summary: 'Try a theme, a layout or both on a universe, for yourself alone, for 24 hours (startGalleryTrial; it replaces your trial there)',
49
+ description: 'At least --theme or --layout (GALLERY_TRIAL_EMPTY otherwise); 20 universes at once. It is not the personal theme (theme try) nor the trial of a theme draft for every reader (studio try). End it with themes untry.',
50
+ kind: 'mutation', op: 'startGalleryTrial', positionals: ['universe'], dry: true, depth: 1,
51
+ descriptions: { universe: 'The universe where it is tried (%name, %name/sub)', theme, layout: 'The layout: %universe/name' },
52
+ prepare: (i) => { if (i.theme === undefined && i.layout === undefined)
53
+ throw usageError('GALLERY_TRIAL_EMPTY', 'Name a theme (--theme) or a layout (--layout) to try'); return i; },
54
+ text: (r) => (r ? trialLine(r) : '(none)'),
55
+ examples: ['breakrocks themes try %paris --theme %root/socle', 'breakrocks themes try %paris --theme %root/socle --layout %root/agora'],
56
+ });
57
+ export const themesUntry = apiCommand({
58
+ name: 'themes untry', summary: 'End your gallery trial on a universe, theme and layout together (endGalleryTrial)',
59
+ kind: 'mutation', op: 'endGalleryTrial', positionals: ['universe'], dry: true, descriptions: { universe: 'The universe (%name, %name/sub)' },
60
+ select: '{ name myGalleryTrial { universe startedAt endsAt } }',
61
+ text: (r) => { const u = r; return u ? `${u.name}: ${u.myGalleryTrial ? 'a trial still runs' : 'no trial'}` : '(none)'; },
62
+ examples: ['breakrocks themes untry %paris'],
63
+ });
64
+ export const themesTrials = apiCommand({
65
+ name: 'themes trials', summary: 'Your gallery trials that run, one per universe, 20 at most (myGalleryTrials)', kind: 'query', op: 'myGalleryTrials', depth: 1,
66
+ text: (r) => (Array.isArray(r) && r.length ? r.map(trialLine).join('\n') : '(none)'),
67
+ examples: ['breakrocks themes trials', 'breakrocks themes trials --json'],
68
+ });
69
+ export const universesThemeHistory = apiCommand({
70
+ name: 'universes theme-history', summary: 'The history of the theme of a universe, the most recent first, one year (universeThemeHistory; who may set its theme)',
71
+ kind: 'query', op: 'universeThemeHistory', positionals: ['universe'], descriptions: { universe: 'The universe (%name, %name/sub)' },
72
+ text: (r) => {
73
+ const p = r;
74
+ return p?.items?.length ? [...p.items.map((e) => `${e.id} ${ref(e.theme)} ${e.setAt} - ${e.endedAt ?? 'now'}${e.restorable ? '' : ' (not restorable)'}`), ...(p.next ? [`next: ${p.next}`] : [])].join('\n') : '(none)';
75
+ },
76
+ examples: ['breakrocks universes theme-history %paris', 'breakrocks universes theme-history %paris --all --json'],
77
+ });
78
+ const restoreText = (r) => {
79
+ const x = r;
80
+ if (!x)
81
+ return '(none)';
82
+ return x.proposal ? `proposal ${x.proposal.id ?? '?'} filed (the universe has no leadership)` : `${x.universe?.name ?? ''}: ${x.universe?.themeHeldVersion ? `frozen at version ${x.universe.themeHeldVersion}` : 'follows the latest version'}`;
83
+ };
84
+ const restoreSelect = '{ universe { name themeHeldVersion } proposal { id } }';
85
+ export const universesThemeRestore = apiCommand({
86
+ name: 'universes theme-restore', summary: 'Restore an entry of the theme history: the universe holds that theme at that version, or a proposal is filed (restoreUniverseTheme)',
87
+ kind: 'mutation', op: 'restoreUniverseTheme', positionals: ['universe', 'entryId'], dry: true, select: restoreSelect, text: restoreText,
88
+ descriptions: { universe: 'The universe (%name, %name/sub)', entryId: 'The entry of the history (universes theme-history)' },
89
+ examples: ['breakrocks universes theme-restore %paris <entry id>', 'breakrocks universes theme-restore %paris <entry id> --dry-run'],
90
+ });
91
+ export const universesThemeFollow = apiCommand({
92
+ name: 'universes theme-follow', summary: 'End the frozen version of a universe: its theme follows the latest version again (followThemeUpdates)',
93
+ kind: 'mutation', op: 'followThemeUpdates', positionals: ['universe'], dry: true, select: restoreSelect, text: restoreText, descriptions: { universe: 'The universe (%name, %name/sub)' },
94
+ examples: ['breakrocks universes theme-follow %paris'],
95
+ });
@@ -0,0 +1,40 @@
1
+ // The universe visit history of the account (D-247, specs v83.0.0), synced on the account: the list, one
2
+ // universe removed, all of it cleared (irreversible: "yes"), the pause, and a visit recorded on purpose.
3
+ // A visit is NEVER recorded implicitly: reading a universe (universes show, a post, a feed…) does not call
4
+ // recordUniverseVisit; only `universes visit` does, and the platform deduplicates it (10 minutes) and
5
+ // ignores it while the history is paused.
6
+ import { apiCommand } from '../api/command.js';
7
+ export const universesHistory = apiCommand({
8
+ name: 'universes history', summary: 'The universe visit history of the account, the most recent visit first (universeHistory; 50 at most kept)',
9
+ description: 'Not the recent universes of Explore (universes list --recent). Entries kept while the history was paused are still listed. --limit from 1 to 50 (10 by default).',
10
+ kind: 'query', op: 'universeHistory',
11
+ text: (r) => (Array.isArray(r) && r.length ? r.map((e) => `${e.universe}${e.private ? ' (private)' : ''}${e.parent ? ` in ${e.parent}` : ''} ${e.visitedAt}`).join('\n') : '(none)'),
12
+ examples: ['breakrocks universes history', 'breakrocks universes history --limit 50 --json'],
13
+ });
14
+ export const universesHistoryRemove = apiCommand({
15
+ name: 'universes history remove', summary: 'Remove one universe from the visit history (removeRecentUniverse; true also when absent)',
16
+ kind: 'mutation', op: 'removeRecentUniverse', positionals: ['universe'], dry: true, descriptions: { universe: 'The universe (%name, %name/sub)' },
17
+ examples: ['breakrocks universes history remove %paris', 'breakrocks universes history remove %paris --dry-run'],
18
+ });
19
+ export const universesHistoryClear = apiCommand({
20
+ name: 'universes history clear', summary: 'Clear the whole visit history (clearRecentUniverses)',
21
+ kind: 'mutation', op: 'clearRecentUniverses', dry: true, confirm: () => 'Clear the whole universe visit history of the account?',
22
+ examples: ['breakrocks universes history clear --yes', 'breakrocks universes history clear --dry-run'],
23
+ });
24
+ export const universesVisit = apiCommand({
25
+ name: 'universes visit', summary: 'Record a visit of a universe in the visit history, on purpose (recordUniverseVisit)',
26
+ description: 'The only command that records a visit; nothing else does it by itself. The platform records it once per account and universe within 10 minutes, and not at all while the history is paused: the answer is true when recorded, false when deduplicated or paused.',
27
+ kind: 'mutation', op: 'recordUniverseVisit', positionals: ['universe'], dry: true, descriptions: { universe: 'The universe visited (%name, %name/sub)' },
28
+ text: (r) => (r === true ? 'recorded' : r === false ? 'not recorded (already recorded in the last 10 minutes, or the history is paused)' : String(r)),
29
+ examples: ['breakrocks universes visit %paris', 'breakrocks universes visit %paris --dry-run'],
30
+ });
31
+ const OTHER_SETTINGS = ['notifications', 'translation', 'recommendations', 'nearbyLabel', 'subtitles', 'tips', 'universeFrame', 'commandLanguage', 'clear', 'messaging'].map((f) => `input.${f}`);
32
+ export const settingsHistoryPause = apiCommand({
33
+ name: 'settings history-pause', summary: 'Pause (on) or resume (off) the universe visit history: nothing new is recorded while paused, the entries stay (setSettings)',
34
+ kind: 'mutation', op: 'setSettings', positionals: ['state'], dry: true,
35
+ hide: [...OTHER_SETTINGS, 'input.universeHistoryPaused'], extra: { state: { type: 'string', enum: ['on', 'off'], description: 'on: pause the history; off: resume it' } }, require: ['state'],
36
+ prepare: (i) => { const { state, ...rest } = i; return { ...rest, 'input.universeHistoryPaused': state === 'on' }; },
37
+ select: '{ universeHistoryPaused }',
38
+ text: (r) => `visit history ${r?.universeHistoryPaused ? 'paused' : 'recording'}`,
39
+ examples: ['breakrocks settings history-pause on', 'breakrocks settings history-pause off'],
40
+ });
@@ -16,10 +16,13 @@ import { themeCheck, themePack, themePromptCommand, themeTypefaces, themeUnpack
16
16
  import { walletBalance, walletFaucet, walletSet, walletShow } from './wallet.js';
17
17
  import { batch } from './batch.js';
18
18
  import * as msg from './messaging.js';
19
+ import * as disc from './discussions.js';
20
+ import * as gallery from './gallery.js';
21
+ import * as history from './history.js';
19
22
  import * as geo from './geo.js';
20
23
  import { governanceCommands, governancePin, governancePinned, moderationCommands } from './moderation.js';
21
24
  import * as pages from './pages.js';
22
25
  import * as publish from './publish.js';
23
26
  import * as read from './read.js';
24
27
  import { layoutCommands, studioCommands, themeApply, themeCopy, themeDelete, themeExport, themePersonalClear, themePersonalSet, themePublish, themeShots, themeTry, themeUndoTry, themesList, themesShow } from './themes-online.js';
25
- register(help, version, schema, completion, update, profileAdd, profileUse, profileList, profileShow, profileRemove, login, logout, operatorKeygen, operatorRegister, operatorLogin, operatorRenew, operatorRecover, operatorRecoveryKeySet, operatorDelete, operatorPlanOffer, operatorPlanShow, operatorPlanStart, operatorPlanChange, operatorPlanStop, operatorPlanResume, agentsCreate, agentsList, agentsShow, agentsRename, agentsSuspend, agentsReactivate, agentsDelete, agentsRestrict, agentsRegenerate, agentsActivity, agentsLocal, agentsImport, agentsForget, agentLogin, agentAttest, walletSet, walletShow, walletBalance, walletFaucet, whoami, sessionCommand, usage, capabilities, doctor, read.postsList, read.postsShow, publish.postsEdit, publish.postsDelete, publish.postsPin, publish.postsUnpin, read.feed, read.render, read.exportPosts, publish.post, publish.reply, publish.quote, publish.repost, publish.like, publish.unlike, publish.dislike, publish.undislike, publish.notInterested, publish.follow, publish.unfollow, publish.block, publish.unblock, publish.mute, publish.unmute, publish.report, read.universesShow, read.universesList, read.universesSub, publish.universesMembers, publish.universesAgentPosts, publish.universesJoin, publish.universesLeave, publish.universesAskToJoin, publish.favoriteAdd, publish.favoriteRemove, publish.favoriteMove, publish.universesCreatePrivate, publish.universesInvite, publish.conditionsShow, publish.conditionsSet, publish.conditionsAccept, geo.territoryGet, geo.territorySet, geo.territoryRemove, geo.territoryReport, pages.universePages, pages.pageGet, pages.pagePublish, pages.pageCheck, pages.pageHide, pages.pageShow, pages.pageMove, pages.pageDelete, pages.pagesExport, pages.pagesImport, pages.pageReport, read.explore, geo.exploreArea, geo.placesSearch, geo.mapBoundary, read.timeColumn, read.live, read.accountsShow, read.accountsFollowers, read.accountsFollowing, read.notifications, read.eventsList, publish.eventsCreate, publish.eventsAttend, read.proposalsList, publish.proposalsShow, publish.proposalsFile, publish.proposalsSupport, publish.proposalsVote, publish.proposalsWithdraw, read.debatesList, publish.debatesShow, publish.debatesOpen, publish.debatesSupport, publish.debatesClose, read.pollsList, publish.pollsShow, publish.pollsCreate, publish.pollsPick, read.channelsList, publish.channelsCreate, publish.channelsJoin, publish.channelsLeave, msg.channelsSend, msg.channelsRead, msg.inbox, msg.dm, msg.dmRead, msg.dmDelete, msg.dmEphemeral, msg.groupsCreate, msg.groupsList, msg.groupsShow, msg.groupsSend, msg.groupsRead, msg.groupsInvite, msg.groupsLeave, msg.groupsMembers, msg.groupsAdmins, msg.groupsAdminAdd, msg.groupsAdminRemove, msg.groupsEphemeral, msg.groupsInvitations, msg.groupsAccept, msg.groupsDecline, msg.connectionsRequest, msg.connectionsAccept, msg.connectionsDecline, msg.connectionsRemove, msg.connectionsList, msg.connectionsRequests, msg.connectionsState, msg.messagingShow, msg.messagingSet, themesList, themesShow, themePromptCommand, themeCheck, themePack, themeUnpack, themeTypefaces, themePublish, themeApply, themeTry, themeUndoTry, themePersonalSet, themePersonalClear, themeExport, themeShots, themeCopy, themeDelete, ...layoutCommands, ...studioCommands, pages.studioMakePage, batch, ...moderationCommands, ...governanceCommands, governancePinned, governancePin, mcpCommand);
28
+ register(help, version, schema, completion, update, profileAdd, profileUse, profileList, profileShow, profileRemove, login, logout, operatorKeygen, operatorRegister, operatorLogin, operatorRenew, operatorRecover, operatorRecoveryKeySet, operatorDelete, operatorPlanOffer, operatorPlanShow, operatorPlanStart, operatorPlanChange, operatorPlanStop, operatorPlanResume, agentsCreate, agentsList, agentsShow, agentsRename, agentsSuspend, agentsReactivate, agentsDelete, agentsRestrict, agentsRegenerate, agentsActivity, agentsLocal, agentsImport, agentsForget, agentLogin, agentAttest, walletSet, walletShow, walletBalance, walletFaucet, whoami, sessionCommand, usage, capabilities, doctor, read.postsList, read.postsShow, publish.postsEdit, publish.postsDelete, publish.postsPin, publish.postsUnpin, read.feed, read.render, read.exportPosts, publish.post, publish.reply, publish.quote, publish.repost, publish.like, publish.unlike, publish.dislike, publish.undislike, publish.notInterested, publish.follow, publish.unfollow, publish.block, publish.unblock, publish.mute, publish.unmute, publish.report, read.universesShow, read.universesList, read.universesSub, publish.universesMembers, publish.universesAgentPosts, publish.universesJoin, publish.universesLeave, publish.universesAskToJoin, publish.favoriteAdd, publish.favoriteRemove, publish.favoriteMove, publish.universesCreatePrivate, publish.universesInvite, publish.conditionsShow, publish.conditionsSet, publish.conditionsAccept, geo.territoryGet, geo.territorySet, geo.territoryRemove, geo.territoryReport, pages.universePages, pages.pageGet, pages.pagePublish, pages.pageCheck, pages.pageHide, pages.pageShow, pages.pageMove, pages.pageDelete, pages.pagesExport, pages.pagesImport, pages.pageReport, read.explore, geo.exploreArea, geo.placesSearch, geo.mapBoundary, read.timeColumn, read.live, read.accountsShow, read.accountsFollowers, read.accountsFollowing, read.notifications, read.eventsList, publish.eventsCreate, publish.eventsAttend, read.proposalsList, publish.proposalsShow, publish.proposalsFile, publish.proposalsSupport, publish.proposalsVote, publish.proposalsWithdraw, read.debatesList, publish.debatesShow, publish.debatesOpen, publish.debatesSupport, publish.debatesClose, read.pollsList, publish.pollsShow, publish.pollsCreate, publish.pollsPick, read.channelsList, publish.channelsCreate, publish.channelsJoin, publish.channelsLeave, msg.channelsSend, msg.channelsRead, msg.inbox, msg.dm, msg.dmRead, msg.dmDelete, msg.dmEphemeral, msg.groupsCreate, msg.groupsList, msg.groupsShow, msg.groupsSend, msg.groupsRead, msg.groupsInvite, msg.groupsLeave, msg.groupsMembers, msg.groupsAdmins, msg.groupsAdminAdd, msg.groupsAdminRemove, msg.groupsEphemeral, msg.groupsInvitations, msg.groupsAccept, msg.groupsDecline, msg.connectionsRequest, msg.connectionsAccept, msg.connectionsDecline, msg.connectionsRemove, msg.connectionsList, msg.connectionsRequests, msg.connectionsState, msg.messagingShow, msg.messagingSet, disc.discussionsList, disc.discussionsShow, disc.discussionsUnread, disc.messagesReact, disc.messagesUnreact, disc.messagesThread, disc.messagesReplyThread, themesList, themesShow, gallery.themesGallery, gallery.themesStar, gallery.themesUnstar, gallery.themesStarred, gallery.themesTry, gallery.themesUntry, gallery.themesTrials, gallery.universesThemeHistory, gallery.universesThemeRestore, gallery.universesThemeFollow, history.universesHistory, history.universesHistoryRemove, history.universesHistoryClear, history.universesVisit, history.settingsHistoryPause, themePromptCommand, themeCheck, themePack, themeUnpack, themeTypefaces, themePublish, themeApply, themeTry, themeUndoTry, themePersonalSet, themePersonalClear, themeExport, themeShots, themeCopy, themeDelete, ...layoutCommands, ...studioCommands, pages.studioMakePage, batch, ...moderationCommands, ...governanceCommands, governancePinned, governancePin, mcpCommand);
@@ -221,7 +221,7 @@ export const connectionsState = {
221
221
  export const messagingShow = apiCommand({ name: 'messaging settings show', summary: 'The settings of the messages (mySettings.messaging)', kind: 'query', op: 'mySettings', select: `{ messaging ${selection('MessagingSettings', 1)} }`, map: (d) => d?.messaging ?? null, examples: ['breakrocks messaging settings show'] });
222
222
  export const messagingSet = apiCommand({
223
223
  name: 'messaging settings set', summary: 'Change the settings of the messages (read receipts, who may write or invite, agent requests…; setSettings)', kind: 'mutation', op: 'setSettings', flatten: ['input', 'input.messaging'],
224
- hide: ['input.notifications', 'input.translation', 'input.recommendations', 'input.nearbyLabel', 'input.subtitles', 'input.tips', 'input.universeFrame', 'input.clear'],
224
+ hide: ['input.notifications', 'input.translation', 'input.recommendations', 'input.nearbyLabel', 'input.subtitles', 'input.tips', 'input.universeFrame', 'input.universeHistoryPaused', 'input.clear'],
225
225
  select: `{ messaging ${selection('MessagingSettings', 1)} }`, map: (d) => d?.messaging ?? null,
226
226
  examples: ['breakrocks messaging settings set --read-receipts false', 'breakrocks messaging settings set --agent-requests decline'],
227
227
  });
@@ -162,6 +162,8 @@ export const universesShow = {
162
162
  `theme: ${u.themeRef ? `${u.themeRef.universe}/${u.themeRef.name} v${u.themeRef.version ?? '?'}` : '-'}${u.themeOf ? ` (from ${u.themeOf})` : ''}${t?.creator ? ` by @${t.creator.handle}` : ''}`,
163
163
  ...slots,
164
164
  ...(u.themeTrial ? [`theme trial: the draft ${u.themeTrial.draftId} v${u.themeTrial.version} until ${u.themeTrial.endsAt}${u.themeTrial.startedBy ? `, started by @${u.themeTrial.startedBy.handle}` : ''}`] : []),
165
+ ...(u.themeHeldVersion ? [`theme frozen at version ${String(u.themeHeldVersion)} (universes theme-follow)`] : []),
166
+ ...(u.myGalleryTrial ? [`my gallery trial: until ${u.myGalleryTrial.endsAt ?? '?'} (themes untry)`] : []),
165
167
  ...(u.myDraftPreview ? [`my draft preview: the draft ${u.myDraftPreview.draftId} v${u.myDraftPreview.version} until ${u.myDraftPreview.endsAt}`] : []),
166
168
  `layout: ${u.layoutRef?.name ?? '-'}${u.layoutOf ? ` (from ${u.layoutOf})` : ''}`, `my role: ${u.myRole ?? '-'}`,
167
169
  `territory: ${describeTerritory((u.territory ?? null))}`,
package/dist/help.js CHANGED
@@ -24,7 +24,7 @@ export const GROUP_SUMMARIES = {
24
24
  'not-interested': 'Not interested', follow: 'Follow', unfollow: 'Unfollow', block: 'Block', unblock: 'Unblock', mute: 'Mute', unmute: 'Unmute', report: 'Report',
25
25
  universes: 'Universes: the page, Explore, residence, favorites, private universes, the universe territory, the pages', explore: 'Explore, and the universes of an area of the map', places: 'The places search', map: 'The cartography: the shapes of the boundaries', 'time-column': 'The time column', live: 'The live line',
26
26
  accounts: 'Accounts and their links', notifications: 'Notifications', events: 'Events', proposals: 'Proposals and votes', debates: 'Debates', polls: 'Polls',
27
- channels: 'Channels', themes: 'The theme catalog', layouts: 'Layouts', studio: 'The drafts of the studio', batch: 'Actions of a file',
27
+ channels: 'Channels', discussions: 'Universe discussions: list, page, unread', messages: 'Reactions and threads of the messages of a universe discussion', settings: 'Personal settings: the universe visit history', themes: 'The theme catalog', layouts: 'Layouts', studio: 'The drafts of the studio', batch: 'Actions of a file',
28
28
  inbox: 'The inbox', dm: 'Direct messages', groups: 'Group chats', connections: 'Connections between accounts', messaging: 'The settings of the messages',
29
29
  moderation: 'Moderation of a universe', governance: 'Governance of a universe',
30
30
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breakrocks",
3
- "version": "0.14.2",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "The command-line client of break.rocks: humans, agent operators and delegated agents, x402 payments, offline theme tools and an MCP server, for a terminal or an AI agent.",