@dench.com/cli 3.6.6-staging.386 → 3.8.0-staging.389
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 +43 -44
- package/agent-config.ts +21 -57
- package/app-sdk.d.ts +367 -24
- package/chat-spawn.ts +11 -2
- package/chat.ts +1 -1
- package/context-settings.ts +86 -0
- package/crm.ts +92 -24
- package/cron.ts +19 -0
- package/dench.ts +318 -90
- package/email-inbox.ts +169 -0
- package/email.ts +103 -21
- package/environments.ts +694 -0
- package/lib/api-schemas.ts +160 -14
- package/lib/app-sdk-catalog.generated.ts +39 -19
- package/lib/bundled-references.ts +17 -0
- package/lib/cli-args.ts +30 -3
- package/lib/command-args.ts +252 -0
- package/lib/command-help.ts +93 -0
- package/lib/command-registry.ts +296 -10
- package/lib/environment-archive.ts +255 -0
- package/lib/environment-cli-args.ts +96 -0
- package/lib/environment-contract.ts +237 -0
- package/lib/environment-execution.ts +769 -0
- package/lib/environment-source.ts +165 -0
- package/lib/load-dev-env.mjs +3 -1
- package/lib/load-dev-env.test.mjs +26 -9
- package/lib/native-email-wait.ts +75 -0
- package/lib/parent-argv.ts +20 -0
- package/lib/public-error.ts +82 -0
- package/lib/references.generated.json +74 -0
- package/package.json +7 -3
- package/reference.ts +47 -0
- package/workspace-secrets.ts +231 -0
- package/agent.ts +0 -37
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ConvexHttpClient } from "convex/browser";
|
|
2
|
+
import { makeFunctionReference } from "convex/server";
|
|
3
|
+
import { CliArgError } from "./lib/cli-args";
|
|
4
|
+
import { parseEnvironmentOptions } from "./lib/environment-cli-args";
|
|
5
|
+
|
|
6
|
+
export const CONTEXT_SECTIONS = [
|
|
7
|
+
"identity",
|
|
8
|
+
"organisation",
|
|
9
|
+
"user",
|
|
10
|
+
"tools",
|
|
11
|
+
"rules",
|
|
12
|
+
] as const;
|
|
13
|
+
export function validateContextSet(input: readonly string[]) {
|
|
14
|
+
const parsed = parseEnvironmentOptions(
|
|
15
|
+
input,
|
|
16
|
+
["--content"],
|
|
17
|
+
["--stdin", "--json"],
|
|
18
|
+
);
|
|
19
|
+
const [verb, section] = parsed.positional;
|
|
20
|
+
if (
|
|
21
|
+
verb !== "set" ||
|
|
22
|
+
parsed.positional.length !== 2 ||
|
|
23
|
+
!CONTEXT_SECTIONS.includes(section as never) ||
|
|
24
|
+
parsed.child.length
|
|
25
|
+
)
|
|
26
|
+
throw new CliArgError(
|
|
27
|
+
`Usage: dench context set <${CONTEXT_SECTIONS.join("|")}> --stdin | --content <text> [--json]`,
|
|
28
|
+
);
|
|
29
|
+
if (
|
|
30
|
+
Number(parsed.flags["--stdin"] === true) +
|
|
31
|
+
Number(typeof parsed.flags["--content"] === "string") !==
|
|
32
|
+
1
|
|
33
|
+
)
|
|
34
|
+
throw new CliArgError(
|
|
35
|
+
"Choose exactly one of --stdin or --content. Store credentials with dench secrets set, never in context.",
|
|
36
|
+
);
|
|
37
|
+
return {
|
|
38
|
+
section: section as (typeof CONTEXT_SECTIONS)[number],
|
|
39
|
+
content: parsed.flags["--content"] as string | undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runContextSet(opts: {
|
|
44
|
+
convex: ConvexHttpClient;
|
|
45
|
+
args: string[];
|
|
46
|
+
sessionToken?: string;
|
|
47
|
+
readStdin?: () => Promise<string>;
|
|
48
|
+
}) {
|
|
49
|
+
const parsed = validateContextSet(opts.args);
|
|
50
|
+
const content =
|
|
51
|
+
parsed.content ??
|
|
52
|
+
(await (
|
|
53
|
+
opts.readStdin ??
|
|
54
|
+
(async () => {
|
|
55
|
+
const chunks: Buffer[] = [];
|
|
56
|
+
let size = 0;
|
|
57
|
+
for await (const chunk of process.stdin) {
|
|
58
|
+
size += Buffer.byteLength(chunk);
|
|
59
|
+
if (size > 32000)
|
|
60
|
+
throw new CliArgError(
|
|
61
|
+
"Context exceeds 32 KB. Keep standing instructions short; use workspace files for references.",
|
|
62
|
+
);
|
|
63
|
+
chunks.push(Buffer.from(chunk));
|
|
64
|
+
}
|
|
65
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
66
|
+
})
|
|
67
|
+
)());
|
|
68
|
+
if (!content.trim() || Buffer.byteLength(content) > 32000)
|
|
69
|
+
throw new CliArgError("Context must contain 1–32000 UTF-8 bytes.");
|
|
70
|
+
const fn = {
|
|
71
|
+
identity: "updateIdentity",
|
|
72
|
+
organisation: "updateOrganisation",
|
|
73
|
+
user: "updateUserProfile",
|
|
74
|
+
tools: "updateToolsNotes",
|
|
75
|
+
rules: "writeMagicFileFromUI",
|
|
76
|
+
}[parsed.section];
|
|
77
|
+
await opts.convex.mutation(
|
|
78
|
+
makeFunctionReference<"mutation">(`functions/agentConfig:${fn}`),
|
|
79
|
+
{
|
|
80
|
+
...(opts.sessionToken ? { sessionToken: opts.sessionToken } : {}),
|
|
81
|
+
content,
|
|
82
|
+
...(parsed.section === "rules" ? { path: "/RULES.md" } : {}),
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
console.log(JSON.stringify({ ok: true, section: parsed.section }));
|
|
86
|
+
}
|
package/crm.ts
CHANGED
|
@@ -239,6 +239,7 @@ function normalizeRpcError(error: unknown): Error {
|
|
|
239
239
|
userFacingConvexErrorMessage(error) ??
|
|
240
240
|
(error instanceof Error ? error.message : String(error)),
|
|
241
241
|
),
|
|
242
|
+
{ cause: error },
|
|
242
243
|
);
|
|
243
244
|
}
|
|
244
245
|
|
|
@@ -246,7 +247,8 @@ export function shift(args: string[], expected: string): string {
|
|
|
246
247
|
try {
|
|
247
248
|
return shiftRaw(args, expected);
|
|
248
249
|
} catch (error) {
|
|
249
|
-
if (error instanceof CliArgError)
|
|
250
|
+
if (error instanceof CliArgError)
|
|
251
|
+
throw new CrmCliError(error.message, { cause: error });
|
|
250
252
|
throw error;
|
|
251
253
|
}
|
|
252
254
|
}
|
|
@@ -290,7 +292,8 @@ function resolveLimitFlag(
|
|
|
290
292
|
try {
|
|
291
293
|
parsed = parseLimitRaw(raw, opts);
|
|
292
294
|
} catch (error) {
|
|
293
|
-
if (error instanceof CliArgError)
|
|
295
|
+
if (error instanceof CliArgError)
|
|
296
|
+
throw new CrmCliError(error.message, { cause: error });
|
|
294
297
|
throw error;
|
|
295
298
|
}
|
|
296
299
|
if (parsed.clamped) {
|
|
@@ -365,7 +368,8 @@ function parseJson(value: string | undefined): unknown {
|
|
|
365
368
|
try {
|
|
366
369
|
return parseJsonRaw(value);
|
|
367
370
|
} catch (error) {
|
|
368
|
-
if (error instanceof CliArgError)
|
|
371
|
+
if (error instanceof CliArgError)
|
|
372
|
+
throw new CrmCliError(error.message, { cause: error });
|
|
369
373
|
throw error;
|
|
370
374
|
}
|
|
371
375
|
}
|
|
@@ -377,7 +381,8 @@ export function assertNoUnknownFlags(
|
|
|
377
381
|
try {
|
|
378
382
|
assertNoUnknownFlagsRaw(args, command);
|
|
379
383
|
} catch (error) {
|
|
380
|
-
if (error instanceof CliArgError)
|
|
384
|
+
if (error instanceof CliArgError)
|
|
385
|
+
throw new CrmCliError(error.message, { cause: error });
|
|
381
386
|
throw error;
|
|
382
387
|
}
|
|
383
388
|
}
|
|
@@ -780,11 +785,23 @@ export async function callQuery(
|
|
|
780
785
|
}
|
|
781
786
|
}
|
|
782
787
|
|
|
788
|
+
/** Every parsed token must be accounted for before an external effect. */
|
|
789
|
+
function assertWriteArguments(ctx: CrmCliContext): void {
|
|
790
|
+
assertNoUnknownFlags(ctx.args, "crm");
|
|
791
|
+
if (ctx.args.length) {
|
|
792
|
+
const error = new CliArgError(
|
|
793
|
+
`Unexpected argument: ${ctx.args[0]}. Run the command with --help for usage.`,
|
|
794
|
+
);
|
|
795
|
+
throw new CrmCliError(error.message, { cause: error });
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
783
799
|
export async function callMutation(
|
|
784
800
|
ctx: CrmCliContext,
|
|
785
801
|
fn: Parameters<ConvexHttpClient["mutation"]>[0],
|
|
786
802
|
args: Record<string, unknown> = {},
|
|
787
803
|
): Promise<unknown> {
|
|
804
|
+
assertWriteArguments(ctx);
|
|
788
805
|
try {
|
|
789
806
|
return await ctx.convex.mutation(fn, {
|
|
790
807
|
...args,
|
|
@@ -800,6 +817,7 @@ export async function callAction(
|
|
|
800
817
|
fn: Parameters<ConvexHttpClient["action"]>[0],
|
|
801
818
|
args: Record<string, unknown> = {},
|
|
802
819
|
): Promise<unknown> {
|
|
820
|
+
assertWriteArguments(ctx);
|
|
803
821
|
try {
|
|
804
822
|
return await ctx.convex.action(fn, {
|
|
805
823
|
...args,
|
|
@@ -914,6 +932,9 @@ const api = {
|
|
|
914
932
|
},
|
|
915
933
|
objects: {
|
|
916
934
|
list: makeFunctionReference<"query">("functions/crm/objects:list"),
|
|
935
|
+
listCompact: makeFunctionReference<"query">(
|
|
936
|
+
"functions/crm/objects:listCompact",
|
|
937
|
+
),
|
|
917
938
|
get: makeFunctionReference<"query">("functions/crm/objects:get"),
|
|
918
939
|
materializeView: makeFunctionReference<"action">(
|
|
919
940
|
"functions/crm/objects:completeView",
|
|
@@ -963,6 +984,9 @@ const api = {
|
|
|
963
984
|
},
|
|
964
985
|
fields: {
|
|
965
986
|
list: makeFunctionReference<"query">("functions/crm/fields:list"),
|
|
987
|
+
listCompact: makeFunctionReference<"query">(
|
|
988
|
+
"functions/crm/fields:listCompact",
|
|
989
|
+
),
|
|
966
990
|
create: makeFunctionReference<"mutation">("functions/crm/fields:create"),
|
|
967
991
|
update: makeFunctionReference<"mutation">("functions/crm/fields:update"),
|
|
968
992
|
remove: makeFunctionReference<"mutation">("functions/crm/fields:remove"),
|
|
@@ -1013,6 +1037,9 @@ const api = {
|
|
|
1013
1037
|
),
|
|
1014
1038
|
aggregate: makeFunctionReference<"query">("functions/crm/query:aggregate"),
|
|
1015
1039
|
search: makeFunctionReference<"query">("functions/crm/query:search"),
|
|
1040
|
+
searchPage: makeFunctionReference<"query">(
|
|
1041
|
+
"functions/crm/query:searchPage",
|
|
1042
|
+
),
|
|
1016
1043
|
},
|
|
1017
1044
|
statuses: {
|
|
1018
1045
|
list: makeFunctionReference<"query">("functions/crm/statuses:list"),
|
|
@@ -1459,8 +1486,16 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
1459
1486
|
const verb = ctx.args.shift();
|
|
1460
1487
|
switch (verb) {
|
|
1461
1488
|
case "list": {
|
|
1489
|
+
const compact = hasFlag(ctx.args, "--compact");
|
|
1462
1490
|
assertNoUnknownFlags(ctx.args, "crm objects list");
|
|
1463
|
-
out(
|
|
1491
|
+
out(
|
|
1492
|
+
ctx,
|
|
1493
|
+
await callQuery(
|
|
1494
|
+
ctx,
|
|
1495
|
+
compact ? api.objects.listCompact : api.objects.list,
|
|
1496
|
+
{},
|
|
1497
|
+
),
|
|
1498
|
+
);
|
|
1464
1499
|
return;
|
|
1465
1500
|
}
|
|
1466
1501
|
case "get": {
|
|
@@ -2159,8 +2194,16 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2159
2194
|
switch (verb) {
|
|
2160
2195
|
case "list": {
|
|
2161
2196
|
const objectName = shift(ctx.args, "object name");
|
|
2197
|
+
const compact = hasFlag(ctx.args, "--compact");
|
|
2162
2198
|
assertNoUnknownFlags(ctx.args, "crm fields list");
|
|
2163
|
-
out(
|
|
2199
|
+
out(
|
|
2200
|
+
ctx,
|
|
2201
|
+
await callQuery(
|
|
2202
|
+
ctx,
|
|
2203
|
+
compact ? api.fields.listCompact : api.fields.list,
|
|
2204
|
+
{ objectName },
|
|
2205
|
+
),
|
|
2206
|
+
);
|
|
2164
2207
|
return;
|
|
2165
2208
|
}
|
|
2166
2209
|
case "create": {
|
|
@@ -2294,7 +2337,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2294
2337
|
// Assert before consuming the rest as positional field names so a
|
|
2295
2338
|
// typo'd flag fails loudly rather than getting silently filtered.
|
|
2296
2339
|
assertNoUnknownFlags(ctx.args, "crm fields reorder");
|
|
2297
|
-
const orderedFieldNames =
|
|
2340
|
+
const orderedFieldNames = ctx.args.splice(0);
|
|
2298
2341
|
out(
|
|
2299
2342
|
ctx,
|
|
2300
2343
|
await callMutation(ctx, api.fields.reorder, {
|
|
@@ -2311,7 +2354,7 @@ async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2311
2354
|
// there are no stray flags so a typo'd `--limit` fails loud
|
|
2312
2355
|
// instead of being silently consumed as an enum value.
|
|
2313
2356
|
assertNoUnknownFlags(ctx.args, "crm fields enum-reorder");
|
|
2314
|
-
const orderedValues =
|
|
2357
|
+
const orderedValues = ctx.args.splice(0);
|
|
2315
2358
|
if (orderedValues.length === 0) {
|
|
2316
2359
|
throw new CrmCliError(
|
|
2317
2360
|
"dench crm fields enum-reorder requires at least one value. " +
|
|
@@ -2417,6 +2460,15 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2417
2460
|
const objectName = shift(ctx.args, "object name");
|
|
2418
2461
|
const withMeta = hasFlag(ctx.args, "--with-meta");
|
|
2419
2462
|
const cursorSpecified = ctx.args.includes("--cursor");
|
|
2463
|
+
const cursorValue = ctx.args[ctx.args.indexOf("--cursor") + 1];
|
|
2464
|
+
if (
|
|
2465
|
+
cursorSpecified &&
|
|
2466
|
+
(!cursorValue?.trim() || cursorValue.startsWith("--"))
|
|
2467
|
+
)
|
|
2468
|
+
throw new CrmCliError(
|
|
2469
|
+
"--cursor requires a non-empty continuation cursor.",
|
|
2470
|
+
{ cause: new CliArgError("Invalid --cursor") },
|
|
2471
|
+
);
|
|
2420
2472
|
const cursor = getFlag(ctx.args, "--cursor");
|
|
2421
2473
|
if (cursorSpecified && (!cursor?.trim() || cursor.startsWith("--")))
|
|
2422
2474
|
throw new CrmCliError(
|
|
@@ -2483,8 +2535,7 @@ async function runEntriesCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
2483
2535
|
{ entryCount?: number; visibility?: string } | null,
|
|
2484
2536
|
];
|
|
2485
2537
|
const entries = Array.isArray(result?.page) ? result.page : [];
|
|
2486
|
-
const isDone =
|
|
2487
|
-
typeof result?.isDone === "boolean" ? result.isDone : null;
|
|
2538
|
+
const isDone = typeof result?.isDone === "boolean" ? result.isDone : null;
|
|
2488
2539
|
const truncated = result?.truncated === true;
|
|
2489
2540
|
const continueCursor =
|
|
2490
2541
|
isDone === false ? result?.continueCursor || null : null;
|
|
@@ -3234,25 +3285,31 @@ async function runAggregateCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3234
3285
|
|
|
3235
3286
|
async function runSearchCommand(ctx: CrmCliContext): Promise<void> {
|
|
3236
3287
|
const objectName = getFlag(ctx.args, "--object");
|
|
3237
|
-
|
|
3238
|
-
|
|
3288
|
+
const selectRaw = getFlag(ctx.args, "--select");
|
|
3289
|
+
const select = selectRaw?.split(",").map((name) => name.trim());
|
|
3290
|
+
if (select && (select.length > 30 || select.some((name) => !name))) {
|
|
3291
|
+
throw new CrmCliError(
|
|
3292
|
+
"--select requires between 1 and 30 nonempty field names.",
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
const page = hasFlag(ctx.args, "--page");
|
|
3296
|
+
const cursor = getFlag(ctx.args, "--cursor");
|
|
3297
|
+
const projected = select !== undefined || page || cursor !== undefined;
|
|
3239
3298
|
const limit = resolveLimitFlag(ctx.args, {
|
|
3240
|
-
defaultValue: 50,
|
|
3299
|
+
defaultValue: projected ? 20 : 50,
|
|
3241
3300
|
cap: 100,
|
|
3242
3301
|
command: "crm search",
|
|
3243
3302
|
});
|
|
3244
|
-
// Drain known flags before joining the rest into the search text so a
|
|
3245
|
-
// typo like `--objct` surfaces instead of being concatenated into the
|
|
3246
|
-
// query string.
|
|
3247
3303
|
assertNoUnknownFlags(ctx.args, "crm search");
|
|
3248
3304
|
const text = ctx.args.join(" ").trim();
|
|
3249
3305
|
if (!text) throw new CrmCliError("Search text required");
|
|
3250
3306
|
out(
|
|
3251
3307
|
ctx,
|
|
3252
|
-
await callQuery(ctx, api.query.search, {
|
|
3308
|
+
await callQuery(ctx, projected ? api.query.searchPage : api.query.search, {
|
|
3253
3309
|
text,
|
|
3254
3310
|
objectName,
|
|
3255
3311
|
limit: limit.effective,
|
|
3312
|
+
...(projected ? { select, cursor } : {}),
|
|
3256
3313
|
}),
|
|
3257
3314
|
);
|
|
3258
3315
|
}
|
|
@@ -3382,7 +3439,8 @@ async function runActionsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
3382
3439
|
const objectName = shift(ctx.args, "object name");
|
|
3383
3440
|
const fieldId = shift(ctx.args, "field id");
|
|
3384
3441
|
const actionId = shift(ctx.args, "action id");
|
|
3385
|
-
|
|
3442
|
+
assertNoUnknownFlags(ctx.args, "crm actions run");
|
|
3443
|
+
const entryIds = ctx.args.splice(0);
|
|
3386
3444
|
if (entryIds.length === 0) {
|
|
3387
3445
|
throw new CrmCliError("Provide at least one entry id");
|
|
3388
3446
|
}
|
|
@@ -3922,7 +3980,7 @@ export function remapKeys(
|
|
|
3922
3980
|
// ── SQL escape hatch ─────────────────────────────────────────────────────
|
|
3923
3981
|
|
|
3924
3982
|
async function runSqlCommand(ctx: CrmCliContext): Promise<void> {
|
|
3925
|
-
const sql = ctx.args.join(" ").trim();
|
|
3983
|
+
const sql = ctx.args.splice(0).join(" ").trim();
|
|
3926
3984
|
if (!sql) {
|
|
3927
3985
|
throw new CrmCliError("dench crm sql '<SELECT ... FROM ...>'");
|
|
3928
3986
|
}
|
|
@@ -4411,6 +4469,7 @@ async function enrichCellThroughGateway(
|
|
|
4411
4469
|
ctx: CrmCliContext,
|
|
4412
4470
|
args: CellEnrichmentArgs,
|
|
4413
4471
|
): Promise<Record<string, unknown>> {
|
|
4472
|
+
assertWriteArguments(ctx);
|
|
4414
4473
|
const target = await resolveEnrichmentTarget(ctx, args);
|
|
4415
4474
|
const entry = asEntry(
|
|
4416
4475
|
await callQuery(ctx, api.entries.get, {
|
|
@@ -4434,6 +4493,7 @@ async function enrichObjectThroughGateway(
|
|
|
4434
4493
|
ctx: CrmCliContext,
|
|
4435
4494
|
args: ObjectEnrichmentArgs,
|
|
4436
4495
|
): Promise<Record<string, unknown>> {
|
|
4496
|
+
assertWriteArguments(ctx);
|
|
4437
4497
|
const target = await resolveEnrichmentTarget(ctx, args);
|
|
4438
4498
|
const entries = asEntries(
|
|
4439
4499
|
await callQuery(ctx, api.entries.list, {
|
|
@@ -4615,6 +4675,7 @@ async function runEnrichCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
4615
4675
|
const lookups = input.map((value, index) =>
|
|
4616
4676
|
requireNonEmptyJsonRecord(value, `lookup ${index + 1}`),
|
|
4617
4677
|
);
|
|
4678
|
+
assertWriteArguments(ctx);
|
|
4618
4679
|
out(ctx, await enrichProfiles({ kind, lookups }, ctx.enrichmentGateway));
|
|
4619
4680
|
return;
|
|
4620
4681
|
}
|
|
@@ -4720,10 +4781,14 @@ async function runReportsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
4720
4781
|
}
|
|
4721
4782
|
|
|
4722
4783
|
function crmHelp(): void {
|
|
4723
|
-
console.log(
|
|
4784
|
+
console.log(crmHelpText());
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
export function crmHelpText(): string {
|
|
4788
|
+
return `Usage: dench crm <subcommand>
|
|
4724
4789
|
|
|
4725
4790
|
Objects (CRM tables):
|
|
4726
|
-
dench crm objects list [--json]
|
|
4791
|
+
dench crm objects list [--json] [--compact]
|
|
4727
4792
|
dench crm objects get <name>
|
|
4728
4793
|
dench crm objects create <name> [--description ...] [--default-view kanban] [--icon ...]
|
|
4729
4794
|
dench crm objects update <name> [--default-view ...] [--icon ...] [--display-name "Clients"] [--display-name-singular "Client"] [--visibility workspace|owner_only|owner_plus_members]
|
|
@@ -4810,7 +4875,7 @@ Sidebar (progressive disclosure — Library + home Pinned strip; layout is WORKS
|
|
|
4810
4875
|
shared shortcut, not a Library move; open to any member.
|
|
4811
4876
|
|
|
4812
4877
|
Fields (columns):
|
|
4813
|
-
dench crm fields list <object> [--json]
|
|
4878
|
+
dench crm fields list <object> [--compact] [--json]
|
|
4814
4879
|
dench crm fields create <object> <name> --type text|email|phone|url|number|boolean|date|enum|relation|user|tags|action|file|richtext \\
|
|
4815
4880
|
[--required] [--description ...] [--enum-values JSON] [--enum-colors JSON] \\
|
|
4816
4881
|
[--enum-multiple] [--related-object <name>] [--relationship many_to_one|many_to_many] \\
|
|
@@ -5031,7 +5096,10 @@ Aggregates:
|
|
|
5031
5096
|
dench crm aggregate <object> --field Status [--sum Amount]
|
|
5032
5097
|
|
|
5033
5098
|
Full-text search:
|
|
5034
|
-
dench crm search [--object <name>] [--limit N] <text>
|
|
5099
|
+
dench crm search [--object <name>] [--select "Name,Email"] [--limit N] [--page] [--cursor <cursor>] <text>
|
|
5100
|
+
--select returns a compact page with stable IDs, selected cells, isDone
|
|
5101
|
+
and continueCursor. Follow cursors until isDone for complete results.
|
|
5102
|
+
Without --select/--page/--cursor the legacy full-row array is unchanged.
|
|
5035
5103
|
|
|
5036
5104
|
Statuses (kanban columns):
|
|
5037
5105
|
dench crm statuses list <object>
|
|
@@ -5215,5 +5283,5 @@ unambiguous, but prefer canonical names from 'crm objects list'.
|
|
|
5215
5283
|
|
|
5216
5284
|
Global flags:
|
|
5217
5285
|
--json Output raw JSON instead of pretty-printed text.
|
|
5218
|
-
|
|
5286
|
+
`;
|
|
5219
5287
|
}
|
package/cron.ts
CHANGED
|
@@ -392,6 +392,9 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
|
|
|
392
392
|
"--prompt is required (becomes the user message of each cron-fired chat thread)",
|
|
393
393
|
);
|
|
394
394
|
}
|
|
395
|
+
const environment = getFlag(ctx.args, "--environment");
|
|
396
|
+
if (environment && !/^[a-z][a-z0-9-]{0,63}$/.test(environment))
|
|
397
|
+
throw new CronCliError("Invalid environment name");
|
|
395
398
|
const description = getFlag(ctx.args, "--description");
|
|
396
399
|
const overlapPolicy = getFlag(ctx.args, "--overlap") as
|
|
397
400
|
| "skip"
|
|
@@ -431,6 +434,7 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
|
|
|
431
434
|
triggerType,
|
|
432
435
|
name,
|
|
433
436
|
prompt,
|
|
437
|
+
environment,
|
|
434
438
|
description,
|
|
435
439
|
overlapPolicy,
|
|
436
440
|
disabled,
|
|
@@ -462,6 +466,7 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
|
|
|
462
466
|
const created = (await callMutation(ctx, createCronTriggerRef, {
|
|
463
467
|
name,
|
|
464
468
|
prompt,
|
|
469
|
+
environment,
|
|
465
470
|
triggerType,
|
|
466
471
|
schedule,
|
|
467
472
|
description,
|
|
@@ -551,6 +556,7 @@ async function runSecret(ctx: CronCliContext): Promise<void> {
|
|
|
551
556
|
}
|
|
552
557
|
|
|
553
558
|
export type AppOrCrmCreateBase = {
|
|
559
|
+
environment?: string;
|
|
554
560
|
triggerType: "composio" | "crm";
|
|
555
561
|
name: string;
|
|
556
562
|
prompt: string;
|
|
@@ -584,6 +590,7 @@ export function buildAppOrCrmCreateBody(
|
|
|
584
590
|
const body: Record<string, unknown> = {
|
|
585
591
|
name: base.name,
|
|
586
592
|
prompt: base.prompt,
|
|
593
|
+
environment: base.environment,
|
|
587
594
|
triggerType: base.triggerType,
|
|
588
595
|
description: base.description,
|
|
589
596
|
overlapPolicy: base.overlapPolicy,
|
|
@@ -731,6 +738,15 @@ async function runEvents(ctx: CronCliContext): Promise<void> {
|
|
|
731
738
|
|
|
732
739
|
async function runUpdate(ctx: CronCliContext): Promise<void> {
|
|
733
740
|
const jobId = shift(ctx.args, "job id");
|
|
741
|
+
const environment = getFlag(ctx.args, "--environment");
|
|
742
|
+
const clearEnvironment = hasFlag(ctx.args, "--clear-environment");
|
|
743
|
+
if (
|
|
744
|
+
environment &&
|
|
745
|
+
(clearEnvironment || !/^[a-z][a-z0-9-]{0,63}$/.test(environment))
|
|
746
|
+
)
|
|
747
|
+
throw new CronCliError(
|
|
748
|
+
"Choose a valid --environment name or --clear-environment",
|
|
749
|
+
);
|
|
734
750
|
const name = getFlag(ctx.args, "--name");
|
|
735
751
|
const prompt = getFlag(ctx.args, "--prompt");
|
|
736
752
|
const description = getFlag(ctx.args, "--description");
|
|
@@ -753,6 +769,7 @@ async function runUpdate(ctx: CronCliContext): Promise<void> {
|
|
|
753
769
|
const schedule = consumeScheduleFlags(ctx.args);
|
|
754
770
|
const result = await callMutation(ctx, updateCronTriggerRef, {
|
|
755
771
|
jobId,
|
|
772
|
+
environment: clearEnvironment ? null : environment,
|
|
756
773
|
name,
|
|
757
774
|
prompt,
|
|
758
775
|
description,
|
|
@@ -866,6 +883,8 @@ Browse:
|
|
|
866
883
|
dench cron events <toolkit> [--json] # app-event types for a connected app
|
|
867
884
|
|
|
868
885
|
Create / update:
|
|
886
|
+
Add --environment <name> to bind a ready reusable setup.
|
|
887
|
+
New runs use its latest ready revision; --clear-environment removes the binding on update.
|
|
869
888
|
dench cron create "Daily standup digest" \\
|
|
870
889
|
--prompt "Pull yesterday's commits + Slack #standup msgs and post a summary…" \\
|
|
871
890
|
--type schedule # default. also: webhook | app | crm (see below)
|