@wirecat/cli-core 0.545.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 +76 -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/messages-command.js +2 -19
- 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/stored.js +56 -3
- 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/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 +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/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/sqlite/evidence-sources.js +2 -2
- 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/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`],
|
|
@@ -97,9 +97,8 @@ export const messagesCommand = (messenger) => {
|
|
|
97
97
|
annotate(list, { output: { entity: "message" }, query: history });
|
|
98
98
|
const readWindow = async (command, chat, message, window) => {
|
|
99
99
|
const context = messengerContext(command, messenger);
|
|
100
|
-
const target = targetOf(messenger, chat, message);
|
|
101
100
|
const { found, reader } = await context.withServices(async (services) => ({
|
|
102
|
-
found: await services.messages.around(
|
|
101
|
+
found: await services.messages.around(chat, message?.trim(), window),
|
|
103
102
|
reader: await services.messages.reader(),
|
|
104
103
|
}));
|
|
105
104
|
const hearing = await hearForCommand(context, messenger, found, false);
|
|
@@ -156,9 +155,8 @@ export const messagesCommand = (messenger) => {
|
|
|
156
155
|
const requested = threadRequest(this).thread;
|
|
157
156
|
if (requested) {
|
|
158
157
|
const context = messengerContext(this, messenger);
|
|
159
|
-
const target = targetOf(messenger, chat, message);
|
|
160
158
|
const { found, reader } = await context.withServices(async (services) => ({
|
|
161
|
-
found: await services.messages.thread(
|
|
159
|
+
found: await services.messages.thread(chat, message?.trim(), { ...requested, before, after }),
|
|
162
160
|
reader: await services.messages.reader(),
|
|
163
161
|
}));
|
|
164
162
|
if (found.fallback)
|
|
@@ -203,19 +201,4 @@ const count = (value) => {
|
|
|
203
201
|
throw new CliError("validation_error", `"${value}" is not a count`);
|
|
204
202
|
return parsed;
|
|
205
203
|
};
|
|
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
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
|
})
|
|
@@ -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")
|
|
@@ -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")
|
package/dist/cli/program.js
CHANGED
|
@@ -13,6 +13,7 @@ import { recorded, wasSettled } from "../runs/recording.js";
|
|
|
13
13
|
import { runContextOf } from "../runs/run-context.js";
|
|
14
14
|
import { processRun, useAsProcessRun } from "../runs/scope.js";
|
|
15
15
|
import { AUTHORITY_KEY, BOT_API_KEY, isBotApi, keyForCommand } from "../sends/permissions.js";
|
|
16
|
+
import { liftOptionsAfterDashes } from "./after-dashes.js";
|
|
16
17
|
import { logParsedArgv, logVersion } from "./argv-log.js";
|
|
17
18
|
import { commandAvailability } from "./command-availability.js";
|
|
18
19
|
import { commandPathOf } from "./command-contract.js";
|
|
@@ -135,15 +136,16 @@ export const run = async (argv, definition, options = {}) => {
|
|
|
135
136
|
useAsProcessRun(outer);
|
|
136
137
|
}
|
|
137
138
|
};
|
|
139
|
+
const isMachine = (words) => words.some((word) => word === "--json" || word.startsWith("--json=") || word === "--jsonl");
|
|
138
140
|
const runOnce = async (argv, definition, options) => {
|
|
139
141
|
const streams = options.streams ?? processStreams;
|
|
140
142
|
if (!options.streams)
|
|
141
143
|
quietBrokenPipes();
|
|
142
144
|
const delimiter = argv.indexOf("--");
|
|
143
145
|
const flags = argv.slice(0, delimiter < 0 ? undefined : delimiter);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
let machine = isMachine(flags);
|
|
147
|
+
let debug = flags.includes("--debug");
|
|
148
|
+
let reporting = machine ? { ...options, tty: false } : options;
|
|
147
149
|
const control = execution(streams, { maxOutputBytes: 0 });
|
|
148
150
|
let restoreInput;
|
|
149
151
|
let signalExit;
|
|
@@ -439,12 +441,21 @@ const runOnce = async (argv, definition, options) => {
|
|
|
439
441
|
forEachCommand(program, (child) => child.exitOverride());
|
|
440
442
|
// Before commander, not inside it: commander has no hook that runs before it decides which
|
|
441
443
|
// subcommand it is looking at.
|
|
442
|
-
const
|
|
444
|
+
const profiled = liftProfile(argv, commandWords(program), valuedOptions(program));
|
|
445
|
+
const { profile } = profiled;
|
|
446
|
+
// An agent's call builds its own argv, with its values after `--` on purpose: none of them may become an option.
|
|
447
|
+
const lifted = options.agent
|
|
448
|
+
? profiled.rest
|
|
449
|
+
: liftOptionsAfterDashes(profiled.rest, (words) => commandAt(program, commandPath(program, words)));
|
|
450
|
+
const liftedEnd = lifted.indexOf("--");
|
|
451
|
+
const liftedFlags = lifted.slice(0, liftedEnd < 0 ? undefined : liftedEnd);
|
|
452
|
+
machine ||= isMachine(liftedFlags);
|
|
453
|
+
debug ||= liftedFlags.includes("--debug");
|
|
454
|
+
reporting = machine ? { ...options, tty: false } : options;
|
|
443
455
|
// `wa 'chats list' --help` reads "chats list" as a profile and prints the root help with exit 0.
|
|
444
456
|
try {
|
|
445
457
|
// Only before `--`: what follows is a positional, an agent's text among them, never a command word.
|
|
446
|
-
const
|
|
447
|
-
for (const words of [flags, end < 0 ? lifted : lifted.slice(0, end)])
|
|
458
|
+
for (const words of [flags, liftedFlags])
|
|
448
459
|
refuseJoinedPath(program, words.filter((word) => !word.startsWith("-")), profile !== undefined && !/\s/.test(profile) ? [profile] : []);
|
|
449
460
|
}
|
|
450
461
|
catch (failure) {
|
|
@@ -110,6 +110,7 @@ export const scheduleCommandFor = ({ app, context: contextOf }) => {
|
|
|
110
110
|
return { context, system, state, say, timer, environment, save };
|
|
111
111
|
};
|
|
112
112
|
withWhen(grantsAuthority(annotate(command.command("command"), {
|
|
113
|
+
literal_after_dashes: true,
|
|
113
114
|
effects: LOCAL_WRITE,
|
|
114
115
|
examples: [
|
|
115
116
|
`${app.command} personal schedule command health --every 1h -- doctor`,
|
|
@@ -24,13 +24,16 @@ export type MessageLink = MessagePermalink & {
|
|
|
24
24
|
files?: FileLink[];
|
|
25
25
|
};
|
|
26
26
|
export declare const fileLinks: (attachments: readonly Attachment[]) => FileLink[];
|
|
27
|
+
/** A chat and a message id, or one reference naming both; a reference's account is for the caller to check. */
|
|
27
28
|
export declare const messageLinkTarget: (reference: string, message: string | undefined, provider: string) => {
|
|
28
29
|
chat: string;
|
|
29
30
|
message: string;
|
|
30
31
|
account: string;
|
|
32
|
+
text: string;
|
|
31
33
|
} | {
|
|
32
34
|
chat: string;
|
|
33
35
|
message: string;
|
|
34
36
|
account: undefined;
|
|
37
|
+
text: string;
|
|
35
38
|
};
|
|
36
39
|
export declare const validatePermalink: (link: MessagePermalink) => MessagePermalink;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { hitFiles } from "./attachments.js";
|
|
3
|
-
import {
|
|
3
|
+
import { chatReferenceOf } from "./ref-arguments.js";
|
|
4
4
|
export const fileLinks = (attachments) => hitFiles(attachments).map(({ position, kind, name }) => ({
|
|
5
5
|
position,
|
|
6
6
|
kind,
|
|
@@ -8,28 +8,23 @@ export const fileLinks = (attachments) => hitFiles(attachments).map(({ position,
|
|
|
8
8
|
url: null,
|
|
9
9
|
reason: "unsupported_provider",
|
|
10
10
|
}));
|
|
11
|
+
/** A chat and a message id, or one reference naming both; a reference's account is for the caller to check. */
|
|
11
12
|
export const messageLinkTarget = (reference, message, provider) => {
|
|
12
|
-
|
|
13
|
+
const named = chatReferenceOf(reference, provider, "message");
|
|
14
|
+
if (named?.message !== undefined) {
|
|
13
15
|
if (message !== undefined)
|
|
14
16
|
throw new CliError("validation_error", "a locator already names the message");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
target = parseLocator(reference);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
throw new CliError("validation_error", "expected msg:<provider>/<account>/<chat>/<message>");
|
|
21
|
-
}
|
|
22
|
-
if (target.provider !== provider)
|
|
23
|
-
throw new CliError("validation_error", "that locator belongs to another messenger");
|
|
24
|
-
validMessageId(target.message);
|
|
25
|
-
return { chat: target.chat, message: target.message, account: target.account };
|
|
17
|
+
validMessageId(named.message);
|
|
18
|
+
return { chat: named.chat, message: named.message, account: named.account, text: named.text };
|
|
26
19
|
}
|
|
27
20
|
if (message === undefined)
|
|
28
21
|
throw new CliError("validation_error", "which message? give its id after the chat");
|
|
29
22
|
validMessageId(message);
|
|
23
|
+
if (named)
|
|
24
|
+
return { chat: named.chat, message, account: named.account, text: named.text };
|
|
30
25
|
if (!reference.trim())
|
|
31
26
|
throw new CliError("validation_error", "which chat? give its id or title");
|
|
32
|
-
return { chat: reference, message, account: undefined };
|
|
27
|
+
return { chat: reference, message, account: undefined, text: reference };
|
|
33
28
|
};
|
|
34
29
|
const validMessageId = (id) => {
|
|
35
30
|
if (!id ||
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TaskCategory } from "./task-categories.js";
|
|
2
|
+
/**
|
|
3
|
+
* Verb classes for reading money in a chat, matched by stem as task categories are. A stem cannot tell the imperative
|
|
4
|
+
* from the past tense (`оплати`, `оплатил` → `оплат`) or from a promise (`pagué`, `pagaré` → `pag`), so a stem that
|
|
5
|
+
* several classes share is listed once, under the request, and a word only counts where its stem is its own. The
|
|
6
|
+
* signal that reads these keeps the sender's side (owner or other) to decide the direction.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MONEY_REQUEST = "money-request";
|
|
9
|
+
export declare const MONEY_DONE = "money-done";
|
|
10
|
+
export declare const PROMISE = "promise";
|
|
11
|
+
export declare const MONEY_WORDS: readonly TaskCategory[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verb classes for reading money in a chat, matched by stem as task categories are. A stem cannot tell the imperative
|
|
3
|
+
* from the past tense (`оплати`, `оплатил` → `оплат`) or from a promise (`pagué`, `pagaré` → `pag`), so a stem that
|
|
4
|
+
* several classes share is listed once, under the request, and a word only counts where its stem is its own. The
|
|
5
|
+
* signal that reads these keeps the sender's side (owner or other) to decide the direction.
|
|
6
|
+
*/
|
|
7
|
+
export const MONEY_REQUEST = "money-request";
|
|
8
|
+
export const MONEY_DONE = "money-done";
|
|
9
|
+
export const PROMISE = "promise";
|
|
10
|
+
export const MONEY_WORDS = [
|
|
11
|
+
{
|
|
12
|
+
name: MONEY_REQUEST,
|
|
13
|
+
meaning: "asks for a payment or sends an invoice",
|
|
14
|
+
triggers: {
|
|
15
|
+
en: ["pay", "send", "transfer", "invoice", "owe", "refund", "remit"],
|
|
16
|
+
ru: ["переведи", "оплати", "заплати", "вышли", "отправь", "скинь", "верни", "счёт", "выстави", "должен"],
|
|
17
|
+
es: ["envía", "transfiere", "factura", "debes", "devuelve", "abona"],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: MONEY_DONE,
|
|
22
|
+
meaning: "says a payment was made or received",
|
|
23
|
+
triggers: {
|
|
24
|
+
en: ["paid", "sent", "received", "settled"],
|
|
25
|
+
ru: ["перевёл", "перевела", "выслал", "вернул", "скинул", "получил", "получила"],
|
|
26
|
+
es: ["recibido"],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: PROMISE,
|
|
31
|
+
meaning: "promises to pay or send later",
|
|
32
|
+
triggers: {
|
|
33
|
+
en: ["promise", "promised", "guarantee"],
|
|
34
|
+
ru: ["оплачу", "заплачу", "отправлю", "обещаю", "гарантирую"],
|
|
35
|
+
es: ["prometo", "transferiré", "devolveré"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
];
|
|
@@ -11,6 +11,8 @@ export interface ProviderEntry {
|
|
|
11
11
|
}
|
|
12
12
|
/** The seed: what cli-core knows before any CLI has written its entry into the store. */
|
|
13
13
|
export declare const PROVIDERS: readonly ProviderEntry[];
|
|
14
|
+
/** How a text names a provider to a person: `MAX`, not `max`. */
|
|
15
|
+
export declare const providerLabel: (provider: Provider) => string;
|
|
14
16
|
/**
|
|
15
17
|
* A notes folder is an `accounts` row of one of these providers; people call every one `notes`. `obsidian` is a vault,
|
|
16
18
|
* `local` a plain folder (store version 32). `folder` is what they were before, and what Drive's stand-in folders and
|
package/dist/domain/providers.js
CHANGED
|
@@ -3,11 +3,11 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
3
3
|
export const KINDS = ["messages", "mail", "notes", "documents", "meetings", "events"];
|
|
4
4
|
/** The seed: what cli-core knows before any CLI has written its entry into the store. */
|
|
5
5
|
export const PROVIDERS = [
|
|
6
|
-
{ name: "telegram", kind: "messages", aliases: ["tg"] },
|
|
6
|
+
{ name: "telegram", kind: "messages", aliases: ["tg"], command: "tg" },
|
|
7
7
|
{ name: "telegram-bot", kind: "messages", aliases: ["tg-bot"] },
|
|
8
|
-
{ name: "max", kind: "messages", aliases: [] },
|
|
8
|
+
{ name: "max", kind: "messages", aliases: [], command: "max" },
|
|
9
9
|
{ name: "max-bot", kind: "messages", aliases: [] },
|
|
10
|
-
{ name: "whatsapp", kind: "messages", aliases: ["wa"] },
|
|
10
|
+
{ name: "whatsapp", kind: "messages", aliases: ["wa"], command: "wa" },
|
|
11
11
|
{ name: "whatsapp-bot", kind: "messages", aliases: ["wa-bot"] },
|
|
12
12
|
{ name: "email", kind: "mail", aliases: [] },
|
|
13
13
|
{ name: "notes", kind: "notes", aliases: [] },
|
|
@@ -15,6 +15,18 @@ export const PROVIDERS = [
|
|
|
15
15
|
{ name: "obsidian", kind: "notes", aliases: [] },
|
|
16
16
|
{ name: "zoom", kind: "meetings", aliases: [] },
|
|
17
17
|
];
|
|
18
|
+
const LABELS = {
|
|
19
|
+
telegram: "Telegram",
|
|
20
|
+
"telegram-bot": "Telegram bot",
|
|
21
|
+
max: "MAX",
|
|
22
|
+
"max-bot": "MAX bot",
|
|
23
|
+
whatsapp: "WhatsApp",
|
|
24
|
+
"whatsapp-bot": "WhatsApp bot",
|
|
25
|
+
"google-drive": "Google Drive",
|
|
26
|
+
email: "mail",
|
|
27
|
+
};
|
|
28
|
+
/** How a text names a provider to a person: `MAX`, not `max`. */
|
|
29
|
+
export const providerLabel = (provider) => LABELS[provider] ?? provider;
|
|
18
30
|
/**
|
|
19
31
|
* A notes folder is an `accounts` row of one of these providers; people call every one `notes`. `obsidian` is a vault,
|
|
20
32
|
* `local` a plain folder (store version 32). `folder` is what they were before, and what Drive's stand-in folders and
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import type { Id, Provider } from "./models.js";
|
|
3
|
+
/** A `chat:` or `msg:` reference given where a messenger command takes a chat or a message. */
|
|
4
|
+
export interface ChatReference {
|
|
5
|
+
text: string;
|
|
6
|
+
provider: Provider;
|
|
7
|
+
account: Id;
|
|
8
|
+
chat: Id;
|
|
9
|
+
message?: Id;
|
|
10
|
+
}
|
|
11
|
+
/** The account a reference must belong to; `account` is `undefined` while this profile's is not known yet. */
|
|
12
|
+
export interface RefScope {
|
|
13
|
+
account: Id | undefined;
|
|
14
|
+
profile?: string;
|
|
15
|
+
}
|
|
16
|
+
/** The profiles of this app the store saw on an account; `undefined` when that cannot be told. */
|
|
17
|
+
export type ProfilesOn = (account: Id) => Promise<readonly string[] | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* The chat or message a reference names, when `text` is one of this provider's; `undefined` for text that is no
|
|
20
|
+
* reference, which stays an id or a title. Another kind or another messenger is refused, never read as a title.
|
|
21
|
+
*/
|
|
22
|
+
export declare const chatReferenceOf: (text: string, provider: Provider, takes?: "chat" | "message") => ChatReference | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The identity a `contact:` reference names, when it is one of this provider's; `undefined` for any other text. A
|
|
25
|
+
* `person:` reference is the store's person, which the callers that take one read before this.
|
|
26
|
+
*/
|
|
27
|
+
export declare const contactReferenceOf: (text: string, provider: Provider | undefined) => Id | undefined;
|
|
28
|
+
/** The refusal of another account's reference: switching to it silently would read or send as somebody else. */
|
|
29
|
+
export declare const wrongAccount: (reference: Pick<ChatReference, "text" | "account">, scope: RefScope, profiles?: readonly string[]) => CliError;
|
|
30
|
+
/** Refuses a reference of another account than the scope's, naming the profiles that have it. */
|
|
31
|
+
export declare const ensureAccount: (reference: Pick<ChatReference, "text" | "account">, scope: RefScope, profilesOn?: ProfilesOn) => Promise<void>;
|