@dench.com/cli 3.7.0-staging.387 → 3.8.1-staging.390

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.
@@ -20,15 +20,23 @@
20
20
  */
21
21
  import { type ZodTypeAny, z } from "zod";
22
22
  import {
23
+ CONDITION_CHECKS,
24
+ CONDITION_OPERATORS,
23
25
  checkCampaignSteps,
24
26
  checkStepEdits,
27
+ hasWorkflowShape,
25
28
  LINKEDIN_REACTIONS,
29
+ MAX_ACCEPTANCE_WAIT_HOURS,
26
30
  MAX_CAMPAIGN_STEPS,
27
31
  MAX_INMAIL_BODY_CHARS,
28
32
  MAX_INMAIL_SUBJECT_CHARS,
29
33
  MAX_LIKE_POST_AGE_DAYS,
34
+ MAX_LIKE_POST_COUNT,
30
35
  MAX_WAIT_HOURS,
36
+ MIN_ACCEPTANCE_WAIT_HOURS,
31
37
  MIN_WITHDRAW_AFTER_HOURS,
38
+ STEP_TYPES,
39
+ WORKFLOW_END,
32
40
  } from "./campaign-steps";
33
41
 
34
42
  const nativeEmailRecipient = z.object({
@@ -667,16 +675,25 @@ const campaignSendSchedule = z
667
675
  );
668
676
 
669
677
  const campaignStepType = z
670
- .enum([
671
- "email",
672
- "wait",
673
- "linkedin_visit",
674
- "linkedin_connection",
675
- "linkedin_message",
676
- "linkedin_like_post",
677
- "linkedin_inmail",
678
- ])
679
- .describe("Step type.");
678
+ .enum(STEP_TYPES)
679
+ .describe(
680
+ "Step type. condition (If / else) and wait_for_connection (Wait for them to accept) send nothing; they continue on yesNext or noNext and make the campaign a workflow.",
681
+ );
682
+
683
+ const workflowTarget = (exit: string) =>
684
+ z
685
+ .string()
686
+ .min(1)
687
+ .optional()
688
+ .describe(
689
+ `Workflow steps: ${exit}: another step's key, or "${WORKFLOW_END}" to stop there. Fixed once the campaign launches.`,
690
+ );
691
+
692
+ const campaignStepModel = z
693
+ .enum(["linear", "workflow"])
694
+ .describe(
695
+ "linear: steps run in order. workflow: steps have keys and paths (next, yesNext, noNext) and may include condition and wait_for_connection. Left out, a workflow stays one and steps with keys, paths or those types make one; nothing turns a workflow back into a list.",
696
+ );
680
697
 
681
698
  const campaignStepInput = z
682
699
  .object({
@@ -723,7 +740,7 @@ const campaignStepInput = z
723
740
  .string()
724
741
  .optional()
725
742
  .describe(
726
- "Legacy LinkedIn step sender. Pool campaigns use the campaign's linkedinAccountIds instead.",
743
+ "Legacy LinkedIn step sender (also on wait_for_connection, and on a condition that checks linkedin_connected). Pool campaigns use the campaign's linkedinAccountIds instead.",
727
744
  ),
728
745
  linkedinMessageBody: z
729
746
  .string()
@@ -743,7 +760,7 @@ const campaignStepInput = z
743
760
  .max(MAX_WAIT_HOURS)
744
761
  .optional()
745
762
  .describe(
746
- `linkedin_message with linkedinMessageMode=wait_until_connected: wait 1-${MAX_WAIT_HOURS} hours for acceptance (default 720). After a connection step this mode is the default.`,
763
+ `linkedin_message with linkedinMessageMode=wait_until_connected: wait 1-${MAX_WAIT_HOURS} hours for acceptance (default 720); after a connection step this mode is the default in a linear campaign. wait_for_connection: wait ${MIN_ACCEPTANCE_WAIT_HOURS}-${MAX_ACCEPTANCE_WAIT_HOURS} hours (1-90 days, default 720) before taking the Not accepted path.`,
747
764
  ),
748
765
  likeReaction: z
749
766
  .enum(LINKEDIN_REACTIONS)
@@ -760,12 +777,27 @@ const campaignStepInput = z
760
777
  .describe(
761
778
  `linkedin_like_post only: select the newest eligible original post from the last 1-${MAX_LIKE_POST_AGE_DAYS} days (default 30).`,
762
779
  ),
780
+ likeSkipPreviousWait: z
781
+ .boolean()
782
+ .optional()
783
+ .describe(
784
+ "linkedin_like_post only: before the immediately preceding wait starts, check for eligible posts. If none exist, skip that wait and the like step, keeping following waits. Defaults to false when omitted.",
785
+ ),
763
786
  likeSkipNextWait: z
764
787
  .boolean()
765
788
  .optional()
766
789
  .describe(
767
790
  "linkedin_like_post only: skip consecutive following wait steps whenever the reaction step is skipped (default true). False keeps those waits.",
768
791
  ),
792
+ likeCount: z
793
+ .number()
794
+ .int()
795
+ .min(1)
796
+ .max(MAX_LIKE_POST_COUNT)
797
+ .optional()
798
+ .describe(
799
+ `linkedin_like_post only: how many recent posts to react to, 1-${MAX_LIKE_POST_COUNT} (default 1).`,
800
+ ),
769
801
  inmailSubject: z
770
802
  .string()
771
803
  .optional()
@@ -781,6 +813,46 @@ const campaignStepInput = z
781
813
  .describe(
782
814
  `linkedin_connection only: withdraw a still-pending invitation sent by this campaign after ${MIN_WITHDRAW_AFTER_HOURS}-${MAX_WAIT_HOURS} hours (7-90 days). Omitted means off on creation; null turns it off on edit.`,
783
815
  ),
816
+ key: z
817
+ .string()
818
+ .min(1)
819
+ .optional()
820
+ .describe(
821
+ `Workflow steps: this step's id, unique in the campaign, which next, yesNext and noNext point at (not "${WORKFLOW_END}"). Left out, one is made.`,
822
+ ),
823
+ next: workflowTarget(
824
+ "where a step that isn't a condition or wait_for_connection goes; left out, the following step",
825
+ ),
826
+ yesNext: workflowTarget(
827
+ "condition and wait_for_connection only, required: where the Yes (Accepted) path goes",
828
+ ),
829
+ noNext: workflowTarget(
830
+ "condition and wait_for_connection only, required: where the No (Not accepted) path goes",
831
+ ),
832
+ check: z
833
+ .enum(CONDITION_CHECKS)
834
+ .optional()
835
+ .describe(
836
+ "condition only, required: linkedin_connected, has_email, opened_last_email (needs an email before it on every path) or field_value. Fixed once the campaign launches.",
837
+ ),
838
+ conditionField: z
839
+ .string()
840
+ .optional()
841
+ .describe(
842
+ 'condition with check=field_value: the lead field to check, like "Job Title" or "Source" (the campaign itself changes Status, Last touch and LinkedIn connection). Fixed once the campaign launches.',
843
+ ),
844
+ conditionOperator: z
845
+ .enum(CONDITION_OPERATORS)
846
+ .optional()
847
+ .describe(
848
+ "condition with check=field_value: is, is_not, contains, is_empty or is_not_empty (all case-insensitive). Fixed once the campaign launches.",
849
+ ),
850
+ conditionValue: z
851
+ .string()
852
+ .optional()
853
+ .describe(
854
+ "condition with check=field_value and is, is_not or contains: the text to compare with. Editable after launch.",
855
+ ),
784
856
  })
785
857
  .describe("One campaign step, in order.");
786
858
 
@@ -814,10 +886,16 @@ const campaignTimelineRun = z
814
886
  .optional()
815
887
  .describe("The email this run sent, in emailMessages[]."),
816
888
  lastError: z.string().optional(),
889
+ timeoutAt: z
890
+ .number()
891
+ .optional()
892
+ .describe("wait_for_connection: when it stops waiting for the accept."),
817
893
  result: z
818
894
  .unknown()
819
895
  .optional()
820
- .describe("For a skipped step, { reason } says why."),
896
+ .describe(
897
+ 'For a skipped step, { reason } says why. A finished condition or wait_for_connection has { branch: "yes" | "no" } (Yes / Accepted or No / Not accepted), and { reason } for some No paths.',
898
+ ),
821
899
  })
