dsh-web-search-firecrawl 0.1.1 → 0.1.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/README.md CHANGED
@@ -1,28 +1,40 @@
1
1
  # dsh-web-search-firecrawl
2
2
 
3
- ```sh
4
- dsh plugin --profile web add github:aa2246740/dsh-firecrawl
3
+ ## 安装
4
+
5
+ ### DSH Studio 桌面 App(推荐)
6
+
7
+ 打开 **设置 → 插件 → 添加插件**,在“包名或地址”中输入:
8
+
9
+ ```text
10
+ github:aa2246740/dsh-firecrawl#v0.1.4
5
11
  ```
6
12
 
7
- PATH 上要有官方 `dsh`(没有就用 `npx @deepseek-ai/dsh`)和 **pnpm**。`dsh plugin add` 会在 `$DSH_HOME/profiles/web` 里跑 pnpm。仓库已提交编译好的 `lib/`,git 安装不用 `prepare`,也不用改 profile 的 `allowBuilds`。
13
+ 桌面端插件管理器负责 Desktop profile 和内置包管理器。本发布已包含编译好的 `lib/`;普通使用不需要 clone 或本地构建。若应用提示刷新或重新打开,请按提示完成。
14
+
15
+ ### Web CLI
16
+
17
+ ```sh
18
+ dsh plugin --profile web add github:aa2246740/dsh-firecrawl#v0.1.4
19
+ ```
8
20
 
9
- 然后重启这个 Host,再刷新页面。`dsh plugin add` 只写 profile,不会热挂正在跑的进程。
21
+ 这条官方 CLI 命令只写入 `web` profile,不能修改 Desktop App 的 profile。对于已经运行的 Web Host,请重新打开该 Host 一次,再刷新网页。
10
22
 
11
23
  给 DeepSeek Harness 的 `web_search` 换 Firecrawl,并带多账号池。包名仍是 `dsh-web-search-firecrawl`。仓库是 `dsh-firecrawl`。
12
24
 
13
25
  在 `ctx.web` 上注册 `id: firecrawl` 的 `WebSearchProvider`,走 Firecrawl `/v1/search`。调度默认 `round-robin`,也可 `least-loaded`。`429` 或 `5xx` 切到下一个健康账号,默认冷却 60 秒。不可重试的 `4xx` 立刻失败并点名那个账号。
14
26
 
15
- 这个 bundle 会把官方 `web.searchProvider` 设成 `firecrawl`,并保留 `fetchProvider: http`。设置在 Settings → Plugins → Plugin configuration → **Firecrawl search**。
27
+ 这个 bundle 会把官方 `web.searchProvider` 设成 `firecrawl`,并保留 `fetchProvider: http`。账号在侧栏 **插件** 里打开已安装的 `dsh-web-search-firecrawl`,再点该行的 **配置**。
16
28
 
17
- 面向官方 DeepSeek Harness **0.1.5-rc.3**。
29
+ 面向官方 DeepSeek Harness **0.2.0-rc.2**(`@deepseek-ai/dsh@0.2.0-rc.2`)。`@deepseek-ai/dsh` 与同名 `@deepseek-ai/dsh-*` peer 是 `>=0.2.0-rc.1 <0.2.1`:接受 `0.2.0-rc.2` 和稳定版 `0.2.0`,拒绝 `0.2.0` alpha,也拒绝 `0.1.7-rc.2`。
18
30
 
19
- ## 其它装法
31
+ ## 其它装法(开发/本地测试)
20
32
 
21
33
  本地目录或 tarball:
22
34
 
23
35
  ```sh
24
36
  dsh plugin --profile web add ./dsh-firecrawl
25
- dsh plugin --profile web add ./dsh-web-search-firecrawl-0.1.1.tgz
37
+ dsh plugin --profile web add ./dsh-web-search-firecrawl-0.1.4.tgz
26
38
  ```
27
39
 
28
40
  `dsh.bundle` 是开机捕获的。不要再往 profile 的 `cordis.patch.yml` 手写同一条 insert,会重复挂载。
@@ -33,7 +45,7 @@ dsh plugin --profile web remove dsh-web-search-firecrawl
33
45
 
34
46
  ## 使用
35
47
 
36
- 1. 打开 WebUI:Settings → Plugins → Plugin configuration → **Firecrawl search**。
48
+ 1. 打开 WebUI 侧栏 **插件**,进入 `dsh-web-search-firecrawl`,点 **配置**。
37
49
  2. 加至少一个 Firecrawl API key([firecrawl.dev](https://firecrawl.dev) 的 `fc-...`),点 **保存**。密钥不会回显。改名时 key 留空表示保留原 key。
38
50
  3. 用会暴露 `web_search` 的新会话。
39
51
 
package/lib/client.js CHANGED
@@ -18,21 +18,24 @@ window.__ModuleLoader__.load({
18
18
  //#region src/client/accounts-controller.ts
19
19
  /** All edits use the namespace's revision fence and never read credential values. */
20
20
  var AccountsController = class {
21
- scope;
22
- constructor(scope) {
23
- this.scope = scope;
21
+ form;
22
+ constructor(form) {
23
+ this.form = form;
24
24
  }
25
25
  snapshot() {
26
- const snapshot = this.scope.getSnapshot();
27
- if (snapshot.status !== "ready" || !snapshot.writable) throw new Error("Settings are not writable");
26
+ const snapshot = this.form.getSnapshot();
27
+ if (snapshot.status !== "ready" || !snapshot.writable || snapshot.revision === void 0) throw new Error("Settings are not writable");
28
28
  return {
29
29
  accounts: metadataOnly(snapshot.value?.accounts ?? []),
30
30
  revision: snapshot.revision
31
31
  };
32
32
  }
33
+ async commit(ops, revision) {
34
+ if (!await this.form.mutate(ops, revision)) throw new Error("Settings write was not accepted");
35
+ }
33
36
  add(id) {
34
37
  const { accounts, revision } = this.snapshot();
35
- return this.scope.mutate([{
38
+ return this.commit([{
36
39
  op: "set",
37
40
  path: ["accounts"],
38
41
  value: [...accounts, {
@@ -57,11 +60,11 @@ window.__ModuleLoader__.load({
57
60
  path: ["apiKeys", id],
58
61
  value: key.trim()
59
62
  });
60
- return this.scope.mutate(ops, revision);
63
+ return this.commit(ops, revision);
61
64
  }
62
65
  remove(id) {
63
66
  const { accounts, revision } = this.snapshot();
64
- return this.scope.mutate([{
67
+ return this.commit([{
65
68
  op: "set",
66
69
  path: ["accounts"],
67
70
  value: accounts.filter((account) => account.id !== id)
@@ -74,24 +77,27 @@ window.__ModuleLoader__.load({
74
77
  //#endregion
75
78
  //#region src/client/index.tsx
76
79
  const NAMESPACE = "dsh-web-search-firecrawl";
80
+ /** `plugins.row.config` key: `<package name>#<row id>` from cordis.patch.yml. */
81
+ const ROW_KEY = "dsh-web-search-firecrawl#dsh-web-search-firecrawl";
77
82
  const name = "dsh-web-search-firecrawl-client";
