lattris 0.1.4 → 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/README.md +2 -2
- package/dist/build.json +1 -1
- package/dist/datasets/capture.js +118 -0
- package/dist/datasets/page.js +64 -0
- package/dist/datasets/pieces.js +78 -0
- package/dist/dispatch-form.js +202 -0
- package/dist/dispatch-words.js +133 -0
- package/dist/dispatch.js +67 -0
- package/dist/entry/session.js +219 -0
- package/dist/entry/set.js +130 -0
- package/dist/entry/setup-boot.js +151 -0
- package/dist/entry/setup.js +145 -0
- package/dist/entry/use.js +127 -0
- package/dist/help.js +65 -0
- package/dist/main.js +143 -6887
- package/dist/memory/access.js +88 -0
- package/dist/memory/capture-common.js +171 -0
- package/dist/memory/capture-help.js +195 -0
- package/dist/memory/capture-note.js +200 -0
- package/dist/memory/capture-session.js +168 -0
- package/dist/memory/delete.js +88 -0
- package/dist/memory/erase.js +67 -0
- package/dist/{extract-claude-jsonl.js → memory/extract-claude-jsonl.js} +5 -94
- package/dist/memory/get-help.js +72 -0
- package/dist/memory/get.js +246 -0
- package/dist/memory/history.js +134 -0
- package/dist/memory/recording-update.js +128 -0
- package/dist/memory/recording.js +258 -0
- package/dist/memory/resend.js +82 -0
- package/dist/memory/restore.js +73 -0
- package/dist/memory/save-session-chain.js +59 -0
- package/dist/memory/transcript.js +88 -0
- package/dist/memory/update-help.js +126 -0
- package/dist/memory/update.js +220 -0
- package/dist/memory/views.js +146 -0
- package/dist/nouns/consent.js +29 -0
- package/dist/nouns/contact.js +203 -0
- package/dist/nouns/datasets.js +109 -0
- package/dist/nouns/edges.js +106 -0
- package/dist/nouns/form.js +2 -0
- package/dist/nouns/index.js +48 -0
- package/dist/nouns/invite.js +98 -0
- package/dist/nouns/questions.js +116 -0
- package/dist/nouns/share.js +266 -0
- package/dist/nouns/words.js +81 -0
- package/dist/registry/datasets.js +15 -0
- package/dist/registry/entry.js +51 -0
- package/dist/registry/index.js +17 -0
- package/dist/registry/memory.js +116 -0
- package/dist/render/datasets.js +77 -0
- package/dist/render/door.js +39 -0
- package/dist/render/output.js +19 -0
- package/dist/render/prompt.js +4 -0
- package/dist/render/rows.js +82 -0
- package/dist/render/text.js +21 -0
- package/dist/request/access-token.js +67 -0
- package/dist/request/api-fetch.js +76 -0
- package/dist/request/callback.js +86 -0
- package/dist/request/door.js +74 -0
- package/dist/request/flags.js +38 -0
- package/dist/request/io.js +2 -0
- package/dist/request/paste-and-device.js +99 -0
- package/dist/request/stdin.js +22 -0
- package/dist/request/token.js +66 -0
- package/dist/request/upload.js +6 -0
- package/dist/request/version-check.js +79 -0
- package/dist/store/chains.js +25 -0
- package/dist/store/drafts.js +48 -0
- package/dist/store/environment.js +57 -0
- package/dist/store/exit.js +13 -0
- package/dist/store/io.js +2 -0
- package/dist/store/json-file.js +9 -0
- package/dist/store/session-home.js +189 -0
- package/dist/store/session-lock.js +169 -0
- package/dist/store/session.js +70 -0
- package/dist/store/write-identity.js +153 -0
- package/dist/version.js +16 -0
- package/package.json +3 -4
- package/dist/save-session-chain.js +0 -136
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { formatAsked, formatClosed, formatQuestionList } from "../render/rows.js";
|
|
2
|
+
export const ASK_HELP = [
|
|
3
|
+
'ask — leave a question on someone’s tree.',
|
|
4
|
+
'',
|
|
5
|
+
'Usage:',
|
|
6
|
+
' echo "Where did the export land?" | lattris ask <handle> [--about <block-id>]',
|
|
7
|
+
'',
|
|
8
|
+
'The question comes from stdin — one per ask, prose only: that text is the',
|
|
9
|
+
'whole of what crosses to the tree’s owner. --about anchors it to the node',
|
|
10
|
+
'you stood on when the tree ran out (a block id readable on that owner’s wall).',
|
|
11
|
+
'',
|
|
12
|
+
'The owner closes it with a close pointing at the node that answers.',
|
|
13
|
+
'Watch yours: lattris get questions --mine'
|
|
14
|
+
].join('\n');
|
|
15
|
+
const askVerb = {
|
|
16
|
+
verb: 'ask',
|
|
17
|
+
form: {
|
|
18
|
+
usage: 'Usage: echo "…" | lattris ask <handle> [--about <block-id>]',
|
|
19
|
+
flags: { '--about': 'about' },
|
|
20
|
+
positionals: 1,
|
|
21
|
+
stdinBody: {
|
|
22
|
+
atTty: 'The question comes from stdin: echo "…" | lattris ask <handle>\n',
|
|
23
|
+
empty: 'Nothing to ask — the question comes from stdin.\n'
|
|
24
|
+
},
|
|
25
|
+
request: {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
path: '/by/{p0}/questions',
|
|
28
|
+
body: { body: { stdin: true }, aboutBlockId: { flag: 'about' } }
|
|
29
|
+
},
|
|
30
|
+
render: (q) => formatAsked(q)
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const CLOSE_USAGE = [
|
|
34
|
+
'Usage: lattris close question <id-or-prefix> --close-as updated|already-answered --block <ref> [--note "…"]',
|
|
35
|
+
' lattris close question <id-or-prefix> --close-as declined --note "…"'
|
|
36
|
+
].join('\n');
|
|
37
|
+
export const CLOSE_HELP = [
|
|
38
|
+
'close — close a question in your inbox.',
|
|
39
|
+
'',
|
|
40
|
+
CLOSE_USAGE,
|
|
41
|
+
'',
|
|
42
|
+
' <id-or-prefix> the question’s full UUID, or a >=4-char hex prefix over',
|
|
43
|
+
' YOUR OWN questions — an ambiguous prefix refuses and',
|
|
44
|
+
' lists the candidates, like `get memory`',
|
|
45
|
+
'',
|
|
46
|
+
' updated the question changed the tree — --block points at the',
|
|
47
|
+
' node that now answers it (required)',
|
|
48
|
+
' already-answered the tree already held the answer — --block points at',
|
|
49
|
+
' it (required)',
|
|
50
|
+
' declined you won’t act on it — no --block, and --note is required:',
|
|
51
|
+
' there is no answering node, so the words ARE the closure',
|
|
52
|
+
'',
|
|
53
|
+
' --block <ref> the memory’s full UUID, or an address ending in its',
|
|
54
|
+
' handle (slug-blind: price-list-abcd and bare abcd both',
|
|
55
|
+
' land) — NEVER a prefix, deliberately: the asker reads',
|
|
56
|
+
' this pointer back, so it must name one block exactly',
|
|
57
|
+
' --note "…" the closure in plain words (what the asker reads back);',
|
|
58
|
+
' optional beside a --block, required for declined — the',
|
|
59
|
+
' server refuses without it and says so',
|
|
60
|
+
'',
|
|
61
|
+
'The API is authoritative on the vocabulary — an unknown close value refuses',
|
|
62
|
+
'with the accepted set. Your inbox: lattris get questions'
|
|
63
|
+
].join('\n');
|
|
64
|
+
const closeVerb = {
|
|
65
|
+
verb: 'close',
|
|
66
|
+
nouns: ['question', 'questions'],
|
|
67
|
+
form: {
|
|
68
|
+
usage: CLOSE_USAGE,
|
|
69
|
+
idPositionals: [0],
|
|
70
|
+
flags: { '--close-as': 'closedAs', '--block': 'block', '--note': 'note' },
|
|
71
|
+
positionals: 1,
|
|
72
|
+
requiredFlags: ['closedAs'],
|
|
73
|
+
requireWhen: [
|
|
74
|
+
{
|
|
75
|
+
key: 'closedAs',
|
|
76
|
+
values: ['updated', 'already-answered'],
|
|
77
|
+
needs: 'block',
|
|
78
|
+
message: `--close-as {value} needs --block <ref>: the block that now answers the question.\n${CLOSE_USAGE}\n`
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
request: {
|
|
82
|
+
method: 'PATCH',
|
|
83
|
+
path: '/questions/{p0}',
|
|
84
|
+
body: {
|
|
85
|
+
closedAs: { flag: 'closedAs' },
|
|
86
|
+
closeBlockId: { flag: 'block' },
|
|
87
|
+
closeNote: { flag: 'note' }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
render: (q) => formatClosed(q)
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const getQuestions = {
|
|
94
|
+
verb: 'get',
|
|
95
|
+
nouns: ['question', 'questions'],
|
|
96
|
+
form: {
|
|
97
|
+
usage: 'Usage: lattris get questions [--mine]',
|
|
98
|
+
switches: ['--mine'],
|
|
99
|
+
positionals: 0,
|
|
100
|
+
unknownFlagIsUsage: true,
|
|
101
|
+
request: { method: 'GET', path: '/questions{--mine:/mine}' },
|
|
102
|
+
render: (qs, context) => qs.length > 0
|
|
103
|
+
? formatQuestionList(qs)
|
|
104
|
+
: context.on.has('--mine')
|
|
105
|
+
? "You have asked no questions on other people's trees."
|
|
106
|
+
: 'No questions on your tree.'
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
export const questions = {
|
|
110
|
+
name: 'questions',
|
|
111
|
+
words: { singular: 'question', plural: 'questions' },
|
|
112
|
+
help: CLOSE_HELP,
|
|
113
|
+
verbs: [askVerb, closeVerb, getQuestions]
|
|
114
|
+
};
|
|
115
|
+
export { CLOSE_USAGE };
|
|
116
|
+
//# sourceMappingURL=questions.js.map
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { audienceOf, formatAccessRow, formatAudiences, waitsForNameSentence } from "../render/rows.js";
|
|
2
|
+
import { renderRunHint, sanitizeForTerminal } from "../render/text.js";
|
|
3
|
+
import { consent } from "./consent.js";
|
|
4
|
+
const ALL_CONTACTS = 'all-contacts';
|
|
5
|
+
const AUDIENCE_WORDS = '--with is required. Use public, all-contacts, or one of your contact names or ids.\n';
|
|
6
|
+
const DENY_WORDS = '--to is required. Use all-contacts or one of your contact names or ids.\n';
|
|
7
|
+
const PUBLIC_CANNOT_BE_DENIED = 'public cannot be denied: a deny names a contact or all-contacts. To stop sharing with everyone, unshare --with public.\n';
|
|
8
|
+
export const SHARE_HELP = [
|
|
9
|
+
'share — allow an audience to see a memory: public, all your contacts,',
|
|
10
|
+
'or one contact. A contact needs no Lattris account; a linked contact also',
|
|
11
|
+
'sees it on the website.',
|
|
12
|
+
'',
|
|
13
|
+
'Usage:',
|
|
14
|
+
' lattris share memory <id-or-prefix> --with <public | all-contacts | contact> [--yes]',
|
|
15
|
+
'',
|
|
16
|
+
' --with public, all-contacts, or an exact contact name or id from',
|
|
17
|
+
' `lattris contact list`. The words public and all-contacts win',
|
|
18
|
+
' over a contact of that name; reach such a contact by its id.',
|
|
19
|
+
' group:<name> is reserved for groups, which are not built yet.',
|
|
20
|
+
' --yes required when not at a terminal; at a terminal you are always',
|
|
21
|
+
' asked, --yes or not.',
|
|
22
|
+
'',
|
|
23
|
+
'A deny on the memory beats any allow: the answer names who is still shut',
|
|
24
|
+
'out, and `lattris undeny` lifts it. Sharing with an audience that already',
|
|
25
|
+
'has access succeeds and changes nothing.',
|
|
26
|
+
'',
|
|
27
|
+
'Alias: publish → share'
|
|
28
|
+
].join('\n');
|
|
29
|
+
export const DENY_HELP = [
|
|
30
|
+
'deny — shut a memory to one contact or to all your contacts, whatever',
|
|
31
|
+
'else allows it. Nothing is asked: a deny is the safe direction.',
|
|
32
|
+
'',
|
|
33
|
+
'Usage:',
|
|
34
|
+
' lattris deny memory <id-or-prefix> --to <all-contacts | contact>',
|
|
35
|
+
'',
|
|
36
|
+
' --to all-contacts, or an exact contact name or id from',
|
|
37
|
+
' `lattris contact list`. public cannot be denied. group:<name>',
|
|
38
|
+
' is reserved for groups, which are not built yet.',
|
|
39
|
+
'',
|
|
40
|
+
'A deny cannot hold while the memory is public or shared on the website',
|
|
41
|
+
'with the person it would shut out; the refusal names the share in the',
|
|
42
|
+
'way. `lattris undeny` lifts a deny.'
|
|
43
|
+
].join('\n');
|
|
44
|
+
export const UNDENY_HELP = [
|
|
45
|
+
'undeny — lift a deny from a memory, for one contact or for all your',
|
|
46
|
+
'contacts. It opens the memory to whatever allows it, so it asks at a',
|
|
47
|
+
'terminal and needs --yes on a pipe.',
|
|
48
|
+
'',
|
|
49
|
+
'Usage:',
|
|
50
|
+
' lattris undeny memory <id-or-prefix> --to <all-contacts | contact> [--yes]'
|
|
51
|
+
].join('\n');
|
|
52
|
+
const isAccessGrant = (grant) => typeof grant === 'object' &&
|
|
53
|
+
grant !== null &&
|
|
54
|
+
'shutBy' in grant &&
|
|
55
|
+
Array.isArray(grant.shutBy);
|
|
56
|
+
const whoWasShared = (grant, context) => context.contact?.displayName !== undefined
|
|
57
|
+
? sanitizeForTerminal(context.contact.displayName)
|
|
58
|
+
: context.reserved === ALL_CONTACTS
|
|
59
|
+
? 'all contacts'
|
|
60
|
+
: String(grant.kind ?? '');
|
|
61
|
+
const shareSentences = (grant, context) => {
|
|
62
|
+
const lines = [
|
|
63
|
+
`Shared memory ${context.memory?.id ?? context.positionals[0]} with ${whoWasShared(grant, context)}`
|
|
64
|
+
];
|
|
65
|
+
if (!isAccessGrant(grant))
|
|
66
|
+
return lines;
|
|
67
|
+
if (grant.paired) {
|
|
68
|
+
lines.push(`${sanitizeForTerminal(grant.contactName ?? '')} is linked to an account, so the website shows it too.`);
|
|
69
|
+
}
|
|
70
|
+
else if (grant.unpairedBecause === 'owner-has-no-name') {
|
|
71
|
+
lines.push(waitsForNameSentence(grant.contactName ?? ''));
|
|
72
|
+
}
|
|
73
|
+
if (grant.shutBy.length > 0) {
|
|
74
|
+
const denies = grant.shutBy.map(formatAccessRow).join(', ');
|
|
75
|
+
lines.push(grant.kind === 'contact'
|
|
76
|
+
? `${sanitizeForTerminal(grant.contactName ?? '')} is still shut out by: ${denies}`
|
|
77
|
+
: `Still shut out by a deny of their own: ${grant.shutBy.map(audienceOf).join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
return lines;
|
|
80
|
+
};
|
|
81
|
+
const otherEffectWords = (asked) => (body, context) => {
|
|
82
|
+
if (body.code !== 'memory:withdraw-effect-mismatch')
|
|
83
|
+
return '';
|
|
84
|
+
const found = body.detail?.effect === 'deny' ? 'deny' : 'allow';
|
|
85
|
+
if (found === asked)
|
|
86
|
+
return '';
|
|
87
|
+
const id = context.memory?.id ?? context.positionals[0];
|
|
88
|
+
const who = context.contact?.id ?? ALL_CONTACTS;
|
|
89
|
+
return found === 'deny'
|
|
90
|
+
? `This is a deny, not a share. Lift it with: ${renderRunHint(['undeny', 'memory', id, '--to', who])}\n`
|
|
91
|
+
: `This is a share, not a deny. Withdraw it with: ${renderRunHint(['unshare', 'memory', id, '--with', who])}\n`;
|
|
92
|
+
};
|
|
93
|
+
const inTheWay = (body) => {
|
|
94
|
+
const rows = body.detail?.blockedBy;
|
|
95
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
96
|
+
return '';
|
|
97
|
+
return `In the way: ${rows.map(formatAccessRow).join(', ')}\n`;
|
|
98
|
+
};
|
|
99
|
+
const contactBody = (effect) => ({
|
|
100
|
+
kind: { value: 'contact' },
|
|
101
|
+
contactId: { context: 'contact.id' },
|
|
102
|
+
effect: { value: effect }
|
|
103
|
+
});
|
|
104
|
+
const shareVerb = {
|
|
105
|
+
verb: 'share',
|
|
106
|
+
nouns: ['memory', 'memories'],
|
|
107
|
+
form: {
|
|
108
|
+
usage: 'Usage: lattris share memory <id> --with <audience> [--yes]',
|
|
109
|
+
flags: { '--with': 'with' },
|
|
110
|
+
switches: ['--yes'],
|
|
111
|
+
positionals: 1,
|
|
112
|
+
idPositionals: [0],
|
|
113
|
+
steps: {
|
|
114
|
+
requireFlag: { key: 'with', message: AUDIENCE_WORDS },
|
|
115
|
+
pipedNeedsYes: {
|
|
116
|
+
message: 'Refusing to share without confirmation on a non-interactive session. Pass --yes to skip the prompt.\n'
|
|
117
|
+
},
|
|
118
|
+
contact: { from: ['flag:with'], publicWord: 'public', reserved: [ALL_CONTACTS] },
|
|
119
|
+
memory: 'at-tty',
|
|
120
|
+
confirm: {
|
|
121
|
+
question: 'Share "{title}" ({memory.id}) with {audience}? [y/N] ',
|
|
122
|
+
onNo: { text: 'Cancelled.\n', to: 'stderr' },
|
|
123
|
+
onInterrupted: { text: 'Cancelled.\n', exit: 1 }
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
request: {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
path: '/memories/{memory.id}/grants',
|
|
129
|
+
body: contactBody('allow'),
|
|
130
|
+
bodyWithPublic: { audience: 'public' },
|
|
131
|
+
bodyFor: { [ALL_CONTACTS]: { kind: 'all_contacts' } }
|
|
132
|
+
},
|
|
133
|
+
ambiguousRefusal: true,
|
|
134
|
+
refusalWords: inTheWay,
|
|
135
|
+
render: (grant, context) => shareSentences(grant, context).join('\n')
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const denyVerb = {
|
|
139
|
+
verb: 'deny',
|
|
140
|
+
nouns: ['memory', 'memories'],
|
|
141
|
+
form: {
|
|
142
|
+
usage: 'Usage: lattris deny memory <id> --to <all-contacts | contact>',
|
|
143
|
+
flags: { '--to': 'to' },
|
|
144
|
+
positionals: 1,
|
|
145
|
+
idPositionals: [0],
|
|
146
|
+
steps: {
|
|
147
|
+
requireFlag: { key: 'to', message: DENY_WORDS },
|
|
148
|
+
contact: {
|
|
149
|
+
from: ['flag:to'],
|
|
150
|
+
reserved: [ALL_CONTACTS],
|
|
151
|
+
refusedWords: { public: PUBLIC_CANNOT_BE_DENIED }
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
request: {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
path: '/memories/{memory.id}/grants',
|
|
157
|
+
body: contactBody('deny'),
|
|
158
|
+
bodyFor: { [ALL_CONTACTS]: { kind: 'all_contacts', effect: 'deny' } }
|
|
159
|
+
},
|
|
160
|
+
ambiguousRefusal: true,
|
|
161
|
+
refusalWords: inTheWay,
|
|
162
|
+
render: (_grant, context) => `Denied memory ${context.positionals[0]} to ${context.contact ? sanitizeForTerminal(context.contact.displayName) : 'all contacts'}`
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const undenyVerb = {
|
|
166
|
+
verb: 'undeny',
|
|
167
|
+
nouns: ['memory', 'memories'],
|
|
168
|
+
form: {
|
|
169
|
+
usage: 'Usage: lattris undeny memory <id> --to <all-contacts | contact> [--yes]',
|
|
170
|
+
flags: { '--to': 'to' },
|
|
171
|
+
switches: ['--yes'],
|
|
172
|
+
positionals: 1,
|
|
173
|
+
idPositionals: [0],
|
|
174
|
+
steps: {
|
|
175
|
+
requireFlag: { key: 'to', message: DENY_WORDS },
|
|
176
|
+
pipedNeedsYes: {
|
|
177
|
+
message: 'Refusing to lift a deny without confirmation on a non-interactive session. Pass --yes to skip the prompt.\n'
|
|
178
|
+
},
|
|
179
|
+
contact: {
|
|
180
|
+
from: ['flag:to'],
|
|
181
|
+
reserved: [ALL_CONTACTS],
|
|
182
|
+
refusedWords: { public: PUBLIC_CANNOT_BE_DENIED }
|
|
183
|
+
},
|
|
184
|
+
memory: 'at-tty',
|
|
185
|
+
confirm: {
|
|
186
|
+
question: 'Lift the deny on "{title}" ({memory.id}) for {audience}? [y/N] ',
|
|
187
|
+
onNo: { text: 'Cancelled.\n', to: 'stderr' },
|
|
188
|
+
onInterrupted: { text: 'Cancelled.\n', exit: 1 }
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
request: {
|
|
192
|
+
method: 'DELETE',
|
|
193
|
+
path: '/memories/{memory.id}/grants/contact/{contact.id}?effect=deny',
|
|
194
|
+
pathFor: { [ALL_CONTACTS]: '/memories/{memory.id}/grants/all-contacts?effect=deny' }
|
|
195
|
+
},
|
|
196
|
+
ambiguousRefusal: true,
|
|
197
|
+
refusalWords: otherEffectWords('deny'),
|
|
198
|
+
renderTo: 'stderr',
|
|
199
|
+
render: (_data, context) => `Lifted the deny on memory ${context.memory?.id ?? context.positionals[0]} for ${context.contact ? sanitizeForTerminal(context.contact.displayName) : 'all contacts'}`
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
export const UNSHARE_HELP = [
|
|
203
|
+
'unshare — withdraw an audience from a memory. No confirmation — withdrawal',
|
|
204
|
+
'is the safe direction.',
|
|
205
|
+
'',
|
|
206
|
+
'Usage:',
|
|
207
|
+
' lattris unshare memory <id-or-prefix> --with <public | all-contacts | contact>',
|
|
208
|
+
'',
|
|
209
|
+
' --with public, all-contacts, or an exact contact name or id from',
|
|
210
|
+
' `lattris contact list`. For a contact, the website row paired',
|
|
211
|
+
' with its allow goes too, and so does a website share written',
|
|
212
|
+
' before contact shares existed. group:<name> is reserved for',
|
|
213
|
+
' groups, which are not built yet.',
|
|
214
|
+
'',
|
|
215
|
+
'Bare (no --with): refuses and shows what the memory is currently shared',
|
|
216
|
+
'with, so you can see the state and the fix in one line. A deny is lifted',
|
|
217
|
+
'with `lattris undeny`, not here.',
|
|
218
|
+
'',
|
|
219
|
+
'Alias: unpublish → unshare'
|
|
220
|
+
].join('\n');
|
|
221
|
+
const unshareVerb = {
|
|
222
|
+
verb: 'unshare',
|
|
223
|
+
nouns: ['memory', 'memories'],
|
|
224
|
+
form: {
|
|
225
|
+
usage: 'Usage: lattris unshare memory <id> [--with <audience>]',
|
|
226
|
+
flags: { '--with': 'with' },
|
|
227
|
+
positionals: 1,
|
|
228
|
+
idPositionals: [0],
|
|
229
|
+
steps: {
|
|
230
|
+
contact: { from: ['flag:with'], publicWord: 'public', reserved: [ALL_CONTACTS] },
|
|
231
|
+
memory: 'always',
|
|
232
|
+
noAudienceReport: {
|
|
233
|
+
key: 'with',
|
|
234
|
+
first: AUDIENCE_WORDS,
|
|
235
|
+
second: '"{title}" ({memory.id}) is currently shared with: {audiences}\n'
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
request: {
|
|
239
|
+
method: 'DELETE',
|
|
240
|
+
path: '/memories/{memory.id}/grants/contact/{contact.id}?effect=allow',
|
|
241
|
+
pathWithPublic: '/memories/{memory.id}/grants/public',
|
|
242
|
+
pathFor: { [ALL_CONTACTS]: '/memories/{memory.id}/grants/all-contacts?effect=allow' },
|
|
243
|
+
fallback: {
|
|
244
|
+
status: 404,
|
|
245
|
+
path: '/memories/{memory.id}/grants/person/{contact.claimedPersonId}',
|
|
246
|
+
when: 'contact.linked'
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
ambiguousRefusal: true,
|
|
250
|
+
refusalWords: otherEffectWords('allow'),
|
|
251
|
+
renderTo: 'stderr',
|
|
252
|
+
render: (_data, context) => `Unshared memory ${context.memory?.id ?? ''} from ${context.contact
|
|
253
|
+
? sanitizeForTerminal(context.contact.displayName)
|
|
254
|
+
: context.reserved === ALL_CONTACTS
|
|
255
|
+
? 'all contacts'
|
|
256
|
+
: 'public'}`
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
export const audiencesOf = (memory) => formatAudiences(memory.audiences);
|
|
260
|
+
export const grants = {
|
|
261
|
+
name: 'grants',
|
|
262
|
+
words: { singular: 'grant', plural: 'grants' },
|
|
263
|
+
help: SHARE_HELP,
|
|
264
|
+
verbs: [shareVerb, unshareVerb, denyVerb, undenyVerb, consent]
|
|
265
|
+
};
|
|
266
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const VERB_ALIASES = {
|
|
2
|
+
read: 'get',
|
|
3
|
+
show: 'get',
|
|
4
|
+
view: 'get',
|
|
5
|
+
fetch: 'get',
|
|
6
|
+
remove: 'delete',
|
|
7
|
+
publish: 'share',
|
|
8
|
+
unpublish: 'unshare'
|
|
9
|
+
};
|
|
10
|
+
export const USAGE_LINES = [
|
|
11
|
+
' lattris login Sign in via the browser (PKCE) and store the session',
|
|
12
|
+
' lattris setup Transition this repo’s Claude to Lattris as its memory system',
|
|
13
|
+
' lattris use <name|path> Bind this directory to an actor (.lattris; bare `use` shows, --clear removes)',
|
|
14
|
+
' lattris whoami Print "<name> · #<lattris_no>" for the signed-in person',
|
|
15
|
+
' lattris set name "<value>" Set the name shown on your public page',
|
|
16
|
+
' lattris set root <id> Set the block a walker starts from on your tree (whoami shows it)',
|
|
17
|
+
' lattris set banned-words The words your own notes refuse (bare shows; words given replace; --clear)',
|
|
18
|
+
' lattris capture memory Capture a memory — `echo "…" | lattris capture memory`',
|
|
19
|
+
' lattris capture session <transcript> Save an AI conversation — the whole chain from its file',
|
|
20
|
+
' lattris capture recording Keep a recording \u2014 held audio, a source, or a transcript file',
|
|
21
|
+
' lattris get memories List your memories, newest first, with filters (--for <contact> for what they may see)',
|
|
22
|
+
' lattris contact create "<name>" Record a person or an AI you work with (--ai, --phone); add, list, get, update, delete',
|
|
23
|
+
' lattris invite <contact> Mint an invitation for one of your person contacts',
|
|
24
|
+
' lattris get invites List the invitations you have minted',
|
|
25
|
+
' lattris get memory <id> Read one memory back \u2014 words and edges (--links/--depth/--content)',
|
|
26
|
+
' lattris ask <handle> Leave a question on someone\u2019s tree \u2014 `echo "\u2026" | lattris ask <handle>`',
|
|
27
|
+
' lattris get questions The questions on your tree, open first (--mine: ones you asked)',
|
|
28
|
+
' lattris close question <id> Close a question (--close-as updated|declined|already-answered)',
|
|
29
|
+
' lattris share memory <id> Allow an audience to see it (--with public | all-contacts | <contact>)',
|
|
30
|
+
' lattris unshare memory <id> Withdraw an audience (--with public | all-contacts | <contact>)',
|
|
31
|
+
' lattris deny memory <id> Shut it to a contact or all contacts, whatever allows it (--to)',
|
|
32
|
+
' lattris undeny memory <id> Lift a deny (--to all-contacts | <contact>)',
|
|
33
|
+
' lattris get access <id> May a contact or a number see it, and because of what (--for)',
|
|
34
|
+
' lattris consent <id> Put a memory shared with you on your graph (--withdraw reverses)',
|
|
35
|
+
' lattris link <from> <to> State that one memory leads to another (--verb, --reason)',
|
|
36
|
+
' lattris unlink <from> <to> Remove that claim (--verb; the blocks stay)',
|
|
37
|
+
' lattris update edge <from> Rewrite an edge\u2019s reason (--verb, --to, --reason)',
|
|
38
|
+
' lattris update memory <id> Correct a memory — `echo "…" | lattris update memory <id> --base-fingerprint <fp>`',
|
|
39
|
+
' lattris update recording Replace a recording\u2019s transcript, or name its speakers',
|
|
40
|
+
' lattris get history <id> A note\u2019s history: every change, newest first, and the head fingerprint',
|
|
41
|
+
' lattris delete memory <id> Move a note to deleted items (its history stays); other types delete',
|
|
42
|
+
' lattris get deleted Your deleted items, with the fingerprint restore and erase need',
|
|
43
|
+
' lattris restore memory <id> Bring a note back, or an earlier revision (--revision, --base-fingerprint)',
|
|
44
|
+
' lattris erase memory <id> Permanently erase a note in deleted items. No undo.',
|
|
45
|
+
' lattris resend <request.json> Send a kept request again, exactly (a lost capture or overwrite)',
|
|
46
|
+
' lattris get datasets The datasets the API keeps, whether you hold rows, and their time range',
|
|
47
|
+
' lattris get dataset <name> One dataset: its identity, filters, fields and an example line',
|
|
48
|
+
' lattris get rows <name> One page of a dataset\u2019s rows (field=value filters, --from, --to, --limit)',
|
|
49
|
+
' lattris get row <name> <id> One row of a dataset',
|
|
50
|
+
' lattris capture rows <name> Save rows \u2014 newline-delimited JSON on stdin, sent in pieces of 5,000 lines',
|
|
51
|
+
' lattris delete row <name> <id> Remove one row of a dataset, permanently',
|
|
52
|
+
' lattris logout Forget the stored session'
|
|
53
|
+
];
|
|
54
|
+
export const COMMAND_WORDS = new Set([
|
|
55
|
+
'login',
|
|
56
|
+
'setup',
|
|
57
|
+
'use',
|
|
58
|
+
'whoami',
|
|
59
|
+
'set',
|
|
60
|
+
'capture',
|
|
61
|
+
'get',
|
|
62
|
+
'ask',
|
|
63
|
+
'close',
|
|
64
|
+
'delete',
|
|
65
|
+
'update',
|
|
66
|
+
'link',
|
|
67
|
+
'unlink',
|
|
68
|
+
'invite',
|
|
69
|
+
'share',
|
|
70
|
+
'unshare',
|
|
71
|
+
'deny',
|
|
72
|
+
'undeny',
|
|
73
|
+
'consent',
|
|
74
|
+
'restore',
|
|
75
|
+
'erase',
|
|
76
|
+
'resend',
|
|
77
|
+
'contact',
|
|
78
|
+
'logout',
|
|
79
|
+
'--as'
|
|
80
|
+
]);
|
|
81
|
+
//# sourceMappingURL=words.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { captureRows } from "../datasets/capture.js";
|
|
2
|
+
import { getRows } from "../datasets/page.js";
|
|
3
|
+
import { GET_DATASETS_HELP } from "../nouns/datasets.js";
|
|
4
|
+
export const datasetNouns = [
|
|
5
|
+
{
|
|
6
|
+
name: 'rows',
|
|
7
|
+
words: { singular: 'row', plural: 'rows' },
|
|
8
|
+
help: GET_DATASETS_HELP,
|
|
9
|
+
verbs: [
|
|
10
|
+
{ verb: 'get', nouns: ['rows'], handler: getRows },
|
|
11
|
+
{ verb: 'capture', nouns: ['rows'], handler: captureRows }
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
];
|
|
15
|
+
//# sourceMappingURL=datasets.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { LOGIN_HELP, WHOAMI_HELP, login, logout, whoami } from "../entry/session.js";
|
|
2
|
+
import { SET_HELP, setBannedWords, setName, setRoot } from "../entry/set.js";
|
|
3
|
+
import { setup } from "../entry/setup.js";
|
|
4
|
+
import { use } from "../entry/use.js";
|
|
5
|
+
export const entryNouns = [
|
|
6
|
+
{
|
|
7
|
+
name: 'session',
|
|
8
|
+
words: { singular: 'session', plural: 'sessions' },
|
|
9
|
+
help: LOGIN_HELP,
|
|
10
|
+
verbs: [
|
|
11
|
+
{ verb: 'login', handler: login },
|
|
12
|
+
{ verb: 'logout', handler: async (_cfg, io) => logout(io) },
|
|
13
|
+
{ verb: 'whoami', handler: whoami },
|
|
14
|
+
{ verb: 'setup', handler: setup },
|
|
15
|
+
{ verb: 'use', handler: use }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'person',
|
|
20
|
+
words: { singular: 'person', plural: 'persons' },
|
|
21
|
+
help: SET_HELP,
|
|
22
|
+
verbs: [
|
|
23
|
+
{ verb: 'set', nouns: ['name'], handler: setName },
|
|
24
|
+
{ verb: 'set', nouns: ['root'], handler: setRoot },
|
|
25
|
+
{ verb: 'set', nouns: ['banned-words'], handler: setBannedWords }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
export const entryVerbs = {
|
|
30
|
+
login: { help: LOGIN_HELP, helpBeforeAuth: true },
|
|
31
|
+
logout: {
|
|
32
|
+
help: [
|
|
33
|
+
'Usage: lattris logout',
|
|
34
|
+
'',
|
|
35
|
+
'Signs this machine dir out: removes its session file, so the next',
|
|
36
|
+
'command that needs a session asks for lattris login. Nothing is sent',
|
|
37
|
+
'to the server; with no session there it still prints "Logged out."',
|
|
38
|
+
'Exit 1 when the session file cannot be removed.'
|
|
39
|
+
].join('\n'),
|
|
40
|
+
helpBeforeAuth: true
|
|
41
|
+
},
|
|
42
|
+
whoami: { help: WHOAMI_HELP, helpBeforeAuth: true },
|
|
43
|
+
setup: {},
|
|
44
|
+
use: {},
|
|
45
|
+
set: {
|
|
46
|
+
help: SET_HELP,
|
|
47
|
+
bareIsHelp: true,
|
|
48
|
+
badNoun: { stderr: 'Unknown field: {noun}\n\n', stdout: `${SET_HELP}\n` }
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { datasetNouns } from "./datasets.js";
|
|
2
|
+
import { entryNouns, entryVerbs } from "./entry.js";
|
|
3
|
+
import { memoryNouns, memoryVerbs } from "./memory.js";
|
|
4
|
+
import { listNouns, listVerbs } from "../nouns/index.js";
|
|
5
|
+
export const registry = {
|
|
6
|
+
nouns: [...listNouns, ...memoryNouns, ...entryNouns, ...datasetNouns],
|
|
7
|
+
verbs: { ...listVerbs, ...memoryVerbs, ...entryVerbs }
|
|
8
|
+
};
|
|
9
|
+
export const commandWordsOf = (r) => [
|
|
10
|
+
...new Set([
|
|
11
|
+
...r.nouns
|
|
12
|
+
.filter((n) => n.grammar === 'noun-first')
|
|
13
|
+
.flatMap((n) => [n.words.singular, ...(n.words.aliases ?? [])]),
|
|
14
|
+
...r.nouns.filter((n) => n.grammar !== 'noun-first').flatMap((n) => n.verbs.map((v) => v.verb))
|
|
15
|
+
])
|
|
16
|
+
];
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { CAPTURE_ROWS_HELP } from "../datasets/capture.js";
|
|
2
|
+
import { DELETE_ROW_HELP, GET_DATASETS_HELP } from "../nouns/datasets.js";
|
|
3
|
+
import { CAPTURE_INDEX, CAPTURE_NOTE_HELP, CAPTURE_SESSION_HELP } from "../memory/capture-help.js";
|
|
4
|
+
import { asksForSessionForm, captureMemory } from "../memory/capture-note.js";
|
|
5
|
+
import { DELETE_HELP, deleteMemory } from "../memory/delete.js";
|
|
6
|
+
import { ERASE_HELP, eraseMemory } from "../memory/erase.js";
|
|
7
|
+
import { GET_ACCESS_HELP, getAccess } from "../memory/access.js";
|
|
8
|
+
import { GET_HELP } from "../memory/get-help.js";
|
|
9
|
+
import { getMemories } from "../memory/get.js";
|
|
10
|
+
import { getDeleted, getHistory } from "../memory/history.js";
|
|
11
|
+
import { CAPTURE_RECORDING_HELP, captureRecording } from "../memory/recording.js";
|
|
12
|
+
import { updateRecording } from "../memory/recording-update.js";
|
|
13
|
+
import { RESEND_HELP, resend } from "../memory/resend.js";
|
|
14
|
+
import { RESTORE_HELP, restoreMemory } from "../memory/restore.js";
|
|
15
|
+
import { UPDATE_HELP } from "../memory/update-help.js";
|
|
16
|
+
import { updateMemory } from "../memory/update.js";
|
|
17
|
+
export const memoryNouns = [
|
|
18
|
+
{
|
|
19
|
+
name: 'memories',
|
|
20
|
+
words: { singular: 'memory', plural: 'memories', aliases: ['note'] },
|
|
21
|
+
help: GET_HELP,
|
|
22
|
+
verbs: [
|
|
23
|
+
{ verb: 'capture', nouns: ['note', 'memory'], handler: captureMemory },
|
|
24
|
+
{
|
|
25
|
+
verb: 'capture',
|
|
26
|
+
nouns: ['session'],
|
|
27
|
+
handler: (cfg, io, rest) => captureMemory(cfg, io, ['--type', 'session', ...rest])
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
verb: 'get',
|
|
31
|
+
nouns: ['memory'],
|
|
32
|
+
handler: (cfg, io, rest) => getMemories(cfg, io, rest, 'memory')
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
verb: 'get',
|
|
36
|
+
nouns: ['memories'],
|
|
37
|
+
handler: (cfg, io, rest) => getMemories(cfg, io, rest, 'memories')
|
|
38
|
+
},
|
|
39
|
+
{ verb: 'get', nouns: ['history'], handler: getHistory },
|
|
40
|
+
{ verb: 'get', nouns: ['deleted'], handler: getDeleted },
|
|
41
|
+
{ verb: 'get', nouns: ['access'], handler: getAccess },
|
|
42
|
+
{ verb: 'update', nouns: ['memory', 'memories'], handler: updateMemory },
|
|
43
|
+
{ verb: 'delete', nouns: ['memory', 'memories'], handler: deleteMemory },
|
|
44
|
+
{ verb: 'restore', nouns: ['memory', 'memories', 'note'], handler: restoreMemory },
|
|
45
|
+
{ verb: 'erase', nouns: ['memory', 'memories', 'note'], handler: eraseMemory },
|
|
46
|
+
{ verb: 'resend', handler: resend }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'recordings',
|
|
51
|
+
words: { singular: 'recording', plural: 'recordings' },
|
|
52
|
+
help: CAPTURE_RECORDING_HELP,
|
|
53
|
+
verbs: [
|
|
54
|
+
{ verb: 'capture', nouns: ['recording'], handler: captureRecording },
|
|
55
|
+
{ verb: 'update', nouns: ['recording', 'recordings'], handler: updateRecording }
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
const captureHelp = (args) => {
|
|
60
|
+
if (args[0] === 'rows')
|
|
61
|
+
return CAPTURE_ROWS_HELP;
|
|
62
|
+
if (args[0] === 'session' || asksForSessionForm(args))
|
|
63
|
+
return CAPTURE_SESSION_HELP;
|
|
64
|
+
if (args[0] === 'note' || args[0] === 'memory')
|
|
65
|
+
return CAPTURE_NOTE_HELP;
|
|
66
|
+
if (args[0] === 'recording')
|
|
67
|
+
return CAPTURE_RECORDING_HELP;
|
|
68
|
+
return CAPTURE_INDEX;
|
|
69
|
+
};
|
|
70
|
+
export const memoryVerbs = {
|
|
71
|
+
capture: {
|
|
72
|
+
help: captureHelp,
|
|
73
|
+
helpBeforeAuth: true,
|
|
74
|
+
bareIsHelp: true,
|
|
75
|
+
badNoun: { stderr: 'Unknown capture form: {noun}\n\n', stdout: `${CAPTURE_INDEX}\n` }
|
|
76
|
+
},
|
|
77
|
+
get: {
|
|
78
|
+
help: (args) => ['datasets', 'dataset', 'rows', 'row'].includes(args[0])
|
|
79
|
+
? GET_DATASETS_HELP
|
|
80
|
+
: args[0] === 'access'
|
|
81
|
+
? GET_ACCESS_HELP
|
|
82
|
+
: GET_HELP,
|
|
83
|
+
bareIsHelp: true,
|
|
84
|
+
badNoun: {
|
|
85
|
+
stderr: 'Usage: lattris get memories | lattris get memory <id> | lattris get access <id> --for <contact | number> | lattris get history <id> | lattris get deleted | lattris get invites | lattris get questions | lattris get datasets | lattris get dataset <name> | lattris get rows <name> | lattris get row <name> <id>\n'
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
update: {
|
|
89
|
+
help: UPDATE_HELP,
|
|
90
|
+
bareIsHelp: true,
|
|
91
|
+
badNoun: { stderr: 'Usage: lattris update memory <id>\n' }
|
|
92
|
+
},
|
|
93
|
+
delete: {
|
|
94
|
+
help: (args) => (args[0] === 'row' ? DELETE_ROW_HELP : DELETE_HELP),
|
|
95
|
+
bareIsHelp: true,
|
|
96
|
+
badNoun: {
|
|
97
|
+
stderr: 'Usage: lattris delete memory <id> [--yes] | lattris delete row <name> <id> [--yes]\n'
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
restore: {
|
|
101
|
+
help: RESTORE_HELP,
|
|
102
|
+
helpBeforeAuth: true,
|
|
103
|
+
bareIsHelp: true,
|
|
104
|
+
badNoun: {
|
|
105
|
+
stderr: 'Usage: lattris restore memory <id> --revision <n> --base-fingerprint <head>\n'
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
erase: {
|
|
109
|
+
help: ERASE_HELP,
|
|
110
|
+
helpBeforeAuth: true,
|
|
111
|
+
bareIsHelp: true,
|
|
112
|
+
badNoun: { stderr: 'Usage: lattris erase memory <id> --base-fingerprint <head> [--yes]\n' }
|
|
113
|
+
},
|
|
114
|
+
resend: { help: RESEND_HELP, helpBeforeAuth: true }
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=memory.js.map
|