822
900
  .passthrough();
823
901
 
@@ -863,6 +941,7 @@ const campaignRow = z
863
941
  sendSchedules: z.array(campaignSendSchedule).optional(),
864
942
  dailySendCap: z.number().optional(),
865
943
  stepCount: z.number(),
944
+ stepModel: campaignStepModel.optional(),
866
945
  totalEnrolled: z.number(),
867
946
  activeCount: z.number(),
868
947
  pausedCount: z.number(),
@@ -3110,7 +3189,12 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3110
3189
  .min(1)
3111
3190
  .max(MAX_CAMPAIGN_STEPS)
3112
3191
  .describe(
3113
- "Every step, in send order. Rejected, not trimmed: a field the step type would drop (onlyIf off email/post-like steps, a body on a visit, reply-in-thread on the first email), a missing subject or required body, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age values, and {TOKEN} single braces, which would be sent as typed.",
3192
+ "Every step, in send order (a workflow may list them in any order; they are stored in path order). Rejected, not trimmed: a field the step type would drop (onlyIf off email/post-like steps, a body on a visit, reply-in-thread on the first email, condition fields its check ignores), a missing subject or required body, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age/acceptance values, {TOKEN} single braces, which would be sent as typed, and workflow paths that don't hold together (duplicate keys, a target that isn't a key or end, a condition or wait_for_connection without both yesNext and noNext, or any other step with them).",
3193
+ ),
3194
+ stepModel: campaignStepModel
3195
+ .optional()
3196
+ .describe(
3197
+ "linear: steps run in order. workflow: steps have keys and paths (next, yesNext, noNext) and may include condition and wait_for_connection. Left out, a workflow stays one and steps with keys, paths or those types make one; nothing turns a workflow back into a list. When replacing a workflow's steps, send workflow, so steps without keys or paths are checked here the way they will be saved.",
3114
3198
  ),