78
83
  const inject = [
79
84
  "slots",
80
- "settingsScope",
85
+ "configForms",
81
86
  "remote",
82
87
  "remote.settings"
83
88
  ];
84
89
  function apply(ctx) {
85
- const scope = ctx.settingsScope.bind({ namespace: NAMESPACE });
86
- const accounts = new AccountsController(scope);
87
- ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
88
- name: "settings.plugin.item",
89
- key: NAMESPACE
90
- }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FirecrawlCard, {
91
- scope,
90
+ const form = ctx.configForms.get(NAMESPACE);
91
+ const accounts = new AccountsController(form);
92
+ ctx.effect(() => ctx.configForms.whileServed([NAMESPACE], () => ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
93
+ name: "plugins.row.config",
94
+ key: ROW_KEY
95
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FirecrawlCard, {
96
+ view: props.view,
97
+ form,
92
98
  accounts,
93
99
  ctx
94
- })));
100
+ })))));
95
101
  }
96
102
  const inputStyle = {
97
103
  padding: "6px 8px",
@@ -150,7 +156,7 @@ window.__ModuleLoader__.load({
150
156
  "aria-label": "Firecrawl API key",
151
157
  style: inputStyle,
152
158
  type: "password",
153
- autoComplete: "off",
159
+ autoComplete: "new-password",
154
160
  value: key,
155
161
  placeholder: configured ? "留空保留已有 key" : "fc-...",
156
162
  onChange: (event) => setKey(event.target.value)
@@ -173,8 +179,8 @@ window.__ModuleLoader__.load({
173
179
  })]
174
180
  });
175
181
  }
176
- function FirecrawlCard({ scope, accounts: controller, ctx }) {
177
- const snapshot = (0, react.useSyncExternalStore)(scope.subscribe.bind(scope), scope.getSnapshot.bind(scope));
182
+ function FirecrawlCard({ view = "page", form, accounts: controller, ctx }) {
183
+ const snapshot = (0, react.useSyncExternalStore)(form.subscribe.bind(form), form.getSnapshot.bind(form));
178
184
  const [open, setOpen] = (0, react.useState)(false);
179
185
  const [busy, setBusy] = (0, react.useState)(false);
180
186
  const [error, setError] = (0, react.useState)("");
@@ -191,6 +197,7 @@ window.__ModuleLoader__.load({
191
197
  current = false;
192
198
  };
193
199
  }, [snapshot.revision, ctx]);
200
+ if (view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "搜索账号与负载均衡" });
194
201
  if (snapshot.status !== "ready") return null;
195
202
  const section = snapshot.value ?? {};
196
203
  async function run(action) {
@@ -273,7 +280,9 @@ window.__ModuleLoader__.load({
273
280
  "aria-label": "账号策略",
274
281
  style: inputStyle,
275
282
  value: section.strategy ?? "round-robin",
276
- onChange: (event) => act(() => scope.set("strategy", event.target.value)),
283
+ onChange: (event) => act(() => form.set("strategy", event.target.value).then((ok) => {
284
+ if (!ok) throw new Error("refused");
285
+ })),
277
286
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
278
287
  value: "round-robin",
279
288
  children: "轮询"
@@ -293,7 +302,9 @@ window.__ModuleLoader__.load({
293
302
  defaultValue: section.limit ?? 5,
294
303
  onBlur: (event) => {
295
304
  const value = Number(event.target.value);
296
- if (Number.isInteger(value) && value > 0) act(() => scope.set("limit", value));
305
+ if (Number.isInteger(value) && value > 0) act(() => form.set("limit", value).then((ok) => {
306
+ if (!ok) throw new Error("refused");
307
+ }));
297
308
  }
298
309
  })] }),
299
310
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: ["冷却毫秒 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -307,7 +318,9 @@ window.__ModuleLoader__.load({
307
318
  defaultValue: section.cooldownMs ?? 6e4,
308
319
  onBlur: (event) => {
309
320
  const value = Number(event.target.value);
310
- if (Number.isInteger(value) && value >= 0) act(() => scope.set("cooldownMs", value));
321
+ if (Number.isInteger(value) && value >= 0) act(() => form.set("cooldownMs", value).then((ok) => {
322
+ if (!ok) throw new Error("refused");
323
+ }));
311
324
  }
312
325
  })] })
