lattris 0.2.0 → 0.2.1

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,4 +1,5 @@
1
1
  import { parseArgs } from "../request/flags.js";
2
+ import { contactIdFor } from "../memory/access.js";
2
3
  import { getRevision } from "../memory/history.js";
3
4
  import { composeAddress, formatMemoryDetail, formatMemoryPage, formatNodeWalk, ownHandleFor, parseWebsiteMemoryAddress } from "../memory/views.js";
4
5
  import { emit } from "../render/output.js";
@@ -80,9 +81,21 @@ const GET_FLAG_NAMES = {
80
81
  '--with': 'with',
81
82
  '--links': 'links',
82
83
  '--depth': 'depth',
83
- '--content': 'content'
84
+ '--content': 'content',
85
+ '--for': 'for',
86
+ '--for-phone': 'forPhone'
84
87
  };
85
- const LIST_ONLY_FLAGS = ['type', 'intent', 'limit', 'cursor', 'q', 'with', 'sharedWithMe'];
88
+ const LIST_ONLY_FLAGS = [
89
+ 'type',
90
+ 'intent',
91
+ 'limit',
92
+ 'cursor',
93
+ 'q',
94
+ 'with',
95
+ 'sharedWithMe',
96
+ 'for',
97
+ 'forPhone'
98
+ ];
86
99
  const WALK_ONLY_FLAGS = ['links', 'depth', 'revision'];