3115
3199
  allowSingleBraces: z
3116
3200
  .boolean()
@@ -3121,8 +3205,20 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3121
3205
  })
3122
3206
  .strict()
3123
3207
  .superRefine((input, ctx) => {
3208
+ if (input.stepModel === "linear") {
3209
+ const shaped = input.steps.findIndex(hasWorkflowShape);
3210
+ if (shaped >= 0)
3211
+ ctx.addIssue({
3212
+ code: "custom",
3213
+ message: `Step ${shaped + 1}: condition, wait_for_connection, key, next, yesNext and noNext only work in a workflow. Send stepModel "workflow", or leave it out.`,
3214
+ path: ["steps", shaped],
3215
+ });
3216
+ }
3124
3217
  for (const issue of checkCampaignSteps(input.steps, {
3125
3218
  allowSingleBraces: input.allowSingleBraces,
3219
+ ...(input.stepModel
3220
+ ? { workflow: input.stepModel === "workflow" }
3221
+ : {}),
3126
3222
  }).errors)
3127
3223
  ctx.addIssue({
3128
3224
  code: "custom",
@@ -3154,13 +3250,13 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3154
3250
  })
3155
3251
  .strict()
3156
3252
  .describe(
3157
- "One step edit: only the fields given change. Clear text with an empty string, onlyIf with always, emailReplyToPrevious with false and withdrawAfterHours with null. Like settings are optional; omitted values stay unchanged.",
3253
+ "One step edit: only the fields given change. Clear text with an empty string, onlyIf with always, emailReplyToPrevious with false and withdrawAfterHours with null. Like settings are optional; omitted values stay unchanged. A workflow step's key, next, yesNext, noNext, check, conditionField and conditionOperator can't change after launch (send them unchanged or leave them out); conditionValue and a wait_for_connection's connectionTimeoutHours can.",
3158
3254
  ),
3159
3255
  )
3160
3256
  .min(1)
3161
3257
  .max(MAX_CAMPAIGN_STEPS)
3162
3258
  .describe(
3163
- "The steps to change. Rejected, not trimmed: a field the step type would drop, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age values, and {TOKEN} single braces.",
3259
+ "The steps to change. Rejected, not trimmed: a field the step type would drop, an overlength note/message/InMail subject, out-of-range wait/withdrawal/post-age/acceptance values, {TOKEN} single braces, and a changed path or If / else check.",
3164
3260
  ),
3165
3261
  allowSingleBraces: z
3166
3262
  .boolean()
@@ -3584,6 +3680,10 @@ export const requestSchemas: Record<string, ZodTypeAny> = {
3584
3680
  includeAutomated: z.boolean().optional(),
3585
3681
  }),