313
326
  ]
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useState","useSyncExternalStore"],"sources":["../src/accounts.ts","../src/client/accounts-controller.ts","../src/client/index.tsx"],"sourcesContent":["import type { FirecrawlAccount } from './provider.js'\n\n/** Legacy secrets are accepted only for migration; new writes keep them separate. */\nexport interface AccountMetadata {\n id: string\n label: string\n apiKey?: string\n}\n\nexport interface AccountSettings {\n accounts?: AccountMetadata[]\n apiKeys?: Record<string, string>\n}\n\n/** Storage boundary: an unfinished account is not a usable credential. */\nexport function resolvedAccounts(config: AccountSettings): FirecrawlAccount[] {\n return (config.accounts ?? []).flatMap(account => {\n const apiKey = config.apiKeys?.[account.id] ?? account.apiKey\n return typeof apiKey === 'string' && apiKey.trim().length > 0\n ? [{ id: account.id, label: account.label, apiKey }]\n : []\n })\n}\n\n/** One atomic owner-side migration, never reconstructed from a redacted view. */\nexport function legacyAccountMigration(config: AccountSettings): AccountSettings | undefined {\n if (!(config.accounts ?? []).some(account => account.apiKey !== undefined)) return undefined\n const apiKeys = { ...config.apiKeys }\n const accounts = (config.accounts ?? []).map(({ id, label, apiKey }) => {\n if (!Object.hasOwn(apiKeys, id) && typeof apiKey === 'string' && apiKey.trim()) apiKeys[id] = apiKey\n return { id, label }\n })\n return { accounts, apiKeys }\n}\n\n/** Wire-safe metadata projection; never repeat any credential in an array write. */\nexport function metadataOnly(accounts: AccountMetadata[]): { id: string; label: string }[] {\n return accounts.map(({ id, label }) => ({ id, label }))\n}\n","import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SettingsPathOpView } from '@deepseek-ai/dsh-settings/types'\nimport { metadataOnly, type AccountSettings } from '../accounts.js'\n\n/** All edits use the namespace's revision fence and never read credential values. */\nexport class AccountsController {\n constructor(private readonly scope: SettingsScope<AccountSettings>) {}\n\n private snapshot() {\n const snapshot = this.scope.getSnapshot()\n if (snapshot.status !== 'ready' || !snapshot.writable) throw new Error('Settings are not writable')\n return { accounts: metadataOnly(snapshot.value?.accounts ?? []), revision: snapshot.revision }\n }\n\n add(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.scope.mutate([{ op: 'set', path: ['accounts'], value: [...accounts, { id, label: '' }] }], revision)\n }\n\n save(id: string, label: string, key: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n if (!accounts.some(account => account.id === id)) throw new Error('Account was removed')\n const ops: SettingsPathOpView[] = [{ op: 'set', path: ['accounts'], value: accounts.map(account => account.id === id ? { id, label } : account) }]\n if (key.trim()) ops.push({ op: 'set', path: ['apiKeys', id], value: key.trim() })\n return this.scope.mutate(ops, revision)\n }\n\n remove(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.scope.mutate([\n { op: 'set', path: ['accounts'], value: accounts.filter(account => account.id !== id) },\n { op: 'unset', path: ['apiKeys', id] },\n ], revision)\n }\n}\n","import { useEffect, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type { SettingsDescribeValue } from '@deepseek-ai/dsh-settings/types'\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type { AccountSettings, AccountMetadata } from '../accounts.js'\nimport { AccountsController } from './accounts-controller.js'\n\nconst NAMESPACE = 'dsh-web-search-firecrawl'\ninterface Section extends AccountSettings {\n limit?: number\n strategy?: 'round-robin' | 'least-loaded'\n cooldownMs?: number\n}\nexport const name = 'dsh-web-search-firecrawl-client'\nexport const inject = ['slots', 'settingsScope', 'remote', 'remote.settings']\n\nexport function apply(ctx: Context): void {\n const scope = ctx.settingsScope.bind<Section>({ namespace: NAMESPACE })\n const accounts = new AccountsController(scope)\n ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NAMESPACE },\n () => <FirecrawlCard scope={scope} accounts={accounts} ctx={ctx} />))\n}\n\nconst inputStyle = { padding: '6px 8px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit' }\nconst buttonStyle = { ...inputStyle, cursor: 'pointer' }\n\nfunction AccountRow({ account, configured, busy, save, remove }: {\n account: AccountMetadata; configured: boolean | undefined; busy: boolean\n save: (label: string, key: string) => Promise<void>; remove: () => void\n}): ReactNode {\n const [label, setLabel] = useState(account.label)\n const [key, setKey] = useState('')\n useEffect(() => { setLabel(account.label) }, [account.label])\n return <fieldset style={{ border: '1px solid #8884', borderRadius: 6, padding: 10, marginTop: 10 }} disabled={busy}>\n <legend>{account.label || '新账号'}</legend>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'end' }}>\n <label style={{ display: 'grid', gap: 4 }}>名称\n <input aria-label=\"账号名称\" style={inputStyle} value={label} onChange={event => setLabel(event.target.value)} />\n </label>\n <label style={{ display: 'grid', gap: 4 }}>API key · {configured === undefined ? '状态待确认' : configured ? '已配置' : '未配置'}\n <input aria-label=\"Firecrawl API key\" style={inputStyle} type=\"password\" autoComplete=\"off\" value={key}\n placeholder={configured ? '留空保留已有 key' : 'fc-...'} onChange={event => setKey(event.target.value)} />\n </label>\n <button style={buttonStyle} onClick={() => { void save(label, key).then(() => setKey('')).catch(() => {}) }}>保存</button>\n <button style={buttonStyle} onClick={remove}>移除账号</button>\n </div>\n </fieldset>\n}\n\nfunction FirecrawlCard({ scope, accounts: controller, ctx }: { scope: SettingsScope<Section>; accounts: AccountsController; ctx: Context }): ReactNode {\n const snapshot = useSyncExternalStore(scope.subscribe.bind(scope), scope.getSnapshot.bind(scope))\n const [open, setOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n const [configured, setConfigured] = useState<Record<string, boolean> | undefined>()\n // The API reports configured-state metadata, never credential values.\n useEffect(() => {\n let current = true\n setConfigured(undefined)\n void ctx.remote.settings.describe().then((result: { ok: true; value: SettingsDescribeValue } | { ok: false }) => {\n if (!current || !result.ok) return\n const ns = result.value.namespaces.find(item => item.ns === NAMESPACE)\n if (ns) setConfigured(Object.fromEntries(ns.secrets.filter(secret => secret.path[0] === 'apiKeys').map(secret => [secret.path[1], secret.set])))\n }).catch(() => {})\n return () => { current = false }\n }, [snapshot.revision, ctx])\n\n if (snapshot.status !== 'ready') return null\n const section = snapshot.value ?? {}\n async function run(action: () => Promise<void>): Promise<void> {\n setBusy(true)\n setError('')\n try { await action() }\n catch (error) { setError('保存失败,未确认写入。请重新打开设置后重试。'); throw error }\n finally { setBusy(false) }\n }\n function act(action: () => Promise<void>): void { void run(action).catch(() => {}) }\n const disabled = busy || !snapshot.writable\n return <li style={{ listStyle: 'none', border: '1px solid #8884', borderRadius: 8, margin: '8px 0' }}>\n <button style={{ ...buttonStyle, border: 0, width: '100%', textAlign: 'left', padding: 14 }} aria-expanded={open} onClick={() => setOpen(!open)}>\n <strong>Firecrawl search</strong><div style={{ opacity: 0.7, fontSize: 12 }}>搜索账号与负载均衡 · {open ? '收起' : '展开'}</div>\n </button>\n {open && <div style={{ padding: '0 14px 14px' }}>\n {(section.accounts ?? []).map(account => <AccountRow key={account.id} account={account}\n configured={configured === undefined ? undefined : configured[account.id] === true} busy={disabled}\n save={(label, key) => run(() => controller.save(account.id, label, key))}\n remove={() => act(() => controller.remove(account.id))} />)}\n <button style={{ ...buttonStyle, marginTop: 10 }} disabled={disabled} onClick={() => act(() => controller.add(crypto.randomUUID()))}>添加账号</button>\n <p style={{ fontSize: 12, opacity: 0.75 }}>Key 只写入、不回显。填写后点“保存”;只改名称不会清除已有 key。</p>\n <fieldset disabled={disabled} style={{ border: 0, padding: 0, display: 'flex', gap: 12, flexWrap: 'wrap' }}>\n <label>策略 <select aria-label=\"账号策略\" style={inputStyle} value={section.strategy ?? 'round-robin'} onChange={event => act(() => scope.set('strategy', event.target.value))}>\n <option value=\"round-robin\">轮询</option><option value=\"least-loaded\">最少使用</option>\n </select></label>\n <label>结果数 <input aria-label=\"搜索结果数\" style={{ ...inputStyle, width: 65 }} type=\"number\" min={1} defaultValue={section.limit ?? 5}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value > 0) act(() => scope.set('limit', value)) }} /></label>\n <label>冷却毫秒 <input aria-label=\"冷却毫秒\" style={{ ...inputStyle, width: 90 }} type=\"number\" min={0} defaultValue={section.cooldownMs ?? 60000}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value >= 0) act(() => scope.set('cooldownMs', value)) }} /></label>\n </fieldset>\n {error && <p role=\"alert\">{error}</p>}\n <p style={{ fontSize: 12, opacity: 0.75 }}>这个 bundle 会把 <code>web.searchProvider</code> 设为 <code>firecrawl</code>。加好账号后,用会暴露 <code>web_search</code> 的新会话。</p>\n </div>}\n </li>\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useState","useSyncExternalStore"],"sources":["../src/accounts.ts","../src/client/accounts-controller.ts","../src/client/index.tsx"],"sourcesContent":["import type { FirecrawlAccount } from './provider.js'\n\n/** Legacy secrets are accepted only for migration; new writes keep them separate. */\nexport interface AccountMetadata {\n id: string\n label: string\n apiKey?: string\n}\n\nexport interface AccountSettings {\n accounts?: AccountMetadata[]\n apiKeys?: Record<string, string>\n}\n\n/** Storage boundary: an unfinished account is not a usable credential. */\nexport function resolvedAccounts(config: AccountSettings): FirecrawlAccount[] {\n return (config.accounts ?? []).flatMap(account => {\n const apiKey = config.apiKeys?.[account.id] ?? account.apiKey\n return typeof apiKey === 'string' && apiKey.trim().length > 0\n ? [{ id: account.id, label: account.label, apiKey }]\n : []\n })\n}\n\n/** One atomic owner-side migration, never reconstructed from a redacted view. */\nexport function legacyAccountMigration(config: AccountSettings): AccountSettings | undefined {\n if (!(config.accounts ?? []).some(account => account.apiKey !== undefined)) return undefined\n const apiKeys = { ...config.apiKeys }\n const accounts = (config.accounts ?? []).map(({ id, label, apiKey }) => {\n if (!Object.hasOwn(apiKeys, id) && typeof apiKey === 'string' && apiKey.trim()) apiKeys[id] = apiKey\n return { id, label }\n })\n return { accounts, apiKeys }\n}\n\n/** Wire-safe metadata projection; never repeat any credential in an array write. */\nexport function metadataOnly(accounts: AccountMetadata[]): { id: string; label: string }[] {\n return accounts.map(({ id, label }) => ({ id, label }))\n}\n","import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SettingsPathOpView } from '@deepseek-ai/dsh-settings/types'\nimport { metadataOnly, type AccountSettings } from '../accounts.js'\n\n/** All edits use the namespace's revision fence and never read credential values. */\nexport class AccountsController {\n constructor(private readonly form: ConfigForm<AccountSettings>) {}\n\n private snapshot() {\n const snapshot = this.form.getSnapshot()\n if (snapshot.status !== 'ready' || !snapshot.writable || snapshot.revision === undefined) {\n throw new Error('Settings are not writable')\n }\n return { accounts: metadataOnly(snapshot.value?.accounts ?? []), revision: snapshot.revision }\n }\n\n private async commit(ops: readonly SettingsPathOpView[], revision: number): Promise<void> {\n const accepted = await this.form.mutate(ops, revision)\n if (!accepted) throw new Error('Settings write was not accepted')\n }\n\n add(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.commit([{ op: 'set', path: ['accounts'], value: [...accounts, { id, label: '' }] }], revision)\n }\n\n save(id: string, label: string, key: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n if (!accounts.some(account => account.id === id)) throw new Error('Account was removed')\n const ops: SettingsPathOpView[] = [{ op: 'set', path: ['accounts'], value: accounts.map(account => account.id === id ? { id, label } : account) }]\n if (key.trim()) ops.push({ op: 'set', path: ['apiKeys', id], value: key.trim() })\n return this.commit(ops, revision)\n }\n\n remove(id: string): Promise<void> {\n const { accounts, revision } = this.snapshot()\n return this.commit([\n { op: 'set', path: ['accounts'], value: accounts.filter(account => account.id !== id) },\n { op: 'unset', path: ['apiKeys', id] },\n ], revision)\n }\n}\n","import { useEffect, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type { SettingsDescribeValue } from '@deepseek-ai/dsh-settings/types'\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type { AccountSettings, AccountMetadata } from '../accounts.js'\nimport { AccountsController } from './accounts-controller.js'\n\nconst NAMESPACE = 'dsh-web-search-firecrawl'\n/** `plugins.row.config` key: `<package name>#<row id>` from cordis.patch.yml. */\nconst ROW_KEY = 'dsh-web-search-firecrawl#dsh-web-search-firecrawl'\ninterface Section extends AccountSettings {\n limit?: number\n strategy?: 'round-robin' | 'least-loaded'\n cooldownMs?: number\n}\nexport const name = 'dsh-web-search-firecrawl-client'\nexport const inject = ['slots', 'configForms', 'remote', 'remote.settings']\n\nexport function apply(ctx: Context): void {\n const form = ctx.configForms.get<Section>(NAMESPACE)\n const accounts = new AccountsController(form)\n ctx.effect(() => ctx.configForms.whileServed([NAMESPACE], () => ctx.slots.inject('plugins.row.config', () => ctx.slots.register({\n name: 'plugins.row.config',\n key: ROW_KEY,\n }, (props: { view: 'summary' | 'page' }) => <FirecrawlCard view={props.view} form={form} accounts={accounts} ctx={ctx} />))))\n}\n\nconst inputStyle = { padding: '6px 8px', border: '1px solid #8886', borderRadius: 6, background: 'transparent', color: 'inherit' }\nconst buttonStyle = { ...inputStyle, cursor: 'pointer' }\n\nfunction AccountRow({ account, configured, busy, save, remove }: {\n account: AccountMetadata; configured: boolean | undefined; busy: boolean\n save: (label: string, key: string) => Promise<void>; remove: () => void\n}): ReactNode {\n const [label, setLabel] = useState(account.label)\n const [key, setKey] = useState('')\n useEffect(() => { setLabel(account.label) }, [account.label])\n return <fieldset style={{ border: '1px solid #8884', borderRadius: 6, padding: 10, marginTop: 10 }} disabled={busy}>\n <legend>{account.label || '新账号'}</legend>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'end' }}>\n <label style={{ display: 'grid', gap: 4 }}>名称\n <input aria-label=\"账号名称\" style={inputStyle} value={label} onChange={event => setLabel(event.target.value)} />\n </label>\n <label style={{ display: 'grid', gap: 4 }}>API key · {configured === undefined ? '状态待确认' : configured ? '已配置' : '未配置'}\n <input aria-label=\"Firecrawl API key\" style={inputStyle} type=\"password\" autoComplete=\"new-password\" value={key}\n placeholder={configured ? '留空保留已有 key' : 'fc-...'} onChange={event => setKey(event.target.value)} />\n </label>\n <button style={buttonStyle} onClick={() => { void save(label, key).then(() => setKey('')).catch(() => {}) }}>保存</button>\n <button style={buttonStyle} onClick={remove}>移除账号</button>\n </div>\n </fieldset>\n}\n\nfunction FirecrawlCard({ view = 'page', form, accounts: controller, ctx }: {\n view?: 'summary' | 'page'\n form: ConfigForm<Section>\n accounts: AccountsController\n ctx: Context\n}): ReactNode {\n const snapshot = useSyncExternalStore(form.subscribe.bind(form), form.getSnapshot.bind(form))\n const [open, setOpen] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState('')\n const [configured, setConfigured] = useState<Record<string, boolean> | undefined>()\n // The API reports configured-state metadata, never credential values.\n useEffect(() => {\n let current = true\n setConfigured(undefined)\n void ctx.remote.settings.describe().then((result: { ok: true; value: SettingsDescribeValue } | { ok: false }) => {\n if (!current || !result.ok) return\n const ns = result.value.namespaces.find(item => item.ns === NAMESPACE)\n if (ns) setConfigured(Object.fromEntries(ns.secrets.filter(secret => secret.path[0] === 'apiKeys').map(secret => [secret.path[1], secret.set])))\n }).catch(() => {})\n return () => { current = false }\n }, [snapshot.revision, ctx])\n\n if (view === 'summary') return <span>搜索账号与负载均衡</span>\n if (snapshot.status !== 'ready') return null\n const section = snapshot.value ?? {}\n async function run(action: () => Promise<void>): Promise<void> {\n setBusy(true)\n setError('')\n try { await action() }\n catch (error) { setError('保存失败,未确认写入。请重新打开设置后重试。'); throw error }\n finally { setBusy(false) }\n }\n function act(action: () => Promise<void>): void { void run(action).catch(() => {}) }\n const disabled = busy || !snapshot.writable\n return <li style={{ listStyle: 'none', border: '1px solid #8884', borderRadius: 8, margin: '8px 0' }}>\n <button style={{ ...buttonStyle, border: 0, width: '100%', textAlign: 'left', padding: 14 }} aria-expanded={open} onClick={() => setOpen(!open)}>\n <strong>Firecrawl search</strong><div style={{ opacity: 0.7, fontSize: 12 }}>搜索账号与负载均衡 · {open ? '收起' : '展开'}</div>\n </button>\n {open && <div style={{ padding: '0 14px 14px' }}>\n {(section.accounts ?? []).map(account => <AccountRow key={account.id} account={account}\n configured={configured === undefined ? undefined : configured[account.id] === true} busy={disabled}\n save={(label, key) => run(() => controller.save(account.id, label, key))}\n remove={() => act(() => controller.remove(account.id))} />)}\n <button style={{ ...buttonStyle, marginTop: 10 }} disabled={disabled} onClick={() => act(() => controller.add(crypto.randomUUID()))}>添加账号</button>\n <p style={{ fontSize: 12, opacity: 0.75 }}>Key 只写入、不回显。填写后点“保存”;只改名称不会清除已有 key。</p>\n <fieldset disabled={disabled} style={{ border: 0, padding: 0, display: 'flex', gap: 12, flexWrap: 'wrap' }}>\n <label>策略 <select aria-label=\"账号策略\" style={inputStyle} value={section.strategy ?? 'round-robin'} onChange={event => act(() => form.set('strategy', event.target.value).then(ok => { if (!ok) throw new Error('refused') }))}>\n <option value=\"round-robin\">轮询</option><option value=\"least-loaded\">最少使用</option>\n </select></label>\n <label>结果数 <input aria-label=\"搜索结果数\" style={{ ...inputStyle, width: 65 }} type=\"number\" min={1} defaultValue={section.limit ?? 5}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value > 0) act(() => form.set('limit', value).then(ok => { if (!ok) throw new Error('refused') })) }} /></label>\n <label>冷却毫秒 <input aria-label=\"冷却毫秒\" style={{ ...inputStyle, width: 90 }} type=\"number\" min={0} defaultValue={section.cooldownMs ?? 60000}\n onBlur={event => { const value = Number(event.target.value); if (Number.isInteger(value) && value >= 0) act(() => form.set('cooldownMs', value).then(ok => { if (!ok) throw new Error('refused') })) }} /></label>\n </fieldset>\n {error && <p role=\"alert\">{error}</p>}\n <p style={{ fontSize: 12, opacity: 0.75 }}>这个 bundle 会把 <code>web.searchProvider</code> 设为 <code>firecrawl</code>。加好账号后,用会暴露 <code>web_search</code> 的新会话。</p>\n </div>}\n </li>\n}\n"],"mappings":"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"}
@@ -125,7 +125,7 @@ function abortable(operation, signal) {
125
125
  }
