feat: Discard all, claude-style image pastes, large local drops, per-account / list
- Changes panel: **Discard all** beside Commit discards every non-conflicted file in one `discard`, after a dialog naming how many untracked files go. - Terminal: a `claude` pane gets dropped/pasted images as bare paths (shown as `[Image #n]`) and other files as `@` mentions (`claudePastes`); a local browser's copied `file://` paths and text-drag file lists paste by path. - `POST /api/run/drop-file`: a loopback browser outside server mode may drop up to 2 GB, streamed to disk; remote stays 20 MB. - cc-config skills/commands/plugins read the USER scope of the account the pane runs as (S3 member home by `cwd`), never another user's or the host's for a non-admin without an account; the run form passes its cwd to plugins.
This commit is contained in:
@@ -95,16 +95,45 @@ function cwdOf(req) {
|
||||
}
|
||||
|
||||
/**
|
||||
* `{scope, cwd}` for the two lists a member may read (the run form's `/`
|
||||
* The Claude home whose USER skills, commands and plugins a request's sessions
|
||||
* load — so the run form's `/` list matches what its pane's Claude can call.
|
||||
* Under S3 a pane runs as its FOLDER's account: `?cwd=` picks that account; a
|
||||
* non-admin with no `?cwd=` reads their own. Undefined = the host's (local
|
||||
* mode, or an admin in a host folder / the Config Explorer); null = none, for
|
||||
* a non-admin with no account of their own — another user's or the host's
|
||||
* skills are never listed as theirs.
|
||||
*/
|
||||
function userClaudeDir(req) {
|
||||
if (!req.user) return undefined;
|
||||
const runAs = require("../lib/run-as");
|
||||
const cwd = typeof req.query.cwd === "string" && req.query.cwd;
|
||||
let acct = null;
|
||||
try {
|
||||
if (cwd && path.isAbsolute(cwd)) acct = runAs.ownerForRead(path.resolve(cwd));
|
||||
else if (req.user.role !== "admin") acct = runAs.accountForUser(req.user.username);
|
||||
} catch {
|
||||
acct = null;
|
||||
}
|
||||
if (acct) return path.join(acct.home, ".claude");
|
||||
return req.user.role === "admin" ? undefined : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* `{scope, cwd, userClaudeDir}` for the two lists a member may read (the run form's `/`
|
||||
* autocomplete). Server mode keeps a member's project items to their own
|
||||
* workspaces: another folder answers 403 EFOLDER, and with no `?cwd=` there is
|
||||
* no project at all (never the dashboard's own working directory). Returns null
|
||||
* once it has answered; `none` = nothing to list.
|
||||
*/
|
||||
function listOpts(req, res) {
|
||||
const opts = { scope: scopeOf(req), cwd: cwdOf(req) };
|
||||
if (scope.memberId(req) == null) return opts;
|
||||
const opts = { scope: scopeOf(req), cwd: cwdOf(req), userClaudeDir: userClaudeDir(req) };
|
||||
const asked = typeof req.query.cwd === "string" && req.query.cwd;
|
||||
// No account of their own: only a named workspace's project items, if any.
|
||||
if (opts.userClaudeDir === null) {
|
||||
if (opts.scope === "user" || !asked) return { none: true };
|
||||
opts.scope = "project";
|
||||
}
|
||||
if (scope.memberId(req) == null) return opts;
|
||||
if (!asked) return opts.scope === "project" ? { none: true } : { ...opts, scope: "user" };
|
||||
if (!path.isAbsolute(asked) || !scope.insideOwnWorkspace(req, path.resolve(asked))) {
|
||||
res.status(403).json({
|
||||
@@ -137,8 +166,10 @@ router.get("/output-styles", (req, res) => {
|
||||
res.json({ items: cc.readOutputStyles({ scope: scopeOf(req), cwd: cwdOf(req) }) });
|
||||
});
|
||||
|
||||
router.get("/plugins", (_req, res) => {
|
||||
res.json(cc.readPlugins());
|
||||
router.get("/plugins", (req, res) => {
|
||||
const home = userClaudeDir(req);
|
||||
if (home === null) return res.json({ manifestPath: null, manifestExists: false, plugins: [] });
|
||||
res.json(cc.readPlugins(home));
|
||||
});
|
||||
|
||||
router.get("/mcp", (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user