3586
3682
  "chat.new": z.object({
3683
+ environment: z
3684
+ .string()
3685
+ .regex(/^[a-z][a-z0-9-]{0,63}$/)
3686
+ .optional(),
3587
3687
  prompt: z.string().describe("The user message that starts the turn."),
3588
3688
  model: z.string().optional(),
3589
3689
  fileContextPath: z.string().optional(),
@@ -5035,7 +5135,11 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5035
5135
  "campaigns.get": z
5036
5136
  .object({
5037
5137
  sequence: campaignRow,
5038
- steps: z.array(campaignStepInput.passthrough()),
5138
+ steps: z
5139
+ .array(campaignStepInput.passthrough())
5140
+ .describe(
5141
+ "The steps in order, each with its _id. condition and wait_for_connection steps also carry a readable label.",
5142
+ ),
5039
5143
  enrollments: z.array(z.record(z.string(), z.unknown())),
5040
5144
  currentRuns: z
5041
5145
  .record(z.string(), z.unknown())
@@ -5075,6 +5179,22 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5075
5179
  replyRate: z.number(),
5076
5180
  openRate: z.number(),
5077
5181
  clickRate: z.number(),
5182
+ branchStats: z
5183
+ .array(
5184
+ z.object({
5185
+ stepId: z.string(),
5186
+ yes: z
5187
+ .number()
5188
+ .describe("People who took the Yes (Accepted) path."),
5189
+ no: z
5190
+ .number()
5191
+ .describe("People who took the No (Not accepted) path."),
5192
+ }),
5193
+ )
5194
+ .optional()
5195
+ .describe(
5196
+ "Workflows only: each condition and wait_for_connection step, in step order, with how many people took each path.",
5197
+ ),
5078
5198
  truncated: z.boolean(),
5079
5199
  })
5080
5200
  .passthrough(),
@@ -5087,7 +5207,28 @@ export const responseSchemas: Record<string, ZodTypeAny> = {
5087
5207
  dailySendCap: z.number().nullable(),
5088
5208
  nextEmailAt: z.number().nullable(),
5089
5209
  now: z.record(z.string(), z.unknown()).nullable(),
5090
- next: z.record(z.string(), z.unknown()).nullable(),
5210
+ next: z
5211
+ .object({
5212
+ kind: z
5213
+ .string()
5214
+ .describe(
5215
+ "profile, visit, invite, message, like, inmail, withdraw, email, wait or check (an If / else).",
5216
+ ),
5217
+ acceptanceDeadline: z
5218
+ .number()
5219
+ .optional()
5220
+ .describe(
5221
+ "Waiting for them to accept: when the wait ends either way.",
5222
+ ),
5223
+ label: z
5224
+ .string()
5225
+ .optional()
5226
+ .describe(
5227
+ 'kind check: what the If / else checks, e.g. "Has an email address".',
5228
+ ),
5229
+ })
5230
+ .passthrough()
5231
+ .nullable(),
5091
5232
  paused: z.record(z.string(), z.unknown()).nullable(),
5092
5233
  senders: z.array(z.record(z.string(), z.unknown())),
5093
5234
  })
@@ -6383,18 +6524,41 @@ export const requestExamples: Record<string, unknown> = {
6383
6524
  },
6384
6525
  "campaigns.steps.replace": {
6385
6526
  sequenceId: "<sequenceId>",
6527
+ stepModel: "workflow",
6386
6528
  steps: [
6387
- { type: "linkedin_visit" },
6388
6529
  {
6530
+ key: "connected",
6531
+ type: "condition",
6532
+ check: "linkedin_connected",
6533
+ yesNext: "message",
6534
+ noNext: "invite",
6535
+ },
6536
+ {
6537
+ key: "invite",
6389
6538
  type: "linkedin_connection",
6390
6539
  linkedinMessageBody:
6391
6540
  "Hi {{First name}}, enjoyed your post on {{Topic}}.",
6541
+ next: "accept",
6542
+ },
6543
+ {
6544
+ key: "accept",
6545
+ type: "wait_for_connection",
6546
+ connectionTimeoutHours: 240,
6547
+ yesNext: "message",
6548
+ noNext: "email",
6392
6549
  },
6393
- { type: "wait", waitHours: 48 },
6394
6550
  {
6551
+ key: "email",
6395
6552
  type: "email",
6396
6553
  emailSubject: "Quick question, {{First name}}",
6397
6554
  emailTextBody: "Hi {{First name}}, ...",
6555
+ next: "end",
6556
+ },
6557
+ {
6558
+ key: "message",
6559
+ type: "linkedin_message",
6560
+ linkedinMessageBody: "Thanks for connecting, {{First name}}. ...",
6561
+ next: "end",
6398
6562
  },
6399
6563
  ],
6400
6564
  },
@@ -6413,6 +6577,12 @@ export const requestExamples: Record<string, unknown> = {
6413
6577
  type: "wait",
6414
6578
  waitHours: 96,
6415
6579
  },
6580
+ {
6581
+ stepId: "<waitForConnectionStepId>",
6582
+ expectedUpdatedAt: 1765526400000,
6583
+ type: "wait_for_connection",
6584
+ connectionTimeoutHours: 336,
6585
+ },
6416
6586
  ],
6417
6587
  },
