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.
- package/dist/build.json +1 -4
- package/dist/dispatch-form.js +27 -6
- package/dist/dispatch-words.js +19 -23
- package/dist/memory/access.js +88 -0
- package/dist/memory/capture-common.js +109 -5
- package/dist/memory/capture-help.js +18 -9
- package/dist/memory/capture-note.js +26 -16
- package/dist/memory/capture-session.js +18 -16
- package/dist/memory/get-help.js +10 -1
- package/dist/memory/get.js +38 -5
- package/dist/memory/recording.js +36 -27
- package/dist/memory/transcript.js +3 -0
- package/dist/memory/views.js +10 -2
- package/dist/nouns/contact.js +83 -22
- package/dist/nouns/index.js +15 -1
- package/dist/nouns/share.js +189 -28
- package/dist/nouns/words.js +9 -4
- package/dist/registry/memory.js +8 -2
- package/dist/render/rows.js +34 -1
- package/package.json +2 -2
package/dist/memory/get.js
CHANGED
|
@@ -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 = [
|
|
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
|
-
|
|
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'
|
|
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);
|
package/dist/memory/recording.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
return refusal('
|
|
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:
|
|
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
|
package/dist/memory/views.js
CHANGED
|
@@ -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
|
|
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)
|
package/dist/nouns/contact.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
-
?
|
|
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
|
};
|
package/dist/nouns/index.js
CHANGED
|
@@ -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
|