@dench.com/cli 2.7.2 → 2.7.4
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/apps.ts +226 -0
- package/dench.ts +128 -9
- package/lib/command-registry.ts +26 -0
- package/package.json +2 -1
- package/session.ts +20 -0
package/apps.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dench apps` — the App Store surface (create / list / gallery / install /
|
|
3
|
+
* publish / uninstall / enable / pin). Distinct from `dench integrations`
|
|
4
|
+
* (Composio connected apps). Backed by convex/functions/appStore.ts — the same
|
|
5
|
+
* functions the workspace App Store panel and the API v1 `/apps/*` routes call.
|
|
6
|
+
*
|
|
7
|
+
* Thin dispatcher; forwards the caller's bearer through `sessionToken`, which
|
|
8
|
+
* the server's requireCrmAccess accepts (and appStore forwards to the file
|
|
9
|
+
* functions as an apiKey for publish/install/create).
|
|
10
|
+
*/
|
|
11
|
+
import type { ConvexHttpClient } from "convex/browser";
|
|
12
|
+
import { makeFunctionReference } from "convex/server";
|
|
13
|
+
|
|
14
|
+
const api = {
|
|
15
|
+
listInstalled: makeFunctionReference<"query">(
|
|
16
|
+
"functions/appStore:listInstalled",
|
|
17
|
+
),
|
|
18
|
+
listListings: makeFunctionReference<"query">(
|
|
19
|
+
"functions/appStore:listListings",
|
|
20
|
+
),
|
|
21
|
+
listGallery: makeFunctionReference<"query">("functions/appStore:listGallery"),
|
|
22
|
+
install: makeFunctionReference<"action">("functions/appStore:install"),
|
|
23
|
+
publish: makeFunctionReference<"action">("functions/appStore:publish"),
|
|
24
|
+
createApp: makeFunctionReference<"action">("functions/appStore:createApp"),
|
|
25
|
+
uninstall: makeFunctionReference<"mutation">("functions/appStore:uninstall"),
|
|
26
|
+
setEnabled: makeFunctionReference<"mutation">(
|
|
27
|
+
"functions/appStore:setEnabled",
|
|
28
|
+
),
|
|
29
|
+
setPinned: makeFunctionReference<"mutation">("functions/appStore:setPinned"),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class AppsCliError extends Error {}
|
|
33
|
+
|
|
34
|
+
type Ctx = {
|
|
35
|
+
convex: ConvexHttpClient;
|
|
36
|
+
args: string[];
|
|
37
|
+
sessionToken?: string;
|
|
38
|
+
jsonOutput: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function shift(args: string[], label: string): string {
|
|
42
|
+
const value = args.shift();
|
|
43
|
+
if (!value) throw new AppsCliError(`Missing ${label}`);
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function takeFlag(args: string[], name: string): string | undefined {
|
|
48
|
+
const i = args.indexOf(name);
|
|
49
|
+
if (i === -1) return undefined;
|
|
50
|
+
const next = args[i + 1];
|
|
51
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
52
|
+
args.splice(i, 2);
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
args.splice(i, 1);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function firstPositional(args: string[]): string | undefined {
|
|
60
|
+
return args.find((a) => !a.startsWith("--"));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function out(ctx: Ctx, value: unknown) {
|
|
64
|
+
console.log(JSON.stringify(value, null, ctx.jsonOutput ? 0 : 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function withAuth(ctx: Ctx, args: Record<string, unknown>) {
|
|
68
|
+
return {
|
|
69
|
+
...args,
|
|
70
|
+
...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
|
|
71
|
+
} as never;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function appsHelp() {
|
|
75
|
+
console.log(`Usage: dench apps <subcommand> (App Store — not Composio; see 'dench integrations')
|
|
76
|
+
|
|
77
|
+
dench apps list [--json] Installed apps in this workspace
|
|
78
|
+
dench apps gallery [--json] Discover listings — Official (Dench) + Community
|
|
79
|
+
dench apps info <slug> [--json] One installed app's record
|
|
80
|
+
dench apps create <slug> --name "My App" [--icon <name>] [--description "…"]
|
|
81
|
+
Scaffold .dench.yaml + index.html and register it
|
|
82
|
+
dench apps install <listingId> [--json] Install a gallery listing into this workspace
|
|
83
|
+
dench apps publish <appPath> --slug <slug> --name "My App" \\
|
|
84
|
+
[--description "…"] [--icon <name>] [--visibility org|public|official|unlisted] [--version 1.0.0]
|
|
85
|
+
Bundle an app's files into a version + listing
|
|
86
|
+
dench apps uninstall <slug> Remove an installed app
|
|
87
|
+
dench apps enable | disable <slug> Toggle whether the app loads
|
|
88
|
+
dench apps pin | unpin <slug> Toggle the rail pin
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function runAppsCommand(opts: {
|
|
93
|
+
convex: ConvexHttpClient;
|
|
94
|
+
args: string[];
|
|
95
|
+
sessionToken?: string;
|
|
96
|
+
}): Promise<void> {
|
|
97
|
+
const jsonOutput = opts.args.includes("--json");
|
|
98
|
+
const args = opts.args.filter((a) => a !== "--json");
|
|
99
|
+
const ctx: Ctx = {
|
|
100
|
+
convex: opts.convex,
|
|
101
|
+
args,
|
|
102
|
+
sessionToken: opts.sessionToken,
|
|
103
|
+
jsonOutput,
|
|
104
|
+
};
|
|
105
|
+
const sub = ctx.args.shift();
|
|
106
|
+
switch (sub) {
|
|
107
|
+
case undefined:
|
|
108
|
+
if (jsonOutput) {
|
|
109
|
+
out(ctx, await ctx.convex.query(api.listInstalled, withAuth(ctx, {})));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
appsHelp();
|
|
113
|
+
return;
|
|
114
|
+
case "help":
|
|
115
|
+
case "--help":
|
|
116
|
+
appsHelp();
|
|
117
|
+
return;
|
|
118
|
+
case "list":
|
|
119
|
+
out(ctx, await ctx.convex.query(api.listInstalled, withAuth(ctx, {})));
|
|
120
|
+
return;
|
|
121
|
+
case "gallery":
|
|
122
|
+
out(ctx, await ctx.convex.query(api.listGallery, withAuth(ctx, {})));
|
|
123
|
+
return;
|
|
124
|
+
case "info": {
|
|
125
|
+
const slug = shift(ctx.args, "app slug");
|
|
126
|
+
const installed = (await ctx.convex.query(
|
|
127
|
+
api.listInstalled,
|
|
128
|
+
withAuth(ctx, {}),
|
|
129
|
+
)) as Array<{ slug: string }>;
|
|
130
|
+
out(ctx, installed.find((a) => a.slug === slug) ?? null);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
case "create": {
|
|
134
|
+
const name = takeFlag(ctx.args, "--name");
|
|
135
|
+
const icon = takeFlag(ctx.args, "--icon");
|
|
136
|
+
const description = takeFlag(ctx.args, "--description");
|
|
137
|
+
const slug = firstPositional(ctx.args) ?? name;
|
|
138
|
+
if (!slug) {
|
|
139
|
+
throw new AppsCliError(
|
|
140
|
+
'Usage: dench apps create <slug> --name "My App" [--icon <name>]',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
out(
|
|
144
|
+
ctx,
|
|
145
|
+
await ctx.convex.action(
|
|
146
|
+
api.createApp,
|
|
147
|
+
withAuth(ctx, {
|
|
148
|
+
slug,
|
|
149
|
+
name: name ?? slug,
|
|
150
|
+
...(icon ? { icon } : {}),
|
|
151
|
+
...(description ? { description } : {}),
|
|
152
|
+
}),
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
case "install": {
|
|
158
|
+
const listingId = shift(ctx.args, "listing id");
|
|
159
|
+
out(
|
|
160
|
+
ctx,
|
|
161
|
+
await ctx.convex.action(api.install, withAuth(ctx, { listingId })),
|
|
162
|
+
);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
case "publish": {
|
|
166
|
+
const slug = takeFlag(ctx.args, "--slug");
|
|
167
|
+
const name = takeFlag(ctx.args, "--name");
|
|
168
|
+
const description = takeFlag(ctx.args, "--description");
|
|
169
|
+
const icon = takeFlag(ctx.args, "--icon");
|
|
170
|
+
const visibility = takeFlag(ctx.args, "--visibility");
|
|
171
|
+
const version = takeFlag(ctx.args, "--version");
|
|
172
|
+
const appPath = firstPositional(ctx.args);
|
|
173
|
+
if (!appPath || !slug || !name) {
|
|
174
|
+
throw new AppsCliError(
|
|
175
|
+
'Usage: dench apps publish <appPath> --slug <slug> --name "My App"',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
out(
|
|
179
|
+
ctx,
|
|
180
|
+
await ctx.convex.action(
|
|
181
|
+
api.publish,
|
|
182
|
+
withAuth(ctx, {
|
|
183
|
+
appPath,
|
|
184
|
+
slug,
|
|
185
|
+
name,
|
|
186
|
+
...(description ? { description } : {}),
|
|
187
|
+
...(icon ? { icon } : {}),
|
|
188
|
+
...(visibility ? { visibility } : {}),
|
|
189
|
+
...(version ? { version } : {}),
|
|
190
|
+
}),
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
case "uninstall": {
|
|
196
|
+
const slug = shift(ctx.args, "app slug");
|
|
197
|
+
out(
|
|
198
|
+
ctx,
|
|
199
|
+
await ctx.convex.mutation(api.uninstall, withAuth(ctx, { slug })),
|
|
200
|
+
);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
case "enable":
|
|
204
|
+
case "disable": {
|
|
205
|
+
const slug = shift(ctx.args, "app slug");
|
|
206
|
+
await ctx.convex.mutation(
|
|
207
|
+
api.setEnabled,
|
|
208
|
+
withAuth(ctx, { slug, enabled: sub === "enable" }),
|
|
209
|
+
);
|
|
210
|
+
out(ctx, { ok: true, slug, enabled: sub === "enable" });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
case "pin":
|
|
214
|
+
case "unpin": {
|
|
215
|
+
const slug = shift(ctx.args, "app slug");
|
|
216
|
+
await ctx.convex.mutation(
|
|
217
|
+
api.setPinned,
|
|
218
|
+
withAuth(ctx, { slug, pinned: sub === "pin" }),
|
|
219
|
+
);
|
|
220
|
+
out(ctx, { ok: true, slug, pinned: sub === "pin" });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
default:
|
|
224
|
+
throw new AppsCliError(`Unknown apps subcommand: ${sub}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
package/dench.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type StoredSessionEntry,
|
|
41
41
|
selectStoredSession,
|
|
42
42
|
selectStoredSessionEntry,
|
|
43
|
+
usesInjectedAgentSession,
|
|
43
44
|
withCurrentSessionSelection,
|
|
44
45
|
withSavedSession,
|
|
45
46
|
} from "./session";
|
|
@@ -173,6 +174,12 @@ const api = {
|
|
|
173
174
|
},
|
|
174
175
|
files: {
|
|
175
176
|
listTree: makeFunctionReference<"query">("functions/files:listTree"),
|
|
177
|
+
getFileContent: makeFunctionReference<"query">(
|
|
178
|
+
"functions/files:getFileContent",
|
|
179
|
+
),
|
|
180
|
+
editFileFromUI: makeFunctionReference<"action">(
|
|
181
|
+
"functions/files:editFileFromUI",
|
|
182
|
+
),
|
|
176
183
|
deleteFile: makeFunctionReference<"mutation">(
|
|
177
184
|
"functions/files:deleteFile",
|
|
178
185
|
),
|
|
@@ -470,7 +477,7 @@ Usage:
|
|
|
470
477
|
dench approval reject <approvalId> [--evidence "User said no in chat"] [--json]
|
|
471
478
|
dench billing status [--json]
|
|
472
479
|
dench billing topup --amount 5 [--no-open] [--json]
|
|
473
|
-
dench
|
|
480
|
+
dench integrations [--json]
|
|
474
481
|
dench tool status [toolkit] [--json]
|
|
475
482
|
dench tool connect <toolkit> [--json]
|
|
476
483
|
dench tool search "create github issue" [--toolkit github] [--limit 20] [--compact] [--json]
|
|
@@ -610,7 +617,8 @@ Self-updating agent harness:
|
|
|
610
617
|
Help: dench identity help
|
|
611
618
|
|
|
612
619
|
External tools:
|
|
613
|
-
dench
|
|
620
|
+
dench integrations is an alias for dench tool status -- it lists connected apps.
|
|
621
|
+
dench apps is the App Store (create / install / publish / list), not Composio.
|
|
614
622
|
dench tool with no subcommand prints tool help.
|
|
615
623
|
Read-only FETCH, GET, LIST, SEARCH, READ, and FIND tools do not need manual approval.
|
|
616
624
|
|
|
@@ -666,7 +674,7 @@ function toolHelp() {
|
|
|
666
674
|
console.log(`Dench tool commands
|
|
667
675
|
|
|
668
676
|
Usage:
|
|
669
|
-
dench
|
|
677
|
+
dench integrations [--json]
|
|
670
678
|
dench tool status [toolkit] [--json]
|
|
671
679
|
dench tool connect <toolkit> [--callback-url <url>] [--json]
|
|
672
680
|
dench tool search "create github issue" [--toolkit github] [--limit 20] [--json]
|
|
@@ -681,7 +689,7 @@ Auth:
|
|
|
681
689
|
same way (the CLI exchanges the session for a gateway key on demand).
|
|
682
690
|
|
|
683
691
|
Notes:
|
|
684
|
-
dench
|
|
692
|
+
dench integrations is an alias for dench tool status.
|
|
685
693
|
dench tool connect prints the OAuth redirect URL for the human to open.
|
|
686
694
|
dench tool run output is redacted for display; pass --json for raw JSON.
|
|
687
695
|
Override the gateway base with DENCH_GATEWAY_URL or GATEWAY_URL.
|
|
@@ -2392,7 +2400,13 @@ async function getRuntime() {
|
|
|
2392
2400
|
if (!hasFlag("--dev")) {
|
|
2393
2401
|
const config = await loadConfig();
|
|
2394
2402
|
const host = resolveHost(config, scope);
|
|
2395
|
-
|
|
2403
|
+
|
|
2404
|
+
// An injected agent-session token outranks whatever `dench signin` last
|
|
2405
|
+
// saved here — see `usesInjectedAgentSession` for why the API key does not
|
|
2406
|
+
// get the same treatment.
|
|
2407
|
+
const stored = usesInjectedAgentSession()
|
|
2408
|
+
? ({ status: "missing" } as const)
|
|
2409
|
+
: await getStoredSession(host, scope);
|
|
2396
2410
|
if (stored.status === "found") {
|
|
2397
2411
|
return {
|
|
2398
2412
|
mode: "session" as const,
|
|
@@ -2451,9 +2465,9 @@ async function getRuntime() {
|
|
|
2451
2465
|
};
|
|
2452
2466
|
}
|
|
2453
2467
|
|
|
2454
|
-
//
|
|
2455
|
-
//
|
|
2456
|
-
// only credential available in this branch.
|
|
2468
|
+
// No API key present: an older sandbox image, a custom setup, or Dench
|
|
2469
|
+
// Code, which injects only this token. Still subject to expiry, but it is
|
|
2470
|
+
// the only credential available in this branch.
|
|
2457
2471
|
const sandboxAgentToken = process.env.DENCH_AGENT_SESSION_TOKEN?.trim();
|
|
2458
2472
|
if (sandboxAgentToken && sandboxConvexUrl) {
|
|
2459
2473
|
const apiHost = resolveApiHost(host);
|
|
@@ -3413,6 +3427,105 @@ async function runFilesRm() {
|
|
|
3413
3427
|
});
|
|
3414
3428
|
}
|
|
3415
3429
|
|
|
3430
|
+
/**
|
|
3431
|
+
* Print a workspace file's text.
|
|
3432
|
+
*
|
|
3433
|
+
* `download` cannot reach the magic files (IDENTITY.md, ORGANISATION.md, …)
|
|
3434
|
+
* because they render from agentConfig and have no storage blob, so this is
|
|
3435
|
+
* the only way to read them from outside a browser session.
|
|
3436
|
+
*/
|
|
3437
|
+
async function runFilesCat() {
|
|
3438
|
+
const runtime = await getRuntime();
|
|
3439
|
+
const apiKey = await requireFilesApiKey(runtime, "dench files cat");
|
|
3440
|
+
const targetRaw = positional(2);
|
|
3441
|
+
if (!targetRaw) {
|
|
3442
|
+
throw new CliError("Usage: dench files cat <path> [--json]", {
|
|
3443
|
+
code: "files_cat_usage",
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
const target = normalizeFilesPath(targetRaw);
|
|
3447
|
+
const file = (await runtime.client.query(api.functions.files.getFileContent, {
|
|
3448
|
+
path: target,
|
|
3449
|
+
apiKey,
|
|
3450
|
+
} as never)) as {
|
|
3451
|
+
path: string;
|
|
3452
|
+
isDir: boolean;
|
|
3453
|
+
virtual: boolean;
|
|
3454
|
+
size: number | null;
|
|
3455
|
+
content: string | null;
|
|
3456
|
+
downloadable: boolean;
|
|
3457
|
+
} | null;
|
|
3458
|
+
|
|
3459
|
+
if (!file) {
|
|
3460
|
+
throw new CliError(`Not found in Convex: ${target}`, {
|
|
3461
|
+
code: "files_cat_not_found",
|
|
3462
|
+
nextActions: [
|
|
3463
|
+
"Run `dench files ls` on the parent directory to confirm.",
|
|
3464
|
+
"If the file only exists on the sandbox FS, run `dench fs sync --initial` first.",
|
|
3465
|
+
],
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
if (file.isDir) {
|
|
3469
|
+
throw new CliError(`${target} is a directory`, {
|
|
3470
|
+
code: "files_cat_directory",
|
|
3471
|
+
nextActions: ["Run `dench files ls` to list its entries."],
|
|
3472
|
+
});
|
|
3473
|
+
}
|
|
3474
|
+
if (file.content === null) {
|
|
3475
|
+
throw new CliError(`No inline text for ${target}`, {
|
|
3476
|
+
code: "files_cat_not_text",
|
|
3477
|
+
nextActions: file.downloadable
|
|
3478
|
+
? ["Run `dench files download` to fetch the bytes."]
|
|
3479
|
+
: ["The file has no stored bytes yet."],
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
if (hasFlag("--json")) {
|
|
3484
|
+
print({ ok: true, ...file });
|
|
3485
|
+
return;
|
|
3486
|
+
}
|
|
3487
|
+
process.stdout.write(
|
|
3488
|
+
file.content.endsWith("\n") ? file.content : `${file.content}\n`,
|
|
3489
|
+
);
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
/**
|
|
3493
|
+
* Replace a workspace file's text, reading the new content from stdin.
|
|
3494
|
+
*
|
|
3495
|
+
* `stage` copies from a sandbox path and `commitFileUpload` needs an upload
|
|
3496
|
+
* round-trip, so neither works for "change these few lines from a terminal".
|
|
3497
|
+
*/
|
|
3498
|
+
async function runFilesWrite() {
|
|
3499
|
+
const runtime = await getRuntime();
|
|
3500
|
+
const apiKey = await requireFilesApiKey(runtime, "dench files write");
|
|
3501
|
+
const targetRaw = positional(2);
|
|
3502
|
+
if (!targetRaw) {
|
|
3503
|
+
throw new CliError("Usage: dench files write <path> < file.md", {
|
|
3504
|
+
code: "files_write_usage",
|
|
3505
|
+
nextActions: ["Pipe the new contents in on stdin."],
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
const target = normalizeFilesPath(targetRaw);
|
|
3509
|
+
const content = await new Response(Bun.stdin.stream()).text();
|
|
3510
|
+
|
|
3511
|
+
const result = (await runtime.client.action(
|
|
3512
|
+
api.functions.files.editFileFromUI,
|
|
3513
|
+
{
|
|
3514
|
+
path: target,
|
|
3515
|
+
content,
|
|
3516
|
+
apiKey,
|
|
3517
|
+
} as never,
|
|
3518
|
+
)) as { path: string; contentHash: string };
|
|
3519
|
+
|
|
3520
|
+
if (hasFlag("--json")) {
|
|
3521
|
+
print({ ok: true, ...result, bytes: content.length });
|
|
3522
|
+
return;
|
|
3523
|
+
}
|
|
3524
|
+
process.stdout.write(
|
|
3525
|
+
`dench files write: ${result.path} (${content.length}B)\n`,
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3416
3529
|
async function writeLocalFileBytes(localPath: string, bytes: Uint8Array) {
|
|
3417
3530
|
const parent = dirname(localPath);
|
|
3418
3531
|
if (parent && parent !== ".") {
|
|
@@ -3547,15 +3660,19 @@ async function runFilesCommand() {
|
|
|
3547
3660
|
if (!sub || sub === "help" || hasFlag("--help")) {
|
|
3548
3661
|
process.stdout.write(
|
|
3549
3662
|
[
|
|
3550
|
-
"dench files <ls|mv|rm|download> — workspace file ops that update the UI tree",
|
|
3663
|
+
"dench files <ls|cat|write|mv|rm|download> — workspace file ops that update the UI tree",
|
|
3551
3664
|
"",
|
|
3552
3665
|
" dench files ls [<path>] [--recursive] [--json]",
|
|
3666
|
+
" dench files cat <path> [--json]",
|
|
3667
|
+
" dench files write <path> < file.md (content on stdin)",
|
|
3553
3668
|
" dench files mv <src> <dst> [--json]",
|
|
3554
3669
|
" dench files rm <path> [--recursive] [--json]",
|
|
3555
3670
|
" dench files download <path> [<local-dest>] [--url] [--json]",
|
|
3556
3671
|
"",
|
|
3557
3672
|
"Paths are POSIX paths under /workspace; you can pass either form.",
|
|
3558
3673
|
"These mutate Convex directly (and the daemon syncs to the volume).",
|
|
3674
|
+
"cat prints inline text and is the only way to read the magic files",
|
|
3675
|
+
"(IDENTITY.md, ORGANISATION.md, …), which have no stored bytes.",
|
|
3559
3676
|
"download fetches canonical bytes from Convex (dirs recurse);",
|
|
3560
3677
|
"--url prints a short-lived signed URL instead of saving.",
|
|
3561
3678
|
].join("\n"),
|
|
@@ -3563,6 +3680,8 @@ async function runFilesCommand() {
|
|
|
3563
3680
|
return;
|
|
3564
3681
|
}
|
|
3565
3682
|
if (sub === "ls") return await runFilesLs();
|
|
3683
|
+
if (sub === "cat") return await runFilesCat();
|
|
3684
|
+
if (sub === "write") return await runFilesWrite();
|
|
3566
3685
|
if (sub === "mv") return await runFilesMv();
|
|
3567
3686
|
if (sub === "rm") return await runFilesRm();
|
|
3568
3687
|
if (sub === "download") return await runFilesDownload();
|
package/lib/command-registry.ts
CHANGED
|
@@ -1957,6 +1957,32 @@ const rawApiOperations = [
|
|
|
1957
1957
|
tokenArg: "apiKey",
|
|
1958
1958
|
}),
|
|
1959
1959
|
}),
|
|
1960
|
+
op({
|
|
1961
|
+
id: "files.content",
|
|
1962
|
+
group: "files",
|
|
1963
|
+
summary: "Read a workspace file's text.",
|
|
1964
|
+
cli: "dench files cat",
|
|
1965
|
+
method: "GET",
|
|
1966
|
+
path: "/files/content",
|
|
1967
|
+
requestSchema: noBody,
|
|
1968
|
+
responseSchema: anyResponse,
|
|
1969
|
+
backend: convex("query", "functions/files:getFileContent", "bearer", {
|
|
1970
|
+
tokenArg: "apiKey",
|
|
1971
|
+
}),
|
|
1972
|
+
}),
|
|
1973
|
+
op({
|
|
1974
|
+
id: "files.write",
|
|
1975
|
+
group: "files",
|
|
1976
|
+
summary: "Write a workspace file's text.",
|
|
1977
|
+
cli: "dench files write",
|
|
1978
|
+
method: "PUT",
|
|
1979
|
+
path: "/files/content",
|
|
1980
|
+
requestSchema: anyObject,
|
|
1981
|
+
responseSchema: anyResponse,
|
|
1982
|
+
backend: convex("action", "functions/files:editFileFromUI", "bearer", {
|
|
1983
|
+
tokenArg: "apiKey",
|
|
1984
|
+
}),
|
|
1985
|
+
}),
|
|
1960
1986
|
op({
|
|
1961
1987
|
id: "files.downloadUrl",
|
|
1962
1988
|
group: "files",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.4",
|
|
4
4
|
"description": "Dench agent workspace CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"search.ts",
|
|
33
33
|
"image.ts",
|
|
34
34
|
"tools.ts",
|
|
35
|
+
"apps.ts",
|
|
35
36
|
"agentKind.ts",
|
|
36
37
|
"agent-config.ts",
|
|
37
38
|
"host.ts",
|
package/session.ts
CHANGED
|
@@ -208,6 +208,26 @@ export function resolveSessionScope({
|
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Whether an injected agent-session token should stand in for a saved
|
|
213
|
+
* `dench signin` session.
|
|
214
|
+
*
|
|
215
|
+
* `DENCH_AGENT_SESSION_TOKEN` names which identity to act as, so it outranks
|
|
216
|
+
* whatever was last saved on the host. Only sandboxes and Dench Code set it —
|
|
217
|
+
* nobody exports it by hand — and without this a host with any saved session
|
|
218
|
+
* silently ignores it, or fails as `session_ambiguous` when several are saved.
|
|
219
|
+
*
|
|
220
|
+
* `DENCH_API_KEY` deliberately does not qualify. People export that one
|
|
221
|
+
* manually for unrelated reasons, and quietly swapping their identity would be
|
|
222
|
+
* a surprise. Sandboxes set both and have no saved session anyway, so they
|
|
223
|
+
* keep landing on the API key, which is what they want: the key has no TTL
|
|
224
|
+
* while the agent-session token expires.
|
|
225
|
+
*/
|
|
226
|
+
export function usesInjectedAgentSession(env: Env = process.env) {
|
|
227
|
+
if (env.DENCH_API_KEY?.trim()) return false;
|
|
228
|
+
return Boolean(env.DENCH_AGENT_SESSION_TOKEN?.trim());
|
|
229
|
+
}
|
|
230
|
+
|
|
211
231
|
export function sessionConfigKey(host: string, scope: SessionScope) {
|
|
212
232
|
return `${normalizeHostValue(host)}#${scope.key}`;
|
|
213
233
|
}
|