@xbzbing/dsh-git-panel 0.2.0 → 0.3.0
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 +15 -16
- package/README_EN.md +15 -16
- package/cordis.patch.yml +1 -0
- package/lib/client.js +87 -36
- package/lib/host/core.d.ts +28 -2
- package/lib/host/git.d.ts +4 -1
- package/lib/host/index.d.ts +7 -4
- package/lib/host/index.js +150 -12
- package/lib/host/types.d.ts +36 -0
- package/package.json +4 -7
package/lib/host/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GitRunner } from './git.ts';
|
|
2
|
-
import type { GitChange, GitErrorCode, GitSnapshotResult } from './types.ts';
|
|
2
|
+
import type { DiffViewMode, GitChange, GitErrorCode, GitSnapshotResult } from './types.ts';
|
|
3
3
|
export interface GitPanelConfig {
|
|
4
4
|
readonly timeoutMs: number;
|
|
5
5
|
/** Per-command stdout cap; also the per-side image-diff payload cap. */
|
|
@@ -8,9 +8,13 @@ export interface GitPanelConfig {
|
|
|
8
8
|
readonly refreshIntervalMs: number;
|
|
9
9
|
/** Whether the input-bar git marker pill is shown. */
|
|
10
10
|
readonly showInputPill: boolean;
|
|
11
|
+
/** Default diff layout the views open with (user can switch per-diff). */
|
|
12
|
+
readonly defaultDiffView: DiffViewMode;
|
|
11
13
|
}
|
|
12
14
|
export declare const DEFAULT_CONFIG: GitPanelConfig;
|
|
13
15
|
export declare function normalizeConfig(raw: unknown): GitPanelConfig;
|
|
16
|
+
/** Read the default-diff-view field, unwrapping a schemastery volatile ref. */
|
|
17
|
+
export declare function readDiffView(value: unknown, fallback: DiffViewMode): DiffViewMode;
|
|
14
18
|
/**
|
|
15
19
|
* Read a boolean config field, unwrapping a schemastery volatile reference
|
|
16
20
|
* (`{ get() }`) so a live-editable toggle reflects the latest value. Absent or
|
|
@@ -28,6 +32,11 @@ export interface SnapshotDeps {
|
|
|
28
32
|
}>;
|
|
29
33
|
/** Raw bytes (no encoding) — image sides for the image-diff query. */
|
|
30
34
|
readFile(path: string): Promise<Buffer>;
|
|
35
|
+
/** One directory's entries (name + is-directory) — the file browser. */
|
|
36
|
+
readdir(path: string): Promise<ReadonlyArray<{
|
|
37
|
+
name: string;
|
|
38
|
+
isDirectory: boolean;
|
|
39
|
+
}>>;
|
|
31
40
|
/** Best-effort unlink (force) — temp-blob cleanup. */
|
|
32
41
|
remove(path: string): Promise<void>;
|
|
33
42
|
};
|
|
@@ -62,10 +71,27 @@ export type WorkspaceResolution = {
|
|
|
62
71
|
ok: false;
|
|
63
72
|
};
|
|
64
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a directory root for the file browser, which must work outside a git
|
|
76
|
+
* repository too. Inside a repo it is the work-tree top; otherwise it falls
|
|
77
|
+
* back to the session's cwd (realpath'd) so directory listing / file preview
|
|
78
|
+
* still function. Only a missing cwd is a hard failure.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resolveBrowseRoot(deps: SnapshotDeps, sessionId: string): Promise<{
|
|
81
|
+
ok: true;
|
|
82
|
+
root: string;
|
|
83
|
+
isGitRepo: boolean;
|
|
84
|
+
} | {
|
|
85
|
+
ok: false;
|
|
86
|
+
error: {
|
|
87
|
+
code: GitErrorCode;
|
|
88
|
+
message?: string;
|
|
89
|
+
};
|
|
90
|
+
}>;
|
|
65
91
|
/** Resolve the git work-tree root for a session's cwd. */
|
|
66
92
|
export declare function resolveWorkspace(deps: SnapshotDeps, sessionId: string): Promise<WorkspaceResolution>;
|
|
67
93
|
/** Run one git command; a spawn-level failure returns { failure }. */
|
|
68
|
-
export declare function runCommand(runner: GitRunner, argv: readonly string[], cwd: string, _label: string, signal?: AbortSignal): Promise<{
|
|
94
|
+
export declare function runCommand(runner: GitRunner, argv: readonly string[], cwd: string, _label: string, signal?: AbortSignal, stdinData?: string): Promise<{
|
|
69
95
|
run: Awaited<ReturnType<GitRunner['run']>>;
|
|
70
96
|
} | {
|
|
71
97
|
failure: unknown;
|
package/lib/host/git.d.ts
CHANGED
|
@@ -10,7 +10,9 @@ interface SpawnSpec {
|
|
|
10
10
|
readonly argv: readonly string[];
|
|
11
11
|
readonly cwd: string;
|
|
12
12
|
readonly stdio: {
|
|
13
|
-
readonly stdin: 'ignore'
|
|
13
|
+
readonly stdin: 'ignore' | {
|
|
14
|
+
readonly data: string;
|
|
15
|
+
};
|
|
14
16
|
readonly stdout: CollectDisposition;
|
|
15
17
|
readonly stderr: CollectDisposition;
|
|
16
18
|
};
|
|
@@ -53,6 +55,7 @@ export interface GitRunner {
|
|
|
53
55
|
run(argv: readonly string[], opts: {
|
|
54
56
|
readonly cwd: string;
|
|
55
57
|
readonly signal?: AbortSignal;
|
|
58
|
+
readonly stdinData?: string;
|
|
56
59
|
}): Promise<GitRunResult>;
|
|
57
60
|
}
|
|
58
61
|
/**
|
package/lib/host/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
11
|
import Schema from '@deepseek-ai/schemastery';
|
|
12
12
|
import type { GitActionRequest, GitActionResult, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult, GitVersionInfo, GitVersionRequest } from './types.ts';
|
|
13
|
-
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, GitVersionRequest, GitVersionInfo, } from './types.ts';
|
|
13
|
+
export type { GitSnapshot, GitSnapshotResult, GitSnapshotRequest, GitFailure, GitCommit, GraphCommit, GitRef, GitChange, GitChangeStatus, GitAction, GitActionRequest, GitActionResult, GitErrorCode, GitQuery, GitQueryRequest, GitQueryResponse, GitQueryResult, GitBranch, GitFileStat, WorktreeStats, GitVersionRequest, GitVersionInfo, DiffViewMode, DirEntry, } from './types.ts';
|
|
14
14
|
export { normalizeConfig, DEFAULT_CONFIG, snapshotForSession, resolveWorkspace } from './core.ts';
|
|
15
15
|
export { createGitRunner } from './git.ts';
|
|
16
16
|
export { parseStatus, parseGraphLog, parseBranches, parseNameStatus, sumNumstat } from './parser.ts';
|
|
@@ -20,14 +20,17 @@ export { readVersionInfo, checkLatestVersion, compareVersions, parseRepository }
|
|
|
20
20
|
export declare class GitPanelService extends TypertRemoteService {
|
|
21
21
|
static inject: string[];
|
|
22
22
|
/**
|
|
23
|
-
* Config schema surfaced on the plugin detail page.
|
|
24
|
-
* `.volatile()`, so the settings host renders
|
|
25
|
-
* the operational limits stay profile-only and out
|
|
23
|
+
* Config schema surfaced on the plugin detail page. `showInputPill` and
|
|
24
|
+
* `defaultDiffView` are `.volatile()`, so the settings host renders them as
|
|
25
|
+
* live-editable controls; the operational limits stay profile-only and out
|
|
26
|
+
* of the UI form.
|
|
26
27
|
*/
|
|
27
28
|
static Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
28
29
|
showInputPill: Schema<boolean, boolean, "volatile-defined">;
|
|
30
|
+
defaultDiffView: Schema<"split" | "unified", "split" | "unified", "volatile-defined">;
|
|
29
31
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
30
32
|
showInputPill: Schema<boolean, boolean, "volatile-defined">;
|
|
33
|
+
defaultDiffView: Schema<"split" | "unified", "split" | "unified", "volatile-defined">;
|
|
31
34
|
}>>, "plain">;
|
|
32
35
|
private readonly deps;
|
|
33
36
|
private config;
|
package/lib/host/index.js
CHANGED
|
@@ -50,7 +50,7 @@ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "acce
|
|
|
50
50
|
// src/host/index.ts
|
|
51
51
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
52
52
|
import Schema from "@deepseek-ai/schemastery";
|
|
53
|
-
import { readFile as readFile3, realpath, rm, stat } from "node:fs/promises";
|
|
53
|
+
import { readFile as readFile3, readdir, realpath, rm, stat } from "node:fs/promises";
|
|
54
54
|
|
|
55
55
|
// src/host/git.ts
|
|
56
56
|
import { readFile } from "node:fs/promises";
|
|
@@ -66,7 +66,7 @@ function createGitRunner(subprocess, timeoutMs, maxBytes) {
|
|
|
66
66
|
argv,
|
|
67
67
|
cwd: opts.cwd,
|
|
68
68
|
stdio: {
|
|
69
|
-
stdin: "ignore",
|
|
69
|
+
stdin: opts.stdinData === void 0 ? "ignore" : { data: opts.stdinData },
|
|
70
70
|
stdout: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } },
|
|
71
71
|
stderr: { collect: { maxBytes, spill: { maxBytes: spillMaxBytes } } }
|
|
72
72
|
},
|
|
@@ -305,7 +305,8 @@ var DEFAULT_CONFIG = {
|
|
|
305
305
|
maxBytes: 4 * 1024 * 1024,
|
|
306
306
|
maxChanges: 1e3,
|
|
307
307
|
refreshIntervalMs: 3e4,
|
|
308
|
-
showInputPill: true
|
|
308
|
+
showInputPill: true,
|
|
309
|
+
defaultDiffView: "unified"
|
|
309
310
|
};
|
|
310
311
|
function normalizeConfig(raw) {
|
|
311
312
|
const c = raw ?? {};
|
|
@@ -315,14 +316,35 @@ function normalizeConfig(raw) {
|
|
|
315
316
|
maxBytes: num(c.maxBytes, DEFAULT_CONFIG.maxBytes),
|
|
316
317
|
maxChanges: num(c.maxChanges, DEFAULT_CONFIG.maxChanges),
|
|
317
318
|
refreshIntervalMs: num(c.refreshIntervalMs, DEFAULT_CONFIG.refreshIntervalMs),
|
|
318
|
-
showInputPill: readBool(c.showInputPill, DEFAULT_CONFIG.showInputPill)
|
|
319
|
+
showInputPill: readBool(c.showInputPill, DEFAULT_CONFIG.showInputPill),
|
|
320
|
+
defaultDiffView: readDiffView(c.defaultDiffView, DEFAULT_CONFIG.defaultDiffView)
|
|
319
321
|
};
|
|
320
322
|
}
|
|
323
|
+
function unwrapVolatile(value) {
|
|
324
|
+
return value !== null && typeof value === "object" && "get" in value && typeof value.get === "function" ? value.get() : value;
|
|
325
|
+
}
|
|
326
|
+
function readDiffView(value, fallback) {
|
|
327
|
+
const raw = unwrapVolatile(value);
|
|
328
|
+
return raw === "unified" || raw === "split" ? raw : fallback;
|
|
329
|
+
}
|
|
321
330
|
function readBool(value, fallback) {
|
|
322
|
-
const raw = value
|
|
331
|
+
const raw = unwrapVolatile(value);
|
|
323
332
|
return typeof raw === "boolean" ? raw : fallback;
|
|
324
333
|
}
|
|
325
334
|
var NEG_CACHE_MS = 15e3;
|
|
335
|
+
async function resolveBrowseRoot(deps, sessionId) {
|
|
336
|
+
const ws = await resolveWorkspace(deps, sessionId);
|
|
337
|
+
if (ws.ok) return { ok: true, root: ws.root, isGitRepo: true };
|
|
338
|
+
const err = ws.failure.error;
|
|
339
|
+
if (err.code === "not-a-git-repo" && err.cwd !== void 0 && err.cwd !== "") {
|
|
340
|
+
try {
|
|
341
|
+
return { ok: true, root: await deps.fs.realpath(err.cwd), isGitRepo: false };
|
|
342
|
+
} catch {
|
|
343
|
+
return { ok: false, error: { code: "git-error" } };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { ok: false, error: mapWorkspaceFailure(ws.failure) };
|
|
347
|
+
}
|
|
326
348
|
async function resolveWorkspace(deps, sessionId) {
|
|
327
349
|
let cwd = deps.sessions.liveCwd(sessionId);
|
|
328
350
|
if (cwd === void 0 || cwd === "") {
|
|
@@ -363,9 +385,9 @@ async function resolveWorkspace(deps, sessionId) {
|
|
|
363
385
|
deps.rootCache?.set(cwd, root);
|
|
364
386
|
return { ok: true, root };
|
|
365
387
|
}
|
|
366
|
-
async function runCommand(runner, argv, cwd, _label, signal) {
|
|
388
|
+
async function runCommand(runner, argv, cwd, _label, signal, stdinData) {
|
|
367
389
|
try {
|
|
368
|
-
const run = await runner.run(argv, { cwd, ...signal ? { signal } : {} });
|
|
390
|
+
const run = await runner.run(argv, { cwd, ...signal ? { signal } : {}, ...stdinData !== void 0 ? { stdinData } : {} });
|
|
369
391
|
return { run };
|
|
370
392
|
} catch (error) {
|
|
371
393
|
return { failure: error };
|
|
@@ -478,6 +500,7 @@ async function snapshotForSession(deps, config, sessionId) {
|
|
|
478
500
|
truncated,
|
|
479
501
|
refreshIntervalMs: config.refreshIntervalMs,
|
|
480
502
|
showInputPill: config.showInputPill,
|
|
503
|
+
defaultDiffView: config.defaultDiffView,
|
|
481
504
|
checkedAt: Date.now()
|
|
482
505
|
};
|
|
483
506
|
return { ok: true, value: snapshot };
|
|
@@ -649,10 +672,19 @@ function isHexLike(text) {
|
|
|
649
672
|
return /^[0-9a-fA-F]{7,40}$/.test(text.trim());
|
|
650
673
|
}
|
|
651
674
|
async function runQuery(deps, config, request) {
|
|
675
|
+
const q = request.query;
|
|
676
|
+
if (q.kind === "dir-list" || q.kind === "file-content") {
|
|
677
|
+
const browse = await resolveBrowseRoot(deps, request.sessionId);
|
|
678
|
+
if (!browse.ok) return { ok: false, error: browse.error };
|
|
679
|
+
try {
|
|
680
|
+
return q.kind === "dir-list" ? await queryDirList(deps, browse.root, q, browse.isGitRepo) : await queryFileContent(deps, config, browse.root, q);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : String(error) } };
|
|
683
|
+
}
|
|
684
|
+
}
|
|
652
685
|
const workspace = await resolveWorkspace(deps, request.sessionId);
|
|
653
686
|
if (!workspace.ok) return { ok: false, error: mapWorkspaceFailure(workspace.failure) };
|
|
654
687
|
const root = workspace.root;
|
|
655
|
-
const q = request.query;
|
|
656
688
|
try {
|
|
657
689
|
switch (q.kind) {
|
|
658
690
|
case "history":
|
|
@@ -892,6 +924,106 @@ async function worktreeSide(deps, root, path, cap) {
|
|
|
892
924
|
return void 0;
|
|
893
925
|
}
|
|
894
926
|
}
|
|
927
|
+
var DIR_ENTRY_CAP = 2e3;
|
|
928
|
+
async function queryDirList(deps, root, q, isGitRepo) {
|
|
929
|
+
const rel = q.path;
|
|
930
|
+
if (rel !== "" && !isSafePath(rel) || hasGitSegment(rel)) return { ok: false, error: { code: "invalid-path", message: rel } };
|
|
931
|
+
const dir = rel === "" ? root : join3(root, rel);
|
|
932
|
+
const inside = await isInsideRoot(deps, root, dir, true);
|
|
933
|
+
if (!inside) return { ok: false, error: { code: "invalid-path", message: rel } };
|
|
934
|
+
let raw;
|
|
935
|
+
try {
|
|
936
|
+
raw = await deps.fs.readdir(dir);
|
|
937
|
+
} catch (error) {
|
|
938
|
+
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : "readdir failed" } };
|
|
939
|
+
}
|
|
940
|
+
const filtered = raw.filter((e) => e.name !== ".git");
|
|
941
|
+
filtered.sort((a, b) => a.isDirectory !== b.isDirectory ? a.isDirectory ? -1 : 1 : a.name.localeCompare(b.name));
|
|
942
|
+
const truncated = filtered.length > DIR_ENTRY_CAP;
|
|
943
|
+
const slice = truncated ? filtered.slice(0, DIR_ENTRY_CAP) : filtered;
|
|
944
|
+
const ignored = isGitRepo ? await ignoredEntries(deps, root, rel, slice.map((e) => e.name)) : /* @__PURE__ */ new Set();
|
|
945
|
+
const entries = await Promise.all(slice.map(async (e) => {
|
|
946
|
+
const ignoreFlag = ignored.has(e.name) ? { ignored: true } : {};
|
|
947
|
+
if (e.isDirectory) return { name: e.name, dir: true, ...ignoreFlag };
|
|
948
|
+
let size;
|
|
949
|
+
try {
|
|
950
|
+
size = (await deps.fs.stat(join3(dir, e.name))).size;
|
|
951
|
+
} catch {
|
|
952
|
+
size = void 0;
|
|
953
|
+
}
|
|
954
|
+
return { name: e.name, dir: false, ...size !== void 0 ? { size } : {}, ...ignoreFlag };
|
|
955
|
+
}));
|
|
956
|
+
const path = rel === "" ? "" : rel.replace(/\/+$/, "");
|
|
957
|
+
return { ok: true, value: { kind: "dir-list", path, entries, truncated } };
|
|
958
|
+
}
|
|
959
|
+
async function ignoredEntries(deps, root, parent, names) {
|
|
960
|
+
const ignored = /* @__PURE__ */ new Set();
|
|
961
|
+
const paths = names.map((name) => parent === "" ? name : `${parent}/${name}`);
|
|
962
|
+
for (let index = 0; index < paths.length; ) {
|
|
963
|
+
const batch = [];
|
|
964
|
+
let bytes = 0;
|
|
965
|
+
while (index < paths.length && batch.length < 128) {
|
|
966
|
+
const candidate = paths[index];
|
|
967
|
+
const size = Buffer.byteLength(candidate) + 1;
|
|
968
|
+
if (batch.length > 0 && bytes + size > 32 * 1024) break;
|
|
969
|
+
batch.push(candidate);
|
|
970
|
+
bytes += size;
|
|
971
|
+
index++;
|
|
972
|
+
}
|
|
973
|
+
const result = await runCommand(deps.run, ["git", "check-ignore", "-z", "--stdin"], root, "dir-ignore", deps.signal, `${batch.join("\0")}\0`);
|
|
974
|
+
if ("failure" in result || result.run.timedOut || result.run.cancelled || result.run.stdoutLossy || ![0, 1].includes(result.run.exitCode ?? -1)) continue;
|
|
975
|
+
const matches = new Set(result.run.stdout.split("\0"));
|
|
976
|
+
for (const path of batch) if (matches.has(path)) ignored.add(parent === "" ? path : path.slice(parent.length + 1));
|
|
977
|
+
}
|
|
978
|
+
return ignored;
|
|
979
|
+
}
|
|
980
|
+
async function queryFileContent(deps, config, root, q) {
|
|
981
|
+
if (!isSafePath(q.path) || hasGitSegment(q.path)) return { ok: false, error: { code: "invalid-path", message: q.path } };
|
|
982
|
+
const file = join3(root, q.path);
|
|
983
|
+
const inside = await isInsideRoot(deps, root, file, true);
|
|
984
|
+
if (!inside) return { ok: false, error: { code: "invalid-path", message: q.path } };
|
|
985
|
+
const cap = config.maxBytes;
|
|
986
|
+
let info;
|
|
987
|
+
try {
|
|
988
|
+
info = await deps.fs.stat(file);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : "stat failed" } };
|
|
991
|
+
}
|
|
992
|
+
const mime = imageMimeFor(q.path);
|
|
993
|
+
if (info.size > cap) return { ok: true, value: { kind: "file-content", path: q.path, variant: mime !== null ? "image" : "text", tooLarge: true } };
|
|
994
|
+
let buf;
|
|
995
|
+
try {
|
|
996
|
+
buf = await deps.fs.readFile(file);
|
|
997
|
+
} catch (error) {
|
|
998
|
+
return { ok: false, error: { code: "git-error", message: error instanceof Error ? error.message : "read failed" } };
|
|
999
|
+
}
|
|
1000
|
+
if (buf.length > cap) return { ok: true, value: { kind: "file-content", path: q.path, variant: mime !== null ? "image" : "text", tooLarge: true } };
|
|
1001
|
+
if (mime !== null) {
|
|
1002
|
+
return { ok: true, value: { kind: "file-content", path: q.path, variant: "image", dataUrl: `data:${mime};base64,${buf.toString("base64")}` } };
|
|
1003
|
+
}
|
|
1004
|
+
if (isBinaryBuffer(buf)) return { ok: true, value: { kind: "file-content", path: q.path, variant: "binary" } };
|
|
1005
|
+
const content = buf.toString("utf8");
|
|
1006
|
+
const lines = content === "" ? 0 : content.split("\n").length - (content.endsWith("\n") ? 1 : 0);
|
|
1007
|
+
return { ok: true, value: { kind: "file-content", path: q.path, variant: "text", content, lines } };
|
|
1008
|
+
}
|
|
1009
|
+
function hasGitSegment(path) {
|
|
1010
|
+
return path.split(/[\\/]/).includes(".git");
|
|
1011
|
+
}
|
|
1012
|
+
async function isInsideRoot(deps, root, path, hideGit = false) {
|
|
1013
|
+
try {
|
|
1014
|
+
const real = await deps.fs.realpath(path);
|
|
1015
|
+
const rootReal = await deps.fs.realpath(root);
|
|
1016
|
+
if (real !== rootReal && !real.startsWith(rootReal + sep2)) return false;
|
|
1017
|
+
return !hideGit || !hasGitSegment(real.slice(rootReal.length + 1));
|
|
1018
|
+
} catch {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function isBinaryBuffer(buf) {
|
|
1023
|
+
const n = Math.min(buf.length, 8192);
|
|
1024
|
+
for (let i = 0; i < n; i++) if (buf[i] === 0) return true;
|
|
1025
|
+
return false;
|
|
1026
|
+
}
|
|
895
1027
|
async function queryShow(deps, root, ref) {
|
|
896
1028
|
if (!isSafeRev(ref)) return { ok: false, error: { code: "invalid-name", message: `unsafe ref: ${ref}` } };
|
|
897
1029
|
const metaFormat = "--format=%H%x1f%h%x1f%s%x1f%an%x1f%aI%x1f%b";
|
|
@@ -1107,6 +1239,7 @@ var GitPanelService = class extends (_a = TypertRemoteService, _snapshot_dec = [
|
|
|
1107
1239
|
realpath,
|
|
1108
1240
|
stat: async (p) => stat(p),
|
|
1109
1241
|
readFile: readFile3,
|
|
1242
|
+
readdir: async (p) => (await readdir(p, { withFileTypes: true })).map((e) => ({ name: e.name, isDirectory: e.isDirectory() })),
|
|
1110
1243
|
remove: async (p) => {
|
|
1111
1244
|
await rm(p, { force: true });
|
|
1112
1245
|
}
|
|
@@ -1170,12 +1303,17 @@ __decorateElement(_init, 1, "version", _version_dec, GitPanelService);
|
|
|
1170
1303
|
__decoratorMetadata(_init, GitPanelService);
|
|
1171
1304
|
__publicField(GitPanelService, "inject", ["subprocess", "sessions", "sessionPersistence"]);
|
|
1172
1305
|
/**
|
|
1173
|
-
* Config schema surfaced on the plugin detail page.
|
|
1174
|
-
* `.volatile()`, so the settings host renders
|
|
1175
|
-
* the operational limits stay profile-only and out
|
|
1306
|
+
* Config schema surfaced on the plugin detail page. `showInputPill` and
|
|
1307
|
+
* `defaultDiffView` are `.volatile()`, so the settings host renders them as
|
|
1308
|
+
* live-editable controls; the operational limits stay profile-only and out
|
|
1309
|
+
* of the UI form.
|
|
1176
1310
|
*/
|
|
1177
1311
|
__publicField(GitPanelService, "Config", Schema.object({
|
|
1178
|
-
showInputPill: Schema.boolean().default(true).volatile().description("\u663E\u793A\u8F93\u5165\u6846\u7684 Git \u5206\u652F\u6807\u8BB0")
|
|
1312
|
+
showInputPill: Schema.boolean().default(true).volatile().description("\u663E\u793A\u8F93\u5165\u6846\u7684 Git \u5206\u652F\u6807\u8BB0"),
|
|
1313
|
+
defaultDiffView: Schema.union([
|
|
1314
|
+
Schema.const("unified").description("\u7EDF\u4E00\u89C6\u56FE\uFF08\u5355\u680F\u884C\u5185\u5BF9\u6BD4\uFF09"),
|
|
1315
|
+
Schema.const("split").description("\u5E76\u6392\u89C6\u56FE\uFF08\u5DE6\u53F3\u5206\u680F\u5BF9\u6BD4\uFF09")
|
|
1316
|
+
]).default("unified").volatile().description("\u5DEE\u5F02\u5BF9\u6BD4\u9ED8\u8BA4\u89C6\u56FE")
|
|
1179
1317
|
}));
|
|
1180
1318
|
var index_default = GitPanelService;
|
|
1181
1319
|
export {
|
package/lib/host/types.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* The client half (src/client/rpc.ts) reuses these types directly, so the
|
|
4
4
|
* wire contract has a single definition and cannot drift.
|
|
5
5
|
*/
|
|
6
|
+
/** Diff layout: `unified` = single inline column, `split` = side-by-side. */
|
|
7
|
+
export type DiffViewMode = 'unified' | 'split';
|
|
6
8
|
/** Wire request: the browser sends only session identity, never a path. */
|
|
7
9
|
export interface GitSnapshotRequest {
|
|
8
10
|
readonly sessionId: string;
|
|
@@ -56,6 +58,8 @@ export interface GitSnapshot {
|
|
|
56
58
|
readonly refreshIntervalMs: number;
|
|
57
59
|
/** Whether the input-bar git marker pill should render (user preference). */
|
|
58
60
|
readonly showInputPill: boolean;
|
|
61
|
+
/** Default diff layout the views open with (user preference). */
|
|
62
|
+
readonly defaultDiffView: DiffViewMode;
|
|
59
63
|
/** Epoch millis of the snapshot. */
|
|
60
64
|
readonly checkedAt: number;
|
|
61
65
|
}
|
|
@@ -176,6 +180,12 @@ export type GitQuery = {
|
|
|
176
180
|
readonly path: string;
|
|
177
181
|
readonly base: 'commit';
|
|
178
182
|
readonly commit: string;
|
|
183
|
+
} | {
|
|
184
|
+
readonly kind: 'dir-list';
|
|
185
|
+
readonly path: string;
|
|
186
|
+
} | {
|
|
187
|
+
readonly kind: 'file-content';
|
|
188
|
+
readonly path: string;
|
|
179
189
|
} | {
|
|
180
190
|
readonly kind: 'show';
|
|
181
191
|
readonly ref: string;
|
|
@@ -190,6 +200,15 @@ export type GitQuery = {
|
|
|
190
200
|
} | {
|
|
191
201
|
readonly kind: 'worktree-stats';
|
|
192
202
|
};
|
|
203
|
+
/** One entry in a `dir-list` result. */
|
|
204
|
+
export interface DirEntry {
|
|
205
|
+
readonly name: string;
|
|
206
|
+
readonly dir: boolean;
|
|
207
|
+
/** File byte size; absent for directories. */
|
|
208
|
+
readonly size?: number;
|
|
209
|
+
/** True when Git excludes an untracked item; absent outside a repository. */
|
|
210
|
+
readonly ignored?: boolean;
|
|
211
|
+
}
|
|
193
212
|
/** One commit's changed-file line (from --name-status). */
|
|
194
213
|
export interface GitFileStat {
|
|
195
214
|
readonly path: string;
|
|
@@ -272,6 +291,23 @@ export type GitQueryResult = {
|
|
|
272
291
|
} | {
|
|
273
292
|
readonly kind: 'worktree-stats';
|
|
274
293
|
readonly stats: WorktreeStats;
|
|
294
|
+
} | {
|
|
295
|
+
readonly kind: 'dir-list';
|
|
296
|
+
readonly path: string;
|
|
297
|
+
readonly entries: readonly DirEntry[];
|
|
298
|
+
readonly truncated: boolean;
|
|
299
|
+
} | {
|
|
300
|
+
readonly kind: 'file-content';
|
|
301
|
+
readonly path: string;
|
|
302
|
+
readonly variant: 'text' | 'image' | 'binary';
|
|
303
|
+
/** text variant: the file's full UTF-8 content. */
|
|
304
|
+
readonly content?: string;
|
|
305
|
+
/** text variant: total line count. */
|
|
306
|
+
readonly lines?: number;
|
|
307
|
+
/** image variant: a `data:<mime>;base64,…` URL. */
|
|
308
|
+
readonly dataUrl?: string;
|
|
309
|
+
/** The file exceeded the byte cap, so no content is returned. */
|
|
310
|
+
readonly tooLarge?: true;
|
|
275
311
|
};
|
|
276
312
|
export type GitQueryResponse = {
|
|
277
313
|
readonly ok: true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbzbing/dsh-git-panel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "DeepSeek Harness (dsh) plugin: an IDE-style Git panel in the Web GUI — branch/history overview, uncommitted changes with commit & amend, diff stats, and a zsh-style branch marker in the input bar.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,8 +44,7 @@
|
|
|
44
44
|
"test:unit": "node build.mjs && node --test --test-force-exit test/unit/*.test.mjs",
|
|
45
45
|
"test:e2e": "node build.mjs && node test/e2e/setup.mjs && node test/e2e/run.mjs && node test/e2e/run2.mjs",
|
|
46
46
|
"test": "npm run test:unit && npm run test:e2e",
|
|
47
|
-
"check": "npm run typecheck && npm run test && git diff --exit-code -- lib/"
|
|
48
|
-
"prepare": "node build.mjs"
|
|
47
|
+
"check": "npm run typecheck && npm run test && git diff --exit-code -- lib/"
|
|
49
48
|
},
|
|
50
49
|
"dsh": {
|
|
51
50
|
"bundle": {
|
|
@@ -56,7 +55,8 @@
|
|
|
56
55
|
"inject": [
|
|
57
56
|
"@deepseek-ai/dsh-client-connection",
|
|
58
57
|
"@deepseek-ai/dsh-client-locale",
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
60
60
|
]
|
|
61
61
|
}
|
|
62
62
|
},
|
|
@@ -76,9 +76,6 @@
|
|
|
76
76
|
"@deepseek-ai/dsh-typert-protocol": ">=0.1.2-rc.1",
|
|
77
77
|
"@deepseek-ai/schemastery": ">=3.18.4"
|
|
78
78
|
},
|
|
79
|
-
"dependencies": {
|
|
80
|
-
"highlight.js": "11.11.1"
|
|
81
|
-
},
|
|
82
79
|
"devDependencies": {
|
|
83
80
|
"@types/node": "22.10.5",
|
|
84
81
|
"@types/react": "18.3.18",
|