@lornatang/dsh-frontgate 0.0.0-stage → 0.1.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/CHANGELOG.md +72 -0
- package/LICENSE +202 -0
- package/README.md +149 -2
- package/cordis.patch.yml +16 -0
- package/lib/index.js +1167 -0
- package/lib/types/auth.d.ts +39 -0
- package/lib/types/config.d.ts +52 -0
- package/lib/types/context.d.ts +48 -0
- package/lib/types/gate.d.ts +35 -0
- package/lib/types/http.d.ts +45 -0
- package/lib/types/index.d.ts +25 -0
- package/lib/types/page.d.ts +2 -0
- package/lib/types/polyfill.d.ts +9 -0
- package/lib/types/proxy.d.ts +30 -0
- package/lib/types/sessions.d.ts +9 -0
- package/lib/types/state.d.ts +23 -0
- package/package.json +74 -4
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Name of the session cookie. */
|
|
2
|
+
export declare const COOKIE = "dsh_gate";
|
|
3
|
+
/** Version prefix of signed session tokens. */
|
|
4
|
+
export declare const TOKEN_VERSION = "v1";
|
|
5
|
+
/** Longest accepted token, in characters. */
|
|
6
|
+
export declare const MAX_TOKEN_LENGTH = 256;
|
|
7
|
+
/** Shortest accepted configured password, in characters. */
|
|
8
|
+
export declare const MIN_PASSWORD_LENGTH = 12;
|
|
9
|
+
/** Stored hash format: scrypt$N$r$p$saltHex$keyHex. */
|
|
10
|
+
export declare const HASH_RE: RegExp;
|
|
11
|
+
/** scrypt cost parameters used when a new hash is created. */
|
|
12
|
+
export interface ScryptParams {
|
|
13
|
+
N?: number;
|
|
14
|
+
r?: number;
|
|
15
|
+
p?: number;
|
|
16
|
+
keylen?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Reject configured passwords that are too short to survive guessing. */
|
|
19
|
+
export declare function assertStrongPassword(password: string, source: string): string;
|
|
20
|
+
/** Hash a password with scrypt; parameters travel with the hash. */
|
|
21
|
+
export declare function hashPassword(password: string, params?: ScryptParams): string;
|
|
22
|
+
/** Check the complete persisted format and its bounded scrypt parameters. */
|
|
23
|
+
export declare function validPasswordHash(value: unknown): value is string;
|
|
24
|
+
/** Async verification: the event loop stays free while scrypt runs. */
|
|
25
|
+
export declare function verifyPassword(password: string, stored: unknown): Promise<boolean>;
|
|
26
|
+
/** Synchronous verification, used only to reuse the persisted salt at startup. */
|
|
27
|
+
export declare function verifyPasswordSync(password: string, stored: unknown): boolean;
|
|
28
|
+
/** Random password shown once when no password is configured. */
|
|
29
|
+
export declare function randomPassword(): string;
|
|
30
|
+
/** Stable, non-reversible tag of the password hash; embedded in every session. */
|
|
31
|
+
export declare const passwordVersion: (passwordHash: string) => string;
|
|
32
|
+
/** Create a signed session token: v1.issuedAt.expiresAt.nonce.passwordVersion.mac. */
|
|
33
|
+
export declare function signToken(secret: string, pv: string, ttlSeconds: number): string;
|
|
34
|
+
/** Verify signature, version, password binding and expiry in constant time. */
|
|
35
|
+
export declare function verifyToken(secret: string, pv: string, token: string | undefined): boolean;
|
|
36
|
+
/** Session cookie attributes; Secure follows the browser-facing protocol. */
|
|
37
|
+
export declare function sessionCookie(value: string, seconds: number, useTls: boolean): string;
|
|
38
|
+
/** Expired session cookie. */
|
|
39
|
+
export declare const clearCookie: (secure: boolean) => string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin configuration and its loader schema.
|
|
3
|
+
*
|
|
4
|
+
* The values are also the documented defaults, so a patch file that only sets
|
|
5
|
+
* one option behaves exactly like the README's configuration table.
|
|
6
|
+
*/
|
|
7
|
+
import z from '@deepseek-ai/schemastery';
|
|
8
|
+
/** Configuration accepted through cordis.patch.yml or the loader. */
|
|
9
|
+
export interface Config {
|
|
10
|
+
/** Interface the gate listens on. */
|
|
11
|
+
listenHost: string;
|
|
12
|
+
/** Port the gate listens on; 0 picks an ephemeral port. */
|
|
13
|
+
listenPort: number;
|
|
14
|
+
/** Session lifetime in days. */
|
|
15
|
+
sessionDays: number;
|
|
16
|
+
/** Title shown on the login page. */
|
|
17
|
+
title: string;
|
|
18
|
+
/** Plain-text password; hashed at startup and never written to disk as text. */
|
|
19
|
+
password: string;
|
|
20
|
+
/** scrypt password hash; takes precedence over password, environment and state file. */
|
|
21
|
+
passwordHash: string;
|
|
22
|
+
/** Optional TLS certificate; both files must be set to serve HTTPS. */
|
|
23
|
+
tlsCertFile: string;
|
|
24
|
+
/** Optional TLS key; both files must be set to serve HTTPS. */
|
|
25
|
+
tlsKeyFile: string;
|
|
26
|
+
/** Browser-facing origin, required when a reverse proxy terminates TLS. */
|
|
27
|
+
publicOrigin: string;
|
|
28
|
+
/** Honour X-Forwarded-For only when the listener is restricted to a trusted proxy. */
|
|
29
|
+
trustProxy: boolean;
|
|
30
|
+
/** Failed logins before a client is locked out. */
|
|
31
|
+
maxAttempts: number;
|
|
32
|
+
/** Lockout duration in minutes. */
|
|
33
|
+
lockMinutes: number;
|
|
34
|
+
/** Maximum time to exchange the harness launch token, in milliseconds. */
|
|
35
|
+
exchangeTimeoutMs: number;
|
|
36
|
+
/** Maximum simultaneous TCP connections from one peer; 0 disables this limit. */
|
|
37
|
+
maxConnectionsPerPeer: number;
|
|
38
|
+
/** Maximum login forms being received from one client IP. */
|
|
39
|
+
maxPendingLoginsPerIp: number;
|
|
40
|
+
/** Deadline for upstream response headers, including time queued for a socket; 0 disables. */
|
|
41
|
+
upstreamHeaderTimeoutMs: number;
|
|
42
|
+
/** Maximum idle time after upstream response headers; 0 disables. */
|
|
43
|
+
upstreamIdleTimeoutMs: number;
|
|
44
|
+
/** Optional CA certificate for a private HTTPS harness. */
|
|
45
|
+
upstreamCaFile: string;
|
|
46
|
+
/** Optional TLS identity when connecting to an HTTPS harness by IP. */
|
|
47
|
+
upstreamServerName: string;
|
|
48
|
+
}
|
|
49
|
+
/** Defaults applied by the loader and by direct calls to apply(). */
|
|
50
|
+
export declare const DEFAULTS: Config;
|
|
51
|
+
/** Schemastery schema; the loader validates and fills defaults before apply(). */
|
|
52
|
+
export declare const Config: z<Config>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural view of the harness services this plugin talks to.
|
|
3
|
+
*
|
|
4
|
+
* The gate only runs inside the web profile, so the web server and the
|
|
5
|
+
* connection service are always present. Declaring the two interfaces here
|
|
6
|
+
* keeps the plugin typed without depending on the host packages at build time.
|
|
7
|
+
*/
|
|
8
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
/** Minimal surface of the harness web server used by the gate. */
|
|
10
|
+
export interface WebServerView {
|
|
11
|
+
/** Host the harness listener is bound to, when the host service exposes it. */
|
|
12
|
+
config?: {
|
|
13
|
+
host?: unknown;
|
|
14
|
+
};
|
|
15
|
+
/** Origin the harness listener is reachable on, for example http://127.0.0.1:3080. */
|
|
16
|
+
getOrigin(): string;
|
|
17
|
+
/** Register an index.html transform; the returned function unregisters it. */
|
|
18
|
+
tapIndex(transform: (html: string) => string): () => void;
|
|
19
|
+
}
|
|
20
|
+
/** Minimal surface of the harness connection service used by the gate. */
|
|
21
|
+
export interface ConnectionView {
|
|
22
|
+
/** Absolute URL of the entry document with the launch token attached. */
|
|
23
|
+
authenticatedUrl(baseUrl: string): string;
|
|
24
|
+
}
|
|
25
|
+
/** Profile location reported by the harness loader. */
|
|
26
|
+
export interface ProfileContextView {
|
|
27
|
+
/** Absolute path of the profile patch file the plugin was loaded from. */
|
|
28
|
+
patchPath?: string;
|
|
29
|
+
}
|
|
30
|
+
declare module '@deepseek-ai/cordis' {
|
|
31
|
+
interface Context {
|
|
32
|
+
webServer: WebServerView;
|
|
33
|
+
connection: ConnectionView;
|
|
34
|
+
profileContext?: ProfileContextView;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Logger subset used by the gate; lines are written to stderr. */
|
|
38
|
+
export interface LogSink {
|
|
39
|
+
info(message: string): void;
|
|
40
|
+
warn(message: string): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Report startup lines on stderr, which is the only channel a headless web
|
|
44
|
+
* profile leaves visible: the harness routes ctx.logger to its own exporters,
|
|
45
|
+
* and the web profile installs none of them, so a generated password or a
|
|
46
|
+
* listen address logged there would never reach the operator.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveLogger(ctx: Context): LogSink;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gate's HTTP surface: health, login, logout, and the authenticated proxy.
|
|
3
|
+
*
|
|
4
|
+
* Unauthenticated API calls get 401 JSON so the UI can react; every other path
|
|
5
|
+
* is redirected to the login page. Authenticated requests are handed to the
|
|
6
|
+
* proxy with an exchanged harness cookie.
|
|
7
|
+
*/
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
9
|
+
import type { LogSink } from './context.ts';
|
|
10
|
+
/** Values the gate needs at request time. */
|
|
11
|
+
export interface GateRuntime {
|
|
12
|
+
title: string;
|
|
13
|
+
passwordHash: string;
|
|
14
|
+
secret: string;
|
|
15
|
+
pv: string;
|
|
16
|
+
sessionSeconds: number;
|
|
17
|
+
maxAttempts: number;
|
|
18
|
+
maxPendingLoginsPerIp: number;
|
|
19
|
+
lockMs: number;
|
|
20
|
+
trustProxy: boolean;
|
|
21
|
+
useTls: boolean;
|
|
22
|
+
publicOrigin: string;
|
|
23
|
+
secureCookie: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Collaborators the handler delegates to. */
|
|
26
|
+
export interface GateOptions {
|
|
27
|
+
runtime: GateRuntime;
|
|
28
|
+
harnessCookie(): Promise<string>;
|
|
29
|
+
validSession(token: string): boolean;
|
|
30
|
+
revokeSession(token: string): boolean;
|
|
31
|
+
forward(req: IncomingMessage, res: ServerResponse, cookie: string): void;
|
|
32
|
+
log: LogSink;
|
|
33
|
+
}
|
|
34
|
+
/** Build the request handler for one gate instance. */
|
|
35
|
+
export declare function createHandler(options: GateOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request parsing and response helpers shared by the login page and the proxy.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is deliberately small: bounded body reads, one origin check,
|
|
5
|
+
* one redirect sanitizer and one set of gate headers.
|
|
6
|
+
*/
|
|
7
|
+
import type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'node:http';
|
|
8
|
+
/** Largest accepted login form body. */
|
|
9
|
+
export declare const MAX_FORM: number;
|
|
10
|
+
/** Longest time a login body may take to arrive. */
|
|
11
|
+
export declare const FORM_TIMEOUT: number;
|
|
12
|
+
/** Parse an integer option and clamp it into range. */
|
|
13
|
+
export declare const intOr: (value: unknown, fallback: number, min: number, max: number) => number;
|
|
14
|
+
/** Read one cookie value from a Cookie header. */
|
|
15
|
+
export declare function cookieValue(header: string | string[] | undefined, name: string): string;
|
|
16
|
+
/** Client address used for lockout accounting. */
|
|
17
|
+
export declare function clientIp(req: IncomingMessage, trustProxy: boolean): string;
|
|
18
|
+
/** Normalize an explicitly configured, browser-facing HTTP(S) origin. */
|
|
19
|
+
export declare function normalizePublicOrigin(value: string): string;
|
|
20
|
+
/** The configured public authority must match the Host preserved by a proxy. */
|
|
21
|
+
export declare const hostMatchesPublicOrigin: (req: IncomingMessage, publicOrigin: string, useTls: boolean) => boolean;
|
|
22
|
+
/** Require a valid Host, and if present an Origin with identical scheme, host and port. */
|
|
23
|
+
export declare function originMatchesHost(req: IncomingMessage, publicOrigin: string, useTls: boolean): boolean;
|
|
24
|
+
/** Login check: Origin and Fetch-Site must both look same-origin. */
|
|
25
|
+
export declare function sameOrigin(req: IncomingMessage, publicOrigin: string, useTls: boolean): boolean;
|
|
26
|
+
/** Only same-site absolute paths; rejects //evil and backslash tricks. */
|
|
27
|
+
export declare function safeNext(value: unknown): string;
|
|
28
|
+
export declare const escapeHtml: (text: unknown) => string;
|
|
29
|
+
/** Lock the login page and gate responses down to their own origin. */
|
|
30
|
+
export declare const CSP = "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'";
|
|
31
|
+
/** Headers attached to every response the gate writes itself. */
|
|
32
|
+
export declare const GATE_HEADERS: OutgoingHttpHeaders;
|
|
33
|
+
/** Keep cross-origin referrers private without suppressing the login form's Origin. */
|
|
34
|
+
export declare const LOGIN_REFERRER_POLICY = "same-origin";
|
|
35
|
+
/** Write a complete response. */
|
|
36
|
+
export declare function send(res: ServerResponse, status: number, headers: OutgoingHttpHeaders, body: string | Buffer): void;
|
|
37
|
+
export declare const html: (res: ServerResponse, status: number, body: string) => void;
|
|
38
|
+
export declare const json: (res: ServerResponse, status: number, payload: unknown) => void;
|
|
39
|
+
export declare const text: (res: ServerResponse, status: number, body: string, extra?: OutgoingHttpHeaders) => void;
|
|
40
|
+
export declare const redirect: (res: ServerResponse, location: string, status?: number) => void;
|
|
41
|
+
/** Read a bounded, form-encoded login body; never trust content-length alone. */
|
|
42
|
+
export declare function readForm(req: IncomingMessage): Promise<string>;
|
|
43
|
+
export declare const isLoopbackHost: (host: string) => boolean;
|
|
44
|
+
/** Address form accepted by net.connect / tls.connect. */
|
|
45
|
+
export declare const connectHost: (hostname: string) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AddressInfo } from 'node:net';
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import { Config } from './config.ts';
|
|
4
|
+
/** Cordis metadata: the plugin id shown in logs and the services it waits for. */
|
|
5
|
+
export declare const name = "dsh-frontgate";
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
/** Handle returned to tests and embedders; the loader ignores it. */
|
|
8
|
+
export interface GateHandle {
|
|
9
|
+
/** Resolves once the gate is listening. */
|
|
10
|
+
ready(): Promise<void>;
|
|
11
|
+
/** Bound address of the gate listener. */
|
|
12
|
+
address(): AddressInfo | string | null;
|
|
13
|
+
/** Stop the listener and drop pooled upstream sockets. */
|
|
14
|
+
stop(): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Start the gate.
|
|
18
|
+
*
|
|
19
|
+
* The loader validates the configuration against Config before calling this;
|
|
20
|
+
* defaults are applied again here so direct calls behave the same way.
|
|
21
|
+
*/
|
|
22
|
+
export declare function apply(ctx: Context, config?: Partial<Config>): GateHandle;
|
|
23
|
+
export { Config } from './config.ts';
|
|
24
|
+
export { hashPassword, verifyPassword } from './auth.ts';
|
|
25
|
+
export { injectPolyfill } from './polyfill.ts';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side fallback for a secure-context API.
|
|
3
|
+
*
|
|
4
|
+
* The UI is commonly opened over plain http on a LAN address, which is not a
|
|
5
|
+
* secure context, so crypto.randomUUID is missing there. The transform is
|
|
6
|
+
* idempotent and only touches the harness index document.
|
|
7
|
+
*/
|
|
8
|
+
/** Insert the polyfill once, before the closing head tag. */
|
|
9
|
+
export declare function injectPolyfill(htmlText: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { Duplex } from 'node:stream';
|
|
3
|
+
import type { LogSink } from './context.ts';
|
|
4
|
+
/** Options needed to reach and describe the harness listener. */
|
|
5
|
+
export interface ProxyOptions {
|
|
6
|
+
/** Origin of the harness listener, for example http://127.0.0.1:3080. */
|
|
7
|
+
targetOrigin: string;
|
|
8
|
+
/** Protocol the gate serves to browsers. */
|
|
9
|
+
publicProto: 'http' | 'https';
|
|
10
|
+
/** Honour forwarded headers from a proxy in front of the gate. */
|
|
11
|
+
trustProxy: boolean;
|
|
12
|
+
/** Maximum time spent exchanging a launch token, including connection time. */
|
|
13
|
+
exchangeTimeoutMs: number;
|
|
14
|
+
upstreamHeaderTimeoutMs: number;
|
|
15
|
+
upstreamIdleTimeoutMs: number;
|
|
16
|
+
upstreamCa?: Buffer;
|
|
17
|
+
upstreamServerName: string;
|
|
18
|
+
/** Absolute entry URL with the harness launch token attached. */
|
|
19
|
+
authenticatedUrl(baseUrl: string): string;
|
|
20
|
+
log: LogSink;
|
|
21
|
+
}
|
|
22
|
+
/** Proxy surface used by the request and upgrade handlers. */
|
|
23
|
+
export interface Proxy {
|
|
24
|
+
harnessCookie(): Promise<string>;
|
|
25
|
+
forward(req: IncomingMessage, res: ServerResponse, cookie: string): void;
|
|
26
|
+
forwardUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, cookie: string): void;
|
|
27
|
+
destroy(): void;
|
|
28
|
+
}
|
|
29
|
+
/** Build the proxy for one harness origin. */
|
|
30
|
+
export declare function createProxy(options: ProxyOptions): Proxy;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { State } from './state.ts';
|
|
2
|
+
/** Store only a digest, never the bearer token itself. */
|
|
3
|
+
export declare const sessionDigest: (token: string) => string;
|
|
4
|
+
export interface SessionStore {
|
|
5
|
+
valid(token: string): boolean;
|
|
6
|
+
/** Persist revocation before reporting logout success. */
|
|
7
|
+
revoke(token: string): boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function createSessionStore(path: string, state: State, pv: string, onRevoke: (digest: string) => void): SessionStore;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Bound persistent logout revocations; expired entries are pruned on logout. */
|
|
2
|
+
export declare const MAX_REVOKED_SESSIONS = 10000;
|
|
3
|
+
export interface RevokedSession {
|
|
4
|
+
digest: string;
|
|
5
|
+
expires: number;
|
|
6
|
+
}
|
|
7
|
+
/** Persisted state file contents. */
|
|
8
|
+
export interface State {
|
|
9
|
+
passwordHash?: string;
|
|
10
|
+
secret?: string;
|
|
11
|
+
createdAt?: string;
|
|
12
|
+
revokedSessions?: RevokedSession[];
|
|
13
|
+
}
|
|
14
|
+
/** Read existing state without replacing malformed or inaccessible credentials. */
|
|
15
|
+
export declare function readState(path: string, mustExist?: boolean): State;
|
|
16
|
+
/** Fail closed when a state file is a symlink or cannot be made owner-only. */
|
|
17
|
+
export declare function tightenState(path: string): boolean;
|
|
18
|
+
/** Write a complete owner-only temporary file, then replace the old state. */
|
|
19
|
+
export declare function writeState(path: string, value: State): void;
|
|
20
|
+
/** Resolve the state path for the profile the plugin was loaded from. */
|
|
21
|
+
export declare function statePathFor(patchPath: string | undefined): string;
|
|
22
|
+
/** Reuse the persisted salt while the same password keeps being configured. */
|
|
23
|
+
export declare function hashFor(plain: string, state: State): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lornatang/dsh-frontgate",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Password protection and reverse proxy for the DeepSeek Harness Web UI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib/index.js",
|
|
17
|
+
"lib/types/**/*.d.ts",
|
|
18
|
+
"cordis.patch.yml",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CHANGELOG.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"dsh": {
|
|
24
|
+
"bundle": {
|
|
25
|
+
"patch": "./cordis.patch.yml"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.19",
|
|
30
|
+
"dsh": ">=0.2.2-rc.1"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node scripts/build.mjs",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
36
|
+
"prepack": "npm run build && node --test tests/*.test.mjs",
|
|
37
|
+
"pack:check": "npm pack --dry-run"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@deepseek-ai/cordis": "~4.1.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@deepseek-ai/schemastery": "~3.19.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@deepseek-ai/cordis": "~4.1.0",
|
|
47
|
+
"@types/node": "^22.18.0",
|
|
48
|
+
"esbuild": "^0.25.0",
|
|
49
|
+
"typescript": "^5.9.0"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"dsh",
|
|
53
|
+
"dsh-plugin",
|
|
54
|
+
"deepseek-harness",
|
|
55
|
+
"cordis",
|
|
56
|
+
"password",
|
|
57
|
+
"auth",
|
|
58
|
+
"login",
|
|
59
|
+
"reverse-proxy",
|
|
60
|
+
"websocket",
|
|
61
|
+
"self-hosted"
|
|
62
|
+
],
|
|
63
|
+
"license": "Apache-2.0",
|
|
64
|
+
"author": "Lornatang",
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "git+https://github.com/Lornatang/dsh-frontgate.git"
|
|
68
|
+
},
|
|
69
|
+
"homepage": "https://github.com/Lornatang/dsh-frontgate#readme",
|
|
70
|
+
"bugs": {
|
|
71
|
+
"url": "https://github.com/Lornatang/dsh-frontgate/issues"
|
|
72
|
+
},
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|