6418
6588
  "campaigns.personalization.set": {
@@ -53,6 +53,8 @@ export const APP_SDK_SCOPES = [
53
53
  "cron:schedule",
54
54
  "email:read",
55
55
  "email:write",
56
+ "environments:read",
57
+ "environments:write",
56
58
  "files:read",
57
59
  "files:write",
58
60
  "http:external",
@@ -70,6 +72,8 @@ export const APP_SDK_SCOPES = [
70
72
  "notifications:read",
71
73
  "notifications:write",
72
74
  "search:read",
75
+ "secrets:read",
76
+ "secrets:write",
73
77
  "settings:read",
74
78
  "settings:write",
75
79
  "store:read",
@@ -77,12 +81,21 @@ export const APP_SDK_SCOPES = [
77
81
  "ui",
78
82
  "web:read",
79
83
  "webhooks:receive",
80
- "workspace:read"
84
+ "workspace:read",
85
+ "workspace:write"
81
86
  ] as const;
82
87
  export type AppSdkScope = (typeof APP_SDK_SCOPES)[number];
83
88
 
84
89
  /** Every public /api/v1 operation reachable through dench.api.call. */
85
90
  export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
91
+ {"id":"environments.list","group":"environments","summary":"List workspace-shared reusable setups and ready revisions (metadata only).","cli":"dench env list","method":"GET","path":"/environments","permission":"environments:read","pathParams":[]},
92
+ {"id":"environments.get","group":"environments","summary":"Inspect a saved setup, its manifest, and an optional immutable revision.","cli":"dench env show","method":"GET","path":"/environments/{name}","permission":"environments:read","pathParams":["name"]},
93
+ {"id":"environments.upload","group":"environments","summary":"Reserve an owner-bound source upload. Prefix the uploaded bytes with the returned proof before saving.","cli":"dench env save","method":"POST","path":"/environments/uploads","permission":"environments:write","pathParams":[]},
94
+ {"id":"environments.save","group":"environments","summary":"Publish an explicitly saved source and manifest for one-time preparation. Returns pending/building until verified; does not run the task.","cli":"dench env save","method":"POST","path":"/environments/{name}","permission":"environments:write","pathParams":["name"]},
95
+ {"id":"environments.delete","group":"environments","summary":"Retire a saved setup from new selection; retain immutable revisions and active job evidence.","cli":"dench env delete","method":"DELETE","path":"/environments/{name}","permission":"environments:write","pathParams":["name"]},
96
+ {"id":"secrets.list","group":"secrets","summary":"List workspace-shared credential names, kinds and versions; never values.","cli":"dench secrets list","method":"GET","path":"/secrets","permission":"secrets:read","pathParams":[]},
97
+ {"id":"secrets.set","group":"secrets","summary":"Encrypt a workspace credential. Use CLI stdin or the secure chat form; return metadata only. Do not put values in chat, manifests or scripts.","cli":"dench secrets set","method":"PUT","path":"/secrets/{name}","permission":"secrets:write","pathParams":["name"]},
98
+ {"id":"secrets.delete","group":"secrets","summary":"Revoke a workspace credential for future resolution, optionally checking its version.","cli":"dench secrets delete","method":"DELETE","path":"/secrets/{name}","permission":"secrets:write","pathParams":["name"]},
86
99
  {"id":"campaigns.drafts.create","group":"campaigns","summary":"Create an idempotent empty campaign draft with the caller's available sender pools. Does not launch or send.","cli":"dench campaign draft","method":"POST","path":"/campaigns/drafts","permission":"campaigns:write","pathParams":[]},
87
100
  {"id":"campaigns.audience.search","group":"campaigns","summary":"Search people by filters without adding CRM records or campaign recipients. Search credits apply; this is not a free provider preview.","cli":"dench campaign search","method":"POST","path":"/campaigns/audience/search","permission":"campaigns:write","pathParams":[]},
88
101
  {"id":"campaigns.people.addFromSearch","group":"campaigns","summary":"Search and add up to 50 people to an existing campaign. New identities become Leads. Search credits apply; use nextPageToken for another page.","cli":"dench campaign add --search","method":"POST","path":"/campaigns/{sequenceId}/people/search","permission":"campaigns:write","pathParams":["sequenceId"]},
@@ -368,12 +381,12 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
368
381
  {"id":"campaigns.create","group":"campaigns","summary":"Create a draft outreach campaign with email and LinkedIn sender pools and a business-hours send window in the given timezone.","cli":"dench campaign create","method":"POST","path":"/campaigns","permission":"campaigns:write","pathParams":[]},
369
382
  {"id":"campaigns.duplicate","group":"campaigns","summary":"Copy a campaign into a new draft named \"<name> copy\": the same steps, schedule, sender pools and personalized lines, its lead source paused, and no people. To reach more than 300 people, duplicate and add the next page of the search to the copy.","cli":"dench campaign duplicate","method":"POST","path":"/campaigns/{sequenceId}/duplicate","permission":"campaigns:write","pathParams":["sequenceId"]},
370
383
  {"id":"campaigns.get","group":"campaigns","summary":"Read a campaign: settings, steps, people, their current step and hold, last touches, linked leads.","cli":"dench campaign get","method":"GET","path":"/campaigns/{sequenceId}","permission":"campaigns:read","pathParams":["sequenceId"]},
371
- {"id":"campaigns.analytics","group":"campaigns","summary":"Campaign funnel: enrolled, contacted, invited, accepted, messaged, replied, opens and clicks.","cli":"dench campaign get","method":"GET","path":"/campaigns/{sequenceId}/analytics","permission":"campaigns:read","pathParams":["sequenceId"]},
384
+ {"id":"campaigns.analytics","group":"campaigns","summary":"Campaign funnel: enrolled, contacted, invited, accepted, messaged, replied, opens and clicks, and for a workflow how many people took each If / else and Wait for them to accept path (branchStats).","cli":"dench campaign get","method":"GET","path":"/campaigns/{sequenceId}/analytics","permission":"campaigns:read","pathParams":["sequenceId"]},
372
385
  {"id":"campaigns.live","group":"campaigns","summary":"What the campaign is doing now, what comes next and when, and why it is holding.","cli":"dench campaign get","method":"GET","path":"/campaigns/{sequenceId}/live","permission":"campaigns:read","pathParams":["sequenceId"]},
373
386
  {"id":"campaigns.pipeline","group":"campaigns","summary":"Where a campaign's people sit in the workspace's lead pipeline right now: each lead Status in order with how many enrolled leads are at it, and how many have no lead or a status outside the pipeline.","cli":"dench campaign pipeline","method":"GET","path":"/campaigns/{sequenceId}/pipeline","permission":"campaigns:read","pathParams":["sequenceId"]},
374
387
  {"id":"campaigns.update","group":"campaigns","summary":"Change a campaign's name, sender pools, send windows, daily email cap, stop-on-reply, start or end. Works until archived; existing conversations keep their assigned sender.","cli":"dench campaign set","method":"PATCH","path":"/campaigns/{sequenceId}","permission":"campaigns:write","pathParams":["sequenceId"]},
375
- {"id":"campaigns.steps.replace","group":"campaigns","summary":"Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail). Steps are checked first: a field the step type would drop, missing text or {TOKEN} single braces are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only.","cli":"dench campaign steps set","method":"PUT","path":"/campaigns/{sequenceId}/steps","permission":"campaigns:write","pathParams":["sequenceId"]},
376
- {"id":"campaigns.steps.update","group":"campaigns","summary":"Edit the text, waits and conditions of a launched (or paused) campaign's steps in place. Each step keeps its id, type and place; pass the stepId and the updatedAt you read, and only the fields you change. Messages already sent or being prepared keep their text; drafts use campaigns.steps.replace.","cli":"dench campaign steps edit","method":"PATCH","path":"/campaigns/{sequenceId}/steps","permission":"campaigns:write","pathParams":["sequenceId"]},
388
+ {"id":"campaigns.steps.replace","group":"campaigns","summary":"Replace every step of a draft campaign (email, wait, LinkedIn visit, connection request, message, post reaction, InMail, If / else, Wait for them to accept). A workflow (stepModel workflow, or steps with key, next, yesNext or noNext) branches: condition and wait_for_connection steps continue on yesNext or noNext, other steps on next; each points at another step's key or end. Steps are checked first: a field the step type would drop, missing text, {TOKEN} single braces or paths that don't hold together are rejected, not saved. Configure sender pools on the campaign; step sender ids are legacy-only.","cli":"dench campaign steps set","method":"PUT","path":"/campaigns/{sequenceId}/steps","permission":"campaigns:write","pathParams":["sequenceId"]},
389
+ {"id":"campaigns.steps.update","group":"campaigns","summary":"Edit the text, waits and conditions of a launched (or paused) campaign's steps in place. Each step keeps its id, type and place; pass the stepId and the updatedAt you read, and only the fields you change. A workflow keeps its paths and what each If / else checks (key, next, yesNext, noNext, check, conditionField, conditionOperator); conditionValue and acceptance waits stay editable. Messages already sent or being prepared keep their text; drafts use campaigns.steps.replace.","cli":"dench campaign steps edit","method":"PATCH","path":"/campaigns/{sequenceId}/steps","permission":"campaigns:write","pathParams":["sequenceId"]},
377
390
  {"id":"campaigns.personalization.get","group":"campaigns","summary":"Read a campaign's personalized lines (written for each person): their {{keys}} for steps, instructions and fallbacks, how many people have them, and the cost per person.","cli":"dench campaign personalize get","method":"GET","path":"/campaigns/{sequenceId}/personalization","permission":"campaigns:read","pathParams":["sequenceId"]},
378
391
  {"id":"campaigns.personalization.set","group":"campaigns","summary":"Define up to 5 personalized lines (each used as {{key}} in steps) and what you sell. Saving starts writing missing lines for up to 100 people, about 3 cents a person; for more it returns needsConfirm and writes nothing until campaigns.personalization.write is confirmed.","cli":"dench campaign personalize set","method":"PUT","path":"/campaigns/{sequenceId}/personalization","permission":"campaigns:write","pathParams":["sequenceId"]},
379
392
  {"id":"campaigns.personalization.clear","group":"campaigns","summary":"Turn off a campaign's personalized lines and cancel the ones not yet written. Refused while a step still uses one of the {{keys}}.","cli":"dench campaign personalize clear","method":"DELETE","path":"/campaigns/{sequenceId}/personalization","permission":"campaigns:write","pathParams":["sequenceId"]},
@@ -458,23 +471,24 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
458
471
  {"id":"browser.session.create","group":"browser","summary":"Create a Dench Browser session and return a CDP connect URL for Playwright (chromium.connectOverCDP) plus a live-view URL. Use for durable scripts run from the sandbox.","cli":"dench browser session create","method":"POST","path":"/browser/sessions","permission":"browser:write","pathParams":[]},
459
472
  {"id":"browser.session.status","group":"browser","summary":"Get the status, connect URL, and live-view URL for a Dench Browser session.","cli":"dench browser session status","method":"GET","path":"/browser/sessions/{sessionId}","permission":"browser:read","pathParams":["sessionId"]},
460
473
  {"id":"browser.session.stop","group":"browser","summary":"Stop a Dench Browser session and release the remote browser.","cli":"dench browser session stop","method":"DELETE","path":"/browser/sessions/{sessionId}","permission":"browser:write","pathParams":["sessionId"]},
461
- {"id":"agentConfig.identity.show","group":"agent-config","summary":"Read IDENTITY.md.","cli":"dench identity show","method":"GET","path":"/agent-config/identity","permission":"agent-config:read","pathParams":[]},
462
- {"id":"agentConfig.identity.edit","group":"agent-config","summary":"Update IDENTITY.md.","cli":"dench identity edit","method":"PUT","path":"/agent-config/identity","permission":"agent-config:write","pathParams":[]},
463
- {"id":"agentConfig.organisation.show","group":"agent-config","summary":"Read ORGANISATION.md.","cli":"dench organisation show","method":"GET","path":"/agent-config/organisation","permission":"agent-config:read","pathParams":[]},
464
- {"id":"agentConfig.organisation.edit","group":"agent-config","summary":"Update ORGANISATION.md.","cli":"dench organisation edit","method":"PUT","path":"/agent-config/organisation","permission":"agent-config:write","pathParams":[]},
465
- {"id":"agentConfig.user.show","group":"agent-config","summary":"Read USER.md.","cli":"dench user show","method":"GET","path":"/agent-config/user","permission":"agent-config:read","pathParams":[]},
466
- {"id":"agentConfig.user.edit","group":"agent-config","summary":"Update USER.md.","cli":"dench user edit","method":"PUT","path":"/agent-config/user","permission":"agent-config:write","pathParams":[]},
467
- {"id":"agentConfig.tools.show","group":"agent-config","summary":"Read tool notes.","cli":"dench tools show","method":"GET","path":"/agent-config/tools","permission":"agent-config:read","pathParams":[]},
468
- {"id":"agentConfig.tools.edit","group":"agent-config","summary":"Update tool notes.","cli":"dench tools edit","method":"PUT","path":"/agent-config/tools","permission":"agent-config:write","pathParams":[]},
469
- {"id":"agentConfig.mem.show","group":"agent-config","summary":"Read aggregate memory.","cli":"dench mem show","method":"GET","path":"/agent-config/mem","permission":"agent-config:read","pathParams":[]},
470
- {"id":"agentConfig.mem.edit","group":"agent-config","summary":"Update aggregate memory.","cli":"dench mem set","method":"PUT","path":"/agent-config/mem","permission":"agent-config:write","pathParams":[]},
471
- {"id":"agentConfig.bootstrap.show","group":"agent-config","summary":"Read bootstrap state.","cli":"dench bootstrap status","method":"GET","path":"/agent-config/bootstrap","permission":"agent-config:read","pathParams":[]},
472
- {"id":"agentConfig.bootstrap.complete","group":"agent-config","summary":"Complete bootstrap.","cli":"dench bootstrap complete","method":"POST","path":"/agent-config/bootstrap/complete","permission":"agent-config:write","pathParams":[]},
473
- {"id":"agentConfig.bootstrap.template","group":"agent-config","summary":"Update bootstrap template.","cli":"dench bootstrap template","method":"PUT","path":"/agent-config/bootstrap/template","permission":"agent-config:write","pathParams":[]},
474
+ {"id":"agentConfig.identity.show","group":"agent-config","summary":"Read IDENTITY.md.","cli":"dench context --sections identity","method":"GET","path":"/agent-config/identity","permission":"agent-config:read","pathParams":[]},
475
+ {"id":"agentConfig.identity.edit","group":"agent-config","summary":"Update IDENTITY.md.","cli":"dench context set identity","method":"PUT","path":"/agent-config/identity","permission":"agent-config:write","pathParams":[]},
476
+ {"id":"agentConfig.organisation.show","group":"agent-config","summary":"Read ORGANISATION.md.","cli":"dench context --sections organisation","method":"GET","path":"/agent-config/organisation","permission":"agent-config:read","pathParams":[]},
477
+ {"id":"agentConfig.organisation.edit","group":"agent-config","summary":"Update ORGANISATION.md.","cli":"dench context set organisation","method":"PUT","path":"/agent-config/organisation","permission":"agent-config:write","pathParams":[]},
478
+ {"id":"agentConfig.user.show","group":"agent-config","summary":"Read USER.md.","cli":"dench context --sections user","method":"GET","path":"/agent-config/user","permission":"agent-config:read","pathParams":[]},
479
+ {"id":"agentConfig.user.edit","group":"agent-config","summary":"Update USER.md.","cli":"dench context set user","method":"PUT","path":"/agent-config/user","permission":"agent-config:write","pathParams":[]},
480
+ {"id":"agentConfig.tools.show","group":"agent-config","summary":"Read tool notes.","cli":"dench context --sections tools","method":"GET","path":"/agent-config/tools","permission":"agent-config:read","pathParams":[]},
481
+ {"id":"agentConfig.tools.edit","group":"agent-config","summary":"Update tool notes.","cli":"dench context set tools","method":"PUT","path":"/agent-config/tools","permission":"agent-config:write","pathParams":[]},
482
+ {"id":"workspace.context.rules.set","group":"workspace","summary":"Update standing workspace rules; credentials belong in secrets and runnable setup in environments.","cli":"dench context set rules","method":"PUT","path":"/context/rules","permission":"workspace:write","pathParams":[]},
483
+ {"id":"agentConfig.mem.show","group":"agent-config","summary":"Read aggregate memory.","cli":"dench mem show","method":"GET","path":"/agent-config/mem","permission":"agent-config:read","deprecated":true,"pathParams":[]},
484
+ {"id":"agentConfig.mem.edit","group":"agent-config","summary":"Update aggregate memory.","cli":"dench mem set","method":"PUT","path":"/agent-config/mem","permission":"agent-config:write","deprecated":true,"pathParams":[]},
485
+ {"id":"agentConfig.bootstrap.show","group":"agent-config","summary":"Read bootstrap state.","cli":"dench bootstrap status","method":"GET","path":"/agent-config/bootstrap","permission":"agent-config:read","deprecated":true,"pathParams":[]},
486
+ {"id":"agentConfig.bootstrap.complete","group":"agent-config","summary":"Complete bootstrap.","cli":"dench bootstrap complete","method":"POST","path":"/agent-config/bootstrap/complete","permission":"agent-config:write","deprecated":true,"pathParams":[]},
487
+ {"id":"agentConfig.bootstrap.template","group":"agent-config","summary":"Update bootstrap template.","cli":"dench bootstrap template","method":"PUT","path":"/agent-config/bootstrap/template","permission":"agent-config:write","deprecated":true,"pathParams":[]},
474
488
  {"id":"agentConfig.model.default","group":"agent-config","summary":"Set the default chat model.","cli":"dench model default","method":"PUT","path":"/agent-config/model/default","permission":"agent-config:write","pathParams":[]},
475
489
  {"id":"agentConfig.model.thread","group":"agent-config","summary":"Set a thread model override.","cli":"dench model thread","method":"PUT","path":"/agent-config/model/threads/{threadId}","permission":"agent-config:write","pathParams":["threadId"]},
476
- {"id":"agentConfig.daily.today","group":"agent-config","summary":"Read today's daily activity path.","cli":"dench daily today","method":"GET","path":"/agent-config/daily/today","permission":"agent-config:read","pathParams":[]},
477
- {"id":"agentConfig.daily.show","group":"agent-config","summary":"Read a daily activity path.","cli":"dench daily show","method":"GET","path":"/agent-config/daily/{date}","permission":"agent-config:read","pathParams":["date"]},
490
+ {"id":"agentConfig.daily.today","group":"agent-config","summary":"Read today's daily activity path.","cli":"dench daily today","method":"GET","path":"/agent-config/daily/today","permission":"agent-config:read","deprecated":true,"pathParams":[]},
491
+ {"id":"agentConfig.daily.show","group":"agent-config","summary":"Read a daily activity path.","cli":"dench daily show","method":"GET","path":"/agent-config/daily/{date}","permission":"agent-config:read","deprecated":true,"pathParams":["date"]},
478
492
  {"id":"notifications.list","group":"notifications","summary":"List your notifications (inbox, unread, archived, or all).","cli":"dench notifications list","method":"GET","path":"/notifications","permission":"notifications:read","pathParams":[]},
479
493
  {"id":"notifications.unreadCount","group":"notifications","summary":"How many unread notifications you have.","cli":"dench notifications count","method":"GET","path":"/notifications/unread-count","permission":"notifications:read","pathParams":[]},
480
494
  {"id":"notifications.read","group":"notifications","summary":"Mark a notification read (or unread).","cli":"dench notifications read","method":"PATCH","path":"/notifications/{notificationId}","permission":"notifications:write","pathParams":["notificationId"]},
@@ -487,7 +501,7 @@ export const APP_SDK_OPERATIONS: readonly AppSdkOperationInfo[] = [
487
501
  ];
488
502
 
489
503
  /** CLI-only operation ids (sessions, fs, imports): never callable from an app. */
490
- export const APP_SDK_LOCAL_OPERATIONS: readonly string[] = ["sessions.local.list","sessions.local.use","sessions.local.logout","backend.local.configure","enrichment.filters.options","crm.import","crm.export","meetings.import","fs.daemon","apps.validate"];
504
+ export const APP_SDK_LOCAL_OPERATIONS: readonly string[] = ["environments.run","sessions.local.list","sessions.local.use","sessions.local.logout","backend.local.configure","enrichment.filters.options","crm.import","crm.export","meetings.import","fs.daemon","apps.validate"];
491
505
 
492
506
  /** Curated window.dench methods → permission (host + aliases). */
493
507
  export const APP_SDK_METHODS: Readonly<Record<string, AppSdkMethodInfo>> = {