@tolinax/ayoune-cli 2026.7.2 → 2026.8.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/data/modelsAndRights.js +6 -0
- package/index.js +0 -2
- package/lib/commands/createAggregateCommand.js +2 -0
- package/lib/commands/createDbCommand.js +3 -0
- package/lib/helpers/initializeSettings.js +20 -6
- package/lib/operations/handleEditOperation.js +2 -0
- package/lib/prompts/promptEntry.js +2 -0
- package/lib/prompts/promptFilePath.js +2 -0
- package/package.json +1 -1
- package/lib/db/copyConfigStore.js +0 -88
- package/lib/db/copyEngine.js +0 -87
- package/lib/db/cronMatcher.js +0 -42
- package/lib/db/types.js +0 -1
package/data/modelsAndRights.js
CHANGED
|
@@ -1281,6 +1281,12 @@ const modelsAndRights = [
|
|
|
1281
1281
|
module: "config",
|
|
1282
1282
|
right: "config.datapools",
|
|
1283
1283
|
},
|
|
1284
|
+
{
|
|
1285
|
+
plural: "DataTargets",
|
|
1286
|
+
singular: "DataTarget",
|
|
1287
|
+
module: "config",
|
|
1288
|
+
right: "config.datatargets",
|
|
1289
|
+
},
|
|
1284
1290
|
{
|
|
1285
1291
|
plural: "Decisions",
|
|
1286
1292
|
singular: "Decision",
|
package/index.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { createSpinner } from "nanospinner";
|
|
4
|
-
import { initializeSettings } from "./lib/helpers/initializeSettings.js";
|
|
5
4
|
import { createProgram } from "./lib/commands/createProgram.js";
|
|
6
5
|
//Create new command instance
|
|
7
6
|
const program = new Command();
|
|
8
|
-
initializeSettings();
|
|
9
7
|
//Setup spinner - output to stderr so stdout stays clean for piping
|
|
10
8
|
export const spinner = createSpinner("Getting data...", { stream: process.stderr });
|
|
11
9
|
createProgram(program);
|
|
@@ -6,6 +6,7 @@ import { spinner } from "../../index.js";
|
|
|
6
6
|
import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
7
7
|
import { cliError } from "../helpers/cliError.js";
|
|
8
8
|
import { readPipelineInput } from "../helpers/readPipelineInput.js";
|
|
9
|
+
import { initializeSettings } from "../helpers/initializeSettings.js";
|
|
9
10
|
function wrapAggResult(res) {
|
|
10
11
|
if (Array.isArray(res)) {
|
|
11
12
|
return { payload: res, meta: { resultCount: res.length } };
|
|
@@ -252,6 +253,7 @@ Examples:
|
|
|
252
253
|
cliError("Wizard requires an interactive terminal (TTY)", EXIT_MISUSE);
|
|
253
254
|
}
|
|
254
255
|
// Step 1: Select model
|
|
256
|
+
initializeSettings();
|
|
255
257
|
spinner.start({ text: "Loading models...", color: "magenta" });
|
|
256
258
|
const modelsRes = await apiCallHandler("aggregation", "models", "get");
|
|
257
259
|
spinner.stop();
|
|
@@ -5,6 +5,7 @@ import { EXIT_GENERAL_ERROR, EXIT_MISUSE } from "../exitCodes.js";
|
|
|
5
5
|
import { cliError } from "../helpers/cliError.js";
|
|
6
6
|
import { handleResponseFormatOptions } from "../helpers/handleResponseFormatOptions.js";
|
|
7
7
|
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
8
|
+
import { initializeSettings } from "../helpers/initializeSettings.js";
|
|
8
9
|
function maskUri(uri) {
|
|
9
10
|
return uri.replace(/:\/\/([^:]+):([^@]+)@/, "://***:***@");
|
|
10
11
|
}
|
|
@@ -239,6 +240,7 @@ async function resolveQuery(slugOrId) {
|
|
|
239
240
|
return Array.isArray(entries) ? entries[0] : entries;
|
|
240
241
|
}
|
|
241
242
|
async function promptTarget() {
|
|
243
|
+
initializeSettings();
|
|
242
244
|
spinner.start({ text: "Loading targets...", color: "cyan" });
|
|
243
245
|
const res = await apiCallHandler("config", "datatargets", "get", null, { limit: 100, active: true });
|
|
244
246
|
spinner.stop();
|
|
@@ -256,6 +258,7 @@ async function promptTarget() {
|
|
|
256
258
|
return selected;
|
|
257
259
|
}
|
|
258
260
|
async function promptQuery() {
|
|
261
|
+
initializeSettings();
|
|
259
262
|
spinner.start({ text: "Loading queries...", color: "magenta" });
|
|
260
263
|
const res = await apiCallHandler("config", "queries", "get", null, {
|
|
261
264
|
limit: 100,
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
//Initializes settings for system environment and inquirer
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import inquirerFileTreeSelection from "inquirer-file-tree-selection-prompt";
|
|
6
|
-
import inquirerTableInput from "inquirer-table-input";
|
|
7
|
-
import InterruptedPrompt from "inquirer-interrupted-prompt";
|
|
2
|
+
// Lazy-loaded: inquirer plugins are heavy (~4000 modules incl. rxjs).
|
|
3
|
+
// Only register them when an interactive prompt is actually needed.
|
|
4
|
+
let _initialized = false;
|
|
8
5
|
export function initializeSettings() {
|
|
6
|
+
if (_initialized)
|
|
7
|
+
return;
|
|
8
|
+
_initialized = true;
|
|
9
|
+
const inquirer = require("inquirer");
|
|
10
|
+
const inquirerFuzzyPath = require("inquirer-fuzzy-path");
|
|
11
|
+
const inquirerSearchList = require("inquirer-search-list");
|
|
12
|
+
const inquirerFileTreeSelection = require("inquirer-file-tree-selection-prompt");
|
|
13
|
+
const inquirerTableInput = require("inquirer-table-input");
|
|
14
|
+
const InterruptedPrompt = require("inquirer-interrupted-prompt");
|
|
9
15
|
inquirer.registerPrompt("fuzzypath", inquirerFuzzyPath);
|
|
10
16
|
inquirer.registerPrompt("search-list", inquirerSearchList);
|
|
11
17
|
inquirer.registerPrompt("file-tree-selection", inquirerFileTreeSelection);
|
|
12
18
|
inquirer.registerPrompt("table-input", inquirerTableInput);
|
|
13
19
|
InterruptedPrompt.fromAll(inquirer);
|
|
14
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns the inquirer instance with all custom prompts registered.
|
|
23
|
+
* Call this instead of importing inquirer directly when using custom prompt types.
|
|
24
|
+
*/
|
|
25
|
+
export function getInquirer() {
|
|
26
|
+
initializeSettings();
|
|
27
|
+
return require("inquirer");
|
|
28
|
+
}
|
|
@@ -2,8 +2,10 @@ import inquirer from "inquirer";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { apiCallHandler } from "../api/apiCallHandler.js";
|
|
4
4
|
import { spinner } from "../../index.js";
|
|
5
|
+
import { initializeSettings } from "../helpers/initializeSettings.js";
|
|
5
6
|
export async function handleEditOperation(module, collection, editContent) {
|
|
6
7
|
console.log(editContent);
|
|
8
|
+
initializeSettings();
|
|
7
9
|
const { edits } = await inquirer.prompt([
|
|
8
10
|
{
|
|
9
11
|
type: "table-input",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
|
+
import { initializeSettings } from "../helpers/initializeSettings.js";
|
|
2
3
|
function getEntryLabel(el) {
|
|
3
4
|
const name = el.name || el.title || el.subject || el.label || el.originalname || el.summary;
|
|
4
5
|
if (name) {
|
|
@@ -7,6 +8,7 @@ function getEntryLabel(el) {
|
|
|
7
8
|
return el._id;
|
|
8
9
|
}
|
|
9
10
|
export async function promptEntry(result) {
|
|
11
|
+
initializeSettings();
|
|
10
12
|
const { entry } = await inquirer.prompt([
|
|
11
13
|
{
|
|
12
14
|
type: "search-list",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { initializeSettings } from "../helpers/initializeSettings.js";
|
|
4
5
|
export async function promptFilePath(fp) {
|
|
6
|
+
initializeSettings();
|
|
5
7
|
const { filePath } = await inquirer.prompt([
|
|
6
8
|
{
|
|
7
9
|
type: "fuzzypath",
|
package/package.json
CHANGED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import crypto from "crypto";
|
|
5
|
-
const CONFIG_PATH = path.join(os.homedir(), ".config", "ayoune", "db-copies.json");
|
|
6
|
-
const ALGORITHM = "aes-256-cbc";
|
|
7
|
-
function deriveKey() {
|
|
8
|
-
const seed = `ayoune-cli:${os.hostname()}:${os.userInfo().username}`;
|
|
9
|
-
return crypto.createHash("sha256").update(seed).digest();
|
|
10
|
-
}
|
|
11
|
-
function encrypt(text) {
|
|
12
|
-
const key = deriveKey();
|
|
13
|
-
const iv = crypto.randomBytes(16);
|
|
14
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
15
|
-
let encrypted = cipher.update(text, "utf8", "hex");
|
|
16
|
-
encrypted += cipher.final("hex");
|
|
17
|
-
return iv.toString("hex") + ":" + encrypted;
|
|
18
|
-
}
|
|
19
|
-
function decrypt(data) {
|
|
20
|
-
const key = deriveKey();
|
|
21
|
-
const [ivHex, encrypted] = data.split(":");
|
|
22
|
-
if (!ivHex || !encrypted)
|
|
23
|
-
return data;
|
|
24
|
-
try {
|
|
25
|
-
const iv = Buffer.from(ivHex, "hex");
|
|
26
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
27
|
-
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
28
|
-
decrypted += decipher.final("utf8");
|
|
29
|
-
return decrypted;
|
|
30
|
-
}
|
|
31
|
-
catch (_a) {
|
|
32
|
-
return data;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export function maskUri(uri) {
|
|
36
|
-
return uri.replace(/:\/\/([^:]+):([^@]+)@/, "://***:***@");
|
|
37
|
-
}
|
|
38
|
-
export function loadCopyConfigs() {
|
|
39
|
-
if (!existsSync(CONFIG_PATH))
|
|
40
|
-
return [];
|
|
41
|
-
try {
|
|
42
|
-
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
43
|
-
}
|
|
44
|
-
catch (_a) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
export function saveCopyConfigs(configs) {
|
|
49
|
-
const dir = path.dirname(CONFIG_PATH);
|
|
50
|
-
if (!existsSync(dir))
|
|
51
|
-
mkdirSync(dir, { recursive: true });
|
|
52
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(configs, null, 2), "utf-8");
|
|
53
|
-
}
|
|
54
|
-
export function addCopyConfig(config) {
|
|
55
|
-
const configs = loadCopyConfigs();
|
|
56
|
-
// Encrypt URIs before saving
|
|
57
|
-
config.fromUri = encrypt(config.fromUri);
|
|
58
|
-
config.toUri = encrypt(config.toUri);
|
|
59
|
-
configs.push(config);
|
|
60
|
-
saveCopyConfigs(configs);
|
|
61
|
-
}
|
|
62
|
-
export function removeCopyConfig(id) {
|
|
63
|
-
const configs = loadCopyConfigs();
|
|
64
|
-
const filtered = configs.filter((c) => c.id !== id);
|
|
65
|
-
if (filtered.length === configs.length)
|
|
66
|
-
return false;
|
|
67
|
-
saveCopyConfigs(filtered);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
export function getDecryptedConfig(config) {
|
|
71
|
-
return {
|
|
72
|
-
fromUri: decrypt(config.fromUri),
|
|
73
|
-
toUri: decrypt(config.toUri),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
export function updateCopyConfigLastRun(id, status, error) {
|
|
77
|
-
const configs = loadCopyConfigs();
|
|
78
|
-
const config = configs.find((c) => c.id === id);
|
|
79
|
-
if (config) {
|
|
80
|
-
config.lastRun = new Date().toISOString();
|
|
81
|
-
config.lastStatus = status;
|
|
82
|
-
if (error)
|
|
83
|
-
config.lastError = error;
|
|
84
|
-
else
|
|
85
|
-
delete config.lastError;
|
|
86
|
-
saveCopyConfigs(configs);
|
|
87
|
-
}
|
|
88
|
-
}
|
package/lib/db/copyEngine.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { MongoClient } from "mongodb";
|
|
2
|
-
/**
|
|
3
|
-
* Writes aggregation query results to a target MongoDB collection.
|
|
4
|
-
* This is the core of the copy flow: query results from the aggregation API
|
|
5
|
-
* are written to an external MongoDB target.
|
|
6
|
-
*/
|
|
7
|
-
export async function writeToTarget(targetUri, database, collection, docs, writeMode, batchSize = 1000) {
|
|
8
|
-
const startTime = Date.now();
|
|
9
|
-
const client = new MongoClient(targetUri);
|
|
10
|
-
let written = 0;
|
|
11
|
-
let errors = 0;
|
|
12
|
-
try {
|
|
13
|
-
await client.connect();
|
|
14
|
-
const db = database ? client.db(database) : client.db();
|
|
15
|
-
const col = db.collection(collection);
|
|
16
|
-
if (writeMode === "replace") {
|
|
17
|
-
try {
|
|
18
|
-
await col.drop();
|
|
19
|
-
}
|
|
20
|
-
catch (_a) {
|
|
21
|
-
// Collection may not exist
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
// Write in batches
|
|
25
|
-
for (let i = 0; i < docs.length; i += batchSize) {
|
|
26
|
-
const batch = docs.slice(i, i + batchSize);
|
|
27
|
-
const result = await writeBatch(col, batch, writeMode === "upsert");
|
|
28
|
-
written += result.written;
|
|
29
|
-
errors += result.errors;
|
|
30
|
-
}
|
|
31
|
-
return { written, errors, duration: Date.now() - startTime };
|
|
32
|
-
}
|
|
33
|
-
finally {
|
|
34
|
-
await client.close();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function writeBatch(collection, docs, upsert) {
|
|
38
|
-
var _a, _b, _c, _d;
|
|
39
|
-
try {
|
|
40
|
-
if (upsert) {
|
|
41
|
-
const ops = docs.map((doc) => ({
|
|
42
|
-
updateOne: {
|
|
43
|
-
filter: { _id: doc._id },
|
|
44
|
-
update: { $set: doc },
|
|
45
|
-
upsert: true,
|
|
46
|
-
},
|
|
47
|
-
}));
|
|
48
|
-
const result = await collection.bulkWrite(ops, { ordered: false });
|
|
49
|
-
return { written: (result.upsertedCount || 0) + (result.modifiedCount || 0), errors: 0 };
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
const result = await collection.insertMany(docs, { ordered: false });
|
|
53
|
-
return { written: result.insertedCount, errors: 0 };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
const written = (_d = (_b = (_a = e.result) === null || _a === void 0 ? void 0 : _a.nInserted) !== null && _b !== void 0 ? _b : (_c = e.result) === null || _c === void 0 ? void 0 : _c.insertedCount) !== null && _d !== void 0 ? _d : 0;
|
|
58
|
-
return { written, errors: docs.length - written };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
export async function getDbStats(uri) {
|
|
62
|
-
const client = new MongoClient(uri);
|
|
63
|
-
try {
|
|
64
|
-
await client.connect();
|
|
65
|
-
const db = client.db();
|
|
66
|
-
const stats = await db.stats();
|
|
67
|
-
const collList = await db.listCollections().toArray();
|
|
68
|
-
const collections = [];
|
|
69
|
-
for (const col of collList) {
|
|
70
|
-
if (col.name.startsWith("system."))
|
|
71
|
-
continue;
|
|
72
|
-
try {
|
|
73
|
-
const count = await db.collection(col.name).estimatedDocumentCount();
|
|
74
|
-
const colStats = await db.command({ collStats: col.name });
|
|
75
|
-
collections.push({ name: col.name, documents: count, size: colStats.size || 0 });
|
|
76
|
-
}
|
|
77
|
-
catch (_a) {
|
|
78
|
-
collections.push({ name: col.name, documents: 0, size: 0 });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
collections.sort((a, b) => b.documents - a.documents);
|
|
82
|
-
return { database: stats.db, collections, totalSize: stats.dataSize || 0 };
|
|
83
|
-
}
|
|
84
|
-
finally {
|
|
85
|
-
await client.close();
|
|
86
|
-
}
|
|
87
|
-
}
|
package/lib/db/cronMatcher.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import cronParser from "cron-parser";
|
|
2
|
-
/**
|
|
3
|
-
* Determines whether a cron-scheduled job is due for execution.
|
|
4
|
-
* Returns true if the current time has passed the next scheduled run after lastRun.
|
|
5
|
-
*/
|
|
6
|
-
export function isCronDue(expression, lastRun, now = new Date()) {
|
|
7
|
-
try {
|
|
8
|
-
if (!lastRun)
|
|
9
|
-
return true; // Never run before → always due
|
|
10
|
-
const lastRunDate = new Date(lastRun);
|
|
11
|
-
const interval = cronParser.parseExpression(expression, { currentDate: lastRunDate });
|
|
12
|
-
const nextRun = interval.next().toDate();
|
|
13
|
-
return now >= nextRun;
|
|
14
|
-
}
|
|
15
|
-
catch (_a) {
|
|
16
|
-
return false; // Invalid cron expression
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Returns the next scheduled run time for a cron expression.
|
|
21
|
-
*/
|
|
22
|
-
export function getNextRun(expression, after = new Date()) {
|
|
23
|
-
try {
|
|
24
|
-
const interval = cronParser.parseExpression(expression, { currentDate: after });
|
|
25
|
-
return interval.next().toDate();
|
|
26
|
-
}
|
|
27
|
-
catch (_a) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Validates a cron expression. Returns null if valid, error message if invalid.
|
|
33
|
-
*/
|
|
34
|
-
export function validateCron(expression) {
|
|
35
|
-
try {
|
|
36
|
-
cronParser.parseExpression(expression);
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
catch (e) {
|
|
40
|
-
return e.message || "Invalid cron expression";
|
|
41
|
-
}
|
|
42
|
-
}
|
package/lib/db/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|