@dench.com/cli 3.8.0-staging.389 → 3.8.2-staging.391
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/ads.ts +6 -2
- package/app-sdk.d.ts +132 -27
- package/apps.ts +7 -3
- package/browser.ts +8 -3
- package/campaign.ts +336 -55
- package/chat.ts +8 -6
- package/cron.ts +7 -3
- package/dench.ts +52 -94
- package/image.ts +6 -2
- package/leads.ts +6 -2
- package/lib/api-schemas.ts +186 -20
- package/lib/app-sdk-catalog.generated.ts +3 -3
- package/lib/campaign-steps.ts +474 -12
- package/lib/campaign-timeline.ts +69 -4
- package/lib/command-args.ts +26 -9
- package/lib/command-help.ts +192 -39
- package/lib/command-registry.ts +3 -3
- package/lib/file-command-contract.ts +106 -0
- package/lib/references.generated.json +14 -14
- package/meetings.ts +6 -2
- package/members.ts +6 -2
- package/package.json +1 -1
- package/search.ts +6 -2
- package/tools.ts +9 -5
- package/web.ts +20 -6
package/lib/campaign-timeline.ts
CHANGED
|
@@ -8,6 +8,8 @@ export type TimelineRun = {
|
|
|
8
8
|
startedAt?: number;
|
|
9
9
|
waitUntil?: number;
|
|
10
10
|
retryAt?: number;
|
|
11
|
+
/** A wait for acceptance: when it stops waiting either way. */
|
|
12
|
+
timeoutAt?: number;
|
|
11
13
|
completedAt?: number;
|
|
12
14
|
emailMessageId?: string;
|
|
13
15
|
lastError?: string;
|
|
@@ -49,6 +51,8 @@ export type TimelineResult = {
|
|
|
49
51
|
type: string;
|
|
50
52
|
order: number;
|
|
51
53
|
waitHours?: number;
|
|
54
|
+
/** If / else and Wait until they accept steps: what they check. */
|
|
55
|
+
label?: string;
|
|
52
56
|
}>;
|
|
53
57
|
runs: TimelineRun[];
|
|
54
58
|
emailMessages: TimelineEmail[];
|
|
@@ -63,6 +67,10 @@ export type TimelineStep = {
|
|
|
63
67
|
outcome: string;
|
|
64
68
|
at: number | null;
|
|
65
69
|
detail: string[];
|
|
70
|
+
/** An If / else or a wait for acceptance: the path it took. */
|
|
71
|
+
branch?: "yes" | "no";
|
|
72
|
+
/** Still waiting for them to accept: when it stops waiting. */
|
|
73
|
+
acceptanceDeadline?: number;
|
|
66
74
|
subject?: string;
|
|
67
75
|
text?: string;
|
|
68
76
|
error?: string;
|
|
@@ -101,16 +109,66 @@ const EMAIL_STATUS: Record<string, string> = {
|
|
|
101
109
|
failed: "could not send",
|
|
102
110
|
};
|
|
103
111
|
|
|
112
|
+
function resultField(result: unknown, key: string): unknown {
|
|
113
|
+
return result && typeof result === "object"
|
|
114
|
+
? (result as Record<string, unknown>)[key]
|
|
115
|
+
: undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
function skipReason(result: unknown): string {
|
|
105
|
-
const reason =
|
|
106
|
-
result && typeof result === "object"
|
|
107
|
-
? (result as { reason?: unknown }).reason
|
|
108
|
-
: undefined;
|
|
119
|
+
const reason = resultField(result, "reason");
|
|
109
120
|
return (typeof reason === "string" && SKIP_REASONS[reason]) || "Skipped";
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
/** Why an If / else or a wait for acceptance took its No path. */
|
|
124
|
+
const NO_REASONS: Record<string, string> = {
|
|
125
|
+
missing_linkedin_identity: "no LinkedIn profile",
|
|
126
|
+
missing_email_identity: "no email address",
|
|
127
|
+
linkedin_connection_timeout: "they did not accept in time",
|
|
128
|
+
linkedin_invite_withdrawn: "the connection request was withdrawn",
|
|
129
|
+
linkedin_invite_not_sent: "no connection request went out",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const LOGIC_TYPES = new Set(["condition", "wait_for_connection"]);
|
|
133
|
+
|
|
134
|
+
export function branchOf(run: TimelineRun): "yes" | "no" | undefined {
|
|
135
|
+
if (!LOGIC_TYPES.has(run.type) || run.status !== "sent") return undefined;
|
|
136
|
+
const branch = resultField(run.result, "branch");
|
|
137
|
+
return branch === "yes" || branch === "no" ? branch : undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** What the editor calls each exit: Yes / No, or Accepted / Not accepted. */
|
|
141
|
+
export function branchExitLabel(type: string, branch: "yes" | "no"): string {
|
|
142
|
+
if (type === "wait_for_connection")
|
|
143
|
+
return branch === "yes" ? "Accepted" : "Not accepted";
|
|
144
|
+
return branch === "yes" ? "Yes" : "No";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function logicOutcome(run: TimelineRun): string | undefined {
|
|
148
|
+
if (!LOGIC_TYPES.has(run.type)) return undefined;
|
|
149
|
+
const branch = branchOf(run);
|
|
150
|
+
if (branch) {
|
|
151
|
+
const reason = resultField(run.result, "reason");
|
|
152
|
+
const why =
|
|
153
|
+
branch === "no" && typeof reason === "string" ? NO_REASONS[reason] : "";
|
|
154
|
+
return `${branchExitLabel(run.type, branch)}${why ? ` (${why})` : ""}`;
|
|
155
|
+
}
|
|
156
|
+
if (run.status === "sent") return "Done";
|
|
157
|
+
if (run.type === "wait_for_connection")
|
|
158
|
+
return run.status === "waiting"
|
|
159
|
+
? "Waiting for them to accept"
|
|
160
|
+
: run.status === "queued" || run.status === "running"
|
|
161
|
+
? "Checking whether they accepted"
|
|
162
|
+
: undefined;
|
|
163
|
+
return ["queued", "running", "waiting"].includes(run.status)
|
|
164
|
+
? "Checking"
|
|
165
|
+
: undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
112
168
|
function outcomeOf(run: TimelineRun): string {
|
|
113
169
|
if (run.status === "skipped") return skipReason(run.result);
|
|
170
|
+
const logic = logicOutcome(run);
|
|
171
|
+
if (logic) return logic;
|
|
114
172
|
if (run.status === "sent") {
|
|
115
173
|
if (run.type === "wait") return "Waited";
|
|
116
174
|
if (run.type === "linkedin_visit") return "Visited";
|
|
@@ -153,6 +211,11 @@ export function timelineSteps(result: TimelineResult): TimelineStep[] {
|
|
|
153
211
|
run.status === "sent" || run.status === "skipped"
|
|
154
212
|
? undefined
|
|
155
213
|
: (run.lastError ?? email?.lastError)?.trim();
|
|
214
|
+
const branch = branchOf(run);
|
|
215
|
+
const waitingForAccept =
|
|
216
|
+
run.type === "wait_for_connection" &&
|
|
217
|
+
run.status === "waiting" &&
|
|
218
|
+
typeof run.timeoutAt === "number";
|
|
156
219
|
return {
|
|
157
220
|
step: (step?.order ?? run.stepIndex) + 1,
|
|
158
221
|
stepId: String(run.stepId),
|
|
@@ -167,6 +230,8 @@ export function timelineSteps(result: TimelineResult): TimelineStep[] {
|
|
|
167
230
|
run.scheduledAt ??
|
|
168
231
|
null,
|
|
169
232
|
detail,
|
|
233
|
+
...(branch ? { branch } : {}),
|
|
234
|
+
...(waitingForAccept ? { acceptanceDeadline: run.timeoutAt } : {}),
|
|
170
235
|
...(email?.subject ? { subject: email.subject } : {}),
|
|
171
236
|
...(text ? { text } : {}),
|
|
172
237
|
...(problem ? { error: problem } : {}),
|
package/lib/command-args.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliArgError, missingFlagValue } from "./cli-args";
|
|
2
|
+
import { FILE_COMMAND_ARGUMENTS } from "./file-command-contract";
|
|
2
3
|
|
|
3
4
|
type PositionalArity = readonly [min: number, max: number | null];
|
|
4
5
|
|
|
@@ -8,6 +9,10 @@ type CommandArguments = {
|
|
|
8
9
|
switches: string[];
|
|
9
10
|
positionals: PositionalArity;
|
|
10
11
|
flagPositionals: Record<string, PositionalArity>;
|
|
12
|
+
usage?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
examples?: string[];
|
|
15
|
+
notes?: string[];
|
|
11
16
|
};
|
|
12
17
|
const spec = (
|
|
13
18
|
command: string,
|
|
@@ -138,9 +143,7 @@ export const COMMAND_ARGUMENTS = [
|
|
|
138
143
|
...["delete", "enable", "disable"].map((verb) =>
|
|
139
144
|
spec(`cron ${verb}`, [1, 1]),
|
|
140
145
|
),
|
|
141
|
-
|
|
142
|
-
spec("files mv", [2, 2]),
|
|
143
|
-
spec("files write", [1, 1]),
|
|
146
|
+
...FILE_COMMAND_ARGUMENTS,
|
|
144
147
|
spec("stage", [1, 2], "", "--clean"),
|
|
145
148
|
...["identity", "organisation", "user", "tools", "mem"].flatMap((group) =>
|
|
146
149
|
["edit", "set"].map((verb) =>
|
|
@@ -200,16 +203,30 @@ export const COMMAND_ARGUMENTS = [
|
|
|
200
203
|
browserSpec("stop", [0, 0]),
|
|
201
204
|
];
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const args = argv
|
|
206
|
-
.slice(0, boundary < 0 ? undefined : boundary)
|
|
207
|
-
.filter((arg) => arg !== "--json");
|
|
206
|
+
/** Keep compatibility aliases identical in validation and local discovery. */
|
|
207
|
+
export function normalizeCommandAlias(argv: readonly string[]): string[] {
|
|
208
|
+
const args = [...argv];
|
|
208
209
|
if (["routine", "routines"].includes(args[0])) args[0] = "cron";
|
|
209
210
|
if (["ad", "adline"].includes(args[0])) args[0] = "ads";
|
|
210
|
-
if (args[0] === "agent" && ["new", "send"].includes(args[1]))
|
|
211
|
+
if (args[0] === "agent" && ["new", "send", "follow"].includes(args[1]))
|
|
211
212
|
args[0] = "chat";
|
|
212
213
|
if (args[0] === "crm" && args[1] === "members") args.shift();
|
|
214
|
+
if (args[0] === "campaigns") args[0] = "campaign";
|
|
215
|
+
return args;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function assertDeclaredCommandFlags(argv: readonly string[]): void {
|
|
219
|
+
const boundary = argv.indexOf("--");
|
|
220
|
+
const args = normalizeCommandAlias(
|
|
221
|
+
argv
|
|
222
|
+
.slice(0, boundary < 0 ? undefined : boundary)
|
|
223
|
+
.filter((arg) => arg !== "--json"),
|
|
224
|
+
);
|
|
225
|
+
if (args[0] === "files" && boundary >= 0) {
|
|
226
|
+
throw new CliArgError(
|
|
227
|
+
"dench files does not accept a child-command separator (--). Run `dench files --help` for usage.",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
213
230
|
const contract = COMMAND_ARGUMENTS.find(({ command }) =>
|
|
214
231
|
command.split(" ").every((part, index) => args[index] === part),
|
|
215
232
|
);
|
package/lib/command-help.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import { COMMAND_ARGUMENTS } from "./command-args";
|
|
1
|
+
import { COMMAND_ARGUMENTS, normalizeCommandAlias } from "./command-args";
|
|
2
2
|
import { apiOperations } from "./command-registry";
|
|
3
|
+
import {
|
|
4
|
+
FILE_COMMAND_ARGUMENTS,
|
|
5
|
+
FILE_COMMAND_NOTES,
|
|
6
|
+
} from "./file-command-contract";
|
|
3
7
|
|
|
4
|
-
/** Literal command words only;
|
|
8
|
+
/** Literal command words only; placeholders and quoted data are not commands. */
|
|
5
9
|
function words(usage: string): string[] {
|
|
6
10
|
const parts = usage
|
|
7
11
|
.replace(/^dench\s+/, "")
|
|
8
12
|
.split(/\s+/)
|
|
9
13
|
.filter(Boolean);
|
|
10
|
-
const end = parts.findIndex((part) => /^[<[(
|
|
14
|
+
const end = parts.findIndex((part) => /^[<[('"|-]/.test(part));
|
|
11
15
|
return end < 0 ? parts : parts.slice(0, end);
|
|
12
16
|
}
|
|
13
|
-
|
|
17
|
+
/** Resolve only actual CLI aliases, while callers retain the requested spelling. */
|
|
18
|
+
export function normalizeHelpCommand(argv: readonly string[]): string[] {
|
|
19
|
+
return normalizeCommandAlias(argv.filter((arg) => arg !== "help"));
|
|
20
|
+
}
|
|
14
21
|
function matches(
|
|
15
22
|
prefix: readonly string[],
|
|
16
23
|
candidate: readonly string[],
|
|
@@ -19,28 +26,127 @@ function matches(
|
|
|
19
26
|
candidate[index]?.split("|").includes(word),
|
|
20
27
|
);
|
|
21
28
|
}
|
|
29
|
+
type Entry = {
|
|
30
|
+
usage: string;
|
|
31
|
+
description: string;
|
|
32
|
+
rank: number;
|
|
33
|
+
examples?: string[];
|
|
34
|
+
notes?: string[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Even a dispatcher with no richer usage must disclose required arguments. */
|
|
38
|
+
export function argumentUsage(
|
|
39
|
+
entry: (typeof COMMAND_ARGUMENTS)[number],
|
|
40
|
+
): string {
|
|
41
|
+
if (entry.usage) return entry.usage;
|
|
42
|
+
const [min, max] = entry.positionals;
|
|
43
|
+
const count = max ?? min;
|
|
44
|
+
const positional = Array.from({ length: count }, (_, i) => {
|
|
45
|
+
const name = count === 1 ? "argument" : `argument${i + 1}`;
|
|
46
|
+
return i < min ? `<${name}>` : `[<${name}>]`;
|
|
47
|
+
});
|
|
48
|
+
if (max === null) positional.push("[arguments...]");
|
|
49
|
+
return [
|
|
50
|
+
`dench ${entry.command}`,
|
|
51
|
+
...positional,
|
|
52
|
+
...entry.values.map((flag) => `[${flag} <value>]`),
|
|
53
|
+
...entry.switches.map((flag) => `[${flag}]`),
|
|
54
|
+
].join(" ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nativeFileUsage(usage: string): boolean {
|
|
58
|
+
const path = words(usage);
|
|
59
|
+
return (
|
|
60
|
+
path[0] !== "files" ||
|
|
61
|
+
path.length < 2 ||
|
|
62
|
+
FILE_COMMAND_ARGUMENTS.some((entry) =>
|
|
63
|
+
matches(path.slice(0, 2), entry.command.split(" ")),
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Prefer richer syntax PER command, never suppress uncovered sibling commands. */
|
|
69
|
+
function preferredEntries(entries: Entry[]): Entry[] {
|
|
70
|
+
const groups = new Map<string, Entry[]>();
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
const key = words(entry.usage).join(" ");
|
|
73
|
+
if (!key || !nativeFileUsage(entry.usage)) continue;
|
|
74
|
+
const existing = groups.get(key) ?? [];
|
|
75
|
+
const rank = existing[0]?.rank ?? -1;
|
|
76
|
+
if (entry.rank > rank) groups.set(key, [entry]);
|
|
77
|
+
else if (
|
|
78
|
+
entry.rank === rank &&
|
|
79
|
+
!existing.some((item) => item.usage === entry.usage)
|
|
80
|
+
) {
|
|
81
|
+
groups.set(key, [...existing, entry]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...groups.values()].flat();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stripUsageComment(line: string): string {
|
|
88
|
+
let quote = "";
|
|
89
|
+
for (let i = 0; i < line.length; i++) {
|
|
90
|
+
const char = line[i];
|
|
91
|
+
if (char === "\\" && quote !== "'") {
|
|
92
|
+
i++;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (char === quote) quote = "";
|
|
96
|
+
else if (!quote && (char === "'" || char === '"')) quote = char;
|
|
97
|
+
else if (!quote && char === "#" && /\s/.test(line[i - 1] ?? ""))
|
|
98
|
+
return line.slice(0, i).trim();
|
|
99
|
+
}
|
|
100
|
+
return line.trim();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Preserve immediately following option lines without turning prose into syntax. */
|
|
104
|
+
function usageEntries(text: string): Entry[] {
|
|
105
|
+
const entries: Entry[] = [];
|
|
106
|
+
let current: Entry | undefined;
|
|
107
|
+
let indent = 0;
|
|
108
|
+
for (const line of text.split("\n")) {
|
|
109
|
+
const value = line.trim();
|
|
110
|
+
const depth = line.length - line.trimStart().length;
|
|
111
|
+
if (value.startsWith("dench ")) {
|
|
112
|
+
current = {
|
|
113
|
+
usage: stripUsageComment(value).replace(/\\$/, "").trim(),
|
|
114
|
+
description: "",
|
|
115
|
+
rank: 3,
|
|
116
|
+
};
|
|
117
|
+
entries.push(current);
|
|
118
|
+
indent = depth;
|
|
119
|
+
} else if (current && depth > indent && /^(?:\[?-{1,2}|<)/.test(value)) {
|
|
120
|
+
current.usage += ` ${stripUsageComment(value).replace(/\\$/, "").trim()}`;
|
|
121
|
+
} else {
|
|
122
|
+
current = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return entries;
|
|
126
|
+
}
|
|
22
127
|
|
|
23
128
|
/** Registry metadata supplies discovery, never invokes command implementations. */
|
|
24
129
|
export function commandHelp(argv: readonly string[], usageText = ""): string {
|
|
25
130
|
const args = [...argv];
|
|
26
131
|
if (args[0] === "help") args.shift();
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
132
|
+
const requested = words(args.filter((arg) => arg !== "help").join(" "));
|
|
133
|
+
const input = normalizeHelpCommand(requested);
|
|
134
|
+
const aliased = input.join(" ") !== requested.join(" ");
|
|
135
|
+
if (input[0] === "files" && input[1] === "mkdir") {
|
|
136
|
+
return "dench files mkdir is not a native CLI command. Directory creation is available as files.mkdir through the API/SDK. Run dench files --help for supported file commands.";
|
|
137
|
+
}
|
|
138
|
+
const candidates: Entry[] = [
|
|
139
|
+
...usageEntries(usageText),
|
|
34
140
|
...COMMAND_ARGUMENTS.map((entry) => ({
|
|
35
|
-
usage:
|
|
36
|
-
|
|
37
|
-
|
|
141
|
+
usage: argumentUsage(entry),
|
|
142
|
+
description: entry.description ?? "",
|
|
143
|
+
rank: entry.usage ? 4 : 2,
|
|
144
|
+
examples: entry.examples,
|
|
145
|
+
notes: entry.notes,
|
|
38
146
|
})),
|
|
39
147
|
...apiOperations
|
|
40
148
|
.filter((op) => {
|
|
41
149
|
if (!op.deprecated) return true;
|
|
42
|
-
// Keep exact legacy leaf help available without adding those commands
|
|
43
|
-
// back to the broad index or a parent family's discovery.
|
|
44
150
|
const command = words(op.cli);
|
|
45
151
|
return (
|
|
46
152
|
command.length > 1 &&
|
|
@@ -53,41 +159,88 @@ export function commandHelp(argv: readonly string[], usageText = ""): string {
|
|
|
53
159
|
description: op.deprecated
|
|
54
160
|
? `Compatibility command. ${op.summary}`
|
|
55
161
|
: op.summary,
|
|
162
|
+
rank: 1,
|
|
56
163
|
})),
|
|
57
164
|
];
|
|
165
|
+
const preferred = preferredEntries(candidates);
|
|
166
|
+
if (!input.length) {
|
|
167
|
+
const families = new Map<string, Set<string>>();
|
|
168
|
+
for (const entry of preferred) {
|
|
169
|
+
const path = words(entry.usage);
|
|
170
|
+
if (!path.length) continue;
|
|
171
|
+
const set = families.get(path[0]) ?? new Set<string>();
|
|
172
|
+
set.add(path.join(" "));
|
|
173
|
+
families.set(path[0], set);
|
|
174
|
+
}
|
|
175
|
+
return [
|
|
176
|
+
"Dench CLI",
|
|
177
|
+
"Usage: dench <command> [arguments] [options]",
|
|
178
|
+
"",
|
|
179
|
+
"Commands:",
|
|
180
|
+
...[...families]
|
|
181
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
182
|
+
.map(
|
|
183
|
+
([name, paths]) =>
|
|
184
|
+
` ${name}${paths.size > 1 ? ` (${paths.size} commands)` : ""}`,
|
|
185
|
+
),
|
|
186
|
+
"",
|
|
187
|
+
"Run dench <command> --help or dench <command> <subcommand> --help for syntax and examples.",
|
|
188
|
+
"Connect: dench signin. Inspect the workspace: dench context --json.",
|
|
189
|
+
"--json selects structured results. --help and -h run locally without authentication.",
|
|
190
|
+
"Backend overrides: --prod, --staging, --backend <name>, --host <url>.",
|
|
191
|
+
"Versioned references: dench reference --list --json.",
|
|
192
|
+
].join("\n");
|
|
193
|
+
}
|
|
58
194
|
let prefix = input;
|
|
59
195
|
while (
|
|
60
196
|
prefix.length &&
|
|
61
|
-
!
|
|
62
|
-
)
|
|
197
|
+
!preferred.some((item) => matches(prefix, words(item.usage)))
|
|
198
|
+
)
|
|
63
199
|
prefix = prefix.slice(0, -1);
|
|
64
|
-
|
|
65
|
-
const selected = candidates.filter((item) =>
|
|
200
|
+
const selected = preferred.filter((item) =>
|
|
66
201
|
matches(prefix, words(item.usage)),
|
|
67
202
|
);
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
203
|
+
const lines = selected.slice(0, 12);
|
|
204
|
+
const exact = lines.filter(
|
|
205
|
+
(item) => words(item.usage).length === prefix.length,
|
|
206
|
+
);
|
|
207
|
+
const contract = COMMAND_ARGUMENTS.find(
|
|
208
|
+
(entry) => entry.command === prefix.join(" "),
|
|
209
|
+
);
|
|
210
|
+
const printedUsage = lines.map((item) => item.usage).join(" ");
|
|
211
|
+
const missingFlags = contract
|
|
212
|
+
? [
|
|
213
|
+
...contract.values
|
|
214
|
+
.filter((flag) => !printedUsage.includes(flag))
|
|
215
|
+
.map((flag) => `${flag} <value>`),
|
|
216
|
+
...contract.switches.filter((flag) => !printedUsage.includes(flag)),
|
|
217
|
+
]
|
|
218
|
+
: [];
|
|
219
|
+
const displayPath = aliased
|
|
220
|
+
? requested.slice(0, prefix.length + requested.length - input.length)
|
|
221
|
+
: prefix;
|
|
78
222
|
return [
|
|
79
|
-
|
|
80
|
-
...
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
...(lines.length > 12
|
|
223
|
+
`Usage: dench ${displayPath.join(" ")}${displayPath.length ? "" : "<command>"}`,
|
|
224
|
+
...(aliased ? [`Alias of dench ${prefix.join(" ")}.`] : []),
|
|
225
|
+
...lines.map(
|
|
226
|
+
(item) =>
|
|
227
|
+
` ${item.usage}${item.description ? `\n ${item.description}` : ""}`,
|
|
228
|
+
),
|
|
229
|
+
...(selected.length > 12
|
|
87
230
|
? [
|
|
88
|
-
` ${
|
|
231
|
+
` ${selected.length - 12} more commands; add a subcommand before --help.`,
|
|
89
232
|
]
|
|
90
233
|
: []),
|
|
234
|
+
...(missingFlags.length
|
|
235
|
+
? [`Additional options: ${missingFlags.join(", ")}`]
|
|
236
|
+
: []),
|
|
237
|
+
...exact.flatMap((item) => item.notes ?? []),
|
|
238
|
+
...(prefix.length === 1 && prefix[0] === "files" ? FILE_COMMAND_NOTES : []),
|
|
239
|
+
...exact.flatMap((item) =>
|
|
240
|
+
item.examples?.length
|
|
241
|
+
? ["Examples:", ...item.examples.map((value) => ` ${value}`)]
|
|
242
|
+
: [],
|
|
243
|
+
),
|
|
91
244
|
"Options: --help, -h (local help); --json (structured command results).",
|
|
92
245
|
].join("\n");
|
|
93
246
|
}
|
package/lib/command-registry.ts
CHANGED
|
@@ -4183,7 +4183,7 @@ const rawApiOperations = [
|
|
|
4183
4183
|
id: "campaigns.analytics",
|
|
4184
4184
|
group: "campaigns",
|
|
4185
4185
|
summary:
|
|
4186
|
-
"Campaign funnel: enrolled, contacted, invited, accepted, messaged, replied, opens and clicks.",
|
|
4186
|
+
"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).",
|
|
4187
4187
|
cli: "dench campaign get",
|
|
4188
4188
|
method: "GET",
|
|
4189
4189
|
path: "/campaigns/{sequenceId}/analytics",
|
|
@@ -4237,7 +4237,7 @@ const rawApiOperations = [
|
|
|
4237
4237
|
id: "campaigns.steps.replace",
|
|
4238
4238
|
group: "campaigns",
|
|
4239
4239
|
summary:
|
|
4240
|
-
"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
|
|
4240
|
+
"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.",
|
|
4241
4241
|
cli: "dench campaign steps set",
|
|
4242
4242
|
method: "PUT",
|
|
4243
4243
|
path: "/campaigns/{sequenceId}/steps",
|
|
@@ -4249,7 +4249,7 @@ const rawApiOperations = [
|
|
|
4249
4249
|
id: "campaigns.steps.update",
|
|
4250
4250
|
group: "campaigns",
|
|
4251
4251
|
summary:
|
|
4252
|
-
"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.",
|
|
4252
|
+
"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.",
|
|
4253
4253
|
cli: "dench campaign steps edit",
|
|
4254
4254
|
method: "PATCH",
|
|
4255
4255
|
path: "/campaigns/{sequenceId}/steps",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** Native file commands implemented by cli/dench.ts. API operations are not
|
|
2
|
+
* necessarily CLI commands: files.mkdir is available only through the API/SDK. */
|
|
3
|
+
type FileCommandArguments = {
|
|
4
|
+
command: string;
|
|
5
|
+
positionals: readonly [number, number];
|
|
6
|
+
values: string[];
|
|
7
|
+
switches: string[];
|
|
8
|
+
flagPositionals: Record<string, readonly [number, number | null]>;
|
|
9
|
+
usage: string;
|
|
10
|
+
description: string;
|
|
11
|
+
examples: string[];
|
|
12
|
+
notes: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const FILE_COMMAND_ARGUMENTS: FileCommandArguments[] = [
|
|
16
|
+
{
|
|
17
|
+
command: "files ls",
|
|
18
|
+
positionals: [0, 1] as const,
|
|
19
|
+
values: [],
|
|
20
|
+
switches: ["--recursive", "-r"],
|
|
21
|
+
flagPositionals: {},
|
|
22
|
+
usage: "dench files ls [<path>] [--recursive|-r] [--json]",
|
|
23
|
+
description: "List workspace files; the default path is /.",
|
|
24
|
+
examples: ["dench files ls /reports --recursive --json"],
|
|
25
|
+
notes: ["--recursive (or -r) includes descendants."],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
command: "files cat",
|
|
29
|
+
positionals: [1, 1] as const,
|
|
30
|
+
values: [],
|
|
31
|
+
switches: [],
|
|
32
|
+
flagPositionals: {},
|
|
33
|
+
usage: "dench files cat <path> [--json]",
|
|
34
|
+
description: "Read a workspace file's inline text.",
|
|
35
|
+
examples: ["dench files cat /reports/summary.md"],
|
|
36
|
+
notes: [
|
|
37
|
+
"Use cat for virtual files such as IDENTITY.md and ORGANISATION.md, which have no download blob.",
|
|
38
|
+
"For binary files or files without inline text, use files download.",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
command: "files write",
|
|
43
|
+
positionals: [1, 1] as const,
|
|
44
|
+
values: [],
|
|
45
|
+
switches: [],
|
|
46
|
+
flagPositionals: {},
|
|
47
|
+
usage: "dench files write <path> [--json]",
|
|
48
|
+
description: "Write a workspace file's text from stdin.",
|
|
49
|
+
examples: [
|
|
50
|
+
"dench files write /reports/summary.md --json < ./summary.md",
|
|
51
|
+
"printf '%s\\n' 'Report ready.' | dench files write /reports/summary.md --json",
|
|
52
|
+
],
|
|
53
|
+
notes: [
|
|
54
|
+
"The destination path is required. Pipe or redirect the raw UTF-8 file text into stdin.",
|
|
55
|
+
"--json controls the result format; stdin is file content, not an API JSON envelope. No --stdin, --content or --file flag is supported.",
|
|
56
|
+
"Writing replaces the complete text; empty stdin writes an empty file.",
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
command: "files mv",
|
|
61
|
+
positionals: [2, 2] as const,
|
|
62
|
+
values: [],
|
|
63
|
+
switches: [],
|
|
64
|
+
flagPositionals: {},
|
|
65
|
+
usage: "dench files mv <src> <dst> [--json]",
|
|
66
|
+
description: "Move or rename a workspace file or directory.",
|
|
67
|
+
examples: ["dench files mv /reports/draft.md /reports/summary.md --json"],
|
|
68
|
+
notes: ["Both paths refer to workspace storage, not local disk."],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
command: "files rm",
|
|
72
|
+
positionals: [1, 1] as const,
|
|
73
|
+
values: [],
|
|
74
|
+
switches: ["--recursive", "-r"],
|
|
75
|
+
flagPositionals: {},
|
|
76
|
+
usage: "dench files rm <path> [--recursive|-r] [--json]",
|
|
77
|
+
description: "Remove a workspace file or directory.",
|
|
78
|
+
examples: ["dench files rm /reports/old --recursive --json"],
|
|
79
|
+
notes: [
|
|
80
|
+
"Directories require --recursive (or -r). An absent path is already removed.",
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
command: "files download",
|
|
85
|
+
positionals: [1, 2] as const,
|
|
86
|
+
values: [],
|
|
87
|
+
switches: ["--url"],
|
|
88
|
+
flagPositionals: {},
|
|
89
|
+
usage: "dench files download <path> [<local-dest>] [--url] [--json]",
|
|
90
|
+
description:
|
|
91
|
+
"Download workspace bytes to local disk, or print a signed URL.",
|
|
92
|
+
examples: [
|
|
93
|
+
"dench files download /reports/summary.pdf ./summary.pdf --json",
|
|
94
|
+
"dench files download /reports/summary.pdf --url --json",
|
|
95
|
+
],
|
|
96
|
+
notes: [
|
|
97
|
+
"Without --url, downloads directories recursively and defaults the local destination to the path's basename (/ becomes workspace).",
|
|
98
|
+
"--url prints a short-lived signed URL for one stored file instead of saving locally; local-dest is unused in this mode.",
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
export const FILE_COMMAND_NOTES = [
|
|
104
|
+
"Workspace paths are POSIX paths under /workspace; /reports/a.md and /workspace/reports/a.md address the same file.",
|
|
105
|
+
"These commands read or update workspace storage. Use dench stage to upload local files or directories.",
|
|
106
|
+
];
|