@fleox-agent-crm/agentcrm 0.3.0 → 0.3.2
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 +16 -16
- package/changelog.json +30 -0
- package/dist/admin/admin-files.json +8 -8
- package/dist/admin/assets/{chartsImpl-CE2akJat.js → chartsImpl-qlHW3VOv.js} +30 -30
- package/dist/admin/assets/firebase-U38f5kc9.js +2 -0
- package/dist/admin/assets/{gitJson-BulGh2KV.js → gitJson-wy2Tytfd.js} +1 -1
- package/dist/admin/assets/{index-DidVVK7L.js → index-BpaxrJx9.js} +265 -265
- package/dist/admin/assets/{index-hMXwHSvN.css → index-TcjySLVg.css} +1 -1
- package/dist/admin/assets/{lucide-react.min-DEDlHCPj.js → lucide-react.min-Djrg16BH.js} +1 -1
- package/dist/admin/assets/{sdk-CnrLnlRo.js → sdk-mu08RFtk.js} +1 -1
- package/dist/admin/index.html +2 -2
- package/dist/agent/agentcrm.mjs +334 -151
- package/dist/agent/chunks/{api-B6OqNVWp.mjs → api-Cz0Ubj-B.mjs} +4 -3
- package/dist/agent/chunks/api-Dwb4xz1k.mjs +27 -0
- package/dist/agent/chunks/{appearance-BaShelzt.mjs → appearance-BloHbiLA.mjs} +1 -1
- package/dist/agent/chunks/{automations-BWrvLqP6.mjs → automations-DwWruxmu.mjs} +3 -3
- package/dist/agent/chunks/{backup-ClSmxmFZ.mjs → backup-W3CjgkAJ.mjs} +16 -3
- package/dist/agent/chunks/{compact-CsFPaAAq.mjs → compact-BGHI92tm.mjs} +2 -2
- package/dist/agent/chunks/{exportCsv-B5hRvkNm.mjs → exportCsv-C6e6XEmB.mjs} +38 -3
- package/dist/agent/chunks/{extCheck--7lmVvl7.mjs → extCheck-GgHG1dYB.mjs} +16 -9
- package/dist/agent/chunks/{extPackage-BGl5Oqs8.mjs → extPackage-DuZ6GZhp.mjs} +2 -2
- package/dist/agent/chunks/{extScaffold-DqxI7R1e.mjs → extScaffold-Aqyjf1nr.mjs} +3 -3
- package/dist/agent/chunks/{extensions-BP1k60md.mjs → extensions-Bp72uZ9a.mjs} +19 -6
- package/dist/agent/chunks/{featureRegistry-C9nbVShp.mjs → featureRegistry-DSsZbhrh.mjs} +3 -3
- package/dist/agent/chunks/{files-q8Xu5bft.mjs → files-D1daDgY0.mjs} +1 -1
- package/dist/agent/chunks/{firebase-DxGVQV9E.mjs → firebase-BciZWdc6.mjs} +41 -17
- package/dist/agent/chunks/{forms-BKU5F9WF.mjs → forms-BfuWaQjA.mjs} +1 -1
- package/dist/agent/chunks/{hosting-Bme7LxPc.mjs → hosting-Dnn1k3cV.mjs} +1 -1
- package/dist/agent/chunks/{importCsv-DmE_sBch.mjs → importCsv-LVmMTaWU.mjs} +2 -2
- package/dist/agent/chunks/{index-DUSdTBkz.mjs → index-CNXq2Yk_.mjs} +1 -24
- package/dist/agent/chunks/index-Dio0aBWA.mjs +6920 -0
- package/dist/agent/chunks/{index-CRkw3_iF.mjs → index-rut-KC39.mjs} +1 -1
- package/dist/agent/chunks/{install-CpHoUFtL.mjs → install-BBTA4nMm.mjs} +16 -7
- package/dist/agent/chunks/{loader-C9nm3iwx.mjs → loader-MPD9iAvh.mjs} +20 -20
- package/dist/agent/chunks/{modules-CqdVQVJb.mjs → modules-clWdE-K2.mjs} +27 -2
- package/dist/agent/chunks/{nodeImport-BPCt5uMu.mjs → nodeImport-ylfRDvqt.mjs} +2 -2
- package/dist/agent/chunks/{package-DWbn3t2R.mjs → package-B7mI6HGi.mjs} +1 -1
- package/dist/agent/chunks/{papaparse-DjBYnQM2.mjs → papaparse-Df7i1dHr.mjs} +31 -7
- package/dist/agent/chunks/{pipelines-DCdRBNBV.mjs → pipelines-BAKpPk1w.mjs} +1 -1
- package/dist/agent/chunks/{plan-C0TBrU92.mjs → plan-BmTat6-T.mjs} +1 -1
- package/dist/agent/chunks/{records-DoqiT9xR.mjs → records-DcHFelq5.mjs} +5 -1
- package/dist/agent/chunks/{registry-Ceexawxl.mjs → registry-COfJrSmg.mjs} +18 -18
- package/dist/agent/chunks/{reports-BB59N6ke.mjs → reports-BACHGccj.mjs} +3 -3
- package/dist/agent/chunks/{runtime-DHo7ZEEO.mjs → runtime-C0Xf0Axi.mjs} +53 -32
- package/dist/agent/chunks/{schema-DlHt2PxI.mjs → schema-C4lSslXv.mjs} +10 -4
- package/dist/agent/chunks/{schema-7-4-qtSS.mjs → schema-DnLj8dUO.mjs} +10 -0
- package/dist/agent/chunks/{sdk-whvfg786.mjs → sdk-CYgNyuoK.mjs} +132 -7005
- package/dist/agent/chunks/{seed-CugPGYZL.mjs → seed-6MRmQQBR.mjs} +26 -8
- package/dist/agent/chunks/{server-ISwlDRCN.mjs → server-DC3t8r30.mjs} +5 -5
- package/dist/agent/chunks/{settings-BtvR_jdm.mjs → settings-Di8nzNEL.mjs} +35 -0
- package/dist/agent/chunks/{stats-CfeJssSS.mjs → stats-BfuLWhVV.mjs} +2 -2
- package/dist/agent/chunks/{tools-Bew8lxKx.mjs → tools-iSH-yMpl.mjs} +59 -19
- package/dist/agent/chunks/{views-fO_BM5U_.mjs → views-DH1n9Lrt.mjs} +1 -1
- package/dist/version.json +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/agentcrm-admin/SKILL.md +67 -67
- package/plugin/skills/agentcrm-admin/references/appearance.md +1 -1
- package/plugin/skills/agentcrm-admin/references/records.md +5 -5
- package/plugin/skills/agentcrm-admin/references/schema.md +20 -20
- package/plugin/skills/agentcrm-extensions/SKILL.md +9 -9
- package/plugin/skills/agentcrm-extensions/references/module-recipes.md +2 -2
- package/plugin/skills/agentcrm-extensions/references/testing.md +16 -14
- package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +3 -3
- package/plugin/skills/agentcrm-install/SKILL.md +13 -13
- package/plugin/skills/agentcrm-install/references/firebase.md +1 -1
- package/plugin/skills/agentcrm-install/references/github.md +1 -1
- package/plugin/skills/agentcrm-install/references/mcp.md +3 -3
- package/plugin/skills/agentcrm-install/references/plans.md +1 -1
- package/plugin/skills/agentcrm-install/references/s3.md +1 -1
- package/plugin/skills/agentcrm-manage/SKILL.md +15 -15
- package/plugin/skills/agentcrm-sales/SKILL.md +18 -18
- package/dist/admin/assets/firebase-BHSUggRo.js +0 -2
package/dist/agent/agentcrm.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { dirname as __acrmDirname } from "node:path";
|
|
|
5
5
|
const require2 = __acrmRequire(import.meta.url);
|
|
6
6
|
const __filename = __acrmPath(import.meta.url);
|
|
7
7
|
const __dirname = __acrmDirname(__filename);
|
|
8
|
-
import { parseArgs } from "node:util";
|
|
9
|
-
import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync, chmodSync, renameSync, openSync, writeSync, closeSync, unlinkSync, mkdtempSync, readdirSync, createReadStream } from "node:fs";
|
|
8
|
+
import { format, parseArgs } from "node:util";
|
|
9
|
+
import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync, chmodSync, renameSync, openSync, writeSync, closeSync, unlinkSync, mkdtempSync, rmSync, readdirSync, createReadStream } from "node:fs";
|
|
10
10
|
import { join, resolve, dirname, basename, extname, sep, normalize, isAbsolute } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
@@ -54,6 +54,13 @@ class CliError extends Error {
|
|
|
54
54
|
this.exitCode = exitCode;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
function routeConsoleToStderr(target = console, write = (t) => void process.stderr.write(t)) {
|
|
58
|
+
const toStderr = (...args) => write(`${format(...args)}
|
|
59
|
+
`);
|
|
60
|
+
target.log = toStderr;
|
|
61
|
+
target.info = toStderr;
|
|
62
|
+
target.debug = toStderr;
|
|
63
|
+
}
|
|
57
64
|
function createIo(json) {
|
|
58
65
|
return {
|
|
59
66
|
json,
|
|
@@ -68,10 +75,20 @@ function printResult(io, data, human) {
|
|
|
68
75
|
const OPS_HINTS = {
|
|
69
76
|
forbidden: "The CRM user the CLI acts as lacks this permission (see `site show` → user, `users list`). Ask an admin, or re-register the site with an admin user (`site add … --user <email>`).",
|
|
70
77
|
notFound: "Check the id (list commands print them: `records list --object …`, `objects list`, `views list`…).",
|
|
71
|
-
validation: "Fix the input
|
|
78
|
+
validation: "Fix the input and retry (`agentcrm help <command>` lists the options; for record fields, `objects get <object>` lists field ids, types and select options).",
|
|
72
79
|
conflict: "Something with the same key already exists — list it first, then update instead of creating.",
|
|
73
80
|
invalid: "Check the arguments with `agentcrm help <command>`."
|
|
74
81
|
};
|
|
82
|
+
function dependencyHint(details) {
|
|
83
|
+
const ids = (key) => Array.isArray(details?.[key]) ? details[key].map(String) : [];
|
|
84
|
+
const blockers = ids("blockers");
|
|
85
|
+
if (blockers.length) return `Blocked by: ${blockers.join(", ")}. Re-run with --cascade (AI tools: "cascade": true) to change ${blockers.length > 1 ? "them" : "it"} too — show the user the plan's \`changed\` list first.`;
|
|
86
|
+
const dependents = ids("dependents");
|
|
87
|
+
if (dependents.length) return `Disable or uninstall ${dependents.join(", ")} first (\`ext disable <id>\` / \`ext uninstall <id>\`), or pass --force to \`ext uninstall\`.`;
|
|
88
|
+
const missing = ids("missing");
|
|
89
|
+
if (missing.length) return `Install the missing module(s) first: ${missing.join(", ")} (\`ext install <zip>\`).`;
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
75
92
|
function knownError(error) {
|
|
76
93
|
if (!(error instanceof Error)) return null;
|
|
77
94
|
if (error.name === "PassphraseError") {
|
|
@@ -84,7 +101,8 @@ function knownError(error) {
|
|
|
84
101
|
}
|
|
85
102
|
if (error.name === "OpsError") {
|
|
86
103
|
const code = error.code ?? "invalid";
|
|
87
|
-
|
|
104
|
+
const details = error.details;
|
|
105
|
+
return new CliError(code, error.message, code === "conflict" && dependencyHint(details) || OPS_HINTS[code], code === "validation" || code === "invalid" ? 2 : 1);
|
|
88
106
|
}
|
|
89
107
|
if (error.name === "FieldValidationError") {
|
|
90
108
|
return new CliError("validation", error.message, OPS_HINTS.validation, 2);
|
|
@@ -120,10 +138,11 @@ const output = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
120
138
|
knownError,
|
|
121
139
|
printError,
|
|
122
140
|
printResult,
|
|
141
|
+
routeConsoleToStderr,
|
|
123
142
|
toCliError
|
|
124
143
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
125
144
|
const name = "@fleox-agent-crm/agentcrm";
|
|
126
|
-
const version = "0.3.
|
|
145
|
+
const version = "0.3.2";
|
|
127
146
|
const type = "module";
|
|
128
147
|
const description = "The static CRM your AI agent can run: a Twenty / HubSpot-style CRM that runs entirely in the browser or headless through the agentcrm CLI, with data on storage you own.";
|
|
129
148
|
const license = "SEE LICENSE IN LICENSE";
|
|
@@ -505,8 +524,8 @@ function tmpFolder() {
|
|
|
505
524
|
return tmpBase;
|
|
506
525
|
}
|
|
507
526
|
async function nodeImportBundle() {
|
|
508
|
-
await import("./chunks/runtime-
|
|
509
|
-
const { createNodeImportBundle } = await import("./chunks/nodeImport-
|
|
527
|
+
await import("./chunks/runtime-C0Xf0Axi.mjs");
|
|
528
|
+
const { createNodeImportBundle } = await import("./chunks/nodeImport-ylfRDvqt.mjs");
|
|
510
529
|
return createNodeImportBundle({ runtimeDir: runtimeDir(), tmpDir: tmpFolder() });
|
|
511
530
|
}
|
|
512
531
|
function hasEnabledExtensions(store) {
|
|
@@ -520,15 +539,9 @@ async function loadSessionExtensions(store, opts = {}) {
|
|
|
520
539
|
if (!loadedAny && !opts.force && !hasEnabledExtensions(store)) return null;
|
|
521
540
|
try {
|
|
522
541
|
const importBundle = await nodeImportBundle();
|
|
523
|
-
const { syncExtensions } = await import("./chunks/loader-
|
|
542
|
+
const { syncExtensions } = await import("./chunks/loader-MPD9iAvh.mjs");
|
|
524
543
|
loadedAny = true;
|
|
525
|
-
|
|
526
|
-
console.error = () => void 0;
|
|
527
|
-
try {
|
|
528
|
-
return await syncExtensions(store, { importBundle });
|
|
529
|
-
} finally {
|
|
530
|
-
console.error = original;
|
|
531
|
-
}
|
|
544
|
+
return await syncExtensions(store, { importBundle, onError: () => void 0 });
|
|
532
545
|
} catch (err) {
|
|
533
546
|
return { loaded: [], unloaded: [], failed: [{ id: "*", error: err.message }], skipped: [] };
|
|
534
547
|
}
|
|
@@ -540,7 +553,7 @@ async function loadSessionExtensions(store, opts = {}) {
|
|
|
540
553
|
sync,
|
|
541
554
|
async close() {
|
|
542
555
|
if (!loadedAny) return;
|
|
543
|
-
(await import("./chunks/registry-
|
|
556
|
+
(await import("./chunks/registry-COfJrSmg.mjs")).deactivateAllExtensions();
|
|
544
557
|
}
|
|
545
558
|
};
|
|
546
559
|
}
|
|
@@ -595,7 +608,7 @@ async function openStore(runtime, opts) {
|
|
|
595
608
|
}
|
|
596
609
|
if (runtime.backend === "journal") {
|
|
597
610
|
if (!opts.dataTarget) throw new CliError("internal", "journal store without a data location");
|
|
598
|
-
const { buildConnector } = await import("./chunks/hosting-
|
|
611
|
+
const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
|
|
599
612
|
const { createJournalStore } = await import("./chunks/index-B-4W0-P4.mjs");
|
|
600
613
|
const { publicSettings, secrets } = rc.splitSecretSettings(opts.dataTarget.settings);
|
|
601
614
|
rc.setConnectorSecrets(runtime, secrets);
|
|
@@ -612,7 +625,11 @@ async function openStore(runtime, opts) {
|
|
|
612
625
|
const token = String(opts.secrets?.token ?? "");
|
|
613
626
|
if (token) rc.setGitToken(runtime.git, token);
|
|
614
627
|
}
|
|
615
|
-
|
|
628
|
+
if (runtime.backend === "firebase" && opts.account && typeof opts.secrets?.email === "string") {
|
|
629
|
+
const { prepareFirebaseSession: prepareFirebaseSession2 } = await Promise.resolve().then(() => firebaseSession);
|
|
630
|
+
await prepareFirebaseSession2(opts.account, runtime.firebase, opts.secrets.email);
|
|
631
|
+
}
|
|
632
|
+
const { createDataStore } = await import("./chunks/index-rut-KC39.mjs");
|
|
616
633
|
return createDataStore(runtime, { secrets: opts.secrets ?? {}, passphrase: opts.passphrase ?? null, clientId: cliClientId() });
|
|
617
634
|
}
|
|
618
635
|
function siteSecrets(site) {
|
|
@@ -626,7 +643,7 @@ function siteSecrets(site) {
|
|
|
626
643
|
`Store it (\`agentcrm credentials set ${site.runtime.backend === "firebase" ? "firebase" : site.runtime.git.provider}:<name>\`), then \`agentcrm site credentials ${site.alias} --data-credential <account>\`.`
|
|
627
644
|
);
|
|
628
645
|
}
|
|
629
|
-
return { secrets: requireCredential(site.credentials.data).values, passphrase };
|
|
646
|
+
return { secrets: requireCredential(site.credentials.data).values, passphrase, account: site.credentials.data };
|
|
630
647
|
}
|
|
631
648
|
return { passphrase };
|
|
632
649
|
}
|
|
@@ -663,7 +680,7 @@ async function openSession(alias, opts = {}) {
|
|
|
663
680
|
const [{ createOpsContext }, { createPluginRegistry, plugins: defaultPlugins }, { installAutomations }] = await Promise.all([
|
|
664
681
|
import("./chunks/context-CxFfqS4D.mjs"),
|
|
665
682
|
import("./chunks/pluginRegistry-DyHtlkcr.mjs"),
|
|
666
|
-
import("./chunks/automations-
|
|
683
|
+
import("./chunks/automations-DwWruxmu.mjs")
|
|
667
684
|
]);
|
|
668
685
|
const own = createPluginRegistry((hook, err) => process.stderr.write(`[agentcrm] ${hook} handler failed: ${String(err?.message ?? err)}
|
|
669
686
|
`));
|
|
@@ -690,14 +707,14 @@ async function openSession(alias, opts = {}) {
|
|
|
690
707
|
})();
|
|
691
708
|
if (target) {
|
|
692
709
|
const { setStorageConfigSource, registerStorageConnector } = await import("./chunks/registry-DijAKZ24.mjs").then((n) => n.r);
|
|
693
|
-
const { buildConnector } = await import("./chunks/hosting-
|
|
710
|
+
const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
|
|
694
711
|
if (target.connectorId === "local-folder") registerStorageConnector({ id: "local-folder", create: (s) => buildConnector({ connectorId: "local-folder", settings: s }) });
|
|
695
712
|
setStorageConfigSource(async () => target);
|
|
696
713
|
}
|
|
697
714
|
}
|
|
698
715
|
{
|
|
699
|
-
const forms = await import("./chunks/forms-
|
|
700
|
-
const { buildConnector } = await import("./chunks/hosting-
|
|
716
|
+
const forms = await import("./chunks/forms-BfuWaQjA.mjs");
|
|
717
|
+
const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
|
|
701
718
|
forms.setInboxSourceResolver((_ctx, form) => {
|
|
702
719
|
if (form.inbox.kind !== "s3") return null;
|
|
703
720
|
const settings = form.inbox.settings ?? {};
|
|
@@ -768,6 +785,12 @@ const session = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
|
|
|
768
785
|
openStore,
|
|
769
786
|
withSession
|
|
770
787
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
788
|
+
function writeOutFile(path, data) {
|
|
789
|
+
const abs = resolve(path);
|
|
790
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
791
|
+
writeFileSync(abs, data);
|
|
792
|
+
return abs;
|
|
793
|
+
}
|
|
771
794
|
function usage(message, hint) {
|
|
772
795
|
throw new CliError("usage", message, hint, 2);
|
|
773
796
|
}
|
|
@@ -912,7 +935,7 @@ function readLocalFile(path) {
|
|
|
912
935
|
return { name: basename(full), bytes: new Uint8Array(readFileSync(full)) };
|
|
913
936
|
}
|
|
914
937
|
async function uploadLocalFiles(ctx, paths, targets2 = []) {
|
|
915
|
-
const { uploadFile } = await import("./chunks/files-
|
|
938
|
+
const { uploadFile } = await import("./chunks/files-D1daDgY0.mjs");
|
|
916
939
|
const { guessMime, imageDimensions } = await import("./chunks/files-CjsPJSR6.mjs");
|
|
917
940
|
const out = [];
|
|
918
941
|
for (const p of paths) {
|
|
@@ -960,7 +983,7 @@ const filesCommand = {
|
|
|
960
983
|
switch (action) {
|
|
961
984
|
case "list":
|
|
962
985
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
963
|
-
const { listFiles } = await import("./chunks/files-
|
|
986
|
+
const { listFiles } = await import("./chunks/files-D1daDgY0.mjs");
|
|
964
987
|
const target = strOpt(options, "record") ?? strOpt(options, "activity") ?? null;
|
|
965
988
|
const items = listFiles(ctx, target, { includeRelated: boolOpt(options, "related"), kind: strOpt(options, "kind"), search: strOpt(options, "search") });
|
|
966
989
|
const limit = Math.min(1e3, numOpt(options, "limit") ?? 100);
|
|
@@ -975,7 +998,7 @@ ${table(r.files, ["id", "name", "mime", "size", "createdAt"])}`);
|
|
|
975
998
|
if (record && activity) usage("Give --record or --activity, not both.");
|
|
976
999
|
for (const p of args) readLocalFile(p);
|
|
977
1000
|
return withSession(io, options, { lock: true }, async ({ ctx }) => {
|
|
978
|
-
const { attachFile } = await import("./chunks/files-
|
|
1001
|
+
const { attachFile } = await import("./chunks/files-D1daDgY0.mjs");
|
|
979
1002
|
const files2 = await uploadLocalFiles(ctx, args, record ? [record] : []);
|
|
980
1003
|
if (activity) for (const f of files2) await attachFile(ctx, f.id, { kind: "activity", id: activity });
|
|
981
1004
|
const { requireFile } = await import("./chunks/store-5gqRSKXg.mjs");
|
|
@@ -988,17 +1011,17 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
|
|
|
988
1011
|
const out = strOpt(options, "out");
|
|
989
1012
|
if (!out) usage("files get needs --out <path>.");
|
|
990
1013
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
991
|
-
const { getFile, getFileBytes } = await import("./chunks/files-
|
|
1014
|
+
const { getFile, getFileBytes } = await import("./chunks/files-D1daDgY0.mjs");
|
|
992
1015
|
const meta = getFile(ctx, id);
|
|
993
1016
|
const bytes = await getFileBytes(ctx, id, { thumb: boolOpt(options, "thumb") });
|
|
994
|
-
|
|
1017
|
+
writeOutFile(out, bytes);
|
|
995
1018
|
return { id, name: meta.name, mime: meta.mime, bytes: bytes.length, path: resolve(out) };
|
|
996
1019
|
}, (r) => `Wrote ${r.bytes} bytes (${r.name}) to ${r.path}`);
|
|
997
1020
|
}
|
|
998
1021
|
case "delete": {
|
|
999
1022
|
const id = positional(args, 0, "file id", U$1.delete);
|
|
1000
1023
|
return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
|
|
1001
|
-
const { getFile, deleteFile } = await import("./chunks/files-
|
|
1024
|
+
const { getFile, deleteFile } = await import("./chunks/files-D1daDgY0.mjs");
|
|
1002
1025
|
const meta = fileOut(getFile(ctx, id));
|
|
1003
1026
|
if (options.yes !== true) return { needsConfirmation: true, plan: { delete: meta } };
|
|
1004
1027
|
await deleteFile(ctx, id);
|
|
@@ -1006,7 +1029,7 @@ ${table(r.uploaded, ["id", "name", "size"])}`);
|
|
|
1006
1029
|
}, (r) => "plan" in r && r.plan ? `Would delete ${r.plan.delete.name} (${r.plan.delete.id}). Re-run with --yes once the user agrees.` : `Deleted ${"deleted" in r ? r.deleted : id} (bytes purged by \`agentcrm compact\`).`);
|
|
1007
1030
|
}
|
|
1008
1031
|
case "usage":
|
|
1009
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/files-
|
|
1032
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/files-D1daDgY0.mjs")).fileUsage(ctx));
|
|
1010
1033
|
default:
|
|
1011
1034
|
usage(`Unknown files action "${action ?? ""}".`, "Use list, upload, get, delete or usage.");
|
|
1012
1035
|
}
|
|
@@ -1019,7 +1042,7 @@ async function recordImageAction(io, options, args) {
|
|
|
1019
1042
|
if (!remove && !path) usage("records image needs an image path (or --remove).");
|
|
1020
1043
|
const local = remove ? null : readLocalFile(path);
|
|
1021
1044
|
return withSession(io, options, { lock: true }, async ({ ctx }) => {
|
|
1022
|
-
const { setRecordImage, clearRecordImage } = await import("./chunks/files-
|
|
1045
|
+
const { setRecordImage, clearRecordImage } = await import("./chunks/files-D1daDgY0.mjs");
|
|
1023
1046
|
const field = strOpt(options, "field");
|
|
1024
1047
|
if (!local) {
|
|
1025
1048
|
const r = await clearRecordImage(ctx, recordId, field);
|
|
@@ -1307,7 +1330,7 @@ const recordsCommand = {
|
|
|
1307
1330
|
const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
|
|
1308
1331
|
const sorts = parseSorts(multiOpt(options, "sort"));
|
|
1309
1332
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
1310
|
-
const { listRecords } = await import("./chunks/records-
|
|
1333
|
+
const { listRecords } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1311
1334
|
const page = listRecords(ctx, objectId, {
|
|
1312
1335
|
filters,
|
|
1313
1336
|
sorts,
|
|
@@ -1325,7 +1348,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1325
1348
|
case "get": {
|
|
1326
1349
|
const id = positional(args, 0, "record id", U.get);
|
|
1327
1350
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
1328
|
-
const { getRecordDetails } = await import("./chunks/records-
|
|
1351
|
+
const { getRecordDetails } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1329
1352
|
const d = getRecordDetails(ctx, id);
|
|
1330
1353
|
return { ...await recordOut(ctx, d.record), relations: d.relations, related: d.inverse.filter((i) => i.records.length), activityCount: d.activityCount };
|
|
1331
1354
|
});
|
|
@@ -1336,7 +1359,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1336
1359
|
if (!Object.keys(fields).length) usage("records create needs --fields or --set.", `Usage: agentcrm ${U.create}`);
|
|
1337
1360
|
const owner = strOpt(options, "owner");
|
|
1338
1361
|
return withSession(io, options, { lock: true }, async ({ ctx }) => {
|
|
1339
|
-
const { createRecord } = await import("./chunks/records-
|
|
1362
|
+
const { createRecord } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1340
1363
|
return recordOut(ctx, await createRecord(ctx, objectId, fields, owner ? { ownerId: owner } : {}));
|
|
1341
1364
|
}, (r) => `Created ${r.object} ${r.id} (${String(r.label)})`);
|
|
1342
1365
|
}
|
|
@@ -1346,7 +1369,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1346
1369
|
const owner = strOpt(options, "owner");
|
|
1347
1370
|
if (!Object.keys(fields).length && owner === void 0) usage("records update needs --fields, --set or --owner.");
|
|
1348
1371
|
return withSession(io, options, { lock: true }, async ({ ctx }) => {
|
|
1349
|
-
const { updateRecord } = await import("./chunks/records-
|
|
1372
|
+
const { updateRecord } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1350
1373
|
return recordOut(ctx, await updateRecord(ctx, id, fields, owner !== void 0 ? { ownerId: owner || null } : {}));
|
|
1351
1374
|
}, (r) => `Updated ${r.id} (${String(r.label)})`);
|
|
1352
1375
|
}
|
|
@@ -1354,7 +1377,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1354
1377
|
if (!args.length) usage("records delete needs at least one id.", `Usage: agentcrm ${U.delete}`);
|
|
1355
1378
|
const hard = boolOpt(options, "hard");
|
|
1356
1379
|
return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
|
|
1357
|
-
const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-
|
|
1380
|
+
const { getRecord, deleteRecord, bulkDelete } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1358
1381
|
const { recordLabel } = await import("./chunks/fields-B0bitgmr.mjs");
|
|
1359
1382
|
const { objectOf } = await import("./chunks/store-5gqRSKXg.mjs");
|
|
1360
1383
|
const targets2 = args.map((id) => getRecord(ctx, id, { includeDeleted: true }));
|
|
@@ -1367,7 +1390,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1367
1390
|
case "restore": {
|
|
1368
1391
|
const id = positional(args, 0, "record id", U.restore);
|
|
1369
1392
|
return withSession(io, options, { lock: true }, async ({ ctx }) => {
|
|
1370
|
-
const { restoreRecord } = await import("./chunks/records-
|
|
1393
|
+
const { restoreRecord } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1371
1394
|
return recordOut(ctx, await restoreRecord(ctx, id));
|
|
1372
1395
|
});
|
|
1373
1396
|
}
|
|
@@ -1375,7 +1398,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1375
1398
|
const query = args.join(" ").trim();
|
|
1376
1399
|
if (!query) usage("records search needs a query.", `Usage: agentcrm ${U.search}`);
|
|
1377
1400
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
1378
|
-
const { searchRecords } = await import("./chunks/records-
|
|
1401
|
+
const { searchRecords } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1379
1402
|
return searchRecords(ctx, query, { objects: listOpt(options, "object"), limit: Math.min(100, numOpt(options, "limit") ?? 20) });
|
|
1380
1403
|
});
|
|
1381
1404
|
}
|
|
@@ -1392,7 +1415,7 @@ ${table(r.records, ["id", "label", "updatedAt"])}`);
|
|
|
1392
1415
|
const [primary, ...dups] = args;
|
|
1393
1416
|
if (!primary || !dups.length) usage("records merge needs <primaryId> <duplicateId>….", `Usage: agentcrm ${U.merge}`);
|
|
1394
1417
|
return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
|
|
1395
|
-
const { getRecord } = await import("./chunks/records-
|
|
1418
|
+
const { getRecord } = await import("./chunks/records-DcHFelq5.mjs");
|
|
1396
1419
|
const all = [primary, ...dups].map((id) => getRecord(ctx, id));
|
|
1397
1420
|
const plan = { primary: await recordOut(ctx, all[0]), duplicates: await Promise.all(all.slice(1).map((r) => recordOut(ctx, r))) };
|
|
1398
1421
|
if (options.yes !== true) return { needsConfirmation: true, plan };
|
|
@@ -1424,13 +1447,13 @@ const pipelinesCommand = {
|
|
|
1424
1447
|
io,
|
|
1425
1448
|
options,
|
|
1426
1449
|
{},
|
|
1427
|
-
async ({ ctx }) => (await import("./chunks/pipelines-
|
|
1450
|
+
async ({ ctx }) => (await import("./chunks/pipelines-BAKpPk1w.mjs")).listPipelines(ctx, strOpt(options, "object")),
|
|
1428
1451
|
(ps) => ps.map((p) => `${p.id} (${p.name}, ${p.object}): ${p.stages.map((s) => `${s.label} ${Math.round(s.probability * (s.probability <= 1 ? 100 : 1))}%`).join(" → ")}`).join("\n")
|
|
1429
1452
|
);
|
|
1430
1453
|
}
|
|
1431
1454
|
if (action === "move") {
|
|
1432
1455
|
if (!id || !stage.length) usage("pipelines move needs <recordId> <stage>.");
|
|
1433
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => recordOut(ctx, await (await import("./chunks/pipelines-
|
|
1456
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => recordOut(ctx, await (await import("./chunks/pipelines-BAKpPk1w.mjs")).moveStage(ctx, id, stage.join(" "), { lostReason: strOpt(options, "lost-reason") })));
|
|
1434
1457
|
}
|
|
1435
1458
|
usage(`Unknown pipelines action "${action}".`, "Use list or move.");
|
|
1436
1459
|
}
|
|
@@ -1462,7 +1485,7 @@ const viewsCommand = {
|
|
|
1462
1485
|
const [action = "list", id] = positionals;
|
|
1463
1486
|
switch (action) {
|
|
1464
1487
|
case "list":
|
|
1465
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/views-
|
|
1488
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/views-DH1n9Lrt.mjs")).listViews(ctx, strOpt(options, "object")), (vs) => table(vs, ["id", "object", "name", "type", "shared"]));
|
|
1466
1489
|
case "create": {
|
|
1467
1490
|
const type2 = strOpt(options, "type") ?? "table";
|
|
1468
1491
|
if (type2 !== "table" && type2 !== "kanban") usage("--type must be table or kanban.");
|
|
@@ -1477,12 +1500,12 @@ const viewsCommand = {
|
|
|
1477
1500
|
...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
|
|
1478
1501
|
shared: boolOpt(options, "shared")
|
|
1479
1502
|
};
|
|
1480
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-
|
|
1503
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/views-DH1n9Lrt.mjs")).createView(ctx, input));
|
|
1481
1504
|
}
|
|
1482
1505
|
case "delete":
|
|
1483
1506
|
if (!id) usage("views delete needs a view id.");
|
|
1484
1507
|
return withSession(io, options, { lock: options.yes === true }, async ({ ctx }) => {
|
|
1485
|
-
const { getView, deleteView } = await import("./chunks/views-
|
|
1508
|
+
const { getView, deleteView } = await import("./chunks/views-DH1n9Lrt.mjs");
|
|
1486
1509
|
const v = getView(ctx, id);
|
|
1487
1510
|
if (options.yes !== true) return { needsConfirmation: true, plan: { delete: { id: v.id, name: v.name, object: v.object, shared: v.shared } } };
|
|
1488
1511
|
return deleteView(ctx, id);
|
|
@@ -1545,11 +1568,11 @@ const settingsCommand = {
|
|
|
1545
1568
|
options: [SITE_OPTION, AS_OPTION, { name: "set", type: "string", description: "key=value (repeatable), e.g. --set staleDealDays=21 --set defaultCurrency=EUR.", multiple: true }, { name: "data", type: "string", description: "JSON object of settings, or @file." }],
|
|
1546
1569
|
async run({ io, options, positionals }) {
|
|
1547
1570
|
const [action = "get"] = positionals;
|
|
1548
|
-
if (action === "get") return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/settings-
|
|
1571
|
+
if (action === "get") return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/settings-Di8nzNEL.mjs")).getSettings(ctx));
|
|
1549
1572
|
if (action === "set") {
|
|
1550
1573
|
const patch = fieldsInput(options, "data");
|
|
1551
1574
|
if (!Object.keys(patch).length) usage("settings set needs --set key=value or --data.");
|
|
1552
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/settings-
|
|
1575
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/settings-Di8nzNEL.mjs")).updateSettings(ctx, patch));
|
|
1553
1576
|
}
|
|
1554
1577
|
usage(`Unknown settings action "${action}".`, "Use get or set.");
|
|
1555
1578
|
}
|
|
@@ -1651,6 +1674,57 @@ function copyToClipboard(text) {
|
|
|
1651
1674
|
}
|
|
1652
1675
|
throw new CliError("no-clipboard", "No clipboard tool found (pbcopy, clip, wl-copy, xclip or xsel).", "Install one, or read the value from ~/.agentcrm/credentials.json yourself.");
|
|
1653
1676
|
}
|
|
1677
|
+
function sessionFile(account) {
|
|
1678
|
+
return homePath("sessions", `${account.replace(/[^a-z0-9._-]+/gi, "_")}.json`);
|
|
1679
|
+
}
|
|
1680
|
+
function forgetFirebaseSession(account) {
|
|
1681
|
+
const f = sessionFile(account);
|
|
1682
|
+
if (existsSync(f)) rmSync(f, { force: true });
|
|
1683
|
+
}
|
|
1684
|
+
function filePersistence(account, email) {
|
|
1685
|
+
const file2 = sessionFile(account);
|
|
1686
|
+
const saved = readJsonFile(file2, null);
|
|
1687
|
+
const items = saved && saved.email === email ? { ...saved.items } : {};
|
|
1688
|
+
const flush = () => {
|
|
1689
|
+
if (Object.keys(items).length) writeJsonFile(file2, { email, items }, 384);
|
|
1690
|
+
else forgetFirebaseSession(account);
|
|
1691
|
+
};
|
|
1692
|
+
return class FileSessionPersistence {
|
|
1693
|
+
static type = "LOCAL";
|
|
1694
|
+
type = "LOCAL";
|
|
1695
|
+
async _isAvailable() {
|
|
1696
|
+
return true;
|
|
1697
|
+
}
|
|
1698
|
+
async _set(key, value) {
|
|
1699
|
+
items[key] = value;
|
|
1700
|
+
flush();
|
|
1701
|
+
}
|
|
1702
|
+
async _get(key) {
|
|
1703
|
+
return items[key] ?? null;
|
|
1704
|
+
}
|
|
1705
|
+
async _remove(key) {
|
|
1706
|
+
delete items[key];
|
|
1707
|
+
flush();
|
|
1708
|
+
}
|
|
1709
|
+
_addListener() {
|
|
1710
|
+
}
|
|
1711
|
+
_removeListener() {
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
async function prepareFirebaseSession(account, options, email) {
|
|
1716
|
+
const { getFirebaseApp } = await import("./chunks/sdk-CYgNyuoK.mjs");
|
|
1717
|
+
const { initializeAuth } = await import("./chunks/index-Dio0aBWA.mjs").then((n) => n.x);
|
|
1718
|
+
try {
|
|
1719
|
+
initializeAuth(getFirebaseApp(options), { persistence: filePersistence(account, email.trim().toLowerCase()) });
|
|
1720
|
+
} catch {
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
const firebaseSession = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
1724
|
+
__proto__: null,
|
|
1725
|
+
forgetFirebaseSession,
|
|
1726
|
+
prepareFirebaseSession
|
|
1727
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
1654
1728
|
const credentialsCommand = {
|
|
1655
1729
|
name: "credentials",
|
|
1656
1730
|
usage: "credentials <set|list|remove|copy|verify|types> [<type>:<name>] [--from-env VAR[,VAR]] [--from-gh] [--from-aws-profile <name>] [--generate]",
|
|
@@ -1691,6 +1765,7 @@ const credentialsCommand = {
|
|
|
1691
1765
|
const spec = CREDENTIAL_TYPES[type2];
|
|
1692
1766
|
if (action === "remove") {
|
|
1693
1767
|
const removed = removeCredential(account);
|
|
1768
|
+
forgetFirebaseSession(account);
|
|
1694
1769
|
printResult(io, { account, removed }, () => removed ? `Removed ${account}.` : `${account} was not stored.`);
|
|
1695
1770
|
return;
|
|
1696
1771
|
}
|
|
@@ -1705,7 +1780,7 @@ const credentialsCommand = {
|
|
|
1705
1780
|
return;
|
|
1706
1781
|
}
|
|
1707
1782
|
if (!process.stdin.isTTY) {
|
|
1708
|
-
throw new CliError("needs-terminal", "verify reads the value the USER pastes in their own terminal.", `Ask the user to run it themselves (in Claude Code: \`! npx -y
|
|
1783
|
+
throw new CliError("needs-terminal", "verify reads the value the USER pastes in their own terminal.", `Ask the user to run it themselves (in Claude Code: \`! npx -y ${PACKAGE_NAME}@${readVersionInfo().version} credentials verify ${account}\`).`);
|
|
1709
1784
|
}
|
|
1710
1785
|
try {
|
|
1711
1786
|
copyToClipboard("");
|
|
@@ -1740,6 +1815,7 @@ const credentialsCommand = {
|
|
|
1740
1815
|
for (const field of spec.fields) values[field] = await promptHidden(`${field}: `);
|
|
1741
1816
|
}
|
|
1742
1817
|
const saved = setCredential(account, values);
|
|
1818
|
+
forgetFirebaseSession(account);
|
|
1743
1819
|
printResult(io, { account, type: type2, fields: Object.keys(saved.values) }, () => `Stored ${account} (${Object.keys(saved.values).join(", ")}).`);
|
|
1744
1820
|
}
|
|
1745
1821
|
};
|
|
@@ -1765,6 +1841,18 @@ function parseJsonValue(raw, name2) {
|
|
|
1765
1841
|
usage(`--${name2} is not valid JSON: ${err.message}`, `Example: --${name2} '{"targets":{"owner":50000}}' or --${name2} @config.json`);
|
|
1766
1842
|
}
|
|
1767
1843
|
}
|
|
1844
|
+
const MODULE_CONFIG_HINT = "Module configuration is JSON (≤ 256 KB) readable by every CRM user: no secrets (apiKey, token, secret, password…). Module ids: `agentcrm modules list`.";
|
|
1845
|
+
async function withHint(hint, p) {
|
|
1846
|
+
try {
|
|
1847
|
+
return await p;
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
const e = err;
|
|
1850
|
+
if (e?.name === "OpsError" && (e.code === "validation" || e.code === "notFound" || e.code === "invalid")) {
|
|
1851
|
+
throw new CliError(e.code, e.message, hint, e.code === "notFound" ? 1 : 2);
|
|
1852
|
+
}
|
|
1853
|
+
throw err;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1768
1856
|
const modulesCommand = {
|
|
1769
1857
|
name: "modules",
|
|
1770
1858
|
usage: "modules <list|get|enable|disable|config> …",
|
|
@@ -1782,13 +1870,13 @@ const modulesCommand = {
|
|
|
1782
1870
|
const apply = options.yes === true;
|
|
1783
1871
|
switch (action) {
|
|
1784
1872
|
case "list":
|
|
1785
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-
|
|
1873
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-clWdE-K2.mjs")).listModules(ctx), (rows) => table(
|
|
1786
1874
|
rows.map((m) => ({ ...m, enabled: m.enabled ? "yes" : "no", required: m.required ? "yes" : "", dependsOn: m.dependsOn.join(",") })),
|
|
1787
1875
|
["id", "name", "category", "source", "enabled", "required", "dependsOn"]
|
|
1788
1876
|
));
|
|
1789
1877
|
case "get": {
|
|
1790
1878
|
const id = positional(positionals, 1, "module id", "modules get <id>");
|
|
1791
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-
|
|
1879
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/modules-clWdE-K2.mjs")).getModule(ctx, id));
|
|
1792
1880
|
}
|
|
1793
1881
|
case "enable":
|
|
1794
1882
|
case "disable": {
|
|
@@ -1796,7 +1884,7 @@ const modulesCommand = {
|
|
|
1796
1884
|
const enabled = action === "enable";
|
|
1797
1885
|
const cascade = boolOpt(options, "cascade");
|
|
1798
1886
|
return withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
1799
|
-
const { setModuleEnabled } = await import("./chunks/modules-
|
|
1887
|
+
const { setModuleEnabled } = await import("./chunks/modules-clWdE-K2.mjs");
|
|
1800
1888
|
if (apply) return setModuleEnabled(ctx, id, enabled, { cascade });
|
|
1801
1889
|
const { result } = await dryRun(ctx, (copy) => setModuleEnabled(copy, id, enabled, { cascade }));
|
|
1802
1890
|
return { needsConfirmation: true, plan: result };
|
|
@@ -1806,20 +1894,20 @@ Nothing changed — re-run with --yes once the user agrees.` : r.changed.length
|
|
|
1806
1894
|
case "config": {
|
|
1807
1895
|
const sub = positionals[1];
|
|
1808
1896
|
const id = positional(positionals, 2, "module id", "modules config <get|set> <id>");
|
|
1809
|
-
if (sub === "get") return withSession(io, options, {}, async ({ ctx }) => ({ id, config: (await import("./chunks/modules-
|
|
1897
|
+
if (sub === "get") return withSession(io, options, {}, async ({ ctx }) => ({ id, config: (await import("./chunks/modules-clWdE-K2.mjs")).getModuleConfig(ctx, id) ?? null }));
|
|
1810
1898
|
if (sub !== "set") usage(`Unknown modules config action "${sub ?? ""}".`, "Use modules config get <id> or modules config set <id> --value <json>.");
|
|
1811
1899
|
const clear = boolOpt(options, "clear");
|
|
1812
1900
|
const raw = strOpt(options, "value");
|
|
1813
1901
|
if (!clear && raw === void 0) usage("modules config set needs --value <json|@file|-> (or --clear).");
|
|
1814
1902
|
const value = clear ? null : parseJsonValue(raw, "value");
|
|
1815
|
-
return withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
1816
|
-
const mod = await import("./chunks/modules-
|
|
1903
|
+
return withHint(MODULE_CONFIG_HINT, withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
1904
|
+
const mod = await import("./chunks/modules-clWdE-K2.mjs");
|
|
1817
1905
|
mod.getModule(ctx, id);
|
|
1818
1906
|
const before = mod.getModuleConfig(ctx, id) ?? null;
|
|
1819
1907
|
if (apply) return { id, config: await mod.setModuleConfig(ctx, id, value) ?? null, before };
|
|
1820
1908
|
const { result } = await dryRun(ctx, (copy) => mod.setModuleConfig(copy, id, value));
|
|
1821
1909
|
return { needsConfirmation: true, plan: { id, before, after: result ?? null } };
|
|
1822
|
-
});
|
|
1910
|
+
}));
|
|
1823
1911
|
}
|
|
1824
1912
|
default:
|
|
1825
1913
|
usage(`Unknown modules action "${action}".`, "Use list, get, enable, disable or config.");
|
|
@@ -1845,14 +1933,27 @@ function imageDataUrl(file2) {
|
|
|
1845
1933
|
function view(a, full) {
|
|
1846
1934
|
if (full) return { ...a };
|
|
1847
1935
|
const { logo, favicon, customCss, ...rest } = a;
|
|
1848
|
-
const
|
|
1936
|
+
const image = (s) => `${s.slice(5, s.indexOf(";"))}, ${formatSize(Math.floor((s.length - s.indexOf(",") - 1) * 3 / 4))}, #${shortHash(s)}`;
|
|
1849
1937
|
return {
|
|
1850
1938
|
...rest,
|
|
1851
|
-
logo: logo ?
|
|
1852
|
-
favicon: favicon ?
|
|
1853
|
-
customCss: customCss ? `${new TextEncoder().encode(customCss).length} bytes` : null
|
|
1939
|
+
logo: logo ? image(logo) : null,
|
|
1940
|
+
favicon: favicon ? image(favicon) : null,
|
|
1941
|
+
customCss: customCss ? `${new TextEncoder().encode(customCss).length} bytes, #${shortHash(customCss)}` : null
|
|
1854
1942
|
};
|
|
1855
1943
|
}
|
|
1944
|
+
function formatSize(n) {
|
|
1945
|
+
return n < 1024 ? `${n} B` : `${(n / 1024).toFixed(1)} KB`;
|
|
1946
|
+
}
|
|
1947
|
+
function shortHash(s) {
|
|
1948
|
+
let h = 2166136261;
|
|
1949
|
+
for (let i = 0; i < s.length; i++) {
|
|
1950
|
+
h ^= s.charCodeAt(i);
|
|
1951
|
+
h = Math.imul(h, 16777619);
|
|
1952
|
+
}
|
|
1953
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
1954
|
+
}
|
|
1955
|
+
const APPEARANCE_HINT = "Allowed values: `agentcrm help appearance`; presets and installed themes: `agentcrm appearance presets`.";
|
|
1956
|
+
const withAppearanceHint = (p) => withHint(APPEARANCE_HINT, p);
|
|
1856
1957
|
function diff(before, after) {
|
|
1857
1958
|
const out = {};
|
|
1858
1959
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
@@ -1918,8 +2019,8 @@ const appearanceCommand = {
|
|
|
1918
2019
|
const [action = "get"] = positionals;
|
|
1919
2020
|
const apply = options.yes === true;
|
|
1920
2021
|
const full = boolOpt(options, "full");
|
|
1921
|
-
const appearance = () => import("./chunks/appearance-
|
|
1922
|
-
const change = (describe, op) => withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
2022
|
+
const appearance = () => import("./chunks/appearance-BloHbiLA.mjs");
|
|
2023
|
+
const change = (describe, op) => withAppearanceHint(withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
1923
2024
|
const before = view((await appearance()).getAppearance(ctx), full);
|
|
1924
2025
|
if (apply) {
|
|
1925
2026
|
const after = view(await op(ctx), full);
|
|
@@ -1931,7 +2032,7 @@ const appearanceCommand = {
|
|
|
1931
2032
|
const changes = r.plan ? r.plan.changes : r.changes ?? {};
|
|
1932
2033
|
const lines = Object.entries(changes).map(([k, c]) => ` ${k}: ${JSON.stringify(c.from)} → ${JSON.stringify(c.to)}`);
|
|
1933
2034
|
return [`${r.plan ? "Would apply" : "Applied"}: ${describe}`, ...lines.length ? lines : [" (no visible change)"], ...r.plan ? ["Nothing changed — re-run with --yes once the user agrees."] : []].join("\n");
|
|
1934
|
-
});
|
|
2035
|
+
}));
|
|
1935
2036
|
switch (action) {
|
|
1936
2037
|
case "get":
|
|
1937
2038
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
@@ -1986,7 +2087,7 @@ async function scaffold({ io, options, positionals }) {
|
|
|
1986
2087
|
if (existsSync(dir) && readdirSync(dir).length > 0 && !boolOpt(options, "force")) {
|
|
1987
2088
|
throw new CliError("dir-not-empty", `${dir} already exists and is not empty.`, "Pick another --dir (or --force to write into it).", 2);
|
|
1988
2089
|
}
|
|
1989
|
-
const { scaffoldExtension } = await import("./chunks/extScaffold-
|
|
2090
|
+
const { scaffoldExtension } = await import("./chunks/extScaffold-Aqyjf1nr.mjs");
|
|
1990
2091
|
const res = scaffoldExtension({ kind, id, dir, name: strOpt(options, "name"), description: strOpt(options, "description"), author: strOpt(options, "author") });
|
|
1991
2092
|
printResult(io, res, () => [`Created ${kind} "${id}" in ${dir}:`, ...res.files.map((f) => ` ${f}`), "", "Next:", ...res.next.map((n) => ` ${n}`)].join("\n"));
|
|
1992
2093
|
}
|
|
@@ -1994,8 +2095,8 @@ async function check({ io, options, positionals }) {
|
|
|
1994
2095
|
const target = positionals[1] ?? ".";
|
|
1995
2096
|
const kind = strOpt(options, "kind");
|
|
1996
2097
|
if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
|
|
1997
|
-
const { readRawPackage } = await import("./chunks/extPackage-
|
|
1998
|
-
const { checkExtension } = await import("./chunks/extCheck
|
|
2098
|
+
const { readRawPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
|
|
2099
|
+
const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
|
|
1999
2100
|
const raw = await readRawPackage(target);
|
|
2000
2101
|
const report = await checkExtension(raw, { kind, callTools: !boolOpt(options, "no-call") });
|
|
2001
2102
|
printResult(io, report, () => {
|
|
@@ -2024,10 +2125,10 @@ async function check({ io, options, positionals }) {
|
|
|
2024
2125
|
}
|
|
2025
2126
|
async function pack({ io, options, positionals }) {
|
|
2026
2127
|
const target = positionals[1] ?? ".";
|
|
2027
|
-
const { readPackage } = await import("./chunks/extPackage-
|
|
2128
|
+
const { readPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
|
|
2028
2129
|
const { raw, pkg: pkg2 } = await readPackage(target);
|
|
2029
2130
|
if (raw.sourceType === "zip") usage("ext pack takes a project or an unpacked folder, not a .zip.");
|
|
2030
|
-
const { packExtension } = await import("./chunks/package-
|
|
2131
|
+
const { packExtension } = await import("./chunks/package-B7mI6HGi.mjs");
|
|
2031
2132
|
const bytes = await packExtension(pkg2);
|
|
2032
2133
|
const out = resolve(strOpt(options, "out") ?? join(raw.source, `${pkg2.manifest.id}-${pkg2.manifest.version}.zip`));
|
|
2033
2134
|
mkdirSync(dirname(out), { recursive: true });
|
|
@@ -2037,12 +2138,23 @@ async function pack({ io, options, positionals }) {
|
|
|
2037
2138
|
}
|
|
2038
2139
|
async function install({ io, options, positionals }) {
|
|
2039
2140
|
const target = positional(positionals, 1, "ZIP or folder", "ext install <zip|dir> [--site <alias>] [--yes]");
|
|
2040
|
-
const { readPackage } = await import("./chunks/extPackage-
|
|
2141
|
+
const { readPackage } = await import("./chunks/extPackage-DuZ6GZhp.mjs");
|
|
2041
2142
|
const { pkg: pkg2, raw } = await readPackage(target);
|
|
2042
2143
|
const apply = options.yes === true;
|
|
2043
2144
|
const disabled = boolOpt(options, "disabled");
|
|
2145
|
+
if (!boolOpt(options, "force")) {
|
|
2146
|
+
const { checkExtension } = await import("./chunks/extCheck-GgHG1dYB.mjs");
|
|
2147
|
+
const report = await checkExtension(raw, { callTools: false });
|
|
2148
|
+
if (!report.valid) {
|
|
2149
|
+
throw new CliError(
|
|
2150
|
+
"invalid-package",
|
|
2151
|
+
`The extension doesn't pass the check: ${report.errors.map((e) => e.message).join(" ")}`,
|
|
2152
|
+
`Nothing was installed. Run \`agentcrm ext check ${target}\` for the full report (--force installs it anyway).`
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2044
2156
|
return withSession(io, options, { lock: apply }, async (s) => {
|
|
2045
|
-
const ext = await import("./chunks/extensions-
|
|
2157
|
+
const ext = await import("./chunks/extensions-Bp72uZ9a.mjs");
|
|
2046
2158
|
const existing = s.store.get("extensions", pkg2.manifest.id);
|
|
2047
2159
|
const m = pkg2.manifest;
|
|
2048
2160
|
const plan = {
|
|
@@ -2067,7 +2179,7 @@ async function install({ io, options, positionals }) {
|
|
|
2067
2179
|
}
|
|
2068
2180
|
const result = await ext.installExtension(s.ctx, pkg2, disabled ? { enabled: false } : {});
|
|
2069
2181
|
const report = await s.extensions.sync();
|
|
2070
|
-
const { getLoadedExtension } = await import("./chunks/registry-
|
|
2182
|
+
const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
|
|
2071
2183
|
const loaded = getLoadedExtension(m.id);
|
|
2072
2184
|
const failure = report?.failed.find((f) => f.id === m.id || f.id === "*");
|
|
2073
2185
|
if (failure && !loaded) return { ...result, author: m.author ?? null, load: { status: "error", error: failure.error } };
|
|
@@ -2078,7 +2190,7 @@ async function install({ io, options, positionals }) {
|
|
|
2078
2190
|
...r.plan.warnings.map((w) => ` note: ${w}`),
|
|
2079
2191
|
` ${r.plan.warning}`,
|
|
2080
2192
|
"Nothing changed — re-run with --yes once the user agrees."
|
|
2081
|
-
].join("\n") : `${r.mode === "upgrade" ? `Upgraded ${r.previousVersion} → ` :
|
|
2193
|
+
].join("\n") : `${r.mode === "upgrade" ? `Upgraded ${r.id} ${r.previousVersion} → ${r.version}` : `Installed ${r.id} ${r.version}`} (${r.enabled ? "enabled" : "disabled"}; load: ${r.load.status}${"error" in r.load ? ` — ${r.load.error}` : ""})${r.warnings.length ? `
|
|
2082
2194
|
${r.warnings.join("\n ")}` : ""}`);
|
|
2083
2195
|
}
|
|
2084
2196
|
const extCommand = {
|
|
@@ -2089,7 +2201,7 @@ const extCommand = {
|
|
|
2089
2201
|
{ name: "new", usage: "ext new <module|theme> <id> [--dir <path>] [--name <name>] [--description <text>] [--author <name>] [--force]", summary: "Scaffold a standalone extension project (package.json, vite.config.ts, tsconfig + runtime typings, src/index.tsx, README).", writes: true },
|
|
2090
2202
|
{ name: "check", usage: "ext check [<dir|zip>] [--kind module|theme] [--no-call]", summary: "Validate manifest, ids, apiVersion, sizes, imports; load the bundle in a throw-away CRM and report routes, nav, slots, AI tools, automation actions, theme values." },
|
|
2091
2203
|
{ name: "pack", usage: "ext pack [<dir>] [--out <file.zip>]", summary: "ZIP manifest.json + bundle.js (+ style.css) for Settings → Modules → Install or `ext install`.", writes: true },
|
|
2092
|
-
{ name: "install", usage: "ext install <zip|dir> [--site <alias>] [--disabled] [--yes]", summary: "Install or upgrade an extension on a CRM. Prints the plan (id, version, author, upgrade, size) unless --yes.", writes: true, destructive: true },
|
|
2204
|
+
{ name: "install", usage: "ext install <zip|dir> [--site <alias>] [--disabled] [--force] [--yes]", summary: "Install or upgrade an extension on a CRM. The bundle is first loaded in a throw-away CRM (like ext check; --force skips it). Prints the plan (id, version, author, upgrade, size) unless --yes.", writes: true, destructive: true },
|
|
2093
2205
|
{ name: "list", usage: "ext list [--kind module|theme]", summary: "Installed extensions with version, enabled state and load status in this process." },
|
|
2094
2206
|
{ name: "enable", usage: "ext enable <id> [--cascade] [--yes]", summary: "Enable an installed extension (plan unless --yes).", writes: true },
|
|
2095
2207
|
{ name: "disable", usage: "ext disable <id> [--cascade] [--yes]", summary: "Disable it without uninstalling (plan unless --yes).", writes: true },
|
|
@@ -2104,7 +2216,7 @@ const extCommand = {
|
|
|
2104
2216
|
{ name: "name", type: "string", description: "new: display name." },
|
|
2105
2217
|
{ name: "description", type: "string", description: "new: one-sentence description." },
|
|
2106
2218
|
{ name: "author", type: "string", description: "new: author shown in the install plan and Settings → Modules." },
|
|
2107
|
-
{ name: "force", type: "boolean", description: "new: write into a non-empty folder. uninstall: even if enabled modules depend on it." },
|
|
2219
|
+
{ name: "force", type: "boolean", description: "new: write into a non-empty folder. install: skip the trial load (ext check). uninstall: even if enabled modules depend on it." },
|
|
2108
2220
|
{ name: "kind", type: "string", description: "check: expected kind. list: only modules or themes." },
|
|
2109
2221
|
{ name: "no-call", type: "boolean", description: "check: don't smoke-call the AI tools on the demo data." },
|
|
2110
2222
|
{ name: "out", type: "string", description: "pack / export: ZIP path (default: <dir>/<id>-<version>.zip, ./<id>-<version>.zip)." },
|
|
@@ -2128,8 +2240,8 @@ const extCommand = {
|
|
|
2128
2240
|
const kind = strOpt(options, "kind");
|
|
2129
2241
|
if (kind && kind !== "module" && kind !== "theme") usage("--kind is module or theme.");
|
|
2130
2242
|
return withSession(io, options, {}, async (s) => {
|
|
2131
|
-
const { listExtensions } = await import("./chunks/extensions-
|
|
2132
|
-
const { getLoadedExtension } = await import("./chunks/registry-
|
|
2243
|
+
const { listExtensions } = await import("./chunks/extensions-Bp72uZ9a.mjs");
|
|
2244
|
+
const { getLoadedExtension } = await import("./chunks/registry-COfJrSmg.mjs");
|
|
2133
2245
|
return listExtensions(s.ctx, kind ? { kind } : {}).map((e) => ({ ...e, load: s.extensions.safeMode ? "skipped" : getLoadedExtension(e.id)?.status ?? (e.enabled ? "not loaded" : "disabled"), ...getLoadedExtension(e.id)?.error ? { loadError: getLoadedExtension(e.id)?.error } : {} }));
|
|
2134
2246
|
}, (rows) => table(rows.map((r) => ({ ...r, enabled: r.enabled ? "yes" : "no" })), ["id", "kind", "name", "version", "enabled", "load", "author"]));
|
|
2135
2247
|
}
|
|
@@ -2139,15 +2251,15 @@ const extCommand = {
|
|
|
2139
2251
|
const enabled = action === "enable";
|
|
2140
2252
|
const cascade = boolOpt(options, "cascade");
|
|
2141
2253
|
return withSession(io, options, { lock: apply }, async (s) => {
|
|
2142
|
-
const ext = await import("./chunks/extensions-
|
|
2254
|
+
const ext = await import("./chunks/extensions-Bp72uZ9a.mjs");
|
|
2143
2255
|
const before = ext.getExtension(s.ctx, id);
|
|
2144
2256
|
if (!apply) {
|
|
2145
2257
|
const { result } = await dryRun(s.ctx, async (copy) => {
|
|
2146
2258
|
await ext.setExtensionEnabled(copy, id, enabled, { cascade });
|
|
2147
|
-
return (await import("./chunks/modules-
|
|
2259
|
+
return (await import("./chunks/modules-clWdE-K2.mjs")).moduleStates(copy.store);
|
|
2148
2260
|
});
|
|
2149
2261
|
const now = new Map(result.map((m) => [m.id, m.enabled]));
|
|
2150
|
-
const changed = (await import("./chunks/modules-
|
|
2262
|
+
const changed = (await import("./chunks/modules-clWdE-K2.mjs")).moduleStates(s.store).filter((m) => now.has(m.id) && now.get(m.id) !== m.enabled).map((m) => ({ id: m.id, enabled: now.get(m.id) }));
|
|
2151
2263
|
if (before.kind === "theme" && before.enabled !== enabled) changed.push({ id, enabled });
|
|
2152
2264
|
return { needsConfirmation: true, plan: { id, kind: before.kind, from: before.enabled, to: enabled, changed } };
|
|
2153
2265
|
}
|
|
@@ -2161,7 +2273,7 @@ Nothing changed — re-run with --yes once the user agrees.` : `${r.id} is now $
|
|
|
2161
2273
|
const id = positional(positionals, 1, "extension id", "ext uninstall <id> [--yes]");
|
|
2162
2274
|
const force = boolOpt(options, "force");
|
|
2163
2275
|
return withSession(io, options, { lock: apply }, async (s) => {
|
|
2164
|
-
const ext = await import("./chunks/extensions-
|
|
2276
|
+
const ext = await import("./chunks/extensions-Bp72uZ9a.mjs");
|
|
2165
2277
|
const summary = ext.getExtension(s.ctx, id);
|
|
2166
2278
|
if (!apply) {
|
|
2167
2279
|
await dryRun(s.ctx, (copy) => ext.uninstallExtension(copy, id, { force }));
|
|
@@ -2176,8 +2288,8 @@ Nothing changed — re-run with --yes once the user agrees.` : `Uninstalled ${r.
|
|
|
2176
2288
|
case "export": {
|
|
2177
2289
|
const id = positional(positionals, 1, "extension id", "ext export <id> [--out <file.zip>]");
|
|
2178
2290
|
return withSession(io, options, {}, async (s) => {
|
|
2179
|
-
const { getExtensionPackage } = await import("./chunks/extensions-
|
|
2180
|
-
const { packExtension } = await import("./chunks/package-
|
|
2291
|
+
const { getExtensionPackage } = await import("./chunks/extensions-Bp72uZ9a.mjs");
|
|
2292
|
+
const { packExtension } = await import("./chunks/package-B7mI6HGi.mjs");
|
|
2181
2293
|
const pkg2 = getExtensionPackage(s.ctx, id);
|
|
2182
2294
|
const bytes = await packExtension(pkg2);
|
|
2183
2295
|
const out = resolve(strOpt(options, "out") ?? `${id}-${pkg2.manifest.version}.zip`);
|
|
@@ -2191,9 +2303,6 @@ Nothing changed — re-run with --yes once the user agrees.` : `Uninstalled ${r.
|
|
|
2191
2303
|
}
|
|
2192
2304
|
}
|
|
2193
2305
|
};
|
|
2194
|
-
function printPlan(io, plan) {
|
|
2195
|
-
printResult(io, plan, () => `Would restore ${String(plan.plan.restore)} (${String(plan.plan.mode)}). Re-run with --yes once the user agrees.`);
|
|
2196
|
-
}
|
|
2197
2306
|
function mappingOpt(options) {
|
|
2198
2307
|
const raw = typeof options.map === "string" ? readTextValue(options.map, "map").trim() : void 0;
|
|
2199
2308
|
if (!raw) return void 0;
|
|
@@ -2242,10 +2351,22 @@ const importCommand = {
|
|
|
2242
2351
|
if (!existsSync2(path)) usage(`${path} does not exist.`);
|
|
2243
2352
|
const bytes = new Uint8Array(readFileSync2(path));
|
|
2244
2353
|
if (options.yes !== true) {
|
|
2245
|
-
|
|
2246
|
-
|
|
2354
|
+
return withSession(
|
|
2355
|
+
io,
|
|
2356
|
+
options,
|
|
2357
|
+
{},
|
|
2358
|
+
async ({ ctx }) => {
|
|
2359
|
+
const plan = await (await import("./chunks/backup-W3CjgkAJ.mjs")).planBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" });
|
|
2360
|
+
return { needsConfirmation: true, plan: { restore: resolve(path), bytes: bytes.length, ...plan } };
|
|
2361
|
+
},
|
|
2362
|
+
(r) => [
|
|
2363
|
+
`Would restore ${r.plan.restore} (${r.plan.mode}): ${r.plan.added} new, ${r.plan.overwritten} overwritten, ${r.plan.deleted} deleted document(s), ${r.plan.files} file(s).`,
|
|
2364
|
+
...r.plan.warnings.map((w) => `Warning: ${w}`),
|
|
2365
|
+
"Re-run with --yes once the user agrees."
|
|
2366
|
+
].join("\n")
|
|
2367
|
+
);
|
|
2247
2368
|
}
|
|
2248
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/backup-
|
|
2369
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/backup-W3CjgkAJ.mjs")).importBackupZip(ctx, bytes, { mode: options.replace === true ? "replace" : "merge" }), (r) => `Restored ${r.written} document(s) and ${r.files} file(s)`);
|
|
2249
2370
|
}
|
|
2250
2371
|
if (kind !== "csv") usage('Only "import csv" and "import zip" are supported.', "Usage: agentcrm import csv <file> --object <id>");
|
|
2251
2372
|
const source = positional(positionals, 1, "CSV file (or -)", "import csv <file|-> --object <id>");
|
|
@@ -2256,7 +2377,7 @@ const importCommand = {
|
|
|
2256
2377
|
const apply = options.yes === true && options["dry-run"] !== true;
|
|
2257
2378
|
const shown = numOpt(options, "rows") ?? 100;
|
|
2258
2379
|
return withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
2259
|
-
const { importCsv } = await import("./chunks/importCsv-
|
|
2380
|
+
const { importCsv } = await import("./chunks/importCsv-LVmMTaWU.mjs");
|
|
2260
2381
|
const report = await importCsv(ctx, {
|
|
2261
2382
|
object: objectId,
|
|
2262
2383
|
csv,
|
|
@@ -2305,16 +2426,16 @@ const exportCommand = {
|
|
|
2305
2426
|
const file2 = strOpt(options, "out");
|
|
2306
2427
|
if (!file2) usage("export zip needs --out <file.zip>.");
|
|
2307
2428
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
2308
|
-
const { exportBackupZip } = await import("./chunks/backup-
|
|
2429
|
+
const { exportBackupZip } = await import("./chunks/backup-W3CjgkAJ.mjs");
|
|
2309
2430
|
const { zip, report } = await exportBackupZip(ctx, { includeFiles: options["no-files"] !== true });
|
|
2310
|
-
|
|
2431
|
+
writeOutFile(file2, zip);
|
|
2311
2432
|
return { file: resolve(file2), zipBytes: zip.length, files: report.files, missing: report.missing, fileBytes: report.bytes };
|
|
2312
2433
|
}, (r) => `Wrote ${r.zipBytes} bytes (${r.files} file(s)${r.missing.length ? `, ${r.missing.length} missing` : ""}) to ${r.file}`);
|
|
2313
2434
|
}
|
|
2314
2435
|
const out = strOpt(options, "out");
|
|
2315
2436
|
const deliver = (text, extra) => {
|
|
2316
2437
|
if (out) {
|
|
2317
|
-
|
|
2438
|
+
writeOutFile(out, text);
|
|
2318
2439
|
return { file: resolve(out), bytes: Buffer.byteLength(text), ...extra };
|
|
2319
2440
|
}
|
|
2320
2441
|
return { ...extra, content: text };
|
|
@@ -2324,7 +2445,7 @@ const exportCommand = {
|
|
|
2324
2445
|
const filters = parseFilters(multiOpt(options, "filter"), FILTER_OPS);
|
|
2325
2446
|
const sorts = parseSorts(multiOpt(options, "sort"));
|
|
2326
2447
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
2327
|
-
const { exportCsv } = await import("./chunks/exportCsv-
|
|
2448
|
+
const { exportCsv } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
|
|
2328
2449
|
const csv = exportCsv(ctx, objectId, {
|
|
2329
2450
|
viewId: strOpt(options, "view"),
|
|
2330
2451
|
filters,
|
|
@@ -2333,12 +2454,14 @@ const exportCommand = {
|
|
|
2333
2454
|
values: boolOpt(options, "raw") ? "raw" : "labels",
|
|
2334
2455
|
includeDeleted: boolOpt(options, "deleted")
|
|
2335
2456
|
});
|
|
2336
|
-
|
|
2457
|
+
const { default: Papa } = await import("./chunks/papaparse-Df7i1dHr.mjs").then((n) => n.p);
|
|
2458
|
+
const rows = Math.max(0, Papa.parse(csv, { skipEmptyLines: true }).data.length - 1);
|
|
2459
|
+
return deliver(csv, { object: objectId, rows });
|
|
2337
2460
|
}, (r) => "content" in r ? String(r.content) : `Wrote ${String(r.rows)} row(s) to ${String(r.file)}`);
|
|
2338
2461
|
}
|
|
2339
2462
|
if (kind === "json") {
|
|
2340
2463
|
return withSession(io, options, {}, async ({ ctx }) => {
|
|
2341
|
-
const { exportJson } = await import("./chunks/exportCsv-
|
|
2464
|
+
const { exportJson } = await import("./chunks/exportCsv-C6e6XEmB.mjs");
|
|
2342
2465
|
const data = exportJson(ctx, { collections: listOpt(options, "collections") });
|
|
2343
2466
|
const text = JSON.stringify(data, null, 2);
|
|
2344
2467
|
if (out) return deliver(text, {});
|
|
@@ -2374,6 +2497,7 @@ const reportCommand = {
|
|
|
2374
2497
|
async run({ io, options, positionals }) {
|
|
2375
2498
|
const name2 = positional(positionals, 0, "report name", "report <name>");
|
|
2376
2499
|
const internal = REPORT_ALIASES[name2] ?? name2;
|
|
2500
|
+
if (!Object.values(REPORT_ALIASES).includes(internal)) usage(`Unknown report "${name2}".`, `Reports: ${Object.keys(REPORT_ALIASES).join(", ")}.`);
|
|
2377
2501
|
const params = {
|
|
2378
2502
|
...jsonObjectOpt(options, "params") ?? {},
|
|
2379
2503
|
...numOpt(options, "months") !== void 0 ? { months: numOpt(options, "months") } : {},
|
|
@@ -2381,7 +2505,7 @@ const reportCommand = {
|
|
|
2381
2505
|
...strOpt(options, "pipeline") ? { pipelineId: strOpt(options, "pipeline") } : {},
|
|
2382
2506
|
...strOpt(options, "owner") ? { ownerId: strOpt(options, "owner") } : {}
|
|
2383
2507
|
};
|
|
2384
|
-
return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-
|
|
2508
|
+
return withSession(io, options, {}, async ({ ctx }) => ({ report: name2, data: (await import("./chunks/reports-BACHGccj.mjs")).runReport(ctx, internal, params) }));
|
|
2385
2509
|
}
|
|
2386
2510
|
};
|
|
2387
2511
|
const statsCommand = {
|
|
@@ -2391,7 +2515,7 @@ const statsCommand = {
|
|
|
2391
2515
|
options: [SITE_OPTION, AS_OPTION],
|
|
2392
2516
|
async run({ io, options }) {
|
|
2393
2517
|
return withSession(io, options, {}, async ({ ctx, store }) => {
|
|
2394
|
-
const stats = (await import("./chunks/stats-
|
|
2518
|
+
const stats = (await import("./chunks/stats-BfuLWhVV.mjs")).getStats(ctx);
|
|
2395
2519
|
const journal = store.stats;
|
|
2396
2520
|
return { ...stats, ...journal ? { journal: await journal.call(store) } : {} };
|
|
2397
2521
|
}, (s) => [`backend: ${s.backend}`, ...s.objects.map((o) => ` ${o.label}: ${o.count}${o.deleted ? ` (+${o.deleted} deleted)` : ""}`), `open tasks: ${s.openTasks} (${s.overdueTasks} overdue)`, `users: ${s.users}`].join("\n"));
|
|
@@ -2405,7 +2529,7 @@ const compactCommand = {
|
|
|
2405
2529
|
async run({ io, options }) {
|
|
2406
2530
|
const apply = options.yes === true;
|
|
2407
2531
|
return withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
2408
|
-
const { compactData } = await import("./chunks/compact-
|
|
2532
|
+
const { compactData } = await import("./chunks/compact-BGHI92tm.mjs");
|
|
2409
2533
|
const report = await compactData(ctx, { olderThanDays: numOpt(options, "older-than"), keepRuns: numOpt(options, "keep-runs"), dryRun: !apply });
|
|
2410
2534
|
return apply ? report : { needsConfirmation: true, plan: report };
|
|
2411
2535
|
}, (r) => "plan" in r ? `Would purge ${r.plan.purgedRecords.length} record(s), ${r.plan.purgedActivities} activit(ies), ${r.plan.purgedRuns} run(s), ${r.plan.purgedFiles} file(s). Re-run with --yes once the user agrees.` : `Purged ${r.purgedRecords.length} record(s), ${r.purgedFiles} file(s); storage compacted: ${r.storageCompacted}`);
|
|
@@ -2426,19 +2550,19 @@ const automationsCommand = {
|
|
|
2426
2550
|
const [action = "list", id] = positionals;
|
|
2427
2551
|
switch (action) {
|
|
2428
2552
|
case "list":
|
|
2429
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/automations-
|
|
2553
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/automations-DwWruxmu.mjs")).listAutomations(ctx).map((a) => ({ id: a.id, name: a.name, enabled: a.enabled, object: a.object, trigger: a.trigger, actions: a.actions.map((x) => x.type), lastRunAt: a.lastRunAt ? new Date(a.lastRunAt).toISOString() : null })), (rows) => table(rows, ["id", "name", "enabled", "object", "lastRunAt"]));
|
|
2430
2554
|
case "enable":
|
|
2431
2555
|
case "disable": {
|
|
2432
2556
|
if (!id) usage(`automations ${action} needs an id.`);
|
|
2433
2557
|
return withSession(io, options, { lock: true }, async ({ ctx }) => {
|
|
2434
|
-
const mod = await import("./chunks/automations-
|
|
2558
|
+
const mod = await import("./chunks/automations-DwWruxmu.mjs");
|
|
2435
2559
|
return mod.setAutomationEnabled(ctx, id, action === "enable");
|
|
2436
2560
|
});
|
|
2437
2561
|
}
|
|
2438
2562
|
case "run": {
|
|
2439
2563
|
const apply = options.yes === true;
|
|
2440
2564
|
return withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
2441
|
-
const { runScheduled } = await import("./chunks/automations-
|
|
2565
|
+
const { runScheduled } = await import("./chunks/automations-DwWruxmu.mjs");
|
|
2442
2566
|
const report = await runScheduled(ctx, { dryRun: !apply, automationId: strOpt(options, "id"), force: boolOpt(options, "force") });
|
|
2443
2567
|
return apply ? report : { needsConfirmation: true, ...report };
|
|
2444
2568
|
}, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}ran ${r.ran}, skipped ${r.skipped}, errors ${r.errors}`);
|
|
@@ -2462,16 +2586,16 @@ const formsCommand = {
|
|
|
2462
2586
|
const [action = "list", id] = positionals;
|
|
2463
2587
|
switch (action) {
|
|
2464
2588
|
case "list":
|
|
2465
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-
|
|
2589
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/forms-BfuWaQjA.mjs")).listForms(ctx), (fs) => table(fs, ["id", "name", "object", "publishedPath"]));
|
|
2466
2590
|
case "publish": {
|
|
2467
2591
|
if (!id) usage("forms publish needs a form id.");
|
|
2468
2592
|
return withSession(io, options, { lock: options.yes === true }, async (s) => {
|
|
2469
|
-
const forms = await import("./chunks/forms-
|
|
2593
|
+
const forms = await import("./chunks/forms-BfuWaQjA.mjs");
|
|
2470
2594
|
const built = await forms.buildFormHtml(s.ctx, id);
|
|
2471
2595
|
const target = s.hostingTarget();
|
|
2472
2596
|
if (!target) usage(`${s.site.alias} has no hosting to publish the form to.`);
|
|
2473
2597
|
if (options.yes !== true) return { needsConfirmation: true, plan: { form: id, path: built.path, hosting: s.site.hosting?.type, bytes: built.html.length } };
|
|
2474
|
-
const { buildConnector } = await import("./chunks/hosting-
|
|
2598
|
+
const { buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
|
|
2475
2599
|
const connector = buildConnector(target);
|
|
2476
2600
|
const siteUrl = typeof s.site.hosting?.settings.siteUrl === "string" ? s.site.hosting.settings.siteUrl.replace(/\/+$/, "") : "";
|
|
2477
2601
|
return forms.publishForm(s.ctx, id, { put: (input) => connector.put(input), publicUrl: async (path) => siteUrl ? `${siteUrl}/${path}` : connector.publicUrl(path) });
|
|
@@ -2480,7 +2604,7 @@ const formsCommand = {
|
|
|
2480
2604
|
case "ingest": {
|
|
2481
2605
|
const apply = options.yes === true;
|
|
2482
2606
|
return withSession(io, options, { lock: apply }, async ({ ctx }) => {
|
|
2483
|
-
const { ingestInbox } = await import("./chunks/forms-
|
|
2607
|
+
const { ingestInbox } = await import("./chunks/forms-BfuWaQjA.mjs");
|
|
2484
2608
|
const report = await ingestInbox(ctx, strOpt(options, "form"), { dryRun: !apply, limit: numOpt(options, "limit") });
|
|
2485
2609
|
return apply ? report : { needsConfirmation: true, ...report };
|
|
2486
2610
|
}, (r) => `${"needsConfirmation" in r ? "DRY RUN — " : ""}processed ${r.processed}: ${r.created} created, ${r.updated} updated, ${r.failed} failed`);
|
|
@@ -2549,6 +2673,31 @@ async function fetchHostedAdmin(adminUrl, fetchImpl = fetch) {
|
|
|
2549
2673
|
return { version: null, dataSchema: null, error: "version.json is not valid JSON" };
|
|
2550
2674
|
}
|
|
2551
2675
|
}
|
|
2676
|
+
function localAdminFolder(hosting) {
|
|
2677
|
+
if (!hosting || hosting.connectorId !== "local-folder") return null;
|
|
2678
|
+
const root = typeof hosting.settings.path === "string" ? hosting.settings.path : "";
|
|
2679
|
+
if (!root) return null;
|
|
2680
|
+
return join(root, hosting.folder ?? "admin");
|
|
2681
|
+
}
|
|
2682
|
+
function readLocalAdmin(folder) {
|
|
2683
|
+
for (const name2 of ["version.json", "admin-files.json"]) {
|
|
2684
|
+
const file2 = join(folder, name2);
|
|
2685
|
+
if (!existsSync(file2)) continue;
|
|
2686
|
+
try {
|
|
2687
|
+
const m = JSON.parse(readFileSync(file2, "utf8"));
|
|
2688
|
+
return { version: m.version ?? null, dataSchema: m.dataSchema ?? null };
|
|
2689
|
+
} catch {
|
|
2690
|
+
return { version: null, dataSchema: null, error: `${name2} is not valid JSON` };
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
return { version: null, dataSchema: null, error: `no version.json in ${folder}` };
|
|
2694
|
+
}
|
|
2695
|
+
async function siteAdminVersion(site) {
|
|
2696
|
+
if (site.adminUrl) return { ...await fetchHostedAdmin(site.adminUrl), source: "url" };
|
|
2697
|
+
const folder = localAdminFolder(site.hosting);
|
|
2698
|
+
if (folder) return { ...readLocalAdmin(folder), source: "folder" };
|
|
2699
|
+
return { version: null, dataSchema: null, source: "none" };
|
|
2700
|
+
}
|
|
2552
2701
|
function readChangelog() {
|
|
2553
2702
|
try {
|
|
2554
2703
|
const data = JSON.parse(readFileSync(join(packageRoot(), "changelog.json"), "utf8"));
|
|
@@ -2591,12 +2740,12 @@ const deployAdminCommand = {
|
|
|
2591
2740
|
const sites = options.all === true ? listSites().sites.filter((s) => s.hosting) : [resolveSite(strOpt(options, "site"))];
|
|
2592
2741
|
if (!sites.length) throw new CliError("no-sites", "No registered CRM has a hosting.");
|
|
2593
2742
|
installBrowserEnv();
|
|
2594
|
-
const { adminOps, applyOps, buildConnector } = await import("./chunks/hosting-
|
|
2743
|
+
const { adminOps, applyOps, buildConnector } = await import("./chunks/hosting-Dnn1k3cV.mjs");
|
|
2595
2744
|
const { hostingTargetOf: hostingTargetOf2 } = await Promise.resolve().then(() => session);
|
|
2596
2745
|
const plans = [];
|
|
2597
2746
|
for (const site of sites) {
|
|
2598
2747
|
if (!site.hosting) throw new CliError("not-hosted", `${site.alias} has no hosting recorded.`, "Register it with `site add --admin-url …` from a CRM whose admin is hosted, or reinstall with a hosting.");
|
|
2599
|
-
const hosted =
|
|
2748
|
+
const hosted = await siteAdminVersion(site);
|
|
2600
2749
|
const plan = {
|
|
2601
2750
|
alias: site.alias,
|
|
2602
2751
|
adminUrl: site.adminUrl,
|
|
@@ -2695,7 +2844,7 @@ async function runDoctor(site, opts = {}) {
|
|
|
2695
2844
|
installBrowserEnv();
|
|
2696
2845
|
try {
|
|
2697
2846
|
if (site.runtime.backend === "journal" && site.runtime.journal.connectorId === "git-pages") {
|
|
2698
|
-
const { probeTarget } = await import("./chunks/install-
|
|
2847
|
+
const { probeTarget } = await import("./chunks/install-BBTA4nMm.mjs");
|
|
2699
2848
|
const { dataTargetOf: dataTargetOf2 } = await Promise.resolve().then(() => session);
|
|
2700
2849
|
const probe = await probeTarget(dataTargetOf2(site), { requireSiteUrl: false, requirePrivate: true });
|
|
2701
2850
|
add(probe.ok ? { id: "data-repo", status: "ok", message: probe.detail } : { id: "data-repo", status: "fail", message: probe.detail, fix: "Make the data repository private and check the token's Contents permission." });
|
|
@@ -2712,7 +2861,7 @@ async function runDoctor(site, opts = {}) {
|
|
|
2712
2861
|
if (!settingsDoc(store)) missing.push("settings");
|
|
2713
2862
|
if (!usersOf(store).some((u) => u.role === "admin" && !u.disabled)) missing.push("an active admin user");
|
|
2714
2863
|
if (missing.length && opts.repair) {
|
|
2715
|
-
const { seedCrm } = await import("./chunks/seed-
|
|
2864
|
+
const { seedCrm } = await import("./chunks/seed-6MRmQQBR.mjs");
|
|
2716
2865
|
repaired = await seedCrm(session$1.system, { admin: site.user ? { name: site.user.split("@")[0], email: site.user } : void 0 });
|
|
2717
2866
|
add({ id: "dataset", status: "ok", message: `Repaired: seeded ${missing.join(", ")}.` });
|
|
2718
2867
|
} else {
|
|
@@ -2740,26 +2889,40 @@ async function runDoctor(site, opts = {}) {
|
|
|
2740
2889
|
add({ id: "data-access", status: "fail", message: e.message, fix: e.hint });
|
|
2741
2890
|
}
|
|
2742
2891
|
const adminUrl = site.adminUrl;
|
|
2892
|
+
const adminFolder = adminUrl ? null : localAdminFolder(site.hosting);
|
|
2743
2893
|
let origin = null;
|
|
2744
|
-
if (!adminUrl) {
|
|
2894
|
+
if (!adminUrl && !adminFolder) {
|
|
2745
2895
|
add({ id: "admin", status: "skip", message: "Admin not hosted (use `agentcrm serve`)." });
|
|
2746
2896
|
} else {
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2897
|
+
if (adminUrl) {
|
|
2898
|
+
origin = new URL(adminUrl).origin;
|
|
2899
|
+
add(
|
|
2900
|
+
adminUrl.startsWith("https://") || /^http:\/\/(localhost|127\.0\.0\.1)/.test(adminUrl) ? { id: "https", status: "ok", message: "Admin served over HTTPS (or localhost)." } : { id: "https", status: "fail", message: `${adminUrl} is plain HTTP: browsers refuse WebCrypto (signing, encryption) there.`, fix: "Serve the admin over HTTPS (CDN / custom domain with TLS, the bucket's https endpoint, GitHub Pages)." }
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
const read = async (name2) => {
|
|
2904
|
+
if (adminUrl) {
|
|
2905
|
+
const res = await fetchText(`${adminUrl}${name2}`);
|
|
2906
|
+
return { ok: res.ok, text: res.text, error: res.error ?? (res.ok ? void 0 : `HTTP ${res.status}`) };
|
|
2907
|
+
}
|
|
2908
|
+
const file2 = join(adminFolder, name2);
|
|
2909
|
+
if (!existsSync(file2)) return { ok: false, text: "", error: `${file2} not found` };
|
|
2910
|
+
return { ok: true, text: readFileSync(file2, "utf8") };
|
|
2911
|
+
};
|
|
2912
|
+
const index = await read("index.html");
|
|
2752
2913
|
if (!index.ok) {
|
|
2753
|
-
add({ id: "admin", status: "fail", message: `Admin page not
|
|
2914
|
+
add({ id: "admin", status: "fail", message: `Admin page not ${adminUrl ? "served" : "found"} (${index.error}).`, fix: `\`agentcrm deploy-admin --site ${site.alias} --yes\`${adminUrl ? " (Pages hosts take a minute after a commit)" : ""}.` });
|
|
2754
2915
|
} else {
|
|
2755
|
-
add(
|
|
2756
|
-
|
|
2916
|
+
add(
|
|
2917
|
+
adminUrl ? { id: "admin", status: "ok", message: `Admin served at ${adminUrl}.` } : { id: "admin", status: "ok", message: `Admin in the folder ${adminFolder} (no public URL): open it with \`agentcrm serve --dir ${adminFolder}\`.` }
|
|
2918
|
+
);
|
|
2919
|
+
const hosted = adminUrl ? await fetchHostedAdmin(adminUrl) : readLocalAdmin(adminFolder);
|
|
2757
2920
|
add(
|
|
2758
2921
|
!hosted.version ? { id: "admin-version", status: "warn", message: "version.json missing next to the admin.", fix: `\`agentcrm deploy-admin --site ${site.alias} --yes\`.` } : compareVersions(hosted.version, info.version) >= 0 ? { id: "admin-version", status: "ok", message: `Admin ${hosted.version} is up to date.` } : { id: "admin-version", status: "warn", message: `Admin ${hosted.version} is older than ${info.version}.`, fix: `Offer \`agentcrm deploy-admin --site ${site.alias}\`.` }
|
|
2759
2922
|
);
|
|
2760
|
-
const cfg = await
|
|
2923
|
+
const cfg = await read("config.js");
|
|
2761
2924
|
try {
|
|
2762
|
-
if (!cfg.ok) throw new Error(cfg.error
|
|
2925
|
+
if (!cfg.ok) throw new Error(cfg.error);
|
|
2763
2926
|
const runtime = parseConfigJs(cfg.text);
|
|
2764
2927
|
const secrets = findSecretKeys(runtime);
|
|
2765
2928
|
if (secrets.length) add({ id: "config-js", status: "fail", message: `config.js publishes secrets: ${secrets.join(", ")}.`, fix: "Revoke those keys NOW, then redeploy the admin (config.js is regenerated without secrets)." });
|
|
@@ -2823,7 +2986,7 @@ function readPlan(source) {
|
|
|
2823
2986
|
try {
|
|
2824
2987
|
return JSON.parse(text);
|
|
2825
2988
|
} catch (err) {
|
|
2826
|
-
throw new CliError("invalid-plan", `The plan is not valid JSON: ${err.message}`,
|
|
2989
|
+
throw new CliError("invalid-plan", `The plan is not valid JSON: ${err.message}`, "Write the plan as one JSON object (`agentcrm options --json` documents every field).", 2);
|
|
2827
2990
|
}
|
|
2828
2991
|
}
|
|
2829
2992
|
const installCommand = {
|
|
@@ -2838,10 +3001,10 @@ const installCommand = {
|
|
|
2838
3001
|
const planPath = strOpt(options, "plan");
|
|
2839
3002
|
if (!planPath) usage("install needs --plan <file|->.", "Run `agentcrm options` for the format.");
|
|
2840
3003
|
installBrowserEnv();
|
|
2841
|
-
const { resolvePlan } = await import("./chunks/plan-
|
|
3004
|
+
const { resolvePlan } = await import("./chunks/plan-BmTat6-T.mjs");
|
|
2842
3005
|
const plan = resolvePlan(readPlan(planPath));
|
|
2843
3006
|
if (getSite(plan.alias)) throw new CliError("site-exists", `A site named "${plan.alias}" is already registered.`, "Use another alias, or `agentcrm site forget` it first.");
|
|
2844
|
-
const { checkPlan, runInstall } = await import("./chunks/install-
|
|
3007
|
+
const { checkPlan, runInstall } = await import("./chunks/install-BBTA4nMm.mjs");
|
|
2845
3008
|
io.err("Checking access…");
|
|
2846
3009
|
const checks = await checkPlan(plan);
|
|
2847
3010
|
const summary = {
|
|
@@ -2861,7 +3024,7 @@ const installCommand = {
|
|
|
2861
3024
|
throw new CliError(
|
|
2862
3025
|
"check-failed",
|
|
2863
3026
|
[!checks.data.ok && `Data: ${checks.data.detail}`, checks.hosting && !checks.hosting.ok && `Hosting: ${checks.hosting.detail}`].filter(Boolean).join(" — "),
|
|
2864
|
-
"Fix the access (key / token permissions, bucket / repo names, URL, CORS) and retry the dry run."
|
|
3027
|
+
checks.data.existing && !checks.data.ok ? `Reuse it: the user stores its passphrase (\`agentcrm credentials set ${plan.passphraseAccount ?? `passphrase:${plan.alias}`}\` in their own terminal, or --from-env), then add "encryption": { "credential": "${plan.passphraseAccount ?? `passphrase:${plan.alias}`}" } to the plan's data and retry the dry run. Or pick another folder / root for a new CRM.` : "Fix the access (key / token permissions, bucket / repo names, URL, CORS) and retry the dry run."
|
|
2865
3028
|
);
|
|
2866
3029
|
}
|
|
2867
3030
|
if (options["dry-run"] === true) {
|
|
@@ -2887,6 +3050,8 @@ const installCommand = {
|
|
|
2887
3050
|
});
|
|
2888
3051
|
const nextSteps = [];
|
|
2889
3052
|
if (plan.adminUrl) nextSteps.push(`Open the admin: ${plan.adminUrl}`);
|
|
3053
|
+
else if (localAdminFolder(plan.hosting)) nextSteps.push(`The admin is in ${localAdminFolder(plan.hosting)} (no public URL): open it with \`agentcrm serve --dir ${localAdminFolder(plan.hosting)}\`, or serve that folder over HTTPS.`);
|
|
3054
|
+
else if (plan.hosting) nextSteps.push(`The admin was uploaded to the ${plan.hosting.type} hosting; its public URL is unknown (add siteUrl to the plan's hosting settings next time).`);
|
|
2890
3055
|
else nextSteps.push("The admin was not uploaded: run `agentcrm serve` to use it on this computer.");
|
|
2891
3056
|
if (plan.backend === "journal") {
|
|
2892
3057
|
nextSteps.push(`Each new browser asks once for the data location keys${plan.passphraseAccount ? " and the passphrase" : ""} (they stay in that browser).`);
|
|
@@ -3136,9 +3301,10 @@ const versionCommand = {
|
|
|
3136
3301
|
}
|
|
3137
3302
|
};
|
|
3138
3303
|
async function siteStatus(site, version2, dataSchema) {
|
|
3139
|
-
const
|
|
3140
|
-
|
|
3141
|
-
const hosted = await
|
|
3304
|
+
const folder = site.adminUrl ? null : localAdminFolder(site.hosting);
|
|
3305
|
+
const base = { alias: site.alias, adminUrl: site.adminUrl, ...folder ? { adminFolder: folder } : {} };
|
|
3306
|
+
const hosted = await siteAdminVersion(site);
|
|
3307
|
+
if (hosted.source === "none") return { ...base, adminVersion: null, state: "not-hosted" };
|
|
3142
3308
|
if (!hosted.version) return { ...base, adminVersion: null, state: "unreachable", error: hosted.error };
|
|
3143
3309
|
if (hosted.version !== site.adminVersion) putSite({ ...site, adminVersion: hosted.version });
|
|
3144
3310
|
const cmp = compareVersions(hosted.version, version2);
|
|
@@ -3199,7 +3365,7 @@ const optionsCommand = {
|
|
|
3199
3365
|
printResult(io, { email: rulesEmail, rules, where: "Firebase console → Firestore Database → Rules → paste → Publish (or `firebase deploy --only firestore:rules`)." }, () => rules);
|
|
3200
3366
|
return;
|
|
3201
3367
|
}
|
|
3202
|
-
const { PLAN_EXAMPLE } = await import("./chunks/plan-
|
|
3368
|
+
const { PLAN_EXAMPLE } = await import("./chunks/plan-BmTat6-T.mjs");
|
|
3203
3369
|
const data = {
|
|
3204
3370
|
order: ["data backend", "data location", "admin hosting"],
|
|
3205
3371
|
backends: DATA_BACKENDS,
|
|
@@ -3272,13 +3438,13 @@ const objectsCommand = {
|
|
|
3272
3438
|
switch (action) {
|
|
3273
3439
|
case "list":
|
|
3274
3440
|
return withSession(io, options, {}, async ({ ctx, store }) => {
|
|
3275
|
-
const { listObjects } = await import("./chunks/schema-
|
|
3441
|
+
const { listObjects } = await import("./chunks/schema-C4lSslXv.mjs");
|
|
3276
3442
|
const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
|
|
3277
3443
|
return listObjects(ctx).map((o) => ({ id: o.id, labelSingular: o.labelSingular, labelPlural: o.labelPlural, standard: o.standard, labelField: o.labelField, fields: o.fields.length, records: recordsOf(store, o.id).length }));
|
|
3278
3444
|
}, (rows) => table(rows, ["id", "labelPlural", "fields", "records", "standard"]));
|
|
3279
3445
|
case "get": {
|
|
3280
3446
|
const objectId = positional(positionals, 1, "object id", "objects get <id>");
|
|
3281
|
-
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-
|
|
3447
|
+
return withSession(io, options, {}, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).describeSchema(ctx, objectId)[0]);
|
|
3282
3448
|
}
|
|
3283
3449
|
case "create": {
|
|
3284
3450
|
const data = jsonObjectOpt(options, "data") ?? {};
|
|
@@ -3291,7 +3457,7 @@ const objectsCommand = {
|
|
|
3291
3457
|
...strOpt(options, "color") ? { color: strOpt(options, "color") } : {}
|
|
3292
3458
|
};
|
|
3293
3459
|
if (!input.labelSingular || !input.labelPlural) usage("objects create needs --singular and --plural (or --data).");
|
|
3294
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-
|
|
3460
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createObject(ctx, input));
|
|
3295
3461
|
}
|
|
3296
3462
|
case "update": {
|
|
3297
3463
|
const objectId = positional(positionals, 1, "object id", "objects update <id> …");
|
|
@@ -3303,12 +3469,12 @@ const objectsCommand = {
|
|
|
3303
3469
|
...strOpt(options, "label-field") ? { labelField: strOpt(options, "label-field") } : {}
|
|
3304
3470
|
};
|
|
3305
3471
|
if (!Object.keys(patch).length) usage("objects update needs --singular, --plural, --icon, --color or --label-field.");
|
|
3306
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-
|
|
3472
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateObject(ctx, objectId, patch));
|
|
3307
3473
|
}
|
|
3308
3474
|
case "delete": {
|
|
3309
3475
|
if (!id) usage("objects delete needs an object id.");
|
|
3310
3476
|
return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
|
|
3311
|
-
const { getObject, deleteObject } = await import("./chunks/schema-
|
|
3477
|
+
const { getObject, deleteObject } = await import("./chunks/schema-C4lSslXv.mjs");
|
|
3312
3478
|
const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
|
|
3313
3479
|
const o = getObject(ctx, id);
|
|
3314
3480
|
const plan = { object: o.id, label: o.labelPlural, standard: o.standard, recordsDeleted: recordsOf(store, o.id, { includeDeleted: true }).length };
|
|
@@ -3366,7 +3532,7 @@ const fieldsCommand = {
|
|
|
3366
3532
|
...boolOpt(options, "unique") ? { unique: true } : {},
|
|
3367
3533
|
...strOpt(options, "description") ? { description: strOpt(options, "description") } : {}
|
|
3368
3534
|
};
|
|
3369
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-
|
|
3535
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).createField(ctx, objectId, input));
|
|
3370
3536
|
}
|
|
3371
3537
|
case "update": {
|
|
3372
3538
|
if (!fieldId) usage("fields update needs a field id.");
|
|
@@ -3383,12 +3549,12 @@ const fieldsCommand = {
|
|
|
3383
3549
|
...strOpt(options, "description") !== void 0 ? { description: strOpt(options, "description") } : {}
|
|
3384
3550
|
};
|
|
3385
3551
|
if (!Object.keys(patch).length) usage("fields update needs at least one change.");
|
|
3386
|
-
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-
|
|
3552
|
+
return withSession(io, options, { lock: true }, async ({ ctx }) => (await import("./chunks/schema-C4lSslXv.mjs")).updateField(ctx, objectId, fieldId, patch));
|
|
3387
3553
|
}
|
|
3388
3554
|
case "delete": {
|
|
3389
3555
|
if (!fieldId) usage("fields delete needs a field id.");
|
|
3390
3556
|
return withSession(io, options, { lock: options.yes === true }, async ({ ctx, store }) => {
|
|
3391
|
-
const { getObject, deleteField } = await import("./chunks/schema-
|
|
3557
|
+
const { getObject, deleteField } = await import("./chunks/schema-C4lSslXv.mjs");
|
|
3392
3558
|
const { recordsOf } = await import("./chunks/store-5gqRSKXg.mjs");
|
|
3393
3559
|
const field = getObject(ctx, objectId).fields.find((f) => f.id === fieldId);
|
|
3394
3560
|
if (!field) usage(`No field "${fieldId}" on ${objectId}.`, `\`agentcrm objects get ${objectId}\` lists them.`);
|
|
@@ -3695,7 +3861,7 @@ const toolsCommand = {
|
|
|
3695
3861
|
async run({ io, options, positionals }) {
|
|
3696
3862
|
const [action = "list"] = positionals;
|
|
3697
3863
|
if (action === "list") {
|
|
3698
|
-
const { listAiTools, AI_TOOLS } = await import("./chunks/tools-
|
|
3864
|
+
const { listAiTools, AI_TOOLS } = await import("./chunks/tools-iSH-yMpl.mjs");
|
|
3699
3865
|
const builtin = new Set(AI_TOOLS.map((t) => t.name));
|
|
3700
3866
|
const shape = () => listAiTools().map((t) => ({ name: t.name, description: t.description, mutating: t.mutating, source: builtin.has(t.name) ? "builtin" : "extension", inputSchema: t.inputSchema }));
|
|
3701
3867
|
const human = (r) => table(r.tools, ["name", "mutating", "source", "description"]);
|
|
@@ -3715,7 +3881,7 @@ const toolsCommand = {
|
|
|
3715
3881
|
}
|
|
3716
3882
|
if (action === "call") {
|
|
3717
3883
|
const name2 = positional(positionals, 1, "tool name", "tools call <name> --input <json>");
|
|
3718
|
-
const { findTool, callTool, listAiTools } = await import("./chunks/tools-
|
|
3884
|
+
const { findTool, callTool, listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
|
|
3719
3885
|
const input = jsonObjectOpt(options, "input") ?? {};
|
|
3720
3886
|
const builtin = findTool(name2);
|
|
3721
3887
|
return withSession(io, options, { lock: builtin ? builtin.mutating : true }, async ({ ctx }) => {
|
|
@@ -3737,8 +3903,8 @@ const mcpCommand = {
|
|
|
3737
3903
|
const log = (msg) => process.stderr.write(`${msg}
|
|
3738
3904
|
`);
|
|
3739
3905
|
for (const w of session2.warnings) log(`[agentcrm mcp] note: ${w}`);
|
|
3740
|
-
const { listAiTools } = await import("./chunks/tools-
|
|
3741
|
-
const { createMcpServer, serveStdio } = await import("./chunks/server-
|
|
3906
|
+
const { listAiTools } = await import("./chunks/tools-iSH-yMpl.mjs");
|
|
3907
|
+
const { createMcpServer, serveStdio } = await import("./chunks/server-DC3t8r30.mjs");
|
|
3742
3908
|
let lastRefresh = Date.now();
|
|
3743
3909
|
const server = createMcpServer({
|
|
3744
3910
|
readOnly,
|
|
@@ -3761,7 +3927,10 @@ const mcpCommand = {
|
|
|
3761
3927
|
async withWrite(fn) {
|
|
3762
3928
|
const unlock = lockSite(session2.site.alias);
|
|
3763
3929
|
try {
|
|
3764
|
-
|
|
3930
|
+
const result = await fn();
|
|
3931
|
+
const report = await session2.extensions.sync();
|
|
3932
|
+
for (const f of report?.failed ?? []) log(`[agentcrm mcp] extension ${f.id} failed to load: ${f.error}`);
|
|
3933
|
+
return result;
|
|
3765
3934
|
} finally {
|
|
3766
3935
|
unlock();
|
|
3767
3936
|
}
|
|
@@ -3866,7 +4035,11 @@ async function main(argv, io) {
|
|
|
3866
4035
|
const json = argv.includes("--json");
|
|
3867
4036
|
const out = createIo(json);
|
|
3868
4037
|
try {
|
|
3869
|
-
const
|
|
4038
|
+
const args = argv.filter((a) => a !== "--json");
|
|
4039
|
+
const leading = [];
|
|
4040
|
+
while (args[0] && LEADING_GLOBAL_FLAGS.has(args[0])) leading.push(args.shift());
|
|
4041
|
+
const [name2 = "help", ...rest] = args;
|
|
4042
|
+
rest.push(...leading);
|
|
3870
4043
|
if (name2 === "--help" || name2 === "-h") return await run("help", rest, out) ?? 0;
|
|
3871
4044
|
if (name2 === "--version" || name2 === "-v") return await run("version", rest, out) ?? 0;
|
|
3872
4045
|
return await run(name2, rest, out) ?? 0;
|
|
@@ -3874,6 +4047,7 @@ async function main(argv, io) {
|
|
|
3874
4047
|
return printError(out, error);
|
|
3875
4048
|
}
|
|
3876
4049
|
}
|
|
4050
|
+
const LEADING_GLOBAL_FLAGS = new Set(GLOBAL_OPTIONS.filter((o) => o.type === "boolean" && o.name !== "help").map((o) => `--${o.name}`));
|
|
3877
4051
|
async function run(name2, args, io) {
|
|
3878
4052
|
const command = findCommand(name2);
|
|
3879
4053
|
if (!command) {
|
|
@@ -3891,13 +4065,21 @@ async function run(name2, args, io) {
|
|
|
3891
4065
|
throw new CliError("usage", e.message, `Usage: agentcrm ${command.usage}`, 2);
|
|
3892
4066
|
}
|
|
3893
4067
|
if (parsed.values.help && command.name !== "help") return run("help", [command.name], io);
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
4068
|
+
try {
|
|
4069
|
+
return await command.run({
|
|
4070
|
+
io,
|
|
4071
|
+
options: parsed.values,
|
|
4072
|
+
positionals: parsed.positionals
|
|
4073
|
+
});
|
|
4074
|
+
} catch (e) {
|
|
4075
|
+
if (e instanceof CliError && !e.hint && e.exitCode === 2) {
|
|
4076
|
+
throw new CliError(e.code, e.message, `Usage: agentcrm ${command.usage} (details: \`agentcrm help ${command.name}\`).`, e.exitCode);
|
|
4077
|
+
}
|
|
4078
|
+
throw e;
|
|
4079
|
+
}
|
|
3899
4080
|
}
|
|
3900
4081
|
installBrowserEnv();
|
|
4082
|
+
routeConsoleToStderr();
|
|
3901
4083
|
main(process.argv.slice(2)).then(
|
|
3902
4084
|
(code) => process.exit(code),
|
|
3903
4085
|
(err) => {
|
|
@@ -3913,15 +4095,16 @@ export {
|
|
|
3913
4095
|
LOCATIONS as L,
|
|
3914
4096
|
PACKAGE_NAME as P,
|
|
3915
4097
|
USER_ROLES as U,
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
4098
|
+
FIELD_TYPES as a,
|
|
4099
|
+
parseAccount as b,
|
|
4100
|
+
requireCredential as c,
|
|
4101
|
+
findLocation as d,
|
|
4102
|
+
findHosting as e,
|
|
3921
4103
|
findBackend as f,
|
|
3922
4104
|
getCredential as g,
|
|
3923
4105
|
openStore as h,
|
|
3924
|
-
|
|
4106
|
+
installBrowserEnv as i,
|
|
4107
|
+
distRoot as j,
|
|
3925
4108
|
nodeImportBundle as n,
|
|
3926
4109
|
ownPackageRoot as o,
|
|
3927
4110
|
packageRoot as p,
|