dsh-home-hosted 0.1.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/cordis.patch.yml +6 -0
  4. package/docs/DESIGN.md +114 -0
  5. package/lib/client.js +9 -0
  6. package/lib/index.js +3694 -0
  7. package/lib/index.js.map +7 -0
  8. package/lib/types/boot/common.d.ts +40 -0
  9. package/lib/types/boot/escape.d.ts +59 -0
  10. package/lib/types/boot/fallback.d.ts +10 -0
  11. package/lib/types/boot/index.d.ts +12 -0
  12. package/lib/types/boot/ladder.d.ts +4 -0
  13. package/lib/types/boot/launchd.d.ts +8 -0
  14. package/lib/types/boot/systemd.d.ts +10 -0
  15. package/lib/types/boot/types.d.ts +94 -0
  16. package/lib/types/boot/windows.d.ts +16 -0
  17. package/lib/types/boot/xdg.d.ts +3 -0
  18. package/lib/types/config.d.ts +15 -0
  19. package/lib/types/home-hosted/config-file.d.ts +23 -0
  20. package/lib/types/home-hosted/dsh-entry.d.ts +22 -0
  21. package/lib/types/home-hosted/entries.d.ts +20 -0
  22. package/lib/types/home-hosted/launch.d.ts +25 -0
  23. package/lib/types/home-hosted/launcher.d.ts +38 -0
  24. package/lib/types/home-hosted/panel-control.d.ts +34 -0
  25. package/lib/types/home-hosted/panel.d.ts +44 -0
  26. package/lib/types/home-hosted/resolve.d.ts +34 -0
  27. package/lib/types/home-hosted/runtime.d.ts +20 -0
  28. package/lib/types/home-hosted/token.d.ts +20 -0
  29. package/lib/types/index.d.ts +15 -0
  30. package/lib/types/rpc.d.ts +11 -0
  31. package/lib/types/service.d.ts +125 -0
  32. package/lib/types/settings.d.ts +13 -0
  33. package/lib/types/shared/contracts.d.ts +245 -0
  34. package/lib/types/tools.d.ts +14 -0
  35. package/lib/types/util/exec.d.ts +29 -0
  36. package/lib/types/util/fsx.d.ts +9 -0
  37. package/lib/types/util/paths.d.ts +12 -0
  38. package/package.json +108 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The one implementation behind both surfaces: the browser page and the agent