87
100
  const listMemories = async (cfg, io, flags) => {
88
101
  const accessToken = await signedIn(io, cfg);
@@ -105,10 +118,22 @@ const listMemories = async (cfg, io, flags) => {
105
118
  url.searchParams.set('content', flags.content);
106
119
  if (flags.sharedWithMe)
107
120
  url.searchParams.set('sharedWithMe', '1');
121
+ if (flags.for !== undefined && flags.forPhone !== undefined) {
122
+ return refusal('--for and --for-phone name one audience; pass one.\n');
123
+ }
124
+ if (flags.for !== undefined) {
125
+ const contact = await contactIdFor(cfg, io, accessToken, flags.for);
126
+ if ('exit' in contact)
127
+ return contact;
128
+ url.searchParams.set('forContact', contact.id);
129
+ }
130
+ if (flags.forPhone !== undefined)
131
+ url.searchParams.set('forPhone', flags.forPhone);
108
132
  const result = await io.api(accessToken, url);
109
133
  if (!result.ok)
110
134
  return refusalOf(result);
111
- emit(io, result.data, result.rawText, formatMemoryPage);
135
+ const asked = flags.forPhone ?? flags.for;
136
+ emit(io, result.data, result.rawText, (page) => formatMemoryPage(page, asked));
112
137
  if (result.data.nextCursor) {
113
138
  const argv = ['get', 'memories'];
114
139
  if (flags.type !== undefined)
@@ -119,6 +144,10 @@ const listMemories = async (cfg, io, flags) => {
119
144
  argv.push('--q', flags.q);
120
145
  if (flags.with !== undefined)
121
146
  argv.push('--with', flags.with);
147
+ if (flags.for !== undefined)
148
+ argv.push('--for', flags.for);
149
+ if (flags.forPhone !== undefined)
150
+ argv.push('--for-phone', flags.forPhone);
122
151
  if (flags.limit !== undefined)
123
152
  argv.push('--limit', flags.limit);
124
153
  if (flags.content !== undefined)
@@ -195,9 +224,13 @@ export const getMemories = async (cfg, io, rest, noun) => {
195
224
  return refusal(`--${walkFlag} applies to a single memory, not the list.\n`);
196
225
  }
197
226
  if (flags.by !== undefined) {
198
- const ownerOnly = ['type', 'intent', 'q', 'with', 'sharedWithMe'].find((key) => flags[key] !== undefined);
227
+ const ownerOnly = ['type', 'intent', 'q', 'with', 'sharedWithMe', 'for', 'forPhone'].find((key) => flags[key] !== undefined);
199
228
  if (ownerOnly !== undefined) {
200
- const flagName = ownerOnly === 'sharedWithMe' ? 'shared-with-me' : ownerOnly;
229
+ const flagName = ownerOnly === 'sharedWithMe'
230
+ ? 'shared-with-me'
231
+ : ownerOnly === 'forPhone'
232
+ ? 'for-phone'
233
+ : ownerOnly;
201
234
  return refusal(`--${flagName} does not apply to a wall listing.\n`);
202
235
  }
203
236
  return getWallMemories(cfg, io, flags.by, flags);
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { parseArgs } from "../request/flags.js";
3
3
  import { HELP_TOKENS } from "../request/flags.js";
4
4
  import { RECORDING_FORMATS, TRANSCRIPT_CONVERTERS, buildRecordingBody, formatRecording, mimeForAudio } from "../memory/transcript.js";
5
- import { SHARE_AUDIENCES } from "../memory/capture-common.js";
5
+ import { allowWords, checkShareWords, isRefusalValue, needsYesWords, resolveBornShares, takeShareFlags } from "../memory/capture-common.js";
6
6
  import { emit } from "../render/output.js";
7
7
  import { confirmAction } from "../render/prompt.js";
8
8
  import { sanitizeForTerminal } from "../render/text.js";
@@ -41,7 +41,10 @@ Usage:
41
41
  --captured-at <t> when it was recorded (ISO 8601; the future is refused)
42
42
  --duration <n> length in seconds
43
43
  --span <uuid> group it into one of your spans
44
- --share-with public make it public immediately
44
+ --share-with <who> who may see it from birth: public, all-contacts, or a contact
45
+ (repeatable); asks at a terminal, needs --yes on a pipe
46
+ --deny <who> who may not, whatever else allows it: all-contacts or a
47
+ contact (repeatable); asks nothing
45
48
  --contributor <id> whose expression it is — a contact id
46
49
  --allow-banned-words keep a flagged word in the title deliberately
47
50
 
@@ -60,7 +63,6 @@ const RECORDING_FLAG_NAMES = {
60
63
  '--captured-at': 'capturedAt',
61
64
  '--duration': 'duration',
62
65
  '--span': 'span',
63
- '--share-with': 'shareWith',
64
66
  '--contributor': 'contributor'
65
67
  };
66
68
  export const captureRecording = async (cfg, io, args) => {
@@ -70,22 +72,21 @@ export const captureRecording = async (cfg, io, args) => {
70
72
  }
71
73
  const allowBanned = args.includes('--allow-banned-words');
72
74
  const yes = args.includes('--yes');
73
- const { flags, positionals, unknown } = parseArgs(RECORDING_FLAG_NAMES, args.filter((a) => a !== '--allow-banned-words' && a !== '--yes'));
74
- if (unknown) {
75
- return refusal(`Unknown flag: ${unknown}\n`);
75
+ const taken = takeShareFlags(args.filter((a) => a !== '--allow-banned-words' && a !== '--yes'));
76
+ const { flags, positionals, unknown } = parseArgs(RECORDING_FLAG_NAMES, taken.rest);
77
+ if (unknown ?? taken.unknown) {
78
+ return refusal(`Unknown flag: ${unknown ?? taken.unknown}\n`);
76
79
  }
77
80
  if (positionals.length > 1) {
78
81
  return refusal(`Unexpected argument: ${positionals[1]}\n`);
79
82
  }
80
83
  const transcriptFile = positionals[0];
81
- if (flags.shareWith !== undefined &&
82
- !SHARE_AUDIENCES.includes(flags.shareWith)) {
83
- return refusal(`Unknown audience: ${flags.shareWith}. Accepted: ${SHARE_AUDIENCES.join(', ')}\n`);
84
- }
85
- if (flags.shareWith !== undefined && !io.stdin.isTTY && !yes) {
86
- return refusal('This will save the recording AND make it public — anyone, including people ' +
87
- 'without accounts. There is no prompt here (the content already arrived on ' +
88
- 'a pipe) — pass --yes once the person has confirmed both facts.\n');
84
+ const badShareWords = checkShareWords(taken.shareWith, taken.deny);
85
+ if (badShareWords)
86
+ return badShareWords;
87
+ const allows = allowWords(taken.shareWith);
88
+ if (allows.length > 0 && !io.stdin.isTTY && !yes) {
89
+ return refusal(needsYesWords('recording', allows));
89
90
  }
90
91
  if (transcriptFile === undefined && flags.audio === undefined && flags.sourceUrl === undefined) {
91
92
  return refusal('A recording needs at least one of: a transcript file, --audio, or --source-url.\n');
@@ -134,6 +135,25 @@ export const captureRecording = async (cfg, io, args) => {
134
135
  if (flags.duration !== undefined && !/^\d+$/.test(flags.duration)) {
135
136
  return refusal(`--duration must be a whole number of seconds: ${flags.duration}\n`);
136
137
  }
138
+ if (allows.length > 0 && io.stdin.isTTY) {
139
+ const answer = await confirmAction(io, `Share "${sanitizeForTerminal(flags.title ?? '(untitled)')}" with ${allows.join(', ')}? [y/N] `);
140
+ if (answer === 'interrupted')
141
+ return refusal('Cancelled.\n', EXIT.GENERIC);
142
+ if (answer === 'no') {
143
+ io.stderr.write('Cancelled.\n');
144
+ return;
145
+ }
146
+ }
147
+ const accessToken = await signedIn(io, cfg);
148
+ if (typeof accessToken !== 'string')
149
+ return accessToken;
150
+ let shares;
151
+ if (taken.shareWith.length > 0 || taken.deny.length > 0) {
152
+ const resolved = await resolveBornShares(cfg, io, accessToken, taken.shareWith, taken.deny);
153
+ if (isRefusalValue(resolved))
154
+ return resolved;
155
+ shares = resolved;
156
+ }
137
157
  const body = buildRecordingBody({
138
158
  transcript,
139
159
  upload,
@@ -141,7 +161,8 @@ export const captureRecording = async (cfg, io, args) => {
141
161
  title: flags.title,
142
162
  capturedAt: flags.capturedAt,
143
163
  spanId: flags.span,
144
- audience: flags.shareWith,
164
+ audience: shares?.audience,
165
+ access: shares !== undefined && shares.access.length > 0 ? shares.access : undefined,
145
166
  contributor: flags.contributor,
146
167
  sourceName: flags.sourceName,
147
168
  titleAtSource: flags.titleAtSource,
@@ -150,18 +171,6 @@ export const captureRecording = async (cfg, io, args) => {
150
171
  durationSeconds: flags.duration === undefined ? undefined : Number(flags.duration),
151
172
  allowBannedWords: allowBanned
152
173
  });
153
- if (flags.shareWith !== undefined && io.stdin.isTTY) {
154
- const answer = await confirmAction(io, `Share "${sanitizeForTerminal(flags.title ?? '(untitled)')}" with public — anyone, including people without accounts? [y/N] `);
155
- if (answer === 'interrupted')
156
- return refusal('Cancelled.\n', EXIT.GENERIC);
157
- if (answer === 'no') {
158
- io.stderr.write('Cancelled.\n');
159
- return;
160
- }
161
- }
162
- const accessToken = await signedIn(io, cfg);
163
- if (typeof accessToken !== 'string')
164
- return accessToken;
165
174
  const created = await io.api(accessToken, `${cfg.apiUrl}/memories/recordings`, {
166
175
  method: 'POST',
167
176
  headers: { 'Content-Type': 'application/json' },
@@ -1,3 +1,4 @@
1
+ import { waitsForNameLines } from "../render/rows.js";
1
2
  import { sanitizeForTerminal } from "../render/text.js";
2
3
  const AUDIO_MIME_BY_EXT = {
3
4
  mp3: 'audio/mpeg',
@@ -80,6 +81,8 @@ export const formatRecording = (r) => {
80
81
  if (r.durationSeconds)
81
82
  lines.push(` duration ${r.durationSeconds}s`);
82
83
  lines.push(` capturedAt ${r.capturedAt}`);
84
+ for (const sentence of waitsForNameLines(r.access))
85
+ lines.push(` website ${sentence}`);
83
86
  return lines.join('\n');
84
87
  };
85
88
  //# sourceMappingURL=transcript.js.map
@@ -1,4 +1,4 @@
1
- import { formatAudiences } from "../render/rows.js";
1
+ import { describeForContact, formatAccess, formatAudiences } from "../render/rows.js";
2
2
  import { sanitizeForTerminal } from "../render/text.js";
3
3
  import { stripTrailingSlash } from "../store/environment.js";
4
4
  export const formatDeletedLine = (b) => `${b.id} ${b.deletedAt ?? '-'} rev ${b.revisionNo ?? '-'} ${b.fingerprint ?? '-'} ${sanitizeForTerminal(b.title ?? '(untitled)')}`;
@@ -74,6 +74,8 @@ export const formatMemoryDetail = (m, address) => {
74
74
  if (m.source)
75
75
  lines.push(`source ${sanitizeForTerminal(m.source)}`);
76
76
  lines.push(`audiences ${formatAudiences(m.audiences)}`);
77
+ if (Array.isArray(m.access))
78
+ lines.push(`access ${formatAccess(m.access)}`);
77
79
  if (m.fingerprint)
78
80
  lines.push(`fingerprint ${m.fingerprint}`);
79
81
  if (m.revisionNo)
@@ -92,7 +94,13 @@ export const formatMemoryDetail = (m, address) => {
92
94
  }
93
95
  return lines.join('\n');
94
96
  };
95
- export const formatMemoryPage = (page) => page.items.map((i) => formatBlockLine(i)).join('\n');
97
+ export const formatMemoryPage = (page, asked) => {
98
+ const rows = page.items.map((i) => formatBlockLine(i));
99
+ if (page.forContact === undefined)
100
+ return rows.join('\n');
101
+ const header = `for ${describeForContact(page.forContact, asked)}`;
102
+ return [header, ...(rows.length === 0 ? ['(nothing they may see)'] : rows)].join('\n');
103
+ };
96
104
  export const formatFindingAid = (a) => {
97
105
  const lines = [`id ${a.id}`, `handle ${a.handle}`, `type ${a.type}`];
98
106
  if (a.title)
@@ -1,4 +1,5 @@
1
1
  import { formatContact } from "../render/rows.js";
2
+ import { renderRunHint, sanitizeForTerminal } from "../render/text.js";
2
3
  import { verbsOf } from "./form.js";
3
4
  const CONTACT_HELP = [
4
5
  'contact — the people and AIs you work with. A contact is yours alone: it',
@@ -6,33 +7,61 @@ const CONTACT_HELP = [
6
7
  'so its contact can point at its page.',
7
8
  '',
8
9
  'Usage:',
9
- ' lattris contact add --name "<v>" [--email <v>] [--ai]',
10
+ ' lattris contact add --name "<v>" [--email <v>] [--phone <v>] [--ai]',
10
11
  ' record a person by name; --email keeps',
11
12
  ' their address as your own note',
12
- ' lattris contact create "<name>" [--ai] [--email <v>]',
13
+ ' lattris contact create "<name>" [--ai] [--email <v>] [--phone <v>]',
13
14
  ' the same, with the name first',
14
- ' lattris contact list every contact of yours, as the API sends them',
15
+ ' lattris contact list [--phone <v>] every contact of yours, as the API sends them;',
16
+ ' --phone answers the one holding that number, or none',
15
17
  ' lattris contact get <id> one contact, by its full id or a unique prefix',
16
- ' lattris contact update <id> [--name "<v>"] [--email <v> | --clear-email] [--ai | --person] [--root <block-id>]',
17
- ' rename, change the email or the kind, or point',
18
+ ' lattris contact update <id> [--name "<v>"] [--email <v> | --clear-email] [--phone <v> | --clear-phone] [--ai | --person] [--root <block-id>]',
19
+ ' rename, change the email, the number or the kind, or point',
18
20
  ' it at a block of yours (an AI role at its page)',
21
+ ' lattris contact delete <id-or-name> [--yes] remove a contact nothing holds on to: asks at a',
22
+ ' terminal, needs --yes on a pipe',
19
23
  '',
20
24
  ' --email <v> the address you keep for this contact. It is your record',
21
25
  ' alone: the contact never sees it, nobody else reads it,',
22
26
  ' and changing it asks nobody',
27
+ ' --phone <v> the number a chat reaches this contact at, in full',
28
+ ' international form: + then digits, like +61412345678.',
29
+ ' One per contact, and no two of your contacts may hold',
30
+ ' one number. An AI contact holds none.',
23
31
  ' --root <block-id> the full id of one of your own blocks, never a prefix; the',
24
32
  ' API refuses a block you do not own',
25
33
  '',
26
34
  'A contact shows "linked" once it stands for a Lattris account: yours are',
27
35
  'linked when someone accepts the invitation you minted for that contact,',
28
36
  'and the person who invited you appears among your contacts, linked, the',
29
- 'moment you accept. Sharing with --with needs a linked contact.',
37
+ 'moment you accept. A share or a deny names any contact, linked or not;',
38
+ 'a linked contact also sees a share on the website.',
30
39
  '',
31
40
  'Another person’s contact is not yours to read or change: the API answers',
32
41
  'not found. A block names a contact with --contributor <id>, on capture and',
33
- 'on update memory alike.'
42
+ 'on update memory alike.',
43
+ '',
44
+ 'A contact that contributed a memory, that an invitation links, or that a',
45
+ 'deny names cannot be deleted: the API refuses and says which. A deny is',
46
+ 'lifted with `lattris undeny memory <id> --to <contact>` first, so the same',
47
+ 'number added back later never skips it.'
34
48
  ].join('\n');
35
- const CONTACT_UPDATE_USAGE = 'Usage: lattris contact update <id> [--name "<v>"] [--email <v> | --clear-email] [--ai | --person] [--root <block-id>]';
49
+ const deniedBlocksWords = (body, contactId) => {
50
+ if (body.code !== 'contact:has-denies')
51
+ return '';
52
+ const blocks = body.detail?.blocks;
53
+ if (!Array.isArray(blocks) || blocks.length === 0)
54
+ return '';
55
+ const lines = blocks.map((b) => ` ${b.id} ${sanitizeForTerminal(b.title ?? '(untitled)')} — ${renderRunHint([
56
+ 'undeny',
57
+ 'memory',
58
+ b.id,
59
+ '--to',
60
+ contactId
61
+ ])}`);
62
+ return `Denies naming this contact:\n${lines.join('\n')}\n`;
63
+ };
64
+ const CONTACT_UPDATE_USAGE = 'Usage: lattris contact update <id> [--name "<v>"] [--email <v> | --clear-email] [--phone <v> | --clear-phone] [--ai | --person] [--root <block-id>]';
36
65
  const contactRow = (c) => formatContact(c);
37
66
  export const contacts = {
38
67
  name: 'contacts',
@@ -43,8 +72,8 @@ export const contacts = {
43
72
  unknownVerb: 'Unknown contact verb: {verb}\n\n' + CONTACT_HELP + '\n',
44
73
  verbs: verbsOf({
45
74
  add: {
46
- usage: 'Usage: lattris contact add --name "<v>" [--email <v>] [--ai]',
47
- flags: { '--name': 'name', '--email': 'email' },
75
+ usage: 'Usage: lattris contact add --name "<v>" [--email <v>] [--phone <v>] [--ai]',
76
+ flags: { '--name': 'name', '--email': 'email', '--phone': 'phone' },
48
77
  switches: ['--ai'],
49
78
  positionals: 0,
50
79
  requiredFlags: ['name'],
@@ -54,14 +83,15 @@ export const contacts = {
54
83
  body: {
55
84
  displayName: { flag: 'name' },
56
85
  type: { switch: '--ai', value: 'ai' },
57
- email: { flag: 'email' }
86
+ email: { flag: 'email' },
87
+ phone: { flag: 'phone' }
58
88
  }
59
89
  },
60
90
  render: (c) => `Recorded contact ${contactRow(c)}`
61
91
  },
62
92
  create: {
63
- usage: 'Usage: lattris contact create "<name>" [--ai] [--email <v>]',
64
- flags: { '--email': 'email' },
93
+ usage: 'Usage: lattris contact create "<name>" [--ai] [--email <v>] [--phone <v>]',
94
+ flags: { '--email': 'email', '--phone': 'phone' },
65
95
  switches: ['--ai'],
66
96
  positionals: 1,
67
97
  request: {
@@ -70,18 +100,22 @@ export const contacts = {
70
100
  body: {
71
101
  displayName: { positional: 0 },
72
102
  type: { switch: '--ai', value: 'ai' },
73
- email: { flag: 'email' }
103
+ email: { flag: 'email' },
104
+ phone: { flag: 'phone' }
74
105
  }
75
106
  },
76
107
  render: (c) => `Recorded contact ${contactRow(c)}`
77
108
  },
78
109
  list: {
79
- usage: 'Usage: lattris contact list',
110
+ usage: 'Usage: lattris contact list [--phone <v>]',
111
+ flags: { '--phone': 'phone' },
80
112
  positionals: 0,
81
113
  unknownFlagIsUsage: true,
82
- request: { method: 'GET', path: '/contacts' },
83
- render: (list) => list.length === 0
84
- ? 'No contacts yet. `lattris contact create "<name>"` records one.'
114
+ request: { method: 'GET', path: '/contacts', query: { phone: { flag: 'phone' } } },
115
+ render: (list, context) => list.length === 0
116
+ ? context.flags.phone !== undefined
117
+ ? `No contact of yours holds ${sanitizeForTerminal(context.flags.phone)}.`
118
+ : 'No contacts yet. `lattris contact create "<name>"` records one.'
85
119
  : list.map(formatContact).join('\n')
86
120
  },
87
121
  get: {
@@ -98,8 +132,8 @@ export const contacts = {
98
132
  },
99
133
  update: {
100
134
  usage: CONTACT_UPDATE_USAGE,
101
- flags: { '--name': 'name', '--root': 'root', '--email': 'email' },
102
- switches: ['--ai', '--person', '--clear-email'],
135
+ flags: { '--name': 'name', '--root': 'root', '--email': 'email', '--phone': 'phone' },
136
+ switches: ['--ai', '--person', '--clear-email', '--clear-phone'],
103
137
  positionals: 1,
104
138
  uuidPositionals: [0],
105
139
  idPositionals: [0],
@@ -113,6 +147,11 @@ export const contacts = {
113
147
  a: '--clear-email',
114
148
  b: 'email',
115
149
  message: '--email and --clear-email contradict each other; pass one.\n'
150
+ },
151
+ {
152
+ a: '--clear-phone',
153
+ b: 'phone',
154
+ message: '--phone and --clear-phone contradict each other; pass one.\n'
116
155
  }
117
156
  ],
118
157
  uuidFlags: [
@@ -131,11 +170,33 @@ export const contacts = {
131
170
  ],
132
171
  rootBlockId: { flag: 'root' },
133
172
  displayName: { flag: 'name' },
134
- email: [{ flag: 'email' }, { switch: '--clear-email', value: null }]
173
+ email: [{ flag: 'email' }, { switch: '--clear-email', value: null }],
174
+ phone: [{ flag: 'phone' }, { switch: '--clear-phone', value: null }]
135
175
  },
136
- emptyBodyMessage: `Nothing to update: pass --name, --email, --clear-email, --ai, --person or --root.\n${CONTACT_UPDATE_USAGE}\n`
176
+ emptyBodyMessage: `Nothing to update: pass --name, --email, --clear-email, --phone, --clear-phone, --ai, --person or --root.\n${CONTACT_UPDATE_USAGE}\n`
137
177
  },
138
178
  render: (c) => `Updated contact ${contactRow(c)}`
179
+ },
180
+ delete: {
181
+ usage: 'Usage: lattris contact delete <id-or-name> [--yes]',
182
+ switches: ['--yes'],
183
+ positionals: 1,
184
+ idPositionals: [0],
185
+ steps: {
186
+ contact: { from: ['p0'] },
187
+ pipedNeedsYes: {
188
+ message: 'Refusing to delete a contact without confirmation on a non-interactive session. Pass --yes to skip the prompt.\n'
189
+ },
190
+ confirm: {
191
+ question: 'Delete contact "{name}" ({contact.id}) permanently? [y/N] ',
192
+ onNo: { text: 'Cancelled.\n', to: 'stderr' },
193
+ onInterrupted: { text: 'Cancelled.\n', exit: 1 }
194
+ }
195
+ },
196
+ request: { method: 'DELETE', path: '/contacts/{contact.id}' },
197
+ refusalWords: (body, context) => deniedBlocksWords(body, context.contact?.id ?? ''),
198
+ renderTo: 'stderr',
199
+ render: (_answer, context) => `Deleted contact ${sanitizeForTerminal(context.contact?.displayName ?? context.positionals[0])}`
139
200
  }
140
201
  })
141
202
  };
@@ -6,7 +6,7 @@ import { datasets } from "./datasets.js";
6
6
  import { LINK_HELP, edges } from "./edges.js";
7
7
  import { INVITE_HELP, invites } from "./invite.js";
8
8
  import { ASK_HELP, CLOSE_HELP, CLOSE_USAGE, questions } from "./questions.js";
9
- import { SHARE_HELP, UNSHARE_HELP, grants } from "./share.js";
9
+ import { DENY_HELP, SHARE_HELP, UNDENY_HELP, UNSHARE_HELP, grants } from "./share.js";
10
10
  export const listNouns = [contacts, questions, edges, invites, grants, datasets];
11
11
  export const listVerbs = {
12
12
  ask: { help: ASK_HELP, helpBeforeAuth: true },
@@ -29,6 +29,20 @@ export const listVerbs = {
29
29
  help: UNSHARE_HELP,
30
30
  bareIsHelp: true,
31
31
  badNoun: { stderr: 'Usage: lattris unshare memory <id> [--with <audience>]\n' }
32
+ },
33
+ deny: {
34
+ help: DENY_HELP,
35
+ helpBeforeAuth: true,
36
+ bareIsHelp: true,
37
+ badNoun: { stderr: 'Usage: lattris deny memory <id> --to <all-contacts | contact>\n' }
38
+ },
39
+ undeny: {
40
+ help: UNDENY_HELP,
41
+ helpBeforeAuth: true,
42
+ bareIsHelp: true,
43
+ badNoun: {
44
+ stderr: 'Usage: lattris undeny memory <id> --to <all-contacts | contact> [--yes]\n'
45
+ }
32
46
  }
33
47
  };
34
48
  //# sourceMappingURL=index.js.map