126
126
  /** Add endpoint/account recovery instructions to a provider failure. */
127
127
  function searchEndpointError(message, cause) {
128
- return new WebError(`${message}\n\nThis came from the Firecrawl search provider. Check the accounts in Settings > Plugins > Plugin configuration > Firecrawl search: each saved account needs a valid Firecrawl API key, and rate-limited accounts are cooled down automatically before they are reused. Only the user should change or choose which account serves a search.`, "WEB_PROVIDER_ERROR", cause === void 0 ? void 0 : { cause });
128
+ return new WebError(`${message}\n\nThis came from the Firecrawl search provider. Check the accounts in Plugins > dsh-web-search-firecrawl > 配置: each saved account needs a valid Firecrawl API key, and rate-limited accounts are cooled down automatically before they are reused. Only the user should change or choose which account serves a search.`, "WEB_PROVIDER_ERROR", cause === void 0 ? void 0 : { cause });
129
129
  }
130
130
  /**
131
131
  * The Firecrawl-backed search provider. Spreads work across a pool of accounts,
@@ -156,7 +156,7 @@ var FirecrawlSearchProvider = class {
156
156
  const options = this.resolveOptions();
157
157
  throwIfSearchAborted(signal);
158
158
  const slots = this.syncSlots(options);
159
- if (slots.length === 0) throw new WebError("Firecrawl search has no accounts configured; add at least one Firecrawl API key through Settings > Plugins > Plugin configuration > Firecrawl search, or set the dsh-web-search-firecrawl config", "WEB_PROVIDER_CREDENTIAL_MISSING");
159
+ if (slots.length === 0) throw new WebError("Firecrawl search has no accounts configured; add at least one Firecrawl API key through Plugins > dsh-web-search-firecrawl > 配置, or set the dsh-web-search-firecrawl config", "WEB_PROVIDER_CREDENTIAL_MISSING");
160
160
  const endpoint = `${options.baseURL}/v1/search`;
161
161
  const body = {
162
162
  query: request.query,
@@ -282,7 +282,7 @@ var FirecrawlSearchProvider = class {
282
282
  * Two halves, one package:
283
283
  * - Host half (this file): registers the {@link FirecrawlSearchProvider} on
284
284
  * `ctx.web` and a `settings` section so accounts are manageable.
285
- * - Browser half (`src/client/`): a `settings.plugin.item` card that adds,
285
+ * - Browser half (`src/client/`): a `plugins.row.config` page that adds,
286
286
  * edits, and removes accounts and tunes pool behavior.
287
287
  *
288
288
  * @module dsh-web-search-firecrawl
@@ -300,13 +300,24 @@ const Config = z.object({
300
300
  id: z.string(),
301
301
  label: z.string(),
302
302
  apiKey: z.string().role("secret")
303
- })),
304
- apiKeys: z.dict(z.string().role("secret")),
305
- baseURL: z.string(),
306
- limit: z.number().step(1).min(1),
307
- strategy: z.union(["round-robin", "least-loaded"]),
308
- cooldownMs: z.number().step(1).min(0)
303
+ })).volatile(),
304
+ apiKeys: z.dict(z.string().role("secret")).volatile(),
305
+ baseURL: z.string().default(FIRECRAWL_DEFAULT_BASE_URL).volatile(),
306
+ limit: z.number().step(1).min(1).default(5).volatile(),
307
+ strategy: z.union(["round-robin", "least-loaded"]).default("round-robin").volatile(),
308
+ cooldownMs: z.number().step(1).min(0).default(DEFAULT_COOLDOWN_MS).volatile()
309
309
  });
310
+ /** Copy one volatile snapshot into the plain section the provider and migration read. */
311
+ function readSection(config) {
312
+ return {
313
+ accounts: config.accounts.get()?.map((account) => ({ ...account })),
314
+ apiKeys: { ...config.apiKeys.get() ?? {} },
315
+ baseURL: config.baseURL.get(),
316
+ limit: config.limit.get(),
317
+ strategy: config.strategy.get(),
318
+ cooldownMs: config.cooldownMs.get()
319
+ };
320
+ }
310
321
  /** Project one resolved section into the options the provider serves next. */
