@iola_adm/iola-cli 0.2.26 → 0.2.28
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/package.json +1 -1
- package/src/cli.js +95 -7
- package/test/smoke-test.js +6 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4002,6 +4002,20 @@ async function yandexMailList(options = {}) {
|
|
|
4002
4002
|
}
|
|
4003
4003
|
}
|
|
4004
4004
|
|
|
4005
|
+
async function yandexMailCount(options = {}) {
|
|
4006
|
+
const { token, email } = await yandexMailCredentials();
|
|
4007
|
+
const session = await imapConnect();
|
|
4008
|
+
try {
|
|
4009
|
+
await imapAuthenticate(session, email, token);
|
|
4010
|
+
await imapCommand(session, `SELECT ${quoteImapMailbox(options.mailbox || "INBOX")}`);
|
|
4011
|
+
const criterion = options.unread ? "UNSEEN" : "ALL";
|
|
4012
|
+
const search = await imapCommand(session, `UID SEARCH ${criterion}`);
|
|
4013
|
+
return parseImapSearchUids(search).length;
|
|
4014
|
+
} finally {
|
|
4015
|
+
await imapClose(session);
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4005
4019
|
async function yandexMailSearch(query, options = {}) {
|
|
4006
4020
|
const normalized = normalizeGeoText(query);
|
|
4007
4021
|
if (!normalized) return yandexMailList(options);
|
|
@@ -9456,6 +9470,20 @@ async function aiAsk(args, context = {}) {
|
|
|
9456
9470
|
const historyEnabled = !options.bare && !options["no-history"] && isFeatureEnabled("sqlite-history");
|
|
9457
9471
|
const sessionId = historyEnabled && isFeatureEnabled("sessions") ? ensureSessionForAsk(options, providerConfig, question) : null;
|
|
9458
9472
|
const history = context.history || (sessionId ? getSessionAiHistory(sessionId) : []);
|
|
9473
|
+
const casualAnswer = buildCasualDirectAnswer(question);
|
|
9474
|
+
if (casualAnswer) {
|
|
9475
|
+
if (historyEnabled) {
|
|
9476
|
+
recordAskHistory({ question, answer: casualAnswer, providerConfig, dataContext, error: "", sessionId });
|
|
9477
|
+
appendSessionExchange(sessionId, question, casualAnswer, dataContext, "");
|
|
9478
|
+
}
|
|
9479
|
+
emitEvent(options, "answer", { length: casualAnswer.length, sessionId, direct: true });
|
|
9480
|
+
if (options.output) {
|
|
9481
|
+
await assertPermission("writeFiles");
|
|
9482
|
+
await writeFile(options.output, casualAnswer, "utf8");
|
|
9483
|
+
}
|
|
9484
|
+
if (!options.quiet) console.log(casualAnswer);
|
|
9485
|
+
return casualAnswer;
|
|
9486
|
+
}
|
|
9459
9487
|
const yandexAnswer = await buildYandexDirectAnswer(question, context.history || history);
|
|
9460
9488
|
if (yandexAnswer) {
|
|
9461
9489
|
if (historyEnabled) {
|
|
@@ -9588,16 +9616,18 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
9588
9616
|
const normalized = String(question || "").toLocaleLowerCase("ru-RU");
|
|
9589
9617
|
const previousAssistantText = [...(history || [])].reverse().find((item) => item.role === "assistant")?.content || "";
|
|
9590
9618
|
const mailContext = /Яндекс Почта|Письмо #|\bUID\b|#\d{3,}/iu.test(previousAssistantText);
|
|
9591
|
-
|
|
9619
|
+
const mailFollowup = mailContext && isYandexMailFollowupQuestion(normalized, question);
|
|
9620
|
+
if (!isYandexServiceQuestion(normalized) && !mailFollowup) return "";
|
|
9592
9621
|
try {
|
|
9593
|
-
if (
|
|
9594
|
-
const uid =
|
|
9622
|
+
if (mailFollowup && (isYandexMailReadRequest(normalized) || isYandexMailSelectionQuestion(question))) {
|
|
9623
|
+
const uid = resolveYandexMailUidFromQuestion(question, previousAssistantText)
|
|
9624
|
+
|| await getLatestYandexMailUid({ unread: /непрочитан/iu.test(normalized) });
|
|
9595
9625
|
const row = await yandexMailRead(uid);
|
|
9596
9626
|
if (!row || row.status === "not-found") return `Письмо #${uid} не найдено.`;
|
|
9597
9627
|
return formatYandexMailRead(row);
|
|
9598
9628
|
}
|
|
9599
9629
|
|
|
9600
|
-
if (
|
|
9630
|
+
if (isYandexIdentityQuestion(normalized)) {
|
|
9601
9631
|
const profile = await getYandexIdentityProfile();
|
|
9602
9632
|
return [
|
|
9603
9633
|
"Подключен Yandex ID:",
|
|
@@ -9620,8 +9650,18 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
9620
9650
|
const result = await yandexMailSend({ ...draft, confirm: true });
|
|
9621
9651
|
return `Письмо отправлено: ${result.to.join(", ")}. Тема: ${result.subject}.`;
|
|
9622
9652
|
}
|
|
9653
|
+
if (/(сколько|количеств|есть\s+ли)/iu.test(normalized) && /непрочитан/iu.test(normalized)) {
|
|
9654
|
+
const count = await yandexMailCount({ unread: true });
|
|
9655
|
+
if (!count) return "Непрочитанных писем нет.";
|
|
9656
|
+
const latest = await yandexMailList({ limit: 1, unread: true });
|
|
9657
|
+
return [
|
|
9658
|
+
`Непрочитанных писем: ${count}.`,
|
|
9659
|
+
latest[0] ? `Самое свежее: ${formatYandexMailSummary(latest[0])}` : "",
|
|
9660
|
+
].filter(Boolean).join("\n");
|
|
9661
|
+
}
|
|
9623
9662
|
if (isYandexMailReadRequest(normalized)) {
|
|
9624
|
-
const uid =
|
|
9663
|
+
const uid = resolveYandexMailUidFromQuestion(question, previousAssistantText)
|
|
9664
|
+
|| await getLatestYandexMailUid({ unread: /непрочитан/iu.test(normalized) });
|
|
9625
9665
|
if (!uid) return "Писем для чтения не найдено.";
|
|
9626
9666
|
const row = await yandexMailRead(uid);
|
|
9627
9667
|
if (!row || row.status === "not-found") return `Письмо #${uid} не найдено.`;
|
|
@@ -9658,6 +9698,22 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
9658
9698
|
return "";
|
|
9659
9699
|
}
|
|
9660
9700
|
|
|
9701
|
+
function isYandexServiceQuestion(normalized) {
|
|
9702
|
+
return /(яндекс|яндес|язндекс|язндекс|яндкс|yandex|почт|письм|календар|контакт|телемост)/iu.test(String(normalized || ""));
|
|
9703
|
+
}
|
|
9704
|
+
|
|
9705
|
+
function isYandexIdentityQuestion(normalized) {
|
|
9706
|
+
const text = String(normalized || "");
|
|
9707
|
+
return /(аккаунт|профил|логин|кто подключен|какой.*подключен|email|e-mail)/iu.test(text)
|
|
9708
|
+
&& /(яндекс|яндес|язндекс|язндекс|яндкс|yandex)/iu.test(text);
|
|
9709
|
+
}
|
|
9710
|
+
|
|
9711
|
+
function isYandexMailFollowupQuestion(normalized, question) {
|
|
9712
|
+
return isYandexMailReadRequest(normalized)
|
|
9713
|
+
|| isYandexMailSelectionQuestion(question)
|
|
9714
|
+
|| /(самое\s+свеж|последн|текст\s+(?:то\s+)?(?:письм|где)|содержим)/iu.test(String(normalized || ""));
|
|
9715
|
+
}
|
|
9716
|
+
|
|
9661
9717
|
function cleanupYandexQuery(question) {
|
|
9662
9718
|
const stop = /^(?:в|на|у|из|для|по|яндекс|yandex|найди|поиск|покажи|посмотри|проверь|почт\p{L}*|письм\p{L}*|календар\p{L}*|контакт\p{L}*)$/iu;
|
|
9663
9719
|
return String(question || "")
|
|
@@ -9674,9 +9730,41 @@ function extractYandexMailUid(question) {
|
|
|
9674
9730
|
return explicit ? Number(explicit) : 0;
|
|
9675
9731
|
}
|
|
9676
9732
|
|
|
9733
|
+
function isYandexMailSelectionQuestion(question) {
|
|
9734
|
+
return /^\s*\d{1,3}\.?\s*$/u.test(String(question || ""));
|
|
9735
|
+
}
|
|
9736
|
+
|
|
9737
|
+
function resolveYandexMailUidFromQuestion(question, previousAssistantText = "") {
|
|
9738
|
+
const explicitUid = extractYandexMailUid(question);
|
|
9739
|
+
if (explicitUid) return explicitUid;
|
|
9740
|
+
const ordinal = String(question || "").match(/^\s*(\d{1,3})\.?\s*$/u)?.[1];
|
|
9741
|
+
if (ordinal) {
|
|
9742
|
+
const uid = extractYandexMailUidByOrdinal(previousAssistantText, Number(ordinal));
|
|
9743
|
+
if (uid) return uid;
|
|
9744
|
+
}
|
|
9745
|
+
if (/(самое\s+свеж|последн|текст\s+(?:то\s+)?(?:письм|где)|содержим)/iu.test(String(question || ""))) {
|
|
9746
|
+
return extractFirstYandexMailUid(previousAssistantText);
|
|
9747
|
+
}
|
|
9748
|
+
return 0;
|
|
9749
|
+
}
|
|
9750
|
+
|
|
9751
|
+
function extractYandexMailUidByOrdinal(text, ordinal) {
|
|
9752
|
+
if (!ordinal || ordinal < 1) return 0;
|
|
9753
|
+
const rows = String(text || "").split(/\r?\n/u);
|
|
9754
|
+
for (const row of rows) {
|
|
9755
|
+
const match = row.match(/^\s*(\d{1,3})\.\s+#(\d{3,})/u);
|
|
9756
|
+
if (match && Number(match[1]) === ordinal) return Number(match[2]);
|
|
9757
|
+
}
|
|
9758
|
+
return 0;
|
|
9759
|
+
}
|
|
9760
|
+
|
|
9761
|
+
function extractFirstYandexMailUid(text) {
|
|
9762
|
+
return Number(String(text || "").match(/#(\d{3,})/u)?.[1] || 0);
|
|
9763
|
+
}
|
|
9764
|
+
|
|
9677
9765
|
function isYandexMailReadRequest(normalizedQuestion) {
|
|
9678
|
-
return /(^|\s)(
|
|
9679
|
-
|| /(покажи\s+содерж|о чем|о
|
|
9766
|
+
return /(^|\s)(прочитай|прочти|открой|раскрой|прочитаем)(\s|$)/iu.test(normalizedQuestion)
|
|
9767
|
+
|| /(покажи\s+содерж|о чем|о чём|текст\s+(?:то\s+)?(?:письм|где)|содержим)/iu.test(normalizedQuestion);
|
|
9680
9768
|
}
|
|
9681
9769
|
|
|
9682
9770
|
async function getLatestYandexMailUid(options = {}) {
|
package/test/smoke-test.js
CHANGED
|
@@ -76,6 +76,12 @@ assertIncludes(cliSource, "partial (", "Yandex connector status should report pa
|
|
|
76
76
|
assertIncludes(cliSource, "hasYandexOAuthAppToken", "Yandex setup should detect tokens per OAuth app");
|
|
77
77
|
assertIncludes(cliSource, "isYandexConnectorFullyConnected", "Yandex master status should require all OAuth app tokens");
|
|
78
78
|
assertIncludes(cliSource, "--app", "Yandex token command should persist tokens by OAuth app group");
|
|
79
|
+
assertIncludes(cliSource, "isYandexIdentityQuestion", "Yandex ID questions should be handled directly");
|
|
80
|
+
assertIncludes(cliSource, "язндекс", "Yandex direct router should tolerate common typos");
|
|
81
|
+
assertIncludes(cliSource, "yandexMailCount", "Yandex mail should answer unread count questions directly");
|
|
82
|
+
assertIncludes(cliSource, "resolveYandexMailUidFromQuestion", "Yandex mail follow-ups should resolve selected message UID");
|
|
83
|
+
assertIncludes(cliSource, "extractYandexMailUidByOrdinal", "Yandex mail follow-ups should support numbered selections");
|
|
84
|
+
assertIncludes(cliSource, "buildCasualDirectAnswer(question)", "Casual greetings should bypass external AI providers");
|
|
79
85
|
assertNotIncludes(cliSource, "Сервисы через запятую [identity,disk]", "Yandex setup should not ask for services during connector setup");
|
|
80
86
|
if (!packageJson.files.includes("docs/assets/iola-oauth-icon.png")) {
|
|
81
87
|
throw new Error("package files should include the Yandex OAuth icon");
|