@wirecat/cli-core 0.396.0 → 0.398.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/CHANGELOG.md +74 -1
- package/README.md +13 -9
- package/dist/background/daily-archive-timer.d.ts +40 -0
- package/dist/background/daily-archive-timer.js +181 -0
- package/dist/background/index.d.ts +2 -0
- package/dist/background/index.js +1 -0
- package/dist/cli/bot/admins.js +2 -2
- package/dist/cli/bot/command.js +2 -2
- package/dist/cli/bot/setup.js +3 -3
- package/dist/cli/commands-command.js +1 -1
- package/dist/cli/config-command.js +1 -1
- package/dist/cli/guide.js +0 -1
- package/dist/cli/help/command-help.d.ts +9 -2
- package/dist/cli/help/command-help.js +22 -4
- package/dist/cli/help/first-screen.js +3 -3
- package/dist/cli/messenger/account-command.js +3 -1
- package/dist/cli/messenger/account-sessions-command.js +3 -1
- package/dist/cli/messenger/admin-commands.js +1 -0
- package/dist/cli/messenger/admin-contacts-command.js +9 -3
- package/dist/cli/messenger/archive-commands.js +5 -3
- package/dist/cli/messenger/attachments-command.js +17 -7
- package/dist/cli/messenger/backfill-command.js +7 -3
- package/dist/cli/messenger/catch-up-answers.d.ts +2 -0
- package/dist/cli/messenger/catch-up-answers.js +5 -1
- package/dist/cli/messenger/chats-command.js +3 -1
- package/dist/cli/messenger/chats-members-command.js +4 -0
- package/dist/cli/messenger/chats-stats-command.js +1 -0
- package/dist/cli/messenger/chats-tracking-command.js +4 -1
- package/dist/cli/messenger/complete-command.js +6 -2
- package/dist/cli/messenger/contacts-command.js +11 -4
- package/dist/cli/messenger/context.d.ts +5 -0
- package/dist/cli/messenger/conversations-command.js +18 -1
- package/dist/cli/messenger/counters-command.js +1 -0
- package/dist/cli/messenger/doctor-command.js +2 -2
- package/dist/cli/messenger/download-command.js +1 -0
- package/dist/cli/messenger/flood-command.js +2 -1
- package/dist/cli/messenger/guard-commands.js +5 -4
- package/dist/cli/messenger/inbox.js +2 -0
- package/dist/cli/messenger/lacks.d.ts +7 -0
- package/dist/cli/messenger/lacks.js +18 -1
- package/dist/cli/messenger/mcp-command.js +6 -2
- package/dist/cli/messenger/media-archive-command.d.ts +3 -0
- package/dist/cli/messenger/media-archive-command.js +72 -0
- package/dist/cli/messenger/media-archive-move-command.d.ts +3 -0
- package/dist/cli/messenger/media-archive-move-command.js +39 -0
- package/dist/cli/messenger/media-archive-timer-command.d.ts +3 -0
- package/dist/cli/messenger/media-archive-timer-command.js +70 -0
- package/dist/cli/messenger/messages-command.js +2 -1
- package/dist/cli/messenger/messages-comments-command.js +1 -0
- package/dist/cli/messenger/messages-delete-command.js +1 -1
- package/dist/cli/messenger/messages-search-command.js +1 -0
- package/dist/cli/messenger/messages-stats-command.js +1 -0
- package/dist/cli/messenger/models-command.js +8 -2
- package/dist/cli/messenger/private-people-command.js +12 -10
- package/dist/cli/messenger/rankings-command.js +2 -0
- package/dist/cli/messenger/reactions-command.js +8 -2
- package/dist/cli/messenger/replies-command.js +1 -1
- package/dist/cli/messenger/retention-command.js +1 -0
- package/dist/cli/messenger/review.js +8 -2
- package/dist/cli/messenger/schedule-command.js +3 -3
- package/dist/cli/messenger/search-command.js +7 -3
- package/dist/cli/messenger/searches-command.js +24 -7
- package/dist/cli/messenger/serve-command.js +5 -1
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/cli/messenger/server-command.js +17 -3
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/store-maintenance-command.js +10 -3
- package/dist/cli/messenger/tags-command.js +6 -2
- package/dist/cli/messenger/tasks-command.js +10 -7
- package/dist/cli/messenger/transcribe-command.js +1 -0
- package/dist/cli/messenger/watch-command.js +6 -1
- package/dist/cli/runs/command.js +2 -3
- package/dist/cli/skill-command.js +1 -1
- package/dist/domain/models.d.ts +2 -0
- package/dist/entities/contact-read.d.ts +119 -0
- package/dist/entities/contact-read.js +50 -0
- package/dist/entities/index.d.ts +120 -1
- package/dist/entities/index.js +5 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/parity/catalogue.js +1 -1
- package/dist/parity/manifest.d.ts +3 -1
- package/dist/parity/manifest.js +2 -0
- package/dist/parity/wording.js +2 -0
- package/dist/sends/permissions.js +7 -0
- package/dist/services/deps.d.ts +1 -1
- package/dist/services/deps.js +1 -1
- package/dist/services/inbox.js +5 -5
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.js +2 -0
- package/dist/services/media-archive-files.d.ts +10 -0
- package/dist/services/media-archive-files.js +110 -0
- package/dist/services/media-archive-locks.d.ts +6 -0
- package/dist/services/media-archive-locks.js +71 -0
- package/dist/services/media-archive-manifest.d.ts +29 -0
- package/dist/services/media-archive-manifest.js +116 -0
- package/dist/services/media-archive-move.d.ts +21 -0
- package/dist/services/media-archive-move.js +163 -0
- package/dist/services/media-archive.d.ts +34 -0
- package/dist/services/media-archive.js +165 -0
- package/dist/services/search-kind.js +1 -1
- package/package.json +2 -2
- package/parity.json +116 -193
|
@@ -34,9 +34,10 @@ const maxMeetingsOf = (value) => {
|
|
|
34
34
|
};
|
|
35
35
|
const allCommand = (messenger) => {
|
|
36
36
|
const command = annotate(new Command("all"), { effects: REMOTE_READ, partial: true })
|
|
37
|
+
.summary("search stored messages, notes and selected meeting transcripts")
|
|
37
38
|
.description((messenger.serverSearch
|
|
38
|
-
? "search messenger messages and notes in the local store, and the messenger's server for
|
|
39
|
-
"(--backend
|
|
39
|
+
? "search messenger messages and notes in the local store, and by default the messenger's server for " +
|
|
40
|
+
"messages too (notes are local only; --backend archive or --offline keeps it to the local store)"
|
|
40
41
|
: "search messenger messages and notes in the local store") +
|
|
41
42
|
", and a meeting account's transcripts when --source names one — best match first; not mail (the separate memo CLI searches it: memo search mail), " +
|
|
42
43
|
"tasks, people, memories or projects. Use it when you do not know where something was written; search messages " +
|
|
@@ -95,6 +96,7 @@ const allCommand = (messenger) => {
|
|
|
95
96
|
});
|
|
96
97
|
};
|
|
97
98
|
const notesCommand = (messenger) => annotate(new Command("notes"), { effects: LOCAL_READ, partial: true, output: { entity: "note", view: "hit" } })
|
|
99
|
+
.summary("find stored notes by words and filters")
|
|
98
100
|
.description("search the notes — written in memo, or imported from a notes folder — by words and, with the local " +
|
|
99
101
|
"text model, by meaning; each hit says which found it and what it links to")
|
|
100
102
|
.argument("<query...>", 'strict Lucene query: words, "phrases", AND/OR/NOT, tag:, source:internal (written in memo) or source:file ' +
|
|
@@ -150,7 +152,9 @@ const notesCommand = (messenger) => annotate(new Command("notes"), { effects: LO
|
|
|
150
152
|
* hierarchy"). `topics` only where the CLI has forum topics.
|
|
151
153
|
*/
|
|
152
154
|
export const searchCommand = (messenger, { topics = false } = {}) => {
|
|
153
|
-
const search = new Command("search")
|
|
155
|
+
const search = new Command("search")
|
|
156
|
+
.summary("search the local archive")
|
|
157
|
+
.description("find things by text: one resource when you know it (messages, notes), or search all across messages and notes; for mail, the separate memo CLI: memo search mail");
|
|
154
158
|
search
|
|
155
159
|
.addCommand(allCommand(messenger))
|
|
156
160
|
.addCommand(messagesSearchCommand(messenger))
|
|
@@ -24,8 +24,16 @@ const line = ({ id, name, command, params, runs, lastRunAt }) => [
|
|
|
24
24
|
export const SEARCHES_QUERY = unfilteredQuery("saved_search", savedSearch);
|
|
25
25
|
/** Saved searches and the history of `search messages` and `stats messages show`, in the local store only. */
|
|
26
26
|
export const searchesCommand = (messenger) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
27
|
+
const tool = `${messenger.app.command} personal`;
|
|
28
|
+
const searches = new Command("searches")
|
|
29
|
+
.summary("saved searches and their run history")
|
|
30
|
+
.description("saved searches and the history of search messages and stats messages show, kept in the local store; --saved runs one");
|
|
31
|
+
annotate(searches.command("create"), {
|
|
32
|
+
effects: LOCAL_WRITE,
|
|
33
|
+
output: { entity: "saved_search" },
|
|
34
|
+
examples: [`${tool} searches create project-drafts draft --chat "Project room"`],
|
|
35
|
+
})
|
|
36
|
+
.summary("save a search and its query")
|
|
29
37
|
.description("save a search under a name without running it; search messages --saved <name> runs it")
|
|
30
38
|
.argument("<name>", "up to 64 letters a–z, digits and hyphens, not only digits")
|
|
31
39
|
.argument("[query...]", "the query, as for search messages; none matches every stored message")
|
|
@@ -64,7 +72,11 @@ export const searchesCommand = (messenger) => {
|
|
|
64
72
|
const saved = await context.withServices((services) => services.searches.create(name, params, { replace: replace === true }));
|
|
65
73
|
renderAnswer(this, context, itemOf(savedSearchItem(saved)), () => `${line(saved)}\n`);
|
|
66
74
|
});
|
|
67
|
-
annotate(searches.command("show"), {
|
|
75
|
+
annotate(searches.command("show"), {
|
|
76
|
+
effects: REMOTE_READ,
|
|
77
|
+
output: { entity: "saved_search" },
|
|
78
|
+
examples: [`${tool} searches show project-drafts`],
|
|
79
|
+
})
|
|
68
80
|
.description("one saved search or earlier run: its query, options and how often it ran")
|
|
69
81
|
.argument("<name|id>", "a saved search's name, or the id of any row of searches history")
|
|
70
82
|
.action(async function (reference) {
|
|
@@ -72,7 +84,7 @@ export const searchesCommand = (messenger) => {
|
|
|
72
84
|
const found = await context.withServices((services) => services.searches.show(reference));
|
|
73
85
|
renderAnswer(this, context, itemOf(savedSearchItem(found)), () => `${line(found)}\n`);
|
|
74
86
|
});
|
|
75
|
-
withQuery(annotate(searches.command("list"), { effects: LOCAL_READ }), SEARCHES_QUERY)
|
|
87
|
+
withQuery(annotate(searches.command("list"), { effects: LOCAL_READ, examples: [`${tool} searches list`] }), SEARCHES_QUERY)
|
|
76
88
|
.description("the saved searches, by name")
|
|
77
89
|
.action(async function (words) {
|
|
78
90
|
const context = messengerContext(this, messenger);
|
|
@@ -83,9 +95,10 @@ export const searchesCommand = (messenger) => {
|
|
|
83
95
|
if (found.length === 0 && context.format === "pretty")
|
|
84
96
|
context.renderer.note("no saved searches — `searches create <name> <query>` saves one");
|
|
85
97
|
});
|
|
86
|
-
annotate(withPaging(annotate(searches.command("history"), { effects: LOCAL_READ })), {
|
|
98
|
+
annotate(withPaging(annotate(searches.command("history"), { effects: LOCAL_READ, examples: [`${tool} searches history --limit 10`] })), {
|
|
87
99
|
output: { entity: "saved_search" },
|
|
88
100
|
})
|
|
101
|
+
.summary("past searches and counts, without their results")
|
|
89
102
|
.description("the searches and counts that ran, newest first — saved ones included; never their results")
|
|
90
103
|
.action(async function () {
|
|
91
104
|
const context = messengerContext(this, messenger);
|
|
@@ -98,7 +111,11 @@ export const searchesCommand = (messenger) => {
|
|
|
98
111
|
if (found.items.length === 0 && context.format === "pretty")
|
|
99
112
|
context.renderer.note("no searches ran yet");
|
|
100
113
|
});
|
|
101
|
-
annotate(searches.command("delete"), {
|
|
114
|
+
annotate(searches.command("delete"), {
|
|
115
|
+
effects: LOCAL_WRITE,
|
|
116
|
+
output: { entity: "saved_search" },
|
|
117
|
+
examples: [`${tool} searches delete project-drafts`],
|
|
118
|
+
})
|
|
102
119
|
.description("delete a saved search, or one run from the history")
|
|
103
120
|
.argument("<name|id>", "a saved search's name, or the id of any row of searches history")
|
|
104
121
|
.action(async function (reference) {
|
|
@@ -107,7 +124,7 @@ export const searchesCommand = (messenger) => {
|
|
|
107
124
|
const deleted = await context.withServices((services) => services.searches.delete(reference));
|
|
108
125
|
renderAnswer(this, context, itemOf(savedSearchItem(deleted)), () => `deleted ${deleted.name ?? `run ${deleted.id}`}\n`);
|
|
109
126
|
});
|
|
110
|
-
annotate(searches.command("clear"), { effects: LOCAL_WRITE })
|
|
127
|
+
annotate(searches.command("clear"), { effects: LOCAL_WRITE, examples: [`${tool} searches clear`] })
|
|
111
128
|
.description("empty the history; saved searches stay")
|
|
112
129
|
.action(async function () {
|
|
113
130
|
const context = messengerContext(this, messenger);
|
|
@@ -30,7 +30,11 @@ export { lockPath, readLock, servingProfiles } from "../../background/lock.js";
|
|
|
30
30
|
* taken over. Started by a person, a service unit or `server start` — never by itself (NEED-9).
|
|
31
31
|
*/
|
|
32
32
|
export const serveCommand = (messenger) => {
|
|
33
|
-
const command = annotate(new Command("serve"), {
|
|
33
|
+
const command = annotate(new Command("serve"), {
|
|
34
|
+
effects: REMOTE_WRITE,
|
|
35
|
+
lifetime: "persistent",
|
|
36
|
+
examples: [`${messenger.app.command} personal serve`],
|
|
37
|
+
}).description("keep the local store current until stopped — what a systemd or launchd unit runs");
|
|
34
38
|
command.action(async function () {
|
|
35
39
|
const relay = messenger.connection === "exclusive" && process.platform !== "win32" ? serveRelay(this, messenger) : undefined;
|
|
36
40
|
const context = recordDiagnostics(messengerContext(this, relay?.messenger ?? messenger), messenger.app);
|
|
@@ -51,6 +51,7 @@ export declare const serveRelay: (command: Command, messenger: Messenger) => {
|
|
|
51
51
|
scheduledSends?: boolean;
|
|
52
52
|
autoReplies?: boolean;
|
|
53
53
|
lacks?: readonly (keyof MessengerAdapter)[];
|
|
54
|
+
never?: readonly (keyof MessengerAdapter)[];
|
|
54
55
|
deleteForMe?: boolean;
|
|
55
56
|
topics?: boolean;
|
|
56
57
|
topicShow?: boolean;
|
|
@@ -60,7 +60,9 @@ const lockProcess = (app, marker, serveArgv) => (context, system, logPath) => ({
|
|
|
60
60
|
*/
|
|
61
61
|
export const serverCommand = (messenger, options = {}) => {
|
|
62
62
|
const { app } = messenger;
|
|
63
|
-
const command =
|
|
63
|
+
const command = new Command("server")
|
|
64
|
+
.summary("manage this profile's background server")
|
|
65
|
+
.description(`\`${app.command} serve\` in the background: start, stop, restart, status, logs; install adds a systemd or launchd unit`);
|
|
64
66
|
const marker = (profile) => `${app.envPrefix}_SERVER=${profile}`;
|
|
65
67
|
const serveArgv = options.serveArgv ?? ["serve"];
|
|
66
68
|
const prepare = (self) => {
|
|
@@ -181,15 +183,26 @@ export const serverCommand = (messenger, options = {}) => {
|
|
|
181
183
|
return ["--idle", idle];
|
|
182
184
|
};
|
|
183
185
|
const withIdle = (sub) => options.idle ? sub.option("--idle <duration>", "stop after this long with nobody using it — 15m, 1h") : sub;
|
|
184
|
-
withIdle(destroysNothing(annotate(command.command("start"), {
|
|
186
|
+
withIdle(destroysNothing(annotate(command.command("start"), {
|
|
187
|
+
effects: LOCAL_WRITE,
|
|
188
|
+
mcp: false,
|
|
189
|
+
examples: [`${app.command} personal server start`],
|
|
190
|
+
}))
|
|
191
|
+
.summary("start this profile's background server")
|
|
192
|
+
.description("start serve in the background — through the unit if one is installed — and answer once it connects")).action(async function () {
|
|
185
193
|
await start(prepare(this), idleArgs(this));
|
|
186
194
|
});
|
|
187
|
-
destroysNothing(annotate(command.command("stop"), {
|
|
195
|
+
destroysNothing(annotate(command.command("stop"), {
|
|
196
|
+
effects: LOCAL_WRITE,
|
|
197
|
+
mcp: false,
|
|
198
|
+
examples: [`${app.command} personal server stop`],
|
|
199
|
+
}))
|
|
188
200
|
.description("stop this profile's serve — through the unit if it runs under one")
|
|
189
201
|
.action(async function () {
|
|
190
202
|
await stop(prepare(this));
|
|
191
203
|
});
|
|
192
204
|
withIdle(destroysNothing(annotate(command.command("restart"), {
|
|
205
|
+
mcp: false,
|
|
193
206
|
effects: LOCAL_WRITE,
|
|
194
207
|
examples: [`${app.command} personal server restart`],
|
|
195
208
|
})).description("stop it and start it again")).action(async function () {
|
|
@@ -199,6 +212,7 @@ export const serverCommand = (messenger, options = {}) => {
|
|
|
199
212
|
await start(prepared, args);
|
|
200
213
|
});
|
|
201
214
|
annotate(command.command("status"), { effects: LOCAL_READ, examples: [`${app.command} personal server status`] })
|
|
215
|
+
.summary("this profile's background-server status")
|
|
202
216
|
.description("whether serve runs for this profile, since when, who started it, and the unit if there is one")
|
|
203
217
|
.action(async function () {
|
|
204
218
|
const prepared = prepare(this);
|
|
@@ -2,9 +2,9 @@ import { Command } from "commander";
|
|
|
2
2
|
import type { Messenger } from "./context.js";
|
|
3
3
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
4
4
|
export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
5
|
-
error?: undefined;
|
|
6
5
|
path: string;
|
|
7
6
|
exists: boolean;
|
|
7
|
+
error?: undefined;
|
|
8
8
|
} | {
|
|
9
9
|
path: string;
|
|
10
10
|
exists: boolean;
|
|
@@ -93,6 +93,7 @@ const refuseWhileServing = (command, messenger) => {
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
const repairCommand = (messenger) => annotate(new Command("repair"), { effects: LOCAL_WRITE, mcp: false })
|
|
96
|
+
.summary("repair table shapes while keeping the old tables")
|
|
96
97
|
.description("bring every table to this build's shape, deleting nothing: a table of the wrong shape is kept as a copy beside a new one")
|
|
97
98
|
.option("--dry-run", "say what it would do, and change nothing")
|
|
98
99
|
.action(async function () {
|
|
@@ -151,7 +152,8 @@ const copiesCommand = () => new Command("copies").description("the tables `store
|
|
|
151
152
|
const deleted = await reading(path, (database) => deleteCopy(database, name));
|
|
152
153
|
result(this, { path, deleted });
|
|
153
154
|
}));
|
|
154
|
-
const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ
|
|
155
|
+
const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ })
|
|
156
|
+
.summary("the local archive's format, size and contents")
|
|
155
157
|
.description("the store file: where it is, its size, its schema and how many rows it holds; changes nothing")
|
|
156
158
|
.action(async function () {
|
|
157
159
|
const path = storePath(environmentOf(this).env ?? process.env);
|
|
@@ -180,7 +182,8 @@ const infoCommand = () => annotate(new Command("info"), { effects: LOCAL_READ, m
|
|
|
180
182
|
* fetched" (NEED-399 A). Times are the chat's newest message by the messenger's list against the
|
|
181
183
|
* newest message held, not `sync_ranges`: those are in message ids, which a chat list does not give.
|
|
182
184
|
*/
|
|
183
|
-
const checkCommand = (messenger) => annotate(new Command("check"), { effects: LOCAL_READ
|
|
185
|
+
const checkCommand = (messenger) => annotate(new Command("check"), { effects: LOCAL_READ })
|
|
186
|
+
.summary("check the local archive for problems")
|
|
184
187
|
.description("whether the store is healthy — integrity, search indexes, disk, and which chats are behind")
|
|
185
188
|
.action(async function () {
|
|
186
189
|
const { renderer } = outputFor(this);
|
|
@@ -360,6 +363,7 @@ const chatsBehind = (database) => database
|
|
|
360
363
|
refreshed: isoOf(row.refreshed),
|
|
361
364
|
}));
|
|
362
365
|
const migrateCommand = (messenger) => annotate(new Command("migrate"), { effects: LOCAL_WRITE, mcp: false })
|
|
366
|
+
.summary("migrate an older local archive")
|
|
363
367
|
.description("bring the store up to this build's schema, then normalize, index and stem the messages and notes stored before it")
|
|
364
368
|
.action(async function () {
|
|
365
369
|
const { renderer } = outputFor(this);
|
|
@@ -427,7 +431,8 @@ const buildNoteIndex = (database, note, { reset = false } = {}) => {
|
|
|
427
431
|
note(`indexing ${pending} notes`);
|
|
428
432
|
return { notesIndexed: drainNoteIndex(database, stemmerCache()) };
|
|
429
433
|
};
|
|
430
|
-
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("reindex"), { effects: LOCAL_WRITE
|
|
434
|
+
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("reindex"), { effects: LOCAL_WRITE }))
|
|
435
|
+
.summary("rebuild the local archive's search indexes")
|
|
431
436
|
.description("rebuild the word index, its typo vocabulary, the stems, the files' word index, the notes' indexes and who took part in what, from what is stored; loses nothing")
|
|
432
437
|
.action(async function () {
|
|
433
438
|
const { renderer } = outputFor(this);
|
|
@@ -540,6 +545,7 @@ const decryptCommand = () => annotate(new Command("decrypt"), { effects: LOCAL_W
|
|
|
540
545
|
* `serve` runs: that opens the store on its first write, so until then it holds nothing to see.
|
|
541
546
|
*/
|
|
542
547
|
const restoreCommand = (messenger) => annotate(new Command("restore"), { effects: LOCAL_WRITE, mcp: false })
|
|
548
|
+
.summary("restore a backup while keeping the current store")
|
|
543
549
|
.description("put a backup in place of the store; the store it replaces is kept beside it, never deleted")
|
|
544
550
|
.argument("<file>", "a file `store backup` wrote; one written with --encrypt asks for its password")
|
|
545
551
|
.action(async function (file) {
|
|
@@ -656,6 +662,7 @@ const quiesce = async (path, messenger, then, warn, waitMs = STORE_LOCK_WAIT_MS)
|
|
|
656
662
|
* schema. The backup is made before anything is deleted, and its path is said before the delete.
|
|
657
663
|
*/
|
|
658
664
|
const resetCommand = (messenger) => annotate(new Command("reset"), { effects: LOCAL_WRITE, mcp: false })
|
|
665
|
+
.summary("back up the local archive and replace it with an empty one")
|
|
659
666
|
.description("back the store up beside itself, then delete it and start an empty one at this build's schema; asks first, or --yes")
|
|
660
667
|
.option("--no-backup", "delete the store without backing it up first")
|
|
661
668
|
.action(async function ({ backup: keep }) {
|
|
@@ -29,7 +29,9 @@ const sourceOf = (value) => {
|
|
|
29
29
|
/** The owner's labels, in the local store only: `tag:` in a search finds what they label. */
|
|
30
30
|
export const tagsCommand = (messenger) => {
|
|
31
31
|
const tool = `${messenger.app.command} personal`;
|
|
32
|
-
const tags = new Command("tags")
|
|
32
|
+
const tags = new Command("tags")
|
|
33
|
+
.summary("tags on stored messages and other records")
|
|
34
|
+
.description("your own labels on chats, people, messages and tasks, kept in the local store and never sent; tag: in a search finds them");
|
|
33
35
|
changesStore(annotate(tags.command("auto"), { effects: FETCH, examples: [`${tool} tags auto --chat "Book club" --dry-run`] }))
|
|
34
36
|
.description("derive local group/channel tags from cached metadata using keyword rules")
|
|
35
37
|
.option("--chat <chat>", "a stored group/channel; repeat to select several", (value, previous) => [...previous, value], [])
|
|
@@ -73,7 +75,9 @@ export const tagsCommand = (messenger) => {
|
|
|
73
75
|
withQuery(annotate(tags.command("list"), {
|
|
74
76
|
effects: LOCAL_READ,
|
|
75
77
|
examples: [`${tool} tags list`, `${tool} tags list tag:reading`],
|
|
76
|
-
})
|
|
78
|
+
})
|
|
79
|
+
.summary("tagged chats, people, messages and tasks")
|
|
80
|
+
.description("what is tagged: this account's chats, messages and tasks, and the people of its messenger"), TAGGING_QUERY).action(async function (words) {
|
|
77
81
|
const context = messengerContext(this, messenger);
|
|
78
82
|
const query = listQuery(typedQuery(words), TAGGING_QUERY, "tags list");
|
|
79
83
|
const found = await context.withServices((services) => services.tags.list({ ...(query ? { query } : {}), ...pageOf(context.settings) }));
|
|
@@ -129,7 +129,9 @@ export const tasksCommand = (messenger) => {
|
|
|
129
129
|
const writerOf = (command) => readOnlyLockOf(messenger.app, environmentOf(command).env ?? process.env) || environmentOf(command).agent
|
|
130
130
|
? "agent"
|
|
131
131
|
: "owner";
|
|
132
|
-
const tasks = new Command("tasks")
|
|
132
|
+
const tasks = new Command("tasks")
|
|
133
|
+
.summary("tasks found or kept in the local archive")
|
|
134
|
+
.description("what waits on you — unanswered questions, mentions, requests, promises — kept in the local store; review and serve add them");
|
|
133
135
|
withQuery(annotate(tasks.command("list"), {
|
|
134
136
|
effects: LOCAL_READ,
|
|
135
137
|
examples: [`${tool} personal tasks list --limit 10`],
|
|
@@ -283,8 +285,8 @@ export const tasksCommand = (messenger) => {
|
|
|
283
285
|
}))
|
|
284
286
|
.summary("two tasks are not the same job: the pair is never offered again")
|
|
285
287
|
.description("two tasks are not the same job: the pair leaves the list and is never offered again")
|
|
286
|
-
.argument("<
|
|
287
|
-
.argument("<task>", "the task it was paired with")
|
|
288
|
+
.argument("<task>", "the task listed as the duplicate, by its id or key")
|
|
289
|
+
.argument("<other-task>", "the task it was paired with")
|
|
288
290
|
.action(decided("rejected", "tasks.duplicates.reject"));
|
|
289
291
|
destroysNothing(annotate(duplicates.command("defer"), {
|
|
290
292
|
effects: LOCAL_WRITE,
|
|
@@ -292,14 +294,14 @@ export const tasksCommand = (messenger) => {
|
|
|
292
294
|
}))
|
|
293
295
|
.summary("the fields and the text cannot tell: leave the pair to the owner")
|
|
294
296
|
.description("the fields and the text cannot tell: leave the pair to the owner, who lists it with decision:unsure")
|
|
295
|
-
.argument("<
|
|
296
|
-
.argument("<task>", "the task it was paired with")
|
|
297
|
+
.argument("<task>", "the task listed as the duplicate, by its id or key")
|
|
298
|
+
.argument("<other-task>", "the task it was paired with")
|
|
297
299
|
.action(decided("unsure", "tasks.duplicates.defer"));
|
|
298
300
|
destroysNothing(annotate(tasks.command("merge"), { effects: LOCAL_WRITE, examples: [`${tool} tasks merge PR-14 --into PR-12`] }))
|
|
299
301
|
.summary("one job seen twice: fold the duplicate into the task that stays")
|
|
300
302
|
.description("one job seen twice: link every message of the duplicate to the task and close the duplicate dismissed, " +
|
|
301
303
|
"duplicate-of the task; nothing is deleted, and tasks unmerge undoes it")
|
|
302
|
-
.argument("<
|
|
304
|
+
.argument("<task>", "the duplicate, by its id or key; it must be open")
|
|
303
305
|
.requiredOption("--into <id>", "the task that stays, by its id or key")
|
|
304
306
|
.action(async function (dup) {
|
|
305
307
|
const context = messengerContext(this, messenger);
|
|
@@ -312,7 +314,7 @@ export const tasksCommand = (messenger) => {
|
|
|
312
314
|
destroysNothing(annotate(tasks.command("unmerge"), { effects: LOCAL_WRITE, examples: [`${tool} tasks unmerge PR-14`] }))
|
|
313
315
|
.summary("undo a merge: reopen the task and remove its duplicate-of relation")
|
|
314
316
|
.description("undo a merge: reopen the task and remove its duplicate-of relation; messages the merge linked stay linked")
|
|
315
|
-
.argument("<
|
|
317
|
+
.argument("<task>", "the merged task, by its id or key")
|
|
316
318
|
.action(async function (dup) {
|
|
317
319
|
const context = messengerContext(this, messenger);
|
|
318
320
|
refuseLocalWrite(context, messenger.app.command, "tasks.unmerge");
|
|
@@ -417,6 +419,7 @@ export const tasksCommand = (messenger) => {
|
|
|
417
419
|
],
|
|
418
420
|
}))
|
|
419
421
|
.summary("close a task: done, or dismissed when it needs no answer; an agent's unsure close waits on the owner")
|
|
422
|
+
.summary("close a task as done or dismissed")
|
|
420
423
|
.description("close a task: done, or dismissed when it needs no answer. An agent (AI_AGENT or CLAUDECODE set) states its " +
|
|
421
424
|
"--certainty; below config task_agent_close_min, or with task_agent_close off, the task stays open and the " +
|
|
422
425
|
"close waits on the owner, who confirms it with tasks close or refuses it with tasks reject. A closed task " +
|
|
@@ -12,6 +12,7 @@ export const transcribeSubcommand = (messages, messenger) => annotate(messages.c
|
|
|
12
12
|
examples: [`${messenger.app.command} personal messages transcribe "Book club" 101`],
|
|
13
13
|
effects: REMOTE_READ,
|
|
14
14
|
})
|
|
15
|
+
.summary("transcribe one voice message")
|
|
15
16
|
.description(`a voice message as text — by ${messenger.name ?? messenger.app.command} where it can, else by a model on this machine`)
|
|
16
17
|
.argument("<chat>", messenger.chatArgument)
|
|
17
18
|
.argument("<message>", "the id of a voice message")
|
|
@@ -11,7 +11,12 @@ import { messengerContext } from "./context.js";
|
|
|
11
11
|
* for a minute is a complete answer. One `MessageHit` per line with `--jsonl`; `--json` is refused,
|
|
12
12
|
* because a stream is not one value. Nothing is marked read.
|
|
13
13
|
*/
|
|
14
|
-
export const watchCommand = (messenger) => annotate(new Command("watch"), {
|
|
14
|
+
export const watchCommand = (messenger) => annotate(new Command("watch"), {
|
|
15
|
+
effects: REMOTE_READ,
|
|
16
|
+
lifetime: "persistent",
|
|
17
|
+
examples: [`${messenger.app.command} personal watch --jsonl --timeout 30s`],
|
|
18
|
+
})
|
|
19
|
+
.summary("stream new messages until interrupted or timed out")
|
|
15
20
|
.description("print new messages as they arrive, until Ctrl-C or --timeout (either ends it normally)")
|
|
16
21
|
.option("--events", "also edits, deletions, reactions, poll votes and chat events; every line then names its event")
|
|
17
22
|
.action(async function () {
|
package/dist/cli/runs/command.js
CHANGED
|
@@ -20,7 +20,6 @@ export const runsCommand = (app) => {
|
|
|
20
20
|
};
|
|
21
21
|
annotate(command.command("list"), {
|
|
22
22
|
effects: LOCAL_READ,
|
|
23
|
-
mcp: false,
|
|
24
23
|
examples: [`${app.command} runs list --limit 10`],
|
|
25
24
|
})
|
|
26
25
|
.description("recorded runs, newest first")
|
|
@@ -36,7 +35,7 @@ export const runsCommand = (app) => {
|
|
|
36
35
|
if (runs.length === 0)
|
|
37
36
|
renderer.note("nothing recorded — a run is kept with `--record`, or when it fails");
|
|
38
37
|
});
|
|
39
|
-
annotate(command.command("show"), { effects: LOCAL_READ,
|
|
38
|
+
annotate(command.command("show"), { effects: LOCAL_READ, examples: [`${app.command} runs show run-101`] })
|
|
40
39
|
.argument("<run-id>", `an id from \`${app.command} runs list\``)
|
|
41
40
|
.description("one run: what it was, how it ended, and every event it logged")
|
|
42
41
|
.option("--problems", "only failed responses, warnings and crashes")
|
|
@@ -86,7 +85,7 @@ export const runsCommand = (app) => {
|
|
|
86
85
|
since_time: options.sinceTime,
|
|
87
86
|
}));
|
|
88
87
|
});
|
|
89
|
-
annotate(command.command("path"), { effects: NO_EFFECTS,
|
|
88
|
+
annotate(command.command("path"), { effects: NO_EFFECTS, examples: [`${app.command} runs path run-101`] })
|
|
90
89
|
.argument("<run-id>", `an id from \`${app.command} runs list\``)
|
|
91
90
|
.description("the directory holding one run")
|
|
92
91
|
.action(function (id) {
|
|
@@ -20,7 +20,7 @@ export const skillCommand = (app, skill) => {
|
|
|
20
20
|
const show = command.commands.find((sub) => sub.name() === "show");
|
|
21
21
|
if (show)
|
|
22
22
|
annotate(show, { examples: [`${app.command} skill show`, `${app.command} skill show follow-ups`] });
|
|
23
|
-
return
|
|
23
|
+
return command;
|
|
24
24
|
};
|
|
25
25
|
/** Skills this package ships for every CLI on it, printed by `skill show <name>`: `skills/` beside `dist/` and `src/`. */
|
|
26
26
|
const SHARED_SKILLS = {
|
package/dist/domain/models.d.ts
CHANGED
|
@@ -506,6 +506,8 @@ export interface Review {
|
|
|
506
506
|
closed: number;
|
|
507
507
|
review: number;
|
|
508
508
|
};
|
|
509
|
+
/** `--offline`: read from the local store, so it holds only what was already synced. */
|
|
510
|
+
offline?: true;
|
|
509
511
|
}
|
|
510
512
|
/** One change to who is in a chat, or to the chat itself, read from a service message. */
|
|
511
513
|
export interface ChatEvent {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export declare const contactLookup: v.StrictObjectSchema<{
|
|
3
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
4
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5
|
+
readonly username: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
6
|
+
}, undefined>;
|
|
7
|
+
export declare const contactSync: v.StrictObjectSchema<{
|
|
8
|
+
readonly added: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
9
|
+
readonly changed: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
10
|
+
readonly known: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
11
|
+
}, undefined>;
|
|
12
|
+
export declare const contactProfile: v.StrictObjectSchema<{
|
|
13
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
14
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
15
|
+
readonly usernames: v.ArraySchema<v.StringSchema<undefined>, undefined>;
|
|
16
|
+
readonly bio: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
17
|
+
readonly birthday: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
18
|
+
readonly phone: v.OptionalSchema<v.NullableSchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
19
|
+
readonly flags: Omit<v.StrictObjectSchema<{
|
|
20
|
+
readonly bot: v.BooleanSchema<undefined>;
|
|
21
|
+
readonly verified: v.BooleanSchema<undefined>;
|
|
22
|
+
readonly premium: v.BooleanSchema<undefined>;
|
|
23
|
+
readonly scam: v.BooleanSchema<undefined>;
|
|
24
|
+
readonly fake: v.BooleanSchema<undefined>;
|
|
25
|
+
readonly restricted: v.BooleanSchema<undefined>;
|
|
26
|
+
readonly deleted: v.BooleanSchema<undefined>;
|
|
27
|
+
readonly support: v.BooleanSchema<undefined>;
|
|
28
|
+
}, undefined>, "entries" | "~run" | "~standard" | "~types"> & {
|
|
29
|
+
readonly entries: {
|
|
30
|
+
readonly bot: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
31
|
+
readonly verified: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
32
|
+
readonly premium: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
33
|
+
readonly scam: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
34
|
+
readonly fake: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
35
|
+
readonly restricted: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
36
|
+
readonly deleted: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
37
|
+
readonly support: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
38
|
+
};
|
|
39
|
+
readonly "~standard": v.StandardProps<{
|
|
40
|
+
bot?: boolean | undefined;
|
|
41
|
+
verified?: boolean | undefined;
|
|
42
|
+
premium?: boolean | undefined;
|
|
43
|
+
scam?: boolean | undefined;
|
|
44
|
+
fake?: boolean | undefined;
|
|
45
|
+
restricted?: boolean | undefined;
|
|
46
|
+
deleted?: boolean | undefined;
|
|
47
|
+
support?: boolean | undefined;
|
|
48
|
+
}, {
|
|
49
|
+
bot?: boolean | undefined;
|
|
50
|
+
verified?: boolean | undefined;
|
|
51
|
+
premium?: boolean | undefined;
|
|
52
|
+
scam?: boolean | undefined;
|
|
53
|
+
fake?: boolean | undefined;
|
|
54
|
+
restricted?: boolean | undefined;
|
|
55
|
+
deleted?: boolean | undefined;
|
|
56
|
+
support?: boolean | undefined;
|
|
57
|
+
}>;
|
|
58
|
+
readonly "~run": (dataset: v.UnknownDataset, config: v.Config<v.BaseIssue<unknown>>) => v.OutputDataset<{
|
|
59
|
+
bot?: boolean | undefined;
|
|
60
|
+
verified?: boolean | undefined;
|
|
61
|
+
premium?: boolean | undefined;
|
|
62
|
+
scam?: boolean | undefined;
|
|
63
|
+
fake?: boolean | undefined;
|
|
64
|
+
restricted?: boolean | undefined;
|
|
65
|
+
deleted?: boolean | undefined;
|
|
66
|
+
support?: boolean | undefined;
|
|
67
|
+
}, v.BooleanIssue | v.StrictObjectIssue>;
|
|
68
|
+
readonly "~types"?: {
|
|
69
|
+
readonly input: {
|
|
70
|
+
bot?: boolean | undefined;
|
|
71
|
+
verified?: boolean | undefined;
|
|
72
|
+
premium?: boolean | undefined;
|
|
73
|
+
scam?: boolean | undefined;
|
|
74
|
+
fake?: boolean | undefined;
|
|
75
|
+
restricted?: boolean | undefined;
|
|
76
|
+
deleted?: boolean | undefined;
|
|
77
|
+
support?: boolean | undefined;
|
|
78
|
+
};
|
|
79
|
+
readonly output: {
|
|
80
|
+
bot?: boolean | undefined;
|
|
81
|
+
verified?: boolean | undefined;
|
|
82
|
+
premium?: boolean | undefined;
|
|
83
|
+
scam?: boolean | undefined;
|
|
84
|
+
fake?: boolean | undefined;
|
|
85
|
+
restricted?: boolean | undefined;
|
|
86
|
+
deleted?: boolean | undefined;
|
|
87
|
+
support?: boolean | undefined;
|
|
88
|
+
};
|
|
89
|
+
readonly issue: v.BooleanIssue | v.StrictObjectIssue;
|
|
90
|
+
} | undefined;
|
|
91
|
+
};
|
|
92
|
+
readonly seen: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
93
|
+
readonly contact: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
94
|
+
readonly mutual_contact: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
95
|
+
readonly common_chats_count: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>, undefined>;
|
|
96
|
+
readonly registered: v.OptionalSchema<v.NullableSchema<v.StrictObjectSchema<{
|
|
97
|
+
readonly at: v.StringSchema<undefined>;
|
|
98
|
+
readonly source: v.PicklistSchema<["telegram", "max", "estimate"], undefined>;
|
|
99
|
+
readonly precision: v.PicklistSchema<["day", "month"], undefined>;
|
|
100
|
+
}, undefined>, undefined>, undefined>;
|
|
101
|
+
readonly has_photo: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
102
|
+
readonly chats: v.ArraySchema<v.StrictObjectSchema<{
|
|
103
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
104
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
105
|
+
readonly kind: v.PicklistSchema<["dialog", "group", "channel", "saved", "unknown"], undefined>;
|
|
106
|
+
readonly their_messages: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
107
|
+
readonly first_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
108
|
+
readonly last_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
109
|
+
readonly complete: v.BooleanSchema<undefined>;
|
|
110
|
+
}, undefined>, undefined>;
|
|
111
|
+
readonly aliases: v.ArraySchema<v.StrictObjectSchema<{
|
|
112
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
113
|
+
readonly username: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
114
|
+
readonly link: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
115
|
+
readonly first_seen_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
116
|
+
readonly last_seen_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
117
|
+
readonly source: v.PicklistSchema<["profile", "messages"], undefined>;
|
|
118
|
+
}, undefined>, undefined>;
|
|
119
|
+
}, undefined>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import { chat } from "./chat.js";
|
|
3
|
+
import { count, id, text, time } from "./fields.js";
|
|
4
|
+
export const contactLookup = v.strictObject({ id, name: v.nullable(text), username: v.nullable(text) });
|
|
5
|
+
export const contactSync = v.strictObject({ added: count, changed: count, known: count });
|
|
6
|
+
export const contactProfile = v.strictObject({
|
|
7
|
+
id,
|
|
8
|
+
name: v.nullable(text),
|
|
9
|
+
usernames: v.array(text),
|
|
10
|
+
bio: v.nullable(text),
|
|
11
|
+
birthday: v.optional(v.nullable(text)),
|
|
12
|
+
phone: v.optional(v.nullable(text)),
|
|
13
|
+
flags: v.partial(v.strictObject({
|
|
14
|
+
bot: v.boolean(),
|
|
15
|
+
verified: v.boolean(),
|
|
16
|
+
premium: v.boolean(),
|
|
17
|
+
scam: v.boolean(),
|
|
18
|
+
fake: v.boolean(),
|
|
19
|
+
restricted: v.boolean(),
|
|
20
|
+
deleted: v.boolean(),
|
|
21
|
+
support: v.boolean(),
|
|
22
|
+
})),
|
|
23
|
+
seen: v.optional(text),
|
|
24
|
+
contact: v.optional(v.boolean()),
|
|
25
|
+
mutual_contact: v.optional(v.boolean()),
|
|
26
|
+
common_chats_count: v.optional(v.nullable(count)),
|
|
27
|
+
registered: v.optional(v.nullable(v.strictObject({
|
|
28
|
+
at: text,
|
|
29
|
+
source: v.picklist(["telegram", "max", "estimate"]),
|
|
30
|
+
precision: v.picklist(["day", "month"]),
|
|
31
|
+
}))),
|
|
32
|
+
has_photo: v.optional(v.boolean()),
|
|
33
|
+
chats: v.array(v.strictObject({
|
|
34
|
+
id,
|
|
35
|
+
title: v.nullable(text),
|
|
36
|
+
kind: chat.entries.kind,
|
|
37
|
+
their_messages: count,
|
|
38
|
+
first_at: v.nullable(time),
|
|
39
|
+
last_at: v.nullable(time),
|
|
40
|
+
complete: v.boolean(),
|
|
41
|
+
})),
|
|
42
|
+
aliases: v.array(v.strictObject({
|
|
43
|
+
name: v.optional(text),
|
|
44
|
+
username: v.optional(text),
|
|
45
|
+
link: v.optional(text),
|
|
46
|
+
first_seen_at: time,
|
|
47
|
+
last_seen_at: time,
|
|
48
|
+
source: v.picklist(["profile", "messages"]),
|
|
49
|
+
})),
|
|
50
|
+
});
|