@iola_adm/iola-cli 0.1.26 → 0.1.27
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 +6 -0
- package/package.json +1 -1
- package/src/cli.js +472 -9
- package/wiki/Home.md +1 -0
- package/wiki//320/232/320/276/320/274/320/260/320/275/320/264/321/213.md +6 -0
- package/wiki//320/240/320/260/321/201/321/210/320/270/321/200/320/265/320/275/320/270/321/217-/320/270-/320/273/320/276/320/272/320/260/320/273/321/214/320/275/321/213/320/265-/320/264/320/260/320/275/320/275/321/213/320/265.md +54 -0
package/README.md
CHANGED
|
@@ -58,6 +58,10 @@ iola policy use analyst
|
|
|
58
58
|
iola tasks list
|
|
59
59
|
iola artifacts list
|
|
60
60
|
iola trace last
|
|
61
|
+
iola changes list
|
|
62
|
+
iola index status
|
|
63
|
+
iola reports list
|
|
64
|
+
iola plugins list
|
|
61
65
|
iola context init
|
|
62
66
|
iola cron list
|
|
63
67
|
iola daemon status
|
|
@@ -87,6 +91,7 @@ iola version --check
|
|
|
87
91
|
- [Skills и toolsets](https://github.com/adm-iola/iola-cli/wiki/Skills-и-toolsets)
|
|
88
92
|
- [Локальные файлы](https://github.com/adm-iola/iola-cli/wiki/Локальные-файлы)
|
|
89
93
|
- [Рабочая среда агента](https://github.com/adm-iola/iola-cli/wiki/Рабочая-среда-агента)
|
|
94
|
+
- [Расширения и локальные данные](https://github.com/adm-iola/iola-cli/wiki/Расширения-и-локальные-данные)
|
|
90
95
|
- [Daemon, RPC и cron](https://github.com/adm-iola/iola-cli/wiki/Daemon-RPC-и-cron)
|
|
91
96
|
- [Контекст и память](https://github.com/adm-iola/iola-cli/wiki/Контекст-и-память)
|
|
92
97
|
- [Команды](https://github.com/adm-iola/iola-cli/wiki/Команды)
|
|
@@ -102,6 +107,7 @@ iola version --check
|
|
|
102
107
|
- управляемые локальные файловые операции с режимами `locked`, `read-only`, `workspace-write`, `full-access`;
|
|
103
108
|
- планы выполнения, traces, tasks, artifacts, snapshots и policy-профили;
|
|
104
109
|
- экспорт отчетов в Excel/Word-совместимые файлы;
|
|
110
|
+
- staged changes, импорт локальных CSV/JSON, индекс локальных документов, report packs, plugins и локальный MCP endpoint;
|
|
105
111
|
- cron-задачи, локальный daemon и RPC для автоматизаций;
|
|
106
112
|
- контекстные файлы `IOLA.md` и `.iola/context.md`;
|
|
107
113
|
- интеграция с публичным MCP-сервером Йошкар-Олы.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
|
17
17
|
const LAST_GOOD_CONFIG_FILE = path.join(CONFIG_DIR, "config.last-good.json");
|
|
18
18
|
const SECRETS_FILE = path.join(CONFIG_DIR, "secrets.json");
|
|
19
19
|
const DB_FILE = path.join(CONFIG_DIR, "iola.db");
|
|
20
|
-
const DB_SCHEMA_VERSION =
|
|
20
|
+
const DB_SCHEMA_VERSION = 6;
|
|
21
21
|
const LOCAL_TOOLS = ["search_local", "get_card", "export_data", "run_report", "save_view"];
|
|
22
22
|
const FILE_TOOLS = ["files_tree", "files_read", "files_search", "files_write", "files_patch"];
|
|
23
23
|
const ALL_LOCAL_TOOLS = [...LOCAL_TOOLS, ...FILE_TOOLS];
|
|
@@ -242,6 +242,11 @@ const COMMANDS = new Map([
|
|
|
242
242
|
["skills", handleSkills],
|
|
243
243
|
["tools", handleTools],
|
|
244
244
|
["files", handleFiles],
|
|
245
|
+
["changes", handleChanges],
|
|
246
|
+
["import", handleImport],
|
|
247
|
+
["index", handleIndex],
|
|
248
|
+
["reports", handleReports],
|
|
249
|
+
["plugins", handlePlugins],
|
|
245
250
|
["workspace", handleWorkspace],
|
|
246
251
|
["tasks", handleTasks],
|
|
247
252
|
["artifacts", handleArtifacts],
|
|
@@ -284,6 +289,7 @@ const COMMANDS = new Map([
|
|
|
284
289
|
["search", searchAll],
|
|
285
290
|
["mcp-info", showMcpInfo],
|
|
286
291
|
["setup", setupClient],
|
|
292
|
+
["onboard", onboard],
|
|
287
293
|
]);
|
|
288
294
|
|
|
289
295
|
export async function main(argv) {
|
|
@@ -352,6 +358,11 @@ Usage:
|
|
|
352
358
|
iola skills list|show|paths|enable|disable
|
|
353
359
|
iola tools list|toolsets|enable|disable|profile
|
|
354
360
|
iola files status|mode|approvals|tree|read|search|write|patch
|
|
361
|
+
iola changes list|show|apply|discard
|
|
362
|
+
iola import file|folder
|
|
363
|
+
iola index folder|status|search
|
|
364
|
+
iola reports list|run
|
|
365
|
+
iola plugins list|install|run|remove
|
|
355
366
|
iola workspace init|status|use|list
|
|
356
367
|
iola tasks list|add|done|run
|
|
357
368
|
iola artifacts list|show|open
|
|
@@ -366,7 +377,7 @@ Usage:
|
|
|
366
377
|
iola memory show|add|set|clear|export
|
|
367
378
|
iola hooks list|add|delete|run
|
|
368
379
|
iola agents list|run
|
|
369
|
-
iola mcp list|status|install|remove
|
|
380
|
+
iola mcp list|status|install|remove|serve
|
|
370
381
|
iola cache status|warm|clear
|
|
371
382
|
iola sync [--dataset schools|kindergartens]
|
|
372
383
|
iola sync status
|
|
@@ -413,6 +424,7 @@ Usage:
|
|
|
413
424
|
iola search TEXT [--limit 5] [--format table|json|csv]
|
|
414
425
|
iola mcp-info [--json]
|
|
415
426
|
iola setup codex
|
|
427
|
+
iola onboard
|
|
416
428
|
iola version
|
|
417
429
|
|
|
418
430
|
Environment:
|
|
@@ -604,6 +616,26 @@ async function handleAgentLine(line, state) {
|
|
|
604
616
|
return false;
|
|
605
617
|
}
|
|
606
618
|
|
|
619
|
+
if (command === "changes") {
|
|
620
|
+
await handleChanges(args);
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (command === "index") {
|
|
625
|
+
await handleIndex(args);
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (command === "reports") {
|
|
630
|
+
await handleReports(args);
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (command === "plugins") {
|
|
635
|
+
await handlePlugins(args);
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
|
|
607
639
|
if (command === "workspace") {
|
|
608
640
|
await handleWorkspace(args);
|
|
609
641
|
return false;
|
|
@@ -757,6 +789,10 @@ async function handleAgentLine(line, state) {
|
|
|
757
789
|
context: ["context", args],
|
|
758
790
|
skills: ["skills", args],
|
|
759
791
|
files: ["files", args],
|
|
792
|
+
changes: ["changes", args],
|
|
793
|
+
index: ["index", args],
|
|
794
|
+
reports: ["reports", args],
|
|
795
|
+
plugins: ["plugins", args],
|
|
760
796
|
workspace: ["workspace", args],
|
|
761
797
|
tasks: ["tasks", args],
|
|
762
798
|
todos: ["tasks", args],
|
|
@@ -812,6 +848,10 @@ function printAgentHelp() {
|
|
|
812
848
|
/permissions
|
|
813
849
|
/tools
|
|
814
850
|
/files status
|
|
851
|
+
/changes list
|
|
852
|
+
/index status
|
|
853
|
+
/reports list
|
|
854
|
+
/plugins list
|
|
815
855
|
/workspace status
|
|
816
856
|
/tasks list
|
|
817
857
|
/artifacts list
|
|
@@ -1548,6 +1588,7 @@ async function handleWiki(args) {
|
|
|
1548
1588
|
["Skills и toolsets", `${base}/Skills-и-toolsets`],
|
|
1549
1589
|
["Локальные файлы", `${base}/Локальные-файлы`],
|
|
1550
1590
|
["Рабочая среда агента", `${base}/Рабочая-среда-агента`],
|
|
1591
|
+
["Расширения и локальные данные", `${base}/Расширения-и-локальные-данные`],
|
|
1551
1592
|
["Daemon, RPC и cron", `${base}/Daemon-RPC-и-cron`],
|
|
1552
1593
|
["Контекст и память", `${base}/Контекст-и-память`],
|
|
1553
1594
|
["Команды", `${base}/Команды`],
|
|
@@ -1774,22 +1815,156 @@ async function handleFiles(args) {
|
|
|
1774
1815
|
if (!target) throw new Error('Пример: iola files write report.md --text "..."');
|
|
1775
1816
|
const text = options.text ?? rest.join(" ");
|
|
1776
1817
|
if (!text) throw new Error('Для записи нужен --text "..." или текст после пути.');
|
|
1777
|
-
|
|
1778
|
-
|
|
1818
|
+
if (options.stage) {
|
|
1819
|
+
const id = await stageFileChange("write", target, text);
|
|
1820
|
+
console.log(`Изменение подготовлено: ${id}`);
|
|
1821
|
+
} else {
|
|
1822
|
+
await filesWrite(target, text, { append: Boolean(options.append) });
|
|
1823
|
+
console.log(`Файл записан: ${target}`);
|
|
1824
|
+
}
|
|
1779
1825
|
return;
|
|
1780
1826
|
}
|
|
1781
1827
|
|
|
1782
1828
|
if (action === "patch") {
|
|
1783
1829
|
if (!target) throw new Error('Пример: iola files patch README.md --search old --replace new');
|
|
1784
1830
|
if (!options.search || options.replace === undefined) throw new Error("Для patch нужны --search и --replace.");
|
|
1785
|
-
|
|
1786
|
-
|
|
1831
|
+
if (options.stage) {
|
|
1832
|
+
const current = await filesRead(target);
|
|
1833
|
+
const next = current.split(options.search).join(options.replace);
|
|
1834
|
+
const id = await stageFileChange("patch", target, next, current);
|
|
1835
|
+
console.log(`Изменение подготовлено: ${id}`);
|
|
1836
|
+
} else {
|
|
1837
|
+
const result = await filesPatch(target, options.search, options.replace);
|
|
1838
|
+
printKeyValue(result);
|
|
1839
|
+
}
|
|
1787
1840
|
return;
|
|
1788
1841
|
}
|
|
1789
1842
|
|
|
1790
1843
|
throw new Error("Команды files: status, mode MODE, approvals POLICY, tree [PATH], read FILE, search TEXT, write FILE --text TEXT, patch FILE --search OLD --replace NEW.");
|
|
1791
1844
|
}
|
|
1792
1845
|
|
|
1846
|
+
async function handleChanges(args) {
|
|
1847
|
+
const [action = "list", id] = args;
|
|
1848
|
+
if (action === "list" || action === "ls") {
|
|
1849
|
+
printTable(listChanges(), [["id", "ID"], ["kind", "Тип"], ["target", "Файл"], ["status", "Статус"], ["created_at", "Дата"]]);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
if (action === "show") {
|
|
1853
|
+
const change = getChange(Number(id));
|
|
1854
|
+
console.log(unifiedPreview(change.before_text || "", change.after_text || ""));
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (action === "apply") {
|
|
1858
|
+
await applyChange(Number(id));
|
|
1859
|
+
console.log(`Изменение применено: ${id}`);
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
if (action === "discard") {
|
|
1863
|
+
updateChangeStatus(Number(id), "discarded");
|
|
1864
|
+
console.log(`Изменение отклонено: ${id}`);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
throw new Error("Команды changes: list, show ID, apply ID, discard ID.");
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
async function handleImport(args) {
|
|
1871
|
+
const [action, target, ...rest] = args;
|
|
1872
|
+
const options = parseOptions(rest);
|
|
1873
|
+
if (action === "file") {
|
|
1874
|
+
if (!target) throw new Error("Пример: iola import file data.csv --dataset custom");
|
|
1875
|
+
const dataset = options.dataset || path.basename(target, path.extname(target));
|
|
1876
|
+
const count = await importDataFile(target, dataset);
|
|
1877
|
+
console.log(`Импортировано записей: ${count}, dataset=${dataset}`);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
if (action === "folder") {
|
|
1881
|
+
if (!target) throw new Error("Пример: iola import folder ./data");
|
|
1882
|
+
const rows = await filesTree(target, { depth: 1, limit: 200 });
|
|
1883
|
+
let total = 0;
|
|
1884
|
+
for (const row of rows.filter((item) => item.type === "file" && /\.(json|csv)$/i.test(item.path))) {
|
|
1885
|
+
total += await importDataFile(row.path, options.dataset || path.basename(row.path, path.extname(row.path)));
|
|
1886
|
+
}
|
|
1887
|
+
console.log(`Импортировано записей: ${total}`);
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
throw new Error("Команды import: file PATH --dataset NAME, folder PATH.");
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
async function handleIndex(args) {
|
|
1894
|
+
const [action = "status", target, ...rest] = args;
|
|
1895
|
+
const options = parseOptions(rest);
|
|
1896
|
+
if (action === "status") {
|
|
1897
|
+
printKeyValue(getIndexStatus());
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
if (action === "folder") {
|
|
1901
|
+
if (!target) throw new Error("Пример: iola index folder ./docs");
|
|
1902
|
+
const count = await indexFolder(target, options);
|
|
1903
|
+
console.log(`Проиндексировано документов: ${count}`);
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (action === "search") {
|
|
1907
|
+
const query = [target, ...rest].filter(Boolean).join(" ");
|
|
1908
|
+
if (!query) throw new Error('Пример: iola index search "школа 29"');
|
|
1909
|
+
printTable(searchDocs(query, Number(options.limit || 20)), [["file", "Файл"], ["title", "Название"], ["snippet", "Фрагмент"]]);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
throw new Error("Команды index: status, folder PATH, search TEXT.");
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
async function handleReports(args) {
|
|
1916
|
+
const [action = "list", name, ...rest] = args;
|
|
1917
|
+
const packs = {
|
|
1918
|
+
"education-passport": ["education-contacts", "licenses"],
|
|
1919
|
+
"data-quality-pack": ["schools-summary", "missing-phones"],
|
|
1920
|
+
};
|
|
1921
|
+
if (action === "list") {
|
|
1922
|
+
printTable(Object.entries(packs).map(([pack, reports]) => ({ pack, reports: reports.join(", ") })), [["pack", "Пакет"], ["reports", "Отчеты"]]);
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (action === "run") {
|
|
1926
|
+
if (!packs[name]) throw new Error(`Пакет неизвестен: ${Object.keys(packs).join(", ")}`);
|
|
1927
|
+
const options = parseOptions(rest);
|
|
1928
|
+
const dir = options.output || path.join(process.cwd(), `iola-report-${name}-${Date.now()}`);
|
|
1929
|
+
await mkdir(dir, { recursive: true });
|
|
1930
|
+
for (const report of packs[name]) {
|
|
1931
|
+
await handleExport([report, "--format", "xlsx", "--output", path.join(dir, `${report}.xlsx`)]);
|
|
1932
|
+
await handleExport([report, "--format", "docx", "--output", path.join(dir, `${report}.docx`)]);
|
|
1933
|
+
}
|
|
1934
|
+
saveArtifact("report-pack", name, dir, { reports: packs[name] });
|
|
1935
|
+
console.log(`Пакет отчетов создан: ${dir}`);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
throw new Error("Команды reports: list, run NAME [--output DIR].");
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
async function handlePlugins(args) {
|
|
1942
|
+
const [action = "list", name, ...rest] = args;
|
|
1943
|
+
if (action === "list" || action === "ls") {
|
|
1944
|
+
printTable(listPlugins(), [["name", "Plugin"], ["source", "Источник"], ["command", "Команда"]]);
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
if (action === "install") {
|
|
1948
|
+
const options = parseOptions(rest);
|
|
1949
|
+
if (!name) throw new Error("Пример: iola plugins install my-plugin --command \"iola quality\"");
|
|
1950
|
+
savePlugin(name, options.source || name, options.command || "");
|
|
1951
|
+
console.log(`Plugin установлен: ${name}`);
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (action === "run") {
|
|
1955
|
+
const plugin = getPlugin(name);
|
|
1956
|
+
if (!plugin.command) throw new Error("У plugin нет command.");
|
|
1957
|
+
await main(splitCommandLine(plugin.command));
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
if (action === "remove" || action === "delete") {
|
|
1961
|
+
deletePlugin(name);
|
|
1962
|
+
console.log(`Plugin удален: ${name}`);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
throw new Error("Команды plugins: list, install NAME --command CMD, run NAME, remove NAME.");
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1793
1968
|
async function handleWorkspace(args) {
|
|
1794
1969
|
const [action = "status", nameOrPath] = args;
|
|
1795
1970
|
const config = await loadConfig();
|
|
@@ -2309,7 +2484,13 @@ async function handleMcp(args) {
|
|
|
2309
2484
|
return;
|
|
2310
2485
|
}
|
|
2311
2486
|
|
|
2312
|
-
|
|
2487
|
+
if (action === "serve") {
|
|
2488
|
+
const config = await loadConfig();
|
|
2489
|
+
await startMcpServer(config.daemon?.host || "127.0.0.1", Number(config.daemon?.port || DAEMON_PORT) + 1);
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
throw new Error("Команды mcp: status, list, install codex, remove codex, serve.");
|
|
2313
2494
|
}
|
|
2314
2495
|
|
|
2315
2496
|
async function handleCache(args) {
|
|
@@ -3195,6 +3376,39 @@ function initDatabase() {
|
|
|
3195
3376
|
path TEXT NOT NULL,
|
|
3196
3377
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3197
3378
|
);
|
|
3379
|
+
CREATE TABLE IF NOT EXISTS pending_changes (
|
|
3380
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3381
|
+
kind TEXT NOT NULL,
|
|
3382
|
+
target TEXT NOT NULL,
|
|
3383
|
+
before_text TEXT,
|
|
3384
|
+
after_text TEXT NOT NULL,
|
|
3385
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
3386
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3387
|
+
applied_at TEXT
|
|
3388
|
+
);
|
|
3389
|
+
CREATE TABLE IF NOT EXISTS custom_records (
|
|
3390
|
+
dataset TEXT NOT NULL,
|
|
3391
|
+
record_key TEXT NOT NULL,
|
|
3392
|
+
record_json TEXT NOT NULL,
|
|
3393
|
+
searchable_text TEXT NOT NULL,
|
|
3394
|
+
imported_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
3395
|
+
PRIMARY KEY(dataset, record_key)
|
|
3396
|
+
);
|
|
3397
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS custom_records_fts USING fts5(dataset, record_key, searchable_text);
|
|
3398
|
+
CREATE TABLE IF NOT EXISTS doc_index (
|
|
3399
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3400
|
+
file TEXT NOT NULL,
|
|
3401
|
+
title TEXT,
|
|
3402
|
+
content TEXT NOT NULL,
|
|
3403
|
+
indexed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3404
|
+
);
|
|
3405
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS doc_index_fts USING fts5(file, title, content);
|
|
3406
|
+
CREATE TABLE IF NOT EXISTS plugins (
|
|
3407
|
+
name TEXT PRIMARY KEY,
|
|
3408
|
+
source TEXT NOT NULL,
|
|
3409
|
+
command TEXT,
|
|
3410
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
3411
|
+
);
|
|
3198
3412
|
`);
|
|
3199
3413
|
rebuildFtsIfEmpty(db);
|
|
3200
3414
|
db.prepare(`
|
|
@@ -3249,6 +3463,8 @@ function getDbStatus() {
|
|
|
3249
3463
|
const cron = db.prepare("SELECT COUNT(*) AS count FROM cron_jobs").get();
|
|
3250
3464
|
const tasks = db.prepare("SELECT COUNT(*) AS count FROM tasks WHERE status != 'done'").get();
|
|
3251
3465
|
const artifacts = db.prepare("SELECT COUNT(*) AS count FROM artifacts").get();
|
|
3466
|
+
const docs = db.prepare("SELECT COUNT(*) AS count FROM doc_index").get();
|
|
3467
|
+
const custom = db.prepare("SELECT COUNT(*) AS count FROM custom_records").get();
|
|
3252
3468
|
return {
|
|
3253
3469
|
status: "ok",
|
|
3254
3470
|
file: DB_FILE,
|
|
@@ -3262,6 +3478,8 @@ function getDbStatus() {
|
|
|
3262
3478
|
cron_jobs: cron?.count ?? 0,
|
|
3263
3479
|
open_tasks: tasks?.count ?? 0,
|
|
3264
3480
|
artifacts: artifacts?.count ?? 0,
|
|
3481
|
+
indexed_docs: docs?.count ?? 0,
|
|
3482
|
+
custom_records: custom?.count ?? 0,
|
|
3265
3483
|
};
|
|
3266
3484
|
} finally {
|
|
3267
3485
|
db.close();
|
|
@@ -5055,17 +5273,29 @@ async function setupClient(args) {
|
|
|
5055
5273
|
console.log("Codex MCP и skill установлены.");
|
|
5056
5274
|
}
|
|
5057
5275
|
|
|
5276
|
+
async function onboard(args = []) {
|
|
5277
|
+
const options = parseOptions(args);
|
|
5278
|
+
showBanner();
|
|
5279
|
+
initDatabase();
|
|
5280
|
+
await handleConfig(["validate"]);
|
|
5281
|
+
await doctor(["--summary"]);
|
|
5282
|
+
if (options.yes || await confirm("Инициализировать workspace? [Y/n] ")) await handleWorkspace(["init"]);
|
|
5283
|
+
if (options.yes || await confirm("Применить policy analyst? [Y/n] ")) await handlePolicy(["use", "analyst"]);
|
|
5284
|
+
if (options.yes || await confirm("Настроить AI сейчас? [y/N] ")) await aiSetup([]);
|
|
5285
|
+
console.log("Onboard завершен.");
|
|
5286
|
+
}
|
|
5287
|
+
|
|
5058
5288
|
function parseOptions(args) {
|
|
5059
5289
|
const result = { _: [] };
|
|
5060
5290
|
|
|
5061
5291
|
for (let index = 0; index < args.length; index += 1) {
|
|
5062
5292
|
const arg = args[index];
|
|
5063
|
-
if (arg === "--json" || arg === "--yes" || arg === "--silent" || arg === "--events" || arg === "--no-history" || arg === "--summary" || arg === "--all" || arg === "--local" || arg === "--cache" || arg === "--tools" || arg === "--files" || arg === "--plan" || arg === "--trace" || arg === "--diff" || arg === "--fts" || arg === "--bare" || arg === "--quiet" || arg === "--no-color" || arg === "--fail-on-empty" || arg === "--debug" || arg === "--fix" || arg === "--append") {
|
|
5293
|
+
if (arg === "--json" || arg === "--yes" || arg === "--silent" || arg === "--events" || arg === "--no-history" || arg === "--summary" || arg === "--all" || arg === "--local" || arg === "--cache" || arg === "--tools" || arg === "--files" || arg === "--plan" || arg === "--trace" || arg === "--diff" || arg === "--stage" || arg === "--fts" || arg === "--bare" || arg === "--quiet" || arg === "--no-color" || arg === "--fail-on-empty" || arg === "--debug" || arg === "--fix" || arg === "--append") {
|
|
5064
5294
|
result[arg.slice(2)] = true;
|
|
5065
5295
|
} else if (arg === "--check" || arg === "--upgrade-node") {
|
|
5066
5296
|
result.check = true;
|
|
5067
5297
|
result[arg.slice(2)] = true;
|
|
5068
|
-
} 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 === "--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 === "--debug-file") {
|
|
5298
|
+
} 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 === "--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 === "--debug-file") {
|
|
5069
5299
|
result[arg.slice(2)] = args[index + 1];
|
|
5070
5300
|
index += 1;
|
|
5071
5301
|
} else {
|
|
@@ -5432,6 +5662,34 @@ async function startDaemon(host, port) {
|
|
|
5432
5662
|
await new Promise(() => {});
|
|
5433
5663
|
}
|
|
5434
5664
|
|
|
5665
|
+
async function startMcpServer(host, port) {
|
|
5666
|
+
const server = createServer(async (req, res) => {
|
|
5667
|
+
try {
|
|
5668
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
5669
|
+
if (req.method !== "POST") {
|
|
5670
|
+
res.end(JSON.stringify({ name: "iola-local-mcp", tools: ["status", "search", "card", "quality", "files.search", "index.search"] }));
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5673
|
+
const payload = JSON.parse(await readRequestBody(req) || "{}");
|
|
5674
|
+
const method = payload.method === "tools/call" ? payload.params?.name : payload.method;
|
|
5675
|
+
const args = payload.params?.arguments || payload.params || {};
|
|
5676
|
+
let result;
|
|
5677
|
+
if (method === "index.search") result = searchDocs(args.query || "", Number(args.limit || 20));
|
|
5678
|
+
else result = await executeRpc(method, { ...args, _: [] });
|
|
5679
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: payload.id || null, result }));
|
|
5680
|
+
} catch (error) {
|
|
5681
|
+
res.statusCode = 500;
|
|
5682
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { message: error instanceof Error ? error.message : String(error) } }));
|
|
5683
|
+
}
|
|
5684
|
+
});
|
|
5685
|
+
await new Promise((resolve, reject) => {
|
|
5686
|
+
server.once("error", reject);
|
|
5687
|
+
server.listen(port, host, resolve);
|
|
5688
|
+
});
|
|
5689
|
+
console.log(`iola local MCP запущен: http://${host}:${port}`);
|
|
5690
|
+
await new Promise(() => {});
|
|
5691
|
+
}
|
|
5692
|
+
|
|
5435
5693
|
function readRequestBody(req) {
|
|
5436
5694
|
return new Promise((resolve, reject) => {
|
|
5437
5695
|
let body = "";
|
|
@@ -5784,6 +6042,208 @@ async function restoreSnapshot(id) {
|
|
|
5784
6042
|
await cp(row.path, row.workspace, { recursive: true, force: true });
|
|
5785
6043
|
}
|
|
5786
6044
|
|
|
6045
|
+
async function stageFileChange(kind, target, afterText, beforeText = null) {
|
|
6046
|
+
const { resolved, relative } = await resolveFileTarget(target, kind === "patch" ? "edit" : "write");
|
|
6047
|
+
const before = beforeText ?? (existsSync(resolved) ? await readFile(resolved, "utf8").catch(() => "") : "");
|
|
6048
|
+
initDatabase();
|
|
6049
|
+
const db = openDatabase();
|
|
6050
|
+
try {
|
|
6051
|
+
const result = db.prepare("INSERT INTO pending_changes(kind, target, before_text, after_text) VALUES (?, ?, ?, ?)").run(kind, relative, before, afterText);
|
|
6052
|
+
return Number(result.lastInsertRowid);
|
|
6053
|
+
} finally {
|
|
6054
|
+
db.close();
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
|
|
6058
|
+
function listChanges() {
|
|
6059
|
+
initDatabase();
|
|
6060
|
+
const db = openDatabase();
|
|
6061
|
+
try {
|
|
6062
|
+
return db.prepare("SELECT id, kind, target, status, created_at FROM pending_changes ORDER BY id DESC LIMIT 100").all();
|
|
6063
|
+
} finally {
|
|
6064
|
+
db.close();
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
|
|
6068
|
+
function getChange(id) {
|
|
6069
|
+
initDatabase();
|
|
6070
|
+
const db = openDatabase();
|
|
6071
|
+
try {
|
|
6072
|
+
const row = db.prepare("SELECT * FROM pending_changes WHERE id = ?").get(id);
|
|
6073
|
+
if (!row) throw new Error(`Изменение не найдено: ${id}`);
|
|
6074
|
+
return row;
|
|
6075
|
+
} finally {
|
|
6076
|
+
db.close();
|
|
6077
|
+
}
|
|
6078
|
+
}
|
|
6079
|
+
|
|
6080
|
+
function updateChangeStatus(id, status) {
|
|
6081
|
+
initDatabase();
|
|
6082
|
+
const db = openDatabase();
|
|
6083
|
+
try {
|
|
6084
|
+
db.prepare("UPDATE pending_changes SET status = ?, applied_at = CASE WHEN ? = 'applied' THEN datetime('now') ELSE applied_at END WHERE id = ?").run(status, status, id);
|
|
6085
|
+
} finally {
|
|
6086
|
+
db.close();
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
|
|
6090
|
+
async function applyChange(id) {
|
|
6091
|
+
const change = getChange(id);
|
|
6092
|
+
if (change.status !== "pending") throw new Error(`Изменение уже не pending: ${change.status}`);
|
|
6093
|
+
await filesWrite(change.target, change.after_text);
|
|
6094
|
+
updateChangeStatus(id, "applied");
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6097
|
+
async function importDataFile(target, dataset) {
|
|
6098
|
+
const text = await filesRead(target, { maxBytes: 5_000_000 });
|
|
6099
|
+
const ext = path.extname(target).toLocaleLowerCase("ru-RU");
|
|
6100
|
+
let rows = [];
|
|
6101
|
+
if (ext === ".json") {
|
|
6102
|
+
const parsed = JSON.parse(text);
|
|
6103
|
+
rows = Array.isArray(parsed) ? parsed : normalizeItems(parsed);
|
|
6104
|
+
} else if (ext === ".csv") {
|
|
6105
|
+
rows = parseCsv(text);
|
|
6106
|
+
} else {
|
|
6107
|
+
throw new Error("Поддерживается импорт JSON и CSV.");
|
|
6108
|
+
}
|
|
6109
|
+
saveCustomRecords(dataset, rows);
|
|
6110
|
+
return rows.length;
|
|
6111
|
+
}
|
|
6112
|
+
|
|
6113
|
+
function parseCsv(text) {
|
|
6114
|
+
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
6115
|
+
const headers = splitCsvLine(lines.shift() || "");
|
|
6116
|
+
return lines.map((line) => {
|
|
6117
|
+
const values = splitCsvLine(line);
|
|
6118
|
+
return Object.fromEntries(headers.map((header, index) => [header, values[index] || ""]));
|
|
6119
|
+
});
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
function splitCsvLine(line) {
|
|
6123
|
+
const result = [];
|
|
6124
|
+
let current = "";
|
|
6125
|
+
let quote = false;
|
|
6126
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
6127
|
+
const char = line[index];
|
|
6128
|
+
if (char === '"') quote = !quote;
|
|
6129
|
+
else if (char === "," && !quote) {
|
|
6130
|
+
result.push(current);
|
|
6131
|
+
current = "";
|
|
6132
|
+
} else current += char;
|
|
6133
|
+
}
|
|
6134
|
+
result.push(current);
|
|
6135
|
+
return result.map((value) => value.trim());
|
|
6136
|
+
}
|
|
6137
|
+
|
|
6138
|
+
function saveCustomRecords(dataset, rows) {
|
|
6139
|
+
initDatabase();
|
|
6140
|
+
const db = openDatabase();
|
|
6141
|
+
try {
|
|
6142
|
+
const insert = db.prepare("INSERT INTO custom_records(dataset, record_key, record_json, searchable_text) VALUES (?, ?, ?, ?) ON CONFLICT(dataset, record_key) DO UPDATE SET record_json = excluded.record_json, searchable_text = excluded.searchable_text, imported_at = datetime('now')");
|
|
6143
|
+
const insertFts = db.prepare("INSERT INTO custom_records_fts(dataset, record_key, searchable_text) VALUES (?, ?, ?)");
|
|
6144
|
+
db.prepare("DELETE FROM custom_records_fts WHERE dataset = ?").run(dataset);
|
|
6145
|
+
rows.forEach((row, index) => {
|
|
6146
|
+
const key = String(row.id || row.inn || index + 1);
|
|
6147
|
+
const json = JSON.stringify(row);
|
|
6148
|
+
const text = json.toLocaleLowerCase("ru-RU");
|
|
6149
|
+
insert.run(dataset, key, json, text);
|
|
6150
|
+
insertFts.run(dataset, key, text);
|
|
6151
|
+
});
|
|
6152
|
+
} finally {
|
|
6153
|
+
db.close();
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
async function indexFolder(target, options = {}) {
|
|
6158
|
+
const rows = await filesTree(target, { depth: Number(options.depth || 5), limit: Number(options.limit || 1000) });
|
|
6159
|
+
let count = 0;
|
|
6160
|
+
for (const row of rows.filter((item) => item.type === "file" && /\.(md|txt|csv|json|html)$/i.test(item.path))) {
|
|
6161
|
+
try {
|
|
6162
|
+
const text = await filesRead(row.path, { maxBytes: 1_000_000 });
|
|
6163
|
+
saveIndexedDoc(row.path, path.basename(row.path), text);
|
|
6164
|
+
count += 1;
|
|
6165
|
+
} catch {
|
|
6166
|
+
// Skip unreadable files.
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
return count;
|
|
6170
|
+
}
|
|
6171
|
+
|
|
6172
|
+
function saveIndexedDoc(file, title, content) {
|
|
6173
|
+
initDatabase();
|
|
6174
|
+
const db = openDatabase();
|
|
6175
|
+
try {
|
|
6176
|
+
const result = db.prepare("INSERT INTO doc_index(file, title, content) VALUES (?, ?, ?)").run(file, title, content);
|
|
6177
|
+
db.prepare("INSERT INTO doc_index_fts(rowid, file, title, content) VALUES (?, ?, ?, ?)").run(Number(result.lastInsertRowid), file, title, content);
|
|
6178
|
+
} finally {
|
|
6179
|
+
db.close();
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
|
|
6183
|
+
function getIndexStatus() {
|
|
6184
|
+
initDatabase();
|
|
6185
|
+
const db = openDatabase();
|
|
6186
|
+
try {
|
|
6187
|
+
const docs = db.prepare("SELECT COUNT(*) AS count FROM doc_index").get();
|
|
6188
|
+
return { docs: docs?.count || 0 };
|
|
6189
|
+
} finally {
|
|
6190
|
+
db.close();
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
|
|
6194
|
+
function searchDocs(query, limit = 20) {
|
|
6195
|
+
initDatabase();
|
|
6196
|
+
const db = openDatabase();
|
|
6197
|
+
try {
|
|
6198
|
+
const rows = db.prepare("SELECT file, title, snippet(doc_index_fts, 2, '[', ']', '...', 16) AS snippet FROM doc_index_fts WHERE doc_index_fts MATCH ? LIMIT ?").all(toFtsQuery(query), limit);
|
|
6199
|
+
return rows;
|
|
6200
|
+
} finally {
|
|
6201
|
+
db.close();
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6205
|
+
function listPlugins() {
|
|
6206
|
+
initDatabase();
|
|
6207
|
+
const db = openDatabase();
|
|
6208
|
+
try {
|
|
6209
|
+
return db.prepare("SELECT name, source, COALESCE(command, '-') AS command FROM plugins ORDER BY name").all();
|
|
6210
|
+
} finally {
|
|
6211
|
+
db.close();
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
function savePlugin(name, source, command) {
|
|
6216
|
+
initDatabase();
|
|
6217
|
+
const db = openDatabase();
|
|
6218
|
+
try {
|
|
6219
|
+
db.prepare("INSERT INTO plugins(name, source, command) VALUES (?, ?, ?) ON CONFLICT(name) DO UPDATE SET source = excluded.source, command = excluded.command").run(name, source, command);
|
|
6220
|
+
} finally {
|
|
6221
|
+
db.close();
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
function getPlugin(name) {
|
|
6226
|
+
initDatabase();
|
|
6227
|
+
const db = openDatabase();
|
|
6228
|
+
try {
|
|
6229
|
+
const row = db.prepare("SELECT * FROM plugins WHERE name = ?").get(name);
|
|
6230
|
+
if (!row) throw new Error(`Plugin не найден: ${name}`);
|
|
6231
|
+
return row;
|
|
6232
|
+
} finally {
|
|
6233
|
+
db.close();
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
function deletePlugin(name) {
|
|
6238
|
+
initDatabase();
|
|
6239
|
+
const db = openDatabase();
|
|
6240
|
+
try {
|
|
6241
|
+
db.prepare("DELETE FROM plugins WHERE name = ?").run(name);
|
|
6242
|
+
} finally {
|
|
6243
|
+
db.close();
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
|
|
5787
6247
|
function unifiedPreview(before, after) {
|
|
5788
6248
|
const beforeLines = before.split(/\r?\n/);
|
|
5789
6249
|
const afterLines = after.split(/\r?\n/);
|
|
@@ -5831,6 +6291,9 @@ async function executeRpc(method, options = {}) {
|
|
|
5831
6291
|
if (method === "files.search") {
|
|
5832
6292
|
return filesSearch(options.query || options.search || "", options);
|
|
5833
6293
|
}
|
|
6294
|
+
if (method === "index.search") {
|
|
6295
|
+
return searchDocs(options.query || options.search || "", Number(options.limit || 20));
|
|
6296
|
+
}
|
|
5834
6297
|
throw new Error(`RPC method неизвестен: ${method}. Доступно: status, search, card, quality, sync.`);
|
|
5835
6298
|
}
|
|
5836
6299
|
|
package/wiki/Home.md
CHANGED
|
@@ -31,6 +31,7 @@ iola ask "найди школу 29"
|
|
|
31
31
|
- [Skills и toolsets](Skills-и-toolsets)
|
|
32
32
|
- [Локальные файлы](Локальные-файлы)
|
|
33
33
|
- [Рабочая среда агента](Рабочая-среда-агента)
|
|
34
|
+
- [Расширения и локальные данные](Расширения-и-локальные-данные)
|
|
34
35
|
- [Daemon, RPC и cron](Daemon-RPC-и-cron)
|
|
35
36
|
- [Контекст и память](Контекст-и-память)
|
|
36
37
|
- [Команды](Команды)
|
|
@@ -57,6 +57,12 @@ iola artifacts list
|
|
|
57
57
|
iola snapshot list
|
|
58
58
|
iola trace last
|
|
59
59
|
iola export education-contacts --format xlsx --output contacts.xlsx
|
|
60
|
+
iola changes list
|
|
61
|
+
iola import file data.csv --dataset custom
|
|
62
|
+
iola index folder ./docs
|
|
63
|
+
iola reports list
|
|
64
|
+
iola plugins list
|
|
65
|
+
iola mcp serve
|
|
60
66
|
iola memory suggest
|
|
61
67
|
```
|
|
62
68
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Расширения и локальные данные
|
|
2
|
+
|
|
3
|
+
Staged changes:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
iola files write report.md --text "..." --stage
|
|
7
|
+
iola files patch README.md --search old --replace new --stage
|
|
8
|
+
iola changes list
|
|
9
|
+
iola changes show 1
|
|
10
|
+
iola changes apply 1
|
|
11
|
+
iola changes discard 1
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Импорт локальных данных:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
iola import file data.csv --dataset custom-schools
|
|
18
|
+
iola import file data.json --dataset custom
|
|
19
|
+
iola import folder ./data
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Индекс документов:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
iola index folder ./docs
|
|
26
|
+
iola index status
|
|
27
|
+
iola index search "школа 29"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Пакеты отчетов:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
iola reports list
|
|
34
|
+
iola reports run education-passport
|
|
35
|
+
iola reports run data-quality-pack
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Plugins:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
iola plugins list
|
|
42
|
+
iola plugins install quality --command "iola quality"
|
|
43
|
+
iola plugins run quality
|
|
44
|
+
iola plugins remove quality
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Локальный MCP endpoint:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
iola mcp serve
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
По умолчанию MCP запускается на порту `daemon.port + 1`.
|
|
54
|
+
|