@wirecat/cli-core 0.405.0 → 0.406.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 +24 -1
- package/dist/botcheck/reasons.d.ts +1 -1
- package/dist/cli/analysis-choice.js +1 -1
- package/dist/cli/answer-entity.d.ts +5 -0
- package/dist/cli/answer-entity.js +25 -0
- package/dist/cli/command-contract.js +2 -5
- package/dist/cli/commands-command.js +4 -1
- package/dist/cli/effects.js +1 -1
- package/dist/cli/embedding-choice.d.ts +1 -1
- package/dist/cli/embedding-choice.js +2 -2
- package/dist/cli/help/command-help.js +2 -2
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-fields.js +2 -2
- package/dist/cli/meeting-project-candidates-command.js +0 -1
- package/dist/cli/meeting-series-inventory-command.js +0 -1
- package/dist/cli/messenger/admin-commands.js +14 -14
- package/dist/cli/messenger/archive-commands.js +2 -2
- package/dist/cli/messenger/attachments-command.js +7 -8
- package/dist/cli/messenger/backfill-command.js +1 -1
- package/dist/cli/messenger/chat-answers.js +3 -3
- package/dist/cli/messenger/chat-lists.js +3 -3
- package/dist/cli/messenger/chats-command.js +8 -8
- package/dist/cli/messenger/chats-members-command.js +1 -1
- package/dist/cli/messenger/contacts-command.js +10 -7
- package/dist/cli/messenger/context.d.ts +11 -4
- package/dist/cli/messenger/context.js +6 -0
- package/dist/cli/messenger/conversation-examples.js +7 -6
- package/dist/cli/messenger/conversations-command.d.ts +0 -4
- package/dist/cli/messenger/conversations-command.js +130 -135
- package/dist/cli/messenger/counters-command.js +4 -2
- package/dist/cli/messenger/doctor-command.js +8 -4
- package/dist/cli/messenger/gaps-command.js +2 -2
- package/dist/cli/messenger/hearing-command.d.ts +1 -1
- package/dist/cli/messenger/hearing-command.js +1 -1
- package/dist/cli/messenger/images-command.js +1 -1
- package/dist/cli/messenger/inbox.js +1 -3
- package/dist/cli/messenger/lacks.d.ts +1 -0
- package/dist/cli/messenger/lacks.js +1 -1
- package/dist/cli/messenger/mcp-command.js +4 -4
- package/dist/cli/messenger/messages-comments-command.js +10 -5
- package/dist/cli/messenger/metadata-command.js +5 -5
- package/dist/cli/messenger/models-command.js +101 -90
- package/dist/cli/messenger/private-people-command.js +5 -6
- package/dist/cli/messenger/replies-edit-command.js +0 -1
- package/dist/cli/messenger/review.js +1 -3
- package/dist/cli/messenger/serve-relay.d.ts +1 -0
- package/dist/cli/messenger/stats-command.js +1 -1
- package/dist/cli/messenger/store-maintenance-command.d.ts +2 -2
- package/dist/cli/messenger/store-maintenance-command.js +4 -4
- package/dist/cli/messenger/stored.js +5 -0
- package/dist/cli/messenger/tags-command.js +4 -10
- package/dist/cli/messenger/tasks-command.js +3 -3
- package/dist/cli/messenger/transcribe-command.js +1 -1
- package/dist/cli/moved.d.ts +2 -0
- package/dist/cli/moved.js +39 -1
- package/dist/cli/permission-keys.js +2 -0
- package/dist/cli/settings.js +1 -1
- package/dist/cli/steps.js +4 -4
- package/dist/cli/store-settings.js +1 -1
- package/dist/cli/upgrade-command.js +0 -1
- package/dist/domain/models.d.ts +3 -3
- package/dist/domain/task-categories.js +1 -1
- package/dist/embeddings/remote.js +1 -1
- package/dist/entities/member.d.ts +2 -2
- package/dist/entities/member.js +2 -2
- package/dist/mcp/agent.d.ts +1 -0
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/tools/attachments.js +1 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/ports/app.d.ts +1 -1
- package/dist/ports/messenger.d.ts +6 -1
- package/dist/search/task-similarity.js +1 -1
- package/dist/sends/guard.js +2 -2
- package/dist/sends/permission-aliases.d.ts +5 -0
- package/dist/sends/permission-aliases.js +49 -0
- package/dist/sends/permissions.js +33 -25
- package/dist/services/admin.js +7 -7
- package/dist/services/chats.js +1 -1
- package/dist/services/conversations.d.ts +1 -1
- package/dist/services/conversations.js +1 -1
- package/dist/services/counters.js +1 -1
- package/dist/services/embeddings-maintenance.js +2 -2
- package/dist/services/embeddings-search.js +1 -1
- package/dist/services/embeddings.d.ts +4 -4
- package/dist/services/messages-search.d.ts +1 -1
- package/dist/services/messages-search.js +2 -2
- package/dist/services/metadata.js +2 -2
- package/dist/services/notes-search.js +3 -3
- package/dist/services/search-catchup.js +1 -1
- package/dist/speech/install.d.ts +4 -2
- package/dist/speech/install.js +16 -4
- package/dist/speech/local.js +1 -1
- package/dist/speech/transcribe.js +1 -1
- package/dist/store/search-index.d.ts +1 -1
- package/dist/store/sqlite/attachment-texts.d.ts +1 -1
- package/dist/store/sqlite/attachment-texts.js +1 -1
- package/dist/store/sqlite/batches.js +1 -1
- package/dist/store/sqlite/involvements.d.ts +1 -1
- package/dist/store/sqlite/note-index.d.ts +2 -2
- package/dist/store/sqlite/note-index.js +2 -2
- package/dist/store/sqlite/search-index.d.ts +1 -1
- package/dist/store/sqlite/search-index.js +1 -1
- package/dist/store/sqlite/stems-fill.d.ts +3 -3
- package/dist/store/sqlite/stems-fill.js +3 -3
- package/dist/store/sqlite/stems.d.ts +1 -1
- package/dist/store/sqlite/task-pairs.d.ts +1 -1
- package/dist/store/sqlite/task-pairs.js +1 -1
- package/package.json +1 -1
- package/parity.json +130 -139
- package/skills/follow-ups/SKILL.md +5 -5
- package/skills/link-conversations/SKILL.md +8 -8
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
4
|
-
import {
|
|
4
|
+
import { defineQuery } from "@wirecat/cli-base/query";
|
|
5
|
+
import { Command } from "commander";
|
|
5
6
|
import { isTextModelInstalled, textModelsDirectory } from "../../embeddings/embed.js";
|
|
6
7
|
import { embeddingKeys } from "../../embeddings/keys.js";
|
|
7
8
|
import { DEFAULT_TEXT_MODEL, TEXT_MODELS } from "../../embeddings/models.js";
|
|
@@ -15,10 +16,19 @@ import { readSecret } from "../../terminal/prompt.js";
|
|
|
15
16
|
import { baseContext, environmentOf } from "../context.js";
|
|
16
17
|
import { FETCH, grantsAuthority, LOCAL_READ, LOCAL_WRITE, reachesOtherService } from "../effects.js";
|
|
17
18
|
import { itemOf, listOf, renderAnswer } from "../envelope.js";
|
|
18
|
-
import { listQuery, typedQuery, unfilteredQuery, withQuery } from "../list-query.js";
|
|
19
|
+
import { listQuery, nativeTerms, termField, typedQuery, unfilteredQuery, withQuery } from "../list-query.js";
|
|
19
20
|
import { nextStep } from "../steps.js";
|
|
20
21
|
/** A model list is the catalog this version ships. */
|
|
21
22
|
const MODELS_QUERY = unfilteredQuery("model", modelEntity);
|
|
23
|
+
const ROOT_MODELS_QUERY = defineQuery({
|
|
24
|
+
entity: "model",
|
|
25
|
+
backend: "native",
|
|
26
|
+
fields: [
|
|
27
|
+
termField("kind", "enum", "kind:audio", { values: ["audio", "text"] }),
|
|
28
|
+
termField("id", "kw", "id:e5-small"),
|
|
29
|
+
termField("downloaded", "bool", "downloaded:true"),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
22
32
|
/** Every model this version knows: a short, fixed list, never paged. */
|
|
23
33
|
const everyModel = (command, items, next = []) => listOf(command, { items, limit: Math.max(1, items.length), has_more: false, next });
|
|
24
34
|
const audioItem = (model, directory, chosen) => ({
|
|
@@ -56,97 +66,47 @@ const imageItem = (model, directory) => ({
|
|
|
56
66
|
const downloaded = (body) => "item" in body ? `${body.item.id}: downloaded and works, in ${body.item.path}\n` : "";
|
|
57
67
|
const modelLine = (item, width) => `${item.default ? "*" : " "} ${item.id.padEnd(width)} ${megabytes(item.size_bytes).padStart(7)} ` +
|
|
58
68
|
(item.downloaded ? "downloaded" : "—".padEnd(10));
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!isInstalled(model, directory)) {
|
|
91
|
-
context.renderer.note(`${model.id}: ${megabytes(installedBytes(model) + VAD.bytes)} from Hugging Face and GitHub`);
|
|
92
|
-
await install(model, directory, { progress: (line) => context.renderer.note(line) });
|
|
93
|
-
}
|
|
94
|
-
// Loading is the test — the files, the WebAssembly engine and the memory it needs — on a
|
|
95
|
-
// second of silence, since no recording ships with the package.
|
|
96
|
-
context.renderer.note(`${model.id}: loading it once to check it works`);
|
|
97
|
-
const { openRecognizer, SAMPLE_RATE } = await import("../../speech/recognize.js");
|
|
98
|
-
const recognizer = openRecognizer(model, modelPath(directory, model), vadPath(directory));
|
|
99
|
-
try {
|
|
100
|
-
recognizer.recognize(new Float32Array(SAMPLE_RATE));
|
|
101
|
-
}
|
|
102
|
-
finally {
|
|
103
|
-
recognizer.free();
|
|
104
|
-
}
|
|
105
|
-
const chosen = choose(messenger, context.settings, {}, context.env).model.id;
|
|
106
|
-
renderAnswer(this, context, itemOf(audioItem(model, directory, chosen)), downloaded);
|
|
107
|
-
}, { unbounded: true });
|
|
108
|
-
});
|
|
109
|
-
const text = models.command("text").description("embedding models for searching conversations by meaning");
|
|
110
|
-
withQuery(annotate(text.command("list"), { effects: LOCAL_READ, examples: [`${tool} models text list`] }), MODELS_QUERY)
|
|
111
|
-
.summary("embedding models and their download status")
|
|
112
|
-
.description("the embedding models, most suitable first, which are downloaded, and which one is the default")
|
|
113
|
-
.action(async function (words) {
|
|
114
|
-
const context = baseContext(this, messenger.resolveSettings);
|
|
115
|
-
await context.run(async () => {
|
|
116
|
-
listQuery(typedQuery(words), MODELS_QUERY, "models text list");
|
|
117
|
-
const directory = textModelsDirectory(context.env);
|
|
118
|
-
const items = TEXT_MODELS.map((model) => textItem(model, directory));
|
|
119
|
-
const missing = items.find((item) => item.default && !item.downloaded);
|
|
120
|
-
renderAnswer(this, context, everyModel("models text list", items, missing ? [nextStep("models.download", { args: { model: missing.id } })] : []), () => items.map((item) => `${modelLine(item, 16)} ${item.licence}\n`).join(""));
|
|
121
|
-
if (context.format === "pretty")
|
|
122
|
-
context.renderer.note(`kept in ${directory}`);
|
|
69
|
+
const downloadAudioModel = async (command, messenger, id) => {
|
|
70
|
+
const context = baseContext(command, messenger.resolveSettings);
|
|
71
|
+
await context.run(async () => {
|
|
72
|
+
const model = speechModel(id);
|
|
73
|
+
const directory = modelsDirectory(context.env);
|
|
74
|
+
if (!isInstalled(model, directory)) {
|
|
75
|
+
context.renderer.note(`${model.id}: ${megabytes(installedBytes(model) + VAD.bytes)} from Hugging Face and GitHub`);
|
|
76
|
+
await install(model, directory, { progress: (line) => context.renderer.note(line) });
|
|
77
|
+
}
|
|
78
|
+
// Loading is the test — the files, the WebAssembly engine and the memory it needs — on a
|
|
79
|
+
// second of silence, since no recording ships with the package.
|
|
80
|
+
context.renderer.note(`${model.id}: loading it once to check it works`);
|
|
81
|
+
const { openRecognizer, SAMPLE_RATE } = await import("../../speech/recognize.js");
|
|
82
|
+
const recognizer = openRecognizer(model, modelPath(directory, model), vadPath(directory));
|
|
83
|
+
try {
|
|
84
|
+
recognizer.recognize(new Float32Array(SAMPLE_RATE));
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
recognizer.free();
|
|
88
|
+
}
|
|
89
|
+
const chosen = choose(messenger, context.settings, {}, context.env).model.id;
|
|
90
|
+
renderAnswer(command, context, itemOf(audioItem(model, directory, chosen)), downloaded);
|
|
91
|
+
}, { unbounded: true });
|
|
92
|
+
};
|
|
93
|
+
const downloadEmbeddingModel = async (command, messenger, id, options) => {
|
|
94
|
+
const context = baseContext(command, messenger.resolveSettings);
|
|
95
|
+
await context.run(async () => {
|
|
96
|
+
const { model, directory } = await downloadTextModel(id, {
|
|
97
|
+
env: context.env,
|
|
98
|
+
acceptTerms: options.acceptTerms === true,
|
|
99
|
+
progress: (line) => context.renderer.note(line),
|
|
123
100
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}))
|
|
130
|
-
.argument("<model>", "a model id from `models text list`")
|
|
131
|
-
.addOption(new Option("--accept-terms", "accept the model's licence terms, for a model that has its own"))
|
|
132
|
-
.summary("download and verify an embedding model")
|
|
133
|
-
.description("download an embedding model once, checked against the sha256 this version expects")
|
|
134
|
-
.action(async function (id, options) {
|
|
135
|
-
const context = baseContext(this, messenger.resolveSettings);
|
|
136
|
-
await context.run(async () => {
|
|
137
|
-
const { model, directory } = await downloadTextModel(id, {
|
|
138
|
-
env: context.env,
|
|
139
|
-
acceptTerms: options.acceptTerms === true,
|
|
140
|
-
progress: (line) => context.renderer.note(line),
|
|
141
|
-
});
|
|
142
|
-
renderAnswer(this, context, itemOf(textItem(model, directory)), downloaded);
|
|
143
|
-
}, { unbounded: true });
|
|
144
|
-
});
|
|
145
|
-
const key = text.command("key").description("API keys for embedding and analysis providers");
|
|
101
|
+
renderAnswer(command, context, itemOf(textItem(model, directory)), downloaded);
|
|
102
|
+
}, { unbounded: true });
|
|
103
|
+
};
|
|
104
|
+
const modelKeysCommand = (messenger, tool) => {
|
|
105
|
+
const key = new Command("keys").description("API keys for embedding and analysis providers");
|
|
146
106
|
grantsAuthority(annotate(key.command("set"), {
|
|
147
107
|
effects: LOCAL_WRITE,
|
|
148
108
|
output: { entity: "model_key", view: "stored" },
|
|
149
|
-
examples: [`${tool} models
|
|
109
|
+
examples: [`${tool} models keys set openai`],
|
|
150
110
|
}))
|
|
151
111
|
.argument("<provider>", "openai, anthropic, or the host of a --base-url server that wants a key")
|
|
152
112
|
.description("store a key, typed at a hidden prompt or piped on stdin — never as an argument")
|
|
@@ -164,7 +124,7 @@ export const modelsCommand = (messenger) => {
|
|
|
164
124
|
grantsAuthority(annotate(key.command("remove"), {
|
|
165
125
|
effects: LOCAL_WRITE,
|
|
166
126
|
output: { entity: "model_key", view: "removed" },
|
|
167
|
-
examples: [`${tool} models
|
|
127
|
+
examples: [`${tool} models keys remove openai`],
|
|
168
128
|
}))
|
|
169
129
|
.argument("<provider>", "openai, anthropic, or a server's host")
|
|
170
130
|
.description("forget a stored key")
|
|
@@ -174,6 +134,57 @@ export const modelsCommand = (messenger) => {
|
|
|
174
134
|
renderAnswer(this, context, itemOf({ provider, removed: embeddingKeys(messenger.app, context.env, context.keyring).remove(provider) }));
|
|
175
135
|
});
|
|
176
136
|
});
|
|
137
|
+
return key;
|
|
138
|
+
};
|
|
139
|
+
export const modelsCommand = (messenger) => {
|
|
140
|
+
const tool = `${messenger.app.command} personal`;
|
|
141
|
+
const models = new Command("models").description("models that run on this machine");
|
|
142
|
+
models.addCommand(modelKeysCommand(messenger, tool));
|
|
143
|
+
withQuery(annotate(models.command("list"), {
|
|
144
|
+
effects: LOCAL_READ,
|
|
145
|
+
examples: [`${tool} models list`, `${tool} models list kind:audio`],
|
|
146
|
+
}), ROOT_MODELS_QUERY)
|
|
147
|
+
.description("speech and embedding models, their download status and the default of each kind")
|
|
148
|
+
.action(async function (words) {
|
|
149
|
+
const terms = nativeTerms(listQuery(typedQuery(words), ROOT_MODELS_QUERY, "models list"), "models list");
|
|
150
|
+
const context = baseContext(this, messenger.resolveSettings);
|
|
151
|
+
await context.run(async () => {
|
|
152
|
+
const kinds = terms.get("kind")?.value;
|
|
153
|
+
const id = terms.get("id")?.value;
|
|
154
|
+
const kept = terms.get("downloaded")?.value;
|
|
155
|
+
const audioModels = kinds === undefined || kinds === "audio"
|
|
156
|
+
? orderedModels(messenger.speechModels).filter((model) => id === undefined || model.id === id)
|
|
157
|
+
: [];
|
|
158
|
+
const textModels = kinds === undefined || kinds === "text"
|
|
159
|
+
? TEXT_MODELS.filter((model) => id === undefined || model.id === id)
|
|
160
|
+
: [];
|
|
161
|
+
const chosen = audioModels.length > 0 ? choose(messenger, context.settings, {}, context.env).model.id : "";
|
|
162
|
+
const catalogue = [
|
|
163
|
+
...audioModels.map((model) => audioItem(model, modelsDirectory(context.env), chosen)),
|
|
164
|
+
...textModels.map((model) => textItem(model, textModelsDirectory(context.env))),
|
|
165
|
+
];
|
|
166
|
+
const items = catalogue.filter((item) => kept === undefined || item.downloaded === (kept === "true"));
|
|
167
|
+
const next = items
|
|
168
|
+
.filter((item) => item.default && !item.downloaded)
|
|
169
|
+
.map((item) => nextStep("models.download", { args: { model: item.id } }));
|
|
170
|
+
renderAnswer(this, context, everyModel("models list", items, next), () => items.map((item) => `${item.kind.padEnd(5)} ${modelLine(item, 18)} ${item.languages}\n`).join(""));
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
reachesOtherService(annotate(models.command("download"), {
|
|
174
|
+
effects: FETCH,
|
|
175
|
+
output: { entity: "model" },
|
|
176
|
+
examples: [`${tool} models download parakeet-v3`, `${tool} models download ${DEFAULT_TEXT_MODEL}`],
|
|
177
|
+
}))
|
|
178
|
+
.description("download and verify one speech or embedding model")
|
|
179
|
+
.argument("<model>", "a model id from models list")
|
|
180
|
+
.option("--accept-terms", "accept the model's licence terms, for a model that has its own")
|
|
181
|
+
.action(async function (id, options) {
|
|
182
|
+
if (orderedModels().some((model) => model.id === id))
|
|
183
|
+
return downloadAudioModel(this, messenger, id);
|
|
184
|
+
if (TEXT_MODELS.some((model) => model.id === id))
|
|
185
|
+
return downloadEmbeddingModel(this, messenger, id, options);
|
|
186
|
+
throw new CliError("validation_error", "unknown model; choose an id from models list");
|
|
187
|
+
});
|
|
177
188
|
return models;
|
|
178
189
|
};
|
|
179
190
|
/** `images models`: registered with picture search, the only thing these models serve. */
|
|
@@ -13,22 +13,22 @@ import { readAll } from "./stdin.js";
|
|
|
13
13
|
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
|
-
const alias = new Command("
|
|
16
|
+
const alias = new Command("aliases").description("a private local display name in the selected account");
|
|
17
17
|
annotate(alias.command("set"), { effects: LOCAL_WRITE, output: { entity: "contact_alias" } })
|
|
18
18
|
.description("your own name for a person in this account; kept on this machine, never sent")
|
|
19
19
|
.argument("<person>", "the person's id, reference or known name")
|
|
20
20
|
.argument("<alias>", "the private name to use for this person")
|
|
21
21
|
.action(async function (person, value) {
|
|
22
22
|
const context = messengerContext(this, messenger);
|
|
23
|
-
refuseLocalWrite(context, messenger.app.command, "contacts.
|
|
23
|
+
refuseLocalWrite(context, messenger.app.command, "contacts.aliases.set");
|
|
24
24
|
renderAnswer(this, context, itemOf(snakeKeys(await context.withServices((services) => services.privatePeople.alias(person, value)))));
|
|
25
25
|
});
|
|
26
|
-
annotate(alias.command("
|
|
26
|
+
annotate(alias.command("remove"), { effects: LOCAL_WRITE, output: { entity: "contact_alias" } })
|
|
27
27
|
.description("drop your local name for a person; their own name shows again")
|
|
28
28
|
.argument("<person>", "the person's id, reference or known name")
|
|
29
29
|
.action(async function (person) {
|
|
30
30
|
const context = messengerContext(this, messenger);
|
|
31
|
-
refuseLocalWrite(context, messenger.app.command, "contacts.
|
|
31
|
+
refuseLocalWrite(context, messenger.app.command, "contacts.aliases.remove");
|
|
32
32
|
renderAnswer(this, context, itemOf(snakeKeys(await context.withServices((services) => services.privatePeople.alias(person, null)))));
|
|
33
33
|
});
|
|
34
34
|
const notes = new Command("notes").description("your private notes on a stored contact, the same in every account that sees them");
|
|
@@ -57,8 +57,7 @@ export const privatePeopleCommands = (messenger) => {
|
|
|
57
57
|
? "add a private note on a person from stdin or --text-file; never sent"
|
|
58
58
|
: "replace a note's text; --revision must match the one you read")
|
|
59
59
|
.argument("<person>", "the person's id, reference or known name")
|
|
60
|
-
.addOption(ownerOnlyOption("--text-file <path>", "read note text from a file; omitted or - reads stdin")
|
|
61
|
-
.addOption(ownerOnlyOption("--file <path>", "compatibility alias for --text-file").hideHelp().conflicts("textFile"));
|
|
60
|
+
.addOption(ownerOnlyOption("--text-file <path>", "read note text from a file; omitted or - reads stdin"));
|
|
62
61
|
if (name === "edit")
|
|
63
62
|
command
|
|
64
63
|
.argument("<id>", "the note id from contacts notes list")
|
|
@@ -65,7 +65,6 @@ export const addReplyEditors = (replies, messenger) => {
|
|
|
65
65
|
option.conflicts("model");
|
|
66
66
|
edit.addOption(option);
|
|
67
67
|
}
|
|
68
|
-
edit.addOption(new Option("--model <mode>", "compatibility alias for --template-mode").hideHelp().conflicts("templateMode"));
|
|
69
68
|
// Commander gives negated-only flags a true default; omission must preserve the saved field.
|
|
70
69
|
edit.action(function (id) {
|
|
71
70
|
const options = this.opts();
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { annotate } from "@wirecat/cli-base/commands";
|
|
3
|
-
import { Command
|
|
3
|
+
import { Command } from "commander";
|
|
4
4
|
import { parseDuration } from "../../domain/duration.js";
|
|
5
5
|
import { renderMessages } from "../../render/messages.js";
|
|
6
|
-
import { CHAT_KINDS } from "../../services/chats.js";
|
|
7
6
|
import { kindsOf, REVIEW_DAYS, reviewStart, UNANSWERED_HOURS } from "../../services/inbox.js";
|
|
8
7
|
import { modelWith } from "../../speech/hearing.js";
|
|
9
8
|
import { REMOTE_READ } from "../effects.js";
|
|
@@ -34,7 +33,6 @@ export const reviewCommand = (messenger) => annotate(new Command("review"), {
|
|
|
34
33
|
.description("every message, yours too, in chats that changed since a point — for reviewing who owes what")
|
|
35
34
|
.addHelpText("after", queryHelp(REVIEW_QUERY))
|
|
36
35
|
.argument("[query...]", `where the last review ended and which chat: date>2h, chat:"Book club"; ${REVIEW_DAYS} days ago if no date`)
|
|
37
|
-
.addOption(new Option("--kind <kinds>", `compatibility spelling for kind:; ${CHAT_KINDS.join(", ")}`).hideHelp())
|
|
38
36
|
.option("--unanswered [duration]", `only questions to you or a group's admins that nobody answered, asked at least this long ago — 4h, 1d; ${UNANSWERED_HOURS}h if not given`)
|
|
39
37
|
.option("--all", "muted and archived chats too — left out unless they mention you or reply to you")
|
|
40
38
|
.option(...TRANSCRIBE_OPTION)
|
|
@@ -81,6 +81,7 @@ export declare const serveRelay: (command: Command, messenger: Messenger) => {
|
|
|
81
81
|
joinRequestsAll?: boolean;
|
|
82
82
|
joinRequestsByLink?: boolean;
|
|
83
83
|
channelComments?: boolean;
|
|
84
|
+
readsComments?: boolean;
|
|
84
85
|
addsWithHistory?: boolean;
|
|
85
86
|
adminRights?: readonly import("../../domain/models.js").AdminRight[];
|
|
86
87
|
pickAdminRights?: boolean;
|
|
@@ -93,7 +93,7 @@ export const statsCommand = (messenger, loadRenderer = chartRenderer) => {
|
|
|
93
93
|
"contacts evidence": '101 --selection "$selection" --component messages',
|
|
94
94
|
"contacts responses": 'chat:"Project room"',
|
|
95
95
|
"contacts top": 'chat:"Book club" --limit 10',
|
|
96
|
-
"messages counters
|
|
96
|
+
"messages counters sync": '--chat "Book club" --dry-run',
|
|
97
97
|
"messages counters show": '--chat "Book club" --limit 10',
|
|
98
98
|
"messages discussion": 'chat:"Book club"',
|
|
99
99
|
"messages evidence": '"$message" --selection "$selection" --component reactions',
|
|
@@ -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;
|
|
@@ -22,7 +22,7 @@ export declare const storeSummary: (env: NodeJS.ProcessEnv) => Promise<{
|
|
|
22
22
|
export declare const storeMaintenanceCommands: (messenger: Messenger) => Command[];
|
|
23
23
|
/**
|
|
24
24
|
* Stems what is left, after rebuilding stems made by other stemmer choices than the store's setting —
|
|
25
|
-
* only here and in `store
|
|
25
|
+
* only here and in `store rebuild`, never in a search's quick fill.
|
|
26
26
|
*/
|
|
27
27
|
export declare const buildStems: (database: CacheDatabase, note: (text: string) => void, { force }?: {
|
|
28
28
|
force?: boolean | undefined;
|
|
@@ -237,7 +237,7 @@ const checkCommand = (messenger) => annotate(new Command("check"), { output: { e
|
|
|
237
237
|
renderer.note(`${orphans} pointers name a row that is gone or a type the store does not know — see orphan_pointers`);
|
|
238
238
|
const older = answer.conversations.filter((chat) => chat.provider === messenger.provider && !chat.current).length;
|
|
239
239
|
if (older > 0) {
|
|
240
|
-
renderer.note(`${older} chats' conversations were built by older rules — \`${command} conversations
|
|
240
|
+
renderer.note(`${older} chats' conversations were built by older rules — \`${command} conversations links rebuild --chat <chat>\``);
|
|
241
241
|
}
|
|
242
242
|
});
|
|
243
243
|
const inspect = (path) => reading(path, (database) => {
|
|
@@ -407,7 +407,7 @@ const buildWordIndex = (database, note) => {
|
|
|
407
407
|
};
|
|
408
408
|
/**
|
|
409
409
|
* Stems what is left, after rebuilding stems made by other stemmer choices than the store's setting —
|
|
410
|
-
* only here and in `store
|
|
410
|
+
* only here and in `store rebuild`, never in a search's quick fill.
|
|
411
411
|
*/
|
|
412
412
|
export const buildStems = (database, note, { force = false } = {}) => {
|
|
413
413
|
const before = stemsState(database);
|
|
@@ -422,7 +422,7 @@ export const buildStems = (database, note, { force = false } = {}) => {
|
|
|
422
422
|
const { stemmed, drained } = fillStems(database, { onBatch: (step, done) => note(`${done} ${step}`) });
|
|
423
423
|
return { stemmed: stemmed + drained };
|
|
424
424
|
};
|
|
425
|
-
/** Indexes the notes written since — all of them after `store
|
|
425
|
+
/** Indexes the notes written since — all of them after `store rebuild` queued them again. */
|
|
426
426
|
const buildNoteIndex = (database, note, { reset = false } = {}) => {
|
|
427
427
|
if (reset)
|
|
428
428
|
resetNoteIndex(database);
|
|
@@ -431,7 +431,7 @@ const buildNoteIndex = (database, note, { reset = false } = {}) => {
|
|
|
431
431
|
note(`indexing ${pending} notes`);
|
|
432
432
|
return { notesIndexed: drainNoteIndex(database, stemmerCache()) };
|
|
433
433
|
};
|
|
434
|
-
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("
|
|
434
|
+
const reindexCommand = (messenger) => destroysNothing(annotate(new Command("rebuild"), { output: { entity: "store_reindex" }, effects: LOCAL_WRITE }))
|
|
435
435
|
.summary("rebuild the local archive's search indexes")
|
|
436
436
|
.description("rebuild the word index, its typo vocabulary, the stems, the files' word index, the notes' indexes and who took part in what, from what is stored; loses nothing")
|
|
437
437
|
.action(async function () {
|
|
@@ -534,6 +534,11 @@ const fill = async (store, account, batch) => {
|
|
|
534
534
|
of(chatId).stars.push({ messageId, starred });
|
|
535
535
|
for (const [chatId, { messages, votes, reactions, stars }] of byChat)
|
|
536
536
|
await store.messages.saveMessages(account, chatId, messages, { via: "history", votes, reactions, stars });
|
|
537
|
+
const deletions = new Map();
|
|
538
|
+
for (const { chatId, messageId } of batch.deletions ?? [])
|
|
539
|
+
deletions.set(chatId, [...(deletions.get(chatId) ?? []), messageId]);
|
|
540
|
+
for (const [chatId, ids] of deletions)
|
|
541
|
+
await store.messages.markDeleted(account, ids, { chatId });
|
|
537
542
|
const changes = new Map();
|
|
538
543
|
for (const { chatId, change } of batch.events ?? [])
|
|
539
544
|
changes.set(chatId, [...(changes.get(chatId) ?? []), change]);
|
|
@@ -32,10 +32,10 @@ export const tagsCommand = (messenger) => {
|
|
|
32
32
|
const tags = new Command("tags")
|
|
33
33
|
.summary("tags on stored messages and other records")
|
|
34
34
|
.description("your own labels on chats, people, messages and tasks, kept in the local store and never sent; tag: in a search finds them");
|
|
35
|
-
changesStore(annotate(tags.command("
|
|
35
|
+
changesStore(annotate(tags.command("rebuild"), {
|
|
36
36
|
output: { entity: "automatic_tags" },
|
|
37
37
|
effects: FETCH,
|
|
38
|
-
examples: [`${tool} tags
|
|
38
|
+
examples: [`${tool} tags rebuild --chat "Book club" --dry-run`],
|
|
39
39
|
}))
|
|
40
40
|
.description("derive local group/channel tags from cached metadata using keyword rules")
|
|
41
41
|
.option("--chat <chat>", "a stored group/channel; repeat to select several", (value, previous) => [...previous, value], [])
|
|
@@ -46,7 +46,7 @@ export const tagsCommand = (messenger) => {
|
|
|
46
46
|
const context = messengerContext(this, messenger);
|
|
47
47
|
const { chat, limit, syncFirst, dryRun } = this.opts();
|
|
48
48
|
if (!dryRun)
|
|
49
|
-
refuseLocalWrite(context, messenger.app.command, "tags.
|
|
49
|
+
refuseLocalWrite(context, messenger.app.command, "tags.rebuild");
|
|
50
50
|
const done = await context.withServices((services) => services.metadata.auto({ chats: chat, limit: Number(limit), refresh: syncFirst, dryRun }));
|
|
51
51
|
renderAnswer(this, context, itemOf(snakeKeys(done)));
|
|
52
52
|
});
|
|
@@ -68,13 +68,7 @@ export const tagsCommand = (messenger) => {
|
|
|
68
68
|
effects: LOCAL_WRITE,
|
|
69
69
|
examples: [`${tool} tags remove reading --chat "Book club"`],
|
|
70
70
|
}).description("take tags off one chat, person, message or task"), messenger, "untag")
|
|
71
|
-
.addOption(new Option("--claim <claim>", "remove only this ownership claim: manual or auto")
|
|
72
|
-
.argParser(sourceOf)
|
|
73
|
-
.conflicts("source"))
|
|
74
|
-
.addOption(new Option("--source <manual|auto>", "compatibility alias for --claim")
|
|
75
|
-
.argParser(sourceOf)
|
|
76
|
-
.hideHelp()
|
|
77
|
-
.conflicts("claim"))
|
|
71
|
+
.addOption(new Option("--claim <claim>", "remove only this ownership claim: manual or auto").argParser(sourceOf))
|
|
78
72
|
.action(async function (given) {
|
|
79
73
|
const context = messengerContext(this, messenger);
|
|
80
74
|
refuseLocalWrite(context, messenger.app.command, "tags.remove");
|
|
@@ -185,17 +185,17 @@ export const tasksCommand = (messenger) => {
|
|
|
185
185
|
else if (followUpOf && context.format === "pretty")
|
|
186
186
|
context.renderer.note(`${task.key ?? task.id} follows up ${followUpOf.key} (${followUpOf.status}), which stays closed: linked follow-up-of`);
|
|
187
187
|
});
|
|
188
|
-
planOptions(destroysNothing(annotate(tasks.command("
|
|
188
|
+
planOptions(destroysNothing(annotate(tasks.command("update"), {
|
|
189
189
|
output: { entity: "task_card" },
|
|
190
190
|
effects: LOCAL_WRITE,
|
|
191
|
-
examples: [`${tool} personal tasks
|
|
191
|
+
examples: [`${tool} personal tasks update DOC-12 --title "Review draft"`],
|
|
192
192
|
}))
|
|
193
193
|
.summary("set a task's title, description, due date, next check, category and assignee")
|
|
194
194
|
.description("set a task's title, description, due date, next check, category and assignee; nothing reaches the messenger")
|
|
195
195
|
.argument("<task>", "the task's id or key, as tasks list shows it")
|
|
196
196
|
.option("--category <name>", `${categoryHelp}; a change is kept in the task's history`), "send Alex the API token").action(async function (id) {
|
|
197
197
|
const context = messengerContext(this, messenger);
|
|
198
|
-
refuseLocalWrite(context, messenger.app.command, "tasks.
|
|
198
|
+
refuseLocalWrite(context, messenger.app.command, "tasks.update");
|
|
199
199
|
const plan = planFrom(this.opts());
|
|
200
200
|
if (Object.keys(plan).length === 0)
|
|
201
201
|
throw new CliError("validation_error", "give at least one of --title, --description, --due, --remind-time, --category");
|
|
@@ -18,7 +18,7 @@ export const transcribeSubcommand = (messages, messenger) => annotate(messages.c
|
|
|
18
18
|
.argument("<chat>", messenger.chatArgument)
|
|
19
19
|
.argument("<message>", "the id of a voice message")
|
|
20
20
|
.option("--local", "use the model on this machine, never the messenger")
|
|
21
|
-
.option("--model <model>", "which downloaded model; implies --local (`models
|
|
21
|
+
.option("--model <model>", "which downloaded model; implies --local (`models list`)")
|
|
22
22
|
.action(async function (chat, messageId) {
|
|
23
23
|
const context = messengerContext(this, messenger);
|
|
24
24
|
const choice = {
|
package/dist/cli/moved.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export declare const MOVED_TO_MEMO: Readonly<Record<string, {
|
|
|
7
7
|
what: string;
|
|
8
8
|
memo: string;
|
|
9
9
|
}>>;
|
|
10
|
+
/** Retired spellings are guidance only; none is mounted as a command alias. */
|
|
11
|
+
export declare const RENAMED_COMMANDS: Readonly<Record<string, string>>;
|
|
10
12
|
export declare const movedPointer: (path: string) => string | undefined;
|
|
11
13
|
/** The longest moved path the words start with, unless the program still has it. */
|
|
12
14
|
export declare const movedPath: (words: readonly string[], has: (path: string) => boolean) => string | undefined;
|
package/dist/cli/moved.js
CHANGED
|
@@ -6,12 +6,50 @@
|
|
|
6
6
|
export const MOVED_TO_MEMO = {
|
|
7
7
|
"search mail": { what: "mail is searched by the separate memo CLI", memo: "memo search mail …" },
|
|
8
8
|
};
|
|
9
|
+
/** Retired spellings are guidance only; none is mounted as a command alias. */
|
|
10
|
+
export const RENAMED_COMMANDS = {
|
|
11
|
+
"contacts alias rm": "contacts aliases remove",
|
|
12
|
+
"contacts alias": "contacts aliases",
|
|
13
|
+
"contacts profile": "contacts info",
|
|
14
|
+
"contacts timeline": "contacts events",
|
|
15
|
+
"chats link revoke": "chats links remove",
|
|
16
|
+
"chats link": "chats links",
|
|
17
|
+
"chats members audit": "chats members check",
|
|
18
|
+
"chats requests accept": "chats requests confirm",
|
|
19
|
+
"chats requests decline": "chats requests reject",
|
|
20
|
+
"metadata get": "metadata show",
|
|
21
|
+
"metadata refresh": "metadata sync",
|
|
22
|
+
"tasks plan": "tasks update",
|
|
23
|
+
"tags auto": "tags rebuild",
|
|
24
|
+
"attachments text set": "attachments update",
|
|
25
|
+
"conversations build": "conversations links rebuild",
|
|
26
|
+
"conversations embed status": "conversations embeddings status",
|
|
27
|
+
"conversations embed clear": "conversations embeddings clear",
|
|
28
|
+
"conversations batches next": "conversations batches show",
|
|
29
|
+
"conversations consents revoke": "conversations consents remove",
|
|
30
|
+
"doctor report create": "doctor reports create",
|
|
31
|
+
"doctor report": "doctor reports show",
|
|
32
|
+
"mcp setup": "mcp add",
|
|
33
|
+
"mcp doctor": "mcp check",
|
|
34
|
+
"commands schema": "commands info",
|
|
35
|
+
"store gaps plan": "store gaps show",
|
|
36
|
+
"store reindex": "store rebuild",
|
|
37
|
+
"stats messages counters refresh": "stats messages counters sync",
|
|
38
|
+
"models text key": "models keys",
|
|
39
|
+
"models audio list": "models list kind:audio",
|
|
40
|
+
"models text list": "models list kind:text",
|
|
41
|
+
"models audio download": "models download",
|
|
42
|
+
"models text download": "models download",
|
|
43
|
+
};
|
|
9
44
|
export const movedPointer = (path) => {
|
|
45
|
+
const renamed = RENAMED_COMMANDS[path];
|
|
46
|
+
if (renamed)
|
|
47
|
+
return `use \`${renamed}\``;
|
|
10
48
|
const moved = MOVED_TO_MEMO[path];
|
|
11
49
|
return moved && `${moved.what}: install @wirecat/cli-memo, then \`${moved.memo}\``;
|
|
12
50
|
};
|
|
13
51
|
/** The longest moved path the words start with, unless the program still has it. */
|
|
14
|
-
export const movedPath = (words, has) => Object.keys(MOVED_TO_MEMO)
|
|
52
|
+
export const movedPath = (words, has) => [...Object.keys(MOVED_TO_MEMO), ...Object.keys(RENAMED_COMMANDS)]
|
|
15
53
|
.filter((path) => path.split(" ").every((word, at) => words[at] === word))
|
|
16
54
|
.sort((a, b) => b.length - a.length)
|
|
17
55
|
.find((path) => !has(path));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalPermissionKey } from "../sends/permission-aliases.js";
|
|
1
2
|
import { CEILING_KEY, keyForCommand, WRITE_KEYS } from "../sends/permissions.js";
|
|
2
3
|
const pathOf = (command) => {
|
|
3
4
|
const words = [];
|
|
@@ -19,6 +20,7 @@ const everyCommand = (command) => [command, ...command.commands.flatMap(everyCom
|
|
|
19
20
|
export const knownPermissionKeys = (command, keyOf = (one) => keyForCommand(pathOf(one)), extra = []) => {
|
|
20
21
|
const known = new Set();
|
|
21
22
|
const add = (key) => {
|
|
23
|
+
key = canonicalPermissionKey(key);
|
|
22
24
|
for (let parts = key.split("."); parts.length > 0; parts = parts.slice(0, -1))
|
|
23
25
|
known.add(parts.join("."));
|
|
24
26
|
};
|
package/dist/cli/settings.js
CHANGED
|
@@ -60,7 +60,7 @@ const SHARED_PROFILE_ENTRIES = {
|
|
|
60
60
|
sends_per_hour: v.optional(count),
|
|
61
61
|
requests_per_minute: v.optional(countOrZero),
|
|
62
62
|
transcribe_with: v.optional(v.picklist(["auto", "messenger", "local"], plain("has to be auto, messenger or local"))),
|
|
63
|
-
speech_model: v.optional(v.string(plain("has to be a model id from `models
|
|
63
|
+
speech_model: v.optional(v.string(plain("has to be a model id from `models list`, in quotes"))),
|
|
64
64
|
search_catch_up: v.optional(flag),
|
|
65
65
|
picture_search: v.optional(flag),
|
|
66
66
|
...AI_ENTRIES,
|
package/dist/cli/steps.js
CHANGED
|
@@ -83,7 +83,7 @@ export const STEPS = defineSteps([
|
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
id: "models.download",
|
|
86
|
-
command: "models
|
|
86
|
+
command: "models download",
|
|
87
87
|
args: [{ name: "model", required: true }],
|
|
88
88
|
why: "{model} is not downloaded, so nothing is searched by meaning",
|
|
89
89
|
},
|
|
@@ -116,7 +116,7 @@ export const STEPS = defineSteps([
|
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
id: "store.reindex",
|
|
119
|
-
command: "store
|
|
119
|
+
command: "store rebuild",
|
|
120
120
|
args: [],
|
|
121
121
|
why: "the stems were built by another stemmer; this builds them again",
|
|
122
122
|
},
|
|
@@ -134,7 +134,7 @@ export const STEPS = defineSteps([
|
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
id: "conversations.build",
|
|
137
|
-
command: "conversations
|
|
137
|
+
command: "conversations links rebuild",
|
|
138
138
|
args: [{ name: "chat", required: false }],
|
|
139
139
|
why: "conversations are missing or older than the messages; this builds them",
|
|
140
140
|
},
|
|
@@ -175,7 +175,7 @@ export const STEPS = defineSteps([
|
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
id: "doctor.report-create",
|
|
178
|
-
command: "doctor
|
|
178
|
+
command: "doctor reports create",
|
|
179
179
|
args: [{ name: "run", required: false }],
|
|
180
180
|
why: "writes the report to a file to read and send; it holds no message text",
|
|
181
181
|
},
|
|
@@ -132,7 +132,7 @@ const changeCategory = async (app, env, setting, value) => {
|
|
|
132
132
|
return {
|
|
133
133
|
result: { store: storePath(env), file, scope: "shared", setting, value: set ? taskCategoryValue(set) : null },
|
|
134
134
|
note: [
|
|
135
|
-
`saved in ${file} — rules, tasks add and tasks
|
|
135
|
+
`saved in ${file} — rules, tasks add and tasks update in tg, MAX and memo take categories from it; ` +
|
|
136
136
|
"tasks keep the category they have",
|
|
137
137
|
...unlisted,
|
|
138
138
|
].join("\n"),
|
|
@@ -32,7 +32,6 @@ export const restartServers = (app, script, env, spawn) => {
|
|
|
32
32
|
};
|
|
33
33
|
export const upgradeCommand = (app, packageName, contextFor) => grantsAuthority(annotate(new Command("upgrade"), {
|
|
34
34
|
effects: LOCAL_WRITE,
|
|
35
|
-
mcp: false,
|
|
36
35
|
offline: false,
|
|
37
36
|
examples: [`${app.command} upgrade --check`, `${app.command} upgrade`],
|
|
38
37
|
}), { unless: "check" })
|