@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.
- package/README.md +43 -44
- package/agent-config.ts +5 -55
- package/app-sdk.d.ts +253 -43
- package/campaign.ts +335 -54
- package/chat-spawn.ts +11 -2
- package/chat.ts +3 -5
- package/context-settings.ts +86 -0
- package/cron.ts +19 -0
- package/dench.ts +202 -83
- package/environments.ts +694 -0
- package/lib/api-schemas.ts +190 -20
- package/lib/app-sdk-catalog.generated.ts +34 -20
- package/lib/campaign-steps.ts +474 -12
- package/lib/campaign-timeline.ts +69 -4
- package/lib/command-args.ts +19 -4
- package/lib/command-help.ts +17 -2
- package/lib/command-registry.ts +225 -11
- package/lib/environment-archive.ts +255 -0
- package/lib/environment-cli-args.ts +96 -0
- package/lib/environment-contract.ts +237 -0
- package/lib/environment-execution.ts +769 -0
- package/lib/environment-source.ts +165 -0
- package/lib/load-dev-env.mjs +3 -1
- package/lib/load-dev-env.test.mjs +26 -9
- package/lib/parent-argv.ts +20 -0
- package/lib/references.generated.json +26 -18
- package/package.json +5 -3
- package/workspace-secrets.ts +231 -0
- package/agent.ts +0 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.1-staging.390",
|
|
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",
|
|
@@ -60,7 +59,10 @@
|
|
|
60
59
|
"lib/organizationSelector.ts",
|
|
61
60
|
"lib/",
|
|
62
61
|
"app-sdk.d.ts",
|
|
63
|
-
"README.md"
|
|
62
|
+
"README.md",
|
|
63
|
+
"environments.ts",
|
|
64
|
+
"workspace-secrets.ts",
|
|
65
|
+
"context-settings.ts"
|
|
64
66
|
],
|
|
65
67
|
"exports": {
|
|
66
68
|
"./app-sdk": {
|
|
@@ -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
|
-
}
|