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.
Files changed (79) hide show
  1. package/README.md +2 -2
  2. package/dist/build.json +1 -1
  3. package/dist/datasets/capture.js +118 -0
  4. package/dist/datasets/page.js +64 -0
  5. package/dist/datasets/pieces.js +78 -0
  6. package/dist/dispatch-form.js +202 -0
  7. package/dist/dispatch-words.js +133 -0
  8. package/dist/dispatch.js +67 -0
  9. package/dist/entry/session.js +219 -0
  10. package/dist/entry/set.js +130 -0
  11. package/dist/entry/setup-boot.js +151 -0
  12. package/dist/entry/setup.js +145 -0
  13. package/dist/entry/use.js +127 -0
  14. package/dist/help.js +65 -0
  15. package/dist/main.js +143 -6887
  16. package/dist/memory/access.js +88 -0
  17. package/dist/memory/capture-common.js +171 -0
  18. package/dist/memory/capture-help.js +195 -0
  19. package/dist/memory/capture-note.js +200 -0
  20. package/dist/memory/capture-session.js +168 -0
  21. package/dist/memory/delete.js +88 -0
  22. package/dist/memory/erase.js +67 -0
  23. package/dist/{extract-claude-jsonl.js → memory/extract-claude-jsonl.js} +5 -94
  24. package/dist/memory/get-help.js +72 -0
  25. package/dist/memory/get.js +246 -0
  26. package/dist/memory/history.js +134 -0
  27. package/dist/memory/recording-update.js +128 -0
  28. package/dist/memory/recording.js +258 -0
  29. package/dist/memory/resend.js +82 -0
  30. package/dist/memory/restore.js +73 -0
  31. package/dist/memory/save-session-chain.js +59 -0
  32. package/dist/memory/transcript.js +88 -0
  33. package/dist/memory/update-help.js +126 -0
  34. package/dist/memory/update.js +220 -0
  35. package/dist/memory/views.js +146 -0
  36. package/dist/nouns/consent.js +29 -0
  37. package/dist/nouns/contact.js +203 -0
  38. package/dist/nouns/datasets.js +109 -0
  39. package/dist/nouns/edges.js +106 -0
  40. package/dist/nouns/form.js +2 -0
  41. package/dist/nouns/index.js +48 -0
  42. package/dist/nouns/invite.js +98 -0
  43. package/dist/nouns/questions.js +116 -0
  44. package/dist/nouns/share.js +266 -0
  45. package/dist/nouns/words.js +81 -0
  46. package/dist/registry/datasets.js +15 -0
  47. package/dist/registry/entry.js +51 -0
  48. package/dist/registry/index.js +17 -0
  49. package/dist/registry/memory.js +116 -0
  50. package/dist/render/datasets.js +77 -0
  51. package/dist/render/door.js +39 -0
  52. package/dist/render/output.js +19 -0
  53. package/dist/render/prompt.js +4 -0
  54. package/dist/render/rows.js +82 -0
  55. package/dist/render/text.js +21 -0
  56. package/dist/request/access-token.js +67 -0
  57. package/dist/request/api-fetch.js +76 -0
  58. package/dist/request/callback.js +86 -0
  59. package/dist/request/door.js +74 -0
  60. package/dist/request/flags.js +38 -0
  61. package/dist/request/io.js +2 -0
  62. package/dist/request/paste-and-device.js +99 -0
  63. package/dist/request/stdin.js +22 -0
  64. package/dist/request/token.js +66 -0
  65. package/dist/request/upload.js +6 -0
  66. package/dist/request/version-check.js +79 -0
  67. package/dist/store/chains.js +25 -0
  68. package/dist/store/drafts.js +48 -0
  69. package/dist/store/environment.js +57 -0
  70. package/dist/store/exit.js +13 -0
  71. package/dist/store/io.js +2 -0
  72. package/dist/store/json-file.js +9 -0
  73. package/dist/store/session-home.js +189 -0
  74. package/dist/store/session-lock.js +169 -0
  75. package/dist/store/session.js +70 -0
  76. package/dist/store/write-identity.js +153 -0
  77. package/dist/version.js +16 -0
  78. package/package.json +3 -4
  79. package/dist/save-session-chain.js +0 -136
