@wirecat/cli-core 0.563.0 → 0.565.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 +125 -0
- package/dist/calendar/busy.d.ts +10 -10
- package/dist/calendar/busy.js +9 -9
- package/dist/calendar/cli/answers.d.ts +7 -8
- package/dist/calendar/cli/answers.js +4 -5
- package/dist/calendar/cli/bodies.d.ts +33 -0
- package/dist/calendar/cli/bodies.js +52 -0
- package/dist/calendar/cli/files.d.ts +8 -0
- package/dist/calendar/cli/files.js +41 -0
- package/dist/calendar/cli/index.d.ts +58 -51
- package/dist/calendar/cli/index.js +311 -284
- package/dist/calendar/cli/remind.d.ts +2 -11
- package/dist/calendar/cli/remind.js +81 -81
- package/dist/calendar/cli/search-all.d.ts +3 -8
- package/dist/calendar/cli/search-all.js +47 -42
- package/dist/calendar/mcp/index.d.ts +1 -1
- package/dist/calendar/prepare.d.ts +1 -1
- package/dist/calendar/testing/answers.d.ts +27 -5
- package/dist/calendar/testing/answers.js +88 -17
- package/dist/calendar/testing/writer.d.ts +1 -0
- package/dist/calendar/testing/writer.js +6 -0
- package/dist/calendar/writer.d.ts +6 -1
- package/dist/calendar/writes.d.ts +3 -1
- package/dist/calendar/writes.js +42 -20
- package/dist/cli/config-command.js +1 -1
- package/dist/cli/config-issues.d.ts +3 -1
- package/dist/cli/config-issues.js +2 -2
- package/dist/cli/contract-evidence.d.ts +1 -1
- package/dist/cli/early-refusal.d.ts +2 -1
- package/dist/cli/early-refusal.js +4 -2
- package/dist/cli/entry.d.ts +6 -2
- package/dist/cli/entry.js +4 -1
- package/dist/cli/envelope.d.ts +2 -2
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/messenger/archive-commands.js +17 -0
- package/dist/cli/messenger/catch-up-answers.d.ts +5 -0
- package/dist/cli/messenger/catch-up-answers.js +3 -0
- package/dist/cli/messenger/search-answers.d.ts +1 -0
- package/dist/cli/messenger/search-answers.js +1 -1
- package/dist/cli/messenger/stats-answers.d.ts +3 -3
- package/dist/cli/messenger/stats-answers.js +21 -9
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/tasks-command.js +35 -14
- package/dist/cli/permission-keys.d.ts +3 -1
- package/dist/cli/permission-keys.js +2 -1
- package/dist/cli/preview.d.ts +12 -0
- package/dist/cli/preview.js +8 -1
- package/dist/cli/settings.d.ts +2 -0
- package/dist/cli/settings.js +8 -1
- package/dist/conversations/mail-text.d.ts +6 -0
- package/dist/conversations/mail-text.js +73 -0
- package/dist/domain/calendar.d.ts +2 -0
- package/dist/domain/calendar.js +6 -0
- package/dist/entities/chat-stats.d.ts +16 -2
- package/dist/entities/chat-stats.js +18 -3
- package/dist/entities/index.d.ts +50 -802
- package/dist/entities/index.js +23 -16
- package/dist/entities/mail-counts.d.ts +15 -0
- package/dist/entities/mail-counts.js +16 -0
- package/dist/entities/meeting.d.ts +1 -1
- package/dist/entities/meeting.js +1 -1
- package/dist/entities/store-records.d.ts +8 -0
- package/dist/entities/store-records.js +8 -0
- package/dist/errors/reasons.js +19 -1
- package/dist/mail/cli/index.d.ts +5 -0
- package/dist/mail/cli/index.js +27 -3
- package/dist/mail/cli/review.js +11 -4
- package/dist/mail/index.d.ts +2 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/stats.d.ts +50 -0
- package/dist/mail/stats.js +60 -0
- package/dist/mail/whole.d.ts +4 -4
- package/dist/mail/whole.js +4 -3
- package/dist/mcp/meeting-preparation.d.ts +1 -1
- package/dist/mcp/meeting-project-candidates.d.ts +1 -1
- package/dist/mcp/meeting-series-inventory.d.ts +1 -1
- package/dist/meetings/index.d.ts +2 -2
- package/dist/meetings/index.js +2 -2
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/mcp/passage.d.ts +1 -1
- package/dist/meetings/mcp/project-evidence.d.ts +1 -1
- package/dist/meetings/mcp/search.d.ts +1 -1
- package/dist/meetings/read-answers.d.ts +22 -1
- package/dist/meetings/read-answers.js +38 -0
- package/dist/meetings/stats.d.ts +44 -0
- package/dist/meetings/stats.js +77 -7
- package/dist/search/lucene/registry.js +2 -1
- package/dist/search/question-plan.d.ts +16 -0
- package/dist/search/question-plan.js +34 -0
- package/dist/services/events-search.d.ts +5 -1
- package/dist/services/events-search.js +10 -4
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/meeting-stats.d.ts +12 -1
- package/dist/services/meeting-stats.js +12 -0
- package/dist/services/messages-discovery.d.ts +1 -5
- package/dist/services/messages-discovery.js +5 -18
- package/dist/services/messages-meaning.js +25 -3
- package/dist/services/messages.js +4 -3
- package/dist/services/metadata.d.ts +1 -1
- package/dist/services/notes-search.d.ts +5 -1
- package/dist/services/notes-search.js +16 -5
- package/dist/services/proposals/ids.d.ts +2 -2
- package/dist/services/proposals/ids.js +5 -3
- package/dist/services/proposals/kinds/findings.d.ts +1 -1
- package/dist/services/proposals/kinds/findings.js +1 -1
- package/dist/services/proposals/kinds/meeting-findings.d.ts +7 -0
- package/dist/services/proposals/kinds/meeting-findings.js +73 -0
- package/dist/services/proposals/kinds/task-close.d.ts +7 -0
- package/dist/services/proposals/kinds/task-close.js +76 -0
- package/dist/services/proposals/registry.js +4 -0
- package/dist/services/search-all.d.ts +5 -0
- package/dist/services/search-all.js +47 -14
- package/dist/services/task-stats.d.ts +41 -0
- package/dist/services/task-stats.js +126 -0
- package/dist/services/tasks.d.ts +17 -4
- package/dist/services/tasks.js +31 -14
- package/dist/store/chats.d.ts +3 -1
- package/dist/store/chats.js +6 -0
- package/dist/store/decisions.d.ts +4 -2
- package/dist/store/decisions.js +5 -3
- package/dist/store/exports.d.ts +1 -1
- package/dist/store/exports.js +1 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.js +1 -1
- package/dist/store/sqlite/completeness.d.ts +17 -0
- package/dist/store/sqlite/completeness.js +49 -13
- package/dist/store/sqlite/lucene-fields.js +2 -0
- package/dist/store/sqlite/lucene-groups.d.ts +15 -0
- package/dist/store/sqlite/lucene-groups.js +21 -0
- package/dist/store/sqlite/lucene.d.ts +2 -9
- package/dist/store/sqlite/lucene.js +1 -12
- package/dist/store/sqlite/meeting-stats.d.ts +2 -0
- package/dist/store/sqlite/meeting-stats.js +27 -1
- package/dist/store/sqlite/retention.js +2 -7
- package/dist/store/sqlite/task-board.js +2 -0
- package/dist/store/sqlite/task-stats.d.ts +32 -0
- package/dist/store/sqlite/task-stats.js +45 -0
- package/dist/store/sqlite/tasks.d.ts +2 -1
- package/dist/store/sqlite/tasks.js +2 -0
- package/package.json +3 -3
- package/parity.json +13 -1
package/dist/calendar/writes.js
CHANGED
|
@@ -165,9 +165,38 @@ export const createEvent = async (context, calendarReference, fields, options =
|
|
|
165
165
|
};
|
|
166
166
|
/** The longest line `createEventFromText` sends; a quick add is one sentence, not a description. */
|
|
167
167
|
const TEXT_MAX = 1000;
|
|
168
|
+
/** How far the provider's clock may run behind this computer's when a retry looks for what a lost quick add made. */
|
|
169
|
+
const CLOCK_SKEW_MS = 120_000;
|
|
170
|
+
const TIME_DIGITS = 9;
|
|
171
|
+
/**
|
|
172
|
+
* A quick add's id: `q`, which no hex create id has, then when it was sent, so a retry knows from when to look for the
|
|
173
|
+
* event it may have made.
|
|
174
|
+
*/
|
|
175
|
+
const quickId = (at) => `q${at.toString(32).padStart(TIME_DIGITS, "0")}${newEventId()}`;
|
|
176
|
+
const QUICK_ID = new RegExp(`^q[a-v0-9]{${TIME_DIGITS}}[a-f0-9]{32}$`, "u");
|
|
177
|
+
const sentAt = (id) => Number.parseInt(id.slice(1, 1 + TIME_DIGITS), 32);
|
|
178
|
+
const wordsOf = (text) => text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
179
|
+
/**
|
|
180
|
+
* What a lost quick add made: an event this account made on the calendar since the attempt, whose title's every word
|
|
181
|
+
* is in the line. The provider builds the title from the line's own words.
|
|
182
|
+
*/
|
|
183
|
+
const lostQuickAdd = async (context, calendar, line, id) => {
|
|
184
|
+
const quickAdded = context.writes.writer.quickAdded;
|
|
185
|
+
if (!quickAdded)
|
|
186
|
+
throw new CliError("validation_error", `this provider cannot find a lost quick add; leave out ${context.names.id}`, {
|
|
187
|
+
param: context.names.id,
|
|
188
|
+
});
|
|
189
|
+
const words = new Set(wordsOf(line));
|
|
190
|
+
const found = (await quickAdded.call(context.writes.writer, calendar, sentAt(id) - CLOCK_SKEW_MS)).filter((entry) => entry.status !== "cancelled" && wordsOf(entry.title ?? "").every((word) => words.has(word)));
|
|
191
|
+
if (found.length > 1)
|
|
192
|
+
throw new CliError("validation_error", `${found.length} events made since that attempt could be the one it added; look with events list ` +
|
|
193
|
+
"changed_at>=today and add it again only if none is", { reason: "event.quick-add-ambiguous", operation_id: id, retryable: false });
|
|
194
|
+
return found[0];
|
|
195
|
+
};
|
|
168
196
|
/**
|
|
169
197
|
* An event the provider makes from one line of text, as Google's quickAdd. Nobody is mailed unless `notify` says so:
|
|
170
|
-
* the attendees are known only once the provider has read the text.
|
|
198
|
+
* the attendees are known only once the provider has read the text. The provider picks the event's id, so a retry
|
|
199
|
+
* with the `id` a lost attempt named first looks for the event that attempt made.
|
|
171
200
|
*/
|
|
172
201
|
export const createEventFromText = async (context, calendarReference, text, options = {}) => {
|
|
173
202
|
const quickAdd = context.writes.writer.quickAdd;
|
|
@@ -178,29 +207,22 @@ export const createEventFromText = async (context, calendarReference, text, opti
|
|
|
178
207
|
throw new CliError("validation_error", "the text is empty: say what, when and where");
|
|
179
208
|
if (line.length > TEXT_MAX || /[\r\n]/u.test(line))
|
|
180
209
|
throw new CliError("validation_error", `the text is one line of at most ${TEXT_MAX} characters`);
|
|
210
|
+
if (options.id !== undefined && !QUICK_ID.test(options.id))
|
|
211
|
+
throw new CliError("validation_error", `${context.names.id} is the id a quick add's outcome_unknown named`, {
|
|
212
|
+
param: context.names.id,
|
|
213
|
+
});
|
|
181
214
|
const calendar = writable(await calendarIn(context.store, context.accountId, calendarReference));
|
|
215
|
+
const lost = options.id === undefined ? undefined : await lostQuickAdd(context, calendar, line, options.id);
|
|
182
216
|
const notify = options.notify ?? false;
|
|
183
|
-
const
|
|
184
|
-
const id = newEventId();
|
|
217
|
+
const id = lost ? options.id : quickId(context.now);
|
|
185
218
|
let entry;
|
|
186
|
-
|
|
187
|
-
entry =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
throw new CliError("outcome_unknown", `${error.message} — the provider may have made the event, and a create from text cannot be retried safely: ` +
|
|
192
|
-
"sync, then look with events list changed_at>=today before adding it again", { ...error.details, operation_id: id, retryable: false });
|
|
193
|
-
throw error;
|
|
194
|
-
}
|
|
195
|
-
let saved;
|
|
196
|
-
try {
|
|
197
|
-
;
|
|
198
|
-
[saved] = await context.store.saveEntries(calendar.id, [entry], context.now);
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
throw new CliError(isCliError(error) ? error.code : "invalid_response", `the provider made event ${entry.externalId}, but the local store did not save it ` +
|
|
202
|
-
`(${error instanceof Error ? error.message : String(error)}); sync to store it — do not create it again`, { ...(isCliError(error) ? error.details : {}), operation_id: id, id: entry.externalId, retryable: false });
|
|
219
|
+
if (lost)
|
|
220
|
+
entry = lost;
|
|
221
|
+
else {
|
|
222
|
+
const guard = writeGuard(context, keysFor(context, "quick-add", notify));
|
|
223
|
+
entry = await retryable(context, id, () => guardedWrite(guard, { chatId: null, kind: "edit", key: COMMAND_KEY["quick-add"], operationId: id, sendId: id, notify }, () => quickAdd.call(context.writes.writer, calendar, line, { notify })));
|
|
203
224
|
}
|
|
225
|
+
const [saved] = await savedAfterCreate(context, id, () => context.store.saveEntries(calendar.id, [entry], context.now));
|
|
204
226
|
return answerOf(id, saved, (entry.recurrence?.length ?? 0) > 0);
|
|
205
227
|
};
|
|
206
228
|
/** The most events one import sends: a file of more is refused before anything goes. */
|
|
@@ -56,7 +56,7 @@ export const configCommand = (app, config, { permissionKey = config.permissions,
|
|
|
56
56
|
...(declared ? { default: declared.default, description: declared.description } : {}),
|
|
57
57
|
};
|
|
58
58
|
}),
|
|
59
|
-
store_settings: await storeSettings(env),
|
|
59
|
+
store_settings: (await storeSettings(env)).filter(({ setting }) => offersStoreSetting(app, setting)),
|
|
60
60
|
}));
|
|
61
61
|
for (const issue of [
|
|
62
62
|
...(settings.configIssues ?? []),
|
|
@@ -76,11 +76,13 @@ export declare const issueMessage: (issues: readonly ConfigIssue[], doctor?: str
|
|
|
76
76
|
* `knownPermissions` (from `knownPermissionKeys`), each saved permission key outside it is an issue too, in every
|
|
77
77
|
* section.
|
|
78
78
|
*/
|
|
79
|
-
export declare const checkConfigText: <T extends v.GenericSchema>(text: string, { file, tool, schema, knownPermissions, }: {
|
|
79
|
+
export declare const checkConfigText: <T extends v.GenericSchema>(text: string, { file, tool, schema, knownPermissions, repair, }: {
|
|
80
80
|
file: string;
|
|
81
81
|
tool: string;
|
|
82
82
|
schema: T;
|
|
83
83
|
knownPermissions?: ReadonlySet<string>;
|
|
84
|
+
/** `false` for a CLI with its own `config` that has no `repair`: no fix then names one. */
|
|
85
|
+
repair?: boolean;
|
|
84
86
|
}) => {
|
|
85
87
|
issues: ConfigIssue[];
|
|
86
88
|
config: v.InferOutput<T> | undefined;
|
|
@@ -317,12 +317,12 @@ export const issueMessage = (issues, doctor = "doctor") => {
|
|
|
317
317
|
* `knownPermissions` (from `knownPermissionKeys`), each saved permission key outside it is an issue too, in every
|
|
318
318
|
* section.
|
|
319
319
|
*/
|
|
320
|
-
export const checkConfigText = (text, { file, tool, schema, knownPermissions, }) => {
|
|
320
|
+
export const checkConfigText = (text, { file, tool, schema, knownPermissions, repair = true, }) => {
|
|
321
321
|
const parsed = parseConfigText(text, file);
|
|
322
322
|
if ("issue" in parsed) {
|
|
323
323
|
const issue = publicIssue(parsed.issue);
|
|
324
324
|
// Offered only where it would work, so the desktop never shows a Repair button that fails.
|
|
325
|
-
if (repairConfigText(text).state === "repaired")
|
|
325
|
+
if (repair && repairConfigText(text).state === "repaired")
|
|
326
326
|
issue.fix = `${tool} config repair`;
|
|
327
327
|
return { issues: [issue], config: undefined };
|
|
328
328
|
}
|
|
@@ -15,7 +15,7 @@ export declare const contractEvidence: (root: Command, { fields, errors }?: Cont
|
|
|
15
15
|
field_only: string[];
|
|
16
16
|
reasons: readonly {
|
|
17
17
|
reason: string;
|
|
18
|
-
code: "authentication_error" | "cancelled" | "configuration_error" | "confirmation_required" | "internal_error" | "invalid_response" | "network_error" | "not_found" | "outcome_unknown" | "permission_error" | "provider_error" | "provider_unavailable" | "rate_limited" | "timeout" | "validation_error";
|
|
18
|
+
code: "authentication_error" | "cancelled" | "configuration_error" | "confirmation_required" | "conflict" | "internal_error" | "invalid_response" | "network_error" | "not_found" | "outcome_unknown" | "permission_error" | "provider_error" | "provider_unavailable" | "rate_limited" | "timeout" | "validation_error";
|
|
19
19
|
message: string;
|
|
20
20
|
details: string[];
|
|
21
21
|
step?: string | undefined;
|
|
@@ -22,7 +22,8 @@ export declare const refuseWriteBeforeConnecting: (action: Command, { app, env,
|
|
|
22
22
|
* A command of a CLI that declares its own keys (`commandPermissions`), refused by its key before anything connects:
|
|
23
23
|
* `deny` stops it, `readonly` stops a write, and the read-only lock stops a write to the service. `ask` is left to
|
|
24
24
|
* the write guard, which can put the question. A saved or `--permission` key that names no command stops every keyed
|
|
25
|
-
* command; housekeeping still runs, so `config unset` can remove it.
|
|
25
|
+
* command; housekeeping still runs, so `config unset` can remove it. Under the lock a non-messenger CLI still previews
|
|
26
|
+
* a `--dry-run`, which sends nothing (WC-PERM-12); a messenger's preview stays refused (SEC-65).
|
|
26
27
|
*/
|
|
27
28
|
export declare const refuseByCommandPermissions: (action: Command, permissions: CommandPermissions, { app, env, dryRun, resolve }: {
|
|
28
29
|
app: AppIdentity;
|
|
@@ -79,7 +79,8 @@ export const refuseWriteBeforeConnecting = (action, { app, env, dryRun, resolve
|
|
|
79
79
|
* A command of a CLI that declares its own keys (`commandPermissions`), refused by its key before anything connects:
|
|
80
80
|
* `deny` stops it, `readonly` stops a write, and the read-only lock stops a write to the service. `ask` is left to
|
|
81
81
|
* the write guard, which can put the question. A saved or `--permission` key that names no command stops every keyed
|
|
82
|
-
* command; housekeeping still runs, so `config unset` can remove it.
|
|
82
|
+
* command; housekeeping still runs, so `config unset` can remove it. Under the lock a non-messenger CLI still previews
|
|
83
|
+
* a `--dry-run`, which sends nothing (WC-PERM-12); a messenger's preview stays refused (SEC-65).
|
|
83
84
|
*/
|
|
84
85
|
export const refuseByCommandPermissions = (action, permissions, { app, env, dryRun, resolve }) => {
|
|
85
86
|
const key = permissions.keyOf(commandPathOf(action));
|
|
@@ -87,7 +88,8 @@ export const refuseByCommandPermissions = (action, permissions, { app, env, dryR
|
|
|
87
88
|
return;
|
|
88
89
|
const { effects } = metaOf(action);
|
|
89
90
|
const lock = readOnlyLockOf(app, env);
|
|
90
|
-
|
|
91
|
+
const preview = dryRun && app.messenger === false;
|
|
92
|
+
if (lock && effects?.remote === "write" && !preview)
|
|
91
93
|
throw readOnlyRefusal(lock, `run ${commandPathOf(action).join(" ")}`);
|
|
92
94
|
const flags = action.optsWithGlobals();
|
|
93
95
|
const overrides = environmentOf(action).agent?.overrides ?? [];
|
package/dist/cli/entry.d.ts
CHANGED
|
@@ -16,8 +16,12 @@ export interface EntryContext {
|
|
|
16
16
|
atStart: (command: Command) => Promise<void>;
|
|
17
17
|
/** A yes/no question on stderr; false without asking when there is no terminal, under CI, `--json`, `--jsonl` or `--no-input`. */
|
|
18
18
|
ask: (question: string, command?: Command) => Promise<boolean>;
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Opens the URL in the system browser, or in `browser` when given; never waits for the browser to close. Does nothing
|
|
21
|
+
* where nobody can use a browser (WC-CMD-15): under CI, `--json`, `--jsonl`, `--no-input`, or with no terminal.
|
|
22
|
+
* The caller prints the URL either way.
|
|
23
|
+
*/
|
|
24
|
+
openBrowser: (url: string, browser?: string, command?: Command) => Promise<void>;
|
|
21
25
|
/** True once a reader closed the output (`| head`): nothing more reaches the terminal. */
|
|
22
26
|
outputClosed: () => boolean;
|
|
23
27
|
}
|
package/dist/cli/entry.js
CHANGED
|
@@ -88,7 +88,10 @@ export const startCli = async (entry) => {
|
|
|
88
88
|
});
|
|
89
89
|
return /^(y|yes)$/i.test(answer.trim());
|
|
90
90
|
},
|
|
91
|
-
openBrowser: (url, browser) =>
|
|
91
|
+
openBrowser: async (url, browser, command) => {
|
|
92
|
+
if (!headless(command))
|
|
93
|
+
await openBrowser(url, browser, io);
|
|
94
|
+
},
|
|
92
95
|
outputClosed: () => closed,
|
|
93
96
|
};
|
|
94
97
|
let code;
|
package/dist/cli/envelope.d.ts
CHANGED
|
@@ -89,7 +89,7 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
|
|
|
89
89
|
readonly accounts: v.OptionalSchema<v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>, undefined>;
|
|
90
90
|
readonly complete: v.BooleanSchema<undefined>;
|
|
91
91
|
readonly issues: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
92
|
-
readonly code: v.PicklistSchema<readonly ["validation_error", "configuration_error", "authentication_error", "permission_error", "not_found", "confirmation_required", "rate_limited", "timeout", "network_error", "provider_error", "provider_unavailable", "invalid_response", "outcome_unknown", "cancelled"], undefined>;
|
|
92
|
+
readonly code: v.PicklistSchema<readonly ["validation_error", "configuration_error", "authentication_error", "permission_error", "not_found", "confirmation_required", "rate_limited", "timeout", "network_error", "provider_error", "provider_unavailable", "invalid_response", "outcome_unknown", "conflict", "cancelled"], undefined>;
|
|
93
93
|
readonly reason: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "dotted lower-case words, such as `page.next`">]>, undefined>;
|
|
94
94
|
readonly source: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
95
95
|
}, undefined>, undefined>, undefined>;
|
|
@@ -144,7 +144,7 @@ export declare const answerSchema: (item?: v.GenericSchema) => v.UnionSchema<[v.
|
|
|
144
144
|
readonly accounts: v.OptionalSchema<v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>, undefined>, undefined>;
|
|
145
145
|
readonly complete: v.BooleanSchema<undefined>;
|
|
146
146
|
readonly issues: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
|
|
147
|
-
readonly code: v.PicklistSchema<readonly ["validation_error", "configuration_error", "authentication_error", "permission_error", "not_found", "confirmation_required", "rate_limited", "timeout", "network_error", "provider_error", "provider_unavailable", "invalid_response", "outcome_unknown", "cancelled"], undefined>;
|
|
147
|
+
readonly code: v.PicklistSchema<readonly ["validation_error", "configuration_error", "authentication_error", "permission_error", "not_found", "confirmation_required", "rate_limited", "timeout", "network_error", "provider_error", "provider_unavailable", "invalid_response", "outcome_unknown", "conflict", "cancelled"], undefined>;
|
|
148
148
|
readonly reason: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "dotted lower-case words, such as `page.next`">]>, undefined>;
|
|
149
149
|
readonly source: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "must not be empty">]>;
|
|
150
150
|
}, undefined>, undefined>, undefined>;
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export { type AgentPolicy, type AgentTask, type EvaluationResult, evaluateAgent,
|
|
|
45
45
|
export { answerEntity } from "./answer-entity.js";
|
|
46
46
|
export { type AppDoctorOptions, appDoctorReport } from "./app-doctor-command.js";
|
|
47
47
|
export type { AppMcpOptions } from "./app-mcp-command.js";
|
|
48
|
+
export { actorOf } from "./author.js";
|
|
48
49
|
export type { ApiCommandInput, ApiCommands } from "./bot/api.js";
|
|
49
50
|
export { apiFlagOf, apiJson, apiOptionKey, apiPlainJson, checkApiBody, checkApiParameter, parseApiJson, readApiBody, } from "./bot/api-input.js";
|
|
50
51
|
export { prepareRpcApiBody, type RpcApiBody } from "./bot/api-rpc.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -39,6 +39,7 @@ export { liftOptionsAfterDashes } from "./after-dashes.js";
|
|
|
39
39
|
export { evaluateAgent, } from "./agent-evaluation.js";
|
|
40
40
|
export { answerEntity } from "./answer-entity.js";
|
|
41
41
|
export { appDoctorReport } from "./app-doctor-command.js";
|
|
42
|
+
export { actorOf } from "./author.js";
|
|
42
43
|
export { apiFlagOf, apiJson, apiOptionKey, apiPlainJson, checkApiBody, checkApiParameter, parseApiJson, readApiBody, } from "./bot/api-input.js";
|
|
43
44
|
export { prepareRpcApiBody } from "./bot/api-rpc.js";
|
|
44
45
|
export { botContext, botWords } from "./bot/context.js";
|
|
@@ -26,6 +26,7 @@ export const storeCommand = (messenger) => {
|
|
|
26
26
|
const store = new Command("store")
|
|
27
27
|
.description("the local store of messages")
|
|
28
28
|
.addCommand(statusCommand(messenger))
|
|
29
|
+
.addCommand(storedChatsCommand(messenger))
|
|
29
30
|
.addCommand(gapsCommand(messenger))
|
|
30
31
|
.addCommand(jobsCommand(messenger))
|
|
31
32
|
.addCommand(exportCommand(messenger))
|
|
@@ -35,6 +36,7 @@ export const storeCommand = (messenger) => {
|
|
|
35
36
|
const examples = {
|
|
36
37
|
backup: "./backup.db",
|
|
37
38
|
check: "",
|
|
39
|
+
"chats status": "",
|
|
38
40
|
clear: "--left --dry-run",
|
|
39
41
|
"copies delete": "messages_copy_1",
|
|
40
42
|
decrypt: "./backup.enc --output ./restored.db",
|
|
@@ -93,6 +95,21 @@ const statusCommand = (messenger) => annotate(new Command("status"), { output: {
|
|
|
93
95
|
.map((skip) => ` ${skip.title ?? skip.chatId} ${skip.messages} messages ${skip.reason}`)
|
|
94
96
|
.join("\n"));
|
|
95
97
|
});
|
|
98
|
+
/** `store status` summed over the account's chats, for a count of what the archive holds without a list of them. */
|
|
99
|
+
const storedChatsCommand = (messenger) => {
|
|
100
|
+
const chats = new Command("chats").description("the account's chats as the store holds them");
|
|
101
|
+
annotate(chats.command("status"), { output: { entity: "chat_coverage" }, effects: LOCAL_READ })
|
|
102
|
+
.summary("how many chats the archive holds in full, in part, or not at all")
|
|
103
|
+
.description("over this account's searchable chats of every kind, channels included: fetched (back to the first message, " +
|
|
104
|
+
"up to date, no gaps), partial, never (no fetch recorded and no message held), and the oldest message held; " +
|
|
105
|
+
"reads the store only")
|
|
106
|
+
.action(async function () {
|
|
107
|
+
const context = messengerContext(this, messenger);
|
|
108
|
+
const coverage = await context.withStore((store, account) => store.chats.chatCoverage(account));
|
|
109
|
+
renderAnswer(this, context, itemOf(snakeKeys(coverage)));
|
|
110
|
+
});
|
|
111
|
+
return chats;
|
|
112
|
+
};
|
|
96
113
|
const LISTED = 20;
|
|
97
114
|
/** Titles are the owner's: these lines go to a person's terminal, never into an error message. */
|
|
98
115
|
const leftLines = (left) => {
|
|
@@ -18,6 +18,8 @@ type Read = Message & {
|
|
|
18
18
|
chatTitle?: string | null;
|
|
19
19
|
};
|
|
20
20
|
/** One message of a catch-up answer: the fields `inbox` and `review` show, for every messenger and for mail. */
|
|
21
|
+
/** Written by others, and by the owner, so an agent quotes it and never follows it (WC-SECR-21). */
|
|
22
|
+
export declare const SUMMARY_UNTRUSTED: string[];
|
|
21
23
|
export declare const summaryOf: (message: Read, reader: AccountKey) => {
|
|
22
24
|
ref: string;
|
|
23
25
|
chat_id: string;
|
|
@@ -61,6 +63,7 @@ export declare const summaryOf: (message: Read, reader: AccountKey) => {
|
|
|
61
63
|
url?: string | undefined;
|
|
62
64
|
}[][] | undefined;
|
|
63
65
|
}[];
|
|
66
|
+
untrusted: string[];
|
|
64
67
|
};
|
|
65
68
|
export declare const inboxAnswer: (inbox: Inbox, hearing: Hearing | undefined, reader: AccountKey, transcribe: boolean, meta?: Partial<Meta>) => import("../envelope.js").ListBody<{
|
|
66
69
|
ref: string;
|
|
@@ -105,6 +108,7 @@ export declare const inboxAnswer: (inbox: Inbox, hearing: Hearing | undefined, r
|
|
|
105
108
|
url?: string | undefined;
|
|
106
109
|
}[][] | undefined;
|
|
107
110
|
}[];
|
|
111
|
+
untrusted: string[];
|
|
108
112
|
}>;
|
|
109
113
|
/** What `review --offline` cannot see. */
|
|
110
114
|
export declare const OFFLINE_REVIEW = "offline: only what was already synced to the local store; run review without --offline for the rest";
|
|
@@ -160,5 +164,6 @@ export declare const reviewAnswer: (found: Review, hearing: Hearing | undefined,
|
|
|
160
164
|
url?: string | undefined;
|
|
161
165
|
}[][] | undefined;
|
|
162
166
|
}[];
|
|
167
|
+
untrusted: string[];
|
|
163
168
|
}>;
|
|
164
169
|
export {};
|
|
@@ -34,6 +34,8 @@ export const catchUpQuery = (words, registry, command) => {
|
|
|
34
34
|
};
|
|
35
35
|
};
|
|
36
36
|
/** One message of a catch-up answer: the fields `inbox` and `review` show, for every messenger and for mail. */
|
|
37
|
+
/** Written by others, and by the owner, so an agent quotes it and never follows it (WC-SECR-21). */
|
|
38
|
+
export const SUMMARY_UNTRUSTED = ["chat_title", "sender_name", "text", "transcript", "attachments.name"];
|
|
37
39
|
export const summaryOf = (message, reader) => {
|
|
38
40
|
const { ref, chat_id, chat_title, timestamp, sender_name, sender_is_me, outgoing, text, transcript, attachments } = messageItem(message, reader);
|
|
39
41
|
return {
|
|
@@ -47,6 +49,7 @@ export const summaryOf = (message, reader) => {
|
|
|
47
49
|
text,
|
|
48
50
|
...(transcript === undefined ? {} : { transcript }),
|
|
49
51
|
attachments,
|
|
52
|
+
untrusted: SUMMARY_UNTRUSTED,
|
|
50
53
|
};
|
|
51
54
|
};
|
|
52
55
|
const nameOf = (chat) => chat.title ?? chat.id;
|
|
@@ -18,6 +18,7 @@ export type NoteHitItem = InferOutput<(typeof ENTITIES)["note.hit"]>;
|
|
|
18
18
|
export type HitItem = InferOutput<(typeof ENTITIES)["hit"]>;
|
|
19
19
|
/** The page a `--page` asks for, as the offset the search services take. */
|
|
20
20
|
export declare const offsetOf: (page: number | undefined, limit: number) => number | undefined;
|
|
21
|
+
export declare const MAIL_HIT_UNTRUSTED: string[];
|
|
21
22
|
export declare const messageHitItem: (hit: FoundMessage, query?: string) => MessageHitItem;
|
|
22
23
|
/**
|
|
23
24
|
* What the store holds of a chat that is not complete, in words. A chat that reaches its first message is only
|
|
@@ -10,8 +10,8 @@ import type { MessageStats } from "../../services/messages-search.js";
|
|
|
10
10
|
import type { RankingFound } from "../../services/rankings.js";
|
|
11
11
|
import type { ReplyTimes, Waiting } from "../../services/reply-stats.js";
|
|
12
12
|
import type { RetentionService } from "../../services/retention.js";
|
|
13
|
+
import type { TaskStats } from "../../services/task-stats.js";
|
|
13
14
|
import type { AccountKey, RankedEvidence } from "../../store/store.js";
|
|
14
|
-
import type { GroupStats } from "../../tasks/index.js";
|
|
15
15
|
import { type ItemBody, type ListBody } from "../envelope.js";
|
|
16
16
|
type Of<T extends keyof typeof ENTITIES> = InferOutput<(typeof ENTITIES)[T]>;
|
|
17
17
|
type RetentionFound = Awaited<ReturnType<RetentionService["report"]>>;
|
|
@@ -71,8 +71,8 @@ export declare const refuseJsonl: (format: string) => void;
|
|
|
71
71
|
export declare const chatStatsAnswer: (stats: ChatStats, account: AccountKey, meta?: Partial<Meta>) => ItemBody<Of<"chat_stats">>;
|
|
72
72
|
/** Said where the store has kept no join, leave or other chat event for the chat: a 0 would read as nobody joined. */
|
|
73
73
|
export declare const MEMBERS_UNRECORDED = "joins and leaves are not recorded for this chat yet, so they are not counted";
|
|
74
|
-
/** `stats tasks show`, CLI and MCP alike. */
|
|
75
|
-
export declare const taskStatsAnswer: (
|
|
74
|
+
/** `stats tasks show`, CLI and MCP alike. A row of `--by chat` also names its chat as `chat`, as it always has. */
|
|
75
|
+
export declare const taskStatsAnswer: (stats: TaskStats, account: AccountKey) => ItemBody<Of<"task_stats">>;
|
|
76
76
|
export declare const responsesAnswer: (found: ReplyTimes) => ItemBody<Of<"response_times">>;
|
|
77
77
|
export declare const waitingAnswer: (found: Waiting) => ItemBody<Of<"waiting_chats">>;
|
|
78
78
|
export {};
|
|
@@ -410,15 +410,27 @@ export const chatStatsAnswer = (stats, account, meta) => {
|
|
|
410
410
|
};
|
|
411
411
|
/** Said where the store has kept no join, leave or other chat event for the chat: a 0 would read as nobody joined. */
|
|
412
412
|
export const MEMBERS_UNRECORDED = "joins and leaves are not recorded for this chat yet, so they are not counted";
|
|
413
|
-
/** `stats tasks show`, CLI and MCP alike. */
|
|
414
|
-
export const taskStatsAnswer = (
|
|
415
|
-
open:
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
413
|
+
/** `stats tasks show`, CLI and MCP alike. A row of `--by chat` also names its chat as `chat`, as it always has. */
|
|
414
|
+
export const taskStatsAnswer = (stats, account) => itemOf({
|
|
415
|
+
open: stats.open,
|
|
416
|
+
created: stats.created,
|
|
417
|
+
closed: stats.closed,
|
|
418
|
+
median_close_ms: stats.medianCloseMs ?? null,
|
|
419
|
+
mean_open_age_ms: stats.meanOpenAgeMs ?? null,
|
|
420
|
+
rows: stats.rows.map((row) => {
|
|
421
|
+
const chat = stats.by === "chat" ? chatRef(account, row.group) : undefined;
|
|
422
|
+
return {
|
|
423
|
+
...(chat === undefined ? {} : { chat }),
|
|
424
|
+
key: chat ?? (stats.by === "project" ? `project:${row.group}` : row.group),
|
|
425
|
+
name: row.name,
|
|
426
|
+
open: row.open,
|
|
427
|
+
created: row.created,
|
|
428
|
+
closed: row.closed,
|
|
429
|
+
oldest_open_at: row.oldestOpenAt?.toISOString() ?? null,
|
|
430
|
+
median_close_ms: row.medianCloseMs ?? null,
|
|
431
|
+
mean_open_age_ms: row.meanOpenAgeMs ?? null,
|
|
432
|
+
};
|
|
433
|
+
}),
|
|
422
434
|
});
|
|
423
435
|
const replyFigures = (one) => ({
|
|
424
436
|
replies: one.replies,
|
|
@@ -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;
|
|
@@ -8,7 +8,8 @@ import { aheadOf, momentOf } from "../../services/moment.js";
|
|
|
8
8
|
import { refuseAlreadyRead } from "../../services/task-analysis.js";
|
|
9
9
|
import { unlistedNote } from "../../services/task-categories.js";
|
|
10
10
|
import { ARRIVAL_WINDOW, applyTaskRulesToChats } from "../../services/task-rules.js";
|
|
11
|
-
import {
|
|
11
|
+
import { TASK_STATS_BY } from "../../services/task-stats.js";
|
|
12
|
+
import { CLAIM_LEASE_MS, closedStateOf, TASK_PAIR_QUERY, TASK_QUERY, TASK_STATS_QUERY, taskLinkKindOf, taskTypeOf, } from "../../services/tasks.js";
|
|
12
13
|
import { PRIORITY_LABELS, priorityOption } from "../../tasks/index.js";
|
|
13
14
|
import { actorOf, holderOf as holderIn } from "../author.js";
|
|
14
15
|
import { environmentOf } from "../context.js";
|
|
@@ -539,32 +540,52 @@ export const tasksCommand = (messenger) => {
|
|
|
539
540
|
});
|
|
540
541
|
return tasks;
|
|
541
542
|
};
|
|
543
|
+
const statsByOf = (value) => {
|
|
544
|
+
if (!TASK_STATS_BY.includes(value))
|
|
545
|
+
throw new CliError("validation_error", `--by takes ${TASK_STATS_BY.join(", ")}`);
|
|
546
|
+
return value;
|
|
547
|
+
};
|
|
548
|
+
const minutes = (ms) => (ms === undefined ? "" : `${Math.round(ms / 60_000)} min`);
|
|
542
549
|
export const tasksStatsCommand = (messenger) => {
|
|
543
|
-
return annotate(new Command("show"), {
|
|
544
|
-
|
|
550
|
+
return withQuery(annotate(new Command("show"), {
|
|
551
|
+
effects: LOCAL_READ,
|
|
552
|
+
output: { entity: "task_stats" },
|
|
553
|
+
examples: [`${messenger.app.command} stats tasks show --by meeting -- created>=30d`],
|
|
554
|
+
}), TASK_STATS_QUERY)
|
|
555
|
+
.summary("count tasks by chat, meeting or project, open and closed")
|
|
556
|
+
.description("tasks per chat, meeting or project: open now, and opened and closed in the period (created: in the query), " +
|
|
557
|
+
"with the median time to close and the mean age of the open ones")
|
|
558
|
+
.option("--by <group>", "chat (default) or meeting: this account's tasks; project: the owner's project tasks, every project", statsByOf)
|
|
545
559
|
.option("--chat <chat>", `only this chat; ${messenger.chatArgument}`)
|
|
546
560
|
.option("--type <type>", "only this type: question, request, mention or promise", taskTypeOf)
|
|
547
|
-
.
|
|
561
|
+
.option("--timezone <zone>", "the IANA timezone for calendar days in the period, such as created>=today")
|
|
562
|
+
.action(async function (words) {
|
|
548
563
|
const context = messengerContext(this, messenger);
|
|
549
|
-
const { chat, type } = this.opts();
|
|
564
|
+
const { by, chat, type, timezone } = this.opts();
|
|
550
565
|
refuseJsonl(context.format);
|
|
551
|
-
const
|
|
552
|
-
|
|
566
|
+
const query = listQuery(typedQuery(words), TASK_STATS_QUERY, "stats tasks show");
|
|
567
|
+
const { stats, account } = await context.withServices(async (services) => ({
|
|
568
|
+
stats: await services.tasks.stats({
|
|
569
|
+
...(by === undefined ? {} : { by }),
|
|
553
570
|
...(chat === undefined ? {} : { chat }),
|
|
554
|
-
...(type === undefined ? {} : { type }),
|
|
571
|
+
...(type === undefined ? {} : { kind: type }),
|
|
572
|
+
...(query === undefined ? {} : { query }),
|
|
573
|
+
...(timezone === undefined ? {} : { timezone }),
|
|
555
574
|
}),
|
|
556
575
|
account: await services.messages.reader(),
|
|
557
576
|
}));
|
|
558
|
-
if (rows.length === 0)
|
|
577
|
+
if (stats.rows.length === 0)
|
|
559
578
|
context.renderer.note("no tasks yet");
|
|
560
|
-
renderAnswer(this, context, taskStatsAnswer(
|
|
579
|
+
renderAnswer(this, context, taskStatsAnswer(stats, account), () => stats.rows.length === 0
|
|
561
580
|
? ""
|
|
562
|
-
: `${rows
|
|
563
|
-
.map(({ group, open, oldestOpenAt, medianCloseMs }) => [
|
|
564
|
-
group,
|
|
581
|
+
: `${stats.rows
|
|
582
|
+
.map(({ group, name, open, created, closed, oldestOpenAt, medianCloseMs }) => [
|
|
583
|
+
name ? `${name} (${group})` : group,
|
|
565
584
|
`${open} open`,
|
|
585
|
+
`${created} created`,
|
|
586
|
+
`${closed} closed`,
|
|
566
587
|
oldestOpenAt ? `oldest ${oldestOpenAt.toISOString().slice(0, 16).replace("T", " ")}` : "",
|
|
567
|
-
medianCloseMs === undefined ? "" : `median ${
|
|
588
|
+
medianCloseMs === undefined ? "" : `median ${minutes(medianCloseMs)} to close`,
|
|
568
589
|
]
|
|
569
590
|
.filter(Boolean)
|
|
570
591
|
.join(" "))
|
|
@@ -14,12 +14,14 @@ export interface CommandPermissions {
|
|
|
14
14
|
extra: readonly PermissionKey[];
|
|
15
15
|
/** cli-core's built-in levels and the CLI's own; they only ever tighten. */
|
|
16
16
|
defaults: Readonly<Record<PermissionKey, Level>>;
|
|
17
|
+
/** The other keys a command's real run checks, so its `--dry-run` preview shows each: gcal's `api.calendar.*`. */
|
|
18
|
+
alsoChecks?: (path: readonly string[]) => readonly PermissionKey[];
|
|
17
19
|
}
|
|
18
20
|
/** Either way of naming a program's keys: a messenger's per-command mapping, or a declared CLI's own. */
|
|
19
21
|
export type PermissionKeySource = PermissionKeyOf | CommandPermissions;
|
|
20
22
|
/** A command's own path is its key; the commands that look after the tool have none. */
|
|
21
23
|
export declare const commandPathKey: (path: readonly string[]) => PermissionKey | null;
|
|
22
|
-
export declare const commandPermissions: ({ keyOf, extra, defaults, }?: Partial<CommandPermissions>) => CommandPermissions;
|
|
24
|
+
export declare const commandPermissions: ({ keyOf, extra, defaults, alsoChecks, }?: Partial<CommandPermissions>) => CommandPermissions;
|
|
23
25
|
/**
|
|
24
26
|
* The keys `permissions` may name: every command of the program, every write key, and each of their
|
|
25
27
|
* parents. A key outside it is a typo that would otherwise do nothing (BUG-137).
|
|
@@ -16,10 +16,11 @@ const HOUSEKEEPING = new Set([
|
|
|
16
16
|
]);
|
|
17
17
|
/** A command's own path is its key; the commands that look after the tool have none. */
|
|
18
18
|
export const commandPathKey = (path) => path[0] === undefined || HOUSEKEEPING.has(path[0]) ? null : path.join(".");
|
|
19
|
-
export const commandPermissions = ({ keyOf = commandPathKey, extra = [], defaults = {}, } = {}) => ({
|
|
19
|
+
export const commandPermissions = ({ keyOf = commandPathKey, extra = [], defaults = {}, alsoChecks, } = {}) => ({
|
|
20
20
|
keyOf,
|
|
21
21
|
extra: [AUTHORITY_KEY, ...extra],
|
|
22
22
|
defaults: { ...DEFAULT_PERMISSIONS, ...defaults },
|
|
23
|
+
...(alsoChecks ? { alsoChecks } : {}),
|
|
23
24
|
});
|
|
24
25
|
const pathOf = (command) => {
|
|
25
26
|
const words = [];
|
package/dist/cli/preview.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare const preview: (command: Command, settings: Pick<Settings, "permi
|
|
|
10
10
|
permission?: {
|
|
11
11
|
key: PermissionKey | null;
|
|
12
12
|
level: string;
|
|
13
|
+
also?: {
|
|
14
|
+
key: PermissionKey;
|
|
15
|
+
level: string;
|
|
16
|
+
}[];
|
|
13
17
|
} | undefined;
|
|
14
18
|
validation: {
|
|
15
19
|
arguments: string;
|
|
@@ -42,12 +46,20 @@ export declare const preview: (command: Command, settings: Pick<Settings, "permi
|
|
|
42
46
|
export declare const previewBody: (command: Command, permission: {
|
|
43
47
|
key: PermissionKey | null;
|
|
44
48
|
level: string;
|
|
49
|
+
also?: {
|
|
50
|
+
key: PermissionKey;
|
|
51
|
+
level: string;
|
|
52
|
+
}[];
|
|
45
53
|
} | undefined, messenger: boolean) => {
|
|
46
54
|
preview: boolean;
|
|
47
55
|
path: string[];
|
|
48
56
|
permission?: {
|
|
49
57
|
key: PermissionKey | null;
|
|
50
58
|
level: string;
|
|
59
|
+
also?: {
|
|
60
|
+
key: PermissionKey;
|
|
61
|
+
level: string;
|
|
62
|
+
}[];
|
|
51
63
|
} | undefined;
|
|
52
64
|
validation: {
|
|
53
65
|
arguments: string;
|
package/dist/cli/preview.js
CHANGED
|
@@ -23,7 +23,14 @@ export const preview = (command, settings, tool, keys, messenger = true) => {
|
|
|
23
23
|
(!declared && key !== null && key !== undefined && WRITE_KEYS.includes(key));
|
|
24
24
|
if (level === "deny" || (writes && level === "readonly"))
|
|
25
25
|
throw new CliError("permission_error", `this profile does not permit ${path.join(" ")}`, { permission: key });
|
|
26
|
-
|
|
26
|
+
const also = (declared?.alsoChecks?.(path) ?? []).map((one) => ({
|
|
27
|
+
key: one,
|
|
28
|
+
level: levelFor(permissions, one, declared?.defaults).level,
|
|
29
|
+
}));
|
|
30
|
+
const stopped = also.find((one) => one.level === "deny" || (writes && one.level === "readonly"));
|
|
31
|
+
if (stopped)
|
|
32
|
+
throw new CliError("permission_error", `this profile does not permit ${stopped.key}`, { permission: stopped.key });
|
|
33
|
+
return previewBody(command, { key: key ?? null, level, ...(also.length ? { also } : {}) }, messenger);
|
|
27
34
|
};
|
|
28
35
|
const TARGETS = new Set(["chat", "message", "person", "profile"]);
|
|
29
36
|
/**
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export interface SettingsExtension<T extends object = Record<never, never>> {
|
|
|
42
42
|
* `--permission` then take any command path, which the program checks against its commands.
|
|
43
43
|
*/
|
|
44
44
|
permissions?: CommandPermissions;
|
|
45
|
+
/** `false` for a CLI with its own `config` that has no `repair`: no config issue then names one. */
|
|
46
|
+
configRepair?: boolean;
|
|
45
47
|
}
|
|
46
48
|
export interface SettingsExtensionContext {
|
|
47
49
|
flags: GlobalFlags;
|
package/dist/cli/settings.js
CHANGED
|
@@ -102,6 +102,8 @@ export const MESSENGER_ONLY_SETTINGS = [
|
|
|
102
102
|
"history_group_days",
|
|
103
103
|
"history_group_members_max",
|
|
104
104
|
"read_other_bots",
|
|
105
|
+
"speech_model",
|
|
106
|
+
"picture_search",
|
|
105
107
|
];
|
|
106
108
|
/** One program, one version: whether to look for a newer one is not a per-profile matter. */
|
|
107
109
|
const SHARED_DEFAULTS_ONLY = {
|
|
@@ -259,7 +261,12 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
259
261
|
return { config: { profiles: {} }, issues: [read.issue] };
|
|
260
262
|
if (read.text === undefined)
|
|
261
263
|
return { config: { profiles: {} }, issues: [] };
|
|
262
|
-
const { config, issues } = checkConfigText(read.text, {
|
|
264
|
+
const { config, issues } = checkConfigText(read.text, {
|
|
265
|
+
file: path,
|
|
266
|
+
tool: app.command,
|
|
267
|
+
schema: readSchema,
|
|
268
|
+
repair: extension.configRepair !== false,
|
|
269
|
+
});
|
|
263
270
|
return { config: (config ?? { profiles: {} }), issues };
|
|
264
271
|
};
|
|
265
272
|
/**
|
|
@@ -4,3 +4,9 @@
|
|
|
4
4
|
* not hold stays, so a forward or a reply to an unsaved email is still found.
|
|
5
5
|
*/
|
|
6
6
|
export declare const mailCore: (body: string, earlier: string[]) => string;
|
|
7
|
+
/**
|
|
8
|
+
* What an email says itself, for a model that should not read earlier mail or the sender's signature: no quoted
|
|
9
|
+
* lines, nothing after a reply or Outlook header, no signature or mobile footer. A forwarded message is kept whole,
|
|
10
|
+
* since it is what was sent. When nothing would be left — an email that is only a quote — the body stays as it is.
|
|
11
|
+
*/
|
|
12
|
+
export declare const mailOwnText: (body: string) => string;
|