@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/chat-spawn.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * (`/api/chat/[runId]/stream`) and pretty-prints text deltas + tool
17
17
  * calls until the workflow finishes.
18
18
  */
19
- import { hasFlag } from "./lib/cli-args";
19
+ import { assertNoUnknownFlags, hasFlag } from "./lib/cli-args";
20
20
 
21
21
  class ChatSpawnError extends Error {}
22
22
 
@@ -47,6 +47,10 @@ function consumeFlagValue(args: string[], name: string): string | undefined {
47
47
  const idx = args.indexOf(name);
48
48
  if (idx === -1) return undefined;
49
49
  const value = args[idx + 1];
50
+ if (!value || value.startsWith("--"))
51
+ throw new ChatSpawnError(`${name} requires a value.`);
52
+ if (args.indexOf(name, idx + 1) !== -1)
53
+ throw new ChatSpawnError(`Duplicate flag: ${name}`);
50
54
  args.splice(idx, 2);
51
55
  return value;
52
56
  }
@@ -372,17 +376,21 @@ function joinPositionalsExceptFlags(args: string[]): string {
372
376
  }
373
377
 
374
378
  async function runChatNew(ctx: ChatSpawnContext): Promise<void> {
379
+ const environment = consumeFlagValue(ctx.args, "--environment");
380
+ if (environment !== undefined && !/^[a-z][a-z0-9-]{0,63}$/.test(environment))
381
+ throw new ChatSpawnError("Invalid environment name");
375
382
  const model = consumeFlagValue(ctx.args, "--model");
376
383
  const fileContextPath = consumeFlagValue(ctx.args, "--file-context");
377
384
  const visibility = consumeFlagValue(ctx.args, "--visibility");
378
385
  const title = consumeFlagValue(ctx.args, "--title");
379
386
  const yolo = consumeFlag(ctx.args, "--yolo");
380
387
  const follow = consumeFlag(ctx.args, "--follow");
388
+ assertNoUnknownFlags(ctx.args, "chat new");
381
389
 
382
390
  const prompt = joinPositionalsExceptFlags(ctx.args);
383
391
  if (!prompt) {
384
392
  throw new ChatSpawnError(
385
- 'Usage: dench chat new "<prompt>" [--model M] [--file-context PATH] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]',
393
+ 'Usage: dench chat new "<prompt>" [--environment NAME] [--model M] [--file-context PATH] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]',
386
394
  );
387
395
  }
388
396
 
@@ -391,6 +399,7 @@ async function runChatNew(ctx: ChatSpawnContext): Promise<void> {
391
399
  ctx.runtime.bearerToken,
392
400
  {
393
401
  prompt,
402
+ ...(environment ? { environment } : {}),
394
403
  model,
395
404
  fileContextPath,
396
405
  visibility,
package/chat.ts CHANGED
@@ -145,7 +145,7 @@ PERMANENTLY delete one or more past chat threads (and all their messages):
145
145
  server REFUSES to delete them unless \`--include-automated\` is set.
146
146
 
147
147
  Spawn a new chat-turn workflow (same agent loop as the web UI):
148
- dench chat new "<prompt>" [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
148
+ dench chat new "<prompt>" [--environment NAME] [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
149
149
  dench chat send <thread-id> "<message>" [--model <m>] [--yolo] [--follow] [--json]
150
150
  dench chat follow <thread-id> [--json]
151
151
  dench chat tree [<root-thread-id>] [--limit N] [--json]
@@ -155,10 +155,8 @@ Spawn a new chat-turn workflow (same agent loop as the web UI):
155
155
  thread hierarchy from a root thread.
156
156
 
157
157
  Approval policy:
158
- Without --yolo the workflow uses the workspace policy from
159
- /<slug>/settings -> Approvals (master YOLO + per-rule toggles).
160
- --yolo overrides for this single chat turn — every approval gate is
161
- off, equivalent to flipping the org to YOLO mode for this run.
158
+ Workspace agent runs use Full access by default.
159
+ --yolo remains accepted for compatibility.
162
160
 
163
161
  Auth:
164
162
  Inside a sandbox, this just works — DENCH_API_KEY + DENCH_RUN_ID are
@@ -0,0 +1,86 @@
1
+ import type { ConvexHttpClient } from "convex/browser";
2
+ import { makeFunctionReference } from "convex/server";
3
+ import { CliArgError } from "./lib/cli-args";
4
+ import { parseEnvironmentOptions } from "./lib/environment-cli-args";
5
+
6
+ export const CONTEXT_SECTIONS = [
7
+ "identity",
8
+ "organisation",
9
+ "user",
10
+ "tools",
11
+ "rules",
12
+ ] as const;
13
+ export function validateContextSet(input: readonly string[]) {
14
+ const parsed = parseEnvironmentOptions(
15
+ input,
16
+ ["--content"],
17
+ ["--stdin", "--json"],
18
+ );
19
+ const [verb, section] = parsed.positional;
20
+ if (
21
+ verb !== "set" ||
22
+ parsed.positional.length !== 2 ||
23
+ !CONTEXT_SECTIONS.includes(section as never) ||
24
+ parsed.child.length
25
+ )
26
+ throw new CliArgError(
27
+ `Usage: dench context set <${CONTEXT_SECTIONS.join("|")}> --stdin | --content <text> [--json]`,
28
+ );
29
+ if (
30
+ Number(parsed.flags["--stdin"] === true) +
31
+ Number(typeof parsed.flags["--content"] === "string") !==
32
+ 1
33
+ )
34
+ throw new CliArgError(
35
+ "Choose exactly one of --stdin or --content. Store credentials with dench secrets set, never in context.",
36
+ );
37
+ return {
38
+ section: section as (typeof CONTEXT_SECTIONS)[number],
39
+ content: parsed.flags["--content"] as string | undefined,
40
+ };
41
+ }
42
+
43
+ export async function runContextSet(opts: {
44
+ convex: ConvexHttpClient;
45
+ args: string[];
46
+ sessionToken?: string;
47
+ readStdin?: () => Promise<string>;
48
+ }) {
49
+ const parsed = validateContextSet(opts.args);
50
+ const content =
51
+ parsed.content ??
52
+ (await (
53
+ opts.readStdin ??
54
+ (async () => {
55
+ const chunks: Buffer[] = [];
56
+ let size = 0;
57
+ for await (const chunk of process.stdin) {
58
+ size += Buffer.byteLength(chunk);
59
+ if (size > 32000)
60
+ throw new CliArgError(
61
+ "Context exceeds 32 KB. Keep standing instructions short; use workspace files for references.",
62
+ );
63
+ chunks.push(Buffer.from(chunk));
64
+ }
65
+ return Buffer.concat(chunks).toString("utf8");
66
+ })
67
+ )());
68
+ if (!content.trim() || Buffer.byteLength(content) > 32000)
69
+ throw new CliArgError("Context must contain 1–32000 UTF-8 bytes.");
70
+ const fn = {
71
+ identity: "updateIdentity",
72
+ organisation: "updateOrganisation",
73
+ user: "updateUserProfile",
74
+ tools: "updateToolsNotes",
75
+ rules: "writeMagicFileFromUI",
76
+ }[parsed.section];
77
+ await opts.convex.mutation(
78
+ makeFunctionReference<"mutation">(`functions/agentConfig:${fn}`),
79
+ {
80
+ ...(opts.sessionToken ? { sessionToken: opts.sessionToken } : {}),
81
+ content,
82
+ ...(parsed.section === "rules" ? { path: "/RULES.md" } : {}),
83
+ },
84
+ );
85
+ console.log(JSON.stringify({ ok: true, section: parsed.section }));
86
+ }
package/cron.ts CHANGED
@@ -392,6 +392,9 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
392
392
  "--prompt is required (becomes the user message of each cron-fired chat thread)",
393
393
  );
394
394
  }
395
+ const environment = getFlag(ctx.args, "--environment");
396
+ if (environment && !/^[a-z][a-z0-9-]{0,63}$/.test(environment))
397
+ throw new CronCliError("Invalid environment name");
395
398
  const description = getFlag(ctx.args, "--description");
396
399
  const overlapPolicy = getFlag(ctx.args, "--overlap") as
397
400
  | "skip"
@@ -431,6 +434,7 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
431
434
  triggerType,
432
435
  name,
433
436
  prompt,
437
+ environment,
434
438
  description,
435
439
  overlapPolicy,
436
440
  disabled,
@@ -462,6 +466,7 @@ async function runCreate(ctx: CronCliContext): Promise<void> {
462
466
  const created = (await callMutation(ctx, createCronTriggerRef, {
463
467
  name,
464
468
  prompt,
469
+ environment,
465
470
  triggerType,
466
471
  schedule,
467
472
  description,
@@ -551,6 +556,7 @@ async function runSecret(ctx: CronCliContext): Promise<void> {
551
556
  }
552
557
 
553
558
  export type AppOrCrmCreateBase = {
559
+ environment?: string;
554
560
  triggerType: "composio" | "crm";
555
561
  name: string;
556
562
  prompt: string;
@@ -584,6 +590,7 @@ export function buildAppOrCrmCreateBody(
584
590
  const body: Record<string, unknown> = {
585
591
  name: base.name,
586
592
  prompt: base.prompt,
593
+ environment: base.environment,
587
594
  triggerType: base.triggerType,
588
595
  description: base.description,
589
596
  overlapPolicy: base.overlapPolicy,
@@ -731,6 +738,15 @@ async function runEvents(ctx: CronCliContext): Promise<void> {
731
738
 
732
739
  async function runUpdate(ctx: CronCliContext): Promise<void> {
733
740
  const jobId = shift(ctx.args, "job id");
741
+ const environment = getFlag(ctx.args, "--environment");
742
+ const clearEnvironment = hasFlag(ctx.args, "--clear-environment");
743
+ if (
744
+ environment &&
745
+ (clearEnvironment || !/^[a-z][a-z0-9-]{0,63}$/.test(environment))
746
+ )
747
+ throw new CronCliError(
748
+ "Choose a valid --environment name or --clear-environment",
749
+ );
734
750
  const name = getFlag(ctx.args, "--name");
735
751
  const prompt = getFlag(ctx.args, "--prompt");
736
752
  const description = getFlag(ctx.args, "--description");
@@ -753,6 +769,7 @@ async function runUpdate(ctx: CronCliContext): Promise<void> {
753
769
  const schedule = consumeScheduleFlags(ctx.args);
754
770
  const result = await callMutation(ctx, updateCronTriggerRef, {
755
771
  jobId,
772
+ environment: clearEnvironment ? null : environment,
756
773
  name,
757
774
  prompt,
758
775
  description,
@@ -866,6 +883,8 @@ Browse:
866
883
  dench cron events <toolkit> [--json] # app-event types for a connected app
867
884
 
868
885
  Create / update:
886
+ Add --environment <name> to bind a ready reusable setup.
887
+ New runs use its latest ready revision; --clear-environment removes the binding on update.
869
888
  dench cron create "Daily standup digest" \\
870
889
  --prompt "Pull yesterday's commits + Slack #standup msgs and post a summary…" \\
871
890
  --type schedule # default. also: webhook | app | crm (see below)
package/dench.ts CHANGED
@@ -33,6 +33,7 @@ import { cliVersion } from "./lib/cli-version";
33
33
  import { assertDeclaredCommandFlags } from "./lib/command-args";
34
34
  import { denchConfigDirectory } from "./lib/config-path.mjs";
35
35
  import { resolveOrganizationBySelector } from "./lib/organizationSelector";
36
+ import { normalizeParentArgv } from "./lib/parent-argv";
36
37
  import { backendErrorText, publicErrorDetails } from "./lib/public-error";
37
38
  import { formatApprovalOpenMessage, openUrl } from "./openUrl";
38
39
  import {
@@ -217,13 +218,13 @@ const api = {
217
218
  };
218
219
  const CONFIG_DIR = denchConfigDirectory();
219
220
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
220
- const args = process.argv.slice(2).flatMap((arg) => {
221
- const split = arg.startsWith("--") ? arg.indexOf("=") : -1;
222
- return split > 2 ? [arg.slice(0, split), arg.slice(split + 1)] : [arg];
223
- });
224
- const json = args.includes("--json");
225
- const filteredArgs = args.filter((arg) => arg !== "--json");
226
- const scopedStatus = args.includes("--mine") || args.includes("--self");
221
+ // A child process owns every token after `--`, including --host and --json.
222
+ // Normalize only Dench's parent arguments; pass child argv through byte for byte.
223
+ const { args, runtimeArgs } = normalizeParentArgv(process.argv.slice(2));
224
+ const json = runtimeArgs.includes("--json");
225
+ const filteredArgs = runtimeArgs.filter((arg) => arg !== "--json");
226
+ const scopedStatus =
227
+ runtimeArgs.includes("--mine") || runtimeArgs.includes("--self");
227
228
 
228
229
  function readEnv(name: string) {
229
230
  const value = process.env[name]?.trim();
@@ -645,14 +646,11 @@ Past chat threads (own + shared in this workspace):
645
646
  dench chat delete --ids id1,id2 [--yes] [--json] (PERMANENTLY deletes threads)
646
647
 
647
648
  Spawn a new chat-turn workflow (same as the web UI):
648
- dench chat new "<prompt>" [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
649
+ dench chat new "<prompt>" [--environment NAME] [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
649
650
  dench chat send <thread-id> "<message>" [--model <m>] [--yolo] [--follow] [--json]
650
651
  dench chat follow <thread-id> [--json] (tail a live thread by id)
651
652
  dench chat tree [<root-thread-id>] [--limit N] [--json]
652
653
  (parent-child thread tree)
653
- dench agent new ... (alias for dench chat new)
654
- dench agent send ... (alias for dench chat send)
655
- dench agent follow ... (alias for dench chat follow)
656
654
 
657
655
  Help: dench chat help
658
656
 
@@ -674,32 +672,23 @@ returns ENOSYS on the FUSE-backed volume mount):
674
672
  Persist scratch -> workspace (cp from /tmp into /workspace + cloud sync):
675
673
  dench stage <local-path> [<workspace-dest>] [--clean] [--json]
676
674
 
677
- Subagents:
678
- dench agent spawn '<goal>' [--parent-run-id <id>] [--sandbox own|share_parent] [--time-budget-ms N]
679
- dench agent await --hook <token> | --children id1,id2,id3
680
- dench agent message <toRunId> '<text>' [--from-run-id <id>]
681
- dench agent wait-message
682
- dench agent pause <runId>
683
- dench agent resume <runId> ['optional prompt']
684
- dench agent continue <prevRunId> '<prompt>'
685
- dench agent tree [<rootRunId>]
686
- Help: dench agent help
675
+ Reusable workspace setup:
676
+ dench env list | show <name> | save <name> --path <directory> | run <name> -- <command> | delete <name>
677
+ dench secrets set <NAME> --stdin [--kind env|file]
678
+ dench secrets list | delete <NAME> metadata only; values are never printed
679
+ Help: dench env --help, dench secrets --help
687
680
 
688
681
  dench --version
689
682
  dench reference <name> [--json] Offline CLI-version-matched skill/reference
690
683
  dench reference --list [--json] Bundled reference index and revision
691
684
 
692
- Self-updating agent harness:
693
- dench identity show | edit IDENTITY.md (org-wide)
694
- dench organisation show | edit ORGANISATION.md
695
- dench user show | edit USER.md (per-member)
696
- dench tools show | edit TOOLS.md notes section
697
- dench mem show | append "<text>" | set MEMORY.md aggregate
698
- dench bootstrap status | complete | reopen | template
699
- dench model default <id|clear>
700
- dench model thread <threadId> <id|clear>
701
- dench daily today | show <YYYY-MM-DD>
702
- Help: dench identity help
685
+ Standing context and memory:
686
+ dench context --sections identity,organisation,user,memory,rules,tools [--json]
687
+ dench context set <identity|organisation|user|tools|rules> --stdin
688
+ dench memory search "<query>" | save <key> "<fact>"
689
+ dench model default <id|clear> | thread <threadId> <id|clear>
690
+ Context describes preferences and instructions. Environments store setup;
691
+ secrets store credentials. A memory note does not install or save either.
703
692
 
704
693
  External tools:
705
694
  dench integrations is an alias for dench tool status -- it lists connected apps.
@@ -761,13 +750,14 @@ function contextHelp() {
761
750
  Usage:
762
751
  dench context [--instructions] [--json]
763
752
  dench context --sections organisation,user,memory [--json]
764
- dench instructions [--json]
753
+ dench context set <identity|organisation|user|tools|rules> --stdin | --content "text" [--json]
765
754
 
766
755
  Shows the current workspace, current agent, assigned tasks, pending approvals
767
756
  requested by this agent, connected apps when available, and useful next commands.
768
757
 
769
758
  Use --instructions (or dench instructions) to print the standing workspace
770
- instructions external agents should treat as system-level guidance.
759
+ instructions for the current workspace. Context and memory do not persist
760
+ credentials or installed software: use dench secrets and dench env.
771
761
  `);
772
762
  }
773
763
 
@@ -2709,7 +2699,7 @@ type Runtime = Awaited<ReturnType<typeof getRuntime>>;
2709
2699
  * `dench members list --json` print the human table and `dench crm … --json`
2710
2700
  * fall back to text on empty results (`(no rows)`), breaking `| jq` pipelines.
2711
2701
  */
2712
- function stripRuntimeFlags(args: string[]) {
2702
+ function stripRuntimeFlags(args: string[], validateValues = true) {
2713
2703
  const booleanFlags = new Set([
2714
2704
  "--dev",
2715
2705
  "--prod",
@@ -2726,10 +2716,15 @@ function stripRuntimeFlags(args: string[]) {
2726
2716
  const stripped: string[] = [];
2727
2717
  for (let i = 0; i < args.length; i++) {
2728
2718
  const arg = args[i];
2719
+ if (arg === "--") {
2720
+ stripped.push(...args.slice(i));
2721
+ break;
2722
+ }
2729
2723
  if (booleanFlags.has(arg)) continue;
2730
2724
  if (valueFlags.has(arg)) {
2731
- if (!args[i + 1] || args[i + 1].startsWith("--")) {
2732
- throw new CliArgError(`Missing value for ${arg}`);
2725
+ if (!args[i + 1] || args[i + 1].startsWith("-")) {
2726
+ if (validateValues) throw new CliArgError(`Missing value for ${arg}`);
2727
+ continue;
2733
2728
  }
2734
2729
  i++;
2735
2730
  continue;
@@ -4327,22 +4322,36 @@ function isCommandNotFound(error: unknown) {
4327
4322
  }
4328
4323
 
4329
4324
  async function main() {
4330
- if (isHelpRequest(args)) {
4331
- const helpArgs = stripRuntimeFlags(args).filter((arg) => arg !== "--json");
4325
+ if (isHelpRequest(runtimeArgs)) {
4326
+ const helpArgs = stripRuntimeFlags(runtimeArgs, false).filter(
4327
+ (arg) => !["--json", "--help", "-h"].includes(arg),
4328
+ );
4332
4329
  if (helpArgs[0] === "help") helpArgs.shift();
4333
- if (!helpArgs.length || ["--help", "-h"].includes(helpArgs[0])) {
4330
+ if (!helpArgs.length) {
4334
4331
  help();
4335
4332
  return;
4336
4333
  }
4337
4334
  const path = helpArgs.filter(
4338
4335
  (arg) => !["help", "--help", "-h"].includes(arg),
4339
4336
  );
4337
+ if (path[0] === "daily") {
4338
+ console.log(
4339
+ `dench ${path.join(" ")} was retired. Search history with dench chat search or read existing daily files with dench files download. Harness A does not automatically write daily logs.`,
4340
+ );
4341
+ return;
4342
+ }
4340
4343
  // Root help retains domain-specific setup, account and pagination guidance.
4341
4344
  // These are explicit help-only paths; never pass mutation arguments to a handler.
4342
4345
  if (path.length === 1) {
4343
4346
  const roots: Record<string, () => void | Promise<void>> = {
4344
4347
  reference: async () =>
4345
4348
  console.log((await import("./reference")).REFERENCE_HELP),
4349
+ env: async () =>
4350
+ console.log((await import("./environments")).environmentHelpText()),
4351
+ secrets: async () =>
4352
+ console.log(
4353
+ (await import("./workspace-secrets")).workspaceSecretsHelpText(),
4354
+ ),
4346
4355
  tool: toolHelp,
4347
4356
  integrations: integrationsHelp,
4348
4357
  backend: backendHelp,
@@ -4399,14 +4408,136 @@ async function main() {
4399
4408
  ? (await import("./email")).emailHelpText()
4400
4409
  : helpArgs[0] === "crm"
4401
4410
  ? (await import("./crm")).crmHelpText()
4402
- : "";
4411
+ : helpArgs[0] === "env"
4412
+ ? (await import("./environments")).environmentHelpText()
4413
+ : helpArgs[0] === "secrets"
4414
+ ? (await import("./workspace-secrets")).workspaceSecretsHelpText()
4415
+ : "";
4403
4416
  console.log(commandHelp(helpArgs, usage));
4404
4417
  return;
4405
4418
  }
4406
4419
  const command = positional(0);
4407
4420
  const subcommand = positional(1);
4408
4421
 
4409
- assertDeclaredCommandFlags(stripRuntimeFlags(args));
4422
+ assertDeclaredCommandFlags(stripRuntimeFlags(runtimeArgs));
4423
+
4424
+ // New setup commands validate before authentication and never consume child flags.
4425
+ if (command === "env" || command === "secrets") {
4426
+ const rawSubArgs = args.slice(args.indexOf(command) + 1);
4427
+ const helpRequested =
4428
+ runtimeArgs.includes("--help") ||
4429
+ runtimeArgs.includes("-h") ||
4430
+ subcommand === "help";
4431
+ const subArgs = helpRequested ? rawSubArgs : stripRuntimeFlags(rawSubArgs);
4432
+ const mod =
4433
+ command === "env"
4434
+ ? await import("./environments")
4435
+ : await import("./workspace-secrets");
4436
+ if ("validateEnvironmentArgs" in mod) {
4437
+ const parsed = mod.validateEnvironmentArgs(subArgs);
4438
+ if (parsed.verb === "help") {
4439
+ console.log(mod.environmentHelpText());
4440
+ return;
4441
+ }
4442
+ const runtime = requireAuthenticatedRuntime(
4443
+ await getRuntime(),
4444
+ "dench env",
4445
+ );
4446
+ await mod.runEnvironmentCommand({
4447
+ convex: runtime.client,
4448
+ args: subArgs,
4449
+ sessionToken: runtime.sessionToken,
4450
+ apiUrl: runtime.host,
4451
+ });
4452
+ } else {
4453
+ const parsed = mod.validateWorkspaceSecretsArgs(subArgs);
4454
+ if (parsed.verb === "help") {
4455
+ console.log(mod.workspaceSecretsHelpText());
4456
+ return;
4457
+ }
4458
+ const runtime = requireAuthenticatedRuntime(
4459
+ await getRuntime(),
4460
+ "dench secrets",
4461
+ );
4462
+ await mod.runWorkspaceSecretsCommand({
4463
+ convex: runtime.client,
4464
+ args: subArgs,
4465
+ sessionToken: runtime.sessionToken,
4466
+ });
4467
+ }
4468
+ return;
4469
+ }
4470
+ if (
4471
+ command === "context" &&
4472
+ (runtimeArgs.includes("--help") ||
4473
+ runtimeArgs.includes("-h") ||
4474
+ subcommand === "help")
4475
+ ) {
4476
+ contextHelp();
4477
+ return;
4478
+ }
4479
+ if (command === "daily") {
4480
+ const guidance =
4481
+ "Daily-log commands were retired. Search actual history with dench chat search or read existing daily files with dench files download. Nothing writes daily logs automatically in Harness A.";
4482
+ if (
4483
+ runtimeArgs.includes("--help") ||
4484
+ runtimeArgs.includes("-h") ||
4485
+ subcommand === "help"
4486
+ ) {
4487
+ console.log(guidance);
4488
+ return;
4489
+ }
4490
+ throw new CliError(guidance, {
4491
+ code: "deprecated_command",
4492
+ retryable: false,
4493
+ });
4494
+ }
4495
+ if (command === "context" && subcommand === "set") {
4496
+ const { validateContextSet, runContextSet } = await import(
4497
+ "./context-settings"
4498
+ );
4499
+ const subArgs = stripRuntimeFlags(args.slice(args.indexOf(command) + 1));
4500
+ validateContextSet(subArgs);
4501
+ const runtime = requireAuthenticatedRuntime(
4502
+ await getRuntime(),
4503
+ "dench context set",
4504
+ );
4505
+ await runContextSet({
4506
+ convex: runtime.client,
4507
+ args: subArgs,
4508
+ sessionToken: runtime.sessionToken,
4509
+ });
4510
+ return;
4511
+ }
4512
+ if (
4513
+ command === "memory" &&
4514
+ (runtimeArgs.includes("--help") || runtimeArgs.includes("-h"))
4515
+ ) {
4516
+ memoryHelp();
4517
+ return;
4518
+ }
4519
+ if (command === "memory" && subcommand === "save") {
4520
+ const kind = option("--kind") ?? "fact";
4521
+ if (
4522
+ ![
4523
+ "fact",
4524
+ "decision",
4525
+ "preference",
4526
+ "goal",
4527
+ "tool_note",
4528
+ "other",
4529
+ "convention",
4530
+ "project_context",
4531
+ "agent_lesson",
4532
+ "docs_knowledge",
4533
+ "open_goal",
4534
+ ].includes(kind)
4535
+ )
4536
+ throw new CliError(
4537
+ "Unsupported memory kind. Use memory for facts/preferences; use dench secrets set NAME --stdin for credentials and dench env save for reusable setup.",
4538
+ { code: "validation_failed", retryable: false },
4539
+ );
4540
+ }
4410
4541
 
4411
4542
  if (command === "reference") {
4412
4543
  await (await import("./reference")).runReferenceCommand(
@@ -4415,7 +4546,10 @@ async function main() {
4415
4546
  return;
4416
4547
  }
4417
4548
 
4418
- if (command === "version" || stripRuntimeFlags(args)[0] === "--version") {
4549
+ if (
4550
+ command === "version" ||
4551
+ stripRuntimeFlags(runtimeArgs)[0] === "--version"
4552
+ ) {
4419
4553
  console.log(await cliVersion());
4420
4554
  return;
4421
4555
  }
@@ -5144,41 +5278,31 @@ async function main() {
5144
5278
  if (command === "agent") {
5145
5279
  const subArgs = stripRuntimeFlags(args.slice(args.indexOf("agent") + 1));
5146
5280
  const sub = subArgs[0];
5147
- if (!sub || sub === "help" || sub === "--help") {
5148
- const { runAgentCommand } = await import("./agent");
5149
- // biome-ignore lint/suspicious/noExplicitAny: harmless help-only stub
5150
- await runAgentCommand({ convex: {} as any, args: subArgs });
5151
- return;
5152
- }
5153
- // Aliases: `dench agent new` / `dench agent send` / `dench agent
5154
- // follow` -> the chat-spawn implementation. Surfaced under
5155
- // `agent` so users who associate chat threads with the agent
5156
- // surface still find them; the actual workflow is identical to
5157
- // `dench chat new` / `dench chat send` / `dench chat follow`.
5158
- if (sub === "new" || sub === "send" || sub === "follow") {
5159
- const { runChatSpawnCommand } = await import("./chat-spawn");
5160
- const runtime = await getRuntime();
5161
- const authedRuntime = requireAuthenticatedRuntime(
5162
- runtime,
5163
- `dench agent ${sub}`,
5281
+ if (
5282
+ !sub ||
5283
+ sub === "help" ||
5284
+ subArgs.includes("--help") ||
5285
+ subArgs.includes("-h")
5286
+ ) {
5287
+ console.log(
5288
+ "Use dench chat new, send, follow, or tree. agent new/send/follow remain compatibility aliases. In Harness A, use spawn_task/read_task for child tasks.",
5164
5289
  );
5165
- await runChatSpawnCommand({
5166
- runtime: {
5167
- host: authedRuntime.host,
5168
- bearerToken: authedRuntime.sessionToken,
5169
- },
5170
- args: subArgs.slice(1),
5171
- subcommand: sub,
5172
- });
5173
5290
  return;
5174
5291
  }
5175
- const { runAgentCommand } = await import("./agent");
5176
- const runtime = await getRuntime();
5177
- await runAgentCommand({
5178
- convex: runtime.client,
5179
- args: subArgs,
5180
- sessionToken: runtime.sessionToken,
5181
- callerRunId: process.env.DENCH_RUN_ID?.trim() || undefined,
5292
+ if (sub !== "new" && sub !== "send" && sub !== "follow")
5293
+ throw new CliError(
5294
+ "Unsupported agent command. Use dench chat new/send/follow/tree or the harness subagent tools.",
5295
+ { code: "unsupported_capability", retryable: false },
5296
+ );
5297
+ const { runChatSpawnCommand } = await import("./chat-spawn");
5298
+ const runtime = requireAuthenticatedRuntime(
5299
+ await getRuntime(),
5300
+ `dench chat ${sub}`,
5301
+ );
5302
+ await runChatSpawnCommand({
5303
+ runtime: { host: runtime.host, bearerToken: runtime.sessionToken },
5304
+ args: subArgs.slice(1),
5305
+ subcommand: sub,
5182
5306
  });
5183
5307
  return;
5184
5308
  }
@@ -5253,7 +5377,7 @@ async function main() {
5253
5377
  // ── OpenClaw-parity self-update CLI surface ──────────────────────────
5254
5378
  //
5255
5379
  // Dispatch all `dench identity / organisation / user / tools / mem /
5256
- // bootstrap / model / daily` traffic through the shared
5380
+ // bootstrap / model` traffic through the shared
5257
5381
  // agent-config CLI module. Each command requires the same auth shape
5258
5382
  // as `dench cron` (Bearer DENCH_API_KEY or `dench signin` session
5259
5383
  // token) so server-side `requireCronAccess` accepts both.
@@ -5265,7 +5389,6 @@ async function main() {
5265
5389
  "mem",
5266
5390
  "bootstrap",
5267
5391
  "model",
5268
- "daily",
5269
5392
  ]);
5270
5393
  if (AGENT_CONFIG_COMMANDS.has(command)) {
5271
5394
  const subArgs = stripRuntimeFlags(args.slice(args.indexOf(command) + 1));
@@ -5318,10 +5441,6 @@ async function main() {
5318
5441
  await mod.runModelCommand(ctxBase);
5319
5442
  return;
5320
5443
  }
5321
- if (command === "daily") {
5322
- await mod.runDailyCommand(ctxBase);
5323
- return;
5324
- }
5325
5444
  }
5326
5445
 
5327
5446
  if (command === "signin") {
@@ -5369,7 +5488,7 @@ async function main() {
5369
5488
  return;
5370
5489
  }
5371
5490
 
5372
- if (command === "context" && args.includes("--sections")) {
5491
+ if (command === "context" && runtimeArgs.includes("--sections")) {
5373
5492
  const raw = args[args.indexOf("--sections") + 1];
5374
5493
  const sections = raw
5375
5494
  ?.split(",")
@@ -5407,7 +5526,7 @@ async function main() {
5407
5526
  return;
5408
5527
  }
5409
5528
 
5410
- if (command === "context" && args.includes("--instructions")) {
5529
+ if (command === "context" && runtimeArgs.includes("--instructions")) {
5411
5530
  const instructions = await buildWorkspaceInstructionsContext(runtime);
5412
5531
  print(
5413
5532
  json ? instructions : (stringField(instructions, "instructions") ?? ""),