ente-cli 0.4.81 → 0.4.83
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 +1 -0
- package/dist/SKILL.md +8 -0
- package/dist/cli.mjs +629 -176
- package/dist/cli.mjs.map +4 -4
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -3800,7 +3800,7 @@ async function runCli(cli2, argv, context) {
|
|
|
3800
3800
|
}
|
|
3801
3801
|
|
|
3802
3802
|
// ente/version.ts
|
|
3803
|
-
var VERSION = "0.4.
|
|
3803
|
+
var VERSION = "0.4.83";
|
|
3804
3804
|
|
|
3805
3805
|
// ente/branding.ts
|
|
3806
3806
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
@@ -10861,6 +10861,7 @@ async function makeCaller(opts) {
|
|
|
10861
10861
|
convexUrl: opts.convexUrl,
|
|
10862
10862
|
query: (ref, ...args) => withRetry(() => client.query(ref, ...args)),
|
|
10863
10863
|
mutation: (ref, ...args) => withRetry(() => client.mutation(ref, ...args)),
|
|
10864
|
+
mutationUnqueued: (ref, ...args) => withRetry(() => client.mutation(ref, args[0] ?? {}, { skipQueue: true })),
|
|
10864
10865
|
action: (ref, ...args) => withRetry(() => client.action(ref, ...args))
|
|
10865
10866
|
};
|
|
10866
10867
|
}
|
|
@@ -11739,6 +11740,48 @@ var PinsList = class extends ProfileCommand {
|
|
|
11739
11740
|
};
|
|
11740
11741
|
var PINS_COMMANDS = [PinsList];
|
|
11741
11742
|
|
|
11743
|
+
// ente/pinTargets.ts
|
|
11744
|
+
async function pinToTargets(caller, entityId, targets, opts) {
|
|
11745
|
+
const result = { succeeded: [], failures: [] };
|
|
11746
|
+
if (targets.length === 0) return result;
|
|
11747
|
+
let ids = [];
|
|
11748
|
+
try {
|
|
11749
|
+
ids = await resolveEntitiesC(caller, targets);
|
|
11750
|
+
} catch (exc) {
|
|
11751
|
+
for (const target of targets) result.failures.push({ target, error: `could not resolve target: ${errorMessage(exc)}` });
|
|
11752
|
+
return result;
|
|
11753
|
+
}
|
|
11754
|
+
for (let i = 0; i < ids.length; i++) {
|
|
11755
|
+
const target = targets[i];
|
|
11756
|
+
try {
|
|
11757
|
+
await caller.mutation(pinsCreate, {
|
|
11758
|
+
sourceEntityId: entityId,
|
|
11759
|
+
targetEntityId: ids[i],
|
|
11760
|
+
...opts.shareParents ? {
|
|
11761
|
+
grantMissingParentAccess: true,
|
|
11762
|
+
...opts.parentSpaceGrantFingerprint ? { parentSpaceGrantFingerprint: opts.parentSpaceGrantFingerprint } : {}
|
|
11763
|
+
} : {}
|
|
11764
|
+
});
|
|
11765
|
+
result.succeeded.push(target);
|
|
11766
|
+
} catch (exc) {
|
|
11767
|
+
result.failures.push({ target, error: errorMessage(exc) });
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
return result;
|
|
11771
|
+
}
|
|
11772
|
+
function failOnPinFailures(err, label, entityId, result) {
|
|
11773
|
+
if (result.failures.length === 0) return;
|
|
11774
|
+
err("");
|
|
11775
|
+
err(
|
|
11776
|
+
`\u26A0 ${label} (${entityId}) created, but ${result.failures.length} of ${result.failures.length + result.succeeded.length} pin(s) failed:`
|
|
11777
|
+
);
|
|
11778
|
+
for (const { target, error } of result.failures) {
|
|
11779
|
+
err(` - '${target}': ${error}`);
|
|
11780
|
+
err(` retry: ente entity pin ${entityId} "${target}"`);
|
|
11781
|
+
}
|
|
11782
|
+
throw new ExitWithCode(2);
|
|
11783
|
+
}
|
|
11784
|
+
|
|
11742
11785
|
// ente/commands/entity.ts
|
|
11743
11786
|
var entitiesDuplicate = makeFunctionReference("entities:duplicate");
|
|
11744
11787
|
var entitiesCreate = makeFunctionReference("entities:create");
|
|
@@ -11875,59 +11918,23 @@ var EntityCreate = class extends ProfileCommand {
|
|
|
11875
11918
|
if (this.description) {
|
|
11876
11919
|
await caller.mutation(entitiesSetDescriptionFromMarkdown, { entityId, markdown: this.description });
|
|
11877
11920
|
}
|
|
11878
|
-
const
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
try {
|
|
11883
|
-
pinIds = await resolveEntitiesC(caller, this.pin);
|
|
11884
|
-
} catch (exc) {
|
|
11885
|
-
for (const target of this.pin) pinFailures.push([target, `could not resolve target: ${errorMessage(exc)}`]);
|
|
11886
|
-
}
|
|
11887
|
-
for (let i = 0; i < pinIds.length; i++) {
|
|
11888
|
-
const targetLabel = this.pin[i];
|
|
11889
|
-
try {
|
|
11890
|
-
await caller.mutation(pinsCreate, {
|
|
11891
|
-
sourceEntityId: entityId,
|
|
11892
|
-
targetEntityId: pinIds[i],
|
|
11893
|
-
...this.shareParents ? {
|
|
11894
|
-
grantMissingParentAccess: true,
|
|
11895
|
-
...this.parentSpaceGrantFingerprint ? { parentSpaceGrantFingerprint: this.parentSpaceGrantFingerprint } : {}
|
|
11896
|
-
} : {}
|
|
11897
|
-
});
|
|
11898
|
-
pinSucceeded.push(targetLabel);
|
|
11899
|
-
} catch (exc) {
|
|
11900
|
-
pinFailures.push([targetLabel, errorMessage(exc)]);
|
|
11901
|
-
}
|
|
11902
|
-
}
|
|
11903
|
-
}
|
|
11921
|
+
const pins = await pinToTargets(caller, entityId, this.pin ?? [], {
|
|
11922
|
+
shareParents: this.shareParents,
|
|
11923
|
+
...this.parentSpaceGrantFingerprint ? { parentSpaceGrantFingerprint: this.parentSpaceGrantFingerprint } : {}
|
|
11924
|
+
});
|
|
11904
11925
|
const spaces = await spacesFooterFromMemberships(caller);
|
|
11905
11926
|
const jsonOut = { data: { entityId } };
|
|
11906
|
-
if (
|
|
11927
|
+
if (pins.failures.length) {
|
|
11907
11928
|
jsonOut.error = { message: "Entity created, but some parent relationships failed.", code: "PARTIAL_SUCCESS" };
|
|
11908
|
-
jsonOut.data = {
|
|
11909
|
-
entityId,
|
|
11910
|
-
pinFailures: pinFailures.map(([target, error]) => ({ target, error })),
|
|
11911
|
-
pinSucceeded
|
|
11912
|
-
};
|
|
11929
|
+
jsonOut.data = { entityId, pinFailures: pins.failures, pinSucceeded: pins.succeeded };
|
|
11913
11930
|
}
|
|
11914
11931
|
this.emitText(jsonWithSpaces(jsonOut, spaces), () => {
|
|
11915
11932
|
let body = `Created ${this.type} '${this.name}' (${entityId})`;
|
|
11916
|
-
if (
|
|
11917
|
-
Pinned to: ${
|
|
11933
|
+
if (pins.succeeded.length) body += `
|
|
11934
|
+
Pinned to: ${pins.succeeded.join(", ")}`;
|
|
11918
11935
|
return withFooter(body, spaces);
|
|
11919
11936
|
});
|
|
11920
|
-
|
|
11921
|
-
this.err("");
|
|
11922
|
-
this.err(
|
|
11923
|
-
`\u26A0 Entity '${this.name}' (${entityId}) created, but ${pinFailures.length} of ${pinFailures.length + pinSucceeded.length} pin(s) failed:`
|
|
11924
|
-
);
|
|
11925
|
-
for (const [target, msg] of pinFailures) {
|
|
11926
|
-
this.err(` - '${target}': ${msg}`);
|
|
11927
|
-
this.err(` retry: ente entity pin ${entityId} "${target}"`);
|
|
11928
|
-
}
|
|
11929
|
-
throw new ExitWithCode(2);
|
|
11930
|
-
}
|
|
11937
|
+
failOnPinFailures((line) => this.err(line), `Entity '${this.name}'`, entityId, pins);
|
|
11931
11938
|
}
|
|
11932
11939
|
};
|
|
11933
11940
|
var EntityShowCorrection = class extends ProfileCommand {
|
|
@@ -13720,24 +13727,11 @@ var TASK_COMMANDS = [
|
|
|
13720
13727
|
];
|
|
13721
13728
|
|
|
13722
13729
|
// ente/commands/list.ts
|
|
13730
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
13731
|
+
|
|
13732
|
+
// ente/databaseList.ts
|
|
13723
13733
|
var COLUMN_TYPES = ["text", "number", "boolean", "single-select", "multi-select"];
|
|
13724
13734
|
var SELECT_TYPES = /* @__PURE__ */ new Set(["single-select", "multi-select"]);
|
|
13725
|
-
var DATABASE_LIST_TABS = ["database", "activities"];
|
|
13726
|
-
var entitiesCreate2 = makeFunctionReference("entities:create");
|
|
13727
|
-
var updateDatabaseView = makeFunctionReference("entities:updateDatabaseView");
|
|
13728
|
-
var setDescriptionFromMarkdown = makeFunctionReference("entities:setDescriptionFromMarkdown");
|
|
13729
|
-
var setEnabledTabs = makeFunctionReference("entities:setEnabledTabs");
|
|
13730
|
-
var setCustomIcon = makeFunctionReference("entities:setCustomIcon");
|
|
13731
|
-
var removeDatabaseColumn = makeFunctionReference("entities:removeDatabaseColumn");
|
|
13732
|
-
var findById = makeFunctionReference(
|
|
13733
|
-
"entities:findById"
|
|
13734
|
-
);
|
|
13735
|
-
var listSpacesForEntity = makeFunctionReference(
|
|
13736
|
-
"entities:listSpacesForEntity"
|
|
13737
|
-
);
|
|
13738
|
-
var activitiesCreate3 = makeFunctionReference("activities:create");
|
|
13739
|
-
var setAttributeValue = makeFunctionReference("activities:setAttributeValue");
|
|
13740
|
-
var listRows = makeFunctionReference("activities:listEntityDatabaseRowsPaginated");
|
|
13741
13735
|
function parseColumn(spec) {
|
|
13742
13736
|
const parts = spec.split(":");
|
|
13743
13737
|
const keyPart = (parts[0] ?? "").trim();
|
|
@@ -13750,6 +13744,10 @@ function parseColumn(spec) {
|
|
|
13750
13744
|
if (!/^[a-z0-9_-]+$/.test(key))
|
|
13751
13745
|
throw new Error(`Invalid column key '${key}': use lowercase letters, numbers, '_' or '-'.`);
|
|
13752
13746
|
if (eq !== -1 && !label) throw new Error(`Invalid --column '${spec}': empty label after '='.`);
|
|
13747
|
+
if (typeRaw === "date")
|
|
13748
|
+
throw new Error(
|
|
13749
|
+
`Invalid --column '${spec}': ente database lists have no date column type yet. Store an ISO date (YYYY-MM-DD) in a text column.`
|
|
13750
|
+
);
|
|
13753
13751
|
if (!COLUMN_TYPES.includes(typeRaw))
|
|
13754
13752
|
throw new Error(`Invalid --column '${spec}': type must be one of ${COLUMN_TYPES.join(", ")}.`);
|
|
13755
13753
|
const type = typeRaw;
|
|
@@ -13762,22 +13760,324 @@ function parseColumn(spec) {
|
|
|
13762
13760
|
}
|
|
13763
13761
|
return col;
|
|
13764
13762
|
}
|
|
13765
|
-
|
|
13766
|
-
|
|
13763
|
+
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "yes", "1", "on"]);
|
|
13764
|
+
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "no", "0", "off"]);
|
|
13765
|
+
function assertOption(column, value) {
|
|
13766
|
+
const options = column.options ?? [];
|
|
13767
|
+
if (options.length > 0 && !options.some((o) => o.value === value))
|
|
13768
|
+
throw new Error(
|
|
13769
|
+
`'${value}' is not an option of column '${column.key}'. Options: ${options.map((o) => o.value).join(", ")}.`
|
|
13770
|
+
);
|
|
13771
|
+
}
|
|
13772
|
+
function coerceValue(column, raw) {
|
|
13773
|
+
const where = `column '${column.key}' (${column.type})`;
|
|
13774
|
+
if (raw === null || raw === "") return null;
|
|
13775
|
+
switch (column.type) {
|
|
13767
13776
|
case "number": {
|
|
13768
|
-
|
|
13769
|
-
if (
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
13778
|
+
if (typeof raw === "string") {
|
|
13779
|
+
const n = Number(raw.trim());
|
|
13780
|
+
if (raw.trim() !== "" && Number.isFinite(n)) return n;
|
|
13781
|
+
}
|
|
13782
|
+
throw new Error(`Value ${JSON.stringify(raw)} is not a number for ${where}.`);
|
|
13783
|
+
}
|
|
13784
|
+
case "boolean": {
|
|
13785
|
+
if (typeof raw === "boolean") return raw;
|
|
13786
|
+
const word = typeof raw === "string" ? raw.trim().toLowerCase() : "";
|
|
13787
|
+
if (TRUE_WORDS.has(word)) return true;
|
|
13788
|
+
if (FALSE_WORDS.has(word)) return false;
|
|
13789
|
+
throw new Error(`Value ${JSON.stringify(raw)} is not a boolean for ${where}; use true/false.`);
|
|
13790
|
+
}
|
|
13791
|
+
case "multi-select": {
|
|
13792
|
+
const values = Array.isArray(raw) ? raw.map((v2) => {
|
|
13793
|
+
if (typeof v2 !== "string") throw new Error(`Value ${JSON.stringify(raw)} must list strings for ${where}.`);
|
|
13794
|
+
return v2.trim();
|
|
13795
|
+
}) : typeof raw === "string" ? raw.split(",").map((s) => s.trim()) : null;
|
|
13796
|
+
if (!values) throw new Error(`Value ${JSON.stringify(raw)} is not a list for ${where}.`);
|
|
13797
|
+
const kept = values.filter((s) => s.length > 0);
|
|
13798
|
+
for (const v2 of kept) assertOption(column, v2);
|
|
13799
|
+
return kept;
|
|
13800
|
+
}
|
|
13801
|
+
case "single-select":
|
|
13802
|
+
case "text": {
|
|
13803
|
+
if (typeof raw !== "string") throw new Error(`Value ${JSON.stringify(raw)} is not text for ${where}.`);
|
|
13804
|
+
if (column.type === "single-select") assertOption(column, raw);
|
|
13777
13805
|
return raw;
|
|
13806
|
+
}
|
|
13807
|
+
}
|
|
13808
|
+
}
|
|
13809
|
+
function columnFor(columns, key, listName) {
|
|
13810
|
+
const col = columns.find((c) => c.key === key);
|
|
13811
|
+
if (!col)
|
|
13812
|
+
throw new Error(
|
|
13813
|
+
`'${key}' is not a column of list '${listName}'. Columns: ${columns.map((c) => c.key).join(", ") || "(none)"}. Add it with: ente list column add "${listName}" ${key}:text`
|
|
13814
|
+
);
|
|
13815
|
+
return col;
|
|
13816
|
+
}
|
|
13817
|
+
function coerceAttributes(columns, raw, listName) {
|
|
13818
|
+
const out = {};
|
|
13819
|
+
for (const [key, value] of Object.entries(raw)) out[key] = coerceValue(columnFor(columns, key, listName), value);
|
|
13820
|
+
return out;
|
|
13821
|
+
}
|
|
13822
|
+
function sameValue(a, b) {
|
|
13823
|
+
const empty = (v2) => v2 === void 0 || v2 === null || Array.isArray(v2) && v2.length === 0;
|
|
13824
|
+
if (empty(a) || empty(b)) return empty(a) && empty(b);
|
|
13825
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
13826
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
13827
|
+
return a.every((v2, i) => v2 === b[i]);
|
|
13828
|
+
}
|
|
13829
|
+
return a === b;
|
|
13830
|
+
}
|
|
13831
|
+
function parseWhere(columns, specs, listName) {
|
|
13832
|
+
return specs.map((spec) => {
|
|
13833
|
+
const eq = spec.indexOf("=");
|
|
13834
|
+
if (eq <= 0) throw new Error(`Invalid --where '${spec}'. Use key=value (empty value matches empty cells).`);
|
|
13835
|
+
const key = spec.slice(0, eq);
|
|
13836
|
+
const column = columnFor(columns, key, listName);
|
|
13837
|
+
return { key, value: coerceValue(column, spec.slice(eq + 1)) };
|
|
13838
|
+
});
|
|
13839
|
+
}
|
|
13840
|
+
function matchesWhere(attributes, clauses) {
|
|
13841
|
+
const attrs = attributes ?? {};
|
|
13842
|
+
return clauses.every(({ key, value }) => {
|
|
13843
|
+
const cell = attrs[key];
|
|
13844
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
13845
|
+
return Array.isArray(cell) && value.every((v2) => cell.includes(v2));
|
|
13846
|
+
}
|
|
13847
|
+
return sameValue(cell, value);
|
|
13848
|
+
});
|
|
13849
|
+
}
|
|
13850
|
+
function planUpsert(existing, inputs, keyColumn) {
|
|
13851
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
13852
|
+
for (const row of existing) {
|
|
13853
|
+
const raw = row.attributes?.[keyColumn];
|
|
13854
|
+
if (typeof raw !== "string" || raw === "") continue;
|
|
13855
|
+
const other = byKey.get(raw);
|
|
13856
|
+
if (other)
|
|
13857
|
+
throw new Error(
|
|
13858
|
+
`The list already holds two rows with ${keyColumn}=${JSON.stringify(raw)} (${other.rowId}, ${row.rowId}). Delete one with \`ente activity delete <rowId>\` before syncing.`
|
|
13859
|
+
);
|
|
13860
|
+
byKey.set(raw, row);
|
|
13861
|
+
}
|
|
13862
|
+
const keyless = /* @__PURE__ */ new Map();
|
|
13863
|
+
for (const row of existing) {
|
|
13864
|
+
const raw = row.attributes?.[keyColumn];
|
|
13865
|
+
if ((typeof raw !== "string" || raw === "") && row.title) keyless.set(row.title, row.rowId);
|
|
13866
|
+
}
|
|
13867
|
+
const seen = /* @__PURE__ */ new Map();
|
|
13868
|
+
const steps = inputs.map((input) => {
|
|
13869
|
+
const key = input.attributes[keyColumn];
|
|
13870
|
+
if (typeof key !== "string" || key.trim() === "")
|
|
13871
|
+
throw new Error(`Line ${input.line}: missing a text value for key column '${keyColumn}'.`);
|
|
13872
|
+
const firstLine = seen.get(key);
|
|
13873
|
+
if (firstLine !== void 0)
|
|
13874
|
+
throw new Error(`Line ${input.line}: ${keyColumn}=${JSON.stringify(key)} repeats line ${firstLine}.`);
|
|
13875
|
+
seen.set(key, input.line);
|
|
13876
|
+
const row = byKey.get(key);
|
|
13877
|
+
if (!row) return { action: "create", input, key };
|
|
13878
|
+
const stored = row.attributes ?? {};
|
|
13879
|
+
const changes = {};
|
|
13880
|
+
for (const [k, v2] of Object.entries(input.attributes)) {
|
|
13881
|
+
if (!sameValue(stored[k], v2)) changes[k] = v2;
|
|
13882
|
+
}
|
|
13883
|
+
const title = input.title !== void 0 && input.title !== (row.title ?? "") ? input.title : void 0;
|
|
13884
|
+
if (Object.keys(changes).length === 0 && title === void 0)
|
|
13885
|
+
return { action: "unchanged", input, key, rowId: row.rowId };
|
|
13886
|
+
return { action: "update", input, key, rowId: row.rowId, changes, ...title !== void 0 ? { title } : {} };
|
|
13887
|
+
});
|
|
13888
|
+
const orphans = steps.filter((step) => step.action === "create" && step.input.title !== void 0 && keyless.has(step.input.title)).map((step) => `line ${step.input.line} '${step.input.title}' \u2194 row ${keyless.get(step.input.title)}`);
|
|
13889
|
+
if (orphans.length > 0)
|
|
13890
|
+
throw new Error(
|
|
13891
|
+
`Rows without a ${keyColumn} value carry the title of rows about to be created (an interrupted earlier run?): ${orphans.join(
|
|
13892
|
+
"; "
|
|
13893
|
+
)}. Set their key with \`ente list update <list> <rowId> --attr ${keyColumn}=\u2026\` or trash them with \`ente activity delete <rowId>\`, then rerun.`
|
|
13894
|
+
);
|
|
13895
|
+
return steps;
|
|
13896
|
+
}
|
|
13897
|
+
function parseImportLines(text) {
|
|
13898
|
+
const rows = [];
|
|
13899
|
+
const lines = text.split(/\r?\n/);
|
|
13900
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13901
|
+
const source = lines[i].trim();
|
|
13902
|
+
if (!source) continue;
|
|
13903
|
+
const line = i + 1;
|
|
13904
|
+
let parsed;
|
|
13905
|
+
try {
|
|
13906
|
+
parsed = JSON.parse(source);
|
|
13907
|
+
} catch (err) {
|
|
13908
|
+
throw new Error(`Line ${line}: not valid JSON (${err instanceof Error ? err.message : String(err)}).`);
|
|
13909
|
+
}
|
|
13910
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
13911
|
+
throw new Error(`Line ${line}: expected a JSON object.`);
|
|
13912
|
+
const obj = parsed;
|
|
13913
|
+
const unknownKeys = Object.keys(obj).filter((k) => !["title", "attrs", "content"].includes(k));
|
|
13914
|
+
if (unknownKeys.length > 0)
|
|
13915
|
+
throw new Error(`Line ${line}: unknown field(s) ${unknownKeys.join(", ")}; allowed: title, attrs, content.`);
|
|
13916
|
+
if (obj.title !== void 0 && typeof obj.title !== "string") throw new Error(`Line ${line}: "title" must be a string.`);
|
|
13917
|
+
if (obj.content !== void 0 && typeof obj.content !== "string") throw new Error(`Line ${line}: "content" must be a string.`);
|
|
13918
|
+
const attrs = obj.attrs ?? {};
|
|
13919
|
+
if (!attrs || typeof attrs !== "object" || Array.isArray(attrs)) throw new Error(`Line ${line}: "attrs" must be an object.`);
|
|
13920
|
+
rows.push({
|
|
13921
|
+
line,
|
|
13922
|
+
attrs,
|
|
13923
|
+
...obj.title !== void 0 ? { title: obj.title } : {},
|
|
13924
|
+
...obj.content !== void 0 ? { content: obj.content } : {}
|
|
13925
|
+
});
|
|
13926
|
+
}
|
|
13927
|
+
return rows;
|
|
13928
|
+
}
|
|
13929
|
+
async function runPool(items, limit, work) {
|
|
13930
|
+
let next = 0;
|
|
13931
|
+
const failures = [];
|
|
13932
|
+
const worker = async () => {
|
|
13933
|
+
while (failures.length === 0 && next < items.length) {
|
|
13934
|
+
const item = items[next++];
|
|
13935
|
+
try {
|
|
13936
|
+
await work(item);
|
|
13937
|
+
} catch (error) {
|
|
13938
|
+
failures.push(error);
|
|
13939
|
+
}
|
|
13940
|
+
}
|
|
13941
|
+
};
|
|
13942
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
13943
|
+
if (failures.length === 1) throw failures[0];
|
|
13944
|
+
if (failures.length > 1)
|
|
13945
|
+
throw new Error(
|
|
13946
|
+
`${failures.length} writes failed:
|
|
13947
|
+
${failures.map((e) => ` - ${e instanceof Error ? e.message : String(e)}`).join("\n")}`
|
|
13948
|
+
);
|
|
13949
|
+
}
|
|
13950
|
+
|
|
13951
|
+
// ente/commands/list.ts
|
|
13952
|
+
var DATABASE_LIST_TABS = ["database", "activities"];
|
|
13953
|
+
var ROW_PAGE_SIZE = 100;
|
|
13954
|
+
var entitiesCreate2 = makeFunctionReference("entities:create");
|
|
13955
|
+
var updateDatabaseView = makeFunctionReference("entities:updateDatabaseView");
|
|
13956
|
+
var setDescriptionFromMarkdown = makeFunctionReference("entities:setDescriptionFromMarkdown");
|
|
13957
|
+
var setEnabledTabs = makeFunctionReference("entities:setEnabledTabs");
|
|
13958
|
+
var setCustomIcon = makeFunctionReference("entities:setCustomIcon");
|
|
13959
|
+
var removeDatabaseColumn = makeFunctionReference("entities:removeDatabaseColumn");
|
|
13960
|
+
var findById = makeFunctionReference(
|
|
13961
|
+
"entities:findById"
|
|
13962
|
+
);
|
|
13963
|
+
var listSpacesForEntity = makeFunctionReference(
|
|
13964
|
+
"entities:listSpacesForEntity"
|
|
13965
|
+
);
|
|
13966
|
+
var activitiesCreate3 = makeFunctionReference("activities:create");
|
|
13967
|
+
var activitiesGetByIdRich3 = makeFunctionReference(
|
|
13968
|
+
"activities:getByIdRich"
|
|
13969
|
+
);
|
|
13970
|
+
var activitiesUpdateTitle3 = makeFunctionReference("activities:updateTitle");
|
|
13971
|
+
var setAttributeValue = makeFunctionReference("activities:setAttributeValue");
|
|
13972
|
+
var listRows = makeFunctionReference("activities:listEntityDatabaseRowsPaginated");
|
|
13973
|
+
async function loadList(caller, listNameOrId) {
|
|
13974
|
+
const entityId = await resolveEntityC(caller, listNameOrId);
|
|
13975
|
+
const entity = await caller.query(findById, { id: entityId });
|
|
13976
|
+
if (!entity) throw new Error(`List '${listNameOrId}' not found or not accessible.`);
|
|
13977
|
+
const stored = entity.databaseView;
|
|
13978
|
+
return {
|
|
13979
|
+
entityId,
|
|
13980
|
+
entity,
|
|
13981
|
+
view: {
|
|
13982
|
+
version: 1,
|
|
13983
|
+
columns: stored?.columns ?? [],
|
|
13984
|
+
filters: stored?.filters ?? [],
|
|
13985
|
+
sorts: stored?.sorts ?? []
|
|
13986
|
+
}
|
|
13987
|
+
};
|
|
13988
|
+
}
|
|
13989
|
+
async function fetchAllRows(caller, entityId) {
|
|
13990
|
+
const rows = [];
|
|
13991
|
+
let cursor = null;
|
|
13992
|
+
for (; ; ) {
|
|
13993
|
+
const res = await caller.query(listRows, {
|
|
13994
|
+
entityId,
|
|
13995
|
+
ignoreViewFilters: true,
|
|
13996
|
+
paginationOpts: { numItems: ROW_PAGE_SIZE, cursor }
|
|
13997
|
+
});
|
|
13998
|
+
rows.push(...res.page);
|
|
13999
|
+
if (res.isDone) return rows;
|
|
14000
|
+
cursor = res.continueCursor;
|
|
14001
|
+
}
|
|
14002
|
+
}
|
|
14003
|
+
async function resolveOwnerSpace(caller, entityId, space) {
|
|
14004
|
+
if (space) return resolveSpaceByNameC(caller, space);
|
|
14005
|
+
const spaces = await caller.query(listSpacesForEntity, { entityId });
|
|
14006
|
+
if (spaces.length === 0) throw new Error("The list is in no accessible space; pass --space explicitly.");
|
|
14007
|
+
if (spaces.length > 1)
|
|
14008
|
+
throw new Error(
|
|
14009
|
+
`The list spans multiple spaces (${spaces.map((s) => s.name).join(", ")}); pass --space to choose one.`
|
|
14010
|
+
);
|
|
14011
|
+
return spaces[0]._id;
|
|
14012
|
+
}
|
|
14013
|
+
function rowJson(row) {
|
|
14014
|
+
return {
|
|
14015
|
+
rowId: row._id,
|
|
14016
|
+
title: row.title ?? null,
|
|
14017
|
+
type: row.type,
|
|
14018
|
+
completed: row.completed ?? false,
|
|
14019
|
+
createdAt: row._creationTime,
|
|
14020
|
+
attributes: row.attributes ?? {},
|
|
14021
|
+
entities: (row.entityDetails ?? []).filter((e) => e !== null).map((e) => ({ entityId: e._id, name: e.name }))
|
|
14022
|
+
};
|
|
14023
|
+
}
|
|
14024
|
+
function rowLine(row) {
|
|
14025
|
+
const attrStr = Object.entries(row.attributes ?? {}).filter(([, v2]) => v2 !== null && v2 !== void 0 && !(Array.isArray(v2) && v2.length === 0)).map(([k, v2]) => `${k}=${Array.isArray(v2) ? v2.join(",") : String(v2)}`).join(" ");
|
|
14026
|
+
return `- ${row.title || "(untitled)"} (${row._id})${attrStr ? ` [${attrStr}]` : ""}`;
|
|
14027
|
+
}
|
|
14028
|
+
async function applyStep(ctx, step) {
|
|
14029
|
+
const { caller } = ctx;
|
|
14030
|
+
if (step.action === "unchanged") return step.rowId;
|
|
14031
|
+
if (step.action === "update") {
|
|
14032
|
+
if (step.title !== void 0)
|
|
14033
|
+
await caller.mutationUnqueued(activitiesUpdateTitle3, { activityId: step.rowId, title: step.title });
|
|
14034
|
+
for (const [key, value] of Object.entries(step.changes))
|
|
14035
|
+
await caller.mutationUnqueued(setAttributeValue, { activityId: step.rowId, key, value });
|
|
14036
|
+
return step.rowId;
|
|
14037
|
+
}
|
|
14038
|
+
const { input } = step;
|
|
14039
|
+
const rowId = await caller.mutationUnqueued(activitiesCreate3, {
|
|
14040
|
+
entityId: ctx.entityId,
|
|
14041
|
+
ownerSpaceId: ctx.ownerSpaceId,
|
|
14042
|
+
type: ctx.type,
|
|
14043
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
14044
|
+
...ctx.deadline !== void 0 ? { deadline: ctx.deadline } : {},
|
|
14045
|
+
...input.content !== void 0 ? { contentMarkdown: input.content } : {}
|
|
14046
|
+
});
|
|
14047
|
+
const ordered = Object.entries(input.attributes).sort(
|
|
14048
|
+
([a], [b]) => a === ctx.keyColumn ? -1 : b === ctx.keyColumn ? 1 : 0
|
|
14049
|
+
);
|
|
14050
|
+
try {
|
|
14051
|
+
for (const [key, value] of ordered) {
|
|
14052
|
+
if (value === null || Array.isArray(value) && value.length === 0) continue;
|
|
14053
|
+
await caller.mutationUnqueued(setAttributeValue, { activityId: rowId, key, value });
|
|
14054
|
+
}
|
|
14055
|
+
} catch (exc) {
|
|
14056
|
+
throw new Error(`Line ${input.line}: row ${rowId} was created, but setting its cells failed: ${errorMessage(exc)}`);
|
|
13778
14057
|
}
|
|
14058
|
+
return rowId;
|
|
14059
|
+
}
|
|
14060
|
+
function stepResult(step, rowId) {
|
|
14061
|
+
const base = { line: step.input.line, key: step.key };
|
|
14062
|
+
const contentIgnored = step.action !== "create" && step.input.content !== void 0 ? { contentIgnored: true } : {};
|
|
14063
|
+
if (step.action === "create") return { ...base, action: "created", rowId };
|
|
14064
|
+
if (step.action === "unchanged") return { ...base, action: "unchanged", rowId: step.rowId, ...contentIgnored };
|
|
14065
|
+
return {
|
|
14066
|
+
...base,
|
|
14067
|
+
action: "updated",
|
|
14068
|
+
rowId: step.rowId,
|
|
14069
|
+
changed: [...step.title !== void 0 ? ["title"] : [], ...Object.keys(step.changes)],
|
|
14070
|
+
...contentIgnored
|
|
14071
|
+
};
|
|
14072
|
+
}
|
|
14073
|
+
function countActions(results) {
|
|
14074
|
+
return {
|
|
14075
|
+
created: results.filter((r) => r.action === "created").length,
|
|
14076
|
+
updated: results.filter((r) => r.action === "updated").length,
|
|
14077
|
+
unchanged: results.filter((r) => r.action === "unchanged").length
|
|
14078
|
+
};
|
|
13779
14079
|
}
|
|
13780
|
-
var ListCreate = class extends
|
|
14080
|
+
var ListCreate = class extends ProfileCommand {
|
|
13781
14081
|
static paths = [["list", "create"]];
|
|
13782
14082
|
static usage = Command.Usage({
|
|
13783
14083
|
category: "List",
|
|
@@ -13790,6 +14090,10 @@ var ListCreate = class extends ClientCommand {
|
|
|
13790
14090
|
[
|
|
13791
14091
|
"A recipe collection with labelled columns and an icon",
|
|
13792
14092
|
'ente list create Rezepte --space "ivo&sarah" --icon book-open --column "protein_g=Protein (g):number" --column "vegan:boolean"'
|
|
14093
|
+
],
|
|
14094
|
+
[
|
|
14095
|
+
"A sync target pinned under a project",
|
|
14096
|
+
'ente list create Firmen --space "ivo&thomas" --pin Recherche --column "repo:text" --column "stage:single-select:Kandidat,in Review,verfolgt,verworfen"'
|
|
13793
14097
|
]
|
|
13794
14098
|
]
|
|
13795
14099
|
});
|
|
@@ -13803,13 +14107,20 @@ var ListCreate = class extends ClientCommand {
|
|
|
13803
14107
|
icon = options_exports.String("--icon", {
|
|
13804
14108
|
description: "Entity icon as named in the web icon picker (e.g. book-open, leaf, star)"
|
|
13805
14109
|
});
|
|
14110
|
+
pin = options_exports.Array("--pin", { description: "Pin the list under parent entities (resolved by name)" });
|
|
14111
|
+
shareParents = options_exports.Boolean("--share-parents", false, {
|
|
14112
|
+
description: "Share pinned parents and ancestors into the list's space when required"
|
|
14113
|
+
});
|
|
14114
|
+
parentSpaceGrantFingerprint = options_exports.String("--parent-space-grant-fingerprint", {
|
|
14115
|
+
description: "Exact grantFingerprint returned by the confirmation-required error"
|
|
14116
|
+
});
|
|
13806
14117
|
async run() {
|
|
13807
14118
|
if (this.type !== "topic" && this.type !== "project") {
|
|
13808
14119
|
throw new Error(`--type must be 'topic' or 'project' (got '${this.type}').`);
|
|
13809
14120
|
}
|
|
13810
14121
|
const columns = (this.column ?? []).map(parseColumn);
|
|
13811
|
-
const spaceId = await resolveSpaceByName(this.client(), this.space);
|
|
13812
14122
|
const caller = await resolveCaller();
|
|
14123
|
+
const spaceId = await resolveSpaceByNameC(caller, this.space);
|
|
13813
14124
|
const entityId = await caller.mutation(entitiesCreate2, {
|
|
13814
14125
|
spaceId,
|
|
13815
14126
|
name: this.name,
|
|
@@ -13828,17 +14139,27 @@ var ListCreate = class extends ClientCommand {
|
|
|
13828
14139
|
if (this.icon) {
|
|
13829
14140
|
await caller.mutation(setCustomIcon, { id: entityId, icon: this.icon });
|
|
13830
14141
|
}
|
|
14142
|
+
const pins = await pinToTargets(caller, entityId, this.pin ?? [], {
|
|
14143
|
+
shareParents: this.shareParents,
|
|
14144
|
+
...this.parentSpaceGrantFingerprint ? { parentSpaceGrantFingerprint: this.parentSpaceGrantFingerprint } : {}
|
|
14145
|
+
});
|
|
14146
|
+
const data = { entityId, name: this.name, type: this.type, columns, enabledTabs: DATABASE_LIST_TABS };
|
|
13831
14147
|
this.emitText(
|
|
13832
|
-
|
|
13833
|
-
|
|
14148
|
+
pins.failures.length ? {
|
|
14149
|
+
data: { ...data, pinFailures: pins.failures, pinSucceeded: pins.succeeded },
|
|
14150
|
+
error: { message: "List created, but some parent relationships failed.", code: "PARTIAL_SUCCESS" }
|
|
14151
|
+
} : { data: { ...data, ...pins.succeeded.length ? { pinSucceeded: pins.succeeded } : {} } },
|
|
14152
|
+
() => `Created list '${this.name}' (${entityId}) with ${columns.length} column${columns.length === 1 ? "" : "s"}; opens on its database view.${pins.succeeded.length ? `
|
|
14153
|
+
Pinned to: ${pins.succeeded.join(", ")}` : ""}`
|
|
13834
14154
|
);
|
|
14155
|
+
failOnPinFailures((line) => this.err(line), `List '${this.name}'`, entityId, pins);
|
|
13835
14156
|
}
|
|
13836
14157
|
};
|
|
13837
|
-
var ListAdd = class extends
|
|
14158
|
+
var ListAdd = class extends ProfileCommand {
|
|
13838
14159
|
static paths = [["list", "add"]];
|
|
13839
14160
|
static usage = Command.Usage({
|
|
13840
14161
|
category: "List",
|
|
13841
|
-
description: "Add a row (activity) to a database list",
|
|
14162
|
+
description: "Add a row (activity) to a database list; with --key, update the row holding that key instead",
|
|
13842
14163
|
examples: [
|
|
13843
14164
|
[
|
|
13844
14165
|
"Add a categorised idea",
|
|
@@ -13847,6 +14168,10 @@ var ListAdd = class extends ClientCommand {
|
|
|
13847
14168
|
[
|
|
13848
14169
|
"Add a recipe row whose body is the recipe",
|
|
13849
14170
|
'ente list add Rezepte --title "Tofu-Wraps" --attr vegan=true --content-file tofu-wraps.md'
|
|
14171
|
+
],
|
|
14172
|
+
[
|
|
14173
|
+
"Upsert by a stable key column",
|
|
14174
|
+
'ente list add Firmen --key repo --title "Acme" --attr repo=firmen.csv#42 --attr stage=Kandidat'
|
|
13850
14175
|
]
|
|
13851
14176
|
]
|
|
13852
14177
|
});
|
|
@@ -13854,81 +14179,183 @@ var ListAdd = class extends ClientCommand {
|
|
|
13854
14179
|
title = options_exports.String("--title", { description: "Row title" });
|
|
13855
14180
|
type = options_exports.String("--type", "note", { description: "Row activity type: note (default) or task" });
|
|
13856
14181
|
attr = options_exports.Array("--attr", { description: "Column values as key=value (coerced to the column type)" });
|
|
14182
|
+
key = options_exports.String("--key", {
|
|
14183
|
+
description: "Upsert: update the row whose <key> cell equals this row's --attr <key>=\u2026 instead of adding one"
|
|
14184
|
+
});
|
|
13857
14185
|
deadline = options_exports.String("--deadline", { description: "Deadline (only meaningful for --type task)" });
|
|
13858
14186
|
space = options_exports.String("--space", { description: "Owner space (name or ID); default: the list's space" });
|
|
13859
|
-
content = options_exports.String("--content", { description: "Row body as markdown" });
|
|
14187
|
+
content = options_exports.String("--content", { description: "Row body as markdown (only used when the row is created)" });
|
|
13860
14188
|
contentFile = options_exports.String("--content-file", { description: "Read the row body from a markdown file" });
|
|
13861
14189
|
async run() {
|
|
13862
14190
|
if (this.type !== "note" && this.type !== "task") {
|
|
13863
14191
|
throw new Error(`--type must be 'note' or 'task' (got '${this.type}').`);
|
|
13864
14192
|
}
|
|
13865
|
-
const client = this.client();
|
|
13866
|
-
const entityId = await resolveEntity(client, this.list);
|
|
13867
14193
|
const caller = await resolveCaller();
|
|
13868
|
-
const entity = await caller
|
|
13869
|
-
|
|
13870
|
-
const
|
|
13871
|
-
const
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
}
|
|
13882
|
-
const ownerSpaceId = await this.resolveOwnerSpace(entityId);
|
|
14194
|
+
const { entityId, entity, view } = await loadList(caller, this.list);
|
|
14195
|
+
const attributes = coerceAttributes(view.columns, parseAttrs(this.attr), entity.name);
|
|
14196
|
+
const content = readContentOption(this.content, this.contentFile);
|
|
14197
|
+
const input = {
|
|
14198
|
+
line: 1,
|
|
14199
|
+
attributes,
|
|
14200
|
+
...this.title !== void 0 ? { title: this.title } : {},
|
|
14201
|
+
...content !== void 0 ? { content } : {}
|
|
14202
|
+
};
|
|
14203
|
+
if (this.key !== void 0) columnOrThrow(view.columns, this.key, entity.name);
|
|
14204
|
+
const existing = this.key !== void 0 ? (await fetchAllRows(caller, entityId)).map(toExisting) : [];
|
|
14205
|
+
const keyColumn = this.key ?? "";
|
|
14206
|
+
const step = this.key !== void 0 ? planUpsert(existing, [input], keyColumn)[0] : { action: "create", input, key: "" };
|
|
13883
14207
|
const deadline = this.type === "task" && this.deadline ? parseDeadline(this.deadline) : void 0;
|
|
13884
|
-
const
|
|
13885
|
-
const
|
|
13886
|
-
entityId,
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
...deadline !== void 0 ? { deadline } : {},
|
|
13891
|
-
...contentMarkdown !== void 0 ? { contentMarkdown } : {}
|
|
13892
|
-
});
|
|
13893
|
-
for (const { key, value } of coerced) {
|
|
13894
|
-
await caller.mutation(setAttributeValue, { activityId, key, value });
|
|
13895
|
-
}
|
|
14208
|
+
const ownerSpaceId = step.action === "create" ? await resolveOwnerSpace(caller, entityId, this.space) : "";
|
|
14209
|
+
const rowId = await applyStep(
|
|
14210
|
+
{ caller, entityId, ownerSpaceId, keyColumn, type: this.type, ...deadline !== void 0 ? { deadline } : {} },
|
|
14211
|
+
step
|
|
14212
|
+
);
|
|
14213
|
+
const result = stepResult(step, rowId);
|
|
13896
14214
|
this.emitText(
|
|
13897
|
-
{ data: { activityId, entityId, title: this.title ?? null, attributes
|
|
13898
|
-
() => `Added row '${this.title ?? "(untitled)"}' (${
|
|
14215
|
+
{ data: { ...result, activityId: rowId, entityId, title: this.title ?? null, attributes } },
|
|
14216
|
+
() => result.action === "created" ? `Added row '${this.title ?? "(untitled)"}' (${rowId}) to '${entity.name}'.` : result.action === "updated" ? `Updated row ${rowId} in '${entity.name}': ${(result.changed ?? []).join(", ")}.${result.contentIgnored ? " (body left unchanged)" : ""}` : `Row ${rowId} in '${entity.name}' is unchanged.`
|
|
13899
14217
|
);
|
|
13900
14218
|
}
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
14219
|
+
};
|
|
14220
|
+
function columnOrThrow(columns, key, listName) {
|
|
14221
|
+
const col = columns.find((c) => c.key === key);
|
|
14222
|
+
if (!col) throw new Error(`--key '${key}' is not a column of list '${listName}'.`);
|
|
14223
|
+
if (col.type !== "text") throw new Error(`--key '${key}' must be a text column (it is ${col.type}).`);
|
|
14224
|
+
return col;
|
|
14225
|
+
}
|
|
14226
|
+
function toExisting(row) {
|
|
14227
|
+
return { rowId: row._id, title: row.title ?? null, attributes: row.attributes ?? {} };
|
|
14228
|
+
}
|
|
14229
|
+
var ListUpdate = class extends ProfileCommand {
|
|
14230
|
+
static paths = [["list", "update"]];
|
|
14231
|
+
static usage = Command.Usage({
|
|
14232
|
+
category: "List",
|
|
14233
|
+
description: "Change cells (and optionally the title) of one row of a database list",
|
|
14234
|
+
examples: [
|
|
14235
|
+
["Move a row to another stage", "ente list update Firmen <rowId> --attr stage=verfolgt"],
|
|
14236
|
+
["Clear a cell", "ente list update Firmen <rowId> --attr notiz="]
|
|
14237
|
+
]
|
|
14238
|
+
});
|
|
14239
|
+
list = options_exports.String();
|
|
14240
|
+
rowId = options_exports.String();
|
|
14241
|
+
attr = options_exports.Array("--attr", { description: "Column values as key=value (coerced; empty value clears the cell)" });
|
|
14242
|
+
title = options_exports.String("--title", { description: "New row title" });
|
|
14243
|
+
async run() {
|
|
14244
|
+
if (!this.attr?.length && this.title === void 0) throw new Error("Nothing to update. Pass --attr and/or --title.");
|
|
13904
14245
|
const caller = await resolveCaller();
|
|
13905
|
-
const
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
if (
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
|
|
13912
|
-
|
|
14246
|
+
const { entityId, entity, view } = await loadList(caller, this.list);
|
|
14247
|
+
const attributes = coerceAttributes(view.columns, parseAttrs(this.attr), entity.name);
|
|
14248
|
+
const row = await caller.query(activitiesGetByIdRich3, { activityId: this.rowId });
|
|
14249
|
+
if (!row) throw new Error(`Row ${this.rowId} not found or not accessible.`);
|
|
14250
|
+
if (!(row.entityDetails ?? []).some((e) => e?._id === entityId))
|
|
14251
|
+
throw new Error(`Activity ${this.rowId} is not a row of list '${entity.name}'.`);
|
|
14252
|
+
if (this.title !== void 0) await caller.mutation(activitiesUpdateTitle3, { activityId: this.rowId, title: this.title });
|
|
14253
|
+
for (const [key, value] of Object.entries(attributes))
|
|
14254
|
+
await caller.mutation(setAttributeValue, { activityId: this.rowId, key, value });
|
|
14255
|
+
this.emitText(
|
|
14256
|
+
{ data: { rowId: this.rowId, entityId, ...this.title !== void 0 ? { title: this.title } : {}, attributes } },
|
|
14257
|
+
() => `Updated row ${this.rowId} in '${entity.name}': ${[
|
|
14258
|
+
...this.title !== void 0 ? ["title"] : [],
|
|
14259
|
+
...Object.keys(attributes)
|
|
14260
|
+
].join(", ")}.`
|
|
14261
|
+
);
|
|
13913
14262
|
}
|
|
13914
14263
|
};
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
14264
|
+
var ListImport = class extends ProfileCommand {
|
|
14265
|
+
static paths = [["list", "import"]];
|
|
14266
|
+
static usage = Command.Usage({
|
|
14267
|
+
category: "List",
|
|
14268
|
+
description: "Upsert rows from a JSONL file by a key column (created / updated / unchanged per row)",
|
|
14269
|
+
details: `
|
|
14270
|
+
One JSON object per line: {"title": "\u2026", "attrs": {"<key>": "\u2026", \u2026}, "content": "\u2026markdown\u2026"}.
|
|
14271
|
+
Values are coerced to each column's type like --attr. The whole file is checked before the
|
|
14272
|
+
first write: an unknown column, a bad value, a missing key or a key repeated in the file (or
|
|
14273
|
+
held by two existing rows) fails the run. "content" is only written when a row is created.
|
|
14274
|
+
Rows of the list that are not in the file are left alone.
|
|
14275
|
+
`,
|
|
14276
|
+
examples: [
|
|
14277
|
+
["Preview a sync", "ente list import Firmen --file firmen.jsonl --key repo --dry-run --json"],
|
|
14278
|
+
["Run it", "ente list import Firmen --file firmen.jsonl --key repo"]
|
|
14279
|
+
]
|
|
14280
|
+
});
|
|
14281
|
+
list = options_exports.String();
|
|
14282
|
+
file = options_exports.String("--file", { required: true, description: "JSONL file, one row per line" });
|
|
14283
|
+
key = options_exports.String("--key", { required: true, description: "Text column holding each row's stable key" });
|
|
14284
|
+
dryRun = options_exports.Boolean("--dry-run", false, { description: "Plan only; write nothing" });
|
|
14285
|
+
space = options_exports.String("--space", { description: "Owner space for new rows (name or ID); default: the list's space" });
|
|
14286
|
+
concurrency = options_exports.String("--concurrency", "4", { description: "Rows written in parallel (1-16, default 4)" });
|
|
14287
|
+
async run() {
|
|
14288
|
+
const concurrency = Number(this.concurrency);
|
|
14289
|
+
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 16)
|
|
14290
|
+
throw new Error(`--concurrency must be an integer from 1 to 16 (got '${this.concurrency}').`);
|
|
14291
|
+
const lines = parseImportLines(readFileSync10(this.file, "utf8"));
|
|
14292
|
+
const caller = await resolveCaller();
|
|
14293
|
+
const { entityId, entity, view } = await loadList(caller, this.list);
|
|
14294
|
+
columnOrThrow(view.columns, this.key, entity.name);
|
|
14295
|
+
const inputs = lines.map((l) => {
|
|
14296
|
+
try {
|
|
14297
|
+
return {
|
|
14298
|
+
line: l.line,
|
|
14299
|
+
attributes: coerceAttributes(view.columns, l.attrs, entity.name),
|
|
14300
|
+
...l.title !== void 0 ? { title: l.title } : {},
|
|
14301
|
+
...l.content !== void 0 ? { content: l.content } : {}
|
|
14302
|
+
};
|
|
14303
|
+
} catch (exc) {
|
|
14304
|
+
throw new Error(`Line ${l.line}: ${errorMessage(exc)}`);
|
|
14305
|
+
}
|
|
14306
|
+
});
|
|
14307
|
+
const existing = (await fetchAllRows(caller, entityId)).map(toExisting);
|
|
14308
|
+
const steps = planUpsert(existing, inputs, this.key);
|
|
14309
|
+
const needsSpace = steps.some((s) => s.action === "create");
|
|
14310
|
+
const ownerSpaceId = needsSpace ? await resolveOwnerSpace(caller, entityId, this.space) : "";
|
|
14311
|
+
const results = new Array(steps.length);
|
|
14312
|
+
let failure = null;
|
|
14313
|
+
if (this.dryRun) {
|
|
14314
|
+
steps.forEach((step, i) => results[i] = stepResult(step, step.action === "create" ? null : step.rowId));
|
|
14315
|
+
} else {
|
|
14316
|
+
const ctx = { caller, entityId, ownerSpaceId, keyColumn: this.key, type: "note" };
|
|
14317
|
+
let done = 0;
|
|
14318
|
+
const writes = steps.map((step, index) => ({ step, index })).filter(({ step }) => step.action !== "unchanged");
|
|
14319
|
+
steps.forEach((step, i) => {
|
|
14320
|
+
if (step.action === "unchanged") results[i] = stepResult(step, step.rowId);
|
|
14321
|
+
});
|
|
14322
|
+
try {
|
|
14323
|
+
await runPool(writes, concurrency, async ({ step, index }) => {
|
|
14324
|
+
results[index] = stepResult(step, await applyStep(ctx, step));
|
|
14325
|
+
done += 1;
|
|
14326
|
+
if (done % 100 === 0) this.err(`\u2026 ${done}/${writes.length} rows written`);
|
|
14327
|
+
});
|
|
14328
|
+
} catch (exc) {
|
|
14329
|
+
failure = errorMessage(exc);
|
|
14330
|
+
}
|
|
13928
14331
|
}
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
14332
|
+
const written = results.filter((r) => r !== void 0);
|
|
14333
|
+
const counts = countActions(written);
|
|
14334
|
+
const notWritten = steps.length - written.length;
|
|
14335
|
+
const payload = {
|
|
14336
|
+
entityId,
|
|
14337
|
+
key: this.key,
|
|
14338
|
+
dryRun: this.dryRun,
|
|
14339
|
+
...counts,
|
|
14340
|
+
...failure ? { notWritten } : {},
|
|
14341
|
+
rows: written
|
|
14342
|
+
};
|
|
14343
|
+
this.emitText(
|
|
14344
|
+
failure ? { data: payload, error: { message: failure, code: "IMPORT_FAILED" } } : { data: payload },
|
|
14345
|
+
() => {
|
|
14346
|
+
const head = `${this.dryRun ? "Dry run \u2014 would be " : ""}${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchanged in '${entity.name}'.`;
|
|
14347
|
+
const changed = written.filter((r) => r.action !== "unchanged").map((r) => ` ${r.action.padEnd(8)} ${r.key} ${r.rowId ?? ""}${r.changed ? ` (${r.changed.join(", ")})` : ""}`);
|
|
14348
|
+
return [head, ...changed].join("\n");
|
|
14349
|
+
}
|
|
14350
|
+
);
|
|
14351
|
+
if (failure) {
|
|
14352
|
+
this.err(`Error: ${failure}`);
|
|
14353
|
+
this.err(`${notWritten} row(s) were not written. The import is an upsert \u2014 rerun it to continue.`);
|
|
14354
|
+
throw new ExitWithCode(1);
|
|
14355
|
+
}
|
|
14356
|
+
}
|
|
14357
|
+
};
|
|
14358
|
+
var ListColumnAdd = class extends ProfileCommand {
|
|
13932
14359
|
static paths = [["list", "column", "add"]];
|
|
13933
14360
|
static usage = Command.Usage({
|
|
13934
14361
|
category: "List",
|
|
@@ -13938,7 +14365,8 @@ var ListColumnAdd = class extends ClientCommand {
|
|
|
13938
14365
|
list = options_exports.String();
|
|
13939
14366
|
columns = options_exports.Rest({ required: 1 });
|
|
13940
14367
|
async run() {
|
|
13941
|
-
const
|
|
14368
|
+
const caller = await resolveCaller();
|
|
14369
|
+
const { entityId, entity, view } = await loadList(caller, this.list);
|
|
13942
14370
|
const added = this.columns.map(parseColumn);
|
|
13943
14371
|
const taken = new Set(view.columns.map((c) => c.key));
|
|
13944
14372
|
for (const col of added) {
|
|
@@ -13946,7 +14374,6 @@ var ListColumnAdd = class extends ClientCommand {
|
|
|
13946
14374
|
taken.add(col.key);
|
|
13947
14375
|
}
|
|
13948
14376
|
const columns = [...view.columns, ...added];
|
|
13949
|
-
const caller = await resolveCaller();
|
|
13950
14377
|
await caller.mutation(updateDatabaseView, { id: entityId, databaseView: { ...view, columns } });
|
|
13951
14378
|
this.emitText(
|
|
13952
14379
|
{ data: { entityId, added, columns } },
|
|
@@ -13954,7 +14381,7 @@ var ListColumnAdd = class extends ClientCommand {
|
|
|
13954
14381
|
);
|
|
13955
14382
|
}
|
|
13956
14383
|
};
|
|
13957
|
-
var ListColumnRemove = class extends
|
|
14384
|
+
var ListColumnRemove = class extends ProfileCommand {
|
|
13958
14385
|
static paths = [["list", "column", "remove"]];
|
|
13959
14386
|
static usage = Command.Usage({
|
|
13960
14387
|
category: "List",
|
|
@@ -13964,7 +14391,8 @@ var ListColumnRemove = class extends ClientCommand {
|
|
|
13964
14391
|
list = options_exports.String();
|
|
13965
14392
|
keys = options_exports.Rest({ required: 1 });
|
|
13966
14393
|
async run() {
|
|
13967
|
-
const
|
|
14394
|
+
const caller = await resolveCaller();
|
|
14395
|
+
const { entityId, entity, view } = await loadList(caller, this.list);
|
|
13968
14396
|
const present = new Set(view.columns.map((c) => c.key));
|
|
13969
14397
|
const missing = this.keys.filter((key) => !present.has(key));
|
|
13970
14398
|
if (missing.length > 0) {
|
|
@@ -13972,7 +14400,6 @@ var ListColumnRemove = class extends ClientCommand {
|
|
|
13972
14400
|
`Not a column of '${entity.name}': ${missing.join(", ")}. Columns: ${[...present].join(", ") || "(none)"}.`
|
|
13973
14401
|
);
|
|
13974
14402
|
}
|
|
13975
|
-
const caller = await resolveCaller();
|
|
13976
14403
|
let current = view;
|
|
13977
14404
|
for (const key of this.keys) {
|
|
13978
14405
|
await caller.mutation(removeDatabaseColumn, { id: entityId, key, databaseView: current });
|
|
@@ -13984,33 +14411,59 @@ var ListColumnRemove = class extends ClientCommand {
|
|
|
13984
14411
|
);
|
|
13985
14412
|
}
|
|
13986
14413
|
};
|
|
13987
|
-
var ListShow = class extends
|
|
14414
|
+
var ListShow = class extends ProfileCommand {
|
|
13988
14415
|
static paths = [["list", "show"]];
|
|
13989
14416
|
static usage = Command.Usage({
|
|
13990
14417
|
category: "List",
|
|
13991
|
-
description: "Show the rows of a database list"
|
|
14418
|
+
description: "Show the rows of a database list (all rows, newest first; --view applies the table's saved filters)",
|
|
14419
|
+
examples: [
|
|
14420
|
+
["Every row, for a script", "ente list show Firmen --all --json"],
|
|
14421
|
+
["Only followed rows", "ente list show Firmen --all --where stage=verfolgt"],
|
|
14422
|
+
["Page by page", "ente list show Firmen --limit 100 --cursor <nextCursor> --json"]
|
|
14423
|
+
]
|
|
13992
14424
|
});
|
|
13993
14425
|
list = options_exports.String();
|
|
13994
|
-
limit = options_exports.String("--limit", { description: "
|
|
14426
|
+
limit = options_exports.String("--limit", { description: "Rows per page (default 100); ignored with --all" });
|
|
14427
|
+
cursor = options_exports.String("--cursor", { description: "Continue from the nextCursor of a previous page" });
|
|
14428
|
+
all = options_exports.Boolean("--all", false, { description: "Read every page" });
|
|
14429
|
+
where = options_exports.Array("--where", {
|
|
14430
|
+
description: "Keep rows whose cell equals key=value (repeatable, all must match; multi-select: contains)"
|
|
14431
|
+
});
|
|
14432
|
+
view = options_exports.Boolean("--view", false, { description: "Apply the table's saved filters and sorts" });
|
|
13995
14433
|
async run() {
|
|
13996
|
-
|
|
14434
|
+
if (this.all && this.cursor) throw new Error("Use --all or --cursor, not both.");
|
|
14435
|
+
const numItems = this.limit === void 0 ? ROW_PAGE_SIZE : Number(this.limit);
|
|
14436
|
+
if (!Number.isInteger(numItems) || numItems < 1 || numItems > 200)
|
|
14437
|
+
throw new Error(`--limit must be an integer from 1 to 200 (got '${this.limit}').`);
|
|
13997
14438
|
const caller = await resolveCaller();
|
|
13998
|
-
const
|
|
13999
|
-
const
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14439
|
+
const { entityId, entity, view } = await loadList(caller, this.list);
|
|
14440
|
+
const clauses = parseWhere(view.columns, this.where ?? [], entity.name);
|
|
14441
|
+
const rows = [];
|
|
14442
|
+
let cursor = this.cursor ?? null;
|
|
14443
|
+
let nextCursor = null;
|
|
14444
|
+
for (; ; ) {
|
|
14445
|
+
const res = await caller.query(listRows, {
|
|
14446
|
+
entityId,
|
|
14447
|
+
...this.view ? {} : { ignoreViewFilters: true },
|
|
14448
|
+
paginationOpts: { numItems, cursor }
|
|
14449
|
+
});
|
|
14450
|
+
rows.push(...res.page);
|
|
14451
|
+
nextCursor = res.isDone ? null : res.continueCursor;
|
|
14452
|
+
if (!this.all || nextCursor === null) break;
|
|
14453
|
+
cursor = nextCursor;
|
|
14454
|
+
}
|
|
14455
|
+
const kept = rows.filter((row) => matchesWhere(row.attributes, clauses));
|
|
14456
|
+
this.emitText(
|
|
14457
|
+
{ data: { entityId, rows: kept.map(rowJson), count: kept.length, nextCursor, isDone: nextCursor === null } },
|
|
14458
|
+
() => {
|
|
14459
|
+
const body = kept.length === 0 ? "(no rows)" : kept.map(rowLine).join("\n");
|
|
14460
|
+
return nextCursor ? `${body}
|
|
14461
|
+
\u2026 more rows: --cursor ${nextCursor} (or --all)` : body;
|
|
14462
|
+
}
|
|
14463
|
+
);
|
|
14011
14464
|
}
|
|
14012
14465
|
};
|
|
14013
|
-
var LIST_COMMANDS = [ListCreate, ListAdd, ListColumnAdd, ListColumnRemove, ListShow];
|
|
14466
|
+
var LIST_COMMANDS = [ListCreate, ListAdd, ListUpdate, ListImport, ListColumnAdd, ListColumnRemove, ListShow];
|
|
14014
14467
|
|
|
14015
14468
|
// ente/diagnosticContract.ts
|
|
14016
14469
|
var DIAGNOSTICS_LIMITS = { rows: 100, bytes: 1048576, windowMs: 6048e5, cursorChars: 8192, grantHours: 24 };
|
|
@@ -14720,7 +15173,7 @@ var QUERY_COMMANDS = [QueryCmd];
|
|
|
14720
15173
|
|
|
14721
15174
|
// ente/commands/recordingPipeline.ts
|
|
14722
15175
|
import { spawnSync } from "node:child_process";
|
|
14723
|
-
import { mkdtempSync, readFileSync as
|
|
15176
|
+
import { mkdtempSync, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14724
15177
|
import { tmpdir } from "node:os";
|
|
14725
15178
|
import path5 from "node:path";
|
|
14726
15179
|
var getVoicePipelineSettings = makeFunctionReference("users:getVoicePipelineSettings");
|
|
@@ -14736,7 +15189,7 @@ async function resolveValue(cmd, label, current) {
|
|
|
14736
15189
|
if (cmd.edit) return editInEditor(current, label);
|
|
14737
15190
|
if (cmd.file) {
|
|
14738
15191
|
if (cmd.file === "-") return await readStdin();
|
|
14739
|
-
return
|
|
15192
|
+
return readFileSync11(cmd.file, "utf8");
|
|
14740
15193
|
}
|
|
14741
15194
|
if (cmd.value !== void 0) return cmd.value;
|
|
14742
15195
|
throw new Error(`No ${label} given. Pass a value, --file <path> (or --file -), or --edit.`);
|
|
@@ -14750,7 +15203,7 @@ function editInEditor(initial, label) {
|
|
|
14750
15203
|
if (res.status !== 0) {
|
|
14751
15204
|
throw new Error(`Editor '${editor}' exited with a non-zero status; ${label} unchanged.`);
|
|
14752
15205
|
}
|
|
14753
|
-
return
|
|
15206
|
+
return readFileSync11(file, "utf8");
|
|
14754
15207
|
}
|
|
14755
15208
|
function readStdin() {
|
|
14756
15209
|
return new Promise((resolve2, reject) => {
|
|
@@ -15313,7 +15766,7 @@ function parseAgentIconInput(raw) {
|
|
|
15313
15766
|
}
|
|
15314
15767
|
|
|
15315
15768
|
// ente/commands/agent.ts
|
|
15316
|
-
import { readFileSync as
|
|
15769
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
15317
15770
|
import { randomUUID } from "node:crypto";
|
|
15318
15771
|
|
|
15319
15772
|
// ente/agentCaller.ts
|
|
@@ -15630,7 +16083,7 @@ var AgentSend = class extends ClientCommand {
|
|
|
15630
16083
|
threadId: this.threadId,
|
|
15631
16084
|
claimId,
|
|
15632
16085
|
text: this.message,
|
|
15633
|
-
...this.htmlFile ? { html:
|
|
16086
|
+
...this.htmlFile ? { html: readFileSync12(this.htmlFile, "utf8") } : {},
|
|
15634
16087
|
...this.request === "none" ? {} : { requestKind: this.request },
|
|
15635
16088
|
...this.requestPrompt ? { requestPrompt: this.requestPrompt } : {},
|
|
15636
16089
|
idempotencyKey: this.idempotencyKey ?? randomUUID()
|
|
@@ -15669,7 +16122,7 @@ var AgentNew = class extends ClientCommand {
|
|
|
15669
16122
|
const response = await client.createAgentThread({
|
|
15670
16123
|
title: this.title,
|
|
15671
16124
|
text: this.message,
|
|
15672
|
-
...this.htmlFile ? { html:
|
|
16125
|
+
...this.htmlFile ? { html: readFileSync12(this.htmlFile, "utf8") } : {},
|
|
15673
16126
|
...this.request === "none" ? {} : { requestKind: this.request },
|
|
15674
16127
|
...this.requestPrompt ? { requestPrompt: this.requestPrompt } : {},
|
|
15675
16128
|
idempotencyKey: this.idempotencyKey ?? randomUUID()
|
|
@@ -15716,7 +16169,7 @@ var AgentConfigSetPrompt = class extends ClientCommand {
|
|
|
15716
16169
|
if (!!this.prompt === !!this.file) {
|
|
15717
16170
|
throw new Error("Provide exactly one of --text or --file.");
|
|
15718
16171
|
}
|
|
15719
|
-
const prompt = this.file ?
|
|
16172
|
+
const prompt = this.file ? readFileSync12(this.file, "utf8") : this.prompt;
|
|
15720
16173
|
const client = await boundAgentClient(this.client(), await this.client().getAuthContext());
|
|
15721
16174
|
const { data } = await client.updateAgentPrompt(prompt);
|
|
15722
16175
|
this.emitText({ data }, () => `Updated Ente prompt configuration for ${data.name}.`);
|
|
@@ -15730,7 +16183,7 @@ var AgentSchedulesConfigure = class extends ClientCommand {
|
|
|
15730
16183
|
});
|
|
15731
16184
|
file = options_exports.String("--file", { required: true, description: "JSON file containing an array of schedules" });
|
|
15732
16185
|
async run() {
|
|
15733
|
-
const parsed = JSON.parse(
|
|
16186
|
+
const parsed = JSON.parse(readFileSync12(this.file, "utf8"));
|
|
15734
16187
|
if (!Array.isArray(parsed)) {
|
|
15735
16188
|
throw new Error("The schedules file must contain a JSON array.");
|
|
15736
16189
|
}
|
|
@@ -15755,7 +16208,7 @@ var AGENT_COMMANDS = [
|
|
|
15755
16208
|
];
|
|
15756
16209
|
|
|
15757
16210
|
// ente/commands/agenda.ts
|
|
15758
|
-
import { readFileSync as
|
|
16211
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
15759
16212
|
var AgendaPointCreate = class extends ClientCommand {
|
|
15760
16213
|
static paths = [["agenda", "create-point"]];
|
|
15761
16214
|
static usage = Command.Usage({
|
|
@@ -15785,7 +16238,7 @@ var AgendaPointCreate = class extends ClientCommand {
|
|
|
15785
16238
|
async run() {
|
|
15786
16239
|
if (this.content !== void 0 && this.contentFile !== void 0)
|
|
15787
16240
|
throw new Error("Use either --content or --content-file, not both.");
|
|
15788
|
-
const contentMarkdown = this.content !== void 0 ? this.content : this.contentFile !== void 0 ?
|
|
16241
|
+
const contentMarkdown = this.content !== void 0 ? this.content : this.contentFile !== void 0 ? readFileSync13(this.contentFile, "utf8") : void 0;
|
|
15789
16242
|
const result = await this.client().createAgendaPoint({
|
|
15790
16243
|
meetingActivityId: this.activity,
|
|
15791
16244
|
title: this.title,
|
|
@@ -16549,7 +17002,7 @@ var CANVAS_COMMANDS = [
|
|
|
16549
17002
|
];
|
|
16550
17003
|
|
|
16551
17004
|
// ente/commands/notification.ts
|
|
16552
|
-
import { readFileSync as
|
|
17005
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
16553
17006
|
|
|
16554
17007
|
// ente/notificationEmailContract.ts
|
|
16555
17008
|
var NOTIFICATION_EMAIL_CLI_SURFACE = {
|
|
@@ -16583,7 +17036,7 @@ var CLI_LIMITS = NOTIFICATION_EMAIL_CLI_SURFACE.limits;
|
|
|
16583
17036
|
function readBody(inline, file, label) {
|
|
16584
17037
|
if (inline !== void 0 && file !== void 0)
|
|
16585
17038
|
throw new Error(`notification send: use either --${label} or --${label}-file, not both.`);
|
|
16586
|
-
const value = file !== void 0 ?
|
|
17039
|
+
const value = file !== void 0 ? readFileSync14(file, "utf8") : inline;
|
|
16587
17040
|
if (value !== void 0 && !value.trim())
|
|
16588
17041
|
throw new Error(`notification send: ${label} body must not be empty.`);
|
|
16589
17042
|
return value;
|
|
@@ -16885,7 +17338,7 @@ function parseUserMailboxRequest(input) {
|
|
|
16885
17338
|
|
|
16886
17339
|
// ente/commands/agentMail.ts
|
|
16887
17340
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16888
|
-
import { readFileSync as
|
|
17341
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
16889
17342
|
|
|
16890
17343
|
// ente/agentMailContract.ts
|
|
16891
17344
|
var AGENT_MAIL_CLI_SURFACE = {
|
|
@@ -17085,7 +17538,7 @@ function readBody2(inline, file, label) {
|
|
|
17085
17538
|
if (inline !== void 0 && file !== void 0) {
|
|
17086
17539
|
throw new Error(`agent mail: use either --${label} or --${label}-file, not both.`);
|
|
17087
17540
|
}
|
|
17088
|
-
const value = file === void 0 ? inline :
|
|
17541
|
+
const value = file === void 0 ? inline : readFileSync15(file, "utf8");
|
|
17089
17542
|
if (value !== void 0 && !value.trim()) {
|
|
17090
17543
|
throw new Error(`agent mail: ${label} body must not be empty.`);
|
|
17091
17544
|
}
|
|
@@ -17378,7 +17831,7 @@ var UserMailboxCommand = class extends ClientCommand {
|
|
|
17378
17831
|
requestFile = options_exports.String("--request-file", { description: "Read the complete operation JSON from a file" });
|
|
17379
17832
|
async run() {
|
|
17380
17833
|
if (this.request === void 0 === (this.requestFile === void 0)) throw new Error("Use exactly one of --request or --request-file");
|
|
17381
|
-
const parsed = parseUserMailboxRequest(JSON.parse(this.request ??
|
|
17834
|
+
const parsed = parseUserMailboxRequest(JSON.parse(this.request ?? readFileSync15(this.requestFile, "utf8")));
|
|
17382
17835
|
if ("error" in parsed) throw new Error(parsed.error);
|
|
17383
17836
|
const result = await this.client().userMailbox(parsed.value);
|
|
17384
17837
|
this.emitText(result, () => JSON.stringify(result.data, null, 2));
|