3
+ * tools call the same endpoints, so they cannot drift apart.
4
+ *
5
+ * Writes prefer the panel API (the store ignores its own writes, so nothing is
6
+ * restarted behind the user's back) and fall back to an atomic config-file write
7
+ * only when the panel is not answering.
8
+ */
9
+ import { Service, type Context } from '@deepseek-ai/cordis';
10
+ import type { BootMechanism, BootStatus, HomeHostedStatus, ManagedEntryStatus, PanelControlResult, PanelStatus, RpcEndpoint } from './shared/contracts.js';
11
+ import type { BootSpec } from './boot/types.js';
12
+ import type { SettingsStore } from './settings.js';
13
+ import type { RunResult } from './util/exec.js';
14
+ /** What a boot install/uninstall answers with; mirrors the boot module's shape. */
15
+ export interface BootInstallResult {
16
+ ok: boolean;
17
+ changed: boolean;
18
+ detail: string;
19
+ commands: string[];
20
+ needsPrivilege: boolean;
21
+ /** Present on install; an uninstall names no mechanism. */
22
+ mechanism?: BootMechanism | null;
23
+ status: BootStatus;
24
+ }
25
+ /** The subset of the boot ladder this service uses. */
26
+ export interface BootLadderLike {
27
+ status: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootStatus>;
28
+ install: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootInstallResult>;
29
+ uninstall: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootInstallResult>;
30
+ }
31
+ export declare class HomeHostedError extends Error {
32
+ readonly code: string;
33
+ constructor(message: string, code: string);
34
+ }
35
+ export interface HomeHostedServiceOptions {
36
+ home: string;
37
+ stateDir: string;
38
+ homeHostedCommand?: string;
39
+ defaultEntryId: string;
40
+ settings: SettingsStore;
41
+ /** Test seam: run the home-hosted CLI without spawning it. */
42
+ execCli?: (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>;
43
+ /** Test seam: supply the boot ladder instead of probing the real OS. */
44
+ createLadder?: () => BootLadderLike;
45
+ }
46
+ export declare class HomeHostedService extends Service {
47
+ private readonly options;
48
+ private clientCache;
49
+ private tokenDetail;
50
+ private readonly snapshotsFile;
51
+ constructor(ctx: Context, options: HomeHostedServiceOptions);
52
+ private runtime;
53
+ /** The entry id this very process was started as, when the panel supervises us. */
54
+ selfEntryId(): string | null;
55
+ /** What the config's `meta.writtenBy` names: the panel release when we can read it. */
56
+ private writtenBy;
57
+ private webServer;
58
+ private cliCache;
59
+ /**
60
+ * Resolve the preferred CLI, refresh the stable launcher a boot entry runs, and
61
+ * preflight that launcher the same way the entry will invoke it.
62
+ */
63
+ private cli;
64
+ private panelControlDeps;
65
+ private cliExec;
66
+ panelStatus(): Promise<PanelStatus>;
67
+ private tryClient;
68
+ private requireClient;
69
+ private snapshots;
70
+ private saveSnapshots;
71
+ private liveEntries;
72
+ private createEntry;
73
+ private writeOwned;
74
+ private panelRunning;
75
+ /**
76
+ * The version whose schema will parse what we write: the answering panel, or
77
+ * the CLI that will parse it next. `kill` did not exist before 0.6.0, and an
78
+ * older panel refuses to *boot* with it — so this is checked before any write.
79
+ */
80
+ private configVersion;
81
+ private assertPolicySupported;
82
+ private applyIntents;
83
+ private restoreEntry;
84
+ entriesStatus(): Promise<ManagedEntryStatus[]>;
85
+ private ladder;
86
+ private bootSpec;
87
+ bootStatus(mechanism?: BootMechanism): Promise<BootStatus>;
88
+ installBoot(mechanism?: BootMechanism): Promise<{
89
+ result: BootInstallResult;
90
+ status: BootStatus;
91
+ }>;
92
+ uninstallBoot(mechanism?: BootMechanism): Promise<{
93
+ result: BootInstallResult;
94
+ status: BootStatus;
95
+ }>;
96
+ /** Out-of-the-box start: run the preferred CLI's `up`, which detaches itself. */
97
+ startPanelNow(): Promise<PanelControlResult>;
98
+ /**
99
+ * Replace an answering panel with the preferred copy.
100
+ *
101
+ * That stops the servers the old panel supervises — this process included — so
102
+ * the work is handed to a detached helper and the guard demands that this
103
+ * session is an adopted, autostarting entry the new panel will bring back.
104
+ */
105
+ takeoverPanel(force?: boolean): Promise<PanelControlResult>;
106
+ /** Install the pinned range globally, so the `global` preference has a copy to run. */
107
+ installGlobalCli(): Promise<PanelControlResult & {
108
+ output?: string;
109
+ }>;
110
+ /** Re-assert an entry that is already installed: a node or CLI upgrade moves the
111
+ * paths a unit was written with, and the fix is to rewrite it.
112
+ *
113
+ * Never installs one that is not there. Installing is a deliberate act — it can
114
+ * need root and it makes this machine start something at boot — so it happens on
115
+ * an explicit click or an approved tool call, not as a side effect of startup.
116
+ */
117
+ reconcile(): Promise<void>;
118
+ status(): Promise<HomeHostedStatus>;
119
+ call(endpoint: RpcEndpoint, payload: unknown): Promise<unknown>;
120
+ }
121
+ declare module '@deepseek-ai/cordis' {
122
+ interface Context {
123
+ homeHosted: HomeHostedService;
124
+ }
125
+ }
@@ -0,0 +1,13 @@
1
+ import type { EntryIntent, PluginSettings, SettingsPatch } from './shared/contracts.js';
2
+ export declare class SettingsStore {
3
+ readonly file: string;
4
+ private readonly fallbackEntryId;
5
+ private current;
6
+ private readonly listeners;
7
+ constructor(file: string, fallbackEntryId: string);
8
+ get(): PluginSettings;
9
+ /** The intent for an entry, materialising the default when it has none yet. */
10
+ intentFor(id: string): EntryIntent;
11
+ update(patch: SettingsPatch): PluginSettings;
12
+ onChange(listener: (settings: PluginSettings) => void): () => void;
13
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * The one wire contract shared by the host half and the browser half.
3
+ *
4
+ * Nothing here may import a Node builtin: the browser bundle includes this
5
+ * module verbatim.
6
+ */
7
+ /** Exact Fetch route registered on DeepSeek Harness's authenticated `/api` channel. */
8
+ export declare const RPC_PATH = "/home-hosted";
9
+ /** Bumped when a payload shape changes; a mismatch is reported, never guessed at. */
10
+ export declare const RPC_VERSION = 1;
11
+ export interface RpcError {
12
+ code: string;
13
+ message: string;
14
+ detail?: unknown;
15
+ }
16
+ export type Envelope<T> = {
17
+ ok: true;
18
+ value: T;
19
+ } | {
20
+ ok: false;
21
+ error: RpcError;
22
+ };
23
+ /** Endpoints the browser and the agent tools both speak. */
24
+ export type RpcEndpoint = 'status' | 'servers.list' | 'servers.get' | 'servers.create' | 'servers.update' | 'servers.delete' | 'servers.start' | 'servers.stop' | 'servers.restart' | 'servers.freePort' | 'entries.apply' | 'entries.restore' | 'boot.install' | 'boot.uninstall' | 'boot.verify' | 'panel.start' | 'panel.takeover' | 'cli.installGlobal' | 'settings.update';
25
+ /** A settings write sends only the changed subtree; the host merges group by group. */
26
+ export interface SettingsPatch {
27
+ autostart?: Partial<PluginSettings['autostart']>;
28
+ entries?: PluginSettings['entries'];
29
+ agentTools?: Partial<PluginSettings['agentTools']>;
30
+ cli?: Partial<PluginSettings['cli']>;
31
+ }
32
+ export interface EndpointPayloads {
33
+ 'status': {
34
+ refresh?: boolean;
35
+ };
36
+ 'servers.list': Record<string, never>;
37
+ 'servers.get': {
38
+ id: string;
39
+ };
40
+ 'servers.create': {
41
+ entry: ServerEntry;
42
+ };
43
+ 'servers.update': {
44
+ id: string;
45
+ patch: ServerEntryPatch;
46
+ };
47
+ 'servers.delete': {
48
+ id: string;
49
+ };
50
+ 'servers.start': {
51
+ id: string;
52
+ };
53
+ 'servers.stop': {
54
+ id: string;
55
+ };
56
+ 'servers.restart': {
57
+ id: string;
58
+ };
59
+ 'servers.freePort': {
60
+ id: string;
61
+ };
62
+ 'entries.apply': {
63
+ intents: EntryIntent[];
64
+ };
65
+ 'entries.restore': {
66
+ id: string;
67
+ };
68
+ 'boot.install': {
69
+ mechanism?: BootMechanism;
70
+ };
71
+ 'boot.uninstall': {
72
+ mechanism?: BootMechanism;
73
+ };
74
+ 'boot.verify': Record<string, never>;
75
+ /** Start the preferred CLI as a detached panel; refused while one already answers. */
76
+ 'panel.start': Record<string, never>;
77
+ /** Stop the answering panel and start the preferred copy instead. */
78
+ 'panel.takeover': {
79
+ force?: boolean;
80
+ };
81
+ /** Install the pinned range as a global CLI, so the `global` preference can use it. */
82
+ 'cli.installGlobal': Record<string, never>;
83
+ 'settings.update': {
84
+ patch: SettingsPatch;
85
+ };
86
+ }
87
+ export interface RpcRequest {
88
+ v: number;
89
+ endpoint: RpcEndpoint;
90
+ payload?: unknown;
91
+ }
92
+ export type OnPortConflict = 'block' | 'warn' | 'follow' | 'reclaim' | 'kill';
93
+ /** Only the fields this plugin reads or writes are typed; the rest is preserved. */
94
+ export interface ServerEntry {
95
+ id: string;
96
+ label?: string;
97
+ enabled?: boolean;
98
+ autostart?: boolean;
99
+ command?: string;
100
+ args?: string[];
101
+ cwd?: string;
102
+ env?: Record<string, string>;
103
+ dataEnvs?: Record<string, string>;
104
+ port?: number | null;
105
+ bind?: string;
106
+ onPortConflict?: OnPortConflict;
107
+ stop?: {
108
+ killPortHolders?: boolean;
109
+ [key: string]: unknown;
110
+ };
111
+ health?: Record<string, unknown>;
112
+ restart?: Record<string, unknown>;
113
+ [key: string]: unknown;
114
+ }
115
+ export type ServerEntryPatch = Partial<ServerEntry>;
116
+ /** What the plugin decides about one entry, and the keys it therefore owns. */
117
+ export interface EntryIntent {
118
+ id: string;
119
+ autostart: boolean;
120
+ onPortConflict: OnPortConflict;
121
+ stopKillPortHolders: boolean;
122
+ }
123
+ /** Keys an intent owns: a patch touches these and nothing else. */
124
+ export declare const OWNED_ENTRY_KEYS: readonly ["autostart", "onPortConflict", "stop"];
125
+ export interface ServerEntryView {
126
+ id: string;
127
+ status: string;
128
+ pid: number | null;
129
+ url: string | null;
130
+ config: ServerEntry;
131
+ }
132
+ export interface ManagedEntryStatus {
133
+ intent: EntryIntent;
134
+ exists: boolean;
135
+ /** True once the plugin has adopted the entry and holds a snapshot of what it was. */
136
+ managed: boolean;
137
+ /** Owned keys whose live value differs from the intent. */
138
+ drift: string[];
139
+ live: ServerEntryView | null;
140
+ snapshot: ServerEntry | null;
141
+ }
142
+ export type TokenState = 'enrolled' | 'present' | 'absent' | 'unknown';
143
+ export interface PanelStatus {
144
+ /** `$HHOSTED_HOME` this plugin resolved, whether or not the panel answers. */
145
+ home: string;
146
+ reachable: boolean;
147
+ url: string | null;
148
+ version: string | null;
149
+ pid: number | null;
150
+ /** How writes are authenticated right now. */
151
+ writeVia: 'api' | 'file' | 'none';
152
+ token: TokenState;
153
+ detail: string;
154
+ }
155
+ /** Which home-hosted CLI the plugin drives, and where it came from. */
156
+ export type CliSource = 'config' | 'dependency' | 'path' | 'none';
157
+ /** One place a CLI could come from, with the version found there. */
158
+ export interface CliCandidate {
159
+ source: 'config' | 'dependency' | 'path';
160
+ path: string | null;
161
+ version: string | null;
162
+ }
163
+ export interface PanelControlResult {
164
+ ok: boolean;
165
+ detail: string;
166
+ url?: string | null;
167
+ version?: string | null;
168
+ }
169
+ export interface CliStatus {
170
+ /** `config` (operator override), `dependency` (the pinned copy), `path`, or none. */
171
+ source: CliSource;
172
+ path: string | null;
173
+ version: string | null;
174
+ /** The range the plugin ships in its own dependencies. */
175
+ expectedRange: string;
176
+ /** False when the resolved CLI is older than the oldest release this plugin supports. */
177
+ supported: boolean;
178
+ /** The stable launcher a boot entry runs instead of a moving node_modules path. */
179
+ launcherPath?: string | null;
180
+ /** What the launcher answers right now, as the boot entry would invoke it. */
181
+ launcherVersion?: string | null;
182
+ /** Which copy the plugin prefers. */
183
+ prefer?: 'pinned' | 'global';
184
+ /** The plugin's own copy, when it resolves. */
185
+ dependency?: CliCandidate | null;
186
+ /** The global install on PATH, when there is one. */
187
+ global?: CliCandidate | null;
188
+ detail: string;
189
+ }
190
+ export type BootMechanism = 'systemd-user' | 'systemd-system' | 'xdg-autostart' | 'launchd-agent' | 'launchd-daemon' | 'windows-run' | 'windows-task' | 'container' | 'unsupported';
191
+ export type BootState = 'not-installed' | 'installed-disabled' | 'enabled-running' | 'enabled-failing' | 'unsupported';
192
+ export interface BootCandidate {
193
+ mechanism: BootMechanism;
194
+ available: boolean;
195
+ /** Starts before an interactive login. */
196
+ bootCapable: boolean;
197
+ /** This process can drive it without an elevation prompt. */
198
+ privileged: boolean;
199
+ reason: string;
200
+ }
201
+ export interface BootStatus {
202
+ platform: 'linux' | 'darwin' | 'win32' | 'other';
203
+ mechanism: BootMechanism | null;
204
+ recommended: BootMechanism | null;
205
+ state: BootState;
206
+ bootCapable: boolean;
207
+ privileged: boolean;
208
+ unitPath: string | null;
209
+ /** Exact commands a person can run when this process cannot elevate. */
210
+ commands: string[];
211
+ detail: string;
212
+ candidates: BootCandidate[];
213
+ }
214
+ export type AgentToolName = 'status' | 'servers_list' | 'servers_start' | 'servers_stop' | 'servers_restart' | 'servers_create' | 'servers_update' | 'servers_delete' | 'autostart_install' | 'autostart_uninstall';
215
+ export declare const AGENT_TOOL_NAMES: readonly AgentToolName[];
216
+ /** Tools that change something; every one of them asks for approval first. */
217
+ export declare const MUTATING_AGENT_TOOLS: readonly AgentToolName[];
218
+ export interface PluginSettings {
219
+ autostart: {
220
+ enabled: boolean;
221
+ /** `auto` picks the best available mechanism; an explicit one is honoured only when available. */
222
+ mechanism: 'auto' | BootMechanism;
223
+ };
224
+ entries: EntryIntent[];
225
+ agentTools: {
226
+ enabled: boolean;
227
+ allow: AgentToolName[];
228
+ };
229
+ cli: {
230
+ /** `pinned` runs the copy this plugin ships; `global` runs the one on PATH. */
231
+ prefer: 'pinned' | 'global';
232
+ };
233
+ }
234
+ export declare const DEFAULT_SETTINGS: PluginSettings;
235
+ export interface HomeHostedStatus {
236
+ panel: PanelStatus;
237
+ boot: BootStatus;
238
+ entries: ManagedEntryStatus[];
239
+ servers: ServerEntryView[];
240
+ settings: PluginSettings;
241
+ /** The CLI the plugin drives; absent only from a host older than this field. */
242
+ cli?: CliStatus;
243
+ /** Set when the panel could not be reached, so the UI can show a degraded page. */
244
+ lastError: string | null;
245
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Agent tools, off by default and allowlisted by name.
3
+ *
4
+ * The list is read at registration time and re-read whenever the settings file
5
+ * changes, so toggling a tool takes effect without a restart. Every tool that
6
+ * changes something asks the approval service first and fails closed when it is
7
+ * absent or refuses.
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ import type { AgentToolName } from './shared/contracts.js';
11
+ import type { HomeHostedService } from './service.js';
12
+ import type { SettingsStore } from './settings.js';
13
+ export declare function toolNameFor(name: AgentToolName): string;
14
+ export declare function registerAgentTools(ctx: Context, service: HomeHostedService, settings: SettingsStore): void;
@@ -0,0 +1,29 @@
1
+ export interface RunOptions {
2
+ cwd?: string;
3
+ env?: Record<string, string | undefined>;
4
+ timeoutMs?: number;
5
+ /** Cap on each captured stream; a chatty command is truncated, not buffered forever. */
6
+ maxBytes?: number;
7
+ }
8
+ export interface RunResult {
9
+ command: string;
10
+ args: string[];
11
+ code: number | null;
12
+ signal: NodeJS.Signals | null;
13
+ stdout: string;
14
+ stderr: string;
15
+ timedOut: boolean;
16
+ error: string | null;
17
+ }
18
+ /** Run a program and always resolve; a non-zero code is data, not a throw. */
19
+ export declare function run(command: string, args?: string[], options?: RunOptions): Promise<RunResult>;
20
+ /** Run a program and fail loudly on anything but success. */
21
+ export declare function runOk(command: string, args?: string[], options?: RunOptions): Promise<RunResult>;
22
+ /** True when the program exists and answers to `--version` or similar. */
23
+ export declare function hasCommand(command: string): Promise<boolean>;
24
+ /**
25
+ * Whether this process can run one privileged command with no prompt.
26
+ * `sudo -n` never asks for a password, so a failure is a definitive "not
27
+ * available" rather than something to retry interactively.
28
+ */
29
+ export declare function sudoAvailable(): Promise<boolean>;
@@ -0,0 +1,9 @@
1
+ export declare function fileExists(file: string): boolean;
2
+ export declare function readText(file: string): string | null;
3
+ export declare function readJson<T>(file: string): T | null;
4
+ export declare function ensureDir(dir: string, mode?: number): void;
5
+ /** Write a file through a sibling temp file, then rename it into place. */
6
+ export declare function writeFileAtomic(file: string, data: string, mode?: number): void;
7
+ export declare function writeJsonAtomic(file: string, value: unknown, mode?: number): void;
8
+ /** Best-effort removal; a missing file is not an error. */
9
+ export declare function removeFile(file: string): void;
@@ -0,0 +1,12 @@
1
+ export declare function expandHome(value: string): string;
2
+ /** DeepSeek Harness home: `$DSH_HOME`, else `~/.dsh`. */
3
+ export declare function dshHome(): string;
4
+ /** home-hosted state root: `$HHOSTED_HOME`, else `~/.home-hosted`. */
5
+ export declare function homeHostedHome(): string;
6
+ /** Where this plugin keeps its own durable state (settings, snapshots, token). */
7
+ export declare function pluginStateDir(override?: string): string;
8
+ /** home-hosted's own tokens file, written by the panel at startup. */
9
+ export declare function runtimeFile(home?: string): string;
10
+ /** home-hosted's secrets file (0600): proves whether an API token is enrolled. */
11
+ export declare function secretsFile(home?: string): string;
12
+ export declare function configFile(home?: string): string;
package/package.json ADDED
@@ -0,0 +1,108 @@
1
+ {
2
+ "name": "dsh-home-hosted",
3
+ "version": "0.1.0",
4
+ "description": "Boot autostart for home-hosted and home-hosted server management from DeepSeek Harness.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/NamesMT/dsh-home-hosted.git"
13
+ },
14
+ "homepage": "https://github.com/NamesMT/dsh-home-hosted#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/NamesMT/dsh-home-hosted/issues"
17
+ },
18
+ "keywords": [
19
+ "deepseek-harness",
20
+ "dsh-plugin",
21
+ "home-hosted",
22
+ "autostart",
23
+ "systemd",
24
+ "launchd"
25
+ ],
26
+ "engines": {
27
+ "node": ">=24"
28
+ },
29
+ "main": "lib/index.js",
30
+ "types": "lib/types/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./lib/types/index.d.ts",
34
+ "default": "./lib/index.js"
35
+ },
36
+ "./client": {
37
+ "default": "./lib/client.js"
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "files": [
42
+ "lib",
43
+ "cordis.patch.yml",
44
+ "README.md",
45
+ "docs/DESIGN.md",
46
+ "LICENSE"
47
+ ],
48
+ "scripts": {
49
+ "build": "node scripts/build.mjs && node scripts/build-client.mjs && tsc -p tsconfig.build.json --emitDeclarationOnly",
50
+ "build:host": "node scripts/build.mjs",
51
+ "build:client": "node scripts/build-client.mjs",
52
+ "prepack": "pnpm run build",
53
+ "test": "vitest run",
54
+ "typecheck": "tsc --noEmit -p tsconfig.json",
55
+ "prepublishOnly": "pnpm run typecheck && pnpm run test"
56
+ },
57
+ "dsh": {
58
+ "manifestVersion": 1,
59
+ "bundle": {
60
+ "patch": "./cordis.patch.yml"
61
+ },
62
+ "client": {
63
+ "platform": "web",
64
+ "inject": [
65
+ "@deepseek-ai/dsh-client-ui-settings",
66
+ "@deepseek-ai/dsh-client-locale"
67
+ ]
68
+ }
69
+ },
70
+ "peerDependencies": {
71
+ "@deepseek-ai/cordis": "^4.0.4",
72
+ "@deepseek-ai/dsh-client-connection": ">=0.1.7-rc.2",
73
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.2",
74
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-rc.2",
75
+ "@deepseek-ai/dsh-tools": ">=0.1.7-rc.2",
76
+ "@deepseek-ai/schemastery": "^3.18.1"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "@deepseek-ai/dsh-client-connection": {
80
+ "optional": true
81
+ },
82
+ "@deepseek-ai/dsh-client-locale": {
83
+ "optional": true
84
+ },
85
+ "@deepseek-ai/dsh-client-ui-settings": {
86
+ "optional": true
87
+ }
88
+ },
89
+ "devDependencies": {
90
+ "@deepseek-ai/cordis": "^4.0.4",
91
+ "@deepseek-ai/dsh-client-connection": ">=0.1.7-rc.2",
92
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-rc.2",
93
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-rc.2",
94
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-rc.2",
95
+ "@deepseek-ai/dsh-tools": ">=0.1.7-rc.2",
96
+ "@deepseek-ai/schemastery": "^3.18.1",
97
+ "@types/node": "^22.10.2",
98
+ "@types/react": "^19.0.2",
99
+ "esbuild": "^0.24.2",
100
+ "react": "^19.0.0",
101
+ "react-dom": "^19.0.0",
102
+ "typescript": "^5.7.2",
103
+ "vitest": "^2.1.8"
104
+ },
105
+ "dependencies": {
106
+ "home-hosted": "^0.6.1"
107
+ }
108
+ }