@dench.com/cli 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -158,10 +158,10 @@ Do not claim the top-up succeeded until Stripe returns success or
158
158
  List connected apps:
159
159
 
160
160
  ```bash
161
- dench apps --json
161
+ dench integrations --json
162
162
  ```
163
163
 
164
- `dench apps` is an alias for `dench tool status` with no toolkit.
164
+ `dench integrations` is an alias for `dench tool status` with no toolkit.
165
165
 
166
166
  Other tool commands:
167
167
 
@@ -192,7 +192,7 @@ Do not repeat OTP codes, security codes, passwords, tokens, API keys, or secrets
192
192
  from email bodies. Non-JSON `tool run` display redacts likely sensitive codes;
193
193
  `--json` preserves the raw provider response for cases that need it.
194
194
 
195
- Run `dench tool --help` or `dench apps --help` for command help.
195
+ Run `dench tool --help` or `dench integrations --help` for command help.
196
196
 
197
197
  For staging:
198
198
 
package/crm.ts CHANGED
@@ -437,6 +437,21 @@ async function callMutation(
437
437
  }
438
438
  }
439
439
 
440
+ async function callAction(
441
+ ctx: CrmCliContext,
442
+ fn: Parameters<ConvexHttpClient["action"]>[0],
443
+ args: Record<string, unknown> = {},
444
+ ): Promise<unknown> {
445
+ try {
446
+ return await ctx.convex.action(fn, {
447
+ ...args,
448
+ ...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
449
+ } as never);
450
+ } catch (error) {
451
+ throw normalizeRpcError(error);
452
+ }
453
+ }
454
+
440
455
  async function suppressForwardedConvexLogs<T>(
441
456
  fn: () => Promise<T>,
442
457
  ): Promise<T> {
@@ -613,9 +628,16 @@ const api = {
613
628
  },
614
629
  actions: {
615
630
  list: makeFunctionReference<"query">("functions/crm/actions:list"),
631
+ run: makeFunctionReference<"action">("functions/crm/actions:run"),
616
632
  startActionRun: makeFunctionReference<"mutation">(
617
633
  "functions/crm/actions:startActionRun",
618
634
  ),
635
+ define: makeFunctionReference<"mutation">(
636
+ "functions/crm/actions:defineAction",
637
+ ),
638
+ remove: makeFunctionReference<"mutation">(
639
+ "functions/crm/actions:deleteAction",
640
+ ),
619
641
  },
620
642
  members: {
621
643
  list: makeFunctionReference<"query">("functions/crm/members:list"),
@@ -770,10 +792,7 @@ async function runTemplatesCommand(ctx: CrmCliContext): Promise<void> {
770
792
  case "install": {
771
793
  const templateId = shift(ctx.args, "template id");
772
794
  assertNoUnknownFlags(ctx.args, "crm templates install");
773
- out(
774
- ctx,
775
- await callMutation(ctx, api.templates.install, { templateId }),
776
- );
795
+ out(ctx, await callMutation(ctx, api.templates.install, { templateId }));
777
796
  return;
778
797
  }
779
798
  case "remove-sample-data": {
@@ -1493,7 +1512,9 @@ async function runHistoryCommand(ctx: CrmCliContext): Promise<void> {
1493
1512
  entryId,
1494
1513
  includeSystem,
1495
1514
  paginationOpts: {
1496
- numItems: Number.isFinite(limit) ? Math.min(Math.max(limit, 1), 100) : 50,
1515
+ numItems: Number.isFinite(limit)
1516
+ ? Math.min(Math.max(limit, 1), 100)
1517
+ : 50,
1497
1518
  cursor: cursor ?? null,
1498
1519
  },
1499
1520
  }),
@@ -1771,9 +1792,88 @@ async function runActionsCommand(ctx: CrmCliContext): Promise<void> {
1771
1792
  return;
1772
1793
  }
1773
1794
  case "run": {
1774
- throw new CrmCliError(
1775
- "dench crm actions run has been removed. Start a normal agent with `dench agent new --follow` and ask it to perform the CRM action.",
1795
+ const objectName = shift(ctx.args, "object name");
1796
+ const fieldId = shift(ctx.args, "field id");
1797
+ const actionId = shift(ctx.args, "action id");
1798
+ const entryIds = ctx.args.filter((a) => !a.startsWith("--"));
1799
+ if (entryIds.length === 0) {
1800
+ throw new CrmCliError("Provide at least one entry id");
1801
+ }
1802
+ out(
1803
+ ctx,
1804
+ await callAction(ctx, api.actions.run, {
1805
+ objectName,
1806
+ fieldId: fieldId as any,
1807
+ actionId,
1808
+ entryIds: entryIds as any,
1809
+ }),
1776
1810
  );
1811
+ return;
1812
+ }
1813
+ case "create":
1814
+ case "edit": {
1815
+ const objectName = shift(ctx.args, "object name");
1816
+ const fieldName = shift(ctx.args, "field name");
1817
+ const actionId =
1818
+ verb === "edit"
1819
+ ? shift(ctx.args, "action id")
1820
+ : getFlag(ctx.args, "--id");
1821
+ const label = getFlag(ctx.args, "--label");
1822
+ const recipeRaw = getFlag(ctx.args, "--recipe");
1823
+ const script = getFlag(ctx.args, "--script");
1824
+ const scriptPath = getFlag(ctx.args, "--script-path");
1825
+ const url = getFlag(ctx.args, "--url");
1826
+ const variant = getFlag(ctx.args, "--variant");
1827
+ const icon = getFlag(ctx.args, "--icon");
1828
+ const confirmMessage = getFlag(ctx.args, "--confirm");
1829
+ assertNoUnknownFlags(ctx.args, `crm actions ${verb}`);
1830
+ if (verb === "create" && !label) {
1831
+ throw new CrmCliError("dench crm actions create requires --label");
1832
+ }
1833
+ const recipe = recipeRaw
1834
+ ? (parseJson(recipeRaw) as unknown[] | undefined)
1835
+ : undefined;
1836
+ out(
1837
+ ctx,
1838
+ await callMutation(ctx, api.actions.define, {
1839
+ objectName,
1840
+ fieldName,
1841
+ ...(actionId ? { actionId } : {}),
1842
+ ...(label ? { label } : {}),
1843
+ ...(recipe ? { recipe } : {}),
1844
+ ...(script ? { script } : {}),
1845
+ ...(scriptPath ? { scriptPath } : {}),
1846
+ ...(url ? { url } : {}),
1847
+ ...(variant ? { variant } : {}),
1848
+ ...(icon ? { icon } : {}),
1849
+ ...(confirmMessage ? { confirmMessage } : {}),
1850
+ }),
1851
+ );
1852
+ return;
1853
+ }
1854
+ case "delete": {
1855
+ const objectName = shift(ctx.args, "object name");
1856
+ const actionId = shift(ctx.args, "action id");
1857
+ const fieldName = getFlag(ctx.args, "--field");
1858
+ const fieldId = getFlag(ctx.args, "--field-id");
1859
+ const deleteFieldIfEmpty = hasFlag(ctx.args, "--delete-field-if-empty");
1860
+ assertNoUnknownFlags(ctx.args, "crm actions delete");
1861
+ if (!fieldName && !fieldId) {
1862
+ throw new CrmCliError(
1863
+ "dench crm actions delete needs --field <name> or --field-id <id>",
1864
+ );
1865
+ }
1866
+ out(
1867
+ ctx,
1868
+ await callMutation(ctx, api.actions.remove, {
1869
+ objectName,
1870
+ actionId,
1871
+ ...(fieldName ? { fieldName } : {}),
1872
+ ...(fieldId ? { fieldId: fieldId as never } : {}),
1873
+ ...(deleteFieldIfEmpty ? { deleteFieldIfEmpty: true } : {}),
1874
+ }),
1875
+ );
1876
+ return;
1777
1877
  }
1778
1878
  default:
1779
1879
  throw new CrmCliError(`Unknown crm actions verb: ${verb ?? "<none>"}`);
@@ -3048,9 +3148,30 @@ Automations (deterministic rules, fire inside entry writes):
3048
3148
  dench crm automations enable <automationId>
3049
3149
  dench crm automations disable <automationId>
3050
3150
 
3051
- Actions:
3151
+ Actions (buttons on an \`action\`-typed field; author + run from CLI/API/agent):
3052
3152
  dench crm actions list <entryId>
3053
- dench crm actions run <actionId> --field-id <id> --entry-id <id>
3153
+ dench crm actions run <object> <fieldId> <actionId> <entryId> [<entryId> ...]
3154
+ dench crm actions create <object> <fieldName> --label "Mark Won" \\
3155
+ [--recipe '<json steps>'] [--script '<js>'] [--script-path <path>] \\
3156
+ [--url '<link>'] \\
3157
+ [--variant primary|destructive|success|warning] [--icon <name>] \\
3158
+ [--confirm "Are you sure?"] [--id <actionId>]
3159
+ <fieldName> is the column header — give it a clear custom name (e.g.
3160
+ "Quick actions", "Outreach"), not just "Actions". One column can hold
3161
+ several buttons: reuse the same <fieldName> to add more.
3162
+ Creates the \`action\` field if missing. A button is one kind:
3163
+ --recipe (Tier-A, runs inline), --script/--script-path (Tier-B, spawns a
3164
+ Dench agent), or --url (a link — opens a workspace deep link or external
3165
+ URL). --recipe is a JSON array of steps, e.g.
3166
+ '[{"op":"update_entry","assignments":[{"field":"Status","value":{"from":"static","value":"Won"}}]}]'
3167
+ --url supports {id}, {object}, and {Field Name} tokens, e.g.
3168
+ --url '/?path=deal&entry=deal:{id}' (open this record) or
3169
+ --url 'https://app.stripe.com/customers/{Stripe ID}' (external). See
3170
+ references/NAVIGATION.md for internal deep-link paths.
3171
+ Pass --url WITH --recipe for a "run-then-open" dynamic link: the recipe
3172
+ runs first, then the URL is resolved against the updated row and opened.
3173
+ dench crm actions edit <object> <fieldName> <actionId> [--label ...] [--recipe ...] [...same flags]
3174
+ dench crm actions delete <object> <actionId> --field <fieldName> | --field-id <id> [--delete-field-if-empty]
3054
3175
 
3055
3176
  Object names: commands take the canonical object name (singular lowercase,
3056
3177
  e.g. people, company, prospect). Display labels shown in the UI (a template
package/dench.ts CHANGED
@@ -795,11 +795,11 @@ Notes:
795
795
  `);
796
796
  }
797
797
 
798
- function appsHelp() {
799
- console.log(`Dench apps
798
+ function integrationsHelp() {
799
+ console.log(`Dench integrations
800
800
 
801
801
  Usage:
802
- dench apps [--json]
802
+ dench integrations [--json]
803
803
 
804
804
  Lists all external apps connected to this organization (Slack, Gmail,
805
805
  GitHub, etc.). Authenticates with the active \`dench signin\` agent
@@ -3759,8 +3759,11 @@ async function main() {
3759
3759
  return;
3760
3760
  }
3761
3761
 
3762
- if (command === "apps" && (subcommand === "help" || hasFlag("--help"))) {
3763
- appsHelp();
3762
+ if (
3763
+ command === "integrations" &&
3764
+ (subcommand === "help" || hasFlag("--help"))
3765
+ ) {
3766
+ integrationsHelp();
3764
3767
  return;
3765
3768
  }
3766
3769
 
@@ -4046,9 +4049,14 @@ async function main() {
4046
4049
  return;
4047
4050
  }
4048
4051
 
4049
- if (command === "apps") {
4052
+ // `dench integrations` — Composio connected apps (alias for `dench tool
4053
+ // status`). Composio tool search/run/connect stay under `dench tool`.
4054
+ if (command === "integrations") {
4050
4055
  const runtime = await getRuntime();
4051
- const gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench apps");
4056
+ const gatewayAuth = await resolveGatewayCommandAuth(
4057
+ runtime,
4058
+ "dench integrations",
4059
+ );
4052
4060
  await runToolCommand({
4053
4061
  args: ["status"],
4054
4062
  jsonOutput: json,
@@ -4059,6 +4067,33 @@ async function main() {
4059
4067
  return;
4060
4068
  }
4061
4069
 
4070
+ // `dench apps` — the App Store (create / install / publish / list …).
4071
+ if (command === "apps") {
4072
+ const { runAppsCommand } = await import("./apps");
4073
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf("apps") + 1));
4074
+ if (!subArgs[0] || subArgs[0] === "help" || subArgs[0] === "--help") {
4075
+ // biome-ignore lint/suspicious/noExplicitAny: help-only stub, no client needed
4076
+ await runAppsCommand({ convex: {} as any, args: subArgs });
4077
+ return;
4078
+ }
4079
+ const runtime = await getRuntime();
4080
+ if (runtime.mode === "dev") {
4081
+ await runAppsCommand({
4082
+ convex: runtime.client,
4083
+ args: subArgs,
4084
+ sessionToken: encodeDevCrmSessionToken(runtime.workspaceArgs),
4085
+ });
4086
+ return;
4087
+ }
4088
+ const authedRuntime = requireAuthenticatedRuntime(runtime, "dench apps");
4089
+ await runAppsCommand({
4090
+ convex: authedRuntime.client,
4091
+ args: subArgs,
4092
+ sessionToken: authedRuntime.sessionToken,
4093
+ });
4094
+ return;
4095
+ }
4096
+
4062
4097
  if (command === "tool") {
4063
4098
  const subArgs = stripRuntimeFlags(args.slice(args.indexOf("tool") + 1));
4064
4099
  const filteredSubArgs = subArgs.filter((a) => a !== "--json");
package/fs-daemon.ts CHANGED
@@ -15,8 +15,10 @@
15
15
  * `lastSyncedHash`, we download from Convex Storage to /workspace.
16
16
  * • Per-path 300ms trailing debounce on uploads (batches tight write
17
17
  * loops like editor saves).
18
- * • Optimistic conflict via `expectedPreviousHash`. On 409, we fetch
19
- * latest, write to `<path>.conflicted-<ts>`, surface in UI.
18
+ * • Optimistic conflict via `expectedPreviousHash`. On 409, we stash the
19
+ * local copy at the next free `<name> (N).<ext>` sibling (reusing an
20
+ * existing sibling that already holds the same bytes), then re-pull the
21
+ * latest canonical version. Surfaced in UI.
20
22
  * • Per-path circuit breaker: if a path syncs >5 times in 10s, pause
21
23
  * sync on that path and log loudly to stderr (the live state of
22
24
  * active breakers is exposed via `dench fs status`).
@@ -58,7 +60,7 @@ import {
58
60
  unlink,
59
61
  writeFile,
60
62
  } from "node:fs/promises";
61
- import { dirname, relative } from "node:path";
63
+ import { basename, dirname, extname, join, relative } from "node:path";
62
64
  import { ConvexClient, ConvexHttpClient } from "convex/browser";
63
65
  import { makeFunctionReference } from "convex/server";
64
66
  import { decodeInlineTextOrUndefined } from "./fs-daemon-binary";
@@ -149,6 +151,56 @@ function sha256(bytes: Uint8Array): string {
149
151
  return createHash("sha256").update(bytes).digest("hex");
150
152
  }
151
153
 
154
+ // Stash a conflicting local copy at a sibling path, using an incrementing
155
+ // suffix (`foo (1).txt`, `foo (2).txt`, …) instead of a timestamp so
156
+ // repeated conflicts produce a clean, stable series rather than an
157
+ // ever-growing pile of `.conflicted-<ts>` files. The file is written
158
+ // locally; the daemon's normal watcher path syncs it to Convex like any
159
+ // other file. Returns the local path written.
160
+ //
161
+ // Two properties keep the deterministic suffix safe:
162
+ // • Content-aware reuse — if a `(N)` slot already holds these exact bytes,
163
+ // the copy is already stashed, so we return it instead of minting yet
164
+ // another sibling. This is what keeps repeated conflicts on the same
165
+ // unsynced content from piling up (the whole point of the change), and
166
+ // it also means an unrelated remote `(N)` file mirrored to local disk is
167
+ // skipped rather than overwritten.
168
+ // • Atomic claim — otherwise we write with O_EXCL (`wx`), so two handlers
169
+ // racing for the same slot can't clobber each other; the loser gets
170
+ // EEXIST and advances to the next N.
171
+ async function stashConflictCopy(args: {
172
+ absPath: string;
173
+ bytes: Uint8Array;
174
+ hash: string;
175
+ }): Promise<string> {
176
+ const { absPath, bytes, hash } = args;
177
+ const dir = dirname(absPath);
178
+ const ext = extname(absPath);
179
+ const stem = basename(absPath, ext);
180
+ for (let i = 1; i <= 1000; i++) {
181
+ const candidate = join(dir, `${stem} (${i})${ext}`);
182
+ const existing = await readFile(candidate).catch(() => null);
183
+ if (existing) {
184
+ // Already holds our bytes → nothing to do. Different content → this
185
+ // slot belongs to something else, try the next one.
186
+ if (sha256(existing) === hash) return candidate;
187
+ continue;
188
+ }
189
+ try {
190
+ await writeFile(candidate, bytes, { flag: "wx" });
191
+ return candidate;
192
+ } catch (error) {
193
+ if ((error as NodeJS.ErrnoException).code === "EEXIST") continue;
194
+ throw error;
195
+ }
196
+ }
197
+ // Every increment up to the cap is taken — fall back to a timestamp, which
198
+ // is effectively unique, so we still stash the bytes somewhere.
199
+ const fallback = join(dir, `${stem} (${Date.now()})${ext}`);
200
+ await writeFile(fallback, bytes);
201
+ return fallback;
202
+ }
203
+
152
204
  function normalizePosixWorkspacePath(input: string): string {
153
205
  let cleaned = input.replace(/\\/g, "/").replace(/\/+/g, "/");
154
206
  if (cleaned.startsWith("/workspace/")) {
@@ -772,8 +824,7 @@ async function runDaemon(daemonArgs: Args): Promise<void> {
772
824
  if (result.versionConflict) {
773
825
  // Stash the local copy in a sibling file, then re-pull the remote
774
826
  // version into the canonical path.
775
- const conflicted = `${absPath}.conflicted-${Date.now()}`;
776
- await writeFile(conflicted, bytes);
827
+ const conflicted = await stashConflictCopy({ absPath, bytes, hash });
777
828
  console.warn(
778
829
  `[dench-fs-daemon] version conflict for ${posixPath}; local copy stashed at ${conflicted}`,
779
830
  );
@@ -1580,10 +1580,49 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
1580
1580
  entryId: z.string().optional(),
1581
1581
  }),
1582
1582
  "crm.actions.list": z.object({ entryId: z.string() }),
1583
+ "crm.actions.create": z
1584
+ .object({
1585
+ objectName: z.string(),
1586
+ fieldName: z.string(),
1587
+ actionId: z.string().optional(),
1588
+ label: z.string().optional(),
1589
+ variant: z.string().optional(),
1590
+ icon: z.string().optional(),
1591
+ confirmMessage: z.string().optional(),
1592
+ recipe: z.array(z.any()).optional(),
1593
+ script: z.string().optional(),
1594
+ scriptPath: z.string().optional(),
1595
+ })
1596
+ .passthrough(),
1597
+ "crm.actions.update": z
1598
+ .object({
1599
+ actionId: z.string(),
1600
+ objectName: z.string(),
1601
+ fieldName: z.string(),
1602
+ label: z.string().optional(),
1603
+ variant: z.string().optional(),
1604
+ icon: z.string().optional(),
1605
+ confirmMessage: z.string().optional(),
1606
+ recipe: z.array(z.any()).optional(),
1607
+ script: z.string().optional(),
1608
+ scriptPath: z.string().optional(),
1609
+ })
1610
+ .passthrough(),
1611
+ "crm.actions.delete": z
1612
+ .object({
1613
+ actionId: z.string(),
1614
+ objectName: z.string(),
1615
+ fieldName: z.string().optional(),
1616
+ fieldId: z.string().optional(),
1617
+ deleteFieldIfEmpty: z.boolean().optional(),
1618
+ })
1619
+ .passthrough(),
1583
1620
  "crm.actions.run": z.object({
1584
1621
  actionId: z.string(),
1622
+ objectName: z.string(),
1585
1623
  fieldId: z.string(),
1586
- entryId: z.string(),
1624
+ entryIds: z.array(z.string()),
1625
+ prompts: z.record(z.string(), z.string()).optional(),
1587
1626
  }),
1588
1627
  "crm.reports.generate": z.object({
1589
1628
  objectName: z.string(),
@@ -1847,7 +1886,7 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
1847
1886
  "agentConfig.daily.show": z.object({}),
1848
1887
 
1849
1888
  // Gateway: search + images + tools
1850
- "apps.list": noInput,
1889
+ "integrations.list": noInput,
1851
1890
  "tool.status": noInput,
1852
1891
  "search.web": exaSearch,
1853
1892
  "search.contents": exaContents,
@@ -2201,7 +2240,31 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
2201
2240
  })
2202
2241
  .passthrough(),
2203
2242
  ),
2204
- "crm.actions.run": z.object({ id: z.string() }),
2243
+ "crm.actions.create": z.object({
2244
+ fieldId: z.string(),
2245
+ actionId: z.string(),
2246
+ fieldCreated: z.boolean(),
2247
+ }),
2248
+ "crm.actions.update": z.object({
2249
+ fieldId: z.string(),
2250
+ actionId: z.string(),
2251
+ fieldCreated: z.boolean(),
2252
+ }),
2253
+ "crm.actions.delete": z.object({
2254
+ ok: z.boolean(),
2255
+ remaining: z.number(),
2256
+ fieldDeleted: z.boolean(),
2257
+ }),
2258
+ "crm.actions.run": z.object({
2259
+ results: z.array(
2260
+ z.object({
2261
+ entryId: z.string(),
2262
+ status: z.enum(["success", "error"]),
2263
+ operations: z.number().optional(),
2264
+ error: z.string().optional(),
2265
+ }),
2266
+ ),
2267
+ }),
2205
2268
  "crm.members.list": z.object({
2206
2269
  members: z.array(memberShape),
2207
2270
  organizationId: z.string(),
@@ -2461,7 +2524,7 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
2461
2524
  "search.answer": exaAnswerResponse,
2462
2525
  "image.generate": imageEnvelope,
2463
2526
  "image.edit": imageEnvelope,
2464
- "apps.list": composioConnectionsResponse,
2527
+ "integrations.list": composioConnectionsResponse,
2465
2528
  "tool.status": composioConnectionsResponse,
2466
2529
  "tool.connect": z
2467
2530
  .object({
@@ -3180,7 +3243,20 @@ export const responseExamples: Record<string, unknown> = {
3180
3243
  result: "Enriched 4 fields",
3181
3244
  },
3182
3245
  ],
3183
- "crm.actions.run": { id: "arun_02" },
3246
+ "crm.actions.create": {
3247
+ fieldId: "fld_actions",
3248
+ actionId: "mark_won",
3249
+ fieldCreated: true,
3250
+ },
3251
+ "crm.actions.update": {
3252
+ fieldId: "fld_actions",
3253
+ actionId: "mark_won",
3254
+ fieldCreated: false,
3255
+ },
3256
+ "crm.actions.delete": { ok: true, remaining: 0, fieldDeleted: false },
3257
+ "crm.actions.run": {
3258
+ results: [{ entryId: "ent_lead99", status: "success", operations: 1 }],
3259
+ },
3184
3260
  "crm.members.list": {
3185
3261
  members: [
3186
3262
  {
@@ -3695,7 +3771,7 @@ export const responseExamples: Record<string, unknown> = {
3695
3771
  signedUrl: "https://happy-animal-123.convex.cloud/api/storage/img124",
3696
3772
  sizeBytes: 198432,
3697
3773
  },
3698
- "apps.list": {
3774
+ "integrations.list": {
3699
3775
  items: [
3700
3776
  {
3701
3777
  id: "ca_github01",
@@ -1057,16 +1057,50 @@ const rawApiOperations = [
1057
1057
  responseSchema: anyResponse,
1058
1058
  backend: convex("query", "functions/crm/actions:list"),
1059
1059
  }),
1060
+ op({
1061
+ id: "crm.actions.create",
1062
+ group: "crm",
1063
+ summary:
1064
+ "Create or upsert an action button on a named action column (fieldName is the column header; a column can hold multiple buttons — reuse fieldName to add more).",
1065
+ cli: "dench crm actions create",
1066
+ method: "POST",
1067
+ path: "/crm/actions",
1068
+ requestSchema: anyObject,
1069
+ responseSchema: anyResponse,
1070
+ backend: convex("mutation", "functions/crm/actions:defineAction"),
1071
+ }),
1072
+ op({
1073
+ id: "crm.actions.update",
1074
+ group: "crm",
1075
+ summary: "Update an action button on a CRM field.",
1076
+ cli: "dench crm actions edit",
1077
+ method: "PATCH",
1078
+ path: "/crm/actions/{actionId}",
1079
+ requestSchema: anyObject,
1080
+ responseSchema: anyResponse,
1081
+ backend: convex("mutation", "functions/crm/actions:defineAction"),
1082
+ }),
1083
+ op({
1084
+ id: "crm.actions.delete",
1085
+ group: "crm",
1086
+ summary: "Delete an action button from a CRM field.",
1087
+ cli: "dench crm actions delete",
1088
+ method: "DELETE",
1089
+ path: "/crm/actions/{actionId}",
1090
+ requestSchema: anyObject,
1091
+ responseSchema: anyResponse,
1092
+ backend: convex("mutation", "functions/crm/actions:deleteAction"),
1093
+ }),
1060
1094
  op({
1061
1095
  id: "crm.actions.run",
1062
1096
  group: "crm",
1063
- summary: "Start a CRM action run.",
1097
+ summary: "Run a CRM action on one or more entries.",
1064
1098
  cli: "dench crm actions run",
1065
1099
  method: "POST",
1066
1100
  path: "/crm/actions/{actionId}/runs",
1067
1101
  requestSchema: anyObject,
1068
1102
  responseSchema: anyResponse,
1069
- backend: convex("mutation", "functions/crm/actions:startActionRun"),
1103
+ backend: convex("action", "functions/crm/actions:run"),
1070
1104
  }),
1071
1105
  op({
1072
1106
  id: "crm.members.list",
@@ -1963,16 +1997,104 @@ const rawApiOperations = [
1963
1997
  backend: gateway("POST", "/images/edits"),
1964
1998
  }),
1965
1999
  op({
1966
- id: "apps.list",
2000
+ id: "integrations.list",
1967
2001
  group: "gateway",
1968
- summary: "List connected apps.",
1969
- cli: "dench apps",
2002
+ summary: "List connected Composio integrations.",
2003
+ cli: "dench integrations",
1970
2004
  method: "GET",
1971
- path: "/apps",
2005
+ path: "/integrations",
1972
2006
  requestSchema: noBody,
1973
2007
  responseSchema: anyResponse,
1974
2008
  backend: gateway("GET", "/composio/connections"),
1975
2009
  }),
2010
+ op({
2011
+ id: "apps.list",
2012
+ group: "apps",
2013
+ summary: "List installed App Store apps in the workspace.",
2014
+ cli: "dench apps list",
2015
+ method: "GET",
2016
+ path: "/apps",
2017
+ requestSchema: noBody,
2018
+ responseSchema: anyResponse,
2019
+ backend: convex("query", "functions/appStore:listInstalled"),
2020
+ }),
2021
+ op({
2022
+ id: "apps.gallery",
2023
+ group: "apps",
2024
+ summary: "List Discover listings — Official (Dench) + Community shelves.",
2025
+ cli: "dench apps gallery",
2026
+ method: "GET",
2027
+ path: "/apps/gallery",
2028
+ requestSchema: noBody,
2029
+ responseSchema: anyResponse,
2030
+ backend: convex("query", "functions/appStore:listGallery"),
2031
+ }),
2032
+ op({
2033
+ id: "apps.create",
2034
+ group: "apps",
2035
+ summary: "Scaffold and register a new App Store app.",
2036
+ cli: "dench apps create",
2037
+ method: "POST",
2038
+ path: "/apps",
2039
+ requestSchema: anyObject,
2040
+ responseSchema: anyResponse,
2041
+ backend: convex("action", "functions/appStore:createApp"),
2042
+ }),
2043
+ op({
2044
+ id: "apps.install",
2045
+ group: "apps",
2046
+ summary: "Install an App Store listing into the workspace.",
2047
+ cli: "dench apps install",
2048
+ method: "POST",
2049
+ path: "/apps/install",
2050
+ requestSchema: anyObject,
2051
+ responseSchema: anyResponse,
2052
+ backend: convex("action", "functions/appStore:install"),
2053
+ }),
2054
+ op({
2055
+ id: "apps.publish",
2056
+ group: "apps",
2057
+ summary: "Publish an app's files as a versioned App Store listing.",
2058
+ cli: "dench apps publish",
2059
+ method: "POST",
2060
+ path: "/apps/publish",
2061
+ requestSchema: anyObject,
2062
+ responseSchema: anyResponse,
2063
+ backend: convex("action", "functions/appStore:publish"),
2064
+ }),
2065
+ op({
2066
+ id: "apps.uninstall",
2067
+ group: "apps",
2068
+ summary: "Uninstall an app from the workspace.",
2069
+ cli: "dench apps uninstall",
2070
+ method: "DELETE",
2071
+ path: "/apps/{slug}",
2072
+ requestSchema: anyObject,
2073
+ responseSchema: anyResponse,
2074
+ backend: convex("mutation", "functions/appStore:uninstall"),
2075
+ }),
2076
+ op({
2077
+ id: "apps.setEnabled",
2078
+ group: "apps",
2079
+ summary: "Enable or disable an installed app.",
2080
+ cli: "dench apps enable",
2081
+ method: "PATCH",
2082
+ path: "/apps/{slug}",
2083
+ requestSchema: anyObject,
2084
+ responseSchema: anyResponse,
2085
+ backend: convex("mutation", "functions/appStore:setEnabled"),
2086
+ }),
2087
+ op({
2088
+ id: "apps.setPinned",
2089
+ group: "apps",
2090
+ summary: "Pin or unpin an installed app in the rail.",
2091
+ cli: "dench apps pin",
2092
+ method: "POST",
2093
+ path: "/apps/{slug}/pin",
2094
+ requestSchema: anyObject,
2095
+ responseSchema: anyResponse,
2096
+ backend: convex("mutation", "functions/appStore:setPinned"),
2097
+ }),
1976
2098
  op({
1977
2099
  id: "tool.status",
1978
2100
  group: "gateway",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
5
5
  "type": "module",
6
6
  "bin": {