@dench.com/cli 3.8.4-staging.400 → 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 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().catch((error) => {
5743
- const normalized = normalizeCliError(error);
5744
- const extra = normalized.payload;
5745
- const payload: JsonRecord = {
5746
- ok: false,
5747
- ...extra,
5748
- error: normalized.message,
5749
- };
5750
- if (json) {
5751
- console.error(JSON.stringify(payload, null, 2));
5752
- } else {
5753
- console.error(`Error: ${payload.error}`);
5754
- const nextActions = stringArrayField(payload, "nextActions");
5755
- if (nextActions.length > 0) {
5756
- console.error("Next actions:");
5757
- for (const action of nextActions) {
5758
- console.error(` - ${action}`);
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
- process.exit(1);
5763
- });
5778
+ finishCliInvocation(cliInvocation, {
5779
+ exitCode: 1,
5780
+ errorType: normalized.payload.code,
5781
+ retryable: normalized.payload.retryable,
5782
+ });
5783
+ process.exit(1);
5784
+ });
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "3.8.4-staging.400",
3
+ "version": "3.8.5-staging.406",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {