@dench.com/cli 3.7.0-staging.387 → 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/README.md CHANGED
@@ -112,9 +112,8 @@ next commands. Add `--json` for structured output.
112
112
 
113
113
  Browse and search the user's past chat threads in this workspace (own +
114
114
  shared). Useful for pulling in earlier context when the user refers to
115
- something they discussed before. The chat agent has the equivalent
116
- in-process tools (`list_past_chats`, `search_past_chats`,
117
- `read_past_chat`); this CLI surface is the same data accessed via bash.
115
+ something they discussed before. The shared chat harness uses these same CLI
116
+ commands through its execution tool; there is no separate agent-only chat-history command family.
118
117
 
119
118
  ```bash
120
119
  dench chat list [--query <substr>] [--limit N] [--include-archived] [--json]
@@ -391,54 +390,53 @@ Outreach needs a workspace member's credentials (a `dench signin` session, a
391
390
  member-bound `DENCH_API_KEY`, or an agent session); an organization-only key
392
391
  is refused.
393
392
 
394
- ## Workspace sync
393
+ ## Workspace files and reusable setup
395
394
 
396
- Starting with CLI 2.9.8, `dench fs status` reports cached local daemon
397
- health, not a live comparison between disk and the cloud copy. The status
398
- probe does not scan the workspace, contact the backend, or start a mount
399
- worker.
395
+ Use `dench files` for saved workspace documents and `dench stage` for outputs.
396
+ Harness A keeps a separate scratch directory per job. The old filesystem and
397
+ home-sync daemons are legacy compatibility infrastructure; they are not needed
398
+ to execute a command or restore a saved environment.
400
399
 
401
- ```bash
402
- dench fs status --json
403
- dench fs status --workspace /workspace --json
404
- ```
405
-
406
- | JSON field | Meaning |
407
- | --- | --- |
408
- | `daemon.healthy` | The process is alive and cached progress passes freshness checks, with a short startup grace before the first scan. |
409
- | `daemon.workspaceMatches` | The cached daemon serves the requested workspace root. A mismatch also makes `healthy` false. |
410
- | `daemon.state.mountWorker` | Cached worker state, PID, last progress time, restart count, and error. |
411
- | `counts.onDisk` | Last successful scan's count, only for the matching root; `null` when unavailable. Not a fresh count. |
412
- | `counts.inConvex`, `counts.onlyOnDisk`, `counts.onlyInConvex`, `counts.hashMismatches` | `null`: these counts are unknown, not zero. |
413
- | `driftStatus`, `drift` | `"not_checked"` and `[]`; an empty list does not prove that files match. |
414
-
415
- Status exits **1** when the daemon is missing, dead, stale, stalled, or
416
- serves a different root, including with `--json`; exit **0** means cached
417
- health passed, not that every pending write has synced. The legacy
418
- `--no-hash` and `--drift-limit N` flags remain accepted but have no effect.
419
-
420
- To pull a workspace file through the running daemon:
421
-
422
- ```bash
423
- dench fs pull "/reports/example.csv" --wait --timeout-ms 10000 --json
400
+ ```sh
401
+ dench files cat /reports/example.md
402
+ dench env list --json
403
+ dench env save openai-report --path ./openai-report --json
404
+ dench env show openai-report --json
405
+ dench env run openai-report -- node report.mjs
406
+ dench secrets set OPENAI_API_KEY --stdin < /private/path/key.txt
424
407
  ```
425
408
 
