@dench.com/cli 0.3.1 → 0.3.2
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 +33 -0
- package/crm.ts +16 -5
- package/dench.ts +172 -27
- package/fs-daemon.ts +19 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,3 +149,36 @@ For staging:
|
|
|
149
149
|
dench login --staging
|
|
150
150
|
dench login --host https://workspace-staging.dench.com
|
|
151
151
|
```
|
|
152
|
+
|
|
153
|
+
## Sandbox / API key auth (no `dench login` required)
|
|
154
|
+
|
|
155
|
+
Inside a Daytona sandbox the unified Dench API key is baked into envVars
|
|
156
|
+
at create time (see `src/lib/secrets/sandbox-tokens.ts`). When
|
|
157
|
+
`DENCH_API_KEY` and `CONVEX_URL` are both present, the CLI skips the
|
|
158
|
+
`dench login` flow entirely and uses the API key as a bearer for every
|
|
159
|
+
org-scoped command — `dench crm`, `dench files`, `dench fs sync`, etc.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
export DENCH_API_KEY=dench_pk_live_…
|
|
163
|
+
export CONVEX_URL=https://your-convex-deployment.convex.cloud
|
|
164
|
+
# Optional but enables nicer URLs in CLI output:
|
|
165
|
+
export DENCH_API_URL=https://app.dench.com
|
|
166
|
+
export DENCH_ORG_ID=…
|
|
167
|
+
export DENCH_ORG_SLUG=…
|
|
168
|
+
|
|
169
|
+
dench crm entries list people
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Auth precedence:
|
|
173
|
+
|
|
174
|
+
1. `--dev` flag → use the dev workspace env (`NEXT_PUBLIC_CONVEX_URL`,
|
|
175
|
+
`DENCH_WORKSPACE`, `DENCH_DEV_AGENT_KEY`).
|
|
176
|
+
2. A locally-stored `dench login` session for the current host.
|
|
177
|
+
3. `DENCH_API_KEY` + `CONVEX_URL` (sandbox / automation path).
|
|
178
|
+
4. `--dev`-style env vars (without the flag).
|
|
179
|
+
|
|
180
|
+
Commands that need to impersonate a specific agent (`dench memory`,
|
|
181
|
+
`dench task`, `dench claim`, `dench log`, `dench approval request`,
|
|
182
|
+
etc.) still require an agent session (`dench login`) — the unified API
|
|
183
|
+
key is org-scoped and cannot speak as a single agent. Those commands
|
|
184
|
+
return a clear "agent session required" error in API-key mode.
|
package/crm.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
* `dench crm <subcommand>` — CLI surface for the Convex CRM.
|
|
3
3
|
*
|
|
4
4
|
* All operations resolve via the unified Dench API key baked into the
|
|
5
|
-
* sandbox (DENCH_API_KEY) or
|
|
6
|
-
*
|
|
5
|
+
* sandbox (DENCH_API_KEY) or a locally-stored agent session token from
|
|
6
|
+
* `dench login`. The dispatcher does not care which one — the parent
|
|
7
|
+
* (`cli/dench.ts → getRuntime`) picks whichever auth is available and
|
|
8
|
+
* forwards a single bearer through `sessionToken`. The server-side
|
|
9
|
+
* `requireCrmAccess` (convex/lib/crm/access.ts) accepts either format.
|
|
10
|
+
*
|
|
11
|
+
* Talks to the Convex deployment URL provided by the active runtime
|
|
12
|
+
* (the session's `convexUrl` or the sandbox's `CONVEX_URL` envvar).
|
|
7
13
|
*
|
|
8
14
|
* This is a thin command dispatcher; the heavy lifting lives in
|
|
9
15
|
* convex/functions/crm/* and convex/lib/crm/*.
|
|
@@ -37,9 +43,14 @@ type CrmCliContext = {
|
|
|
37
43
|
args: string[];
|
|
38
44
|
jsonOutput: boolean;
|
|
39
45
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
46
|
+
* Bearer token sent with every Convex call so the server-side
|
|
47
|
+
* `requireCrmAccess` helper can resolve the caller's organization
|
|
48
|
+
* without a Convex Auth cookie. Two formats are accepted:
|
|
49
|
+
* • `dch_agent_*` agent session minted by `dench login`.
|
|
50
|
+
* • Unified Dench API key (`dench_pk_*`, `dench_live_*`,
|
|
51
|
+
* `gw_sk_*`, etc.) baked into sandbox envVars as `DENCH_API_KEY`.
|
|
52
|
+
* The server dispatches on prefix; both resolve to the same
|
|
53
|
+
* organization context.
|
|
43
54
|
*/
|
|
44
55
|
sessionToken?: string;
|
|
45
56
|
};
|
package/dench.ts
CHANGED
|
@@ -1620,11 +1620,27 @@ function hasDevEnv() {
|
|
|
1620
1620
|
);
|
|
1621
1621
|
}
|
|
1622
1622
|
|
|
1623
|
+
/**
|
|
1624
|
+
* Resolve the Convex URL the API-key runtime should talk to. Sandboxes
|
|
1625
|
+
* bake `CONVEX_URL` (see `src/lib/secrets/sandbox-tokens.ts`); local
|
|
1626
|
+
* dev typically only has `NEXT_PUBLIC_CONVEX_URL` exported. We accept
|
|
1627
|
+
* either so the same code path works in both shells without callers
|
|
1628
|
+
* needing to know which env var is set.
|
|
1629
|
+
*/
|
|
1630
|
+
function resolveApiKeyConvexUrl(): string | undefined {
|
|
1631
|
+
return (
|
|
1632
|
+
process.env.CONVEX_URL?.trim() ||
|
|
1633
|
+
process.env.NEXT_PUBLIC_CONVEX_URL?.trim() ||
|
|
1634
|
+
undefined
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1623
1638
|
function loginRequiredError(command = "this command") {
|
|
1624
1639
|
return new CliError(`${command} requires dench login`, {
|
|
1625
1640
|
code: "login_required",
|
|
1626
1641
|
nextActions: [
|
|
1627
|
-
|
|
1642
|
+
"Set DENCH_API_KEY (and CONVEX_URL) in your env if running inside a sandbox or CI.",
|
|
1643
|
+
'Or run dench onboard --kind <kind> --name "AI Agent - Project" to mint an agent session.',
|
|
1628
1644
|
"If already approved, run dench sessions --json, then dench use <session-key-or-workspace-slug>.",
|
|
1629
1645
|
],
|
|
1630
1646
|
});
|
|
@@ -1670,6 +1686,33 @@ async function getRuntime() {
|
|
|
1670
1686
|
],
|
|
1671
1687
|
});
|
|
1672
1688
|
}
|
|
1689
|
+
|
|
1690
|
+
// Sandbox / automation path: the unified Dench API key + Convex URL
|
|
1691
|
+
// baked into sandbox envVars (see
|
|
1692
|
+
// src/lib/secrets/sandbox-tokens.ts) is enough to drive every
|
|
1693
|
+
// command that goes through `requireCrmAccess` server-side. We only
|
|
1694
|
+
// fall into this branch when no stored session is present, so
|
|
1695
|
+
// explicit `dench login` setups keep their existing behavior.
|
|
1696
|
+
const apiKey = process.env.DENCH_API_KEY?.trim();
|
|
1697
|
+
const convexUrl = resolveApiKeyConvexUrl();
|
|
1698
|
+
if (apiKey && convexUrl) {
|
|
1699
|
+
const orgId = process.env.DENCH_ORG_ID?.trim();
|
|
1700
|
+
const orgSlug = process.env.DENCH_ORG_SLUG?.trim();
|
|
1701
|
+
const apiHost =
|
|
1702
|
+
process.env.DENCH_API_URL?.trim() ||
|
|
1703
|
+
process.env.DENCH_INTERNAL_BASE?.trim() ||
|
|
1704
|
+
host;
|
|
1705
|
+
return {
|
|
1706
|
+
mode: "apiKey" as const,
|
|
1707
|
+
host: apiHost,
|
|
1708
|
+
client: new ConvexHttpClient(convexUrl),
|
|
1709
|
+
sessionToken: apiKey,
|
|
1710
|
+
organization:
|
|
1711
|
+
orgId && orgSlug
|
|
1712
|
+
? { id: orgId, name: orgSlug, slug: orgSlug }
|
|
1713
|
+
: undefined,
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1673
1716
|
}
|
|
1674
1717
|
|
|
1675
1718
|
if (!hasDevEnv()) {
|
|
@@ -1688,14 +1731,73 @@ async function getRuntime() {
|
|
|
1688
1731
|
|
|
1689
1732
|
type Runtime = Awaited<ReturnType<typeof getRuntime>>;
|
|
1690
1733
|
|
|
1734
|
+
/**
|
|
1735
|
+
* `session` mode is required for `agentWorkspace.*` calls because they
|
|
1736
|
+
* gate on `validateAgentSession` server-side, which only accepts the
|
|
1737
|
+
* `dch_agent_*` token format. Sandboxes that only have a unified
|
|
1738
|
+
* `DENCH_API_KEY` (apiKey mode) cannot impersonate a specific agent
|
|
1739
|
+
* and will fail at the server with `Invalid agent session` — surface
|
|
1740
|
+
* that here as a clear error instead.
|
|
1741
|
+
*/
|
|
1691
1742
|
function requireSessionRuntime(
|
|
1692
1743
|
runtime: Runtime,
|
|
1693
1744
|
command: string,
|
|
1694
1745
|
): Extract<Runtime, { mode: "session" }> {
|
|
1695
|
-
if (runtime.mode
|
|
1696
|
-
|
|
1746
|
+
if (runtime.mode === "session") return runtime;
|
|
1747
|
+
if (runtime.mode === "apiKey") {
|
|
1748
|
+
throw agentSessionRequiredError(command);
|
|
1749
|
+
}
|
|
1750
|
+
throw loginRequiredError(command);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Org-scoped commands (`dench crm`, `dench files`, etc.) accept either
|
|
1755
|
+
* a `dch_agent_*` agent session token OR a unified Dench API key
|
|
1756
|
+
* (`dench_*` / `gw_sk_*` / etc.) baked into sandbox envVars. The
|
|
1757
|
+
* server-side `requireCrmAccess` helper dispatches on the prefix.
|
|
1758
|
+
*/
|
|
1759
|
+
type AuthenticatedRuntime = Extract<
|
|
1760
|
+
Runtime,
|
|
1761
|
+
{ mode: "session" | "apiKey" }
|
|
1762
|
+
>;
|
|
1763
|
+
|
|
1764
|
+
function requireAuthenticatedRuntime(
|
|
1765
|
+
runtime: Runtime,
|
|
1766
|
+
command: string,
|
|
1767
|
+
): AuthenticatedRuntime {
|
|
1768
|
+
if (runtime.mode === "session" || runtime.mode === "apiKey") return runtime;
|
|
1769
|
+
throw loginRequiredError(command);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function agentSessionRequiredError(command: string) {
|
|
1773
|
+
return new CliError(
|
|
1774
|
+
`${command} requires an agent session — DENCH_API_KEY is org-scoped and cannot impersonate a specific agent`,
|
|
1775
|
+
{
|
|
1776
|
+
code: "agent_session_required",
|
|
1777
|
+
nextActions: [
|
|
1778
|
+
'Run dench onboard --kind <kind> --name "AI Agent - Project" to mint an agent session for this command.',
|
|
1779
|
+
"Or run the equivalent action via the workspace UI / dench crm if it is org-level (e.g. CRM data, files).",
|
|
1780
|
+
],
|
|
1781
|
+
},
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* Some legacy `agentWorkspace.*` commands (task create / claim / log /
|
|
1787
|
+
* approval request) have separate session vs dev branches. With apiKey
|
|
1788
|
+
* mode added, the dev branch would silently receive partial args. Use
|
|
1789
|
+
* this to short-circuit those branches with a clear error.
|
|
1790
|
+
*
|
|
1791
|
+
* Asserts away the `apiKey` mode so the caller's downstream type
|
|
1792
|
+
* narrowing (e.g. `runtime.workspaceArgs`) keeps working.
|
|
1793
|
+
*/
|
|
1794
|
+
function rejectApiKeyForAgentCommand(
|
|
1795
|
+
runtime: Runtime,
|
|
1796
|
+
command: string,
|
|
1797
|
+
): asserts runtime is Exclude<Runtime, { mode: "apiKey" }> {
|
|
1798
|
+
if (runtime.mode === "apiKey") {
|
|
1799
|
+
throw agentSessionRequiredError(command);
|
|
1697
1800
|
}
|
|
1698
|
-
return runtime;
|
|
1699
1801
|
}
|
|
1700
1802
|
|
|
1701
1803
|
async function printToolStatus(runtime: Runtime, toolkit?: string) {
|
|
@@ -1927,23 +2029,36 @@ async function connectedAppsContext(runtime: Runtime) {
|
|
|
1927
2029
|
}
|
|
1928
2030
|
|
|
1929
2031
|
async function buildContext(runtime: Runtime) {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2032
|
+
let mine: JsonRecord;
|
|
2033
|
+
let urls: JsonRecord;
|
|
2034
|
+
if (runtime.mode === "session") {
|
|
2035
|
+
mine = (await runtime.client.query(
|
|
2036
|
+
api.functions.agentWorkspace.whatCanIDoHere,
|
|
2037
|
+
{ sessionToken: runtime.sessionToken },
|
|
2038
|
+
)) as JsonRecord;
|
|
2039
|
+
urls = workspaceUrls(runtime.host, workspaceSlugFromContext(mine));
|
|
2040
|
+
} else if (runtime.mode === "dev") {
|
|
2041
|
+
mine = summarizeDevMine(
|
|
2042
|
+
runtime.workspaceArgs.workspaceSlug,
|
|
2043
|
+
await devOverview(runtime),
|
|
2044
|
+
);
|
|
2045
|
+
urls = {};
|
|
2046
|
+
} else {
|
|
2047
|
+
// apiKey mode: no agent session is available, so we can't call
|
|
2048
|
+
// `whatCanIDoHere` (it gates on `validateAgentSession`). Return a
|
|
2049
|
+
// minimal context derived from sandbox envVars so dench-cli help
|
|
2050
|
+
// surfaces still work without a full login.
|
|
2051
|
+
mine = {
|
|
2052
|
+
authMode: "apiKey",
|
|
2053
|
+
organization: runtime.organization ?? null,
|
|
2054
|
+
runId: process.env.DENCH_RUN_ID?.trim() ?? null,
|
|
2055
|
+
parentRunId: process.env.DENCH_PARENT_RUN_ID?.trim() ?? null,
|
|
2056
|
+
rootRunId: process.env.DENCH_ROOT_RUN_ID?.trim() ?? null,
|
|
2057
|
+
};
|
|
2058
|
+
urls = runtime.host
|
|
2059
|
+
? workspaceUrls(runtime.host, runtime.organization?.slug)
|
|
1946
2060
|
: {};
|
|
2061
|
+
}
|
|
1947
2062
|
|
|
1948
2063
|
return {
|
|
1949
2064
|
...mine,
|
|
@@ -2341,7 +2456,7 @@ function withToolRunGuidance(runtime: Runtime, result: unknown) {
|
|
|
2341
2456
|
if (!record || record.requiresApproval !== true) return result;
|
|
2342
2457
|
const approvalId = stringField(record, "approvalId");
|
|
2343
2458
|
const statusContext =
|
|
2344
|
-
runtime.mode === "session"
|
|
2459
|
+
runtime.mode === "session" || runtime.mode === "apiKey"
|
|
2345
2460
|
? workspaceUrls(runtime.host, runtime.organization?.slug)
|
|
2346
2461
|
: {};
|
|
2347
2462
|
const approvalCommand = approvalId
|
|
@@ -2501,6 +2616,12 @@ async function listArtifacts(runtime: Runtime, limit?: number) {
|
|
|
2501
2616
|
);
|
|
2502
2617
|
}
|
|
2503
2618
|
}
|
|
2619
|
+
if (runtime.mode === "apiKey") {
|
|
2620
|
+
// No agent identity → no artifact list. Return empty so callers
|
|
2621
|
+
// (e.g. `dench artifacts`, `dench suggested-work`) degrade gracefully
|
|
2622
|
+
// instead of throwing for sandbox callers.
|
|
2623
|
+
return [] as JsonRecord[];
|
|
2624
|
+
}
|
|
2504
2625
|
const data = await devOverview(runtime);
|
|
2505
2626
|
return asRecordArray(data.recentArtifacts);
|
|
2506
2627
|
}
|
|
@@ -2675,14 +2796,16 @@ async function main() {
|
|
|
2675
2796
|
}
|
|
2676
2797
|
const { runCrmCommand } = await import("./crm");
|
|
2677
2798
|
const runtime = await getRuntime();
|
|
2678
|
-
|
|
2799
|
+
// CRM is org-scoped — accept either an agent session token
|
|
2800
|
+
// (`dch_agent_*` from `dench login`) or a unified Dench API key
|
|
2801
|
+
// (`DENCH_API_KEY` baked into sandbox envVars). Server-side
|
|
2802
|
+
// `requireCrmAccess` (convex/lib/crm/access.ts) dispatches on the
|
|
2803
|
+
// bearer prefix and resolves the org for both.
|
|
2804
|
+
const authedRuntime = requireAuthenticatedRuntime(runtime, "dench crm");
|
|
2679
2805
|
await runCrmCommand({
|
|
2680
|
-
convex:
|
|
2806
|
+
convex: authedRuntime.client,
|
|
2681
2807
|
args: subArgs,
|
|
2682
|
-
|
|
2683
|
-
// this token and resolves the agent's organization without a Convex
|
|
2684
|
-
// Auth cookie. Mirrors the pattern used by every agentWorkspace fn.
|
|
2685
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
2808
|
+
sessionToken: authedRuntime.sessionToken,
|
|
2686
2809
|
});
|
|
2687
2810
|
return;
|
|
2688
2811
|
}
|
|
@@ -2881,6 +3004,13 @@ async function main() {
|
|
|
2881
3004
|
print(status);
|
|
2882
3005
|
return;
|
|
2883
3006
|
}
|
|
3007
|
+
if (runtime.mode === "apiKey") {
|
|
3008
|
+
// No agent session → no agent-status query. Fall back to a thin
|
|
3009
|
+
// env-derived snapshot so sandbox callers still get something
|
|
3010
|
+
// useful from `dench status`.
|
|
3011
|
+
print(await buildContext(runtime));
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
2884
3014
|
const data = await devOverview(runtime);
|
|
2885
3015
|
const output = scopedStatus
|
|
2886
3016
|
? summarizeDevMine(runtime.workspaceArgs.workspaceSlug, data)
|
|
@@ -2903,6 +3033,11 @@ async function main() {
|
|
|
2903
3033
|
);
|
|
2904
3034
|
return;
|
|
2905
3035
|
}
|
|
3036
|
+
if (runtime.mode === "apiKey") {
|
|
3037
|
+
throw loginRequiredError(
|
|
3038
|
+
"dench tasks (agent-scoped task list)",
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
2906
3041
|
const data = await devOverview(runtime);
|
|
2907
3042
|
print(data.tasks);
|
|
2908
3043
|
return;
|
|
@@ -2919,6 +3054,9 @@ async function main() {
|
|
|
2919
3054
|
print(status.agents);
|
|
2920
3055
|
return;
|
|
2921
3056
|
}
|
|
3057
|
+
if (runtime.mode === "apiKey") {
|
|
3058
|
+
throw loginRequiredError("dench agents");
|
|
3059
|
+
}
|
|
2922
3060
|
const data = await devOverview(runtime);
|
|
2923
3061
|
print(data.agents);
|
|
2924
3062
|
return;
|
|
@@ -2935,6 +3073,9 @@ async function main() {
|
|
|
2935
3073
|
print(status.approvals);
|
|
2936
3074
|
return;
|
|
2937
3075
|
}
|
|
3076
|
+
if (runtime.mode === "apiKey") {
|
|
3077
|
+
throw loginRequiredError("dench approvals");
|
|
3078
|
+
}
|
|
2938
3079
|
const data = await devOverview(runtime);
|
|
2939
3080
|
print(data.approvals);
|
|
2940
3081
|
return;
|
|
@@ -3000,6 +3141,7 @@ async function main() {
|
|
|
3000
3141
|
);
|
|
3001
3142
|
return;
|
|
3002
3143
|
}
|
|
3144
|
+
rejectApiKeyForAgentCommand(runtime, "dench task create");
|
|
3003
3145
|
const taskId = await runtime.client.mutation(
|
|
3004
3146
|
api.functions.agentWorkspace.devCreateTask,
|
|
3005
3147
|
{
|
|
@@ -3108,6 +3250,7 @@ async function main() {
|
|
|
3108
3250
|
);
|
|
3109
3251
|
return;
|
|
3110
3252
|
}
|
|
3253
|
+
rejectApiKeyForAgentCommand(runtime, "dench claim");
|
|
3111
3254
|
const agentId = option("--agent") ?? (await defaultDevAgentId(runtime));
|
|
3112
3255
|
print(
|
|
3113
3256
|
await runtime.client.mutation(api.functions.agentWorkspace.devClaimTask, {
|
|
@@ -3135,6 +3278,7 @@ async function main() {
|
|
|
3135
3278
|
);
|
|
3136
3279
|
return;
|
|
3137
3280
|
}
|
|
3281
|
+
rejectApiKeyForAgentCommand(runtime, "dench log");
|
|
3138
3282
|
print(
|
|
3139
3283
|
await runtime.client.mutation(api.functions.agentWorkspace.devAppendLog, {
|
|
3140
3284
|
...runtime.workspaceArgs,
|
|
@@ -3164,6 +3308,7 @@ async function main() {
|
|
|
3164
3308
|
);
|
|
3165
3309
|
return;
|
|
3166
3310
|
}
|
|
3311
|
+
rejectApiKeyForAgentCommand(runtime, "dench approval request");
|
|
3167
3312
|
const approvalId = await runtime.client.mutation(
|
|
3168
3313
|
api.functions.agentWorkspace.devRequestApproval,
|
|
3169
3314
|
{
|
package/fs-daemon.ts
CHANGED
|
@@ -359,15 +359,23 @@ async function pullDownRemoteFile(
|
|
|
359
359
|
);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
+
/**
|
|
363
|
+
* sysexits.h-style exit codes the supervisor (in
|
|
364
|
+
* src/lib/daytona/sandbox-bootstrap.ts) recognizes as **permanent** —
|
|
365
|
+
* keep restarting won't fix a missing env var or a missing dependency,
|
|
366
|
+
* so the supervisor gives up instead of crash-looping. Anything else
|
|
367
|
+
* (network blip, transient Convex error) exits 1 and gets retried.
|
|
368
|
+
*/
|
|
369
|
+
const EXIT_CONFIG_ERROR = 78;
|
|
370
|
+
|
|
362
371
|
async function runDaemon(daemonArgs: Args): Promise<void> {
|
|
363
372
|
const convexUrl = process.env.CONVEX_URL?.trim();
|
|
364
373
|
const apiKey = process.env.DENCH_API_KEY?.trim();
|
|
365
374
|
if (!convexUrl || !apiKey) {
|
|
366
375
|
console.error(
|
|
367
|
-
"[dench-fs-daemon] CONVEX_URL or DENCH_API_KEY missing in env;
|
|
376
|
+
"[dench-fs-daemon] CONVEX_URL or DENCH_API_KEY missing in env; refusing to start",
|
|
368
377
|
);
|
|
369
|
-
|
|
370
|
-
return;
|
|
378
|
+
process.exit(EXIT_CONFIG_ERROR);
|
|
371
379
|
}
|
|
372
380
|
|
|
373
381
|
const client = new DenchFileClient(convexUrl, apiKey);
|
|
@@ -386,12 +394,11 @@ async function runDaemon(daemonArgs: Args): Promise<void> {
|
|
|
386
394
|
chokidar = (await import("chokidar")) as never;
|
|
387
395
|
} catch (error) {
|
|
388
396
|
console.error(
|
|
389
|
-
`[dench-fs-daemon] chokidar not available;
|
|
397
|
+
`[dench-fs-daemon] chokidar not available; refusing to start (${
|
|
390
398
|
error instanceof Error ? error.message : String(error)
|
|
391
399
|
})`,
|
|
392
400
|
);
|
|
393
|
-
|
|
394
|
-
return;
|
|
401
|
+
process.exit(EXIT_CONFIG_ERROR);
|
|
395
402
|
}
|
|
396
403
|
|
|
397
404
|
const watcher = chokidar.watch(daemonArgs.workspace, {
|
|
@@ -705,10 +712,10 @@ main().catch((error) => {
|
|
|
705
712
|
error instanceof Error ? (error.stack ?? error.message) : String(error)
|
|
706
713
|
}`,
|
|
707
714
|
);
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
715
|
+
// Always exit with a non-zero code so the supervisor (in
|
|
716
|
+
// src/lib/daytona/sandbox-bootstrap.ts) can decide whether to
|
|
717
|
+
// restart. Previously we parked the process on a noop interval,
|
|
718
|
+
// which made `pgrep dench-fs-daemon` report "alive" for a daemon
|
|
719
|
+
// that had silently given up syncing.
|
|
720
|
+
process.exit(1);
|
|
714
721
|
});
|