@wirecat/cli-core 0.407.0 → 0.409.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 +22 -0
- package/dist/background/schedules.d.ts +1 -1
- package/dist/background/schedules.js +3 -1
- package/dist/cli/bot/admins.js +5 -3
- package/dist/cli/bot/command.js +21 -11
- package/dist/cli/bot/list-queries.d.ts +27 -0
- package/dist/cli/bot/list-queries.js +83 -0
- package/dist/cli/bot/messages.js +14 -8
- package/dist/cli/bot/registry.d.ts +1 -1
- package/dist/cli/bot/registry.js +4 -2
- package/dist/cli/bot/setup.js +8 -4
- package/dist/cli/command-availability.d.ts +14 -0
- package/dist/cli/command-availability.js +25 -0
- package/dist/cli/commands-command.d.ts +1 -1
- package/dist/cli/commands-command.js +17 -4
- package/dist/cli/context.d.ts +1 -0
- package/dist/cli/effects.d.ts +1 -1
- package/dist/cli/effects.js +1 -1
- package/dist/cli/help/command-help.js +18 -1
- package/dist/cli/help/commands-text.d.ts +1 -1
- package/dist/cli/help/commands-text.js +3 -2
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/messenger/list-result-queries.d.ts +9 -0
- package/dist/cli/messenger/list-result-queries.js +30 -0
- package/dist/cli/messenger/recipients-query.d.ts +1 -1
- package/dist/cli/messenger/recipients-query.js +2 -2
- package/dist/cli/messenger/records-command.js +14 -4
- package/dist/cli/messenger/schedule-command.js +6 -3
- package/dist/cli/messenger/sends-query.d.ts +1 -1
- package/dist/cli/messenger/sends-query.js +2 -2
- package/dist/cli/messenger/topics-command.js +10 -6
- package/dist/cli/program.d.ts +2 -1
- package/dist/cli/program.js +24 -1
- package/dist/entities/index.d.ts +84 -1
- package/dist/entities/index.js +11 -1
- package/dist/entities/list-results.d.ts +83 -0
- package/dist/entities/list-results.js +69 -0
- package/dist/mcp/agent.d.ts +11 -11
- package/dist/mcp/agent.js +13 -27
- package/dist/mcp/bot/instructions.js +2 -1
- package/dist/mcp/bot/server.d.ts +2 -2
- package/dist/mcp/bot/server.js +29 -7
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/instructions.js +2 -1
- package/dist/mcp/server.js +33 -7
- package/dist/mcp/surface.d.ts +3 -1
- package/dist/mcp/surface.js +8 -2
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
+
## 0.409.0 — 24.10.2026
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Shared lists declare typed queries, including bots, schedules, forum topics, calls and stickers.** Stored bot chats, recipients, send history,
|
|
13
|
+
profiles and schedules filter at their native readers; forum title terms go to the provider. Message predicates
|
|
14
|
+
require `--offline` and run in SQLite before the limit, reporting interpreted time bounds. Provider lists that
|
|
15
|
+
accept no filters refuse terms before connecting, instead of filtering an incomplete response. Existing empty
|
|
16
|
+
queries keep their previous behavior. Shared list-result contracts include the template schema for provider CLIs.
|
|
17
|
+
|
|
18
|
+
## 0.408.0 — 24.10.2026
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **MCP discovery retains permission-denied commands.** Full listings and searches return each command's
|
|
23
|
+
current `availability`, including a reason and permission key when blocked. With a named profile,
|
|
24
|
+
CLI help and command discovery also show current availability without hiding commands. Read-only profiles and process
|
|
25
|
+
locks keep writing commands discoverable. Calls still recheck permissions and refuse before execution;
|
|
26
|
+
seeing a command does not grant access. Bot MCP also lists bot-scoped `config show|set|unset` behind the
|
|
27
|
+
existing authority rules, without exposing options that could select personal-account settings. Consumers
|
|
28
|
+
that pin MCP catalogues should refresh them and use availability rather than absence to determine access.
|
|
29
|
+
|
|
8
30
|
## 0.407.0 — 24.10.2026
|
|
9
31
|
|
|
10
32
|
### Added
|
|
@@ -75,7 +75,7 @@ export declare const scheduledRuns: (state: string) => {
|
|
|
75
75
|
job: string;
|
|
76
76
|
last: LastRun | undefined;
|
|
77
77
|
}[];
|
|
78
|
-
export declare const listSchedules: (state: string) => Schedule[];
|
|
78
|
+
export declare const listSchedules: (state: string, filter?: Partial<Pick<Schedule, "name" | "kind">>) => Schedule[];
|
|
79
79
|
export declare const describeWhen: (when: When) => string;
|
|
80
80
|
/** How one operating system starts `argv` at the schedule's times. Every timer runs the same thing: `<cli> schedule exec <file>`. */
|
|
81
81
|
export interface Timer {
|
|
@@ -98,11 +98,13 @@ export const scheduledRuns = (state) => {
|
|
|
98
98
|
return [];
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
|
-
export const listSchedules = (state) => existsSync(schedulesDir(state))
|
|
101
|
+
export const listSchedules = (state, filter = {}) => existsSync(schedulesDir(state))
|
|
102
102
|
? readdirSync(schedulesDir(state))
|
|
103
103
|
.map((name) => scheduleFile(state, name))
|
|
104
104
|
.filter((file) => existsSync(file))
|
|
105
105
|
.map(readSchedule)
|
|
106
|
+
.filter((schedule) => (filter.name === undefined || schedule.name === filter.name) &&
|
|
107
|
+
(filter.kind === undefined || schedule.kind === filter.kind))
|
|
106
108
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
107
109
|
: [];
|
|
108
110
|
export const describeWhen = (when) => "every" in when
|
package/dist/cli/bot/admins.js
CHANGED
|
@@ -6,10 +6,12 @@ import { guardedWrite } from "../../sends/guarded.js";
|
|
|
6
6
|
import { newOperationId } from "../../sends/send-id.js";
|
|
7
7
|
import { REMOTE_READ, REMOTE_WRITE } from "../effects.js";
|
|
8
8
|
import { renderAnswer, snakeKeys } from "../envelope.js";
|
|
9
|
+
import { listQuery, typedQuery, withQuery } from "../list-query.js";
|
|
9
10
|
import { rightsOf } from "../messenger/admin-commands.js";
|
|
10
11
|
import { renderItem, wholeList } from "../messenger/chat-answers.js";
|
|
11
12
|
import { botContext, online } from "./context.js";
|
|
12
13
|
import { botExamples } from "./examples.js";
|
|
14
|
+
import { BOT_ADMINS_QUERY } from "./list-queries.js";
|
|
13
15
|
import { botCan } from "./messages.js";
|
|
14
16
|
const CHAT = "a chat id, or the title of a chat this bot has seen";
|
|
15
17
|
const PERSON = "the person's user id";
|
|
@@ -24,10 +26,10 @@ const personOf = (typed) => {
|
|
|
24
26
|
export const botAdminsCommand = (bot) => {
|
|
25
27
|
const offered = bot.adminRights ?? ADMIN_RIGHTS;
|
|
26
28
|
const command = new Command("admins").description("the admins of a chat the bot is an admin in");
|
|
27
|
-
annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "chats admins list Team") })
|
|
29
|
+
withQuery(annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "chats admins list Team") })
|
|
28
30
|
.description("the chat's admins and what each may do")
|
|
29
|
-
.argument("<chat>", CHAT)
|
|
30
|
-
|
|
31
|
+
.argument("<chat>", CHAT), BOT_ADMINS_QUERY).action(async function (chat, words) {
|
|
32
|
+
listQuery(typedQuery(words), BOT_ADMINS_QUERY, "bot chats admins list");
|
|
31
33
|
const context = online(botContext(this, bot), this);
|
|
32
34
|
const ref = context.chatRef(chat);
|
|
33
35
|
await context.run(async (events) => {
|
package/dist/cli/bot/command.js
CHANGED
|
@@ -6,10 +6,14 @@ import { guardedWrite } from "../../sends/guarded.js";
|
|
|
6
6
|
import { newOperationId } from "../../sends/send-id.js";
|
|
7
7
|
import { grantsAuthority, LOCAL_READ, LOCAL_WRITE, REMOTE_READ, REMOTE_WRITE } from "../effects.js";
|
|
8
8
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
9
|
+
import { withQuery } from "../list-query.js";
|
|
9
10
|
import { renderItem, wholeList } from "../messenger/chat-answers.js";
|
|
11
|
+
import { RECIPIENTS_QUERY, recipientFilter } from "../messenger/recipients-query.js";
|
|
12
|
+
import { SENDS_QUERY, sendFilter } from "../messenger/sends-query.js";
|
|
10
13
|
import { botAdminsCommand, botMembersCommand } from "./admins.js";
|
|
11
14
|
import { botContext, online } from "./context.js";
|
|
12
15
|
import { botExamples } from "./examples.js";
|
|
16
|
+
import { BOT_CHATS_QUERY, BOT_PROFILES_QUERY, botChatFilter, botProfileName } from "./list-queries.js";
|
|
13
17
|
import { botMcpCommand } from "./mcp.js";
|
|
14
18
|
import { botCan, botIdOf, botMessagesCommand } from "./messages.js";
|
|
15
19
|
import { botModerateCommand, botRulesCommand } from "./moderation.js";
|
|
@@ -114,11 +118,12 @@ const meCommand = (bot) => annotate(new Command("me"), { effects: REMOTE_READ, e
|
|
|
114
118
|
}
|
|
115
119
|
});
|
|
116
120
|
});
|
|
117
|
-
const listCommand = (bot, tokenVariable) => annotate(new Command("list"), { effects: LOCAL_READ, examples: botExamples(bot, "list", "list --check") })
|
|
121
|
+
const listCommand = (bot, tokenVariable) => withQuery(annotate(new Command("list"), { effects: LOCAL_READ, examples: botExamples(bot, "list", "list --check") }), BOT_PROFILES_QUERY)
|
|
118
122
|
.description(`every name on this machine that has a bot token; --check asks ${bot.name ?? "the messenger"} which bot each is`)
|
|
119
123
|
.summary("every bot profile on this machine; --check asks which bot each is")
|
|
120
124
|
.option("--check", "ask the messenger who each bot is, with its token")
|
|
121
|
-
.action(async function () {
|
|
125
|
+
.action(async function (words) {
|
|
126
|
+
const wanted = botProfileName(words);
|
|
122
127
|
const context = botContext(this, bot);
|
|
123
128
|
const check = this.opts().check === true;
|
|
124
129
|
if (check)
|
|
@@ -132,6 +137,8 @@ const listCommand = (bot, tokenVariable) => annotate(new Command("list"), { effe
|
|
|
132
137
|
}
|
|
133
138
|
const rows = [];
|
|
134
139
|
for (const name of names) {
|
|
140
|
+
if (wanted !== undefined && name !== wanted)
|
|
141
|
+
continue;
|
|
135
142
|
const tokens = bot.tokenStore?.(this, name) ??
|
|
136
143
|
new BotTokenStore({
|
|
137
144
|
app: bot.app,
|
|
@@ -161,11 +168,12 @@ const recipientsCommand = (bot) => {
|
|
|
161
168
|
const command = new Command("recipients")
|
|
162
169
|
.description("the chats this bot may write to; with no list, every chat — `clear` removes the list")
|
|
163
170
|
.summary("the chats this bot may write to; with no list, every chat");
|
|
164
|
-
annotate(command.command("list"), { effects: LOCAL_READ, examples: botExamples(bot, "recipients list") })
|
|
171
|
+
withQuery(annotate(command.command("list"), { effects: LOCAL_READ, examples: botExamples(bot, "recipients list") }), RECIPIENTS_QUERY)
|
|
165
172
|
.description("the chats on the list, or nothing when there is no list")
|
|
166
|
-
.action(async function () {
|
|
173
|
+
.action(async function (words) {
|
|
174
|
+
const filter = recipientFilter(words, "bot recipients list");
|
|
167
175
|
const context = botContext(this, bot);
|
|
168
|
-
await context.run(async () => renderAnswer(this, context, wholeList("bot recipients list",
|
|
176
|
+
await context.run(async () => renderAnswer(this, context, wholeList("bot recipients list", context.recipients().list(filter).items.map(snakeKeys))));
|
|
169
177
|
});
|
|
170
178
|
grantsAuthority(annotate(command.command("add"), {
|
|
171
179
|
effects: LOCAL_WRITE,
|
|
@@ -203,21 +211,23 @@ const recipientsCommand = (bot) => {
|
|
|
203
211
|
const sendsCommand = (bot) => new Command("sends")
|
|
204
212
|
.description("what this bot sent, edited and deleted from this machine — ids and outcomes, never text")
|
|
205
213
|
.summary("what this bot sent, edited and deleted from here — never the text")
|
|
206
|
-
.addCommand(annotate(new Command("list"), { effects: LOCAL_READ, examples: botExamples(bot, "sends list") })
|
|
214
|
+
.addCommand(withQuery(annotate(new Command("list"), { effects: LOCAL_READ, examples: botExamples(bot, "sends list") }), SENDS_QUERY)
|
|
207
215
|
.description("every send, edit and delete this bot made from this machine, with its outcome")
|
|
208
|
-
.action(async function () {
|
|
216
|
+
.action(async function (words) {
|
|
217
|
+
const filter = sendFilter(words, "bot sends list");
|
|
209
218
|
const context = botContext(this, bot);
|
|
210
|
-
await context.run(async () => renderAnswer(this, context, wholeList("bot sends list", context.journal().entries().map(snakeKeys))));
|
|
219
|
+
await context.run(async () => renderAnswer(this, context, wholeList("bot sends list", context.journal().entries(filter).map(snakeKeys))));
|
|
211
220
|
}));
|
|
212
221
|
const chatsCommand = (bot) => {
|
|
213
222
|
const command = new Command("chats")
|
|
214
223
|
.description(`the chats this bot is in — ${bot.name ?? "the messenger"} gives a bot no list of them, so \`list\` shows the ones it has seen`)
|
|
215
224
|
.summary("the chats this bot is in, and what it does there");
|
|
216
|
-
annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "chats list") })
|
|
225
|
+
withQuery(annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "chats list") }), BOT_CHATS_QUERY)
|
|
217
226
|
.description(`chats this bot has seen on this machine — not a complete list from ${bot.name ?? "the messenger"}`)
|
|
218
|
-
.action(async function () {
|
|
227
|
+
.action(async function (words) {
|
|
228
|
+
const filter = botChatFilter(words);
|
|
219
229
|
const context = botContext(this, bot);
|
|
220
|
-
await context.run(async () => renderAnswer(this, context, wholeList("bot chats list", context.registry.list().map(snakeKeys))));
|
|
230
|
+
await context.run(async () => renderAnswer(this, context, wholeList("bot chats list", context.registry.list(filter).map(snakeKeys))));
|
|
221
231
|
});
|
|
222
232
|
annotate(command.command("show"), { effects: REMOTE_READ, examples: botExamples(bot, "chats show Team") })
|
|
223
233
|
.description(`one chat from ${bot.name ?? "the messenger"}, and remember it`)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type QueryRegistry } from "@wirecat/cli-base/query";
|
|
2
|
+
import { type QueryWords } from "../list-query.js";
|
|
3
|
+
import type { SeenChat } from "./registry.js";
|
|
4
|
+
export declare const BOT_PROFILES_QUERY: QueryRegistry;
|
|
5
|
+
export declare const botProfileName: (words: QueryWords) => string | undefined;
|
|
6
|
+
export declare const BOT_CHATS_QUERY: QueryRegistry;
|
|
7
|
+
export declare const botChatFilter: (words: QueryWords) => Partial<Pick<SeenChat, "id" | "title" | "kind">>;
|
|
8
|
+
export declare const BOT_MESSAGES_QUERY: QueryRegistry;
|
|
9
|
+
export declare const botMessageWindow: (words: QueryWords) => {
|
|
10
|
+
window: {
|
|
11
|
+
before?: string | undefined;
|
|
12
|
+
since?: string | undefined;
|
|
13
|
+
threadId?: string | undefined;
|
|
14
|
+
outgoing?: boolean | undefined;
|
|
15
|
+
starred?: boolean | undefined;
|
|
16
|
+
};
|
|
17
|
+
meta: {
|
|
18
|
+
timezone: string;
|
|
19
|
+
window: {
|
|
20
|
+
since?: string;
|
|
21
|
+
until?: string;
|
|
22
|
+
};
|
|
23
|
+
} | undefined;
|
|
24
|
+
};
|
|
25
|
+
export declare const BOT_ADMINS_QUERY: QueryRegistry;
|
|
26
|
+
export declare const BOT_MENU_QUERY: QueryRegistry;
|
|
27
|
+
export declare const BOT_WEBHOOKS_QUERY: QueryRegistry;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { queryError, queryFieldsOf } from "@wirecat/cli-base/query";
|
|
2
|
+
import { botAdmin, botMenuEntry, botProfile, botSeenChat, botWebhook, message } from "../../entities/index.js";
|
|
3
|
+
import { listQuery, nativeTerms, queryTerms, timeBounds, typedQuery, unfilteredQuery, } from "../list-query.js";
|
|
4
|
+
const termsOnly = (registry) => ({
|
|
5
|
+
...registry,
|
|
6
|
+
fields: registry.fields.map((field) => field.native
|
|
7
|
+
? {
|
|
8
|
+
...field,
|
|
9
|
+
operators: field.operators.filter((operator) => operator === "term" || operator === "phrase"),
|
|
10
|
+
}
|
|
11
|
+
: field),
|
|
12
|
+
});
|
|
13
|
+
export const BOT_PROFILES_QUERY = termsOnly({
|
|
14
|
+
entity: "bot_profile",
|
|
15
|
+
backend: "native",
|
|
16
|
+
fields: queryFieldsOf(botProfile, { native: ["name"], examples: { name: "name:sales" } }),
|
|
17
|
+
});
|
|
18
|
+
export const botProfileName = (words) => queryTerms(words, BOT_PROFILES_QUERY, "bot list").get("name");
|
|
19
|
+
export const BOT_CHATS_QUERY = termsOnly({
|
|
20
|
+
entity: "bot_seen_chat",
|
|
21
|
+
backend: "native",
|
|
22
|
+
fields: queryFieldsOf(botSeenChat, {
|
|
23
|
+
native: ["id", "title", "kind"],
|
|
24
|
+
examples: { id: "id:7", title: 'title:"Book club"', kind: "kind:group" },
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
export const botChatFilter = (words) => {
|
|
28
|
+
const terms = queryTerms(words, BOT_CHATS_QUERY, "bot chats list");
|
|
29
|
+
const id = terms.get("id"), title = terms.get("title");
|
|
30
|
+
const kind = botSeenChat.entries.kind.wrapped.options.find((value) => value === terms.get("kind"));
|
|
31
|
+
return {
|
|
32
|
+
...(id === undefined ? {} : { id }),
|
|
33
|
+
...(title === undefined ? {} : { title }),
|
|
34
|
+
...(kind === undefined ? {} : { kind }),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export const BOT_MESSAGES_QUERY = {
|
|
38
|
+
entity: "message",
|
|
39
|
+
backend: "native",
|
|
40
|
+
fields: queryFieldsOf(message, {
|
|
41
|
+
native: ["id", "timestamp", "thread_id", "outgoing", "starred"],
|
|
42
|
+
examples: {
|
|
43
|
+
id: "id<42",
|
|
44
|
+
timestamp: "timestamp>2026-10-01",
|
|
45
|
+
thread_id: "thread_id:12",
|
|
46
|
+
outgoing: "outgoing:false",
|
|
47
|
+
starred: "starred:true",
|
|
48
|
+
},
|
|
49
|
+
}).map((field) => {
|
|
50
|
+
if (field.name === "id" || field.name === "timestamp")
|
|
51
|
+
return { ...field, operators: ["range"] };
|
|
52
|
+
return field.native ? { ...field, operators: ["term"] } : field;
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
export const botMessageWindow = (words) => {
|
|
56
|
+
const command = "bot messages list";
|
|
57
|
+
const terms = nativeTerms(listQuery(typedQuery(words), BOT_MESSAGES_QUERY, command), command);
|
|
58
|
+
const id = terms.get("id");
|
|
59
|
+
if (id && (id.value !== "*" || !id.upper || id.upper === "*" || id.upper_inclusive !== false))
|
|
60
|
+
queryError("query.unsupported-operator", id.span, "id takes an exclusive upper bound here: id<42", { command });
|
|
61
|
+
const timestamp = terms.get("timestamp");
|
|
62
|
+
const bounds = timeBounds(timestamp);
|
|
63
|
+
if (timestamp && (bounds.since === undefined || bounds.until !== undefined))
|
|
64
|
+
queryError("query.unsupported-operator", timestamp.span, "timestamp takes a lower bound here: timestamp>7d", {
|
|
65
|
+
command,
|
|
66
|
+
});
|
|
67
|
+
const topic = terms.get("thread_id")?.value;
|
|
68
|
+
const outgoing = terms.get("outgoing")?.value.toLowerCase();
|
|
69
|
+
const starred = terms.get("starred")?.value.toLowerCase();
|
|
70
|
+
return {
|
|
71
|
+
window: {
|
|
72
|
+
...(id?.upper === undefined ? {} : { before: id.upper }),
|
|
73
|
+
...(bounds.since === undefined ? {} : { since: new Date(bounds.since).toISOString() }),
|
|
74
|
+
...(topic === undefined ? {} : { threadId: topic }),
|
|
75
|
+
...(outgoing === undefined ? {} : { outgoing: outgoing === "true" }),
|
|
76
|
+
...(starred === undefined ? {} : { starred: starred === "true" }),
|
|
77
|
+
},
|
|
78
|
+
meta: bounds.meta,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export const BOT_ADMINS_QUERY = unfilteredQuery("bot_admin", botAdmin);
|
|
82
|
+
export const BOT_MENU_QUERY = unfilteredQuery("bot_menu_entry", botMenuEntry);
|
|
83
|
+
export const BOT_WEBHOOKS_QUERY = unfilteredQuery("bot_webhook", botWebhook);
|
package/dist/cli/bot/messages.js
CHANGED
|
@@ -9,12 +9,14 @@ import { readAttachments } from "../../sends/upload.js";
|
|
|
9
9
|
import { environmentOf } from "../context.js";
|
|
10
10
|
import { ownerOnlyOption, REMOTE_READ, REMOTE_WRITE } from "../effects.js";
|
|
11
11
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
12
|
+
import { typedQuery, withQuery } from "../list-query.js";
|
|
12
13
|
import { wholeList } from "../messenger/chat-answers.js";
|
|
13
14
|
import { messageItem } from "../messenger/message-items.js";
|
|
14
15
|
import { readAll } from "../messenger/stdin.js";
|
|
15
16
|
import { positiveCount } from "../paging.js";
|
|
16
17
|
import { botContext } from "./context.js";
|
|
17
18
|
import { botExamples } from "./examples.js";
|
|
19
|
+
import { BOT_MESSAGES_QUERY, botMessageWindow } from "./list-queries.js";
|
|
18
20
|
/** The adapter's `method`, or a refusal naming what this messenger's bot cannot do. */
|
|
19
21
|
export const botCan = (adapter, method, bot, what) => {
|
|
20
22
|
const found = adapter[method];
|
|
@@ -32,10 +34,10 @@ export const botIdOf = async (context, adapter) => {
|
|
|
32
34
|
context.registry.rememberBot(id);
|
|
33
35
|
return id;
|
|
34
36
|
};
|
|
35
|
-
const show = (command, context, bot, botId, messages, one = false) => {
|
|
37
|
+
const show = (command, context, bot, botId, messages, one = false, meta) => {
|
|
36
38
|
const reader = context.copy.accountOf(botId);
|
|
37
39
|
const items = messages.map((message) => messageItem(message, reader));
|
|
38
|
-
renderAnswer(command, context, one && items[0] ? itemOf(items[0]) : wholeList("bot messages list", items), () => renderMessages(messages, {
|
|
40
|
+
renderAnswer(command, context, one && items[0] ? itemOf(items[0]) : { ...wholeList("bot messages list", items), ...(meta ? { meta } : {}) }, () => renderMessages(messages, {
|
|
39
41
|
color: context.color,
|
|
40
42
|
verbosity: context.settings.detail,
|
|
41
43
|
senderColors: context.settings.senderColors,
|
|
@@ -124,33 +126,37 @@ const sendCommand = (bot) => annotate(new Command("send"), {
|
|
|
124
126
|
renderAnswer(this, context, written(context, botId, { operationId }, message));
|
|
125
127
|
});
|
|
126
128
|
});
|
|
127
|
-
const listCommand = (bot) => annotate(new Command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "messages list Team --limit 20") })
|
|
129
|
+
const listCommand = (bot) => withQuery(annotate(new Command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "messages list Team --limit 20") })
|
|
128
130
|
.description(`the latest messages in a chat; where ${bot.name ?? "the messenger"} gives a bot no history, and with --offline, ` +
|
|
129
131
|
"the ones this bot has seen on this machine")
|
|
130
132
|
.summary("the latest messages in a chat")
|
|
131
|
-
.argument("<chat>", "a chat id, user:<id> for a person, or the title of a chat this bot has seen")
|
|
133
|
+
.argument("<chat>", "a chat id, user:<id> for a person, or the title of a chat this bot has seen"), BOT_MESSAGES_QUERY)
|
|
134
|
+
.addHelpText("after", "\nQueries filter the stored copy before limiting results; add --offline. The Bot API does not accept these filters.")
|
|
132
135
|
.option("--limit <n>", "how many, the newest", positiveCount("--limit"))
|
|
133
|
-
.action(async function (chat) {
|
|
136
|
+
.action(async function (chat, words) {
|
|
137
|
+
const narrowed = botMessageWindow(words);
|
|
134
138
|
const context = botContext(this, bot);
|
|
139
|
+
if (typedQuery(words).trim() && !context.settings.offline)
|
|
140
|
+
throw new CliError("validation_error", "bot message queries filter the local copy; add --offline");
|
|
135
141
|
const ref = context.chatRef(chat);
|
|
136
142
|
const limit = this.opts().limit ?? context.settings.limit;
|
|
137
143
|
await context.run(async (events) => {
|
|
138
144
|
const fromCopy = async (botId) => {
|
|
139
|
-
const page = await context.copy.read((store) => store.messages.messages(context.copy.accountOf(botId), ref, { limit }));
|
|
145
|
+
const page = await context.copy.read((store) => store.messages.messages(context.copy.accountOf(botId), ref, { limit, ...narrowed.window }));
|
|
140
146
|
return page.items;
|
|
141
147
|
};
|
|
142
148
|
if (context.settings.offline) {
|
|
143
149
|
const botId = context.registry.botId();
|
|
144
150
|
if (!botId)
|
|
145
151
|
throw nothingKept(context);
|
|
146
|
-
show(this, context, bot, botId, await fromCopy(botId));
|
|
152
|
+
show(this, context, bot, botId, await fromCopy(botId), false, narrowed.meta);
|
|
147
153
|
return;
|
|
148
154
|
}
|
|
149
155
|
const adapter = await context.authenticated({ events });
|
|
150
156
|
const botId = await botIdOf(context, adapter);
|
|
151
157
|
if (!adapter.history) {
|
|
152
158
|
context.renderer.note(`${bot.name ?? "the messenger"} gives a bot no history: these are the messages it kept`);
|
|
153
|
-
show(this, context, bot, botId, await fromCopy(botId));
|
|
159
|
+
show(this, context, bot, botId, await fromCopy(botId), false, narrowed.meta);
|
|
154
160
|
return;
|
|
155
161
|
}
|
|
156
162
|
const messages = await adapter.history(ref, { limit });
|
|
@@ -24,7 +24,7 @@ export declare const botFiles: (app: AppIdentity, profile: string, env?: NodeJS.
|
|
|
24
24
|
export declare class ChatRegistry {
|
|
25
25
|
#private;
|
|
26
26
|
constructor(app: AppIdentity, profile: string, env?: NodeJS.ProcessEnv);
|
|
27
|
-
list(): SeenChat[];
|
|
27
|
+
list(filter?: Partial<Pick<SeenChat, "id" | "title" | "kind">>): SeenChat[];
|
|
28
28
|
/** The bot's own user id, once any command has learned it — the key its messages are stored under. */
|
|
29
29
|
botId(): string | undefined;
|
|
30
30
|
rememberBot(id: string): void;
|
package/dist/cli/bot/registry.js
CHANGED
|
@@ -26,9 +26,11 @@ export class ChatRegistry {
|
|
|
26
26
|
constructor(app, profile, env = process.env) {
|
|
27
27
|
this.#path = botFiles(app, profile, env).registry;
|
|
28
28
|
}
|
|
29
|
-
list() {
|
|
29
|
+
list(filter = {}) {
|
|
30
30
|
const { chats } = this.#read();
|
|
31
|
-
return Array.isArray(chats) ? chats : []
|
|
31
|
+
return (Array.isArray(chats) ? chats : []).filter((chat) => (filter.id === undefined || chat.id === filter.id) &&
|
|
32
|
+
(filter.kind === undefined || chat.kind === filter.kind) &&
|
|
33
|
+
(filter.title === undefined || chat.title?.toLocaleLowerCase() === filter.title?.toLocaleLowerCase()));
|
|
32
34
|
}
|
|
33
35
|
/** The bot's own user id, once any command has learned it — the key its messages are stored under. */
|
|
34
36
|
botId() {
|
package/dist/cli/bot/setup.js
CHANGED
|
@@ -5,9 +5,11 @@ import { guardedWrite } from "../../sends/guarded.js";
|
|
|
5
5
|
import { newOperationId } from "../../sends/send-id.js";
|
|
6
6
|
import { REMOTE_READ, REMOTE_WRITE } from "../effects.js";
|
|
7
7
|
import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
|
|
8
|
+
import { listQuery, typedQuery, withQuery } from "../list-query.js";
|
|
8
9
|
import { wholeList } from "../messenger/chat-answers.js";
|
|
9
10
|
import { botContext, online } from "./context.js";
|
|
10
11
|
import { botExamples } from "./examples.js";
|
|
12
|
+
import { BOT_MENU_QUERY, BOT_WEBHOOKS_QUERY } from "./list-queries.js";
|
|
11
13
|
import { botCan } from "./messages.js";
|
|
12
14
|
import { botFiles } from "./registry.js";
|
|
13
15
|
import { PressLog } from "./updates.js";
|
|
@@ -31,9 +33,10 @@ const botWrite = (context, key, write) => guardedWrite(context.guard(), { operat
|
|
|
31
33
|
/** `bot commands`: the menu people see when they type `/`. */
|
|
32
34
|
export const botMenuCommand = (bot) => {
|
|
33
35
|
const command = new Command("commands").description("the bot's command menu — what people see after /");
|
|
34
|
-
annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "commands list") })
|
|
36
|
+
withQuery(annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "commands list") }), BOT_MENU_QUERY)
|
|
35
37
|
.description("the commands in the menu now")
|
|
36
|
-
.action(async function () {
|
|
38
|
+
.action(async function (words) {
|
|
39
|
+
listQuery(typedQuery(words), BOT_MENU_QUERY, "bot commands list");
|
|
37
40
|
const context = online(botContext(this, bot), this);
|
|
38
41
|
await context.run(async (events) => {
|
|
39
42
|
const adapter = await context.authenticated({ events });
|
|
@@ -111,9 +114,10 @@ export const botWebhooksCommand = (bot) => {
|
|
|
111
114
|
const command = new Command("webhooks")
|
|
112
115
|
.description("where the messenger pushes this bot's updates — while one is set, `bot watch` gets nothing")
|
|
113
116
|
.summary("where the messenger pushes this bot's updates");
|
|
114
|
-
annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "webhooks list") })
|
|
117
|
+
withQuery(annotate(command.command("list"), { effects: REMOTE_READ, examples: botExamples(bot, "webhooks list") }), BOT_WEBHOOKS_QUERY)
|
|
115
118
|
.description("the webhooks this bot has")
|
|
116
|
-
.action(async function () {
|
|
119
|
+
.action(async function (words) {
|
|
120
|
+
listQuery(typedQuery(words), BOT_WEBHOOKS_QUERY, "bot webhooks list");
|
|
117
121
|
const context = online(botContext(this, bot), this);
|
|
118
122
|
await context.run(async (events) => {
|
|
119
123
|
const adapter = await context.authenticated({ events });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CommandInfo } from "@wirecat/cli-base/commands";
|
|
2
|
+
import { type Level, type PermissionKey } from "../sends/permissions.js";
|
|
3
|
+
export type AvailabilityCommand = Pick<CommandInfo, "path" | "effects" | "lifetime" | "reaches">;
|
|
4
|
+
export interface DiscoveredCommand extends CommandInfo {
|
|
5
|
+
availability?: CommandAvailability;
|
|
6
|
+
commands: readonly DiscoveredCommand[];
|
|
7
|
+
}
|
|
8
|
+
export interface CommandAvailability {
|
|
9
|
+
available: boolean;
|
|
10
|
+
reason?: string;
|
|
11
|
+
permission?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Discovery and execution use the same permission decision. */
|
|
14
|
+
export declare const commandAvailability: (info: AvailabilityCommand, levels: Readonly<Record<PermissionKey, Level>>, keyOf?: (path: readonly string[]) => PermissionKey | null | undefined, authority?: (path: readonly string[]) => boolean, readOnly?: boolean, mcp?: boolean) => CommandAvailability;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { writes } from "@wirecat/cli-base/standards";
|
|
2
|
+
import { AUTHORITY_KEY, authorityAllowed, BOT_API_KEY, botApiAllowed, isBotApi, keyForCommand, levelFor, readKeysForCommand, } from "../sends/permissions.js";
|
|
3
|
+
import { changesMessenger } from "./effects.js";
|
|
4
|
+
/** Discovery and execution use the same permission decision. */
|
|
5
|
+
export const commandAvailability = (info, levels, keyOf, authority, readOnly = false, mcp = true) => {
|
|
6
|
+
const blocked = (reason, permission) => ({
|
|
7
|
+
available: false,
|
|
8
|
+
reason,
|
|
9
|
+
...(permission ? { permission } : {}),
|
|
10
|
+
});
|
|
11
|
+
if (readOnly && (changesMessenger(info) || authority?.(info.path)))
|
|
12
|
+
return blocked("the read-only lock blocks this change");
|
|
13
|
+
if (mcp && authority?.(info.path) && !authorityAllowed(levels))
|
|
14
|
+
return blocked(`the profile keeps ${AUTHORITY_KEY} off`, AUTHORITY_KEY);
|
|
15
|
+
if (mcp && isBotApi(info.path) && !botApiAllowed(levels))
|
|
16
|
+
return blocked(`the profile requires ${BOT_API_KEY}: allow`, BOT_API_KEY);
|
|
17
|
+
const key = keyOf?.(info.path) ?? keyForCommand(info.path);
|
|
18
|
+
const level = key ? levelFor(levels, key).level : "allow";
|
|
19
|
+
if (level === "deny")
|
|
20
|
+
return blocked(`the profile denies ${key}`, key ?? undefined);
|
|
21
|
+
if (level === "readonly" && writes(info.effects))
|
|
22
|
+
return blocked(`${key} is readonly in this profile`, key ?? undefined);
|
|
23
|
+
const denied = readKeysForCommand(info.path).find((read) => levelFor(levels, read).level === "deny");
|
|
24
|
+
return denied ? blocked(`the profile denies ${denied}`, denied) : { available: true };
|
|
25
|
+
};
|
|
@@ -7,6 +7,6 @@ import type { AppIdentity } from "../ports/app.js";
|
|
|
7
7
|
export declare const CONTRACT = 3;
|
|
8
8
|
/**
|
|
9
9
|
* The discovery surface an agent reads instead of `--help`. It opens no store, no session and no
|
|
10
|
-
* settings file
|
|
10
|
+
* settings file without a named profile. A named profile adds availability without hiding commands.
|
|
11
11
|
*/
|
|
12
12
|
export declare const commandsCommand: (app: AppIdentity) => Command;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CliError, EXIT_CODES, GENERIC_FAILURE } from "@wirecat/cli-base";
|
|
2
|
-
import { annotate, describeOptions, describeProgram } from "@wirecat/cli-base/commands";
|
|
2
|
+
import { annotate, describeOptions, describeProgram, metaOf } from "@wirecat/cli-base/commands";
|
|
3
3
|
import { CRASH_CODE } from "@wirecat/cli-base/standards";
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { commandContract, findCommand } from "./command-contract.js";
|
|
5
|
+
import { commandContract, commandPathOf, findCommand } from "./command-contract.js";
|
|
6
6
|
import { environmentOf, outputFor } from "./context.js";
|
|
7
7
|
import { NO_EFFECTS } from "./effects.js";
|
|
8
8
|
import { renderDocument } from "./envelope.js";
|
|
@@ -15,7 +15,7 @@ import { rootOf } from "./profile.js";
|
|
|
15
15
|
export const CONTRACT = 3;
|
|
16
16
|
/**
|
|
17
17
|
* The discovery surface an agent reads instead of `--help`. It opens no store, no session and no
|
|
18
|
-
* settings file
|
|
18
|
+
* settings file without a named profile. A named profile adds availability without hiding commands.
|
|
19
19
|
*/
|
|
20
20
|
export const commandsCommand = (app) => {
|
|
21
21
|
const command = annotate(new Command("commands"), { effects: NO_EFFECTS })
|
|
@@ -30,7 +30,12 @@ export const commandsCommand = (app) => {
|
|
|
30
30
|
.action(function (path) {
|
|
31
31
|
const root = rootOf(this);
|
|
32
32
|
const output = outputFor(this);
|
|
33
|
-
const
|
|
33
|
+
const available = environmentOf(this).discoveryAvailability;
|
|
34
|
+
const annotateAvailability = (infos) => infos.map((info) => {
|
|
35
|
+
const availability = available?.(info);
|
|
36
|
+
return { ...info, ...(availability ? { availability } : {}), commands: annotateAvailability(info.commands) };
|
|
37
|
+
});
|
|
38
|
+
const all = annotateAvailability(describeProgram(root));
|
|
34
39
|
const scoped = path.length > 0 ? scopeOf(root, all, path) : undefined;
|
|
35
40
|
const commands = scoped ? [scoped.command] : all;
|
|
36
41
|
const { json, jsonl } = this.optsWithGlobals();
|
|
@@ -62,6 +67,14 @@ export const commandsCommand = (app) => {
|
|
|
62
67
|
version: app.version,
|
|
63
68
|
contract: CONTRACT,
|
|
64
69
|
...commandContract(findCommand(rootOf(this), path)),
|
|
70
|
+
...(() => {
|
|
71
|
+
const found = findCommand(rootOf(this), path);
|
|
72
|
+
const availability = environmentOf(this).discoveryAvailability?.({
|
|
73
|
+
...metaOf(found),
|
|
74
|
+
path: commandPathOf(found),
|
|
75
|
+
});
|
|
76
|
+
return availability ? { availability } : {};
|
|
77
|
+
})(),
|
|
65
78
|
});
|
|
66
79
|
}));
|
|
67
80
|
return command;
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { type Closeable } from "./deadline.js";
|
|
|
11
11
|
import type { GlobalFlags, ResolveOptions, Settings } from "./settings.js";
|
|
12
12
|
/** What every CLI's commands write to, when it is not the real terminal. A CLI adds its own fields. */
|
|
13
13
|
export interface BaseEnvironment {
|
|
14
|
+
discoveryAvailability?: (command: import("./command-availability.js").AvailabilityCommand) => import("./command-availability.js").CommandAvailability | undefined;
|
|
14
15
|
streams?: Streams;
|
|
15
16
|
/** Whether a person is looking. Defaults to whether stdout is a terminal. */
|
|
16
17
|
tty?: boolean;
|
package/dist/cli/effects.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const NO_EFFECTS: Effects;
|
|
|
14
14
|
/**
|
|
15
15
|
* Marks a local write that changes what a later run may do: a schedule, a setting, the recipient list, an
|
|
16
16
|
* auto-reply, an agent's setup. `<PREFIX>_READ_ONLY` refuses it, or an agent under the lock could hand an
|
|
17
|
-
* unlocked run the send it may not make itself (SEC-47). For the same reason an MCP server
|
|
17
|
+
* unlocked run the send it may not make itself (SEC-47). For the same reason an MCP server permits execution only where the
|
|
18
18
|
* profile allows `mcp.authority`, which is off until the owner turns it on (NEED-1662).
|
|
19
19
|
* `unless` names a flag that makes the run only look (`upgrade --check`), which the lock lets through.
|
|
20
20
|
*/
|
package/dist/cli/effects.js
CHANGED
|
@@ -16,7 +16,7 @@ const AUTHORITY = new WeakMap();
|
|
|
16
16
|
/**
|
|
17
17
|
* Marks a local write that changes what a later run may do: a schedule, a setting, the recipient list, an
|
|
18
18
|
* auto-reply, an agent's setup. `<PREFIX>_READ_ONLY` refuses it, or an agent under the lock could hand an
|
|
19
|
-
* unlocked run the send it may not make itself (SEC-47). For the same reason an MCP server
|
|
19
|
+
* unlocked run the send it may not make itself (SEC-47). For the same reason an MCP server permits execution only where the
|
|
20
20
|
* profile allows `mcp.authority`, which is off until the owner turns it on (NEED-1662).
|
|
21
21
|
* `unless` names a flag that makes the run only look (`upgrade --check`), which the lock lets through.
|
|
22
22
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { metaOf } from "@wirecat/cli-base/commands";
|
|
2
2
|
import { writes } from "@wirecat/cli-base/standards";
|
|
3
3
|
import { commandPathOf, outputOf } from "../command-contract.js";
|
|
4
|
+
import { environmentOf } from "../context.js";
|
|
4
5
|
import { PROFILE_FREE } from "../profile.js";
|
|
5
6
|
import { commandAt } from "../steps.js";
|
|
6
7
|
import { short } from "./commands-text.js";
|
|
@@ -195,7 +196,23 @@ const LABEL = 15;
|
|
|
195
196
|
export const commandHelpText = (command) => {
|
|
196
197
|
const { returns, next, examples } = commandHelp(command);
|
|
197
198
|
const rows = (label, values) => values.map((value, at) => `${(at === 0 ? label : "").padEnd(LABEL)}${value}`);
|
|
198
|
-
|
|
199
|
+
const availability = environmentOf(command).discoveryAvailability?.({
|
|
200
|
+
...metaOf(command),
|
|
201
|
+
path: commandPathOf(command),
|
|
202
|
+
});
|
|
203
|
+
const access = availability
|
|
204
|
+
? rows("Availability:", [
|
|
205
|
+
availability.available ? "available for this profile" : `unavailable — ${availability.reason}`,
|
|
206
|
+
])
|
|
207
|
+
: [];
|
|
208
|
+
return [
|
|
209
|
+
"",
|
|
210
|
+
...access,
|
|
211
|
+
...rows("Returns:", [returns]),
|
|
212
|
+
...rows("Next usually:", next),
|
|
213
|
+
...rows("Example:", examples),
|
|
214
|
+
"",
|
|
215
|
+
].join("\n");
|
|
199
216
|
};
|
|
200
217
|
/** The same, as Markdown lines for `docs/commands.md`: hand it to cli-base's `commandsPage` per command. */
|
|
201
218
|
export const commandHelpMarkdown = (root, path) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { CommandInfo } from "@wirecat/cli-base/commands";
|
|
2
1
|
import type { Command } from "commander";
|
|
2
|
+
import type { DiscoveredCommand as CommandInfo } from "../command-availability.js";
|
|
3
3
|
/** The first clause that fits: a line per command is the point, the whole text is in `--help`. */
|
|
4
4
|
export declare const short: (text: string) => string;
|
|
5
5
|
/**
|
|
@@ -33,10 +33,11 @@ export const commandsText = (root, commands, scope) => {
|
|
|
33
33
|
if (infos.length === 0)
|
|
34
34
|
continue;
|
|
35
35
|
lines.push("", SECTIONS[section]);
|
|
36
|
-
for (const { usage, summary, description, effects } of infos) {
|
|
36
|
+
for (const { usage, summary, description, effects, availability } of infos) {
|
|
37
37
|
const what = short(summary ?? description);
|
|
38
38
|
const mark = effects === undefined || writes(effects) ? " (writes)" : "";
|
|
39
|
-
|
|
39
|
+
const access = availability?.available === false ? ` [unavailable: ${availability.reason}]` : "";
|
|
40
|
+
lines.push(` ${usage.padEnd(COLUMN)} ${what}${mark}${access}`.trimEnd());
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
return `${lines.join("\n")}\n`;
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AI_ENTRIES, AI_SETTING_KEYS, type AISettings, resolveAISettings } from "../analysis/settings.js";
|
|
2
2
|
export { parseDuration } from "../domain/duration.js";
|
|
3
|
+
export { botTemplate } from "../entities/list-results.js";
|
|
3
4
|
export { syncProgress } from "../entities/store-records.js";
|
|
4
5
|
export { isCliFailure, isCommanderFailure } from "../errors/failures.js";
|
|
5
6
|
export { type AppErrors, failureContext } from "../errors/reasons.js";
|
|
@@ -49,6 +50,7 @@ export { BOT_ACTIONS, type BotAction, type BotAdapter, type BotCallbacks, type B
|
|
|
49
50
|
export { botFiles, botsDirectory, ChatRegistry, registryProfiles, type SeenChat } from "./bot/registry.js";
|
|
50
51
|
export { BotTokenStore, type BotTokenStoreOptions } from "./bot/token.js";
|
|
51
52
|
export { PressLog, UpdatesCursor } from "./bot/updates.js";
|
|
53
|
+
export type { CommandAvailability, DiscoveredCommand } from "./command-availability.js";
|
|
52
54
|
export { commandContract, type JsonSchema, SCHEMA_DIALECT } from "./command-contract.js";
|
|
53
55
|
export { CONTRACT, commandsCommand } from "./commands-command.js";
|
|
54
56
|
export { configCommand, refuseUnknownKey } from "./config-command.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AI_ENTRIES, AI_SETTING_KEYS, resolveAISettings } from "../analysis/settings.js";
|
|
2
2
|
export { parseDuration } from "../domain/duration.js";
|
|
3
|
+
export { botTemplate } from "../entities/list-results.js";
|
|
3
4
|
export { syncProgress } from "../entities/store-records.js";
|
|
4
5
|
export { isCliFailure, isCommanderFailure } from "../errors/failures.js";
|
|
5
6
|
export { failureContext } from "../errors/reasons.js";
|