@wirecat/cli-core 0.591.0 → 0.592.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 +13 -0
- package/dist/background/deliver.d.ts +58 -0
- package/dist/background/deliver.js +125 -0
- package/dist/background/schedules.d.ts +6 -1
- package/dist/background/schedules.js +32 -0
- package/dist/background/system.d.ts +9 -2
- package/dist/background/system.js +10 -3
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/schedule/schedule-command.js +16 -4
- package/dist/entities/index.d.ts +7 -0
- package/dist/entities/list-results.d.ts +9 -0
- package/dist/entities/list-results.js +9 -0
- package/dist/store/sqlite/email-activity.d.ts +3 -0
- package/dist/store/sqlite/email-activity.js +11 -0
- package/package.json +1 -1
- package/parity.json +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,8 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.592.0 — 06.11.2026
|
|
11
|
+
|
|
10
12
|
## 0.591.0 — 06.11.2026
|
|
11
13
|
|
|
12
14
|
### Added
|
|
@@ -28,6 +30,17 @@ shape of this file.
|
|
|
28
30
|
in `@wirecat/cli-core`.
|
|
29
31
|
- **`DealFilter.account` and `offset`, `pipelines({ account })`.** A list reads the owner's own (`"own"`) or one CRM
|
|
30
32
|
account's copies, a page at a time.
|
|
33
|
+
- **`schedule agent` and `schedule command` take `--to <route>`, repeatable.** After the run, when `--notify-on`
|
|
34
|
+
says so, the scheduler sends the result through the owner's own CLI: `telegram-bot:<profile>/<chat id or
|
|
35
|
+
user:id>` runs `tg <profile> bot messages send`, `email:<profile>/<address>` runs `mail <profile> messages send`.
|
|
36
|
+
A finished run sends its output (an agent's report, a command's log): to a bot as one message up to 4096
|
|
37
|
+
characters, else as a file with its first lines as the caption; by mail as the body, the title as the subject. A
|
|
38
|
+
failed run sends one line: exit code, reason id, the log's path. The run itself stays read-only; nothing it prints
|
|
39
|
+
can change where the result goes; a run under `<PREFIX>_READ_ONLY` sends nothing. Each route's outcome is in
|
|
40
|
+
`last.json` as `deliveries` (`to`, `ok`, a dotted `reason`, the sending CLI's error code); a failed send never fails
|
|
41
|
+
the run, and `schedule list` shows it. `ServerSystem.run` takes an optional `{ input, timeoutMs }`.
|
|
42
|
+
- **`store.mail.writtenTo(addresses)`.** Of these addresses, the ones the owner wrote to: To or Cc of a live outgoing
|
|
43
|
+
email in any account, in lower case, in one read.
|
|
31
44
|
|
|
32
45
|
- **`syncCrm` and the `CrmSource` port, in `@wirecat/cli-core/crm`.** A CRM CLI writes an adapter (`pipelines(kind)`,
|
|
33
46
|
`changes(kind, cursor, page)` answering a page or `{ reset: true }`, and `ids(kind, page)` when the CRM does not name
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type ServerSystem } from "./system.js";
|
|
2
|
+
/**
|
|
3
|
+
* Where a schedule's result goes after the run, by the owner's own CLI: a Telegram bot's message (`tg <profile> bot
|
|
4
|
+
* messages send`) or an email (`mail <profile> messages send`). Only the schedule file names it, so nothing the run
|
|
5
|
+
* printed can move it; the run itself never sends.
|
|
6
|
+
*/
|
|
7
|
+
export type Route = {
|
|
8
|
+
kind: "telegram-bot";
|
|
9
|
+
profile: string;
|
|
10
|
+
chat: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "email";
|
|
13
|
+
profile: string;
|
|
14
|
+
address: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const ROUTE_HELP = "telegram-bot:<profile>/<chat id or user:id>, or email:<profile>/<address>";
|
|
17
|
+
export declare const parseRoute: (text: string) => Route;
|
|
18
|
+
export declare const routeText: (route: Route) => string;
|
|
19
|
+
export declare const DELIVERY_REASONS: {
|
|
20
|
+
readonly readOnly: "delivery.read-only";
|
|
21
|
+
readonly programMissing: "delivery.program-missing";
|
|
22
|
+
readonly routeInvalid: "delivery.route-invalid";
|
|
23
|
+
readonly timeout: "delivery.timeout";
|
|
24
|
+
readonly failed: "delivery.failed";
|
|
25
|
+
};
|
|
26
|
+
export type DeliveryReason = (typeof DELIVERY_REASONS)[keyof typeof DELIVERY_REASONS];
|
|
27
|
+
/** One route's outcome, kept in `last.json`. `error` is the sending CLI's error code and reason id, never its message. */
|
|
28
|
+
export interface Delivery {
|
|
29
|
+
to: string;
|
|
30
|
+
ok: boolean;
|
|
31
|
+
reason?: DeliveryReason;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Telegram's limits, in UTF-16 units as JavaScript counts them: a message's text, and a file's caption. */
|
|
35
|
+
export declare const TELEGRAM_TEXT_CHARS = 4096;
|
|
36
|
+
export declare const CAPTION_CHARS = 1024;
|
|
37
|
+
/** The caption: the title, then the report's first lines as far as they fit, cut where a line ends. */
|
|
38
|
+
export declare const headOf: (title: string, report: string, limit?: number) => string;
|
|
39
|
+
export interface Parcel {
|
|
40
|
+
title: string;
|
|
41
|
+
/** A finished run: its output file, and the name the copy sent along has. */
|
|
42
|
+
file?: {
|
|
43
|
+
path: string;
|
|
44
|
+
name: string;
|
|
45
|
+
};
|
|
46
|
+
/** A failed run: one line, no file. */
|
|
47
|
+
line?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Hands the parcel to each route's CLI, one after another; a route that fails is recorded and the next one still goes. */
|
|
50
|
+
export declare const deliver: (system: ServerSystem, env: NodeJS.ProcessEnv, routes: readonly string[], parcel: Parcel, { find, errorOf, locked, timeoutMs, }: {
|
|
51
|
+
/** Where a program is on the run's PATH. */
|
|
52
|
+
find: (name: string, env: NodeJS.ProcessEnv) => string | undefined;
|
|
53
|
+
/** The one line kept of a failed send's stderr. */
|
|
54
|
+
errorOf: (stderr: string, code: number) => string;
|
|
55
|
+
/** The lock the scheduler itself runs under: nothing goes out from a locked run. */
|
|
56
|
+
locked?: string;
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
}) => Promise<Delivery[]>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { copyFileSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { CliError } from "@wirecat/cli-base";
|
|
5
|
+
import { TIMED_OUT } from "./system.js";
|
|
6
|
+
const PROGRAMS = {
|
|
7
|
+
"telegram-bot": { command: "tg", prefix: "TG" },
|
|
8
|
+
email: { command: "mail", prefix: "MAIL" },
|
|
9
|
+
};
|
|
10
|
+
// A chat by its id only: a title can change, or name a second chat, and the report would go there.
|
|
11
|
+
const CHAT = /^(-?\d+|user:\d+)$/;
|
|
12
|
+
// The profile-name rule of src/cli/profile.ts, which this layer may not import.
|
|
13
|
+
const PROFILE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
14
|
+
const ADDRESS = /^[^\s@/,<>"]+@[^\s@/,<>"]+\.[^\s@/,<>"]+$/;
|
|
15
|
+
export const ROUTE_HELP = "telegram-bot:<profile>/<chat id or user:id>, or email:<profile>/<address>";
|
|
16
|
+
export const parseRoute = (text) => {
|
|
17
|
+
const [, kind = "", profile = "", target = ""] = /^([a-z-]+):([^/]+)\/(.+)$/.exec(text.trim()) ?? [];
|
|
18
|
+
const refuse = (why) => new CliError("validation_error", `--to ${text}: ${why} — ${ROUTE_HELP}`);
|
|
19
|
+
if (!(kind in PROGRAMS))
|
|
20
|
+
throw refuse(kind ? `${kind} is not a route` : "a route is <kind>:<profile>/<where>");
|
|
21
|
+
if (!PROFILE.test(profile))
|
|
22
|
+
throw refuse(`"${profile}" is not a profile name`);
|
|
23
|
+
if (kind === "telegram-bot") {
|
|
24
|
+
if (!CHAT.test(target))
|
|
25
|
+
throw refuse("a bot writes to a chat id or user:<id>, never a title");
|
|
26
|
+
return { kind, profile, chat: target };
|
|
27
|
+
}
|
|
28
|
+
if (!ADDRESS.test(target))
|
|
29
|
+
throw refuse(`"${target}" is not one email address`);
|
|
30
|
+
return { kind: "email", profile, address: target };
|
|
31
|
+
};
|
|
32
|
+
export const routeText = (route) => `${route.kind}:${route.profile}/${route.kind === "telegram-bot" ? route.chat : route.address}`;
|
|
33
|
+
export const DELIVERY_REASONS = {
|
|
34
|
+
readOnly: "delivery.read-only",
|
|
35
|
+
programMissing: "delivery.program-missing",
|
|
36
|
+
routeInvalid: "delivery.route-invalid",
|
|
37
|
+
timeout: "delivery.timeout",
|
|
38
|
+
failed: "delivery.failed",
|
|
39
|
+
};
|
|
40
|
+
/** Telegram's limits, in UTF-16 units as JavaScript counts them: a message's text, and a file's caption. */
|
|
41
|
+
export const TELEGRAM_TEXT_CHARS = 4096;
|
|
42
|
+
export const CAPTION_CHARS = 1024;
|
|
43
|
+
const DELIVERY_TIMEOUT_MS = 120_000;
|
|
44
|
+
/** The caption: the title, then the report's first lines as far as they fit, cut where a line ends. */
|
|
45
|
+
export const headOf = (title, report, limit = CAPTION_CHARS) => {
|
|
46
|
+
const more = "\n… the whole report is in the file";
|
|
47
|
+
const room = limit - title.length - 1;
|
|
48
|
+
const text = report.trim();
|
|
49
|
+
if (text.length <= room)
|
|
50
|
+
return text ? `${title}\n${text}` : title;
|
|
51
|
+
const cut = text.slice(0, room - more.length);
|
|
52
|
+
const line = cut.lastIndexOf("\n");
|
|
53
|
+
return `${title}\n${line > room / 2 ? cut.slice(0, line) : cut}${more}`;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* The words that send along `route`. A bot sends a report that fits one message as its text, and a longer one as a
|
|
57
|
+
* file with its head as the caption; mail takes the text on stdin. The text goes after `--`, so a report that starts
|
|
58
|
+
* with a dash stays text.
|
|
59
|
+
*/
|
|
60
|
+
const argvOf = (program, route, title, report, copy) => {
|
|
61
|
+
const lead = [program, route.profile, "--json", "--no-input"];
|
|
62
|
+
if (route.kind === "email")
|
|
63
|
+
return [...lead, "messages", "send", "--to", route.address, "--subject", title];
|
|
64
|
+
const text = copy ? headOf(title, report) : report.trim() ? `${title}\n${report.trim()}` : title;
|
|
65
|
+
return [...lead, "bot", "messages", "send", ...(copy ? ["--file", copy] : []), "--", route.chat, text];
|
|
66
|
+
};
|
|
67
|
+
/** Hands the parcel to each route's CLI, one after another; a route that fails is recorded and the next one still goes. */
|
|
68
|
+
export const deliver = async (system, env, routes, parcel, { find, errorOf, locked, timeoutMs = DELIVERY_TIMEOUT_MS, }) => {
|
|
69
|
+
const failed = (to, reason, error) => ({
|
|
70
|
+
to,
|
|
71
|
+
ok: false,
|
|
72
|
+
reason,
|
|
73
|
+
...(error ? { error } : {}),
|
|
74
|
+
});
|
|
75
|
+
const report = parcel.line ?? (parcel.file ? readFileSync(parcel.file.path, "utf8") : "");
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const to of routes) {
|
|
78
|
+
let route;
|
|
79
|
+
try {
|
|
80
|
+
route = parseRoute(to);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
out.push(failed(to, DELIVERY_REASONS.routeInvalid));
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const { command, prefix } = PROGRAMS[route.kind];
|
|
87
|
+
const lock = locked ?? [`${prefix}_READ_ONLY`].find((name) => env[name] && env[name] !== "0");
|
|
88
|
+
if (lock) {
|
|
89
|
+
out.push(failed(to, DELIVERY_REASONS.readOnly, lock));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const program = find(command, env);
|
|
93
|
+
if (!program) {
|
|
94
|
+
out.push(failed(to, DELIVERY_REASONS.programMissing, command));
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const tooLong = `${parcel.title}\n${report.trim()}`.length > TELEGRAM_TEXT_CHARS;
|
|
98
|
+
// The sending CLI refuses its own and the other WireCat folders, where the report lives, so it gets a copy.
|
|
99
|
+
const folder = route.kind === "telegram-bot" && parcel.file && tooLong ? mkdtempSync(join(tmpdir(), "wirecat-deliver-")) : "";
|
|
100
|
+
const copy = folder && parcel.file ? join(folder, parcel.file.name) : undefined;
|
|
101
|
+
try {
|
|
102
|
+
if (copy && parcel.file)
|
|
103
|
+
copyFileSync(parcel.file.path, copy);
|
|
104
|
+
const { [`${prefix}_PROFILE`]: _, ...childEnv } = env;
|
|
105
|
+
const ran = await system.run(argvOf(program, route, parcel.title, report, copy), childEnv, {
|
|
106
|
+
timeoutMs,
|
|
107
|
+
...(route.kind === "email" ? { input: report.trim() ? report : parcel.title } : {}),
|
|
108
|
+
});
|
|
109
|
+
if (ran.code === 0)
|
|
110
|
+
out.push({ to, ok: true });
|
|
111
|
+
else if (ran.code === TIMED_OUT)
|
|
112
|
+
out.push(failed(to, DELIVERY_REASONS.timeout));
|
|
113
|
+
else
|
|
114
|
+
out.push(failed(to, DELIVERY_REASONS.failed, errorOf(ran.stderr, ran.code)));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
out.push(failed(to, DELIVERY_REASONS.failed));
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
if (folder)
|
|
121
|
+
rmSync(folder, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AppIdentity } from "../ports/app.js";
|
|
2
|
+
import { type Delivery } from "./deliver.js";
|
|
2
3
|
import type { ServerSystem } from "./system.js";
|
|
3
4
|
/** Minutes between runs, a daily local time, or one local moment. */
|
|
4
5
|
export type When = {
|
|
@@ -24,8 +25,10 @@ export interface Schedule {
|
|
|
24
25
|
mailBatch?: number;
|
|
25
26
|
/** An agent's: no run starts for the rest of the local day once its runs cost this much, in US dollars. */
|
|
26
27
|
maxCostPerDay?: number;
|
|
27
|
-
/** When a finished run shows a desktop notice
|
|
28
|
+
/** When a finished run shows a desktop notice and goes along `to`; absent means `notifyOf`'s default. */
|
|
28
29
|
notify?: Notify;
|
|
30
|
+
/** Delivery routes (`telegram-bot:<profile>/<chat>`, `email:<profile>/<address>`) the result goes along after the run. */
|
|
31
|
+
to?: string[];
|
|
29
32
|
}
|
|
30
33
|
export declare const NOTIFY_WHEN: readonly ["always", "failures", "never"];
|
|
31
34
|
export type Notify = (typeof NOTIFY_WHEN)[number];
|
|
@@ -71,6 +74,8 @@ export interface LastRun {
|
|
|
71
74
|
why: string;
|
|
72
75
|
reason?: SkipReason;
|
|
73
76
|
};
|
|
77
|
+
/** How the result went along each of the schedule's routes; a failed delivery never fails the run. */
|
|
78
|
+
deliveries?: Delivery[];
|
|
74
79
|
}
|
|
75
80
|
/** Why a timer left a run out, as an id a translation can key on; `why` stays the English sentence. */
|
|
76
81
|
export declare const SKIP_REASONS: {
|
|
@@ -5,6 +5,7 @@ import { CliError, errorCodes, writeSecurely } from "@wirecat/cli-base";
|
|
|
5
5
|
import { mailHolderVariable, readOnlyLockOf } from "../ports/app.js";
|
|
6
6
|
import { recordLogFor } from "../runs/record-log.js";
|
|
7
7
|
import { childTrace } from "../runs/scope.js";
|
|
8
|
+
import { deliver } from "./deliver.js";
|
|
8
9
|
export const NOTIFY_WHEN = ["always", "failures", "never"];
|
|
9
10
|
/** A repeating agent would otherwise notify on every tick; any other schedule runs rarely enough to say each result. */
|
|
10
11
|
export const notifyOf = (schedule) => schedule.notify ?? (schedule.kind === "agent" && "every" in schedule.when ? "failures" : "always");
|
|
@@ -105,6 +106,11 @@ export const recordRun = (path, run) => {
|
|
|
105
106
|
writeSecurely(path, `${JSON.stringify(last, null, 2)}\n`, 0o600);
|
|
106
107
|
return last;
|
|
107
108
|
};
|
|
109
|
+
const recordDeliveries = (path, last, deliveries) => {
|
|
110
|
+
const delivered = { ...last, deliveries };
|
|
111
|
+
writeSecurely(path, `${JSON.stringify(delivered, null, 2)}\n`, 0o600);
|
|
112
|
+
return delivered;
|
|
113
|
+
};
|
|
108
114
|
/** Notes on the last run that the timer left this one out, keeping that run's result. */
|
|
109
115
|
export const recordSkip = (file, why, reason, at = new Date()) => {
|
|
110
116
|
const path = join(file, "..", "last.json");
|
|
@@ -455,6 +461,32 @@ export const runSchedule = async (app, system, env, file, { now = () => new Date
|
|
|
455
461
|
else if (exitCode !== 0 && mode !== "never") {
|
|
456
462
|
await notify(system, env, title, `failed (exit ${exitCode}) — ${errors}`);
|
|
457
463
|
}
|
|
464
|
+
const routes = schedule.to ?? [];
|
|
465
|
+
if (routes.length > 0 && (exitCode === 0 ? mode === "always" : mode !== "never")) {
|
|
466
|
+
const at = shortTime(last.at);
|
|
467
|
+
const runEnv = { ...env, ...schedule.environment };
|
|
468
|
+
const locked = readOnlyLockOf(app, runEnv);
|
|
469
|
+
const deliveries = await deliver(system, runEnv, routes, exitCode === 0
|
|
470
|
+
? {
|
|
471
|
+
title: `${title} · ${at}`,
|
|
472
|
+
file: { path: output, name: `${schedule.name}-${at.replace(/[ :]/g, "-")}.${agent ? "md" : "txt"}` },
|
|
473
|
+
}
|
|
474
|
+
: {
|
|
475
|
+
title: `${title} failed · ${at}`,
|
|
476
|
+
line: `${title} failed at ${at} (exit ${exitCode}` +
|
|
477
|
+
`${last.failures && last.failures > 1 ? `, ${last.failures} runs in a row` : ""})` +
|
|
478
|
+
`${last.reason ? `: ${last.reason}` : ""} — ${errors}`,
|
|
479
|
+
}, {
|
|
480
|
+
find: (name, on) => findProgram(name, on, system.platform),
|
|
481
|
+
errorOf: reasonOf,
|
|
482
|
+
...(locked ? { locked } : {}),
|
|
483
|
+
});
|
|
484
|
+
last = recordDeliveries(join(folder, "last.json"), last, deliveries);
|
|
485
|
+
const lost = deliveries.filter((one) => !one.ok);
|
|
486
|
+
if (lost.length > 0 && mode !== "never") {
|
|
487
|
+
await notify(system, env, title, lost.map((one) => `not delivered to ${one.to}: ${one.reason}`).join("\n"));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
458
490
|
if ("once" in schedule.when) {
|
|
459
491
|
await timer?.remove(schedule.name);
|
|
460
492
|
rmSync(file, { force: true });
|
|
@@ -4,9 +4,16 @@ export interface Ran {
|
|
|
4
4
|
stderr: string;
|
|
5
5
|
}
|
|
6
6
|
/** What `server` asks of the machine. Tests hand one in; nothing else does. */
|
|
7
|
+
/** What a run may hand the program besides its words: text on its stdin, and a time after which it is stopped. */
|
|
8
|
+
export interface RunOptions {
|
|
9
|
+
input?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/** The exit code a program stopped at `RunOptions.timeoutMs` answers, as `timeout(1)` does. */
|
|
13
|
+
export declare const TIMED_OUT = 124;
|
|
7
14
|
export interface ServerSystem {
|
|
8
15
|
platform: NodeJS.Platform;
|
|
9
|
-
run: (argv: string[], env: NodeJS.ProcessEnv) => Promise<Ran>;
|
|
16
|
+
run: (argv: string[], env: NodeJS.ProcessEnv, options?: RunOptions) => Promise<Ran>;
|
|
10
17
|
/** The command line that starts this CLI: the node binary and the script, both absolute. */
|
|
11
18
|
entry: string[];
|
|
12
19
|
uid: number;
|
|
@@ -15,5 +22,5 @@ export interface ServerSystem {
|
|
|
15
22
|
/** The pause between two looks at whether `serve` is listening yet. */
|
|
16
23
|
pause: () => Promise<void>;
|
|
17
24
|
}
|
|
18
|
-
export declare const runProgram: (argv: string[], env: NodeJS.ProcessEnv) => Promise<Ran>;
|
|
25
|
+
export declare const runProgram: (argv: string[], env: NodeJS.ProcessEnv, { input, timeoutMs }?: RunOptions) => Promise<Ran>;
|
|
19
26
|
export declare const thisMachine: () => ServerSystem;
|
|
@@ -3,15 +3,22 @@ import { closeSync, mkdirSync, openSync } from "node:fs";
|
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
5
|
import { CliError } from "@wirecat/cli-base";
|
|
6
|
-
|
|
6
|
+
/** The exit code a program stopped at `RunOptions.timeoutMs` answers, as `timeout(1)` does. */
|
|
7
|
+
export const TIMED_OUT = 124;
|
|
8
|
+
export const runProgram = (argv, env, { input, timeoutMs } = {}) => new Promise((resolve, reject) => {
|
|
7
9
|
const [file = "", ...args] = argv;
|
|
8
|
-
execFile(file, args, { env, encoding: "utf8" }, (error, stdout, stderr) => {
|
|
10
|
+
const child = execFile(file, args, { env, encoding: "utf8", ...(timeoutMs ? { timeout: timeoutMs } : {}) }, (error, stdout, stderr) => {
|
|
9
11
|
if (error?.code === "ENOENT") {
|
|
10
12
|
reject(new CliError("configuration_error", `${file} was not found — this machine has no ${file} to run serve`));
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
13
|
-
|
|
15
|
+
const code = error?.killed ? TIMED_OUT : typeof error?.code === "number" ? error.code : error ? 1 : 0;
|
|
16
|
+
resolve({ code, stdout, stderr });
|
|
14
17
|
});
|
|
18
|
+
// A program that does not read its stdin may close it first; that is no failure of the run.
|
|
19
|
+
child.stdin?.on("error", () => { });
|
|
20
|
+
if (input !== undefined)
|
|
21
|
+
child.stdin?.end(input);
|
|
15
22
|
});
|
|
16
23
|
export const thisMachine = () => {
|
|
17
24
|
// The path as the package manager's shim gave it, not resolved: pnpm's global link keeps its name across upgrades
|
|
@@ -10,13 +10,13 @@ export declare const calendarTools: ({
|
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: string;
|
|
12
12
|
properties: {
|
|
13
|
+
limit?: undefined;
|
|
13
14
|
account_id: {
|
|
14
15
|
type: string;
|
|
15
16
|
minimum: number;
|
|
16
17
|
};
|
|
17
18
|
query?: undefined;
|
|
18
19
|
timezone?: undefined;
|
|
19
|
-
limit?: undefined;
|
|
20
20
|
page?: undefined;
|
|
21
21
|
event?: undefined;
|
|
22
22
|
} | {
|
|
@@ -45,9 +45,9 @@ export declare const calendarTools: ({
|
|
|
45
45
|
};
|
|
46
46
|
event?: undefined;
|
|
47
47
|
} | {
|
|
48
|
+
limit?: undefined;
|
|
48
49
|
query?: undefined;
|
|
49
50
|
timezone?: undefined;
|
|
50
|
-
limit?: undefined;
|
|
51
51
|
page?: undefined;
|
|
52
52
|
event: {
|
|
53
53
|
type: string;
|
|
@@ -58,9 +58,9 @@ export declare const calendarTools: ({
|
|
|
58
58
|
minimum: number;
|
|
59
59
|
};
|
|
60
60
|
} | {
|
|
61
|
+
limit?: undefined;
|
|
61
62
|
query?: undefined;
|
|
62
63
|
timezone?: undefined;
|
|
63
|
-
limit?: undefined;
|
|
64
64
|
page?: undefined;
|
|
65
65
|
event?: undefined;
|
|
66
66
|
account_id?: undefined;
|
|
@@ -80,13 +80,13 @@ export declare const calendarTools: ({
|
|
|
80
80
|
inputSchema: {
|
|
81
81
|
type: string;
|
|
82
82
|
properties: {
|
|
83
|
+
limit?: undefined;
|
|
83
84
|
account_id: {
|
|
84
85
|
type: string;
|
|
85
86
|
minimum: number;
|
|
86
87
|
};
|
|
87
88
|
query?: undefined;
|
|
88
89
|
timezone?: undefined;
|
|
89
|
-
limit?: undefined;
|
|
90
90
|
page?: undefined;
|
|
91
91
|
event?: undefined;
|
|
92
92
|
} | {
|
|
@@ -115,9 +115,9 @@ export declare const calendarTools: ({
|
|
|
115
115
|
};
|
|
116
116
|
event?: undefined;
|
|
117
117
|
} | {
|
|
118
|
+
limit?: undefined;
|
|
118
119
|
query?: undefined;
|
|
119
120
|
timezone?: undefined;
|
|
120
|
-
limit?: undefined;
|
|
121
121
|
page?: undefined;
|
|
122
122
|
event: {
|
|
123
123
|
type: string;
|
|
@@ -128,9 +128,9 @@ export declare const calendarTools: ({
|
|
|
128
128
|
minimum: number;
|
|
129
129
|
};
|
|
130
130
|
} | {
|
|
131
|
+
limit?: undefined;
|
|
131
132
|
query?: undefined;
|
|
132
133
|
timezone?: undefined;
|
|
133
|
-
limit?: undefined;
|
|
134
134
|
page?: undefined;
|
|
135
135
|
event?: undefined;
|
|
136
136
|
account_id?: undefined;
|
|
@@ -150,13 +150,13 @@ export declare const calendarTools: ({
|
|
|
150
150
|
inputSchema: {
|
|
151
151
|
type: string;
|
|
152
152
|
properties: {
|
|
153
|
+
limit?: undefined;
|
|
153
154
|
account_id: {
|
|
154
155
|
type: string;
|
|
155
156
|
minimum: number;
|
|
156
157
|
};
|
|
157
158
|
query?: undefined;
|
|
158
159
|
timezone?: undefined;
|
|
159
|
-
limit?: undefined;
|
|
160
160
|
page?: undefined;
|
|
161
161
|
event?: undefined;
|
|
162
162
|
} | {
|
|
@@ -185,9 +185,9 @@ export declare const calendarTools: ({
|
|
|
185
185
|
};
|
|
186
186
|
event?: undefined;
|
|
187
187
|
} | {
|
|
188
|
+
limit?: undefined;
|
|
188
189
|
query?: undefined;
|
|
189
190
|
timezone?: undefined;
|
|
190
|
-
limit?: undefined;
|
|
191
191
|
page?: undefined;
|
|
192
192
|
event: {
|
|
193
193
|
type: string;
|
|
@@ -198,9 +198,9 @@ export declare const calendarTools: ({
|
|
|
198
198
|
minimum: number;
|
|
199
199
|
};
|
|
200
200
|
} | {
|
|
201
|
+
limit?: undefined;
|
|
201
202
|
query?: undefined;
|
|
202
203
|
timezone?: undefined;
|
|
203
|
-
limit?: undefined;
|
|
204
204
|
page?: undefined;
|
|
205
205
|
event?: undefined;
|
|
206
206
|
account_id?: undefined;
|
|
@@ -220,13 +220,13 @@ export declare const calendarTools: ({
|
|
|
220
220
|
inputSchema: {
|
|
221
221
|
type: string;
|
|
222
222
|
properties: {
|
|
223
|
+
limit?: undefined;
|
|
223
224
|
account_id: {
|
|
224
225
|
type: string;
|
|
225
226
|
minimum: number;
|
|
226
227
|
};
|
|
227
228
|
query?: undefined;
|
|
228
229
|
timezone?: undefined;
|
|
229
|
-
limit?: undefined;
|
|
230
230
|
page?: undefined;
|
|
231
231
|
event?: undefined;
|
|
232
232
|
} | {
|
|
@@ -255,9 +255,9 @@ export declare const calendarTools: ({
|
|
|
255
255
|
};
|
|
256
256
|
event?: undefined;
|
|
257
257
|
} | {
|
|
258
|
+
limit?: undefined;
|
|
258
259
|
query?: undefined;
|
|
259
260
|
timezone?: undefined;
|
|
260
|
-
limit?: undefined;
|
|
261
261
|
page?: undefined;
|
|
262
262
|
event: {
|
|
263
263
|
type: string;
|
|
@@ -268,9 +268,9 @@ export declare const calendarTools: ({
|
|
|
268
268
|
minimum: number;
|
|
269
269
|
};
|
|
270
270
|
} | {
|
|
271
|
+
limit?: undefined;
|
|
271
272
|
query?: undefined;
|
|
272
273
|
timezone?: undefined;
|
|
273
|
-
limit?: undefined;
|
|
274
274
|
page?: undefined;
|
|
275
275
|
event?: undefined;
|
|
276
276
|
account_id?: undefined;
|
|
@@ -3,6 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { CliError, resolvePaths, writeSecurely } from "@wirecat/cli-base";
|
|
4
4
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
5
5
|
import { Argument, Command, Option } from "commander";
|
|
6
|
+
import { parseRoute, ROUTE_HELP, routeText } from "../../background/deliver.js";
|
|
6
7
|
import { describeWhen, findProgram, lastRun, listSchedules, NOTIFY_WHEN, notifyOf, readSchedule, recordSkip, runSchedule, SCHEDULE_DEFAULTS, SKIP_REASONS, scheduleFile, scheduleName, spentToday, timerFor, } from "../../background/schedules.js";
|
|
7
8
|
import { thisMachine } from "../../background/system.js";
|
|
8
9
|
import { locationVariables } from "../../background/units.js";
|
|
@@ -80,7 +81,13 @@ const withWhen = (command) => command
|
|
|
80
81
|
.option("--every <duration>", "repeat: 30m, 2h, 1d")
|
|
81
82
|
.option("--at <HH:MM>", "every day at this local time")
|
|
82
83
|
.option("--at-time <time>", "one run: 30m, 2d from now, or an ISO time; the schedule removes itself after it")
|
|
83
|
-
.addOption(new Option("--notify-on <when>", "desktop notice after a run (default failures for an agent that repeats with --every, else always)").choices(NOTIFY_WHEN))
|
|
84
|
+
.addOption(new Option("--notify-on <when>", "desktop notice after a run (default failures for an agent that repeats with --every, else always)").choices(NOTIFY_WHEN))
|
|
85
|
+
.option("--to <route>", `also send the result, when --notify-on says so, by the owner's own tg or mail: ${ROUTE_HELP}; repeat it for more`, (route, routes = []) => [...routes, routeText(parseRoute(route))]);
|
|
86
|
+
/** What a schedule keeps of `--notify-on` and `--to`. */
|
|
87
|
+
const deliveryOf = ({ notifyOn, to }) => ({
|
|
88
|
+
...(notifyOn ? { notify: notifyOn } : {}),
|
|
89
|
+
...(to?.length ? { to } : {}),
|
|
90
|
+
});
|
|
84
91
|
/** What the schedule command needs of a CLI: who it is and, per command, where it prints and which profile runs. */
|
|
85
92
|
/** `skill show follow-ups` exists only for messenger CLIs; any other CLI gets a read-only summary it can run. */
|
|
86
93
|
const promptExample = (app) => app.messenger === false
|
|
@@ -254,7 +261,7 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
254
261
|
when: whenOf(this.opts(), COMMAND_MINIMUM_MINUTES),
|
|
255
262
|
environment: environment(),
|
|
256
263
|
createdAt: new Date().toISOString(),
|
|
257
|
-
...(this.opts()
|
|
264
|
+
...deliveryOf(this.opts()),
|
|
258
265
|
});
|
|
259
266
|
});
|
|
260
267
|
withWhen(grantsAuthority(annotate(command.command("agent"), {
|
|
@@ -336,7 +343,7 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
336
343
|
...(mailWaitMinutes === undefined ? {} : { mailWaitMinutes }),
|
|
337
344
|
...(mailBatch === undefined ? {} : { mailBatch }),
|
|
338
345
|
...(options.maxCostPerDay === undefined ? {} : { maxCostPerDay: options.maxCostPerDay }),
|
|
339
|
-
...(options
|
|
346
|
+
...deliveryOf(options),
|
|
340
347
|
});
|
|
341
348
|
});
|
|
342
349
|
if (newMail)
|
|
@@ -358,6 +365,7 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
358
365
|
kind: schedule.kind,
|
|
359
366
|
when: describeWhen(schedule.when),
|
|
360
367
|
notify: notifyOf(schedule),
|
|
368
|
+
...(schedule.to?.length ? { to: schedule.to } : {}),
|
|
361
369
|
last: lastRun(scheduleFile(state, schedule.name)) ?? null,
|
|
362
370
|
...(schedule.kind === "agent"
|
|
363
371
|
? {
|
|
@@ -378,11 +386,12 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
378
386
|
return "";
|
|
379
387
|
}
|
|
380
388
|
return rows
|
|
381
|
-
.map(({ name, kind, when, notify, last, spentTodayUsd, maxCostPerDayUsd }) => [
|
|
389
|
+
.map(({ name, kind, when, notify, to, last, spentTodayUsd, maxCostPerDayUsd }) => [
|
|
382
390
|
name,
|
|
383
391
|
kind,
|
|
384
392
|
when,
|
|
385
393
|
`notify ${notify}`,
|
|
394
|
+
...(to ? [`to ${to.join(", ")}`] : []),
|
|
386
395
|
last
|
|
387
396
|
? `last ${last.at.slice(0, 16).replace("T", " ")} exit ${last.exitCode}` +
|
|
388
397
|
(last.failures
|
|
@@ -396,6 +405,9 @@ export const scheduleCommandFor = ({ app, context: contextOf, newMail, limits: c
|
|
|
396
405
|
...(last?.skipped
|
|
397
406
|
? [`skipped ${last.skipped.at.slice(0, 16).replace("T", " ")}: ${last.skipped.why}`]
|
|
398
407
|
: []),
|
|
408
|
+
...(last?.deliveries ?? [])
|
|
409
|
+
.filter((one) => !one.ok)
|
|
410
|
+
.map((one) => `not delivered to ${one.to}: ${one.reason}${one.error ? ` (${one.error})` : ""}`),
|
|
399
411
|
].join(" "))
|
|
400
412
|
.join("\n");
|
|
401
413
|
});
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -4112,6 +4112,7 @@ export declare const ENTITIES: {
|
|
|
4112
4112
|
readonly kind: v.PicklistSchema<["command", "agent"], undefined>;
|
|
4113
4113
|
readonly when: v.StringSchema<undefined>;
|
|
4114
4114
|
readonly notify: v.OptionalSchema<v.PicklistSchema<["always", "failures", "never"], undefined>, undefined>;
|
|
4115
|
+
readonly to: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
4115
4116
|
readonly last: v.NullableSchema<v.StrictObjectSchema<{
|
|
4116
4117
|
readonly at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
4117
4118
|
readonly exit_code: v.NumberSchema<undefined>;
|
|
@@ -4127,6 +4128,12 @@ export declare const ENTITIES: {
|
|
|
4127
4128
|
readonly why: v.StringSchema<undefined>;
|
|
4128
4129
|
readonly reason: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
4129
4130
|
}, undefined>, undefined>;
|
|
4131
|
+
readonly deliveries: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
4132
|
+
readonly to: v.StringSchema<undefined>;
|
|
4133
|
+
readonly ok: v.BooleanSchema<undefined>;
|
|
4134
|
+
readonly reason: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
4135
|
+
readonly error: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
4136
|
+
}, undefined>, undefined>, undefined>;
|
|
4130
4137
|
}, undefined>, undefined>;
|
|
4131
4138
|
readonly spent_today_usd: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
4132
4139
|
readonly max_cost_usd: v.OptionalSchema<v.NullableSchema<v.NumberSchema<undefined>, undefined>, undefined>;
|
|
@@ -50,6 +50,8 @@ export declare const scheduledJob: v.StrictObjectSchema<{
|
|
|
50
50
|
readonly when: v.StringSchema<undefined>;
|
|
51
51
|
/** When a finished run shows a desktop notice: `always`, `failures` or `never`; the default is shown, not left out. */
|
|
52
52
|
readonly notify: v.OptionalSchema<v.PicklistSchema<["always", "failures", "never"], undefined>, undefined>;
|
|
53
|
+
/** Where a run's result is sent after it: `telegram-bot:<profile>/<chat>` or `email:<profile>/<address>`. */
|
|
54
|
+
readonly to: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
53
55
|
readonly last: v.NullableSchema<v.StrictObjectSchema<{
|
|
54
56
|
readonly at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
55
57
|
readonly exit_code: v.NumberSchema<undefined>;
|
|
@@ -67,6 +69,13 @@ export declare const scheduledJob: v.StrictObjectSchema<{
|
|
|
67
69
|
/** A dotted id for `why`, so a client can translate it: `cost.day-cap`, `mail.none-new`, `mail.below-threshold`. */
|
|
68
70
|
readonly reason: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
69
71
|
}, undefined>, undefined>;
|
|
72
|
+
/** How the run's result went along each route; `reason` a dotted id, `error` the sending CLI's code and reason id. */
|
|
73
|
+
readonly deliveries: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
74
|
+
readonly to: v.StringSchema<undefined>;
|
|
75
|
+
readonly ok: v.BooleanSchema<undefined>;
|
|
76
|
+
readonly reason: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, undefined>]>, undefined>;
|
|
77
|
+
readonly error: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
78
|
+
}, undefined>, undefined>, undefined>;
|
|
70
79
|
}, undefined>, undefined>;
|
|
71
80
|
/** Agent schedules: what today's runs cost, in US dollars. */
|
|
72
81
|
readonly spent_today_usd: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
@@ -34,6 +34,8 @@ export const scheduledJob = v.strictObject({
|
|
|
34
34
|
when: text,
|
|
35
35
|
/** When a finished run shows a desktop notice: `always`, `failures` or `never`; the default is shown, not left out. */
|
|
36
36
|
notify: v.optional(v.picklist(["always", "failures", "never"])),
|
|
37
|
+
/** Where a run's result is sent after it: `telegram-bot:<profile>/<chat>` or `email:<profile>/<address>`. */
|
|
38
|
+
to: v.optional(v.array(text)),
|
|
37
39
|
last: v.nullable(v.strictObject({
|
|
38
40
|
at: time,
|
|
39
41
|
exit_code: v.number(),
|
|
@@ -51,6 +53,13 @@ export const scheduledJob = v.strictObject({
|
|
|
51
53
|
/** A dotted id for `why`, so a client can translate it: `cost.day-cap`, `mail.none-new`, `mail.below-threshold`. */
|
|
52
54
|
reason: v.optional(v.pipe(text, v.regex(/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/))),
|
|
53
55
|
})),
|
|
56
|
+
/** How the run's result went along each route; `reason` a dotted id, `error` the sending CLI's code and reason id. */
|
|
57
|
+
deliveries: v.optional(v.array(v.strictObject({
|
|
58
|
+
to: text,
|
|
59
|
+
ok: v.boolean(),
|
|
60
|
+
reason: v.optional(v.pipe(text, v.regex(/^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/))),
|
|
61
|
+
error: v.optional(text),
|
|
62
|
+
}))),
|
|
54
63
|
})),
|
|
55
64
|
/** Agent schedules: what today's runs cost, in US dollars. */
|
|
56
65
|
spent_today_usd: v.optional(v.number()),
|
|
@@ -79,6 +79,8 @@ export interface MailActivityStore {
|
|
|
79
79
|
}[]>;
|
|
80
80
|
/** Each live email's marks, by its Message-ID; one the store does not hold is left out. */
|
|
81
81
|
emailMarks(accountId: number, externalIds: readonly string[]): Promise<Map<string, EmailMarks>>;
|
|
82
|
+
/** Of these addresses, the ones the owner has written to, in lower case: To or Cc of a live outgoing email, any account. */
|
|
83
|
+
writtenTo(addresses: readonly string[]): Promise<Set<string>>;
|
|
82
84
|
/**
|
|
83
85
|
* Each live thread of the account whose newest email is after `since` (ms), newest first, with the facts of that
|
|
84
86
|
* email. `where` is a `tasks agenda` query compiled over `MAIL_AGENDA_COLUMNS`.
|
|
@@ -99,5 +101,6 @@ export declare const threadEmailIds: (database: CacheDatabase, accountId: number
|
|
|
99
101
|
until?: number;
|
|
100
102
|
}) => number[];
|
|
101
103
|
export declare const emailMarks: (database: CacheDatabase, accountId: number, externalIds: readonly string[]) => Map<string, EmailMarks>;
|
|
104
|
+
export declare const writtenTo: (database: CacheDatabase, addresses: readonly string[]) => Set<string>;
|
|
102
105
|
export declare const emailsAfter: (database: CacheDatabase, accountId: number, afterId: number, limit: number, ids?: readonly number[]) => MarkedEmail[];
|
|
103
106
|
export declare const mailActivity: (database: CacheDatabase) => MailActivityStore;
|
|
@@ -88,6 +88,16 @@ export const emailMarks = (database, accountId, externalIds) => {
|
|
|
88
88
|
}
|
|
89
89
|
return marks;
|
|
90
90
|
};
|
|
91
|
+
export const writtenTo = (database, addresses) => {
|
|
92
|
+
if (addresses.length === 0)
|
|
93
|
+
return new Set();
|
|
94
|
+
const rows = database
|
|
95
|
+
.prepare(`SELECT DISTINCT lower(r.address) AS address FROM emails e JOIN email_recipients r ON r.email_id = e.id
|
|
96
|
+
WHERE e.outgoing = 1 AND e.deleted_at IS NULL AND r.role IN ('to', 'cc')
|
|
97
|
+
AND lower(r.address) IN (SELECT value FROM json_each(?))`)
|
|
98
|
+
.all(JSON.stringify(addresses.map((address) => address.toLowerCase())));
|
|
99
|
+
return new Set(rows.map((row) => String(row.address)));
|
|
100
|
+
};
|
|
91
101
|
const MARKED = `
|
|
92
102
|
SELECT e.id, e.external_id, t.external_id AS thread, coalesce(e.received_at, e.sent_at) AS at, e.created_at,
|
|
93
103
|
e.updated_at, coalesce(e.draft, 0) AS draft, json_extract(e.metadata, '$.partial') IS NOT NULL AS partial,
|
|
@@ -135,5 +145,6 @@ export const mailActivity = (database) => ({
|
|
|
135
145
|
.all(accountId, prefix.length, prefix)
|
|
136
146
|
.map((row) => ({ key: String(row.key), value: String(row.value), at: Number(row.updated_at) })),
|
|
137
147
|
emailMarks: async (accountId, externalIds) => emailMarks(database, accountId, externalIds),
|
|
148
|
+
writtenTo: async (addresses) => writtenTo(database, addresses),
|
|
138
149
|
threadActivity: async (accountId, since, where) => threadActivity(database, accountId, since, where),
|
|
139
150
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.592.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
package/parity.json
CHANGED
|
@@ -1065,7 +1065,7 @@
|
|
|
1065
1065
|
},
|
|
1066
1066
|
"--to": {
|
|
1067
1067
|
"value": "<chat>",
|
|
1068
|
-
"meaning": "the chat to forward it to
|
|
1068
|
+
"meaning": "where it goes: the chat to forward it to (an id, or part of a chat name), or a route a schedule's result is sent along (telegram-bot:<profile>/<chat>, email:<profile>/<address>)",
|
|
1069
1069
|
"note": "the sentence says how to name a chat the messenger's way, so it differs on purpose (Help text rule 4)"
|
|
1070
1070
|
},
|
|
1071
1071
|
"--topic": {
|
|
@@ -3988,6 +3988,12 @@
|
|
|
3988
3988
|
"planned": {
|
|
3989
3989
|
"wa": "wa backlog: mount the shared schedule command"
|
|
3990
3990
|
}
|
|
3991
|
+
},
|
|
3992
|
+
"--to": {
|
|
3993
|
+
"in": ["max", "tg"],
|
|
3994
|
+
"planned": {
|
|
3995
|
+
"wa": "wa backlog: mount the shared schedule command"
|
|
3996
|
+
}
|
|
3991
3997
|
}
|
|
3992
3998
|
}
|
|
3993
3999
|
},
|
|
@@ -4020,6 +4026,12 @@
|
|
|
4020
4026
|
"planned": {
|
|
4021
4027
|
"wa": "wa backlog: mount the shared schedule command"
|
|
4022
4028
|
}
|
|
4029
|
+
},
|
|
4030
|
+
"--to": {
|
|
4031
|
+
"in": ["max", "tg"],
|
|
4032
|
+
"planned": {
|
|
4033
|
+
"wa": "wa backlog: mount the shared schedule command"
|
|
4034
|
+
}
|
|
4023
4035
|
}
|
|
4024
4036
|
}
|
|
4025
4037
|
},
|