lattris 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/build.json +4 -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 +181 -0
- package/dist/dispatch-words.js +137 -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 -6877
- package/dist/memory/capture-common.js +67 -0
- package/dist/memory/capture-help.js +186 -0
- package/dist/memory/capture-note.js +190 -0
- package/dist/memory/capture-session.js +166 -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 +63 -0
- package/dist/memory/get.js +213 -0
- package/dist/memory/history.js +134 -0
- package/dist/memory/recording-update.js +128 -0
- package/dist/memory/recording.js +249 -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 +85 -0
- package/dist/memory/update-help.js +126 -0
- package/dist/memory/update.js +220 -0
- package/dist/memory/views.js +138 -0
- package/dist/nouns/consent.js +29 -0
- package/dist/nouns/contact.js +142 -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 +34 -0
- package/dist/nouns/invite.js +98 -0
- package/dist/nouns/questions.js +116 -0
- package/dist/nouns/share.js +105 -0
- package/dist/nouns/words.js +76 -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 +110 -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 +49 -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 +4 -5
- package/dist/save-session-chain.js +0 -136
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { formatContact } from "../render/rows.js";
|
|
2
|
+
import { verbsOf } from "./form.js";
|
|
3
|
+
const CONTACT_HELP = [
|
|
4
|
+
'contact — the people and AIs you work with. A contact is yours alone: it',
|
|
5
|
+
'names someone so a block can say whose expression it is, and an AI role',
|
|
6
|
+
'so its contact can point at its page.',
|
|
7
|
+
'',
|
|
8
|
+
'Usage:',
|
|
9
|
+
' lattris contact add --name "<v>" [--email <v>] [--ai]',
|
|
10
|
+
' record a person by name; --email keeps',
|
|
11
|
+
' their address as your own note',
|
|
12
|
+
' lattris contact create "<name>" [--ai] [--email <v>]',
|
|
13
|
+
' the same, with the name first',
|
|
14
|
+
' lattris contact list every contact of yours, as the API sends them',
|
|
15
|
+
' 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
|
+
' it at a block of yours (an AI role at its page)',
|
|
19
|
+
'',
|
|
20
|
+
' --email <v> the address you keep for this contact. It is your record',
|
|
21
|
+
' alone: the contact never sees it, nobody else reads it,',
|
|
22
|
+
' and changing it asks nobody',
|
|
23
|
+
' --root <block-id> the full id of one of your own blocks, never a prefix; the',
|
|
24
|
+
' API refuses a block you do not own',
|
|
25
|
+
'',
|
|
26
|
+
'A contact shows "linked" once it stands for a Lattris account: yours are',
|
|
27
|
+
'linked when someone accepts the invitation you minted for that contact,',
|
|
28
|
+
'and the person who invited you appears among your contacts, linked, the',
|
|
29
|
+
'moment you accept. Sharing with --with needs a linked contact.',
|
|
30
|
+
'',
|
|
31
|
+
'Another person’s contact is not yours to read or change: the API answers',
|
|
32
|
+
'not found. A block names a contact with --contributor <id>, on capture and',
|
|
33
|
+
'on update memory alike.'
|
|
34
|
+
].join('\n');
|
|
35
|
+
const CONTACT_UPDATE_USAGE = 'Usage: lattris contact update <id> [--name "<v>"] [--email <v> | --clear-email] [--ai | --person] [--root <block-id>]';
|
|
36
|
+
const contactRow = (c) => formatContact(c);
|
|
37
|
+
export const contacts = {
|
|
38
|
+
name: 'contacts',
|
|
39
|
+
words: { singular: 'contact', plural: 'contacts' },
|
|
40
|
+
grammar: 'noun-first',
|
|
41
|
+
help: CONTACT_HELP,
|
|
42
|
+
helpBeforeAuth: true,
|
|
43
|
+
unknownVerb: 'Unknown contact verb: {verb}\n\n' + CONTACT_HELP + '\n',
|
|
44
|
+
verbs: verbsOf({
|
|
45
|
+
add: {
|
|
46
|
+
usage: 'Usage: lattris contact add --name "<v>" [--email <v>] [--ai]',
|
|
47
|
+
flags: { '--name': 'name', '--email': 'email' },
|
|
48
|
+
switches: ['--ai'],
|
|
49
|
+
positionals: 0,
|
|
50
|
+
requiredFlags: ['name'],
|
|
51
|
+
request: {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
path: '/contacts',
|
|
54
|
+
body: {
|
|
55
|
+
displayName: { flag: 'name' },
|
|
56
|
+
type: { switch: '--ai', value: 'ai' },
|
|
57
|
+
email: { flag: 'email' }
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
render: (c) => `Recorded contact ${contactRow(c)}`
|
|
61
|
+
},
|
|
62
|
+
create: {
|
|
63
|
+
usage: 'Usage: lattris contact create "<name>" [--ai] [--email <v>]',
|
|
64
|
+
flags: { '--email': 'email' },
|
|
65
|
+
switches: ['--ai'],
|
|
66
|
+
positionals: 1,
|
|
67
|
+
request: {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
path: '/contacts',
|
|
70
|
+
body: {
|
|
71
|
+
displayName: { positional: 0 },
|
|
72
|
+
type: { switch: '--ai', value: 'ai' },
|
|
73
|
+
email: { flag: 'email' }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
render: (c) => `Recorded contact ${contactRow(c)}`
|
|
77
|
+
},
|
|
78
|
+
list: {
|
|
79
|
+
usage: 'Usage: lattris contact list',
|
|
80
|
+
positionals: 0,
|
|
81
|
+
unknownFlagIsUsage: true,
|
|
82
|
+
request: { method: 'GET', path: '/contacts' },
|
|
83
|
+
render: (list) => list.length === 0
|
|
84
|
+
? 'No contacts yet. `lattris contact create "<name>"` records one.'
|
|
85
|
+
: list.map(formatContact).join('\n')
|
|
86
|
+
},
|
|
87
|
+
get: {
|
|
88
|
+
usage: 'Usage: lattris contact get <id>',
|
|
89
|
+
idPositionals: [0],
|
|
90
|
+
positionals: 1,
|
|
91
|
+
unknownFlagIsUsage: true,
|
|
92
|
+
request: { method: 'GET', path: '/contacts' },
|
|
93
|
+
pickFromList: {
|
|
94
|
+
none: 'No contact {typed} among yours. {hint:contact list} shows them.\n',
|
|
95
|
+
ambiguous: '{typed} is ambiguous among your contacts:\n{matches}\n'
|
|
96
|
+
},
|
|
97
|
+
render: contactRow
|
|
98
|
+
},
|
|
99
|
+
update: {
|
|
100
|
+
usage: CONTACT_UPDATE_USAGE,
|
|
101
|
+
flags: { '--name': 'name', '--root': 'root', '--email': 'email' },
|
|
102
|
+
switches: ['--ai', '--person', '--clear-email'],
|
|
103
|
+
positionals: 1,
|
|
104
|
+
uuidPositionals: [0],
|
|
105
|
+
idPositionals: [0],
|
|
106
|
+
contradictions: [
|
|
107
|
+
{
|
|
108
|
+
a: '--ai',
|
|
109
|
+
b: '--person',
|
|
110
|
+
message: '--ai and --person contradict each other; pass one.\n'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
a: '--clear-email',
|
|
114
|
+
b: 'email',
|
|
115
|
+
message: '--email and --clear-email contradict each other; pass one.\n'
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
uuidFlags: [
|
|
119
|
+
{
|
|
120
|
+
key: 'root',
|
|
121
|
+
message: '--root takes the full id of one of your own blocks, never a prefix: {value}\n'
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
request: {
|
|
125
|
+
method: 'PATCH',
|
|
126
|
+
path: '/contacts/{p0}',
|
|
127
|
+
body: {
|
|
128
|
+
type: [
|
|
129
|
+
{ switch: '--ai', value: 'ai' },
|
|
130
|
+
{ switch: '--person', value: 'person' }
|
|
131
|
+
],
|
|
132
|
+
rootBlockId: { flag: 'root' },
|
|
133
|
+
displayName: { flag: 'name' },
|
|
134
|
+
email: [{ flag: 'email' }, { switch: '--clear-email', value: null }]
|
|
135
|
+
},
|
|
136
|
+
emptyBodyMessage: `Nothing to update: pass --name, --email, --clear-email, --ai, --person or --root.\n${CONTACT_UPDATE_USAGE}\n`
|
|
137
|
+
},
|
|
138
|
+
render: (c) => `Updated contact ${contactRow(c)}`
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=contact.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { formatCatalogue, formatDescription, formatRow } from "../render/datasets.js";
|
|
2
|
+
import { sanitizeForTerminal } from "../render/text.js";
|
|
3
|
+
export const GET_DATASETS_HELP = [
|
|
4
|
+
'get — read your datasets: what the API keeps, and your rows in it.',
|
|
5
|
+
'',
|
|
6
|
+
'Usage:',
|
|
7
|
+
' lattris get datasets every dataset the API takes, whether you hold',
|
|
8
|
+
' rows there, and their earliest and latest time',
|
|
9
|
+
' lattris get dataset <name> one dataset: its identity, its filters, each',
|
|
10
|
+
' field with its type and bounds, an example line',
|
|
11
|
+
' lattris get rows <name> [field=value …] [--from <time>] [--to <time>] [--limit <n>] [--cursor <token>]',
|
|
12
|
+
' one page of your rows, in the dataset’s own order',
|
|
13
|
+
' lattris get row <name> <id> one row, by its id',
|
|
14
|
+
'',
|
|
15
|
+
' field=value a filter, sent as typed, split at the first =. The dataset',
|
|
16
|
+
' names its filters (get dataset <name>); the API refuses a',
|
|
17
|
+
' missing, unknown or doubled one, or a bad value, and names',
|
|
18
|
+
' what it takes. A word with no = is refused here.',
|
|
19
|
+
' --from <time> the page starts at this time; --to <time> ends it',
|
|
20
|
+
' --limit <n> rows a page; the dataset names its default and its maximum',
|
|
21
|
+
' --cursor <token> the nextCursor of the page before. At a terminal, a page with',
|
|
22
|
+
' more after it prints the command that reads the next one.',
|
|
23
|
+
'',
|
|
24
|
+
'On a pipe each answer is the API’s JSON, byte for byte. At a terminal: one line',
|
|
25
|
+
'per dataset, the description labelled, one row per line, a row one field per line.',
|
|
26
|
+
'',
|
|
27
|
+
'The API owns every name, filter and field; the CLI keeps no list of them.',
|
|
28
|
+
'To save rows: lattris capture rows <name>. To remove one: lattris delete row <name> <id>.'
|
|
29
|
+
].join('\n');
|
|
30
|
+
export const DELETE_ROW_HELP = [
|
|
31
|
+
'delete row — remove one row of a dataset, permanently.',
|
|
32
|
+
'',
|
|
33
|
+
'Usage:',
|
|
34
|
+
' lattris delete row <name> <id> [--yes]',
|
|
35
|
+
'',
|
|
36
|
+
'A row has no deleted items and no history: the delete cannot be undone.',
|
|
37
|
+
'',
|
|
38
|
+
' --yes required when not at a terminal (there is no prompt to answer',
|
|
39
|
+
' there); at a terminal you are always asked.',
|
|
40
|
+
'',
|
|
41
|
+
'A row that other rows still reference is refused, and the refusal names the',
|
|
42
|
+
'datasets that hold them: delete those first, or keep it.',
|
|
43
|
+
'',
|
|
44
|
+
'Alias: remove → delete'
|
|
45
|
+
].join('\n');
|
|
46
|
+
const NAME_AND_ID = [0, 1];
|
|
47
|
+
export const datasets = {
|
|
48
|
+
name: 'datasets',
|
|
49
|
+
words: { singular: 'dataset', plural: 'datasets' },
|
|
50
|
+
help: GET_DATASETS_HELP,
|
|
51
|
+
verbs: [
|
|
52
|
+
{
|
|
53
|
+
verb: 'get',
|
|
54
|
+
nouns: ['datasets'],
|
|
55
|
+
form: {
|
|
56
|
+
usage: 'Usage: lattris get datasets',
|
|
57
|
+
positionals: 0,
|
|
58
|
+
request: { method: 'GET', path: '/datasets' },
|
|
59
|
+
render: (entries) => formatCatalogue(entries)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
verb: 'get',
|
|
64
|
+
nouns: ['dataset'],
|
|
65
|
+
form: {
|
|
66
|
+
usage: 'Usage: lattris get dataset <name>',
|
|
67
|
+
positionals: 1,
|
|
68
|
+
idPositionals: [0],
|
|
69
|
+
request: { method: 'GET', path: '/datasets/{p0}' },
|
|
70
|
+
render: (description) => formatDescription(description)
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
verb: 'get',
|
|
75
|
+
nouns: ['row'],
|
|
76
|
+
form: {
|
|
77
|
+
usage: 'Usage: lattris get row <name> <id>',
|
|
78
|
+
positionals: 2,
|
|
79
|
+
idPositionals: NAME_AND_ID,
|
|
80
|
+
request: { method: 'GET', path: '/datasets/{p0}/rows/{p1}' },
|
|
81
|
+
render: (row) => formatRow(row)
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
verb: 'delete',
|
|
86
|
+
nouns: ['row'],
|
|
87
|
+
form: {
|
|
88
|
+
usage: 'Usage: lattris delete row <name> <id> [--yes]',
|
|
89
|
+
switches: ['--yes'],
|
|
90
|
+
positionals: 2,
|
|
91
|
+
idPositionals: NAME_AND_ID,
|
|
92
|
+
steps: {
|
|
93
|
+
pipedNeedsYes: {
|
|
94
|
+
message: 'Refusing to permanently delete without confirmation on a non-interactive session. Pass --yes to skip the prompt.\n'
|
|
95
|
+
},
|
|
96
|
+
confirm: {
|
|
97
|
+
question: 'Delete row {p1} of {p0} permanently? [y/N] ',
|
|
98
|
+
onNo: { text: 'Cancelled.\n', to: 'stderr' },
|
|
99
|
+
onInterrupted: { text: 'Cancelled.\n', exit: 1 }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
request: { method: 'DELETE', path: '/datasets/{p0}/rows/{p1}' },
|
|
103
|
+
renderTo: 'stderr',
|
|
104
|
+
render: (_answer, context) => `Deleted row ${sanitizeForTerminal(context.positionals[1])} of ${sanitizeForTerminal(context.positionals[0])}`
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=datasets.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { formatAmendedEdge, formatEdgeReceipt } from "../render/rows.js";
|
|
2
|
+
import { sanitizeForTerminal } from "../render/text.js";
|
|
3
|
+
const LINK_HELP = [
|
|
4
|
+
'link — state that one memory leads to another.',
|
|
5
|
+
'',
|
|
6
|
+
'An edge is a directed claim: FROM leads to TO, under a verb the server’s',
|
|
7
|
+
'registry owns. The reason is written to the FUTURE WALKER — when is this',
|
|
8
|
+
'edge worth following («read this next if …») — routing guidance, never a',
|
|
9
|
+
'restatement of the relationship. Every edge states why it exists, so',
|
|
10
|
+
'--reason is required: there is no default and the CLI will never invent',
|
|
11
|
+
'one.',
|
|
12
|
+
'',
|
|
13
|
+
'One family, three verbs:',
|
|
14
|
+
'',
|
|
15
|
+
' lattris link <from> <to> --verb <verb> --reason "..." create the edge',
|
|
16
|
+
' lattris unlink <from> <to> --verb <verb> remove it',
|
|
17
|
+
' lattris update edge <from> --verb <verb> --to <to> --reason "..."',
|
|
18
|
+
' rewrite the reason (see update)',
|
|
19
|
+
'',
|
|
20
|
+
' <from> the cause-side block — full id or a >=4-char prefix; an',
|
|
21
|
+
' ambiguous prefix refuses and lists the matches',
|
|
22
|
+
' <to> the effect-side block — the FULL id (the wire addresses the',
|
|
23
|
+
' edge by it; it does not search)',
|
|
24
|
+
' --verb required; the server’s registry is the ONE authority on the',
|
|
25
|
+
' vocabulary — an unknown verb refuses WITH the accepted set',
|
|
26
|
+
' for the exact pair sent, and the CLI carries no copy',
|
|
27
|
+
' --reason link only; required, one line — why this edge exists',
|
|
28
|
+
'',
|
|
29
|
+
'unlink states WHICH edge (from, verb, to), never why — it takes no',
|
|
30
|
+
'--reason. Deleting an edge deletes the claim; the blocks stay.',
|
|
31
|
+
'',
|
|
32
|
+
'linked → prints the edge as written. unlinked → confirms the identity.'
|
|
33
|
+
].join('\n');
|
|
34
|
+
const TO_MESSAGE = '<to> must be the FULL block id (the wire addresses the edge by it; it does not search): {value}\n';
|
|
35
|
+
const linkVerb = {
|
|
36
|
+
verb: 'link',
|
|
37
|
+
form: {
|
|
38
|
+
usage: 'Usage: lattris link <from> <to> --verb <verb> --reason "..."',
|
|
39
|
+
flags: { '--verb': 'verb', '--reason': 'reason' },
|
|
40
|
+
positionals: 2,
|
|
41
|
+
idPositionals: [0],
|
|
42
|
+
requiredFlags: ['verb', 'reason'],
|
|
43
|
+
missingFlagsMessage: true,
|
|
44
|
+
uuidPositionalMessages: [{ index: 1, message: TO_MESSAGE }],
|
|
45
|
+
request: {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
path: '/memories/{p0}/edges',
|
|
48
|
+
body: { verb: { flag: 'verb' }, toBlockId: { positional: 1 }, reason: { flag: 'reason' } }
|
|
49
|
+
},
|
|
50
|
+
ambiguousRefusal: true,
|
|
51
|
+
render: (e) => formatEdgeReceipt('Linked.', e)
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const unlinkVerb = {
|
|
55
|
+
verb: 'unlink',
|
|
56
|
+
form: {
|
|
57
|
+
usage: 'Usage: lattris unlink <from> <to> --verb <verb>',
|
|
58
|
+
flags: { '--verb': 'verb' },
|
|
59
|
+
positionals: 2,
|
|
60
|
+
idPositionals: [0],
|
|
61
|
+
requiredFlags: ['verb'],
|
|
62
|
+
missingFlagsMessage: true,
|
|
63
|
+
uuidPositionalMessages: [{ index: 1, message: TO_MESSAGE }],
|
|
64
|
+
request: {
|
|
65
|
+
method: 'DELETE',
|
|
66
|
+
path: '/memories/{p0}/edges',
|
|
67
|
+
body: { verb: { flag: 'verb' }, toBlockId: { positional: 1 } }
|
|
68
|
+
},
|
|
69
|
+
ambiguousRefusal: true,
|
|
70
|
+
renderAlways: true,
|
|
71
|
+
render: (_e, typed) => `Unlinked. (verb: ${sanitizeForTerminal(typed.flags.verb ?? '')})\n from ${typed.positionals[0]}\n to ${typed.positionals[1]}`
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const updateEdge = {
|
|
75
|
+
verb: 'update',
|
|
76
|
+
nouns: ['edge', 'edges'],
|
|
77
|
+
form: {
|
|
78
|
+
usage: 'Usage: lattris update edge <id> --verb <verb> --to <id> --reason "..."',
|
|
79
|
+
idPositionals: [0],
|
|
80
|
+
flags: { '--verb': 'verb', '--to': 'to', '--reason': 'reason' },
|
|
81
|
+
positionals: 1,
|
|
82
|
+
requiredFlags: ['verb', 'to', 'reason'],
|
|
83
|
+
missingFlagsMessage: true,
|
|
84
|
+
uuidFlags: [
|
|
85
|
+
{
|
|
86
|
+
key: 'to',
|
|
87
|
+
message: '--to must be the FULL block id from the edge as read (never a prefix): {value}\n'
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
request: {
|
|
91
|
+
method: 'PATCH',
|
|
92
|
+
path: '/memories/{p0}/edges',
|
|
93
|
+
body: { verb: { flag: 'verb' }, toBlockId: { flag: 'to' }, reason: { flag: 'reason' } }
|
|
94
|
+
},
|
|
95
|
+
ambiguousRefusal: true,
|
|
96
|
+
render: (e) => formatAmendedEdge(e)
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
export { LINK_HELP };
|
|
100
|
+
export const edges = {
|
|
101
|
+
name: 'edges',
|
|
102
|
+
words: { singular: 'edge', plural: 'edges' },
|
|
103
|
+
help: LINK_HELP,
|
|
104
|
+
verbs: [linkVerb, unlinkVerb, updateEdge]
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=edges.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { verbsOf } from "./form.js";
|
|
2
|
+
export { audiencesOf } from "./share.js";
|
|
3
|
+
import { contacts } from "./contact.js";
|
|
4
|
+
import { CONSENT_HELP } from "./consent.js";
|
|
5
|
+
import { datasets } from "./datasets.js";
|
|
6
|
+
import { LINK_HELP, edges } from "./edges.js";
|
|
7
|
+
import { INVITE_HELP, invites } from "./invite.js";
|
|
8
|
+
import { ASK_HELP, CLOSE_HELP, CLOSE_USAGE, questions } from "./questions.js";
|
|
9
|
+
import { SHARE_HELP, UNSHARE_HELP, grants } from "./share.js";
|
|
10
|
+
export const listNouns = [contacts, questions, edges, invites, grants, datasets];
|
|
11
|
+
export const listVerbs = {
|
|
12
|
+
ask: { help: ASK_HELP, helpBeforeAuth: true },
|
|
13
|
+
close: {
|
|
14
|
+
help: CLOSE_HELP,
|
|
15
|
+
helpBeforeAuth: true,
|
|
16
|
+
bareIsHelp: true,
|
|
17
|
+
badNoun: { stderr: `${CLOSE_USAGE}\n` }
|
|
18
|
+
},
|
|
19
|
+
link: { help: LINK_HELP, helpBeforeAuth: true, bareIsHelp: true },
|
|
20
|
+
unlink: { help: LINK_HELP, helpBeforeAuth: true, bareIsHelp: true },
|
|
21
|
+
invite: { help: INVITE_HELP },
|
|
22
|
+
consent: { help: CONSENT_HELP, helpBeforeAuth: true },
|
|
23
|
+
share: {
|
|
24
|
+
help: SHARE_HELP,
|
|
25
|
+
bareIsHelp: true,
|
|
26
|
+
badNoun: { stderr: 'Usage: lattris share memory <id> --with <audience> [--yes]\n' }
|
|
27
|
+
},
|
|
28
|
+
unshare: {
|
|
29
|
+
help: UNSHARE_HELP,
|
|
30
|
+
bareIsHelp: true,
|
|
31
|
+
badNoun: { stderr: 'Usage: lattris unshare memory <id> [--with <audience>]\n' }
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { formatContact } from "../render/rows.js";
|
|
2
|
+
import { sanitizeForTerminal } from "../render/text.js";
|
|
3
|
+
const inviteLink = (webUrl, token) => `${webUrl}/i/${token}`;
|
|
4
|
+
const formatInvite = (webUrl, i) => {
|
|
5
|
+
const label = i.targetName ? ` — ${sanitizeForTerminal(i.targetName)}` : '';
|
|
6
|
+
if (i.redeemedAt) {
|
|
7
|
+
const who = i.redeemedBy
|
|
8
|
+
? `${sanitizeForTerminal(i.redeemedBy.displayName ?? i.redeemedBy.handle)} (${sanitizeForTerminal(i.redeemedBy.handle)})`
|
|
9
|
+
: 'someone';
|
|
10
|
+
return `taken${label}\n by ${who} on ${i.redeemedAt}`;
|
|
11
|
+
}
|
|
12
|
+
if (i.revokedAt)
|
|
13
|
+
return `revoked${label}\n minted ${i.createdAt}`;
|
|
14
|
+
return `live${label}\n ${inviteLink(webUrl, i.token)}\n minted ${i.createdAt}`;
|
|
15
|
+
};
|
|
16
|
+
const INVITE_HELP = [
|
|
17
|
+
'invite — bring one person in.',
|
|
18
|
+
'',
|
|
19
|
+
'Mints a single-use invitation and prints the link. Hand it to ONE person.',
|
|
20
|
+
'It is spent when someone signs up with it.',
|
|
21
|
+
'',
|
|
22
|
+
'AN INVITATION IS NOT PERMISSION. It records who brought someone. Whether an',
|
|
23
|
+
'address may register at all is a separate decision, made by hand — so a',
|
|
24
|
+
'person can hold a good invitation and still be turned away. Tell them that',
|
|
25
|
+
'when you send it.',
|
|
26
|
+
'',
|
|
27
|
+
'Usage:',
|
|
28
|
+
' lattris invite <contact-name-or-id> [--yes]',
|
|
29
|
+
' lattris invite --for <name-or-id> [--yes] compatibility spelling',
|
|
30
|
+
'',
|
|
31
|
+
'The contact must be one of yours. A name is matched exactly, ignoring case.',
|
|
32
|
+
'If that name does not exist, a person contact is created before minting.',
|
|
33
|
+
'Duplicate names are never guessed: use the contact id shown by',
|
|
34
|
+
'`lattris contact list`.',
|
|
35
|
+
'',
|
|
36
|
+
' --yes required when not at a terminal (there is no prompt to answer',
|
|
37
|
+
' there); at a terminal you are always asked.',
|
|
38
|
+
'',
|
|
39
|
+
'To see the ones you have out: lattris get invites'
|
|
40
|
+
].join('\n');
|
|
41
|
+
const inviteVerb = {
|
|
42
|
+
verb: 'invite',
|
|
43
|
+
form: {
|
|
44
|
+
usage: INVITE_HELP,
|
|
45
|
+
flags: { '--for': 'for' },
|
|
46
|
+
switches: ['--yes'],
|
|
47
|
+
positionals: 0,
|
|
48
|
+
positionalsAtMost: 1,
|
|
49
|
+
contradictions: [{ a: 'p0', b: 'for', message: `${INVITE_HELP}\n` }],
|
|
50
|
+
steps: {
|
|
51
|
+
contact: {
|
|
52
|
+
from: ['p0', 'flag:for'],
|
|
53
|
+
required: 'A contact name or id is required.\n',
|
|
54
|
+
mustBePerson: '{name} is an AI contact. An invitation must go to a person contact.\n',
|
|
55
|
+
createIfMissing: true
|
|
56
|
+
},
|
|
57
|
+
pipedNeedsYes: {
|
|
58
|
+
message: 'Refusing to mint without confirmation. Pass --yes once the person has asked you to invite someone.\n'
|
|
59
|
+
},
|
|
60
|
+
confirm: {
|
|
61
|
+
question: 'Mint an invitation for {name}? [y/N] ',
|
|
62
|
+
whenMissing: 'Create person contact "{name}" and mint their invitation? [y/N] ',
|
|
63
|
+
onNo: { text: 'Not minted.\n', to: 'stdout' }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
request: { method: 'POST', path: '/invites', body: { contactId: { context: 'contact.id' } } },
|
|
67
|
+
render: (data, context) => {
|
|
68
|
+
const i = data;
|
|
69
|
+
const contact = context.contact;
|
|
70
|
+
return (`${context.createdContact ? `Recorded person contact ${formatContact(contact)}.\n\n` : ''}` +
|
|
71
|
+
`Invitation minted${i.targetName ? ` for ${i.targetName}` : ''}.\n\n` +
|
|
72
|
+
` ${inviteLink(context.webUrl, i.token)}\n\n` +
|
|
73
|
+
`Hand it to ONE person — it is spent when someone signs up with it.\n` +
|
|
74
|
+
`It is not permission to join; whether they may register is decided separately.\n` +
|
|
75
|
+
`You can see it again with: lattris get invites`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const getInvites = {
|
|
80
|
+
verb: 'get',
|
|
81
|
+
nouns: ['invite', 'invites'],
|
|
82
|
+
form: {
|
|
83
|
+
usage: 'Usage: lattris get invites',
|
|
84
|
+
positionals: 0,
|
|
85
|
+
request: { method: 'GET', path: '/invites' },
|
|
86
|
+
render: (list, context) => list.length === 0
|
|
87
|
+
? 'No invitations yet. `lattris invite <contact>` mints one.'
|
|
88
|
+
: list.map((i) => formatInvite(context.webUrl, i)).join('\n\n')
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
export { INVITE_HELP };
|
|
92
|
+
export const invites = {
|
|
93
|
+
name: 'invites',
|
|
94
|
+
words: { singular: 'invite', plural: 'invites' },
|
|
95
|
+
help: INVITE_HELP,
|
|
96
|
+
verbs: [inviteVerb, getInvites]
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=invite.js.map
|
|
@@ -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
|