@aiaiai-pt/martha-cli 0.6.0 → 0.7.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 +6 -0
- package/dist/index.js +81 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.7.0] — 2026-06-09
|
|
8
|
+
|
|
9
|
+
### Added — #522 bulk-ingest observability + management
|
|
10
|
+
- `document-sync sources status <id>` — ingestion progress for a source: counts by status (pending / ingesting / ready / error), recent drain rate, naive ETA, and the top grouped error reasons. `--window-minutes` tunes the rate window; JSON mode emits the raw summary.
|
|
11
|
+
- `documents retry --collection <id-or-slug-or-name> --status error [--yes]` — re-drive a collection's failed documents (subtree-scoped). Bounded + idempotent: flips matching docs back to `pending` for the ingestion reconciler to re-drive through the per-tenant quota gate at its capped rate (no thundering herd). Non-TTY requires `--yes`; JSON mode emits the result. `--status pending` re-drives stuck-pending docs.
|
|
12
|
+
|
|
7
13
|
### Added — #407 connections command (service-account Drive auth)
|
|
8
14
|
- `martha connections create|list|update|test|delete` — manage Vault-backed integration connections from the CLI (previously admin-UI / raw-curl only). `create` mirrors `POST /api/admin/connections`; `update` mirrors `PUT`.
|
|
9
15
|
- Service-account Google Drive: `martha connections create --integration google_drive --auth-type service_account --credential-value @sa-key.json --config '{"subject":"...","scopes":[...]}'` reads enterprise Shared Drives without CASA (#407). The SA key shape (`type`, `client_email`) is validated before submit; `--credential-value` accepts `@file` / `-` (stdin) / literal.
|
package/dist/index.js
CHANGED
|
@@ -14136,11 +14136,79 @@ ${source_default.bold(`Sources (${result.sources.length} chunks):`)}`);
|
|
|
14136
14136
|
}
|
|
14137
14137
|
}
|
|
14138
14138
|
});
|
|
14139
|
+
cmd.command("retry").description("Re-drive ingestion for a collection's errored documents. Bounded " + "and idempotent — flips matching docs back to pending; the " + "ingestion reconciler re-drives them at its capped rate (no herd).").requiredOption("--collection <ref>", "Collection id, slug, or name").option("--status <status>", "Which docs to retry: error | pending", "error").option("--yes", "Skip confirmation (required in non-interactive mode)").action(async (opts) => {
|
|
14140
|
+
if (!["error", "pending"].includes(opts.status)) {
|
|
14141
|
+
throw new CLIError("--status must be one of: error, pending", 4 /* Validation */);
|
|
14142
|
+
}
|
|
14143
|
+
const ctx = getCtx();
|
|
14144
|
+
const collection = await resolveCollection(ctx, opts.collection);
|
|
14145
|
+
if (!opts.yes) {
|
|
14146
|
+
if (!process.stdin.isTTY) {
|
|
14147
|
+
throw new CLIError("Cannot confirm in non-interactive mode. Use --yes to retry.", 1 /* Error */);
|
|
14148
|
+
}
|
|
14149
|
+
const ok = await confirm(`Retry ${opts.status} documents in '${collection.name}'?`);
|
|
14150
|
+
if (!ok) {
|
|
14151
|
+
if (isJson()) {
|
|
14152
|
+
console.log(JSON.stringify({ reset_count: 0, cancelled: true }));
|
|
14153
|
+
} else {
|
|
14154
|
+
console.log("Cancelled.");
|
|
14155
|
+
}
|
|
14156
|
+
return;
|
|
14157
|
+
}
|
|
14158
|
+
}
|
|
14159
|
+
const result = await ctx.api.post(`/api/admin/collections/${encodeURIComponent(collection.id)}/retry-ingestion`, undefined, { params: { status: opts.status } });
|
|
14160
|
+
if (isJson()) {
|
|
14161
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14162
|
+
return;
|
|
14163
|
+
}
|
|
14164
|
+
console.log(source_default.green(`Re-drove ${result.reset_count} ${opts.status} document(s) in ` + `'${collection.name}'. The ingestion reconciler will pick them ` + `up within ~1 min (rate-limited by the per-tenant quota).`));
|
|
14165
|
+
});
|
|
14139
14166
|
}
|
|
14140
14167
|
|
|
14141
14168
|
// src/commands/document-sync.ts
|
|
14142
14169
|
init_errors();
|
|
14143
14170
|
var VALID_MODES = ["polling", "evented", "manual"];
|
|
14171
|
+
function formatEta(seconds) {
|
|
14172
|
+
if (seconds === null || seconds === undefined)
|
|
14173
|
+
return "—";
|
|
14174
|
+
if (seconds < 60)
|
|
14175
|
+
return "<1m";
|
|
14176
|
+
const mins = Math.round(seconds / 60);
|
|
14177
|
+
if (mins < 60)
|
|
14178
|
+
return `${mins}m`;
|
|
14179
|
+
const hours = Math.floor(mins / 60);
|
|
14180
|
+
const rem = mins % 60;
|
|
14181
|
+
return rem ? `${hours}h ${rem}m` : `${hours}h`;
|
|
14182
|
+
}
|
|
14183
|
+
function progressBar(ready, total, width = 24) {
|
|
14184
|
+
if (total <= 0)
|
|
14185
|
+
return source_default.dim("·".repeat(width));
|
|
14186
|
+
const filled = Math.min(width, Math.round(ready / total * width));
|
|
14187
|
+
return source_default.green("█".repeat(filled)) + source_default.dim("░".repeat(width - filled));
|
|
14188
|
+
}
|
|
14189
|
+
function printIngestionStatus(sourceId, s) {
|
|
14190
|
+
const c = s.counts;
|
|
14191
|
+
const pct = s.total > 0 ? Math.round(c.ready / s.total * 100) : 0;
|
|
14192
|
+
console.log(source_default.bold(`
|
|
14193
|
+
Ingestion status — source ${sourceId}`));
|
|
14194
|
+
console.log(source_default.dim("-".repeat(48)));
|
|
14195
|
+
console.log(` ${progressBar(c.ready, s.total)} ${pct}% (${c.ready}/${s.total} ready)`);
|
|
14196
|
+
console.log(` ${source_default.yellow("pending")} ${String(c.pending).padStart(6)}` + ` ${source_default.cyan("ingesting")} ${String(c.ingesting).padStart(6)}`);
|
|
14197
|
+
console.log(` ${source_default.green("ready")} ${String(c.ready).padStart(6)}` + ` ${source_default.red("error")} ${String(c.error).padStart(6)}`);
|
|
14198
|
+
if (c.other > 0) {
|
|
14199
|
+
console.log(` ${source_default.dim("other")} ${String(c.other).padStart(6)}`);
|
|
14200
|
+
}
|
|
14201
|
+
console.log(source_default.dim("-".repeat(48)));
|
|
14202
|
+
console.log(` Drain rate : ${source_default.cyan(s.drain_rate_per_min.toFixed(1))}/min` + source_default.dim(` (last ${s.drain_window_minutes}m, ${s.completed_in_window} completed)`));
|
|
14203
|
+
console.log(` ETA : ${source_default.cyan(formatEta(s.eta_seconds))}`);
|
|
14204
|
+
if (s.top_errors.length > 0) {
|
|
14205
|
+
console.log(source_default.dim("-".repeat(48)));
|
|
14206
|
+
console.log(source_default.bold(" Top errors:"));
|
|
14207
|
+
for (const e of s.top_errors) {
|
|
14208
|
+
console.log(` ${source_default.red(String(e.count).padStart(4))} ${e.reason}`);
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
}
|
|
14144
14212
|
function registerDocumentSyncCommands(program2) {
|
|
14145
14213
|
const cmd = program2.command("document-sync").description("Manage durable document sync sources (Google Drive, etc.)");
|
|
14146
14214
|
function getCtx() {
|
|
@@ -14269,6 +14337,19 @@ No google_drive sources found.`));
|
|
|
14269
14337
|
console.log(source_default.green(`Started ${op} for source ${sourceId} ` + `(workflow=${resp.workflow_id}, status=${resp.status})`));
|
|
14270
14338
|
});
|
|
14271
14339
|
}
|
|
14340
|
+
sources.command("status <source_id>").description("Show ingestion progress for a source: counts by status, drain " + "rate, ETA, and top error reasons.").option("--window-minutes <n>", "Drain-rate window in minutes (1–1440)", "15").action(async (sourceId, opts) => {
|
|
14341
|
+
const windowMinutes = Number.parseInt(opts.windowMinutes, 10);
|
|
14342
|
+
if (!Number.isFinite(windowMinutes) || windowMinutes < 1 || windowMinutes > 1440) {
|
|
14343
|
+
throw new CLIError("--window-minutes must be an integer between 1 and 1440.", 4 /* Validation */);
|
|
14344
|
+
}
|
|
14345
|
+
const ctx = getCtx();
|
|
14346
|
+
const summary = await ctx.api.get(`/api/admin/document-sync/sources/${encodeURIComponent(sourceId)}/status`, { params: { window_minutes: String(windowMinutes) } });
|
|
14347
|
+
if (isJson()) {
|
|
14348
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
14349
|
+
return;
|
|
14350
|
+
}
|
|
14351
|
+
printIngestionStatus(sourceId, summary);
|
|
14352
|
+
});
|
|
14272
14353
|
}
|
|
14273
14354
|
|
|
14274
14355
|
// src/commands/approvals.ts
|