426
- `--timeout-ms` covers the whole request, including queueing, metadata,
427
- download, and mounted write (default 10 seconds, capped at 60 seconds).
428
- Successful `--wait` means the downloaded original passed its SHA-256 check
429
- and the write finished before the deadline. Without `--wait`, success
430
- means only that the request was queued.
431
-
432
- A running daemon process does not hot-upgrade: restart it after installing a
433
- new CLI.
409
+ A saved directory requires `dench-env.json`, `SKILL.md`, an explicit file allowlist
410
+ and verification commands. It builds dependencies once, publishes a ready
411
+ revision, and reuses the prepared snapshot or compatible bundle. Only the chosen
412
+ environment is loaded. Secrets are encrypted separately and injected into the
413
+ executing process; they never become dependency archives or memory facts.
414
+
415
+ New automation runs select the latest ready revision; active jobs and their
416
+ children retain the resolved revision. State is isolated by job or automation
417
+ unless an explicit shared state key is chosen. State writes and refreshed
418
+ credential files use compare-and-set versions; conflicts do not replay business
419
+ operations. Rotate credentials without reinstalling packages.
420
+
421
+ On a different local OS/runtime, explicitly prepare a separate platform cache
422
+ once using `dench env run openai-report --prepare-local -- node report.mjs`.
423
+ Normal ready runs never invoke a package installer. The local executor provides
424
+ separate directories, not operating-system isolation.
425
+
426
+ See [the environment reference](https://dench.com/references/ENVIRONMENTS.md)
427
+ for the manifest, cold-start costs, mutable state and recovery receipts. Use
428
+ `dench memory search/save` for facts and preferences, `dench context set` for
429
+ standing context, and `dench secrets` for credentials. An old memory claiming
430
+ that a service is configured cannot replace a successful storage receipt.
434
431
 
435
432
  ## Sandbox / API key auth (no `dench signin` required)
436
433
 
437
434
  Inside a Dench sandbox the Dench API key is set in the environment when the
438
435
  sandbox starts. When `DENCH_API_KEY` is present, the CLI skips the
439
436
  `dench signin` flow entirely and uses the key as a bearer for every org-scoped
440
- command — `dench crm`, `dench files`, `dench fs sync`, etc. The key targets
441
- production.
437
+ command — `dench crm`, `dench files`, and other workspace operations. The
438
+ selected connection determines the backend; hosted credentials stay scoped to
439
+ the authorized workspace and job.
442
440
 
443
441
  ```bash
444
442
  export DENCH_API_KEY=dench_pk_live_…
@@ -477,7 +475,8 @@ dench context --sections organisation,user,memory --json
477
475
  ```
478
476
 
479
477
  Section names are `identity`, `organisation`, `user`, `memory`, `rules`, and
480
- `tools`. Multiple names use one query. The existing `organisation show`,
481
- `user show`, and `mem show` commands use the same saved documents; reading never
482
- fills profiles or writes memory. Personal USER context is unavailable to shared
478
+ `tools`. Multiple names use one query. Reading never fills profiles or writes
479
+ memory. Edit the selected context with
480
+ `dench context set <identity|organisation|user|tools|rules> --stdin`; older profile
481
+ aliases remain compatibility paths. Personal USER context is unavailable to shared
483
482
  channel sessions. `dench context --json` keeps its existing workspace overview.
package/agent-config.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `dench identity / organisation / user / bootstrap / model / daily /
2
+ * `dench identity / organisation / user / bootstrap / model /
3
3
  * mem aggregate` — CLI surface for the OpenClaw-
4
4
  * parity self-updating harness.
5
5
  *
@@ -15,14 +15,13 @@
15
15
  * identity show | edit
16
16
  * organisation show | edit
17
17
  * user show | edit
18
- * tools show | notes-edit
18
+ * tools show | edit
19
19
  * memory show | append <text> | set (MEMORY.md aggregate)
20
20
  * bootstrap status | complete | reopen | template
21
21
  * model default <id> | model default clear |
22
22
  * model thread <threadId> <id> | model thread <threadId> clear
23
- * daily today | daily show <YYYY-MM-DD> | daily append <text>
24
23
  *
25
- * `edit` / `notes-edit` / `template` open the
24
+ * `edit` / `template` open the
26
25
  * user's `$EDITOR` (default `vi`) to capture the new body. The
27
26
  * stdin path is also supported via `--stdin`, useful in scripts.
28
27
  */
@@ -496,66 +495,17 @@ export async function runModelCommand(ctx: CliCtx): Promise<void> {
496
495
  await handleModel(ctx, ctx.args);
497
496
  }
498
497
 
499
- /**
500
- * Daily activity log subcommands (real fileTree files under
501
- * `/workspace/daily/`). Today / show <YYYY-MM-DD> read the inline
502
- * cache; append delegates to the agent action so timestamps and
503
- * heading wrappers stay consistent.
504
- */
505
- export async function runDailyCommand(ctx: CliCtx): Promise<void> {
506
- consumeOutputFlag(ctx);
507
- const sub = ctx.args[0];
508
- if (!sub || sub === "today") {
509
- const today = new Date();
510
- const yyyy = today.getUTCFullYear();
511
- const mm = String(today.getUTCMonth() + 1).padStart(2, "0");
512
- const dd = String(today.getUTCDate()).padStart(2, "0");
513
- const path = `/daily/${yyyy}-${mm}-${dd}.md`;
514
- // Daily logs are real files — fetch via the workspace file API
515
- // hop so the same auth (Convex Auth or API key) works.
516
- out(
517
- ctx,
518
- ctx.jsonOutput
519
- ? { path }
520
- : `Today's daily log lives at ${path}. Use \`dench fs cat ${path}\` to read it, or open it in the workspace UI.`,
521
- );
522
- return;
523
- }
524
- if (sub === "show") {
525
- const date = ctx.args[1];
526
- if (!date) {
527
- throw new AgentConfigCliError("Usage: dench daily show <YYYY-MM-DD>");
528
- }
529
- out(
530
- ctx,
531
- ctx.jsonOutput
532
- ? { path: `/daily/${date}.md` }
533
- : `Daily log for ${date} lives at /daily/${date}.md. Use \`dench fs cat\` to read it.`,
534
- );
535
- return;
536
- }
537
- if (sub === "append") {
538
- throw new AgentConfigCliError(
539
- "Daily append is currently only available via the agent's `append_daily_log` tool. Use the chat to log entries with timestamps.",
540
- );
541
- }
542
- throw new AgentConfigCliError(
543
- `Unknown daily subcommand: "${sub}". Try "today", "show <YYYY-MM-DD>".`,
544
- );
545
- }
546
-
547
498
  export function printAgentConfigCliHelp(): void {
548
499
  console.log(
549
- `Self-updating agent harness:
500
+ `Workspace context compatibility aliases (prefer dench context --sections / context set):
550
501
 
551
502
  dench identity show | edit IDENTITY.md (org-wide)
552
503
  dench organisation show | edit ORGANISATION.md
553
504
  dench user show | edit USER.md (per-member)
554
- dench tools show | notes-edit TOOLS.md notes section
505
+ dench tools show | edit TOOLS.md notes section
555
506
  dench mem show | append <text> | set MEMORY.md aggregate
556
507
  dench bootstrap status | complete | reopen | template
557
508
  dench model default <id|clear> | model thread <threadId> <id|clear>
558
- dench daily today | show <YYYY-MM-DD> Activity log paths
559
509
 
560
510
  Editing flags:
561
511
  --content "<markdown>" Inline body
package/app-sdk.d.ts CHANGED
@@ -12,6 +12,67 @@
12
12
  // ── Operation request/response types (from cli/lib/api-schemas.ts) ──────
13
13
 
14
14
  export interface ApiRequests {
15
+ /** List workspace-shared reusable setups and ready revisions (metadata only). — `dench env list` — GET /api/v1/environments */
16
+ "environments.list": {
17
+ limit?: number;
18
+ cursor?: string;
19
+ };
20
+ /** Inspect a saved setup, its manifest, and an optional immutable revision. — `dench env show` — GET /api/v1/environments/{name} */
21
+ "environments.get": {
22
+ name: string;
23
+ revision?: string;
24
+ };
25
+ /** Reserve an owner-bound source upload. Prefix the uploaded bytes with the returned proof before saving. — `dench env save` — POST /api/v1/environments/uploads */
26
+ "environments.upload": {
27
+ kind: "source";
28
+ size: number;
29
+ sha256: string;
30
+ };
31
+ /** Publish an explicitly saved source and manifest for one-time preparation. Returns pending/building until verified; does not run the task. — `dench env save` — POST /api/v1/environments/{name} */
32
+ "environments.save": {
33
+ name: string;
34
+ manifest: {
35
+ version: 1;
36
+ name: string;
37
+ purpose: string;
38
+ files: string[];
39
+ prepare: string[];
40
+ verify: string[];
41
+ env?: Record<string, string>;
42
+ secretEnv?: Record<string, string>;
43
+ secretFiles?: {
44
+ path: string;
45
+ secret: string;
46
+ mutable?: boolean;
47
+ }[];
48
+ statePaths?: string[];
49
+ portable?: boolean;
50
+ };
51
+ uploadId: string;
52
+ storageId: string;
53
+ sha256: string;
54
+ };
55
+ /** Retire a saved setup from new selection; retain immutable revisions and active job evidence. — `dench env delete` — DELETE /api/v1/environments/{name} */
56
+ "environments.delete": {
57
+ name: string;
58
+ };
59
+ /** List workspace-shared credential names, kinds and versions; never values. — `dench secrets list` — GET /api/v1/secrets */
60
+ "secrets.list": {
61
+ limit?: number;
62
+ cursor?: string;
63
+ };
64
+ /** Encrypt a workspace credential. Use CLI stdin or the secure chat form; return metadata only. Do not put values in chat, manifests or scripts. — `dench secrets set` — PUT /api/v1/secrets/{name} */
65
+ "secrets.set": {
66
+ name: string;
67
+ kind: "env" | "file";
68
+ value: string;
69
+ expectedVersion?: number;
70
+ };
71
+ /** Revoke a workspace credential for future resolution, optionally checking its version. — `dench secrets delete` — DELETE /api/v1/secrets/{name} */
72
+ "secrets.delete": {
73
+ name: string;
74
+ expectedVersion?: number;
75
+ };
15
76
  /** Create an idempotent empty campaign draft with the caller's available sender pools. Does not launch or send. — `dench campaign draft` — POST /api/v1/campaigns/drafts */
16
77
  "campaigns.drafts.create": {
17
78
  /** Your stable id for this request, 8-64 characters. Retrying with the same id returns the same draft instead of creating another. */
@@ -699,6 +760,7 @@ export interface ApiRequests {
699
760
  };
700
761
  /** Start a new chat-turn workflow. — `dench chat new` — POST /api/v1/chat/threads */
701
762
  "chat.new": {
763
+ environment?: string;
702
764
  /** The user message that starts the turn. */
703
765
  prompt: string;
704
766
  model?: string;
@@ -3235,30 +3297,34 @@ export interface ApiRequests {
3235
3297
  "browser.session.status": Record<string, unknown>;
3236
3298
  /** Stop a Dench Browser session and release the remote browser. — `dench browser session stop` — DELETE /api/v1/browser/sessions/{sessionId} */
3237
3299
  "browser.session.stop": Record<string, unknown>;
3238
- /** Read IDENTITY.md. — `dench identity show` — GET /api/v1/agent-config/identity */
3300
+ /** Read IDENTITY.md. — `dench context --sections identity` — GET /api/v1/agent-config/identity */
3239
3301
  "agentConfig.identity.show": Record<string, unknown>;
3240
- /** Update IDENTITY.md. — `dench identity edit` — PUT /api/v1/agent-config/identity */
3302
+ /** Update IDENTITY.md. — `dench context set identity` — PUT /api/v1/agent-config/identity */
3241
3303
  "agentConfig.identity.edit": {
3242
3304
  content: string;
3243
3305
  };
3244
- /** Read ORGANISATION.md. — `dench organisation show` — GET /api/v1/agent-config/organisation */
3306
+ /** Read ORGANISATION.md. — `dench context --sections organisation` — GET /api/v1/agent-config/organisation */
3245
3307
  "agentConfig.organisation.show": Record<string, unknown>;
3246
- /** Update ORGANISATION.md. — `dench organisation edit` — PUT /api/v1/agent-config/organisation */
3308
+ /** Update ORGANISATION.md. — `dench context set organisation` — PUT /api/v1/agent-config/organisation */
3247
3309
  "agentConfig.organisation.edit": {
3248
3310
  content: string;
3249
3311
  };
3250
- /** Read USER.md. — `dench user show` — GET /api/v1/agent-config/user */
3312
+ /** Read USER.md. — `dench context --sections user` — GET /api/v1/agent-config/user */
3251
3313
  "agentConfig.user.show": Record<string, unknown>;
3252
- /** Update USER.md. — `dench user edit` — PUT /api/v1/agent-config/user */
3314
+ /** Update USER.md. — `dench context set user` — PUT /api/v1/agent-config/user */
3253
3315
  "agentConfig.user.edit": {
3254
3316
  content: string;
3255
3317
  };
3256
- /** Read tool notes. — `dench tools show` — GET /api/v1/agent-config/tools */
3318
+ /** Read tool notes. — `dench context --sections tools` — GET /api/v1/agent-config/tools */
3257
3319
  "agentConfig.tools.show": Record<string, unknown>;
3258
- /** Update tool notes. — `dench tools edit` — PUT /api/v1/agent-config/tools */
3320
+ /** Update tool notes. — `dench context set tools` — PUT /api/v1/agent-config/tools */
3259
3321
  "agentConfig.tools.edit": {
3260
3322
  content: string;
3261
3323
  };
3324
+ /** Update standing workspace rules; credentials belong in secrets and runnable setup in environments. — `dench context set rules` — PUT /api/v1/context/rules */
3325
+ "workspace.context.rules.set": {
3326
+ content: string;
3327
+ };
3262
3328
  /** Read aggregate memory. — `dench mem show` — GET /api/v1/agent-config/mem */
3263
3329
  "agentConfig.mem.show": Record<string, unknown>;
3264
3330
  /** Update aggregate memory. — `dench mem set` — PUT /api/v1/agent-config/mem */
@@ -3347,6 +3413,20 @@ export interface ApiRequests {
3347
3413
  }
3348
3414
 
3349
3415
  export interface ApiResponses {
3416
+ "environments.list": unknown;
3417
+ "environments.get": unknown;
3418
+ "environments.upload": unknown;
3419
+ "environments.save": unknown;
3420
+ "environments.delete": unknown;
3421
+ "secrets.list": unknown;
3422
+ "secrets.set": {
3423
+ name: string;
3424
+ kind: "env" | "file";
3425
+ version: number;
3426
+ revision: number;
3427
+ status: "stored";
3428
+ };
3429
+ "secrets.delete": unknown;
3350
3430
  "campaigns.drafts.create": {
3351
3431
  sequenceId: string;
3352
3432
  };
@@ -7255,6 +7335,7 @@ export interface ApiResponses {
7255
7335
  template?: string;
7256
7336
  };
7257
7337
  };
7338
+ "workspace.context.rules.set": unknown;
7258
7339
  "agentConfig.mem.show": {
7259
7340
  agentConfig: {
7260
7341
  identity: {
@@ -7566,9 +7647,9 @@ export type DenchApiTree = {
7566
7647
  today(args?: Req<"agentConfig.daily.today">): Promise<Res<"agentConfig.daily.today">>;
7567
7648
  };
7568
7649
  identity: {
7569
- /** Update IDENTITY.md. (`dench identity edit`) */
7650
+ /** Update IDENTITY.md. (`dench context set identity`) */
7570
7651
  edit(args: Req<"agentConfig.identity.edit">): Promise<Res<"agentConfig.identity.edit">>;
7571
- /** Read IDENTITY.md. (`dench identity show`) */
7652
+ /** Read IDENTITY.md. (`dench context --sections identity`) */
7572
7653
  show(args?: Req<"agentConfig.identity.show">): Promise<Res<"agentConfig.identity.show">>;
7573
7654
  };
7574
7655
  mem: {
@@ -7584,21 +7665,21 @@ export type DenchApiTree = {
7584
7665
  thread(args: Req<"agentConfig.model.thread">): Promise<Res<"agentConfig.model.thread">>;
7585
7666
  };
7586
7667
  organisation: {
7587
- /** Update ORGANISATION.md. (`dench organisation edit`) */
7668
+ /** Update ORGANISATION.md. (`dench context set organisation`) */
7588
7669
  edit(args: Req<"agentConfig.organisation.edit">): Promise<Res<"agentConfig.organisation.edit">>;
7589
- /** Read ORGANISATION.md. (`dench organisation show`) */
7670
+ /** Read ORGANISATION.md. (`dench context --sections organisation`) */
7590
7671
  show(args?: Req<"agentConfig.organisation.show">): Promise<Res<"agentConfig.organisation.show">>;
7591
7672
  };
7592
7673
  tools: {
7593
- /** Update tool notes. (`dench tools edit`) */
7674
+ /** Update tool notes. (`dench context set tools`) */
7594
7675
  edit(args: Req<"agentConfig.tools.edit">): Promise<Res<"agentConfig.tools.edit">>;
7595
- /** Read tool notes. (`dench tools show`) */
7676
+ /** Read tool notes. (`dench context --sections tools`) */
7596
7677
  show(args?: Req<"agentConfig.tools.show">): Promise<Res<"agentConfig.tools.show">>;
7597
7678
  };
7598
7679
  user: {
7599
- /** Update USER.md. (`dench user edit`) */
7680
+ /** Update USER.md. (`dench context set user`) */
7600
7681
  edit(args: Req<"agentConfig.user.edit">): Promise<Res<"agentConfig.user.edit">>;
7601
- /** Read USER.md. (`dench user show`) */
7682
+ /** Read USER.md. (`dench context --sections user`) */
7602
7683
  show(args?: Req<"agentConfig.user.show">): Promise<Res<"agentConfig.user.show">>;
7603
7684
  };
7604
7685
  };
@@ -8362,6 +8443,18 @@ export type DenchApiTree = {
8362
8443
  preview(args: Req<"enrichment.search.preview">): Promise<Res<"enrichment.search.preview">>;
8363
8444
  };
8364
8445
  };
8446
+ environments: {
8447
+ /** Retire a saved setup from new selection; retain immutable revisions and active job evidence. (`dench env delete`) */
8448
+ delete(args: Req<"environments.delete">): Promise<Res<"environments.delete">>;
8449
+ /** Inspect a saved setup, its manifest, and an optional immutable revision. (`dench env show`) */
8450
+ get(args: Req<"environments.get">): Promise<Res<"environments.get">>;
8451
+ /** List workspace-shared reusable setups and ready revisions (metadata only). (`dench env list`) */
8452
+ list(args?: Req<"environments.list">): Promise<Res<"environments.list">>;
8453
+ /** Publish an explicitly saved source and manifest for one-time preparation. Returns pending/building until verified; does not run the task. (`dench env save`) */
8454
+ save(args: Req<"environments.save">): Promise<Res<"environments.save">>;
8455
+ /** Reserve an owner-bound source upload. Prefix the uploaded bytes with the returned proof before saving. (`dench env save`) */
8456
+ upload(args: Req<"environments.upload">): Promise<Res<"environments.upload">>;
8457
+ };
8365
8458
  files: {
8366
8459
  /** Read a workspace file's text. (`dench files cat`) */
8367
8460
  content(args?: Req<"files.content">): Promise<Res<"files.content">>;
@@ -8506,6 +8599,14 @@ export type DenchApiTree = {
8506
8599
  /** Search the web through the Dench gateway. (`dench search`) */
8507
8600
  web(args: Req<"search.web">): Promise<Res<"search.web">>;
8508
8601
  };
8602
+ secrets: {
8603
+ /** Revoke a workspace credential for future resolution, optionally checking its version. (`dench secrets delete`) */
8604
+ delete(args: Req<"secrets.delete">): Promise<Res<"secrets.delete">>;
8605
+ /** List workspace-shared credential names, kinds and versions; never values. (`dench secrets list`) */
8606
+ list(args?: Req<"secrets.list">): Promise<Res<"secrets.list">>;
8607
+ /** Encrypt a workspace credential. Use CLI stdin or the secure chat form; return metadata only. Do not put values in chat, manifests or scripts. (`dench secrets set`) */
8608
+ set(args: Req<"secrets.set">): Promise<Res<"secrets.set">>;
8609
+ };
8509
8610
  settings: {
8510
8611
  sync: {
8511
8612
  /** Read the workspace's Sync switches (accounts from email, contacts from LinkedIn, Slack Connect, calendar, dossier, tasks from email, lead auto-qualify, summaries and Brief). All on by default. (`dench settings sync`) */
@@ -8575,6 +8676,10 @@ export type DenchApiTree = {
8575
8676
  /** Return the current Dench workspace context for the bearer. (`dench context`) */
8576
8677
  (args?: Req<"workspace.context">): Promise<Res<"workspace.context">>;
8577
8678
  $call(args?: Req<"workspace.context">): Promise<Res<"workspace.context">>;
8679
+ rules: {
8680
+ /** Update standing workspace rules; credentials belong in secrets and runnable setup in environments. (`dench context set rules`) */
8681
+ set(args: Req<"workspace.context.rules.set">): Promise<Res<"workspace.context.rules.set">>;
8682
+ };
8578
8683
  /** Read selected scoped workspace documents and a compact context index without writes. (`dench context --sections`) */
8579
8684
  sections(args?: Req<"workspace.context.sections">): Promise<Res<"workspace.context.sections">>;
8580
8685
  };
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]
@@ -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)