@wirecat/cli-core 0.544.0 → 0.546.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 +88 -0
- package/dist/calendar/busy.js +4 -1
- package/dist/calendar/cli/answers.d.ts +31 -0
- package/dist/calendar/cli/answers.js +8 -0
- package/dist/calendar/cli/index.d.ts +6 -0
- package/dist/calendar/cli/index.js +43 -4
- package/dist/calendar/cli/remind.js +2 -1
- package/dist/calendar/expand.d.ts +17 -0
- package/dist/calendar/expand.js +340 -0
- package/dist/calendar/feeds.d.ts +96 -0
- package/dist/calendar/feeds.js +324 -0
- package/dist/calendar/ics.d.ts +15 -4
- package/dist/calendar/ics.js +18 -7
- package/dist/calendar/index.d.ts +3 -1
- package/dist/calendar/index.js +2 -0
- package/dist/calendar/server-search.js +3 -1
- package/dist/calendar/writes.js +15 -4
- package/dist/cli/after-dashes.d.ts +16 -0
- package/dist/cli/after-dashes.js +41 -0
- package/dist/cli/bot/context.d.ts +1 -1
- package/dist/cli/bot/context.js +9 -1
- package/dist/cli/bot/messages.js +2 -0
- package/dist/cli/bot/setup.js +1 -0
- package/dist/cli/contract-templates.js +2 -2
- package/dist/cli/entry.d.ts +2 -0
- package/dist/cli/entry.js +3 -2
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/messenger/admin-commands.js +5 -1
- package/dist/cli/messenger/admin-contacts-command.js +5 -1
- package/dist/cli/messenger/admin-folders-command.js +5 -1
- package/dist/cli/messenger/archive-commands.js +20 -2
- package/dist/cli/messenger/backfill-command.js +13 -2
- package/dist/cli/messenger/complete-command.js +1 -0
- package/dist/cli/messenger/context.js +3 -0
- package/dist/cli/messenger/history-gaps.d.ts +12 -0
- package/dist/cli/messenger/history-gaps.js +41 -0
- package/dist/cli/messenger/messages-command.js +9 -22
- package/dist/cli/messenger/messages-edit-command.js +1 -0
- package/dist/cli/messenger/messages-press-command.js +1 -0
- package/dist/cli/messenger/messages-search-command.js +5 -1
- package/dist/cli/messenger/messages-send-command.js +1 -0
- package/dist/cli/messenger/observed.js +2 -2
- package/dist/cli/messenger/polls-command.js +1 -0
- package/dist/cli/messenger/private-people-command.js +5 -1
- package/dist/cli/messenger/search-answers.d.ts +3 -2
- package/dist/cli/messenger/search-answers.js +12 -5
- package/dist/cli/messenger/search-command.js +3 -1
- package/dist/cli/messenger/stored.d.ts +6 -1
- package/dist/cli/messenger/stored.js +94 -5
- package/dist/cli/messenger/topics-command.js +1 -1
- package/dist/cli/program.js +17 -6
- package/dist/cli/schedule/schedule-command.js +1 -0
- package/dist/cli/settings.d.ts +9 -0
- package/dist/cli/settings.js +20 -0
- package/dist/cli/shared-command-exports.d.ts +24 -0
- package/dist/cli/steps.js +1 -0
- package/dist/domain/message-link.d.ts +3 -0
- package/dist/domain/message-link.js +9 -14
- package/dist/domain/money-words.d.ts +11 -0
- package/dist/domain/money-words.js +38 -0
- package/dist/domain/providers.d.ts +2 -0
- package/dist/domain/providers.js +15 -3
- package/dist/domain/ref-arguments.d.ts +31 -0
- package/dist/domain/ref-arguments.js +79 -0
- package/dist/domain/references.d.ts +20 -2
- package/dist/domain/references.js +132 -47
- package/dist/entities/index.d.ts +1 -0
- package/dist/entities/message-transfer.d.ts +4 -0
- package/dist/entities/message-transfer.js +2 -0
- package/dist/errors/reasons.js +18 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/oauth/client-file.d.ts +14 -0
- package/dist/oauth/client-file.js +28 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/ports/messenger.d.ts +3 -2
- package/dist/resolve.d.ts +3 -1
- package/dist/resolve.js +8 -0
- package/dist/search/task-signature.d.ts +5 -1
- package/dist/search/task-signature.js +27 -9
- package/dist/services/agenda.d.ts +5 -0
- package/dist/services/agenda.js +3 -1
- package/dist/services/history-limits.d.ts +45 -0
- package/dist/services/history-limits.js +50 -0
- package/dist/services/index.d.ts +5 -1
- package/dist/services/index.js +5 -1
- package/dist/services/message-resolution.d.ts +4 -1
- package/dist/services/message-resolution.js +19 -3
- package/dist/services/messages.js +15 -13
- package/dist/services/money-words.d.ts +3 -0
- package/dist/services/money-words.js +5 -0
- package/dist/services/questions.d.ts +7 -2
- package/dist/services/questions.js +13 -4
- package/dist/services/stored-project-gathering.d.ts +9 -0
- package/dist/services/stored-project-gathering.js +9 -4
- package/dist/services/task-categories.d.ts +2 -0
- package/dist/services/task-categories.js +19 -8
- package/dist/store/chats.d.ts +10 -0
- package/dist/store/chats.js +25 -0
- package/dist/store/sqlite/chats.js +2 -1
- package/dist/store/sqlite/evidence-sources.js +2 -2
- package/dist/store/sqlite/history-skips.d.ts +24 -0
- package/dist/store/sqlite/history-skips.js +95 -0
- package/dist/store/sqlite/identities.js +1 -1
- package/dist/store/sqlite/knowledge.js +7 -2
- package/dist/store/sqlite/notes.js +4 -4
- package/dist/store/sqlite/things.d.ts +0 -1
- package/dist/store/sqlite/things.js +25 -24
- package/dist/store/store.d.ts +1 -0
- package/package.json +1 -1
- package/parity.json +4 -0
package/dist/cli/entry.d.ts
CHANGED
|
@@ -58,3 +58,5 @@ export interface CliEntry {
|
|
|
58
58
|
* output still flushes.
|
|
59
59
|
*/
|
|
60
60
|
export declare const startCli: (entry: CliEntry) => Promise<void>;
|
|
61
|
+
/** Opens `url` in the system browser, or in `browser` when given; resolves once the browser has started. */
|
|
62
|
+
export declare const openBrowser: (url: string, browser?: string, io?: Pick<EntryIo, "platform" | "spawn">) => Promise<void>;
|
package/dist/cli/entry.js
CHANGED
|
@@ -88,7 +88,7 @@ export const startCli = async (entry) => {
|
|
|
88
88
|
});
|
|
89
89
|
return /^(y|yes)$/i.test(answer.trim());
|
|
90
90
|
},
|
|
91
|
-
openBrowser: (url, browser) => openBrowser(
|
|
91
|
+
openBrowser: (url, browser) => openBrowser(url, browser, io),
|
|
92
92
|
outputClosed: () => closed,
|
|
93
93
|
};
|
|
94
94
|
let code;
|
|
@@ -138,7 +138,8 @@ const commanderExit = (error) => {
|
|
|
138
138
|
return 0;
|
|
139
139
|
return code === "commander.help" ? 2 : undefined;
|
|
140
140
|
};
|
|
141
|
-
|
|
141
|
+
/** Opens `url` in the system browser, or in `browser` when given; resolves once the browser has started. */
|
|
142
|
+
export const openBrowser = (url, browser, io = { platform: process.platform, spawn }) => {
|
|
142
143
|
const [command, prefix] = browser !== undefined
|
|
143
144
|
? [browser, []]
|
|
144
145
|
: io.platform === "darwin"
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export { unanswered } from "../services/questions.js";
|
|
|
39
39
|
export { openRequestTask } from "../services/task-rules.js";
|
|
40
40
|
export { TASK_PAIR_QUERY, TASK_QUERY } from "../services/tasks.js";
|
|
41
41
|
export { answerStdinOption, type Need, type SignInSteps, type SignInStepsOptions, signInSteps, } from "../terminal/sign-in-steps.js";
|
|
42
|
+
export { liftOptionsAfterDashes } from "./after-dashes.js";
|
|
42
43
|
export { type AgentPolicy, type AgentTask, type EvaluationResult, evaluateAgent, type Observation, } from "./agent-evaluation.js";
|
|
43
44
|
export { answerEntity } from "./answer-entity.js";
|
|
44
45
|
export { type AppDoctorOptions, appDoctorReport } from "./app-doctor-command.js";
|
|
@@ -67,7 +68,7 @@ export { type Closeable, withDeadline } from "./deadline.js";
|
|
|
67
68
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
68
69
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
69
70
|
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, previewFlag, REMOTE_READ, REMOTE_WRITE, reachesOtherService, readsOverMcp, readsOverMcpAt, refuseUnderReadOnly, storeChangeRefusal, writesFileOnlyWith, } from "./effects.js";
|
|
70
|
-
export { type CliEntry, type EntryContext, type EntryIo, startCli } from "./entry.js";
|
|
71
|
+
export { type CliEntry, type EntryContext, type EntryIo, openBrowser, startCli } from "./entry.js";
|
|
71
72
|
export { type AnswerOutput, answerSchema, type Body, type ItemBody, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, type ListBody, type ListInput, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
72
73
|
export { withAbort } from "./execution.js";
|
|
73
74
|
export { askSetupQuestions, asksAtStart, type FirstRun, type FirstRunIo, setupAtStart, systemLanguage, } from "./first-run.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -34,6 +34,7 @@ export { unanswered } from "../services/questions.js";
|
|
|
34
34
|
export { openRequestTask } from "../services/task-rules.js";
|
|
35
35
|
export { TASK_PAIR_QUERY, TASK_QUERY } from "../services/tasks.js";
|
|
36
36
|
export { answerStdinOption, signInSteps, } from "../terminal/sign-in-steps.js";
|
|
37
|
+
export { liftOptionsAfterDashes } from "./after-dashes.js";
|
|
37
38
|
export { evaluateAgent, } from "./agent-evaluation.js";
|
|
38
39
|
export { answerEntity } from "./answer-entity.js";
|
|
39
40
|
export { appDoctorReport } from "./app-doctor-command.js";
|
|
@@ -59,7 +60,7 @@ export { withDeadline } from "./deadline.js";
|
|
|
59
60
|
export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
60
61
|
export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
|
|
61
62
|
export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, fetchesOnly, fetchesOnlyAt, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, previewFlag, REMOTE_READ, REMOTE_WRITE, reachesOtherService, readsOverMcp, readsOverMcpAt, refuseUnderReadOnly, storeChangeRefusal, writesFileOnlyWith, } from "./effects.js";
|
|
62
|
-
export { startCli } from "./entry.js";
|
|
63
|
+
export { openBrowser, startCli } from "./entry.js";
|
|
63
64
|
export { answerSchema, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
64
65
|
export { withAbort } from "./execution.js";
|
|
65
66
|
export { askSetupQuestions, asksAtStart, setupAtStart, systemLanguage, } from "./first-run.js";
|
|
@@ -247,7 +247,11 @@ const requestsCommand = (messenger) => {
|
|
|
247
247
|
};
|
|
248
248
|
/** `chats create`, `join`, `leave`, `update` and `link`: the help text is max-cli's, the one both tools say (standard, help rule 2). */
|
|
249
249
|
export const groupCommands = (messenger) => [
|
|
250
|
-
annotate(new Command("create"), {
|
|
250
|
+
annotate(new Command("create"), {
|
|
251
|
+
output: { entity: "chat_group_change" },
|
|
252
|
+
effects: REMOTE_WRITE,
|
|
253
|
+
literal_after_dashes: true,
|
|
254
|
+
})
|
|
251
255
|
.description("create a group or a channel; the people added are told")
|
|
252
256
|
.argument("<title>", "the group's name")
|
|
253
257
|
.argument("[person...]", "people to add: an id, or part of a name")
|
|
@@ -30,7 +30,11 @@ export const contactWriteCommands = (messenger) => {
|
|
|
30
30
|
const done = await context.withServices((services) => services.people[name](person));
|
|
31
31
|
renderAnswer(this, context, itemOf(snakeKeys(done)));
|
|
32
32
|
}));
|
|
33
|
-
const rename = annotate(new Command("rename"), {
|
|
33
|
+
const rename = annotate(new Command("rename"), {
|
|
34
|
+
effects: REMOTE_WRITE,
|
|
35
|
+
output: { entity: "contact_update" },
|
|
36
|
+
literal_after_dashes: true,
|
|
37
|
+
})
|
|
34
38
|
.summary("change a contact's name in the messenger address book")
|
|
35
39
|
.description("rename the contact in the messenger address book; use contacts alias for a private local name")
|
|
36
40
|
.argument("<person>", PERSON)
|
|
@@ -57,7 +57,11 @@ export const foldersCommand = (messenger) => {
|
|
|
57
57
|
const found = await context.withServices((services) => services.folders.show(folder));
|
|
58
58
|
renderAnswer(this, context, itemOf(folderAnswer(found)));
|
|
59
59
|
}));
|
|
60
|
-
folders.addCommand(withRules(annotate(new Command("create"), {
|
|
60
|
+
folders.addCommand(withRules(annotate(new Command("create"), {
|
|
61
|
+
output: { entity: "chat_folder_change" },
|
|
62
|
+
effects: REMOTE_WRITE,
|
|
63
|
+
literal_after_dashes: true,
|
|
64
|
+
})
|
|
61
65
|
.description("create a chat folder")
|
|
62
66
|
.argument("<title>", "the folder's name; the app may refuse a long one")
|
|
63
67
|
.option("--chat <chat>", "a chat to put in it, by id or name; repeat it for more", collect), messenger, false).action(async function (title) {
|
|
@@ -12,11 +12,12 @@ import { kindsOf } from "../../services/inbox.js";
|
|
|
12
12
|
import { dateWindow } from "../../services/moment.js";
|
|
13
13
|
import { destroysNothing, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
|
|
14
14
|
import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
15
|
-
import { commandAt } from "../steps.js";
|
|
15
|
+
import { commandAt, lineOf } from "../steps.js";
|
|
16
16
|
import { jobsCommand } from "./backfill-command.js";
|
|
17
17
|
import { messengerContext } from "./context.js";
|
|
18
18
|
import { admitPassword, linesOf, openFolder, runFileFor, saveManifest, sealRun, writeChanges } from "./export-folder.js";
|
|
19
19
|
import { gapsCommand } from "./gaps-command.js";
|
|
20
|
+
import { historyLimitGaps } from "./history-gaps.js";
|
|
20
21
|
import { messageItems } from "./message-items.js";
|
|
21
22
|
import { ENCRYPT_OPTION, passwordOf } from "./password.js";
|
|
22
23
|
import { storeMaintenanceCommands } from "./store-maintenance-command.js";
|
|
@@ -71,9 +72,26 @@ const statusCommand = (messenger) => annotate(new Command("status"), { output: {
|
|
|
71
72
|
.action(async function (chat) {
|
|
72
73
|
const context = messengerContext(this, messenger);
|
|
73
74
|
const rows = await context.withServices((services) => services.archive.status(chat));
|
|
74
|
-
|
|
75
|
+
const skips = await context
|
|
76
|
+
.withStore((store, account) => store.chats.historySkips(account))
|
|
77
|
+
.then((all) => chat === undefined ? all : all.filter((skip) => rows.some((row) => row.chatId === skip.chatId)))
|
|
78
|
+
.catch(() => []);
|
|
79
|
+
const limited = historyLimitGaps(skips);
|
|
80
|
+
renderAnswer(this, context, listOf("store status", {
|
|
81
|
+
items: rows.map(snakeKeys),
|
|
82
|
+
limit: Math.max(1, rows.length),
|
|
83
|
+
has_more: false,
|
|
84
|
+
next: limited.next,
|
|
85
|
+
missing: limited.missing,
|
|
86
|
+
}));
|
|
75
87
|
if (rows.length === 0 && context.format === "pretty")
|
|
76
88
|
context.renderer.note("the store holds no messages for this profile yet");
|
|
89
|
+
if (skips.length > 0 && context.format === "pretty")
|
|
90
|
+
context.renderer.note(`history limits left out pushed history of ${skips.length} chats — \`${lineOf(this, "messages sync <chat> --full-history")}\` fetches one and keeps it in full:\n` +
|
|
91
|
+
skips
|
|
92
|
+
.slice(0, 20)
|
|
93
|
+
.map((skip) => ` ${skip.title ?? skip.chatId} ${skip.messages} messages ${skip.reason}`)
|
|
94
|
+
.join("\n"));
|
|
77
95
|
});
|
|
78
96
|
const LISTED = 20;
|
|
79
97
|
/** Titles are the owner's: these lines go to a person's terminal, never into an error message. */
|
|
@@ -58,14 +58,17 @@ export const syncCommand = (messenger) => {
|
|
|
58
58
|
.option("--catch-up-chunks <n>", "at most this many local vector chunks", wholeNumber)
|
|
59
59
|
.option("--catch-up-messages <n>", "skip a graph rebuild larger than this many messages", wholeNumber)
|
|
60
60
|
.option("--catch-up-time <duration>", "local preparation time budget, 30s by default")
|
|
61
|
+
.option("--full-history", "keep this chat's pushed history in full from now on, past the history limits (history_* settings)")
|
|
61
62
|
.option("--background", "run as a job that outlives this command; `store jobs show` follows it")
|
|
62
63
|
.option("--dry-run", "only estimate how many messages, requests and minutes a full fetch would still take — from the store, no request")
|
|
63
64
|
.action(async function (chat) {
|
|
64
|
-
const { all, pause, sinceTime: since, last, background, dryRun: estimate, catchUp, catchUpChunks, catchUpMessages, catchUpTime, ...sizes } = this.opts();
|
|
65
|
+
const { all, pause, sinceTime: since, last, background, dryRun: estimate, catchUp, catchUpChunks, catchUpMessages, catchUpTime, fullHistory: include, ...sizes } = this.opts();
|
|
65
66
|
if ((chat === undefined) === (all !== true))
|
|
66
67
|
throw new CliError("validation_error", "name a chat, or --all for every chat");
|
|
67
68
|
if (all && estimate)
|
|
68
69
|
throw new CliError("validation_error", "--dry-run prices one chat; not with --all");
|
|
70
|
+
if (include && chat === undefined)
|
|
71
|
+
throw new CliError("validation_error", "--full-history keeps one chat: name it");
|
|
69
72
|
if (all && messenger.history === "store") {
|
|
70
73
|
throw new CliError("validation_error", `--all walks the messenger's chat list, and ${messenger.name ?? messenger.app.command} pushes its chats ` +
|
|
71
74
|
`instead — keep \`${lineOf(this, "serve")}\` running${messenger.historyOnDemand ? ", or name one chat" : ""}`);
|
|
@@ -164,6 +167,10 @@ export const syncCommand = (messenger) => {
|
|
|
164
167
|
const result = await context.withServices((services) => chat === undefined ? services.archive.fetchAll(options) : services.archive.fetch(chat, options));
|
|
165
168
|
if (jobId)
|
|
166
169
|
finishJob(messenger.app, jobs, jobId, { finishedAt: new Date().toISOString(), result });
|
|
170
|
+
const synced = "chat" in result ? result.chat : null;
|
|
171
|
+
const included = include && synced
|
|
172
|
+
? await context.withStore((store, account) => store.chats.setHistoryIncluded(account, synced, true))
|
|
173
|
+
: undefined;
|
|
167
174
|
const tasks = await taskRulesAfterSync(context);
|
|
168
175
|
const prepared = "prepared" in result ? result.prepared : undefined;
|
|
169
176
|
const unprepared = prepared && !prepared.complete && "chat" in result && result.chat !== null;
|
|
@@ -171,7 +178,11 @@ export const syncCommand = (messenger) => {
|
|
|
171
178
|
const review = await context.withServices((services) => services.mentions.newCount(point.after));
|
|
172
179
|
const toReview = review.count;
|
|
173
180
|
const shown = `${toReview}${review.more ? "+" : ""}`;
|
|
174
|
-
renderAnswer(this, context, itemOf(snakeKeys(
|
|
181
|
+
renderAnswer(this, context, itemOf(snakeKeys({
|
|
182
|
+
...result,
|
|
183
|
+
...(tasks && tasks.added + tasks.closed + (tasks.held ?? 0) > 0 ? { tasks } : {}),
|
|
184
|
+
...(included === undefined ? {} : { historyIncluded: included }),
|
|
185
|
+
}), {
|
|
175
186
|
next: [
|
|
176
187
|
...(unprepared
|
|
177
188
|
? [
|
|
@@ -20,6 +20,7 @@ const SHELLS = ["zsh", "bash", "fish", "powershell"];
|
|
|
20
20
|
* exists — no store, no names — and nothing goes to stderr: a shell shows whatever it is given.
|
|
21
21
|
*/
|
|
22
22
|
export const completeCommand = (messenger, config, options = {}) => annotate(new Command("complete"), {
|
|
23
|
+
literal_after_dashes: true,
|
|
23
24
|
effects: LOCAL_READ,
|
|
24
25
|
mcp: false,
|
|
25
26
|
examples: [`${messenger.app.command} complete zsh`],
|
|
@@ -231,6 +231,9 @@ export const connected = (connection, { app, provider, name, deletedWithoutChat,
|
|
|
231
231
|
recipientIds: () => (new RecipientList(recipientsPathFor(app, settings.profile, env), app.command).read() ?? []).flatMap((recipient) => [recipient.id, ...(recipient.partnerId ? [recipient.partnerId] : [])]),
|
|
232
232
|
...(history === "store" ? { namesFromStore: savedChatId ? { savedChatId } : {} } : {}),
|
|
233
233
|
...(unreadCounts === "store" ? { countsUnread: true } : {}),
|
|
234
|
+
...(history === "store" && settings.historyLimits
|
|
235
|
+
? { historyLimits: settings.historyLimits, command: `${app.command} ${settings.profile}` }
|
|
236
|
+
: {}),
|
|
234
237
|
});
|
|
235
238
|
const passives = history === "store" && self !== null && !watching
|
|
236
239
|
? passivesOf(saving).map((passive) => listened(passive, events, renderer.warn, closeWaitMs))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Missing, NextStep } from "@wirecat/cli-base/standards";
|
|
2
|
+
import type { AccountKey, MessageStore, StoredHistorySkip } from "../../store/store.js";
|
|
3
|
+
/**
|
|
4
|
+
* What the history limits (`history_*` settings) left out of pushed history, as `missing` and `next`: an answer read
|
|
5
|
+
* from the store must not pass for every message the messenger has.
|
|
6
|
+
*/
|
|
7
|
+
export declare const historyLimitGaps: (skips: StoredHistorySkip[]) => {
|
|
8
|
+
next: NextStep[];
|
|
9
|
+
missing: Missing[];
|
|
10
|
+
};
|
|
11
|
+
/** What the limits left out in every account an answer read, most messages first. */
|
|
12
|
+
export declare const historySkipsOf: (store: Pick<MessageStore, "chats">, accounts: readonly AccountKey[]) => Promise<StoredHistorySkip[]>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { nextStep } from "../steps.js";
|
|
2
|
+
const SHOWN = 5;
|
|
3
|
+
const reasonOf = (skip) => skip.reason === "group_too_large"
|
|
4
|
+
? `a group of ${skip.members ?? "many"} people, not saved`
|
|
5
|
+
: `only from ${skip.keptFrom?.slice(0, 10) ?? "the window's start"}`;
|
|
6
|
+
const nameOf = (skip) => skip.title ?? skip.chatId;
|
|
7
|
+
/**
|
|
8
|
+
* What the history limits (`history_*` settings) left out of pushed history, as `missing` and `next`: an answer read
|
|
9
|
+
* from the store must not pass for every message the messenger has.
|
|
10
|
+
*/
|
|
11
|
+
export const historyLimitGaps = (skips) => {
|
|
12
|
+
const [first] = skips;
|
|
13
|
+
if (!first)
|
|
14
|
+
return { next: [], missing: [] };
|
|
15
|
+
const messages = skips.reduce((sum, skip) => sum + skip.messages, 0);
|
|
16
|
+
const listed = skips
|
|
17
|
+
.slice(0, SHOWN)
|
|
18
|
+
.map((skip) => `${nameOf(skip)} (${skip.messages}, ${reasonOf(skip)})`)
|
|
19
|
+
.join("; ");
|
|
20
|
+
const more = skips.length > SHOWN ? `; and ${skips.length - SHOWN} more — \`store status\` lists them` : "";
|
|
21
|
+
return {
|
|
22
|
+
next: [
|
|
23
|
+
{
|
|
24
|
+
...nextStep("messages.sync", { args: { chat: first.chatId, full_history: true } }),
|
|
25
|
+
why: "fetches this chat's history and keeps it in full from now on, past the history limits",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
missing: [
|
|
29
|
+
{
|
|
30
|
+
field: "history_limits",
|
|
31
|
+
why: `the history limits (history_* settings) left ${messages} pushed messages of ${skips.length} chats out of ` +
|
|
32
|
+
`the store: ${listed}${more}`,
|
|
33
|
+
step: "messages.sync",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/** What the limits left out in every account an answer read, most messages first. */
|
|
39
|
+
export const historySkipsOf = async (store, accounts) => (await Promise.all(accounts.map((account) => store.chats.historySkips(account))))
|
|
40
|
+
.flat()
|
|
41
|
+
.sort((a, b) => b.messages - a.messages);
|
|
@@ -18,6 +18,7 @@ import { messengerContext } from "./context.js";
|
|
|
18
18
|
import { linksCommand } from "./conversations-command.js";
|
|
19
19
|
import { downloadSubcommand } from "./download-command.js";
|
|
20
20
|
import { heardItems, hearForCommand, hearingMissing, MODEL_OPTION, spokenItems, TRANSCRIBE_OPTION, } from "./hearing-command.js";
|
|
21
|
+
import { historyLimitGaps } from "./history-gaps.js";
|
|
21
22
|
import { cursorOf, historyQuery, historyWindow } from "./history-query.js";
|
|
22
23
|
import { hideNever } from "./lacks.js";
|
|
23
24
|
import { threadAnswer } from "./message-answers.js";
|
|
@@ -74,9 +75,12 @@ export const messagesCommand = (messenger) => {
|
|
|
74
75
|
: undefined;
|
|
75
76
|
// Offline, the store is the whole answer: say how much of the chat it holds. Online the messenger is read.
|
|
76
77
|
const gaps = context.settings.offline
|
|
77
|
-
?
|
|
78
|
-
await storedChatId(messenger, chat, store, account, { includeLeft: true })
|
|
79
|
-
|
|
78
|
+
? await context.withStore(async (store, account) => {
|
|
79
|
+
const chatId = await storedChatId(messenger, chat, store, account, { includeLeft: true });
|
|
80
|
+
const held = coverageGaps(await chatCoverage(store, account, [chatId]));
|
|
81
|
+
const limited = historyLimitGaps((await store.chats.historySkips(account)).filter((skip) => skip.chatId === chatId));
|
|
82
|
+
return { next: [...limited.next, ...held.next], missing: [...limited.missing, ...held.missing] };
|
|
83
|
+
})
|
|
80
84
|
: { next: [], missing: [] };
|
|
81
85
|
renderAnswer(this, context, listOf("messages list", {
|
|
82
86
|
items: messageItems(heardItems(page.items, hearing), reader),
|
|
@@ -93,9 +97,8 @@ export const messagesCommand = (messenger) => {
|
|
|
93
97
|
annotate(list, { output: { entity: "message" }, query: history });
|
|
94
98
|
const readWindow = async (command, chat, message, window) => {
|
|
95
99
|
const context = messengerContext(command, messenger);
|
|
96
|
-
const target = targetOf(messenger, chat, message);
|
|
97
100
|
const { found, reader } = await context.withServices(async (services) => ({
|
|
98
|
-
found: await services.messages.around(
|
|
101
|
+
found: await services.messages.around(chat, message?.trim(), window),
|
|
99
102
|
reader: await services.messages.reader(),
|
|
100
103
|
}));
|
|
101
104
|
const hearing = await hearForCommand(context, messenger, found, false);
|
|
@@ -152,9 +155,8 @@ export const messagesCommand = (messenger) => {
|
|
|
152
155
|
const requested = threadRequest(this).thread;
|
|
153
156
|
if (requested) {
|
|
154
157
|
const context = messengerContext(this, messenger);
|
|
155
|
-
const target = targetOf(messenger, chat, message);
|
|
156
158
|
const { found, reader } = await context.withServices(async (services) => ({
|
|
157
|
-
found: await services.messages.thread(
|
|
159
|
+
found: await services.messages.thread(chat, message?.trim(), { ...requested, before, after }),
|
|
158
160
|
reader: await services.messages.reader(),
|
|
159
161
|
}));
|
|
160
162
|
if (found.fallback)
|
|
@@ -199,19 +201,4 @@ const count = (value) => {
|
|
|
199
201
|
throw new CliError("validation_error", `"${value}" is not a count`);
|
|
200
202
|
return parsed;
|
|
201
203
|
};
|
|
202
|
-
/** A chat and a message id, or one locator naming both — which must be this messenger's. */
|
|
203
|
-
const targetOf = (messenger, chat, message) => {
|
|
204
|
-
if (isLocator(chat)) {
|
|
205
|
-
if (message !== undefined)
|
|
206
|
-
throw new CliError("validation_error", "a locator already names the message");
|
|
207
|
-
const locator = parseLocator(chat);
|
|
208
|
-
if (locator.provider !== messenger.provider) {
|
|
209
|
-
throw new CliError("validation_error", `that locator is a ${locator.provider} message, not ${messenger.provider}`);
|
|
210
|
-
}
|
|
211
|
-
return { chat: locator.chat, message: locator.message };
|
|
212
|
-
}
|
|
213
|
-
if (message === undefined)
|
|
214
|
-
throw new CliError("validation_error", "which message? give its id after the chat");
|
|
215
|
-
return { chat, message: message.trim() };
|
|
216
|
-
};
|
|
217
204
|
export { sendCommand } from "./messages-send-command.js";
|
|
@@ -9,6 +9,7 @@ import { HTML_HELP } from "./messages-send-command.js";
|
|
|
9
9
|
import { readAll } from "./stdin.js";
|
|
10
10
|
export const editCommand = (messenger) => {
|
|
11
11
|
const edit = annotate(new Command("edit"), {
|
|
12
|
+
literal_after_dashes: true,
|
|
12
13
|
examples: [`${messenger.app.command} personal messages edit "Book club" 101 "The meeting starts at 11."`],
|
|
13
14
|
effects: REMOTE_WRITE,
|
|
14
15
|
output: { entity: "message_write" },
|
|
@@ -6,6 +6,7 @@ import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
|
6
6
|
import { messengerContext } from "./context.js";
|
|
7
7
|
/** `messages press` — one of a bot's callback buttons; the bot sees who pressed it. */
|
|
8
8
|
export const pressCommand = (messenger) => annotate(new Command("press"), {
|
|
9
|
+
literal_after_dashes: true,
|
|
9
10
|
effects: REMOTE_WRITE,
|
|
10
11
|
examples: [`${messenger.app.command} personal messages press "Example Bot" 101 1`],
|
|
11
12
|
})
|
|
@@ -12,6 +12,7 @@ import { renderAnswer } from "../envelope.js";
|
|
|
12
12
|
import { positiveCount } from "../paging.js";
|
|
13
13
|
import { archiveSummary } from "./archive-summary.js";
|
|
14
14
|
import { messengerContext } from "./context.js";
|
|
15
|
+
import { historySkipsOf } from "./history-gaps.js";
|
|
15
16
|
import { meaningNote, messagesAnswer, offsetOf, shownOf } from "./search-answers.js";
|
|
16
17
|
import { backendOptions, backendRequest, noteServer } from "./search-backend-options.js";
|
|
17
18
|
import { syncOptions, syncRequest, syncsFirst } from "./search-sync-options.js";
|
|
@@ -207,7 +208,10 @@ const searchAction = (messenger) => async function (given) {
|
|
|
207
208
|
...(newest ? { newest: true } : {}),
|
|
208
209
|
...(timezone === undefined ? {} : { timezone }),
|
|
209
210
|
};
|
|
210
|
-
|
|
211
|
+
const skips = await context
|
|
212
|
+
.withStore((store) => historySkipsOf(store, found.coverage?.accounts ?? found.accounts ?? []))
|
|
213
|
+
.catch(() => []);
|
|
214
|
+
renderAnswer(this, context, messagesAnswer("search messages", found, { limit, page: page ?? 1, again }, given.join(" "), skips), view);
|
|
211
215
|
};
|
|
212
216
|
const withOffset = (page, limit) => {
|
|
213
217
|
const offset = offsetOf(page, limit);
|
|
@@ -20,6 +20,7 @@ const collect = (value, previous) => [...previous, value];
|
|
|
20
20
|
const repeatable = (flags, description) => new Option(flags, description).argParser(collect).default([], "none");
|
|
21
21
|
export const sendCommand = (messenger) => {
|
|
22
22
|
const send = annotate(new Command("send"), {
|
|
23
|
+
literal_after_dashes: true,
|
|
23
24
|
examples: [`${messenger.app.command} personal messages send "Book club" "The meeting starts at 10."`],
|
|
24
25
|
effects: REMOTE_WRITE,
|
|
25
26
|
output: { entity: messenger.drafts === true ? "message_send" : "message_write" },
|
|
@@ -42,9 +42,9 @@ export const observed = (messenger, events) => {
|
|
|
42
42
|
};
|
|
43
43
|
const counted = async (operation, onItem, work, count) => {
|
|
44
44
|
const counts = {};
|
|
45
|
-
await timed(operation, {}, () => work((item) => {
|
|
45
|
+
await timed(operation, {}, () => work((item, ...rest) => {
|
|
46
46
|
count(item, counts);
|
|
47
|
-
return onItem(item);
|
|
47
|
+
return onItem(item, ...rest);
|
|
48
48
|
}), () => ({ counts }));
|
|
49
49
|
};
|
|
50
50
|
const mint = messenger.newSendId;
|
|
@@ -110,6 +110,7 @@ export const pollsCommand = (messenger) => {
|
|
|
110
110
|
renderAnswer(this, context, itemOf(snakeKeys(closed)));
|
|
111
111
|
});
|
|
112
112
|
const create = annotate(polls.command("create"), {
|
|
113
|
+
literal_after_dashes: true,
|
|
113
114
|
output: { entity: "message_write" },
|
|
114
115
|
effects: REMOTE_WRITE,
|
|
115
116
|
examples: [`${messenger.app.command} personal polls create "Book club" "Which day?" Monday Tuesday`],
|
|
@@ -14,7 +14,11 @@ const NOTES_QUERY = unfilteredQuery("contact_note", contactNote);
|
|
|
14
14
|
const everyNote = (command, items) => listOf(command, { items, limit: Math.max(1, items.length), has_more: false });
|
|
15
15
|
export const privatePeopleCommands = (messenger) => {
|
|
16
16
|
const alias = new Command("aliases").description("a private local display name in the selected account");
|
|
17
|
-
annotate(alias.command("set"), {
|
|
17
|
+
annotate(alias.command("set"), {
|
|
18
|
+
effects: LOCAL_WRITE,
|
|
19
|
+
output: { entity: "contact_alias" },
|
|
20
|
+
literal_after_dashes: true,
|
|
21
|
+
})
|
|
18
22
|
.description("your own name for a person in this account; kept on this machine, never sent")
|
|
19
23
|
.argument("<person>", "the person's id, reference or known name")
|
|
20
24
|
.argument("<alias>", "the private name to use for this person")
|
|
@@ -8,6 +8,7 @@ import type { MeaningSkipped } from "../../services/messages-discovery.js";
|
|
|
8
8
|
import type { FoundNote, NotesFoundOf } from "../../services/notes-search.js";
|
|
9
9
|
import type { SearchAllItem } from "../../services/search-all.js";
|
|
10
10
|
import type { MeetingSearchItem, SearchAllIncludingMeetingsFound } from "../../services/search-all-meetings.js";
|
|
11
|
+
import type { StoredHistorySkip } from "../../store/store.js";
|
|
11
12
|
import { type ListBody } from "../envelope.js";
|
|
12
13
|
/** What a source tool's `search all` reads: never mail, which is memo's (docs/dev/TOOL-BOUNDARIES.md). */
|
|
13
14
|
export declare const SOURCE_SEARCHED: readonly ["messages", "notes", "events", "meetings"];
|
|
@@ -42,10 +43,10 @@ export interface Paging {
|
|
|
42
43
|
/** "showing 20 of 144", for the human view under a page of hits; `+` when the total is a lower bound. */
|
|
43
44
|
export declare const shownOf: (shown: number, offset: number, total: SearchTotal | undefined) => string | undefined;
|
|
44
45
|
/** `search messages` and `search mail`, CLI and MCP alike. */
|
|
45
|
-
export declare const messagesAnswer: (command: string, found: SearchFound, { limit, page, again }: Paging, query?: string) => ListBody<MessageHitItem>;
|
|
46
|
+
export declare const messagesAnswer: (command: string, found: SearchFound, { limit, page, again }: Paging, query?: string, skips?: StoredHistorySkip[]) => ListBody<MessageHitItem>;
|
|
46
47
|
export declare const noteHitItem: (hit: FoundNote) => NoteHitItem;
|
|
47
48
|
export declare const allHitItem: (item: SearchAllItem | MeetingSearchItem) => HitItem;
|
|
48
49
|
/** `search notes`, CLI and MCP alike. */
|
|
49
50
|
export declare const notesAnswer: (command: string, found: NotesFoundOf, { limit, page, again }: Paging, exact?: boolean) => ListBody<NoteHitItem>;
|
|
50
51
|
/** `search all`, CLI and MCP alike. A bounded meeting scan that could not tell whether more match says so. */
|
|
51
|
-
export declare const allAnswer: (command: string, found: SearchAllIncludingMeetingsFound, { limit, page, again }: Paging, exact?: boolean) => ListBody<HitItem>;
|
|
52
|
+
export declare const allAnswer: (command: string, found: SearchAllIncludingMeetingsFound, { limit, page, again }: Paging, exact?: boolean, skips?: StoredHistorySkip[]) => ListBody<HitItem>;
|
|
@@ -7,6 +7,7 @@ import { MAX_SEARCH_OFFSET } from "../../services/messages-archive-search.js";
|
|
|
7
7
|
import { listOf } from "../envelope.js";
|
|
8
8
|
import { nextStep } from "../steps.js";
|
|
9
9
|
import { archiveSummary } from "./archive-summary.js";
|
|
10
|
+
import { historyLimitGaps } from "./history-gaps.js";
|
|
10
11
|
/** What a source tool's `search all` reads: never mail, which is memo's (docs/dev/TOOL-BOUNDARIES.md). */
|
|
11
12
|
export const SOURCE_SEARCHED = ["messages", "notes", "events", "meetings"];
|
|
12
13
|
const nonEmpty = (list) => (list.length > 0 ? list : undefined);
|
|
@@ -249,8 +250,10 @@ export const shownOf = (shown, offset, total) => total === undefined || shown ==
|
|
|
249
250
|
? undefined
|
|
250
251
|
: `showing ${offset + 1}–${offset + shown} of ${total.value}${total.relation === "gte" ? "+" : ""}`;
|
|
251
252
|
/** `search messages` and `search mail`, CLI and MCP alike. */
|
|
252
|
-
export const messagesAnswer = (command, found, { limit, page, again }, query = "") => {
|
|
253
|
+
export const messagesAnswer = (command, found, { limit, page, again }, query = "", skips = []) => {
|
|
253
254
|
const accounts = found.coverage?.accounts ?? found.accounts ?? [];
|
|
255
|
+
const gaps = gapsOf(found);
|
|
256
|
+
const limited = historyLimitGaps(skips);
|
|
254
257
|
return listOf(command, {
|
|
255
258
|
items: [...found.items.map((hit) => messageHitItem(hit, query)), ...pictureItems(found)],
|
|
256
259
|
limit,
|
|
@@ -258,8 +261,9 @@ export const messagesAnswer = (command, found, { limit, page, again }, query = "
|
|
|
258
261
|
page: page ?? 1,
|
|
259
262
|
...(found.total ? { total: found.total } : {}),
|
|
260
263
|
...(again ? { args: again } : {}),
|
|
261
|
-
...
|
|
262
|
-
|
|
264
|
+
next: [...gaps.next, ...limited.next],
|
|
265
|
+
missing: [...gaps.missing, ...limited.missing],
|
|
266
|
+
meta: metaOf(accounts, found.skipped, coverageGaps(found).missing.length > 0 || skips.length > 0),
|
|
263
267
|
});
|
|
264
268
|
};
|
|
265
269
|
/**
|
|
@@ -327,7 +331,8 @@ export const notesAnswer = (command, found, { limit, page, again }, exact = fals
|
|
|
327
331
|
meta: metaOf(found.accounts, found.skipped),
|
|
328
332
|
});
|
|
329
333
|
/** `search all`, CLI and MCP alike. A bounded meeting scan that could not tell whether more match says so. */
|
|
330
|
-
export const allAnswer = (command, found, { limit, page, again }, exact = false) => {
|
|
334
|
+
export const allAnswer = (command, found, { limit, page, again }, exact = false, skips = []) => {
|
|
335
|
+
const limited = historyLimitGaps(skips);
|
|
331
336
|
const scan = found.meetings?.complete === false ? found.meetings : undefined;
|
|
332
337
|
const notes = meaningGap(found.notes?.meaningSkipped, exact);
|
|
333
338
|
const messages = messageMeaningGap(found.messages?.meaningSkipped, "found_by");
|
|
@@ -351,6 +356,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
351
356
|
...notes.next,
|
|
352
357
|
...messages.next.filter((step) => !notes.next.some((asked) => JSON.stringify(asked) === JSON.stringify(step))),
|
|
353
358
|
...archive.next,
|
|
359
|
+
...limited.next,
|
|
354
360
|
],
|
|
355
361
|
missing: [
|
|
356
362
|
...(scan
|
|
@@ -365,7 +371,8 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
|
|
|
365
371
|
...notes.missing,
|
|
366
372
|
...messages.missing,
|
|
367
373
|
...archive.missing,
|
|
374
|
+
...limited.missing,
|
|
368
375
|
],
|
|
369
|
-
meta: metaOf(found.accounts, found.skipped, archive.missing.length > 0),
|
|
376
|
+
meta: metaOf(found.accounts, found.skipped, archive.missing.length > 0 || skips.length > 0),
|
|
370
377
|
});
|
|
371
378
|
};
|
|
@@ -10,6 +10,7 @@ import { runsSearchCommand } from "../runs/search-command.js";
|
|
|
10
10
|
import { commandAt } from "../steps.js";
|
|
11
11
|
import { messengerContext } from "./context.js";
|
|
12
12
|
import { conversationsSearchCommand } from "./conversations-command.js";
|
|
13
|
+
import { historySkipsOf } from "./history-gaps.js";
|
|
13
14
|
import { messagesSearchCommand } from "./messages-search-command.js";
|
|
14
15
|
import { allAnswer, meaningNote, notesAnswer, offsetOf, SOURCE_SEARCHED, shownOf, } from "./search-answers.js";
|
|
15
16
|
import { backendOptions, backendRequest, noteServer } from "./search-backend-options.js";
|
|
@@ -92,7 +93,8 @@ const allCommand = (messenger) => {
|
|
|
92
93
|
...(exact ? { exact: true } : {}),
|
|
93
94
|
...(timezone === undefined ? {} : { timezone }),
|
|
94
95
|
};
|
|
95
|
-
|
|
96
|
+
const skips = await context.withStore((store) => historySkipsOf(store, found.accounts)).catch(() => []);
|
|
97
|
+
renderAnswer(this, context, allAnswer("search all", found, { limit, page: page ?? 1, again }, false, skips), () => {
|
|
96
98
|
if (found.items.length === 0)
|
|
97
99
|
context.renderer.note(found.server && !found.server.skipped
|
|
98
100
|
? "nothing found in the local store or on the messenger's server"
|
|
@@ -2,6 +2,7 @@ import type { Id } from "../../domain/models.js";
|
|
|
2
2
|
import { type MessengerAdapter } from "../../ports/messenger.js";
|
|
3
3
|
import type { MessengerTraits } from "../../ports/traits.js";
|
|
4
4
|
import type { EventSink, Warn } from "../../runs/events.js";
|
|
5
|
+
import { type HistoryLimits } from "../../services/history-limits.js";
|
|
5
6
|
import type { AccountKey, DeletionScope, MessageStore } from "../../store/store.js";
|
|
6
7
|
export interface Saving {
|
|
7
8
|
account: AccountKey;
|
|
@@ -41,6 +42,10 @@ export interface Saving {
|
|
|
41
42
|
namesFromStore?: Pick<MessengerTraits, "savedChatId">;
|
|
42
43
|
/** `MessengerTraits.unreadCounts: "store"`: a message `watch` or `listen` saves raises its chat's unread count. */
|
|
43
44
|
countsUnread?: boolean;
|
|
45
|
+
/** Pushed history (`feed`, `listenHistory`) outside these is not saved, and each chat records what it left out. */
|
|
46
|
+
historyLimits?: HistoryLimits;
|
|
47
|
+
/** The app command and profile, for the warning that names the command listing what the limits left out. */
|
|
48
|
+
command?: string;
|
|
44
49
|
}
|
|
45
50
|
/**
|
|
46
51
|
* The adapter, with what each read answers written to the store. **A failed write never fails the
|
|
@@ -50,4 +55,4 @@ export interface Saving {
|
|
|
50
55
|
* would erase what `chats list` stored. Nor are `chat` and `contact`: a card's member list is not
|
|
51
56
|
* known to be whole, and `saveMembers` replaces the list.
|
|
52
57
|
*/
|
|
53
|
-
export declare const stored: (messenger: MessengerAdapter, { account, store, warn, events, pending, announcing, deletedWithoutChat, profile, rekeyed, rekeyPacing, closing, namesFromStore, recipientIds, countsUnread, }: Saving) => MessengerAdapter;
|
|
58
|
+
export declare const stored: (messenger: MessengerAdapter, { account, store, warn, events, pending, announcing, deletedWithoutChat, profile, rekeyed, rekeyPacing, closing, namesFromStore, recipientIds, countsUnread, historyLimits, command, }: Saving) => MessengerAdapter;
|