311
322
  function resolveOptions(config) {
312
323
  return {
@@ -317,17 +328,17 @@ function resolveOptions(config) {
317
328
  cooldownMs: config.cooldownMs ?? DEFAULT_COOLDOWN_MS
318
329
  };
319
330
  }
320
- /** Register the Firecrawl search provider with `ctx.web` and its settings section. */
331
+ /** Register the Firecrawl search provider with `ctx.web`. */
321
332
  function apply(ctx, config) {
322
333
  console.log("[my-plugins/dsh-web-search-firecrawl] loaded");
323
- let current = () => config;
324
- ctx.inject(["settings"], async (settingsCtx) => {
325
- const scope = settingsCtx.settings.register(FIRECRAWL_SETTINGS_NAMESPACE, Config, { base: config });
326
- const migration = legacyAccountMigration(scope.get());
327
- if (migration !== void 0) await scope.update(migration);
328
- current = () => scope.get();
334
+ ctx.inject(["settings"], (settingsCtx) => {
335
+ settingsCtx.effect(() => settingsCtx.settings.configure({ auto: false }, ctx.fiber));
336
+ return (async () => {
337
+ const migration = legacyAccountMigration(readSection(config));
338
+ if (migration !== void 0) await settingsCtx.settings.update(FIRECRAWL_SETTINGS_NAMESPACE, migration);
339
+ })();
329
340
  });
330
- ctx.web.registerSearchProvider(new FirecrawlSearchProvider(() => resolveOptions(current())));
341
+ ctx.web.registerSearchProvider(new FirecrawlSearchProvider(() => resolveOptions(readSection(config))));
331
342
  }
332
343
  //#endregion
333
344
  export { Config, FIRECRAWL_DEFAULT_BASE_URL, FIRECRAWL_DEFAULT_LIMIT, FIRECRAWL_PROVIDER_ID, FIRECRAWL_SETTINGS_NAMESPACE, FirecrawlSearchProvider, apply, inject, name };
@@ -1,10 +1,11 @@
1
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
1
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
2
2
  import { type AccountSettings } from '../accounts.js';
3
3
  /** All edits use the namespace's revision fence and never read credential values. */
4
4
  export declare class AccountsController {
5
- private readonly scope;
6
- constructor(scope: SettingsScope<AccountSettings>);
5
+ private readonly form;
6
+ constructor(form: ConfigForm<AccountSettings>);
7
7
  private snapshot;
8
+ private commit;
8
9
  add(id: string): Promise<void>;
9
10
  save(id: string, label: string, key: string): Promise<void>;
10
11
  remove(id: string): Promise<void>;
@@ -7,14 +7,14 @@
7
7
  * Two halves, one package:
8
8
  * - Host half (this file): registers the {@link FirecrawlSearchProvider} on
9
9
  * `ctx.web` and a `settings` section so accounts are manageable.
10
- * - Browser half (`src/client/`): a `settings.plugin.item` card that adds,
10
+ * - Browser half (`src/client/`): a `plugins.row.config` page that adds,
11
11
  * edits, and removes accounts and tunes pool behavior.
12
12
  *
13
13
  * @module dsh-web-search-firecrawl
14
14
  */
15
- import type { Context } from '@deepseek-ai/cordis';
15
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
16
16
  import z from '@deepseek-ai/schemastery';
17
- import { type AccountSettings } from './accounts.js';
17
+ import { type AccountMetadata, type AccountSettings } from './accounts.js';
18
18
  import { type FirecrawlPoolStrategy } from './provider.js';
19
19
  export { FIRECRAWL_DEFAULT_BASE_URL, FIRECRAWL_DEFAULT_LIMIT, FIRECRAWL_PROVIDER_ID, FirecrawlSearchProvider, } from './provider.js';
20
20
  export type { FirecrawlAccount, FirecrawlPoolStrategy, FirecrawlSearchProviderOptions, } from './provider.ts';
@@ -26,7 +26,6 @@ export declare const inject: string[];
26
26
  export declare const FIRECRAWL_SETTINGS_NAMESPACE = "dsh-web-search-firecrawl";
27
27
  /** Plugin config (all optional — `apply` fills defaults). */
28
28
  export interface Config extends AccountSettings {
29
- /** Load-balanced pool of Firecrawl accounts (each is one API key). */
30
29
  /** Endpoint base; `/v1/search` is appended. Defaults to the public API. */
31
30
  baseURL?: string;
32
31
  /** Maximum search results requested per search. Defaults to 5. */
@@ -36,7 +35,20 @@ export interface Config extends AccountSettings {
36
35
  /** How long a rate-limited account stays cooled down, in ms. */
37
36
  cooldownMs?: number;
38
37
  }
39
- export declare const Config: z<Config>;
40
- /** Register the Firecrawl search provider with `ctx.web` and its settings section. */
41
- export declare function apply(ctx: Context, config: Config): void;
38
+ /**
39
+ * Live profile config. Harness 0.2.0 reads editable fields from the plugin
40
+ * Config schema; only `.volatile()` fields can change without remounting.
41
+ * Each search calls `.get()` so one operation sees one snapshot.
42
+ */
43
+ interface LiveConfig {
44
+ accounts: Volatile<AccountMetadata[] | undefined>;
45
+ apiKeys: Volatile<Record<string, string> | undefined>;
46
+ baseURL: Volatile<string | undefined>;
47
+ limit: Volatile<number | undefined>;
48
+ strategy: Volatile<FirecrawlPoolStrategy | undefined>;
49
+ cooldownMs: Volatile<number | undefined>;
50
+ }
51
+ export declare const Config: z;
52
+ /** Register the Firecrawl search provider with `ctx.web`. */
53
+ export declare function apply(ctx: Context, config: LiveConfig): void;
42
54
  //# sourceMappingURL=dsh-web-search-firecrawl.d.ts.map
package/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "name": "dsh-web-search-firecrawl",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Firecrawl search provider and multi-account settings for DeepSeek Harness",
5
- "repository": { "type": "git", "url": "https://github.com/aa2246740/dsh-firecrawl.git" },
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/aa2246740/dsh-firecrawl.git"
8
+ },
6
9
  "homepage": "https://github.com/aa2246740/dsh-firecrawl#readme",
7
- "bugs": { "url": "https://github.com/aa2246740/dsh-firecrawl/issues" },
10
+ "bugs": {
11
+ "url": "https://github.com/aa2246740/dsh-firecrawl/issues"
12
+ },
8
13
  "keywords": [
9
14
  "deepseek-harness",
10
15
  "dsh",
@@ -35,7 +40,9 @@
35
40
  },
36
41
  "client": {
37
42
  "inject": [
38
- "@deepseek-ai/dsh-client-ui-settings-plugins"
43
+ "@deepseek-ai/dsh-api-remotes",
44
+ "@deepseek-ai/dsh-client-ui-settings",
45
+ "@deepseek-ai/dsh-client-ui-plugin-manager"
39
46
  ],
40
47
  "platform": "web"
41
48
  }
@@ -53,30 +60,31 @@
53
60
  },
54
61
  "license": "MIT",
55
62
  "peerDependencies": {
56
- "@deepseek-ai/cordis": "^4.0.1",
57
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.3",
58
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.3",
59
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.3",
60
- "@deepseek-ai/dsh-web": "^0.1.5-rc.3",
61
- "@deepseek-ai/schemastery": "^3.18.1",
63
+ "@deepseek-ai/cordis": "^4.0.4",
64
+ "@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1",
65
+ "@deepseek-ai/dsh-api-remotes": ">=0.2.0-rc.1 <0.2.1",
66
+ "@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.2.0-rc.1 <0.2.1",
67
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.1 <0.2.1",
68
+ "@deepseek-ai/dsh-settings": ">=0.2.0-rc.1 <0.2.1",
69
+ "@deepseek-ai/dsh-web": ">=0.2.0-rc.1 <0.2.1",
70
+ "@deepseek-ai/schemastery": "^3.18.4",
62
71
  "react": "^18.2.0",
63
72
  "react-dom": "^18.2.0"
64
73
  },
65
74
  "devDependencies": {
66
- "@deepseek-ai/cordis": "^4.0.1",
67
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.3",
68
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.3",
69
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.3",
70
- "@deepseek-ai/dsh-scope": "^0.1.5-rc.3",
71
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.3",
72
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.3",
73
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.5-rc.3",
74
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.3",
75
- "@deepseek-ai/dsh-web": "^0.1.5-rc.3",
76
- "@deepseek-ai/schemastery": "^3.18.1",
75
+ "@deepseek-ai/cordis": "^4.0.4",
76
+ "@deepseek-ai/dsh-api-remotes": ">=0.2.0-rc.1 <0.2.1",
77
+ "@deepseek-ai/dsh-client-ui-plugin-manager": ">=0.2.0-rc.1 <0.2.1",
78
+ "@deepseek-ai/dsh-client-ui-renderer": ">=0.2.0-rc.1 <0.2.1",
79
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.2.0-rc.1 <0.2.1",
80
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.2.0-rc.1 <0.2.1",
81
+ "@deepseek-ai/dsh-settings": ">=0.2.0-rc.1 <0.2.1",
82
+ "@deepseek-ai/dsh-web": ">=0.2.0-rc.1 <0.2.1",
83
+ "@deepseek-ai/schemastery": "^3.18.4",
77
84
  "@types/react": "~18.3.1",
78
85
  "react": "^18.2.0",
79
86
  "react-dom": "^18.2.0",
87
+ "semver": "^7.8.5",
80
88
  "tsdown": "^0.22.2",
81
89
  "typescript": "^6.0.3"
82
90
  }