@wirecat/cli-core 0.545.0 → 0.547.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 +228 -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/complete-command.js +1 -0
- package/dist/cli/messenger/history-query.d.ts +10 -4
- package/dist/cli/messenger/history-query.js +87 -31
- package/dist/cli/messenger/messages-command.js +12 -23
- package/dist/cli/messenger/messages-comments-command.js +4 -2
- 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-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/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/stored.js +56 -3
- package/dist/cli/messenger/topics-command.js +1 -1
- package/dist/cli/option-hints.d.ts +6 -0
- package/dist/cli/option-hints.js +15 -0
- package/dist/cli/program.js +17 -6
- package/dist/cli/schedule/schedule-command.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/errors/reasons.js +31 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mail/cli/index.js +4 -4
- package/dist/mail/index.d.ts +2 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/partial.d.ts +34 -0
- package/dist/mail/partial.js +94 -0
- package/dist/mail/server-search.js +17 -9
- package/dist/mail/source.d.ts +13 -2
- package/dist/mail/source.js +7 -1
- package/dist/mail/sync-types.d.ts +112 -0
- package/dist/mail/sync-types.js +1 -0
- package/dist/mail/sync.d.ts +4 -106
- package/dist/mail/sync.js +49 -4
- package/dist/mail/testing/fake.d.ts +3 -1
- package/dist/mail/testing/fake.js +10 -2
- 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/deps.d.ts +0 -2
- package/dist/services/deps.js +0 -2
- package/dist/services/documents/ingestion.js +1 -1
- package/dist/services/documents/knowledge.d.ts +10 -6
- package/dist/services/documents/knowledge.js +25 -11
- package/dist/services/index.d.ts +6 -1
- package/dist/services/index.js +6 -1
- package/dist/services/message-resolution.d.ts +4 -1
- package/dist/services/message-resolution.js +19 -3
- package/dist/services/messages.d.ts +2 -0
- package/dist/services/messages.js +40 -36
- package/dist/services/money-words.d.ts +3 -0
- package/dist/services/money-words.js +5 -0
- package/dist/services/proposal-tasks.d.ts +3 -2
- package/dist/services/proposal-tasks.js +5 -2
- package/dist/services/proposals/ids.d.ts +5 -0
- package/dist/services/proposals/ids.js +43 -0
- package/dist/services/proposals/index.d.ts +3 -0
- package/dist/services/proposals/index.js +2 -0
- package/dist/services/proposals/kinds/action.d.ts +7 -0
- package/dist/services/proposals/kinds/action.js +45 -0
- package/dist/services/proposals/kinds/fact.d.ts +4 -0
- package/dist/services/proposals/kinds/fact.js +43 -0
- package/dist/services/proposals/kinds/findings.d.ts +7 -0
- package/dist/services/proposals/kinds/findings.js +75 -0
- package/dist/services/proposals/kinds/key-document.d.ts +9 -0
- package/dist/services/proposals/kinds/key-document.js +75 -0
- package/dist/services/proposals/kinds/memory.d.ts +4 -0
- package/dist/services/proposals/kinds/memory.js +42 -0
- package/dist/services/proposals/kinds/relationship.d.ts +10 -0
- package/dist/services/proposals/kinds/relationship.js +82 -0
- package/dist/services/proposals/kinds/shared.d.ts +25 -0
- package/dist/services/proposals/kinds/shared.js +49 -0
- package/dist/services/proposals/registry.d.ts +9 -0
- package/dist/services/proposals/registry.js +129 -0
- package/dist/services/proposals/types.d.ts +85 -0
- package/dist/services/proposals/types.js +1 -0
- package/dist/services/questions.d.ts +7 -2
- package/dist/services/questions.js +13 -4
- package/dist/services/server-search.d.ts +7 -4
- package/dist/services/server-search.js +5 -2
- 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/services/tasks.js +8 -4
- package/dist/store/decisions.d.ts +65 -0
- package/dist/store/decisions.js +55 -0
- package/dist/store/evidence-findings.d.ts +6 -6
- package/dist/store/index.d.ts +4 -3
- package/dist/store/index.js +2 -1
- package/dist/store/meeting-findings.d.ts +2 -0
- package/dist/store/messages.d.ts +4 -9
- package/dist/store/sqlite/actors.d.ts +13 -2
- package/dist/store/sqlite/actors.js +24 -1
- package/dist/store/sqlite/email-locations.d.ts +16 -0
- package/dist/store/sqlite/email-locations.js +26 -0
- package/dist/store/sqlite/emails.d.ts +6 -2
- package/dist/store/sqlite/emails.js +2 -1
- package/dist/store/sqlite/evidence-findings.js +13 -3
- package/dist/store/sqlite/evidence-sources.js +2 -2
- package/dist/store/sqlite/facts.d.ts +16 -8
- package/dist/store/sqlite/facts.js +48 -15
- package/dist/store/sqlite/findings-core.d.ts +2 -1
- package/dist/store/sqlite/findings-core.js +4 -4
- package/dist/store/sqlite/identities.js +1 -1
- package/dist/store/sqlite/knowledge.d.ts +43 -7
- package/dist/store/sqlite/knowledge.js +62 -17
- package/dist/store/sqlite/meeting-findings.js +6 -6
- package/dist/store/sqlite/memories.d.ts +12 -8
- package/dist/store/sqlite/memories.js +33 -10
- package/dist/store/sqlite/notes.d.ts +5 -1
- package/dist/store/sqlite/notes.js +22 -6
- package/dist/store/sqlite/open-store.d.ts +4 -1
- package/dist/store/sqlite/open-store.js +8 -2
- package/dist/store/sqlite/open.d.ts +3 -0
- package/dist/store/sqlite/proposed-actions.d.ts +21 -8
- package/dist/store/sqlite/proposed-actions.js +29 -9
- package/dist/store/sqlite/reads.d.ts +13 -2
- package/dist/store/sqlite/reads.js +8 -4
- package/dist/store/sqlite/relation-proposals.d.ts +34 -0
- package/dist/store/sqlite/relation-proposals.js +114 -0
- package/dist/store/sqlite/things.d.ts +0 -1
- package/dist/store/sqlite/things.js +25 -24
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +2 -0
- package/package.json +1 -1
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) {
|
|
@@ -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`],
|
|
@@ -9,14 +9,20 @@ export declare const historyQuery: (messenger: {
|
|
|
9
9
|
}) => import("@wirecat/cli-base/query").QueryRegistry;
|
|
10
10
|
declare const COMMENTS_QUERY: import("@wirecat/cli-base/query").QueryRegistry;
|
|
11
11
|
export { COMMENTS_QUERY };
|
|
12
|
-
type Start = Pick<ListWindow, "before" | "beforeTime" | "after">;
|
|
12
|
+
type Start = Pick<ListWindow, "before" | "beforeTime" | "after" | "until">;
|
|
13
13
|
/**
|
|
14
|
-
* Where the last page stopped and which way it read: `before:<id>` goes on back, `after:<id>` forward.
|
|
15
|
-
*
|
|
14
|
+
* Where the last page stopped and which way it read: `before:<id>` goes on back, `after:<id>` forward. A window's end
|
|
15
|
+
* travels as `~<epoch ms>`, so a resumed read stops where the first one would have. Ids are opaque, so only the first
|
|
16
|
+
* colon splits it.
|
|
16
17
|
*/
|
|
17
18
|
export declare const cursorOf: (start: Start, edge: {
|
|
18
19
|
first?: string;
|
|
19
20
|
last?: string;
|
|
20
21
|
}) => string | undefined;
|
|
21
|
-
/** A list reads one way from one place: the query or the cursor names at most one
|
|
22
|
+
/** A list reads one way from one place: the query or the cursor names at most one window. */
|
|
22
23
|
export declare const historyWindow: (words: QueryWords, cursor: string | undefined, command: string, registry?: import("@wirecat/cli-base/query").QueryRegistry) => Omit<ListWindow, "limit">;
|
|
24
|
+
/**
|
|
25
|
+
* The query `page.next` reruns with its cursor: the topic and the stars, never the bounds, which the cursor carries
|
|
26
|
+
* and which would name a second starting point beside it.
|
|
27
|
+
*/
|
|
28
|
+
export declare const resumeQuery: (words: QueryWords, command: string, registry?: import("@wirecat/cli-base/query").QueryRegistry) => string | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
-
import {
|
|
3
|
-
import { listQuery, nativeField,
|
|
2
|
+
import { dateRange, defineQuery, queryError, timezoneOf } from "@wirecat/cli-base/query";
|
|
3
|
+
import { listQuery, nativeField, nativeTermLists, typedQuery } from "../list-query.js";
|
|
4
4
|
/** What a chat's history can be read by: where it starts, and one topic. */
|
|
5
5
|
export const HISTORY_QUERY = defineQuery({
|
|
6
6
|
entity: "message",
|
|
@@ -36,52 +36,108 @@ const COMMENTS_QUERY = defineQuery({
|
|
|
36
36
|
});
|
|
37
37
|
export { COMMENTS_QUERY };
|
|
38
38
|
/**
|
|
39
|
-
* Where the last page stopped and which way it read: `before:<id>` goes on back, `after:<id>` forward.
|
|
40
|
-
*
|
|
39
|
+
* Where the last page stopped and which way it read: `before:<id>` goes on back, `after:<id>` forward. A window's end
|
|
40
|
+
* travels as `~<epoch ms>`, so a resumed read stops where the first one would have. Ids are opaque, so only the first
|
|
41
|
+
* colon splits it.
|
|
41
42
|
*/
|
|
42
43
|
export const cursorOf = (start, edge) => {
|
|
43
44
|
const forward = start.after !== undefined;
|
|
44
45
|
const id = forward ? edge.last : edge.first;
|
|
45
|
-
|
|
46
|
+
if (id === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const end = forward && start.until !== undefined ? `~${start.until}` : "";
|
|
49
|
+
return `${forward ? "after" : "before"}:${id}${end}`;
|
|
46
50
|
};
|
|
47
51
|
const isId = (value) => value !== "" && !/[\s\p{Cc}]/u.test(value);
|
|
48
52
|
const fromCursor = (cursor) => {
|
|
49
53
|
const at = cursor.indexOf(":");
|
|
50
|
-
const [way,
|
|
54
|
+
const [way, rest] = at < 0 ? [cursor, ""] : [cursor.slice(0, at), cursor.slice(at + 1)];
|
|
55
|
+
const ended = way === "after" ? /^(.*)~(\d+)$/u.exec(rest) : null;
|
|
56
|
+
const id = ended ? ended[1] : rest;
|
|
51
57
|
if (!isId(id) || (way !== "before" && way !== "after"))
|
|
52
58
|
throw new CliError("validation_error", `--cursor takes what page.next gave, before:<id> or after:<id> — not "${cursor}"`);
|
|
53
|
-
|
|
59
|
+
if (way === "before")
|
|
60
|
+
return { before: id };
|
|
61
|
+
return { after: { id }, ...(ended ? { until: Number(ended[2]) } : {}) };
|
|
54
62
|
};
|
|
55
|
-
|
|
63
|
+
/** A term's lower and upper bound; a date's as moments in the zone, an exclusive end 1 ms inside. */
|
|
64
|
+
const boundsOf = (term, command) => {
|
|
56
65
|
const context = { command };
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const hasLower = term.value !== "*";
|
|
67
|
+
const hasUpper = term.upper !== undefined && term.upper !== "*";
|
|
68
|
+
const span = term.span;
|
|
69
|
+
if (term.field === "id") {
|
|
70
|
+
if (hasLower === hasUpper)
|
|
71
|
+
queryError("query.unsupported-operator", span, "id takes one side: id< or id>", context);
|
|
72
|
+
return hasLower
|
|
73
|
+
? { lower: { start: { after: { id: term.value } }, span, date: false } }
|
|
74
|
+
: { upper: { start: { before: term.upper }, span, date: false } };
|
|
75
|
+
}
|
|
76
|
+
if (!hasLower && !hasUpper)
|
|
77
|
+
queryError("query.unsupported-operator", span, `${term.field} needs a start, an end or both`, context);
|
|
78
|
+
const range = dateRange(term.value, term.upper ?? "*", term.lower_inclusive ?? true, term.upper_inclusive ?? true, timezoneOf(), span);
|
|
79
|
+
if ((hasLower && range.lower === undefined) || (hasUpper && range.upper === undefined))
|
|
80
|
+
queryError("query.invalid-date", span, "that is not a time", context);
|
|
81
|
+
return {
|
|
82
|
+
// The messenger and the store read strictly after a start, so an inclusive one moves 1 ms back.
|
|
83
|
+
...(range.lower === undefined
|
|
84
|
+
? {}
|
|
85
|
+
: { lower: { start: { after: { time: range.lower - (range.lower_inclusive ? 1 : 0) } }, span, date: true } }),
|
|
86
|
+
...(range.upper === undefined
|
|
87
|
+
? {}
|
|
88
|
+
: { upper: { start: { until: range.upper - (range.upper_inclusive ? 0 : 1) }, span, date: true } }),
|
|
89
|
+
};
|
|
69
90
|
};
|
|
70
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* The window the query names. A start reads forward from it, oldest first, and stops at an end; an end alone reads
|
|
93
|
+
* back from it, newest page first.
|
|
94
|
+
*/
|
|
95
|
+
const windowOf = (terms, command) => {
|
|
96
|
+
const context = { command };
|
|
97
|
+
const lowers = [];
|
|
98
|
+
const uppers = [];
|
|
99
|
+
for (const term of terms) {
|
|
100
|
+
const { lower, upper } = boundsOf(term, command);
|
|
101
|
+
if (lower)
|
|
102
|
+
lowers.push(lower);
|
|
103
|
+
if (upper)
|
|
104
|
+
uppers.push(upper);
|
|
105
|
+
}
|
|
106
|
+
const [lower, extraLower] = lowers;
|
|
107
|
+
const [upper, extraUpper] = uppers;
|
|
108
|
+
if (extraLower)
|
|
109
|
+
queryError("query.unsupported-placement", extraLower.span, "the window has one start", context);
|
|
110
|
+
if (extraUpper)
|
|
111
|
+
queryError("query.unsupported-placement", extraUpper.span, "the window has one end", context);
|
|
112
|
+
if (lower && upper && !upper.date)
|
|
113
|
+
queryError("query.unsupported-placement", upper.span, "a window from a start ends at a date: date<…", context);
|
|
114
|
+
if (lower)
|
|
115
|
+
return { ...lower.start, ...upper?.start };
|
|
116
|
+
if (upper?.start.until !== undefined)
|
|
117
|
+
return { beforeTime: upper.start.until + 1 };
|
|
118
|
+
return upper?.start ?? {};
|
|
119
|
+
};
|
|
120
|
+
/** A list reads one way from one place: the query or the cursor names at most one window. */
|
|
71
121
|
export const historyWindow = (words, cursor, command, registry = HISTORY_QUERY) => {
|
|
72
|
-
const terms =
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (cursor !== undefined)
|
|
77
|
-
starts.push(fromCursor(cursor));
|
|
78
|
-
if (starts.length > 1)
|
|
122
|
+
const terms = nativeTermLists(listQuery(typedQuery(words), registry, command), command, ["date", "id"]);
|
|
123
|
+
const bounds = [...(terms.get("date") ?? []), ...(terms.get("id") ?? [])];
|
|
124
|
+
const start = windowOf(bounds, command);
|
|
125
|
+
if (cursor !== undefined && bounds.length > 0)
|
|
79
126
|
throw new CliError("validation_error", "the query and --cursor name two starting points; give one of them");
|
|
80
|
-
const topic = terms.
|
|
81
|
-
const starred = terms.
|
|
127
|
+
const topic = terms.get("topic")?.[0]?.value;
|
|
128
|
+
const starred = terms.get("starred")?.[0]?.value.toLowerCase();
|
|
82
129
|
return {
|
|
83
|
-
...
|
|
130
|
+
...(cursor === undefined ? start : fromCursor(cursor)),
|
|
84
131
|
...(topic === undefined ? {} : { threadId: topic }),
|
|
85
132
|
...(starred === undefined ? {} : { starred: starred === "true" }),
|
|
86
133
|
};
|
|
87
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* The query `page.next` reruns with its cursor: the topic and the stars, never the bounds, which the cursor carries
|
|
137
|
+
* and which would name a second starting point beside it.
|
|
138
|
+
*/
|
|
139
|
+
export const resumeQuery = (words, command, registry = HISTORY_QUERY) => {
|
|
140
|
+
const terms = nativeTermLists(listQuery(typedQuery(words), registry, command), command, ["date", "id"]);
|
|
141
|
+
const kept = ["topic", "starred"].flatMap((field) => terms.get(field)?.map((term) => `${field}:${term.value}`) ?? []);
|
|
142
|
+
return kept.length > 0 ? kept.join(" ") : undefined;
|
|
143
|
+
};
|
|
@@ -12,6 +12,7 @@ import { modelWith, spoken } from "../../speech/hearing.js";
|
|
|
12
12
|
import { REMOTE_READ } from "../effects.js";
|
|
13
13
|
import { itemOf, listOf, renderAnswer } from "../envelope.js";
|
|
14
14
|
import { queryHelp } from "../list-query.js";
|
|
15
|
+
import { withOptionHints } from "../option-hints.js";
|
|
15
16
|
import { positiveCount } from "../paging.js";
|
|
16
17
|
import { syncCommand } from "./backfill-command.js";
|
|
17
18
|
import { messengerContext } from "./context.js";
|
|
@@ -19,7 +20,7 @@ import { linksCommand } from "./conversations-command.js";
|
|
|
19
20
|
import { downloadSubcommand } from "./download-command.js";
|
|
20
21
|
import { heardItems, hearForCommand, hearingMissing, MODEL_OPTION, spokenItems, TRANSCRIBE_OPTION, } from "./hearing-command.js";
|
|
21
22
|
import { historyLimitGaps } from "./history-gaps.js";
|
|
22
|
-
import { cursorOf, historyQuery, historyWindow } from "./history-query.js";
|
|
23
|
+
import { cursorOf, historyQuery, historyWindow, resumeQuery } from "./history-query.js";
|
|
23
24
|
import { hideNever } from "./lacks.js";
|
|
24
25
|
import { threadAnswer } from "./message-answers.js";
|
|
25
26
|
import { messageItem, messageItems, viewOf } from "./message-items.js";
|
|
@@ -36,6 +37,8 @@ import { sendCommand } from "./messages-send-command.js";
|
|
|
36
37
|
import { coverageGaps } from "./search-answers.js";
|
|
37
38
|
import { threadOptions, threadRequest } from "./thread-options.js";
|
|
38
39
|
import { transcribeSubcommand } from "./transcribe-command.js";
|
|
40
|
+
const WINDOW_EXAMPLE = "date>=2026-10-01 date<2026-10-20";
|
|
41
|
+
const TIME_FLAGS = ["--since", "--since-time", "--until", "--until-time", "--after-time", "--before-time"];
|
|
39
42
|
/** `messages`: reading, and sending through the guard. A CLI may add its own subcommands. */
|
|
40
43
|
export const messagesCommand = (messenger) => {
|
|
41
44
|
const messages = new Command("messages").description("read and send messages");
|
|
@@ -51,8 +54,8 @@ export const messagesCommand = (messenger) => {
|
|
|
51
54
|
.addHelpText("after", queryHelp(history))
|
|
52
55
|
.argument("<chat>", messenger.chatArgument)
|
|
53
56
|
.argument("[query...]", `${messenger.topics === false
|
|
54
|
-
?
|
|
55
|
-
:
|
|
57
|
+
? `where to start or which window: id<42, ${WINDOW_EXAMPLE}`
|
|
58
|
+
: `where to start or which window, and which topic: id<42, ${WINDOW_EXAMPLE}, topic:12`}${messenger.messageStars === true ? "; starred:true keeps the ones you starred" : ""}`)
|
|
56
59
|
.option("--limit <n>", "how many", positiveCount("--limit"))
|
|
57
60
|
.option("--cursor <cursor>", "go on from where the last page stopped; page.next gives it")
|
|
58
61
|
.option(...TRANSCRIBE_OPTION)
|
|
@@ -73,6 +76,8 @@ export const messagesCommand = (messenger) => {
|
|
|
73
76
|
const next = page.hasMore
|
|
74
77
|
? cursorOf(window, { first: page.items[0]?.id, last: page.items.at(-1)?.id })
|
|
75
78
|
: undefined;
|
|
79
|
+
const typed = next === undefined ? query.join(" ") : resumeQuery(query, "messages list", history);
|
|
80
|
+
const queryArg = typed ? { query: typed } : {};
|
|
76
81
|
// Offline, the store is the whole answer: say how much of the chat it holds. Online the messenger is read.
|
|
77
82
|
const gaps = context.settings.offline
|
|
78
83
|
? await context.withStore(async (store, account) => {
|
|
@@ -88,18 +93,18 @@ export const messagesCommand = (messenger) => {
|
|
|
88
93
|
has_more: page.hasMore,
|
|
89
94
|
paging: "cursor",
|
|
90
95
|
...(next === undefined ? {} : { cursor: next }),
|
|
91
|
-
args: { chat, ...
|
|
96
|
+
args: { chat, ...queryArg },
|
|
92
97
|
next: gaps.next,
|
|
93
98
|
missing: [...hearingMissing(hearing, transcribe === true), ...gaps.missing],
|
|
94
99
|
...(gaps.missing.length > 0 ? { meta: { complete: false } } : {}),
|
|
95
100
|
}), () => renderMessages(spokenItems(page.items, hearing), viewOf(context, messenger)));
|
|
96
101
|
});
|
|
97
102
|
annotate(list, { output: { entity: "message" }, query: history });
|
|
103
|
+
withOptionHints(list, Object.fromEntries(TIME_FLAGS.map((flag) => [flag, `messages list reads a window by its query: '${WINDOW_EXAMPLE}'`])));
|
|
98
104
|
const readWindow = async (command, chat, message, window) => {
|
|
99
105
|
const context = messengerContext(command, messenger);
|
|
100
|
-
const target = targetOf(messenger, chat, message);
|
|
101
106
|
const { found, reader } = await context.withServices(async (services) => ({
|
|
102
|
-
found: await services.messages.around(
|
|
107
|
+
found: await services.messages.around(chat, message?.trim(), window),
|
|
103
108
|
reader: await services.messages.reader(),
|
|
104
109
|
}));
|
|
105
110
|
const hearing = await hearForCommand(context, messenger, found, false);
|
|
@@ -156,9 +161,8 @@ export const messagesCommand = (messenger) => {
|
|
|
156
161
|
const requested = threadRequest(this).thread;
|
|
157
162
|
if (requested) {
|
|
158
163
|
const context = messengerContext(this, messenger);
|
|
159
|
-
const target = targetOf(messenger, chat, message);
|
|
160
164
|
const { found, reader } = await context.withServices(async (services) => ({
|
|
161
|
-
found: await services.messages.thread(
|
|
165
|
+
found: await services.messages.thread(chat, message?.trim(), { ...requested, before, after }),
|
|
162
166
|
reader: await services.messages.reader(),
|
|
163
167
|
}));
|
|
164
168
|
if (found.fallback)
|
|
@@ -203,19 +207,4 @@ const count = (value) => {
|
|
|
203
207
|
throw new CliError("validation_error", `"${value}" is not a count`);
|
|
204
208
|
return parsed;
|
|
205
209
|
};
|
|
206
|
-
/** A chat and a message id, or one locator naming both — which must be this messenger's. */
|
|
207
|
-
const targetOf = (messenger, chat, message) => {
|
|
208
|
-
if (isLocator(chat)) {
|
|
209
|
-
if (message !== undefined)
|
|
210
|
-
throw new CliError("validation_error", "a locator already names the message");
|
|
211
|
-
const locator = parseLocator(chat);
|
|
212
|
-
if (locator.provider !== messenger.provider) {
|
|
213
|
-
throw new CliError("validation_error", `that locator is a ${locator.provider} message, not ${messenger.provider}`);
|
|
214
|
-
}
|
|
215
|
-
return { chat: locator.chat, message: locator.message };
|
|
216
|
-
}
|
|
217
|
-
if (message === undefined)
|
|
218
|
-
throw new CliError("validation_error", "which message? give its id after the chat");
|
|
219
|
-
return { chat, message: message.trim() };
|
|
220
|
-
};
|
|
221
210
|
export { sendCommand } from "./messages-send-command.js";
|
|
@@ -7,7 +7,7 @@ import { listOf, renderAnswer } from "../envelope.js";
|
|
|
7
7
|
import { queryHelp } from "../list-query.js";
|
|
8
8
|
import { positiveCount } from "../paging.js";
|
|
9
9
|
import { messengerContext } from "./context.js";
|
|
10
|
-
import { COMMENTS_QUERY, cursorOf, historyWindow } from "./history-query.js";
|
|
10
|
+
import { COMMENTS_QUERY, cursorOf, historyWindow, resumeQuery } from "./history-query.js";
|
|
11
11
|
import { messageItems, viewOf } from "./message-items.js";
|
|
12
12
|
export const commentsCommand = (messenger) => {
|
|
13
13
|
const otherPosts = messenger.readsComments === true && messenger.channelComments === false;
|
|
@@ -42,13 +42,15 @@ export const commentsCommand = (messenger) => {
|
|
|
42
42
|
reader: await services.messages.reader(),
|
|
43
43
|
}));
|
|
44
44
|
const next = found.hasMore ? cursorOf(window, { first: found.items[0]?.id }) : undefined;
|
|
45
|
+
const typed = next === undefined ? query.join(" ") : resumeQuery(query, "messages comments", COMMENTS_QUERY);
|
|
46
|
+
const queryArg = typed ? { query: typed } : {};
|
|
45
47
|
renderAnswer(this, context, listOf("messages comments", {
|
|
46
48
|
items: messageItems(found.items, reader),
|
|
47
49
|
limit,
|
|
48
50
|
has_more: found.hasMore,
|
|
49
51
|
paging: "cursor",
|
|
50
52
|
...(next === undefined ? {} : { cursor: next }),
|
|
51
|
-
args: { chat, post, ...
|
|
53
|
+
args: { chat, post, ...queryArg },
|
|
52
54
|
}), () => renderMessages(found.items, viewOf(context, messenger)));
|
|
53
55
|
});
|
|
54
56
|
return annotate(command, { output: { entity: "message" }, query: COMMENTS_QUERY });
|
|
@@ -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
|
})
|
|
@@ -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")
|
|
@@ -3,9 +3,9 @@ import type { CacheDatabase } from "../../store/driver.js";
|
|
|
3
3
|
import type { Messenger } from "./context.js";
|
|
4
4
|
/** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
|
|
5
5
|
export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
6
|
+
error?: undefined;
|
|
6
7
|
path: string;
|
|
7
8
|
exists: boolean;
|
|
8
|
-
error?: undefined;
|
|
9
9
|
} | {
|
|
10
10
|
path: string;
|
|
11
11
|
exists: boolean;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { chatReferenceOf, ensureAccount } from "../../domain/ref-arguments.js";
|
|
2
3
|
import { capability, throughWrapper, } from "../../ports/messenger.js";
|
|
3
4
|
import { foldName, isId, pickPerson } from "../../resolve.js";
|
|
4
5
|
import { codeOf } from "../../sends/guarded.js";
|
|
5
6
|
import { limitBatch } from "../../services/history-limits.js";
|
|
6
|
-
import { chatIdIn } from "../../services/message-resolution.js";
|
|
7
|
+
import { chatIdIn, profilesOnAccount } from "../../services/message-resolution.js";
|
|
7
8
|
const REKEY_PACING = { lockMs: 50, pauseMs: 25 };
|
|
8
9
|
/**
|
|
9
10
|
* The adapter, with what each read answers written to the store. **A failed write never fails the
|
|
@@ -201,12 +202,12 @@ export const stored = (messenger, { account, store, warn, events, pending, annou
|
|
|
201
202
|
pending?.delete(saving);
|
|
202
203
|
rekeysOnly.delete(saving);
|
|
203
204
|
});
|
|
204
|
-
void onBatch(batch);
|
|
205
|
+
void onBatch(batch, saving);
|
|
205
206
|
return saving;
|
|
206
207
|
};
|
|
207
208
|
};
|
|
208
209
|
// A method not listed here saves nothing and passes through; a lane that should save adds its line.
|
|
209
|
-
|
|
210
|
+
const wrapped = throughWrapper(messenger, {
|
|
210
211
|
...(messenger.forumState
|
|
211
212
|
? {
|
|
212
213
|
forumState: async (chatId) => {
|
|
@@ -465,6 +466,58 @@ export const stored = (messenger, { account, store, warn, events, pending, annou
|
|
|
465
466
|
return sent;
|
|
466
467
|
},
|
|
467
468
|
});
|
|
469
|
+
const chatOf = async (typed) => {
|
|
470
|
+
const named = chatReferenceOf(typed, account.provider);
|
|
471
|
+
if (!named)
|
|
472
|
+
return typed;
|
|
473
|
+
let known;
|
|
474
|
+
try {
|
|
475
|
+
known = account.account;
|
|
476
|
+
}
|
|
477
|
+
catch { }
|
|
478
|
+
await ensureAccount(named, { account: known, ...(profile ? { profile: profile.name } : {}) }, async (other) => {
|
|
479
|
+
const opened = await store();
|
|
480
|
+
return opened && profilesOnAccount(opened, profile?.app, account.provider)(other);
|
|
481
|
+
});
|
|
482
|
+
return named.chat;
|
|
483
|
+
};
|
|
484
|
+
return withChatReferences(wrapped, chatOf);
|
|
485
|
+
};
|
|
486
|
+
/** The adapter methods whose first argument is a chat as typed: an id, a title, or a `chat:` or `msg:` reference. */
|
|
487
|
+
const TYPED_CHAT = [
|
|
488
|
+
"resolve",
|
|
489
|
+
"chat",
|
|
490
|
+
"history",
|
|
491
|
+
"around",
|
|
492
|
+
"historyAfter",
|
|
493
|
+
"historyBefore",
|
|
494
|
+
"historyFrom",
|
|
495
|
+
"topics",
|
|
496
|
+
"topic",
|
|
497
|
+
"topicHistory",
|
|
498
|
+
"download",
|
|
499
|
+
"transcribe",
|
|
500
|
+
"scheduled",
|
|
501
|
+
"admins",
|
|
502
|
+
"chatEvents",
|
|
503
|
+
"leave",
|
|
504
|
+
"group",
|
|
505
|
+
"officialChatStats",
|
|
506
|
+
];
|
|
507
|
+
/**
|
|
508
|
+
* A reference is read here, not in each adapter: an adapter resolves a typed chat as an id or a title, and would take
|
|
509
|
+
* `chat:max/…` for a title. One of another account is refused before the messenger is asked.
|
|
510
|
+
*/
|
|
511
|
+
const withChatReferences = (adapter, chatOf) => {
|
|
512
|
+
const methods = adapter;
|
|
513
|
+
const own = Object.fromEntries(TYPED_CHAT.flatMap((name) => {
|
|
514
|
+
const method = methods[name];
|
|
515
|
+
if (typeof method !== "function")
|
|
516
|
+
return [];
|
|
517
|
+
const call = async (typed, ...rest) => method.call(adapter, await chatOf(typed), ...rest);
|
|
518
|
+
return [[name, call]];
|
|
519
|
+
}));
|
|
520
|
+
return throughWrapper(adapter, own);
|
|
468
521
|
};
|
|
469
522
|
/**
|
|
470
523
|
* A write is saved under the chat the messenger answered, not the one typed: WhatsApp answers a re-keyed person's
|
|
@@ -68,7 +68,7 @@ export const topicsCommand = (messenger) => {
|
|
|
68
68
|
const done = await context.withServices((services) => services.topics.enable(chat, { upgrade: this.opts().upgrade === true }));
|
|
69
69
|
renderAnswer(this, context, itemOf(snakeKeys(done)));
|
|
70
70
|
});
|
|
71
|
-
annotate(topics.command("create"), { effects: REMOTE_WRITE })
|
|
71
|
+
annotate(topics.command("create"), { effects: REMOTE_WRITE, literal_after_dashes: true })
|
|
72
72
|
.description("create a named topic in an existing forum; never enable or upgrade a group implicitly")
|
|
73
73
|
.argument("<chat>", messenger.chatArgument)
|
|
74
74
|
.argument("<title>", "the topic title, at most 128 UTF-8 bytes")
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* A flag a command does not take, but an agent is likely to type, fails with what to type instead rather than
|
|
4
|
+
* commander's bare "unknown option".
|
|
5
|
+
*/
|
|
6
|
+
export declare const withOptionHints: (command: Command, hints: Readonly<Record<string, string>>) => Command;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A flag a command does not take, but an agent is likely to type, fails with what to type instead rather than
|
|
3
|
+
* commander's bare "unknown option".
|
|
4
|
+
*/
|
|
5
|
+
export const withOptionHints = (command, hints) => {
|
|
6
|
+
const unknown = command;
|
|
7
|
+
const fallback = unknown.unknownOption.bind(command);
|
|
8
|
+
unknown.unknownOption = (flag) => {
|
|
9
|
+
const hint = hints[flag.split("=")[0]];
|
|
10
|
+
if (hint === undefined)
|
|
11
|
+
return fallback(flag);
|
|
12
|
+
return command.error(`unknown option '${flag}' — ${hint}`, { code: "commander.unknownOption" });
|
|
13
|
+
};
|
|
14
|
+
return command;
|
|
15
|
+
};
|