@iola_adm/iola-cli 0.1.31 → 0.1.32
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/README.md +3 -0
- package/package.json +1 -1
- package/src/cli.js +271 -1
- package/wiki/Home.md +1 -0
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +4 -0
- package/wiki//320/237/320/276/320/264/320/272/320/273/321/216/321/207/320/265/320/275/320/270/320/265-/320/223/320/276/321/201/321/203/321/201/320/273/321/203/320/263.md +73 -0
package/README.md
CHANGED
|
@@ -75,6 +75,7 @@ iola subagents list
|
|
|
75
75
|
iola trajectory last
|
|
76
76
|
iola review config
|
|
77
77
|
iola browser status
|
|
78
|
+
iola gosuslugi status
|
|
78
79
|
```
|
|
79
80
|
|
|
80
81
|
Локальная модель через Ollama:
|
|
@@ -102,6 +103,7 @@ iola version --check
|
|
|
102
103
|
- [Рабочая среда агента](https://github.com/adm-iola/iola-cli/wiki/Рабочая-среда-агента)
|
|
103
104
|
- [Платформа агента](https://github.com/adm-iola/iola-cli/wiki/Платформа-агента)
|
|
104
105
|
- [Браузерный агент](https://github.com/adm-iola/iola-cli/wiki/Браузерный-агент)
|
|
106
|
+
- [Подключение Госуслуг](https://github.com/adm-iola/iola-cli/wiki/Подключение-Госуслуг)
|
|
105
107
|
- [Расширения и локальные данные](https://github.com/adm-iola/iola-cli/wiki/Расширения-и-локальные-данные)
|
|
106
108
|
- [Архивы и мастер настройки](https://github.com/adm-iola/iola-cli/wiki/Архивы-и-мастер-настройки)
|
|
107
109
|
- [Daemon, RPC и cron](https://github.com/adm-iola/iola-cli/wiki/Daemon-RPC-и-cron)
|
|
@@ -119,6 +121,7 @@ iola version --check
|
|
|
119
121
|
- subagents, skill bundles, layered settings, usage/budget accounting и trajectory export;
|
|
120
122
|
- полноценный локальный MCP server по stdio/http: tools, resources и prompts;
|
|
121
123
|
- браузерный runtime через Playwright: чтение страниц, скриншоты, PDF, клики, ввод и eval;
|
|
124
|
+
- локальный OAuth/OIDC-каркас для подключения Госуслуг/ЕСИА через официальный redirect flow;
|
|
122
125
|
- управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
|
|
123
126
|
- планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
|
|
124
127
|
- экспорт отчетов в Excel/Word-совместимые файлы;
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
3
4
|
import { createServer } from "node:http";
|
|
4
5
|
import { appendFile, copyFile, cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
@@ -109,6 +110,18 @@ const DEFAULT_AI_CONFIG = {
|
|
|
109
110
|
baseUrl: "https://apiiola.yasg.ru/api/v1",
|
|
110
111
|
mcpBaseUrl: "https://apiiola.yasg.ru",
|
|
111
112
|
},
|
|
113
|
+
gosuslugi: {
|
|
114
|
+
enabled: false,
|
|
115
|
+
authUrl: "",
|
|
116
|
+
tokenUrl: "",
|
|
117
|
+
userinfoUrl: "",
|
|
118
|
+
clientId: "",
|
|
119
|
+
clientSecret: "",
|
|
120
|
+
scope: "openid",
|
|
121
|
+
redirectHost: "127.0.0.1",
|
|
122
|
+
redirectPort: 18791,
|
|
123
|
+
redirectPath: "/gosuslugi/callback",
|
|
124
|
+
},
|
|
112
125
|
ai: {
|
|
113
126
|
activeProfile: "local",
|
|
114
127
|
provider: "ollama",
|
|
@@ -262,6 +275,7 @@ const COMMANDS = new Map([
|
|
|
262
275
|
["fork", forkSession],
|
|
263
276
|
["features", handleFeatures],
|
|
264
277
|
["settings", handleSettings],
|
|
278
|
+
["gosuslugi", handleGosuslugi],
|
|
265
279
|
["wiki", handleWiki],
|
|
266
280
|
["context", handleContext],
|
|
267
281
|
["skills", handleSkills],
|
|
@@ -388,6 +402,7 @@ Usage:
|
|
|
388
402
|
iola fork SESSION_ID [TEXT]
|
|
389
403
|
iola features list|enable|disable
|
|
390
404
|
iola settings list|get|validate|doctor|init
|
|
405
|
+
iola gosuslugi configure|status|login|logout|userinfo
|
|
391
406
|
iola wiki [open|links]
|
|
392
407
|
iola context list|show|init
|
|
393
408
|
iola skills list|show|paths|enable|disable|bundles|bundle|doctor
|
|
@@ -684,6 +699,11 @@ async function handleAgentLine(line, state) {
|
|
|
684
699
|
return false;
|
|
685
700
|
}
|
|
686
701
|
|
|
702
|
+
if (command === "gosuslugi") {
|
|
703
|
+
await handleGosuslugi(args);
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
|
|
687
707
|
if (command === "workspace") {
|
|
688
708
|
await handleWorkspace(args);
|
|
689
709
|
return false;
|
|
@@ -833,6 +853,7 @@ async function handleAgentLine(line, state) {
|
|
|
833
853
|
resume: ["resume", args],
|
|
834
854
|
fork: ["fork", args],
|
|
835
855
|
features: ["features", args],
|
|
856
|
+
gosuslugi: ["gosuslugi", args],
|
|
836
857
|
wiki: ["wiki", args],
|
|
837
858
|
context: ["context", args],
|
|
838
859
|
skills: ["skills", args],
|
|
@@ -891,6 +912,7 @@ function printAgentHelp() {
|
|
|
891
912
|
/sessions
|
|
892
913
|
/resume SESSION_ID
|
|
893
914
|
/features list
|
|
915
|
+
/gosuslugi status
|
|
894
916
|
/wiki
|
|
895
917
|
/context list
|
|
896
918
|
/skills list
|
|
@@ -1700,6 +1722,74 @@ async function handleSettings(args) {
|
|
|
1700
1722
|
throw new Error("Команды settings: list, get [KEY], validate, doctor, init.");
|
|
1701
1723
|
}
|
|
1702
1724
|
|
|
1725
|
+
async function handleGosuslugi(args) {
|
|
1726
|
+
const [action = "status", ...rest] = args;
|
|
1727
|
+
const options = parseOptions(rest);
|
|
1728
|
+
|
|
1729
|
+
if (action === "status") {
|
|
1730
|
+
const config = await loadConfig();
|
|
1731
|
+
const secrets = await loadSecrets();
|
|
1732
|
+
const tokens = secrets.gosuslugi?.tokens || null;
|
|
1733
|
+
printKeyValue({
|
|
1734
|
+
enabled: config.gosuslugi?.enabled ? "yes" : "no",
|
|
1735
|
+
configured: isGosuslugiConfigured(config) ? "yes" : "no",
|
|
1736
|
+
clientId: config.gosuslugi?.clientId ? maskSecret(config.gosuslugi.clientId) : "-",
|
|
1737
|
+
authUrl: config.gosuslugi?.authUrl || "-",
|
|
1738
|
+
tokenUrl: config.gosuslugi?.tokenUrl || "-",
|
|
1739
|
+
userinfoUrl: config.gosuslugi?.userinfoUrl || "-",
|
|
1740
|
+
redirectUri: gosuslugiRedirectUri(config),
|
|
1741
|
+
connected: tokens?.access_token ? "yes" : "no",
|
|
1742
|
+
savedAt: secrets.gosuslugi?.savedAt || "-",
|
|
1743
|
+
expiresAt: secrets.gosuslugi?.expiresAt || "-",
|
|
1744
|
+
});
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (action === "configure") {
|
|
1749
|
+
const current = await loadConfig();
|
|
1750
|
+
const next = {
|
|
1751
|
+
...(current.gosuslugi || {}),
|
|
1752
|
+
enabled: true,
|
|
1753
|
+
authUrl: options["auth-url"] || current.gosuslugi?.authUrl || "",
|
|
1754
|
+
tokenUrl: options["token-url"] || current.gosuslugi?.tokenUrl || "",
|
|
1755
|
+
userinfoUrl: options["userinfo-url"] || current.gosuslugi?.userinfoUrl || "",
|
|
1756
|
+
clientId: options["client-id"] || current.gosuslugi?.clientId || "",
|
|
1757
|
+
clientSecret: options["client-secret"] || current.gosuslugi?.clientSecret || "",
|
|
1758
|
+
scope: options.scope || current.gosuslugi?.scope || "openid",
|
|
1759
|
+
redirectHost: options["redirect-host"] || current.gosuslugi?.redirectHost || "127.0.0.1",
|
|
1760
|
+
redirectPort: Number(options["redirect-port"] || current.gosuslugi?.redirectPort || 18791),
|
|
1761
|
+
redirectPath: options["redirect-path"] || current.gosuslugi?.redirectPath || "/gosuslugi/callback",
|
|
1762
|
+
};
|
|
1763
|
+
await saveConfig({ gosuslugi: next });
|
|
1764
|
+
console.log("Настройки подключения к Госуслугам сохранены.");
|
|
1765
|
+
console.log(`Redirect URI: ${gosuslugiRedirectUri({ gosuslugi: next })}`);
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
if (action === "login") {
|
|
1770
|
+
const result = await gosuslugiLogin(options);
|
|
1771
|
+
printKeyValue(result);
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
if (action === "logout") {
|
|
1776
|
+
const secrets = await loadSecrets();
|
|
1777
|
+
delete secrets.gosuslugi;
|
|
1778
|
+
await saveSecrets(secrets);
|
|
1779
|
+
console.log("Локальное подключение Госуслуг удалено.");
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
if (action === "userinfo" || action === "me") {
|
|
1784
|
+
const result = await gosuslugiUserinfo(options);
|
|
1785
|
+
if (options.json) printJson(result);
|
|
1786
|
+
else printKeyValue(flattenObjectForPrint(result));
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
throw new Error("Команды gosuslugi: configure, status, login, logout, userinfo.");
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1703
1793
|
async function handleWiki(args) {
|
|
1704
1794
|
const [action = "links"] = args;
|
|
1705
1795
|
const base = "https://github.com/adm-iola/iola-cli/wiki";
|
|
@@ -1714,6 +1804,7 @@ async function handleWiki(args) {
|
|
|
1714
1804
|
["Рабочая среда агента", `${base}/Рабочая-среда-агента`],
|
|
1715
1805
|
["Платформа агента", `${base}/Платформа-агента`],
|
|
1716
1806
|
["Браузерный агент", `${base}/Браузерный-агент`],
|
|
1807
|
+
["Подключение Госуслуг", `${base}/Подключение-Госуслуг`],
|
|
1717
1808
|
["Расширения и локальные данные", `${base}/Расширения-и-локальные-данные`],
|
|
1718
1809
|
["Архивы и мастер настройки", `${base}/Архивы-и-мастер-настройки`],
|
|
1719
1810
|
["Daemon, RPC и cron", `${base}/Daemon-RPC-и-cron`],
|
|
@@ -2640,6 +2731,177 @@ async function openUrl(url) {
|
|
|
2640
2731
|
await runCommand("xdg-open", [url], { inherit: false });
|
|
2641
2732
|
}
|
|
2642
2733
|
|
|
2734
|
+
async function gosuslugiLogin(options = {}) {
|
|
2735
|
+
const config = await loadConfig();
|
|
2736
|
+
if (!isGosuslugiConfigured(config)) {
|
|
2737
|
+
throw new Error("Подключение не настроено. Пример: iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid");
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
const state = randomUrlSafe(24);
|
|
2741
|
+
const codeVerifier = randomUrlSafe(64);
|
|
2742
|
+
const codeChallenge = base64Url(createHash("sha256").update(codeVerifier).digest());
|
|
2743
|
+
const redirectUri = gosuslugiRedirectUri(config);
|
|
2744
|
+
const callback = waitForOAuthCallback(config.gosuslugi, state, Number(options.timeout || 180000));
|
|
2745
|
+
const authUrl = new URL(config.gosuslugi.authUrl);
|
|
2746
|
+
authUrl.searchParams.set("response_type", "code");
|
|
2747
|
+
authUrl.searchParams.set("client_id", config.gosuslugi.clientId);
|
|
2748
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
2749
|
+
authUrl.searchParams.set("scope", config.gosuslugi.scope || "openid");
|
|
2750
|
+
authUrl.searchParams.set("state", state);
|
|
2751
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
2752
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
2753
|
+
|
|
2754
|
+
console.log("Открываю официальный экран входа Госуслуг/ЕСИА в браузере.");
|
|
2755
|
+
console.log("После входа CLI примет callback на локальном адресе и сохранит токены только на этом компьютере.");
|
|
2756
|
+
await openUrl(authUrl.toString());
|
|
2757
|
+
const params = await callback;
|
|
2758
|
+
if (params.error) throw new Error(`Госуслуги вернули ошибку: ${params.error} ${params.error_description || ""}`.trim());
|
|
2759
|
+
if (!params.code) throw new Error("Authorization code не получен.");
|
|
2760
|
+
|
|
2761
|
+
const tokens = await exchangeGosuslugiCode(config, {
|
|
2762
|
+
code: params.code,
|
|
2763
|
+
codeVerifier,
|
|
2764
|
+
redirectUri,
|
|
2765
|
+
});
|
|
2766
|
+
const secrets = await loadSecrets();
|
|
2767
|
+
const now = new Date();
|
|
2768
|
+
const expiresAt = tokens.expires_in ? new Date(now.getTime() + Number(tokens.expires_in) * 1000).toISOString() : "";
|
|
2769
|
+
secrets.gosuslugi = {
|
|
2770
|
+
savedAt: now.toISOString(),
|
|
2771
|
+
expiresAt,
|
|
2772
|
+
tokens,
|
|
2773
|
+
};
|
|
2774
|
+
await saveSecrets(secrets);
|
|
2775
|
+
return {
|
|
2776
|
+
connected: "yes",
|
|
2777
|
+
savedAt: secrets.gosuslugi.savedAt,
|
|
2778
|
+
expiresAt: expiresAt || "-",
|
|
2779
|
+
tokenType: tokens.token_type || "-",
|
|
2780
|
+
scope: tokens.scope || config.gosuslugi.scope || "-",
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
function waitForOAuthCallback(settings, expectedState, timeoutMs) {
|
|
2785
|
+
const host = settings.redirectHost || "127.0.0.1";
|
|
2786
|
+
const port = Number(settings.redirectPort || 18791);
|
|
2787
|
+
const callbackPath = settings.redirectPath || "/gosuslugi/callback";
|
|
2788
|
+
return new Promise((resolve, reject) => {
|
|
2789
|
+
const timer = setTimeout(() => {
|
|
2790
|
+
server.close(() => {});
|
|
2791
|
+
reject(new Error("Истекло время ожидания входа через Госуслуги."));
|
|
2792
|
+
}, timeoutMs);
|
|
2793
|
+
const server = createServer((req, res) => {
|
|
2794
|
+
const url = new URL(req.url || "/", `http://${host}:${port}`);
|
|
2795
|
+
if (url.pathname !== callbackPath) {
|
|
2796
|
+
res.statusCode = 404;
|
|
2797
|
+
res.end("Not found");
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
const params = Object.fromEntries(url.searchParams.entries());
|
|
2801
|
+
if (params.state !== expectedState) {
|
|
2802
|
+
res.statusCode = 400;
|
|
2803
|
+
res.end("Invalid state");
|
|
2804
|
+
clearTimeout(timer);
|
|
2805
|
+
server.close(() => {});
|
|
2806
|
+
reject(new Error("OAuth state не совпал. Вход отменен."));
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
2810
|
+
res.end("<!doctype html><meta charset=\"utf-8\"><title>iola</title><body>Вход выполнен. Можно закрыть это окно и вернуться в терминал.</body>");
|
|
2811
|
+
clearTimeout(timer);
|
|
2812
|
+
server.close(() => resolve(params));
|
|
2813
|
+
});
|
|
2814
|
+
server.once("error", (error) => {
|
|
2815
|
+
clearTimeout(timer);
|
|
2816
|
+
reject(error);
|
|
2817
|
+
});
|
|
2818
|
+
server.listen(port, host);
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
async function exchangeGosuslugiCode(config, { code, codeVerifier, redirectUri }) {
|
|
2823
|
+
const body = new URLSearchParams();
|
|
2824
|
+
body.set("grant_type", "authorization_code");
|
|
2825
|
+
body.set("code", code);
|
|
2826
|
+
body.set("redirect_uri", redirectUri);
|
|
2827
|
+
body.set("client_id", config.gosuslugi.clientId);
|
|
2828
|
+
body.set("code_verifier", codeVerifier);
|
|
2829
|
+
if (config.gosuslugi.clientSecret) body.set("client_secret", config.gosuslugi.clientSecret);
|
|
2830
|
+
|
|
2831
|
+
const response = await fetch(config.gosuslugi.tokenUrl, {
|
|
2832
|
+
method: "POST",
|
|
2833
|
+
headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
|
|
2834
|
+
body,
|
|
2835
|
+
});
|
|
2836
|
+
const text = await response.text();
|
|
2837
|
+
let payload = {};
|
|
2838
|
+
try {
|
|
2839
|
+
payload = text ? JSON.parse(text) : {};
|
|
2840
|
+
} catch {
|
|
2841
|
+
payload = { raw: text };
|
|
2842
|
+
}
|
|
2843
|
+
if (!response.ok) {
|
|
2844
|
+
throw new Error(`Token endpoint вернул ${response.status}: ${JSON.stringify(payload)}`);
|
|
2845
|
+
}
|
|
2846
|
+
return payload;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
async function gosuslugiUserinfo() {
|
|
2850
|
+
const config = await loadConfig();
|
|
2851
|
+
const secrets = await loadSecrets();
|
|
2852
|
+
const accessToken = secrets.gosuslugi?.tokens?.access_token;
|
|
2853
|
+
if (!accessToken) throw new Error("Госуслуги не подключены. Запустите: iola gosuslugi login");
|
|
2854
|
+
if (!config.gosuslugi?.userinfoUrl) throw new Error("userinfoUrl не настроен.");
|
|
2855
|
+
const response = await fetch(config.gosuslugi.userinfoUrl, {
|
|
2856
|
+
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
|
|
2857
|
+
});
|
|
2858
|
+
const text = await response.text();
|
|
2859
|
+
let payload = {};
|
|
2860
|
+
try {
|
|
2861
|
+
payload = text ? JSON.parse(text) : {};
|
|
2862
|
+
} catch {
|
|
2863
|
+
payload = { raw: text };
|
|
2864
|
+
}
|
|
2865
|
+
if (!response.ok) throw new Error(`Userinfo endpoint вернул ${response.status}: ${JSON.stringify(payload)}`);
|
|
2866
|
+
return payload;
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
function isGosuslugiConfigured(config) {
|
|
2870
|
+
return Boolean(config.gosuslugi?.authUrl && config.gosuslugi?.tokenUrl && config.gosuslugi?.clientId);
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
function gosuslugiRedirectUri(config) {
|
|
2874
|
+
const settings = config.gosuslugi || DEFAULT_AI_CONFIG.gosuslugi;
|
|
2875
|
+
return `http://${settings.redirectHost || "127.0.0.1"}:${Number(settings.redirectPort || 18791)}${settings.redirectPath || "/gosuslugi/callback"}`;
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
function randomUrlSafe(bytes) {
|
|
2879
|
+
return base64Url(randomBytes(bytes));
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
function base64Url(buffer) {
|
|
2883
|
+
return Buffer.from(buffer).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
function maskSecret(value) {
|
|
2887
|
+
const text = String(value || "");
|
|
2888
|
+
if (text.length <= 8) return text ? "***" : "-";
|
|
2889
|
+
return `${text.slice(0, 4)}...${text.slice(-4)}`;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
function flattenObjectForPrint(value, prefix = "") {
|
|
2893
|
+
const rows = {};
|
|
2894
|
+
for (const [key, item] of Object.entries(value || {})) {
|
|
2895
|
+
const name = prefix ? `${prefix}.${key}` : key;
|
|
2896
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
2897
|
+
Object.assign(rows, flattenObjectForPrint(item, name));
|
|
2898
|
+
} else {
|
|
2899
|
+
rows[name] = Array.isArray(item) ? item.join(", ") : item;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
return rows;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2643
2905
|
async function handlePermissions(args) {
|
|
2644
2906
|
const [action = "list", name] = args;
|
|
2645
2907
|
const config = await loadConfig();
|
|
@@ -6001,7 +6263,7 @@ function parseOptions(args) {
|
|
|
6001
6263
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
6002
6264
|
result.check = true;
|
|
6003
6265
|
result[arg.slice(2)] = true;
|
|
6004
|
-
} else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--replace" || arg === "--text" || arg === "--path" || arg === "--depth" || arg === "--max-bytes" || arg === "--query" || arg === "--where" || arg === "--columns" || arg === "--inn" || arg === "--model" || arg === "--provider" || arg === "--profile" || arg === "--name" || arg === "--source" || arg === "--command" || arg === "--prompt" || arg === "--description" || arg === "--base-url" || arg === "--sandbox" || arg === "--approval" || arg === "--cwd" || arg === "--codex-profile" || arg === "--format" || arg === "--output" || arg === "--schema" || arg === "--session" || arg === "--temperature" || arg === "--config" || arg === "--dataset" || arg === "--save" || arg === "--reasoning" || arg === "--agent" || arg === "--scope" || arg === "--selector" || arg === "--url" || arg === "--timeout" || arg === "--wait" || arg === "--viewport" || arg === "--press" || arg === "--script" || arg === "--debug-file") {
|
|
6266
|
+
} else if (arg === "--limit" || arg === "--offset" || arg === "--search" || arg === "--replace" || arg === "--text" || arg === "--path" || arg === "--depth" || arg === "--max-bytes" || arg === "--query" || arg === "--where" || arg === "--columns" || arg === "--inn" || arg === "--model" || arg === "--provider" || arg === "--profile" || arg === "--name" || arg === "--source" || arg === "--command" || arg === "--prompt" || arg === "--description" || arg === "--base-url" || arg === "--sandbox" || arg === "--approval" || arg === "--cwd" || arg === "--codex-profile" || arg === "--format" || arg === "--output" || arg === "--schema" || arg === "--session" || arg === "--temperature" || arg === "--config" || arg === "--dataset" || arg === "--save" || arg === "--reasoning" || arg === "--agent" || arg === "--scope" || arg === "--selector" || arg === "--url" || arg === "--timeout" || arg === "--wait" || arg === "--viewport" || arg === "--press" || arg === "--script" || arg === "--auth-url" || arg === "--token-url" || arg === "--userinfo-url" || arg === "--client-id" || arg === "--client-secret" || arg === "--redirect-host" || arg === "--redirect-port" || arg === "--redirect-path" || arg === "--debug-file") {
|
|
6005
6267
|
result[arg.slice(2)] = args[index + 1];
|
|
6006
6268
|
index += 1;
|
|
6007
6269
|
} else {
|
|
@@ -7769,6 +8031,10 @@ function mergeConfig(base, override) {
|
|
|
7769
8031
|
...base.api,
|
|
7770
8032
|
...(override.api || {}),
|
|
7771
8033
|
},
|
|
8034
|
+
gosuslugi: {
|
|
8035
|
+
...base.gosuslugi,
|
|
8036
|
+
...(override.gosuslugi || {}),
|
|
8037
|
+
},
|
|
7772
8038
|
ai: {
|
|
7773
8039
|
...base.ai,
|
|
7774
8040
|
...(override.ai || {}),
|
|
@@ -7843,6 +8109,9 @@ function validateConfig(config) {
|
|
|
7843
8109
|
for (const toolset of config.toolsets?.enabled || []) {
|
|
7844
8110
|
if (!TOOLSETS[toolset]) errors.push(`toolsets.enabled содержит неизвестный toolset: ${toolset}`);
|
|
7845
8111
|
}
|
|
8112
|
+
if (config.gosuslugi?.enabled && !isGosuslugiConfigured(config)) {
|
|
8113
|
+
errors.push("gosuslugi включен, но authUrl/tokenUrl/clientId не заполнены");
|
|
8114
|
+
}
|
|
7846
8115
|
return errors;
|
|
7847
8116
|
}
|
|
7848
8117
|
|
|
@@ -7852,6 +8121,7 @@ function configSchema() {
|
|
|
7852
8121
|
required: ["api", "ai"],
|
|
7853
8122
|
properties: {
|
|
7854
8123
|
api: { required: ["baseUrl", "mcpBaseUrl"] },
|
|
8124
|
+
gosuslugi: { requiredWhenEnabled: ["authUrl", "tokenUrl", "clientId"], optional: ["userinfoUrl", "clientSecret", "scope", "redirectHost", "redirectPort", "redirectPath"] },
|
|
7855
8125
|
ai: { required: ["activeProfile", "profiles"], providers: ["ollama", "openai", "openrouter", "codex"] },
|
|
7856
8126
|
permissions: { localTools: ALL_LOCAL_TOOLS, runtime: ["readFiles", "writeFiles", "editFiles", "deleteFiles", "sync", "externalApi", "externalAi", "codex"] },
|
|
7857
8127
|
toolsets: { available: Object.keys(TOOLSETS) },
|
package/wiki/Home.md
CHANGED
|
@@ -33,6 +33,7 @@ iola ask "найди школу 29"
|
|
|
33
33
|
- [Рабочая среда агента](Рабочая-среда-агента)
|
|
34
34
|
- [Платформа агента](Платформа-агента)
|
|
35
35
|
- [Браузерный агент](Браузерный-агент)
|
|
36
|
+
- [Подключение Госуслуг](Подключение-Госуслуг)
|
|
36
37
|
- [Расширения и локальные данные](Расширения-и-локальные-данные)
|
|
37
38
|
- [Архивы и мастер настройки](Архивы-и-мастер-настройки)
|
|
38
39
|
- [Daemon, RPC и cron](Daemon-RPC-и-cron)
|
|
@@ -92,6 +92,10 @@ iola context init
|
|
|
92
92
|
iola context list
|
|
93
93
|
iola settings list
|
|
94
94
|
iola settings validate
|
|
95
|
+
iola gosuslugi status
|
|
96
|
+
iola gosuslugi configure --auth-url URL --token-url URL --client-id ID --scope openid
|
|
97
|
+
iola gosuslugi login
|
|
98
|
+
iola gosuslugi userinfo --json
|
|
95
99
|
iola cron add "каждый день 09:00 -- quality"
|
|
96
100
|
iola cron tick
|
|
97
101
|
iola daemon status
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Подключение Госуслуг
|
|
2
|
+
|
|
3
|
+
`iola-cli` поддерживает локальный OAuth/OIDC-каркас для подключения учетной записи через официальный flow ЕСИА/Госуслуг.
|
|
4
|
+
|
|
5
|
+
Важно: CLI не извлекает cookies, токены или session storage из браузера. Пользователь входит на официальном экране ЕСИА, после чего ЕСИА возвращает `authorization code` на локальный `redirect_uri`, а CLI обменивает его на токены через `token endpoint`.
|
|
6
|
+
|
|
7
|
+
## Настройка
|
|
8
|
+
|
|
9
|
+
Для работы нужны параметры официально подключенной информационной системы:
|
|
10
|
+
|
|
11
|
+
- authorization endpoint;
|
|
12
|
+
- token endpoint;
|
|
13
|
+
- client ID;
|
|
14
|
+
- разрешенный redirect URI;
|
|
15
|
+
- scope;
|
|
16
|
+
- при необходимости client secret или иной официальный способ подписи/аутентификации клиента;
|
|
17
|
+
- optional userinfo endpoint.
|
|
18
|
+
|
|
19
|
+
Команда настройки:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
iola gosuslugi configure \
|
|
23
|
+
--auth-url "https://..." \
|
|
24
|
+
--token-url "https://..." \
|
|
25
|
+
--userinfo-url "https://..." \
|
|
26
|
+
--client-id "CLIENT_ID" \
|
|
27
|
+
--scope "openid" \
|
|
28
|
+
--redirect-port 18791
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
CLI покажет redirect URI:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
http://127.0.0.1:18791/gosuslugi/callback
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Этот URI должен быть разрешен в настройках подключенной информационной системы.
|
|
38
|
+
|
|
39
|
+
## Вход
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
iola gosuslugi login
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Что происходит:
|
|
46
|
+
|
|
47
|
+
1. CLI запускает локальный callback server на `127.0.0.1`.
|
|
48
|
+
2. Открывается официальный экран входа ЕСИА/Госуслуг.
|
|
49
|
+
3. Пользователь сам вводит логин, пароль, SMS/2FA.
|
|
50
|
+
4. ЕСИА возвращает `authorization code` на локальный callback.
|
|
51
|
+
5. CLI обменивает code на токены и сохраняет их локально.
|
|
52
|
+
|
|
53
|
+
Токены хранятся в `~/.iola/secrets.json` на компьютере пользователя.
|
|
54
|
+
|
|
55
|
+
## Проверка
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
iola gosuslugi status
|
|
59
|
+
iola gosuslugi userinfo --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Выход
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
iola gosuslugi logout
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Команда удаляет локально сохраненные токены.
|
|
69
|
+
|
|
70
|
+
## Ограничения
|
|
71
|
+
|
|
72
|
+
Без официальных параметров ЕСИА команда `login` не сможет завершить подключение. Это сделано намеренно: CLI реализует легальный redirect flow, а не копирование живой браузерной сессии.
|
|
73
|
+
|