@@ -0,0 +1,246 @@
1
+ import { parseArgs } from "../request/flags.js";
2
+ import { contactIdFor } from "../memory/access.js";
3
+ import { getRevision } from "../memory/history.js";
4
+ import { composeAddress, formatMemoryDetail, formatMemoryPage, formatNodeWalk, ownHandleFor, parseWebsiteMemoryAddress } from "../memory/views.js";
5
+ import { emit } from "../render/output.js";
6
+ import { ambiguousText } from "../render/rows.js";
7
+ import { renderRunHint } from "../render/text.js";
8
+ import { optionalAccessToken, signedIn } from "../request/access-token.js";
9
+ import { refusalOf } from "../request/api-fetch.js";
10
+ import { refusal } from "../store/exit.js";
11
+ import { loadSession, saveSession } from "../store/session.js";
12
+ export const buildGetMemoryUrl = (webUrl, ownerHandle, ref, flags) => {
13
+ const url = new URL(`${webUrl}/by/${encodeURIComponent(ownerHandle)}/memories/${encodeURIComponent(ref)}`);
14
+ const links = flags.links ?? '1';
15
+ const bare = links === '0' && flags.depth === undefined && flags.content === undefined;
16
+ if (!bare) {
17
+ url.searchParams.set('links', links);
18
+ if (flags.depth !== undefined)
19
+ url.searchParams.set('depth', flags.depth);
20
+ if (flags.content !== undefined)
21
+ url.searchParams.set('content', flags.content);
22
+ }
23
+ return url;
24
+ };
25
+ const getMemory = async (cfg, io, idOrAddress, flags, by) => {
26
+ const isAddress = /^https?:\/\//i.test(idOrAddress);
27
+ const address = isAddress ? parseWebsiteMemoryAddress(cfg.webUrl, idOrAddress) : undefined;
28
+ if (isAddress && !address) {
29
+ return refusal(`The memory address must be on ${cfg.webUrl}/by/<owner>/memories/<ref>.\n`);
30
+ }
31
+ if (address && by && address.ownerHandle !== by) {
32
+ return refusal('--by must name the same owner as the memory address.\n');
33
+ }
34
+ const storedSession = loadSession(io.store);
35
+ let ownerHandle = address?.ownerHandle ?? by ?? storedSession?.handle;
36
+ let accessToken;
37
+ if (!ownerHandle && storedSession) {
38
+ accessToken = await optionalAccessToken(io, cfg);
39
+ if (accessToken) {
40
+ ownerHandle = await ownHandleFor(cfg, io, accessToken);
41
+ if (ownerHandle) {
42
+ const currentSession = loadSession(io.store);
43
+ if (currentSession && currentSession.handle !== ownerHandle) {
44
+ saveSession(io.store, { ...currentSession, handle: ownerHandle });
45
+ }
46
+ }
47
+ }
48
+ }
49
+ if (!ownerHandle) {
50
+ return refusal('A memory id needs its wall: use get memory <website-address> or get memory <id> --by <owner-handle>.\n');
51
+ }
52
+ accessToken ??= await optionalAccessToken(io, cfg);
53
+ const url = buildGetMemoryUrl(cfg.webUrl, ownerHandle, address?.ref ?? idOrAddress, flags);
54
+ const enveloped = url.search !== '';
55
+ const result = await io.api(accessToken, url, {
56
+ headers: { Accept: 'application/json' },
57
+ retryAnonymousOn401: true
58
+ }, 'web');
59
+ if (!result.ok) {
60
+ return refusalOf(result, ambiguousText(result.body) +
61
+ (result.body.code === 'memory:not-found' && !address && !by
62
+ ? 'For another wall, use get memory <website-address> or get memory <id> --by <owner-handle>.\n'
63
+ : ''));
64
+ }
65
+ const record = enveloped ? result.data.node : result.data;
66
+ const displayAddress = io.stdout.isTTY
67
+ ? composeAddress(cfg.webUrl, ownerHandle, record)
68
+ : undefined;
69
+ emit(io, result.data, result.rawText, () => enveloped
70
+ ? formatNodeWalk(result.data, displayAddress)
71
+ : formatMemoryDetail(result.data, displayAddress));
72
+ };
73
+ const GET_FLAG_NAMES = {
74
+ '--by': 'by',
75
+ '--revision': 'revision',
76
+ '--type': 'type',
77
+ '--intent': 'intent',
78
+ '--limit': 'limit',
79
+ '--cursor': 'cursor',
80
+ '--q': 'q',
81
+ '--with': 'with',
82
+ '--links': 'links',
83
+ '--depth': 'depth',
84
+ '--content': 'content',
85
+ '--for': 'for',
86
+ '--for-phone': 'forPhone'
87
+ };
88
+ const LIST_ONLY_FLAGS = [
89
+ 'type',
90
+ 'intent',
91
+ 'limit',
92
+ 'cursor',
93
+ 'q',
94
+ 'with',
95
+ 'sharedWithMe',
96
+ 'for',
97
+ 'forPhone'
98
+ ];
99
+ const WALK_ONLY_FLAGS = ['links', 'depth', 'revision'];
100
+ const listMemories = async (cfg, io, flags) => {
101
+ const accessToken = await signedIn(io, cfg);
102
+ if (typeof accessToken !== 'string')
103
+ return accessToken;
104
+ const url = new URL(`${cfg.apiUrl}/memories`);
105
+ if (flags.type !== undefined)
106
+ url.searchParams.set('type', flags.type);
107
+ if (flags.intent !== undefined)
108
+ url.searchParams.set('intent', flags.intent);
109
+ if (flags.limit !== undefined)
110
+ url.searchParams.set('limit', flags.limit);
111
+ if (flags.cursor !== undefined)
112
+ url.searchParams.set('cursor', flags.cursor);
113
+ if (flags.q !== undefined)
114
+ url.searchParams.set('q', flags.q);
115
+ if (flags.with !== undefined)
116
+ url.searchParams.set('audience', flags.with);
117
+ if (flags.content !== undefined)
118
+ url.searchParams.set('content', flags.content);
119
+ if (flags.sharedWithMe)
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);
132
+ const result = await io.api(accessToken, url);
133
+ if (!result.ok)
134
+ return refusalOf(result);
135
+ const asked = flags.forPhone ?? flags.for;
136
+ emit(io, result.data, result.rawText, (page) => formatMemoryPage(page, asked));
137
+ if (result.data.nextCursor) {
138
+ const argv = ['get', 'memories'];
139
+ if (flags.type !== undefined)
140
+ argv.push('--type', flags.type);
141
+ if (flags.intent !== undefined)
142
+ argv.push('--intent', flags.intent);
143
+ if (flags.q !== undefined)
144
+ argv.push('--q', flags.q);
145
+ if (flags.with !== undefined)
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);
151
+ if (flags.limit !== undefined)
152
+ argv.push('--limit', flags.limit);
153
+ if (flags.content !== undefined)
154
+ argv.push('--content', flags.content);
155
+ if (flags.sharedWithMe)
156
+ argv.push('--shared-with-me');
157
+ argv.push('--cursor', result.data.nextCursor);
158
+ io.stderr.write(`More: ${renderRunHint(argv)}\n`);
159
+ }
160
+ };
161
+ const getWallMemories = async (cfg, io, ownerHandle, flags) => {
162
+ const accessToken = await optionalAccessToken(io, cfg);
163
+ const url = new URL(`${cfg.webUrl}/by/${encodeURIComponent(ownerHandle)}/memories`);
164
+ if (flags.limit !== undefined)
165
+ url.searchParams.set('limit', flags.limit);
166
+ if (flags.cursor !== undefined)
167
+ url.searchParams.set('cursor', flags.cursor);
168
+ if (flags.content !== undefined)
169
+ url.searchParams.set('content', flags.content);
170
+ const result = await io.api(accessToken, url, {
171
+ headers: { Accept: 'application/json' },
172
+ retryAnonymousOn401: true
173
+ }, 'web');
174
+ if (!result.ok)
175
+ return refusalOf(result);
176
+ emit(io, result.data, result.rawText, formatMemoryPage);
177
+ if (result.data.nextCursor) {
178
+ const argv = ['get', 'memories', '--by', ownerHandle];
179
+ if (flags.limit !== undefined)
180
+ argv.push('--limit', flags.limit);
181
+ if (flags.content !== undefined)
182
+ argv.push('--content', flags.content);
183
+ argv.push('--cursor', result.data.nextCursor);
184
+ io.stderr.write(`More: ${renderRunHint(argv)}\n`);
185
+ }
186
+ };
187
+ export const getMemories = async (cfg, io, rest, noun) => {
188
+ const sharedWithMeCount = rest.filter((arg) => arg === '--shared-with-me').length;
189
+ if (sharedWithMeCount > 1) {
190
+ return refusal('--shared-with-me was given more than once. Send it once.\n');
191
+ }
192
+ const { flags, positionals, unknown, notAnId } = parseArgs(GET_FLAG_NAMES, rest.filter((arg) => arg !== '--shared-with-me'), [0]);
193
+ if (sharedWithMeCount === 1)
194
+ flags.sharedWithMe = '1';
195
+ if (unknown) {
196
+ return refusal(`Unknown flag: ${unknown}\n`);
197
+ }
198
+ if (notAnId && !/^https?:\/\//i.test(positionals[0]))
199
+ return refusal(notAnId);
200
+ if (positionals.length > 1) {
201
+ return refusal(`Unexpected argument: ${positionals[1]}\n`);
202
+ }
203
+ const id = positionals[0];
204
+ if (id !== undefined) {
205
+ const inapplicable = LIST_ONLY_FLAGS.find((k) => flags[k] !== undefined);
206
+ if (inapplicable !== undefined) {
207
+ return refusal(`--${inapplicable} does not apply to a single memory.\n`);
208
+ }
209
+ if (flags.revision !== undefined) {
210
+ if (flags.by !== undefined || /^https?:\/\//i.test(id)) {
211
+ return refusal('A revision read uses your own id or prefix; --by and website addresses do not apply.\n');
212
+ }
213
+ const walkFlag = ['links', 'depth', 'content'].find((k) => flags[k] !== undefined);
214
+ if (walkFlag !== undefined) {
215
+ return refusal(`--${walkFlag} does not apply to a revision read.\n`);
216
+ }
217
+ return getRevision(cfg, io, id, flags.revision);
218
+ }
219
+ return getMemory(cfg, io, id, { links: flags.links, depth: flags.depth, content: flags.content }, flags.by);
220
+ }
221
+ else if (noun === 'memories') {
222
+ const walkFlag = WALK_ONLY_FLAGS.find((k) => flags[k] !== undefined);
223
+ if (walkFlag !== undefined) {
224
+ return refusal(`--${walkFlag} applies to a single memory, not the list.\n`);
225
+ }
226
+ if (flags.by !== undefined) {
227
+ const ownerOnly = ['type', 'intent', 'q', 'with', 'sharedWithMe', 'for', 'forPhone'].find((key) => flags[key] !== undefined);
228
+ if (ownerOnly !== undefined) {
229
+ const flagName = ownerOnly === 'sharedWithMe'
230
+ ? 'shared-with-me'
231
+ : ownerOnly === 'forPhone'
232
+ ? 'for-phone'
233
+ : ownerOnly;
234
+ return refusal(`--${flagName} does not apply to a wall listing.\n`);
235
+ }
236
+ return getWallMemories(cfg, io, flags.by, flags);
237
+ }
238
+ else {
239
+ return listMemories(cfg, io, flags);
240
+ }
241
+ }
242
+ else {
243
+ return refusal('A website address or id is required for a single memory. To list your own: lattris get memories\n');
244
+ }
245
+ };
246
+ //# sourceMappingURL=get.js.map
@@ -0,0 +1,134 @@
1
+ import { parseArgs } from "../request/flags.js";
2
+ import { formatDeletedPage } from "../memory/views.js";
3
+ import { emit } from "../render/output.js";
4
+ import { renderRunHint, sanitizeForTerminal } from "../render/text.js";
5
+ import { refusal } from "../store/exit.js";
6
+ import { refusalOf } from "../request/api-fetch.js";
7
+ import { signedIn } from "../request/access-token.js";
8
+ import { ambiguousText } from "../render/rows.js";
9
+ export const formatHistoryItem = (i) => {
10
+ const who = i.actor.agentLabel
11
+ ? `${sanitizeForTerminal(i.actor.sessionId)} (${sanitizeForTerminal(i.actor.agentLabel)})`
12
+ : sanitizeForTerminal(i.actor.sessionId);
13
+ const rev = i.revisionNo === null ? '-' : `rev ${i.revisionNo}`;
14
+ const extra = [
15
+ i.restoredFromRevisionNo !== null ? `from rev ${i.restoredFromRevisionNo}` : undefined,
16
+ i.overwriteReason !== null ? `reason: ${sanitizeForTerminal(i.overwriteReason)}` : undefined,
17
+ i.baseline ? 'baseline' : undefined
18
+ ]
19
+ .filter((x) => x !== undefined)
20
+ .join('; ');
21
+ return `${i.recordedAt} ${i.action.padEnd(12)} ${rev.padEnd(9)} ${who}${extra ? ` (${extra})` : ''} ${i.operationId}`;
22
+ };
23
+ export const formatHistoryPage = (page, headFingerprint) => {
24
+ const lines = [];
25
+ if (headFingerprint)
26
+ lines.push(`head fingerprint ${headFingerprint}`, '');
27
+ else
28
+ lines.push('head fingerprint (not on this page)', '');
29
+ lines.push(...page.items.map((i) => formatHistoryItem(i)));
30
+ if (page.items.length === 0)
31
+ lines.push('(no history)');
32
+ lines.push('', `history starts at revision ${page.coverage.startsAtRevisionNo}` +
33
+ (page.coverage.baselineRecordedAt ? ` (baseline ${page.coverage.baselineRecordedAt})` : '') +
34
+ ` \u2014 ${sanitizeForTerminal(page.coverage.note, Infinity)}`);
35
+ if (page.nextBeforeOperation) {
36
+ lines.push(`More: --before ${page.nextBeforeOperation}`);
37
+ }
38
+ return lines.join('\n');
39
+ };
40
+ export const formatRevision = (r) => {
41
+ const lines = [
42
+ `revisionNo ${r.revisionNo ?? '-'}`,
43
+ `fingerprint ${r.fingerprint}`,
44
+ `recordedAt ${r.recordedAt}`,
45
+ `action ${r.action}`,
46
+ `session ${sanitizeForTerminal(r.actor.sessionId)}${r.actor.agentLabel ? ` (${sanitizeForTerminal(r.actor.agentLabel)})` : ''}`
47
+ ];
48
+ if (r.restoredFromRevisionNo !== null)
49
+ lines.push(`restoredFrom rev ${r.restoredFromRevisionNo}`);
50
+ if (r.overwriteReason !== null)
51
+ lines.push(`reason ${sanitizeForTerminal(r.overwriteReason)}`);
52
+ if (r.snapshot.intent)
53
+ lines.push(`intent ${sanitizeForTerminal(r.snapshot.intent)}`);
54
+ if (r.snapshot.capturedAt)
55
+ lines.push(`capturedAt ${r.snapshot.capturedAt}`);
56
+ if (r.snapshot.title)
57
+ lines.push(`title ${sanitizeForTerminal(r.snapshot.title)}`);
58
+ if (r.snapshot.aiSummary)
59
+ lines.push(`aiSummary ${sanitizeForTerminal(r.snapshot.aiSummary, Infinity)}`);
60
+ lines.push('', sanitizeForTerminal(r.snapshot.content, Infinity));
61
+ return lines.join('\n');
62
+ };
63
+ const HISTORY_FLAG_NAMES = {
64
+ '--limit': 'limit',
65
+ '--before': 'before'
66
+ };
67
+ export const getHistory = async (cfg, io, rest) => {
68
+ const { flags, positionals, unknown, notAnId } = parseArgs(HISTORY_FLAG_NAMES, rest, [0]);
69
+ if (unknown) {
70
+ return refusal(`Unknown flag: ${unknown}\n`);
71
+ }
72
+ if (notAnId)
73
+ return refusal(notAnId);
74
+ if (positionals.length !== 1) {
75
+ return refusal(`Usage: lattris get history <id> [--limit <n>] [--before <operation-id>]\n`);
76
+ }
77
+ const accessToken = await signedIn(io, cfg);
78
+ if (typeof accessToken !== 'string')
79
+ return accessToken;
80
+ const url = new URL(`${cfg.apiUrl}/memories/${encodeURIComponent(positionals[0])}/revisions`);
81
+ if (flags.limit !== undefined)
82
+ url.searchParams.set('limit', flags.limit);
83
+ if (flags.before !== undefined)
84
+ url.searchParams.set('beforeOperation', flags.before);
85
+ const result = await io.api(accessToken, url);
86
+ if (!result.ok)
87
+ return refusalOf(result, ambiguousText(result.body));
88
+ emit(io, result.data, result.rawText, (page) => formatHistoryPage(page, page.head?.fingerprint));
89
+ };
90
+ export const REVISION_NO_SHAPE = /^[1-9][0-9]{0,17}$/;
91
+ export const getRevision = async (cfg, io, idOrPrefix, revisionNo) => {
92
+ if (!REVISION_NO_SHAPE.test(revisionNo)) {
93
+ return refusal(`--revision must be a positive number from the history: ${revisionNo}\n`);
94
+ }
95
+ const accessToken = await signedIn(io, cfg);
96
+ if (typeof accessToken !== 'string')
97
+ return accessToken;
98
+ const result = await io.api(accessToken, `${cfg.apiUrl}/memories/${encodeURIComponent(idOrPrefix)}/revisions/${revisionNo}`);
99
+ if (!result.ok)
100
+ return refusalOf(result, ambiguousText(result.body));
101
+ emit(io, result.data, result.rawText, formatRevision);
102
+ };
103
+ const DELETED_FLAG_NAMES = {
104
+ '--limit': 'limit',
105
+ '--cursor': 'cursor'
106
+ };
107
+ export const getDeleted = async (cfg, io, rest) => {
108
+ const { flags, positionals, unknown } = parseArgs(DELETED_FLAG_NAMES, rest);
109
+ if (unknown) {
110
+ return refusal(`Unknown flag: ${unknown}\n`);
111
+ }
112
+ if (positionals.length > 0) {
113
+ return refusal(`Unexpected argument: ${positionals[0]}\n`);
114
+ }
115
+ const accessToken = await signedIn(io, cfg);
116
+ if (typeof accessToken !== 'string')
117
+ return accessToken;
118
+ const url = new URL(`${cfg.apiUrl}/memories`);
119
+ url.searchParams.set('state', 'deleted');
120
+ if (flags.limit !== undefined)
121
+ url.searchParams.set('limit', flags.limit);
122
+ if (flags.cursor !== undefined)
123
+ url.searchParams.set('cursor', flags.cursor);
124
+ const result = await io.api(accessToken, url);
125
+ if (!result.ok)
126
+ return refusalOf(result);
127
+ emit(io, result.data, result.rawText, (page) => {
128
+ const lines = [formatDeletedPage(page)].filter((l) => l.length > 0);
129
+ if (page.nextCursor)
130
+ lines.push(`More: ${renderRunHint(['get', 'deleted', '--cursor', page.nextCursor])}`);
131
+ return lines.join('\n');
132
+ });
133
+ };
134
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1,128 @@
1
+ import { parseArgs } from "../request/flags.js";
2
+ import { RECORDING_FORMATS, TRANSCRIPT_CONVERTERS, formatRecording } from "../memory/transcript.js";
3
+ import { emit } from "../render/output.js";
4
+ import { EXIT, refusal } from "../store/exit.js";
5
+ import { refusalOf } from "../request/api-fetch.js";
6
+ import { signedIn } from "../request/access-token.js";
7
+ import { ambiguousText } from "../render/rows.js";
8
+ const RECORDING_UPDATE_HELP = [
9
+ 'update recording — correct a recording’s transcript or its speaker map.',
10
+ '',
11
+ 'The transcript is replace-whole (turns AND the speaker map are one record);',
12
+ 'the speaker map is the recording’s one correctable finding aid — who each',
13
+ 'voice is.',
14
+ '',
15
+ 'Usage:',
16
+ ' lattris update recording transcript <id> <file> [--format json|a] [--force]',
17
+ ' lattris update recording speakers <id> <key>=<value> [<key>=<value> …]',
18
+ '',
19
+ ' transcript replaces the whole transcript from <file> (same converters as',
20
+ ' capture recording). --force is REQUIRED when the transcript is',
21
+ ' the recording’s ONLY substance — those words ARE the record,',
22
+ ' and the server refuses to replace them without it and says so.',
23
+ '',
24
+ ' speakers corrects the map, one key=value per declared speaker key:',
25
+ ' <key>=me claim that voice as your own',
26
+ ' <key>=<contact-id> map it to one of your contacts',
27
+ ' <key>=clear remove both (back to label-only)',
28
+ ' A key the recording does not declare is a 404 — the map is',
29
+ ' corrected, never widened. Labels are the record and unreachable',
30
+ ' here (re-send the transcript to change a label).'
31
+ ].join('\n');
32
+ const updateRecordingTranscript = async (cfg, io, args) => {
33
+ const force = args.includes('--force');
34
+ const { flags, positionals, unknown, notAnId } = parseArgs({ '--format': 'format' }, args.filter((a) => a !== '--force'), [0]);
35
+ if (unknown) {
36
+ return refusal(`Unknown flag: ${unknown}\n`);
37
+ }
38
+ if (notAnId)
39
+ return refusal(notAnId);
40
+ if (positionals.length !== 2) {
41
+ return refusal('Usage: lattris update recording transcript <id> <file> [--force]\n');
42
+ }
43
+ const [id, file] = positionals;
44
+ const format = flags.format ?? (file.toLowerCase().endsWith('.json') ? 'json' : 'a');
45
+ const convert = TRANSCRIPT_CONVERTERS[format];
46
+ if (convert === undefined) {
47
+ return refusal(`Unknown --format: ${format}. Accepted: ${RECORDING_FORMATS.join(', ')}.\n`);
48
+ }
49
+ let transcript;
50
+ try {
51
+ transcript = convert(io.store.files.readFileSync(file, 'utf8'));
52
+ }
53
+ catch (e) {
54
+ return refusal(`Could not read or parse ${file} as --format ${format}: ${e instanceof Error ? e.message : 'invalid'}\n`);
55
+ }
56
+ const accessToken = await signedIn(io, cfg);
57
+ if (typeof accessToken !== 'string')
58
+ return accessToken;
59
+ const result = await io.api(accessToken, `${cfg.apiUrl}/memories/${encodeURIComponent(id)}/transcript`, {
60
+ method: 'PUT',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify(force ? { transcript, force: true } : { transcript })
63
+ });
64
+ if (!result.ok)
65
+ return refusalOf(result, ambiguousText(result.body));
66
+ emit(io, result.data, result.rawText, (r) => `Updated transcript.\n\n${formatRecording(r)}`);
67
+ };
68
+ export const buildSpeakerCorrection = (value) => {
69
+ if (value === 'me')
70
+ return { me: true };
71
+ if (value === 'clear' || value === 'none')
72
+ return { contactId: null };
73
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value))
74
+ return { contactId: value };
75
+ return null;
76
+ };
77
+ const updateRecordingSpeakers = async (cfg, io, args) => {
78
+ const { positionals, unknown, notAnId } = parseArgs({}, args, [0]);
79
+ if (unknown) {
80
+ return refusal(`Unknown flag: ${unknown}\n`);
81
+ }
82
+ if (notAnId)
83
+ return refusal(notAnId);
84
+ if (positionals.length < 2) {
85
+ return refusal('Usage: lattris update recording speakers <id> <key>=me|<contact-id>|clear [<key>=… …]\n');
86
+ }
87
+ const [id, ...pairs] = positionals;
88
+ const speakers = {};
89
+ for (const pair of pairs) {
90
+ const eq = pair.indexOf('=');
91
+ if (eq <= 0) {
92
+ return refusal(`Each correction is <key>=<value>: ${pair}\n`);
93
+ }
94
+ const key = pair.slice(0, eq);
95
+ if (Object.prototype.hasOwnProperty.call(speakers, key)) {
96
+ return refusal(`speaker "${key}" given twice — one correction per speaker per call.\n`);
97
+ }
98
+ const correction = buildSpeakerCorrection(pair.slice(eq + 1));
99
+ if (correction === null) {
100
+ return refusal(`${key}: a correction is me, a contact id (full UUID), or clear — not "${pair.slice(eq + 1)}".\n`);
101
+ }
102
+ speakers[key] = correction;
103
+ }
104
+ const accessToken = await signedIn(io, cfg);
105
+ if (typeof accessToken !== 'string')
106
+ return accessToken;
107
+ const result = await io.api(accessToken, `${cfg.apiUrl}/memories/${encodeURIComponent(id)}/speakers`, {
108
+ method: 'PUT',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify({ speakers })
111
+ });
112
+ if (!result.ok)
113
+ return refusalOf(result, ambiguousText(result.body));
114
+ emit(io, result.data, result.rawText, (r) => `Updated speakers.\n\n${formatRecording(r)}`);
115
+ };
116
+ export const updateRecording = async (cfg, io, rest) => {
117
+ const sub = rest[0];
118
+ if (sub === 'transcript') {
119
+ return updateRecordingTranscript(cfg, io, rest.slice(1));
120
+ }
121
+ if (sub === 'speakers') {
122
+ return updateRecordingSpeakers(cfg, io, rest.slice(1));
123
+ }
124
+ io.stdout.write(`${RECORDING_UPDATE_HELP}\n`);
125
+ if (sub !== undefined)
126
+ return refusal('', EXIT.USAGE);
127
+ };
128
+ //# sourceMappingURL=recording-update.js.map