@dench.com/cli 3.6.6-staging.386 → 3.8.0-staging.389

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "3.6.6-staging.386",
3
+ "version": "3.8.0-staging.389",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,6 @@
26
26
  "home-sync",
27
27
  "home-sync.ts",
28
28
  "home-sync-lib.ts",
29
- "agent.ts",
30
29
  "chat.ts",
31
30
  "chat-spawn.ts",
32
31
  "crm.ts",
@@ -37,6 +36,8 @@
37
36
  "crm-privacy.ts",
38
37
  "meetings.ts",
39
38
  "email.ts",
39
+ "email-inbox.ts",
40
+ "reference.ts",
40
41
  "campaign.ts",
41
42
  "members.ts",
42
43
  "notifications.ts",
@@ -58,7 +59,10 @@
58
59
  "lib/organizationSelector.ts",
59
60
  "lib/",
60
61
  "app-sdk.d.ts",
61
- "README.md"
62
+ "README.md",
63
+ "environments.ts",
64
+ "workspace-secrets.ts",
65
+ "context-settings.ts"
62
66
  ],
63
67
  "exports": {
64
68
  "./app-sdk": {
package/reference.ts ADDED
@@ -0,0 +1,47 @@
1
+ import {
2
+ bundledReferenceIndex,
3
+ getBundledReference,
4
+ referenceRevision,
5
+ } from "./lib/bundled-references";
6
+ import { assertNoUnknownFlags, CliArgError, hasFlag } from "./lib/cli-args";
7
+
8
+ export const REFERENCE_HELP = `Usage: dench reference <name> [--json]
9
+ dench reference dench Read the bundled SKILL.md
10
+ dench reference crm Read the bundled CRM reference
11
+ dench reference --list [--json] List exact bundled paths and revision
12
+ References match this installed CLI version and are read locally without signing in.`;
13
+
14
+ export async function runReferenceCommand(argv: string[]): Promise<void> {
15
+ const args = [...argv];
16
+ const json = hasFlag(args, "--json");
17
+ const list = hasFlag(args, "--list");
18
+ assertNoUnknownFlags(args, "reference");
19
+ if (list) {
20
+ if (args.length)
21
+ throw new CliArgError("--list does not accept a reference name");
22
+ const index = bundledReferenceIndex();
23
+ console.log(
24
+ json
25
+ ? JSON.stringify(index)
26
+ : `Revision: ${index.revision}\n${index.references.map((entry) => entry.path).join("\n")}`,
27
+ );
28
+ return;
29
+ }
30
+ if (args.length !== 1) throw new CliArgError(REFERENCE_HELP);
31
+ const name = args[0];
32
+ const path = /^(dench|skill(?:\.md)?)$/i.test(name)
33
+ ? "SKILL.md"
34
+ : /^[A-Za-z0-9_-]+(?:\.md)?$/i.test(name)
35
+ ? `references/${name.replace(/\.md$/i, "").toUpperCase()}.md`
36
+ : name;
37
+ const content = getBundledReference(path);
38
+ if (content === undefined)
39
+ throw new CliArgError(
40
+ `Unknown bundled reference: ${name}. Run dench reference --list.`,
41
+ );
42
+ console.log(
43
+ json
44
+ ? JSON.stringify({ path, revision: referenceRevision, content })
45
+ : content,
46
+ );
47
+ }
@@ -0,0 +1,231 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import type { ConvexHttpClient } from "convex/browser";
3
+ import { makeFunctionReference } from "convex/server";
4
+ import { CliArgError } from "./lib/cli-args";
5
+ import {
6
+ integerOption,
7
+ isEnvironmentHelp,
8
+ parseEnvironmentOptions,
9
+ requireSecretName,
10
+ } from "./lib/environment-cli-args";
11
+ import { EnvironmentOutputRedactor } from "./lib/environment-execution";
12
+
13
+ const MAX_SECRET_BYTES = 64 * 1024;
14
+ const HELP = `dench secrets — workspace-shared encrypted credentials
15
+
16
+ dench secrets set NAME --stdin [--kind env|file] [--expected-version N] [--json]
17
+ dench secrets set NAME --file PATH [--kind env|file] [--expected-version N] [--json]
18
+ dench secrets list [--json]
19
+ dench secrets delete NAME [--expected-version N] [--json]
20
+
21
+ Values are accepted only through stdin or a file. Values are never printed.
22
+ Bind a stored name in dench-env.json secretEnv or secretFiles; env run injects
23
+ only those bindings. Memory is not credential storage. --kind defaults to env.
24
+ Example: dench secrets set OPENAI_API_KEY --stdin < /private/path/key.txt`;
25
+
26
+ export function workspaceSecretsHelpText(): string {
27
+ return HELP;
28
+ }
29
+
30
+ export function validateWorkspaceSecretsArgs(input: readonly string[]) {
31
+ if (isEnvironmentHelp(input)) return { verb: "help" as const };
32
+ const verb = input[0];
33
+ if (!verb || !["set", "list", "delete"].includes(verb))
34
+ throw new CliArgError(
35
+ "Use dench secrets set, list, or delete; see --help.",
36
+ );
37
+ const opts = parseEnvironmentOptions(
38
+ input.slice(1),
39
+ verb === "set"
40
+ ? ["--file", "--kind", "--expected-version"]
41
+ : verb === "delete"
42
+ ? ["--expected-version"]
43
+ : [],
44
+ verb === "set" ? ["--stdin", "--json"] : ["--json"],
45
+ );
46
+ if (opts.child.length || opts.positional.length !== (verb === "list" ? 0 : 1))
47
+ throw new CliArgError(
48
+ `Unexpected or missing arguments for dench secrets ${verb}.`,
49
+ );
50
+ const name =
51
+ verb === "list" ? undefined : requireSecretName(opts.positional[0]);
52
+ const kind = opts.flags["--kind"] ?? "env";
53
+ if (kind !== "env" && kind !== "file")
54
+ throw new CliArgError("--kind must be env or file.");
55
+ if (
56
+ verb === "set" &&
57
+ Number(Boolean(opts.flags["--stdin"])) +
58
+ Number(Boolean(opts.flags["--file"])) !==
59
+ 1
60
+ )
61
+ throw new CliArgError(
62
+ "Use exactly one of --stdin or --file; never put secret values in command arguments.",
63
+ );
64
+ const expectedVersion =
65
+ opts.flags["--expected-version"] === undefined
66
+ ? undefined
67
+ : integerOption(
68
+ opts.flags["--expected-version"],
69
+ "--expected-version",
70
+ 0,
71
+ 0,
72
+ Number.MAX_SAFE_INTEGER,
73
+ );
74
+ return {
75
+ verb: verb as "set" | "list" | "delete",
76
+ name,
77
+ kind,
78
+ expectedVersion,
79
+ file: opts.flags["--file"] as string | undefined,
80
+ };
81
+ }
82
+
83
+ async function readSecretInput(): Promise<string> {
84
+ if (process.stdin.isTTY)
85
+ throw new CliArgError(
86
+ "Pipe the credential into stdin, or use --file. Interactive plaintext entry is not supported.",
87
+ );
88
+ const chunks: Buffer[] = [];
89
+ let bytes = 0;
90
+ for await (const chunk of process.stdin) {
91
+ const buffer = Buffer.from(chunk);
92
+ bytes += buffer.length;
93
+ if (bytes > MAX_SECRET_BYTES)
94
+ throw new CliArgError("Secret exceeds the 64 KiB limit.");
95
+ chunks.push(buffer);
96
+ }
97
+ return new TextDecoder("utf-8", { fatal: true }).decode(
98
+ Buffer.concat(chunks),
99
+ );
100
+ }
101
+
102
+ export async function runWorkspaceSecretsCommand(opts: {
103
+ convex: ConvexHttpClient;
104
+ args: string[];
105
+ sessionToken?: string;
106
+ readStdin?: () => Promise<string>;
107
+ }): Promise<void> {
108
+ const parsed = validateWorkspaceSecretsArgs(opts.args);
109
+ if (parsed.verb === "help") {
110
+ console.log(HELP);
111
+ return;
112
+ }
113
+ const token = opts.sessionToken ? { sessionToken: opts.sessionToken } : {};
114
+ if (parsed.verb === "list") {
115
+ console.log(
116
+ JSON.stringify(
117
+ await opts.convex.query(
118
+ makeFunctionReference<"query">("functions/workspaceSecrets:list"),
119
+ token,
120
+ ),
121
+ null,
122
+ 2,
123
+ ),
124
+ );
125
+ return;
126
+ }
127
+ if (parsed.verb === "delete") {
128
+ console.log(
129
+ JSON.stringify(
130
+ await opts.convex.mutation(
131
+ makeFunctionReference<"mutation">(
132
+ "functions/workspaceSecrets:remove",
133
+ ),
134
+ {
135
+ ...token,
136
+ name: parsed.name,
137
+ ...(parsed.expectedVersion === undefined
138
+ ? {}
139
+ : { expectedVersion: parsed.expectedVersion }),
140
+ },
141
+ ),
142
+ null,
143
+ 2,
144
+ ),
145
+ );
146
+ return;
147
+ }
148
+ if (parsed.file && (await stat(parsed.file)).size > MAX_SECRET_BYTES)
149
+ throw new CliArgError("Secret exceeds the 64 KiB limit.");
150
+ let value = parsed.file
151
+ ? new TextDecoder("utf-8", { fatal: true }).decode(
152
+ await readFile(parsed.file),
153
+ )
154
+ : await (opts.readStdin ?? readSecretInput)();
155
+ // Text files commonly contain a final newline; preserve credential-file bytes.
156
+ if (parsed.kind === "env") value = value.replace(/\r?\n$/, "");
157
+ if (
158
+ !value ||
159
+ Buffer.byteLength(value, "utf8") > MAX_SECRET_BYTES ||
160
+ value.includes("\0")
161
+ )
162
+ throw new CliArgError(
163
+ "Credential must be nonempty UTF-8 text without NUL, up to 64 KiB.",
164
+ );
165
+ let result: unknown;
166
+ try {
167
+ result = await opts.convex.action(
168
+ makeFunctionReference<"action">("functions/workspaceSecretsNode:set"),
169
+ {
170
+ ...token,
171
+ name: parsed.name,
172
+ kind: parsed.kind,
173
+ value,
174
+ ...(parsed.expectedVersion === undefined
175
+ ? {}
176
+ : { expectedVersion: parsed.expectedVersion }),
177
+ },
178
+ );
179
+ } catch (error) {
180
+ const redactor = new EnvironmentOutputRedactor([value]);
181
+ const data = (
182
+ error as {
183
+ data?: { code?: string; receiptId?: string; version?: number };
184
+ }
185
+ ).data;
186
+ const sanitized = new Error(
187
+ redactor.push(
188
+ error instanceof Error ? error.message : String(error),
189
+ true,
190
+ ),
191
+ );
192
+ Object.assign(sanitized, {
193
+ data: {
194
+ message: sanitized.message,
195
+ ...(data?.code ? { code: data.code } : {}),
196
+ ...(data?.receiptId ? { receiptId: data.receiptId } : {}),
197
+ ...(data?.version !== undefined ? { version: data.version } : {}),
198
+ },
199
+ });
200
+ throw sanitized;
201
+ }
202
+ // Do not print an arbitrary response shape from a credential endpoint.
203
+ const safe = result as {
204
+ name?: string;
205
+ version?: number;
206
+ kind?: string;
207
+ updatedAt?: number;
208
+ };
209
+ if (
210
+ !safe ||
211
+ !Number.isSafeInteger(safe.version) ||
212
+ (safe.version ?? 0) < 1 ||
213
+ (safe.name && safe.name !== parsed.name)
214
+ )
215
+ throw new Error(
216
+ "secret_storage_unconfirmed: Storage did not return a valid version. Inspect dench secrets list before retrying.",
217
+ );
218
+ console.log(
219
+ JSON.stringify(
220
+ {
221
+ name: safe.name ?? parsed.name,
222
+ version: safe.version,
223
+ kind: safe.kind ?? parsed.kind,
224
+ updatedAt: safe.updatedAt,
225
+ stored: true,
226
+ },
227
+ null,
228
+ 2,
229
+ ),
230
+ );
231
+ }
package/agent.ts DELETED
@@ -1,37 +0,0 @@
1
- import type { ConvexHttpClient } from "convex/browser";
2
-
3
- class AgentCliError extends Error {}
4
-
5
- function agentHelp(): void {
6
- console.log(`Usage: dench agent <subcommand>
7
-
8
- Agent sessions are normal chat-turn workflows, the same path used by the web UI:
9
- dench agent new "<prompt>" [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
10
- dench agent send <thread-id> "<message>" [--model <m>] [--yolo] [--follow] [--json]
11
- dench agent follow <thread-id> [--json]
12
-
13
- These are aliases for:
14
- dench chat new
15
- dench chat send
16
- dench chat follow
17
-
18
- Use spawn_agent from inside a running chat to create child agents. Child agents
19
- are also normal chat threads and can be followed with dench agent follow.
20
- `);
21
- }
22
-
23
- export async function runAgentCommand(_opts: {
24
- convex: ConvexHttpClient;
25
- args: string[];
26
- sessionToken?: string;
27
- callerRunId?: string;
28
- }): Promise<void> {
29
- const subcommand = _opts.args[0];
30
- if (!subcommand || subcommand === "help" || subcommand === "--help") {
31
- agentHelp();
32
- return;
33
- }
34
- throw new AgentCliError(
35
- `Unknown agent subcommand: ${subcommand}. Use dench agent new, send, or follow.`,
36
- );
37
- }