@dench.com/cli 3.8.3-staging.398 → 3.8.5-staging.406
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/dench.ts +41 -20
- package/lib/command-args.ts +13 -1
- package/lib/invocation-observation.ts +260 -0
- package/package.json +1 -1
package/dench.ts
CHANGED
|
@@ -31,6 +31,10 @@ import { CliArgError, isHelpRequest } from "./lib/cli-args";
|
|
|
31
31
|
import { cliVersion } from "./lib/cli-version";
|
|
32
32
|
import { assertDeclaredCommandFlags } from "./lib/command-args";
|
|
33
33
|
import { denchConfigDirectory } from "./lib/config-path.mjs";
|
|
34
|
+
import {
|
|
35
|
+
beginCliInvocation,
|
|
36
|
+
finishCliInvocation,
|
|
37
|
+
} from "./lib/invocation-observation";
|
|
34
38
|
import { resolveOrganizationBySelector } from "./lib/organizationSelector";
|
|
35
39
|
import { normalizeParentArgv } from "./lib/parent-argv";
|
|
36
40
|
import { backendErrorText, publicErrorDetails } from "./lib/public-error";
|
|
@@ -220,6 +224,7 @@ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
|
220
224
|
// A child process owns every token after `--`, including --host and --json.
|
|
221
225
|
// Normalize only Dench's parent arguments; pass child argv through byte for byte.
|
|
222
226
|
const { args, runtimeArgs } = normalizeParentArgv(process.argv.slice(2));
|
|
227
|
+
const cliInvocation = beginCliInvocation(runtimeArgs);
|
|
223
228
|
const json = runtimeArgs.includes("--json");
|
|
224
229
|
const filteredArgs = runtimeArgs.filter((arg) => arg !== "--json");
|
|
225
230
|
const scopedStatus =
|
|
@@ -5739,25 +5744,41 @@ async function main() {
|
|
|
5739
5744
|
throw new Error(`Unknown command: ${filteredArgs.join(" ")}`);
|
|
5740
5745
|
}
|
|
5741
5746
|
|
|
5742
|
-
main()
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
}
|
|
5753
|
-
|
|
5754
|
-
const
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5747
|
+
main()
|
|
5748
|
+
.then(() => {
|
|
5749
|
+
const exitCode =
|
|
5750
|
+
typeof process.exitCode === "number"
|
|
5751
|
+
? process.exitCode
|
|
5752
|
+
: Number(process.exitCode ?? 0);
|
|
5753
|
+
finishCliInvocation(cliInvocation, {
|
|
5754
|
+
exitCode,
|
|
5755
|
+
...(exitCode === 0 ? {} : { errorType: "child_exit_nonzero" }),
|
|
5756
|
+
});
|
|
5757
|
+
})
|
|
5758
|
+
.catch((error) => {
|
|
5759
|
+
const normalized = normalizeCliError(error);
|
|
5760
|
+
const extra = normalized.payload;
|
|
5761
|
+
const payload: JsonRecord = {
|
|
5762
|
+
ok: false,
|
|
5763
|
+
...extra,
|
|
5764
|
+
error: normalized.message,
|
|
5765
|
+
};
|
|
5766
|
+
if (json) {
|
|
5767
|
+
console.error(JSON.stringify(payload, null, 2));
|
|
5768
|
+
} else {
|
|
5769
|
+
console.error(`Error: ${payload.error}`);
|
|
5770
|
+
const nextActions = stringArrayField(payload, "nextActions");
|
|
5771
|
+
if (nextActions.length > 0) {
|
|
5772
|
+
console.error("Next actions:");
|
|
5773
|
+
for (const action of nextActions) {
|
|
5774
|
+
console.error(` - ${action}`);
|
|
5775
|
+
}
|
|
5759
5776
|
}
|
|
5760
5777
|
}
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5778
|
+
finishCliInvocation(cliInvocation, {
|
|
5779
|
+
exitCode: 1,
|
|
5780
|
+
errorType: normalized.payload.code,
|
|
5781
|
+
retryable: normalized.payload.retryable,
|
|
5782
|
+
});
|
|
5783
|
+
process.exit(1);
|
|
5784
|
+
});
|
package/lib/command-args.ts
CHANGED
|
@@ -144,7 +144,19 @@ export const COMMAND_ARGUMENTS = [
|
|
|
144
144
|
spec(`cron ${verb}`, [1, 1]),
|
|
145
145
|
),
|
|
146
146
|
...FILE_COMMAND_ARGUMENTS,
|
|
147
|
-
|
|
147
|
+
{
|
|
148
|
+
...spec("stage", [1, 2], "", "--clean"),
|
|
149
|
+
usage: "dench stage <local-path> [<workspace-dest>] [--clean] [--json]",
|
|
150
|
+
description:
|
|
151
|
+
"Upload a local file or directory into Dench workspace storage.",
|
|
152
|
+
examples: ["dench stage /tmp/report.csv /reports/report.csv --json"],
|
|
153
|
+
notes: [
|
|
154
|
+
"The local source must be outside /workspace. Create scratch files under /tmp before staging them.",
|
|
155
|
+
"The destination is a saved workspace path, not a directory to create on local disk. It defaults to /<source-basename>.",
|
|
156
|
+
"--clean deletes existing workspace files under the destination before uploading.",
|
|
157
|
+
"To save text directly from stdin, use dench files write <workspace-path> --json.",
|
|
158
|
+
],
|
|
159
|
+
},
|
|
148
160
|
...["identity", "organisation", "user", "tools", "mem"].flatMap((group) =>
|
|
149
161
|
["edit", "set"].map((verb) =>
|
|
150
162
|
spec(`${group} ${verb}`, [0, 0], "--content", "--stdin"),
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFileSync, statSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
export type CliInvocationObservation = {
|
|
5
|
+
version: 1;
|
|
6
|
+
id: string;
|
|
7
|
+
tool: string;
|
|
8
|
+
startedAt: number;
|
|
9
|
+
endedAt: number;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
exitCode: number;
|
|
12
|
+
errorType?: string;
|
|
13
|
+
retryable?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const SAFE_FILE =
|
|
17
|
+
/^\/tmp\/dench-harness-a\/[a-f0-9-]{36}\/workspace\/\.dench\/observations\/[a-zA-Z0-9._-]+\.jsonl$/;
|
|
18
|
+
const SAFE_NAME = /^[a-z][a-z0-9_.-]{0,119}$/;
|
|
19
|
+
const SIDECAR_LIMIT = 65_536;
|
|
20
|
+
const KNOWN_ROOTS = new Set([
|
|
21
|
+
"ads",
|
|
22
|
+
"agent",
|
|
23
|
+
"agents",
|
|
24
|
+
"approval",
|
|
25
|
+
"approvals",
|
|
26
|
+
"artifacts",
|
|
27
|
+
"backend",
|
|
28
|
+
"billing",
|
|
29
|
+
"browser",
|
|
30
|
+
"campaign",
|
|
31
|
+
"chat",
|
|
32
|
+
"companies",
|
|
33
|
+
"context",
|
|
34
|
+
"create",
|
|
35
|
+
"crm",
|
|
36
|
+
"cron",
|
|
37
|
+
"email",
|
|
38
|
+
"env",
|
|
39
|
+
"files",
|
|
40
|
+
"fs",
|
|
41
|
+
"help",
|
|
42
|
+
"identity",
|
|
43
|
+
"image",
|
|
44
|
+
"instructions",
|
|
45
|
+
"integrations",
|
|
46
|
+
"jobs",
|
|
47
|
+
"leads",
|
|
48
|
+
"lists",
|
|
49
|
+
"logout",
|
|
50
|
+
"meetings",
|
|
51
|
+
"members",
|
|
52
|
+
"mem",
|
|
53
|
+
"memory",
|
|
54
|
+
"model",
|
|
55
|
+
"notifications",
|
|
56
|
+
"onboard",
|
|
57
|
+
"organisation",
|
|
58
|
+
"project",
|
|
59
|
+
"reference",
|
|
60
|
+
"register",
|
|
61
|
+
"request",
|
|
62
|
+
"secrets",
|
|
63
|
+
"sessions",
|
|
64
|
+
"signin",
|
|
65
|
+
"status",
|
|
66
|
+
"settings",
|
|
67
|
+
"setup",
|
|
68
|
+
"suggested-work",
|
|
69
|
+
"tool",
|
|
70
|
+
"tools",
|
|
71
|
+
"upgrade",
|
|
72
|
+
"use",
|
|
73
|
+
"user",
|
|
74
|
+
"version",
|
|
75
|
+
"web",
|
|
76
|
+
"what-can-i-do",
|
|
77
|
+
]);
|
|
78
|
+
const KNOWN_SUBCOMMANDS = new Set([
|
|
79
|
+
"add",
|
|
80
|
+
"approve",
|
|
81
|
+
"archive",
|
|
82
|
+
"cancel",
|
|
83
|
+
"clear",
|
|
84
|
+
"clone",
|
|
85
|
+
"collapse",
|
|
86
|
+
"complete",
|
|
87
|
+
"compute",
|
|
88
|
+
"configure",
|
|
89
|
+
"create",
|
|
90
|
+
"create-many",
|
|
91
|
+
"create-subtask",
|
|
92
|
+
"delete",
|
|
93
|
+
"disable",
|
|
94
|
+
"draft",
|
|
95
|
+
"edit",
|
|
96
|
+
"enable",
|
|
97
|
+
"expand",
|
|
98
|
+
"export",
|
|
99
|
+
"follow",
|
|
100
|
+
"get",
|
|
101
|
+
"help",
|
|
102
|
+
"hide",
|
|
103
|
+
"history",
|
|
104
|
+
"import",
|
|
105
|
+
"info",
|
|
106
|
+
"install",
|
|
107
|
+
"list",
|
|
108
|
+
"me",
|
|
109
|
+
"mine",
|
|
110
|
+
"move",
|
|
111
|
+
"new",
|
|
112
|
+
"parent",
|
|
113
|
+
"pause",
|
|
114
|
+
"poll",
|
|
115
|
+
"presence",
|
|
116
|
+
"preview",
|
|
117
|
+
"priority",
|
|
118
|
+
"query",
|
|
119
|
+
"read",
|
|
120
|
+
"register",
|
|
121
|
+
"reject",
|
|
122
|
+
"remove",
|
|
123
|
+
"rename",
|
|
124
|
+
"reopen",
|
|
125
|
+
"reorder",
|
|
126
|
+
"repeat",
|
|
127
|
+
"resize",
|
|
128
|
+
"resume",
|
|
129
|
+
"roles",
|
|
130
|
+
"run",
|
|
131
|
+
"save",
|
|
132
|
+
"scope",
|
|
133
|
+
"search",
|
|
134
|
+
"send",
|
|
135
|
+
"series",
|
|
136
|
+
"set",
|
|
137
|
+
"show",
|
|
138
|
+
"status",
|
|
139
|
+
"stop",
|
|
140
|
+
"subtasks",
|
|
141
|
+
"sync",
|
|
142
|
+
"tree",
|
|
143
|
+
"unarchive",
|
|
144
|
+
"uninstall",
|
|
145
|
+
"update",
|
|
146
|
+
"validate",
|
|
147
|
+
"watch",
|
|
148
|
+
]);
|
|
149
|
+
const NESTED_NAMESPACES = new Set([
|
|
150
|
+
"actions",
|
|
151
|
+
"analytics",
|
|
152
|
+
"builder",
|
|
153
|
+
"cells",
|
|
154
|
+
"docs",
|
|
155
|
+
"drafts",
|
|
156
|
+
"entries",
|
|
157
|
+
"fields",
|
|
158
|
+
"inbox",
|
|
159
|
+
"objects",
|
|
160
|
+
"reports",
|
|
161
|
+
"sidebar",
|
|
162
|
+
"sources",
|
|
163
|
+
"statuses",
|
|
164
|
+
"tasks",
|
|
165
|
+
"transactions",
|
|
166
|
+
"views",
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
function words(argv: readonly string[]) {
|
|
170
|
+
const result: string[] = [];
|
|
171
|
+
for (let index = 0; index < argv.length; index++) {
|
|
172
|
+
const value = argv[index];
|
|
173
|
+
if (!value || value === "--") break;
|
|
174
|
+
if (value.startsWith("-")) {
|
|
175
|
+
if (!["--json", "--help", "-h", "--staging", "--prod"].includes(value))
|
|
176
|
+
index++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
result.push(value.toLowerCase().replace(/[^a-z0-9_-]/g, "-"));
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function normalizedCliTool(argv: readonly string[]) {
|
|
185
|
+
const [root = "unknown", ...rest] = words(argv);
|
|
186
|
+
const canonicalRoot =
|
|
187
|
+
root === "campaigns"
|
|
188
|
+
? "campaign"
|
|
189
|
+
: root === "routine" || root === "routines"
|
|
190
|
+
? "cron"
|
|
191
|
+
: root === "ad" || root === "adline"
|
|
192
|
+
? "ads"
|
|
193
|
+
: root;
|
|
194
|
+
const parts = [
|
|
195
|
+
"dench",
|
|
196
|
+
KNOWN_ROOTS.has(canonicalRoot) ? canonicalRoot : "unknown",
|
|
197
|
+
];
|
|
198
|
+
if (KNOWN_SUBCOMMANDS.has(rest[0]) || NESTED_NAMESPACES.has(rest[0]))
|
|
199
|
+
parts.push(rest[0]);
|
|
200
|
+
if (NESTED_NAMESPACES.has(rest[0]) && KNOWN_SUBCOMMANDS.has(rest[1]))
|
|
201
|
+
parts.push(rest[1]);
|
|
202
|
+
const name = parts.join(".");
|
|
203
|
+
return SAFE_NAME.test(name) ? name : "dench.unknown";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function safeErrorType(value: unknown) {
|
|
207
|
+
const raw = typeof value === "string" ? value.toLowerCase() : "cli_error";
|
|
208
|
+
return SAFE_NAME.test(raw) ? raw : "cli_error";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function beginCliInvocation(
|
|
212
|
+
argv: readonly string[],
|
|
213
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
214
|
+
) {
|
|
215
|
+
const file = env.DENCH_HARNESS_OBSERVATION_FILE?.trim();
|
|
216
|
+
if (!file || !SAFE_FILE.test(file)) return null;
|
|
217
|
+
return {
|
|
218
|
+
file,
|
|
219
|
+
id: randomUUID(),
|
|
220
|
+
tool: normalizedCliTool(argv),
|
|
221
|
+
startedAt: Date.now(),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function finishCliInvocation(
|
|
226
|
+
started: ReturnType<typeof beginCliInvocation>,
|
|
227
|
+
result: { exitCode: number; errorType?: unknown; retryable?: unknown },
|
|
228
|
+
) {
|
|
229
|
+
if (!started) return;
|
|
230
|
+
const endedAt = Date.now();
|
|
231
|
+
const observation: CliInvocationObservation = {
|
|
232
|
+
version: 1,
|
|
233
|
+
id: started.id,
|
|
234
|
+
tool: started.tool,
|
|
235
|
+
startedAt: started.startedAt,
|
|
236
|
+
endedAt,
|
|
237
|
+
durationMs: Math.max(0, endedAt - started.startedAt),
|
|
238
|
+
exitCode: Number.isSafeInteger(result.exitCode) ? result.exitCode : 1,
|
|
239
|
+
...(result.exitCode === 0
|
|
240
|
+
? {}
|
|
241
|
+
: { errorType: safeErrorType(result.errorType) }),
|
|
242
|
+
...(typeof result.retryable === "boolean"
|
|
243
|
+
? { retryable: result.retryable }
|
|
244
|
+
: {}),
|
|
245
|
+
};
|
|
246
|
+
try {
|
|
247
|
+
const line = `${JSON.stringify(observation)}\n`;
|
|
248
|
+
let size = 0;
|
|
249
|
+
try {
|
|
250
|
+
size = statSync(started.file).size;
|
|
251
|
+
} catch {}
|
|
252
|
+
if (size + Buffer.byteLength(line) > SIDECAR_LIMIT) return;
|
|
253
|
+
appendFileSync(started.file, line, {
|
|
254
|
+
encoding: "utf8",
|
|
255
|
+
mode: 0o600,
|
|
256
|
+
});
|
|
257
|
+
} catch {
|
|
258
|
+
// Harness telemetry must never change CLI behavior.
|
|
259
|
+
}
|
|
260
|
+
}
|