@vibedev-si/dsh-vibedev 0.2.3 → 0.2.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
@@ -29,7 +29,7 @@
29
29
 
30
30
  - **VibeDev 应用**:已内置本插件,不用另装。VibeDev 账号就是应用的主账号:侧边栏最底下一行和设置里的第一项都是它,DeepSeek 账号作为第二个账号显示在它上方。
31
31
 
32
- 新版本在 npm 上发布后的 24 小时内,只写包名可能装到上一个版本(pnpm 默认不装发布未满一天的版本),想马上用新版就写明版本号,例如 `@vibedev-si/dsh-vibedev@0.2.3`。
32
+ 新版本在 npm 上发布后的 24 小时内,只写包名可能装到上一个版本(pnpm 默认不装发布未满一天的版本),想马上用新版就写明版本号,例如 `@vibedev-si/dsh-vibedev@0.2.4`。
33
33
 
34
34
  ## 登录 VibeDev
35
35
 
@@ -39,6 +39,8 @@
39
39
 
40
40
  0.2.3 起修复了重启后再次要求登录的问题:启动时凭据服务未就绪或临时读取失败,会在服务就绪后恢复,不把空结果永久缓存;正常关闭应用保留登录。过去写到本地备用文件的会话在宿主凭据服务可用后迁入,宿主已有记录优先,显式退出会清理备用会话。VibeDev 应用内置的插件需要随应用升级到修复版;单独安装在 DeepSeek Harness 中的插件可直接升级。
41
41
 
42
+ 0.2.4 起在「设置 → VibeDev 账号」页尾加了「创作工具」一行:缺影视工作台或媒体预览时说明用途,都能用了就只留一个进入 VibeDev 插件中心的入口。是否显示按宿主真实的安装状态判断,已装或应用内置的组件不会再推荐安装;这里不会自动安装任何东西,装上、升级都由插件中心或插件页在你自己点击后完成。升级插件不影响已保存的剧本、分镜和作品文件,也不影响现有登录。
43
+
42
44
  ## VibeDev 的模型
43
45
 
44
46
  - 模型列表来自 VibeDev 网关,按你的账号下发,所以**未登录时不显示**,登录后立即出现,不用重启。
@@ -130,12 +132,14 @@ Sign in to VibeDev in DeepSeek Harness, next to the DeepSeek account:
130
132
 
131
133
  Everything is paid from your VibeDev balance; the DeepSeek account stays DeepSeek Harness's own, and the two do not affect each other. To create an account, register at [vibedev.jzsaas.com](https://vibedev.jzsaas.com). This plugin was renamed from `dsh-media`.
132
134
 
133
- **Install**: install `@vibedev-si/dsh-vibedev` by package name on the Plugins page (the `@vibedev-si/` scope is required), or from the [VibeDev Plugin Center](https://github.com/VibeDev-Si/dsh-ecosystem); from the command line, `dsh plugin add @vibedev-si/dsh-vibedev`, or `dsh plugin --profile desktop add @vibedev-si/dsh-vibedev` for the desktop app (quit it fully first). The VibeDev app ships with the plugin built in, and there the VibeDev account is the main account (the bottom row of the sidebar and the first Settings section), with the DeepSeek account as the second one. Within 24 hours of a release, name the version (e.g. `@vibedev-si/dsh-vibedev@0.2.3`): pnpm does not install versions younger than a day by default.
135
+ **Install**: install `@vibedev-si/dsh-vibedev` by package name on the Plugins page (the `@vibedev-si/` scope is required), or from the [VibeDev Plugin Center](https://github.com/VibeDev-Si/dsh-ecosystem); from the command line, `dsh plugin add @vibedev-si/dsh-vibedev`, or `dsh plugin --profile desktop add @vibedev-si/dsh-vibedev` for the desktop app (quit it fully first). The VibeDev app ships with the plugin built in, and there the VibeDev account is the main account (the bottom row of the sidebar and the first Settings section), with the DeepSeek account as the second one. Within 24 hours of a release, name the version (e.g. `@vibedev-si/dsh-vibedev@0.2.4`): pnpm does not install versions younger than a day by default.
134
136
 
135
137
  **Signing in**: choose "Sign in to VibeDev" at the bottom of the sidebar or in Settings → VibeDev account. The VibeDev sign-in page opens in your system browser: you type your password on VibeDev's own page, never in the plugin. Settings → VibeDev account shows the account, the balance and how many VibeDev models are listed, and lets you top up, see usage and sign out.
136
138
 
137
139
  **Sign-in persistence (0.2.3)**: an early empty or failed startup read is retried when storage becomes ready, and committed credential changes restore the account and model route. A late read cannot overwrite a newly saved sign-in. Normal app exit keeps the sign-in. A session saved in the local fallback is migrated into an available host store without overwriting an existing host record; signing out clears the fallback as well. A built-in copy is upgraded by updating VibeDev, while a separately installed plugin can be upgraded directly.
138
140
 
141
+ **Creator tools (0.2.4)**: Settings → VibeDev account ends with a Creator tools line that names the film workbench or the media viewer while one is missing, and otherwise offers the VibeDev plugin centre alone. What appears follows the Host's real inventory, so an installed or app-provided component is never offered again; nothing is installed automatically. Upgrading changes neither saved screenplays, boards and film files nor the existing sign-in.
142
+
139
143
  **Models**: the list comes from the VibeDev gateway for your account, so nothing is listed before you sign in; models appear right after, without a restart. Using a VibeDev model while signed out, or after the sign-in has ended, asks you to sign in; a used-up balance says so with the top-up link. These messages are separate from the DeepSeek account's. An expired access token is renewed and the request sent again, without repeating any output.
140
144
 
141
145
  **Web search**: the plugin registers the web search provider `vibedev-gateway`, signed in with your VibeDev account. Registering does not select it: DeepSeek Harness keeps its own search unless the profile sets `searchProvider: vibedev-gateway` on the `web` row (the VibeDev app does). Searching is free, but the gateway refuses every request, searches included, while the balance is zero.
@@ -1,4 +1,4 @@
1
- THIRD-PARTY NOTICES · @vibedev-si/dsh-vibedev 0.2.3
1
+ THIRD-PARTY NOTICES · @vibedev-si/dsh-vibedev 0.2.4
2
2
 
3
3
  This package bundles code from the projects below. Each is used under the licence that
4
4
  follows its name; the licence texts are reproduced as the projects ship them.
package/client/client.js CHANGED
@@ -246,7 +246,19 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
246
246
  sidebarMenu: "VibeDev 账号",
247
247
  sidebarIntro: "登录后可用 VibeDev 的模型和媒体生成",
248
248
  settings: "设置",
249
- accountMenu: "VibeDev 账号菜单"
249
+ accountMenu: "VibeDev 账号菜单",
250
+ toolsTitle: "创作工具",
251
+ toolsIntro: "这个账号可以和影视工作台、媒体预览与画廊一起用,也能生成片子要做的一切;它们都列在插件中心,可以一并查看和升级。",
252
+ toolsQuiet: "装上或更新这些工具不会动你的登录、余额和进行中的任务。",
253
+ toolsMissingTitle: "再装上这两件,就能从头做片子",
254
+ toolsMissingIntro: "这个账号负责生成;下面这两件负责把生成的东西编排成片、看清楚。",
255
+ toolsMissingKeep: "补齐之后,登录、余额和进行中的任务都不受影响;升级插件也不会动工作区里的文件。",
256
+ "tools.film.missing": "影视工作台:剧本、分镜画布和导演台,Agent 还带 30 个影视工具。",
257
+ "tools.viewer.missing": "媒体预览与画廊:在右侧栏直接看视频、音频和 HTML,或按文件夹浏览媒体。",
258
+ toolsOpen: "在插件中心查看",
259
+ toolsManual: "也可以打开「插件 → 添加插件」,填入这个包名:",
260
+ toolsCopy: "复制包名",
261
+ toolsCopied: "已复制"
250
262
  };
251
263
  const en = {
252
264
  spending: "Spending",
@@ -309,7 +321,19 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
309
321
  sidebarMenu: "VibeDev account",
310
322
  sidebarIntro: "Sign in to use the VibeDev models and media generation",
311
323
  settings: "Settings",
312
- accountMenu: "VibeDev account menu"
324
+ accountMenu: "VibeDev account menu",
325
+ toolsTitle: "Tools for creators",
326
+ toolsIntro: "This account works with the film workbench and with Media Viewer, and it generates everything a film needs; all of them are listed in the plugin centre, where they can be reviewed and updated together.",
327
+ toolsQuiet: "Installing or updating these tools leaves your sign-in, balance and running tasks alone.",
328
+ toolsMissingTitle: "Two more tools turn this into film making end to end",
329
+ toolsMissingIntro: "This account generates; the two below arrange what you generated into a film, and let you watch it closely.",
330
+ toolsMissingKeep: "Adding them leaves your sign-in, balance and running tasks alone, and updating a plugin never touches the files in your workspace.",
331
+ "tools.film.missing": "Film workbench: screenplay, storyboard canvas and director desk, with 30 film tools for the agent.",
332
+ "tools.viewer.missing": "Media Viewer: play video, audio and HTML right in the sidebar, or browse a folder as a gallery.",
333
+ toolsOpen: "Open the plugin centre",
334
+ toolsManual: "You can also open Plugins → Add plugin and paste this package name:",
335
+ toolsCopy: "Copy package name",
336
+ toolsCopied: "Copied"
313
337
  };
314
338
  /**
315
339
  * Fill `{name}` placeholders.
@@ -324,7 +348,9 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
324
348
  //#region src/client/AccountSection.tsx
325
349
  /**
326
350
  * Settings → VibeDev 账号: sign in to VibeDev, see who is signed in, the
327
- * balance and how many VibeDev models the pickers list, top up, sign out.
351
+ * balance and how many VibeDev models the pickers list, top up, sign out; and,
352
+ * under all of that, the creator tools that work with this account — the film
353
+ * workbench and the media viewer — with the plugin centre as the way to them.
328
354
  * The DeepSeek account keeps its own section; this one never touches it.
329
355
  */
330
356
  const styles$1 = {
@@ -396,8 +422,102 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
396
422
  fontSize: 12,
397
423
  lineHeight: 1.6,
398
424
  color: "var(--dsw-alias-state-error-primary, #d4380d)"
425
+ },
426
+ spec: {
427
+ padding: "2px 6px",
428
+ borderRadius: 6,
429
+ background: "var(--dsw-alias-bg-l1, transparent)",
430
+ color: "var(--dsw-alias-label-primary)",
431
+ fontSize: 12,
432
+ userSelect: "text"
399
433
  }
400
434
  };
435
+ /**
436
+ * Draw the tools block from a settled read.
437
+ * @param props - the translated hint, the manual fallback and the two clicks.
438
+ * @returns the tools block.
439
+ */
440
+ function creatorToolsView({ t, hint, manual, copied, onOpen, onCopy }) {
441
+ const missing = hint.kind === "missing";
442
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
444
+ style: styles$1.title,
445
+ children: t(missing ? "toolsMissingTitle" : "toolsTitle")
446
+ }),
447
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
448
+ style: styles$1.intro,
449
+ children: missing ? t("toolsMissingIntro") : t("toolsIntro")
450
+ }),
451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
452
+ style: styles$1.card,
453
+ children: [
454
+ hint.missing.map((want) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
455
+ style: styles$1.line,
456
+ children: want.line
457
+ }, want.package)),
458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
459
+ style: styles$1.quiet,
460
+ children: missing ? t("toolsMissingKeep") : t("toolsQuiet")
461
+ }),
462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
463
+ style: styles$1.actions,
464
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
465
+ variant: missing ? "primary" : "outline",
466
+ size: "sm",
467
+ onClick: onOpen,
468
+ children: t("toolsOpen")
469
+ })
470
+ }),
471
+ manual === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
473
+ style: styles$1.quiet,
474
+ children: t("toolsManual")
475
+ }),
476
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
477
+ style: styles$1.spec,
478
+ children: manual.spec
479
+ }),
480
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
481
+ style: styles$1.actions,
482
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
483
+ variant: "ghost",
484
+ size: "sm",
485
+ onClick: () => {
486
+ onCopy(manual.spec);
487
+ },
488
+ children: t(copied ? "toolsCopied" : "toolsCopy")
489
+ })
490
+ })
491
+ ] })
492
+ ]
493
+ })
494
+ ] });
495
+ }
496
+ function CreatorTools({ suite, t }) {
497
+ const { read, hint } = (0, react.useSyncExternalStore)(suite.subscribe, suite.getSnapshot, suite.getSnapshot);
498
+ /** Set once a click found nothing to open: the spec to copy is then on screen. */
499
+ const [manual, setManual] = (0, react.useState)(void 0);
500
+ const [copied, setCopied] = (0, react.useState)(false);
501
+ if (!read) return null;
502
+ const open = () => {
503
+ const target = suite.open();
504
+ setCopied(false);
505
+ setManual(target.kind === "manual" ? { spec: target.spec } : void 0);
506
+ };
507
+ const copy = (spec) => {
508
+ suite.copy(spec).then((written) => {
509
+ setCopied(written);
510
+ });
511
+ };
512
+ return creatorToolsView({
513
+ t,
514
+ hint,
515
+ manual,
516
+ copied,
517
+ onOpen: open,
518
+ onCopy: copy
519
+ });
520
+ }
401
521
  /** The status dot for a state. */
402
522
  function statusDot(state) {
403
523
  const view = state.view;
@@ -416,10 +536,10 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
416
536
  }
417
537
  /**
418
538
  * The section.
419
- * @param props - the dictionary, the shared account store and the route's display name.
539
+ * @param props - the dictionary, the shared account store, the route's display name and the entry's suite view.
420
540
  * @returns the section content.
421
541
  */
422
- function AccountSection({ t, store, routeName }) {
542
+ function AccountSection({ t, store, routeName, suite }) {
423
543
  const state = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
424
544
  (0, react.useEffect)(() => {
425
545
  store.refresh(true);
@@ -564,6 +684,10 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
564
684
  children: error
565
685
  })
566
686
  ]
687
+ }),
688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreatorTools, {
689
+ suite,
690
+ t
567
691
  })
568
692
  ]
569
693
  });
@@ -1204,6 +1328,302 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
1204
1328
  });
1205
1329
  }
1206
1330
  //#endregion
1331
+ //#region src/client/suite.ts
1332
+ /**
1333
+ * The complementary VibeDev components this plugin's own work leans on, and the
1334
+ * way to the plugin centre that lists the rest.
1335
+ *
1336
+ * What the Host actually has comes from its plugin inventory (a bundle the
1337
+ * profile installed, or one the application provides itself); the centre's own
1338
+ * page is opened where it is registered, and otherwise the Host's install
1339
+ * entry, and otherwise the spec a person can paste into the Plugins page. Every
1340
+ * service is read optionally through a property check, so a Host without a
1341
+ * plugin manager, without a panel layout, or without the plugin navigation
1342
+ * contract keeps this plugin working and simply shows no entry, or the manual
1343
+ * one. Nothing here installs anything: the centre and the Plugins page own
1344
+ * every install, approval and progress.
1345
+ *
1346
+ * Package names are the stable ones the centre's catalogue and the plugin
1347
+ * manager know; no version is named here, because the catalogue and the install
1348
+ * dialog are what discover the current one.
1349
+ */
1350
+ /** The VibeDev components by package name; the centre is the way to all of them. */
1351
+ const SUITE = {
1352
+ account: "@vibedev-si/dsh-vibedev",
1353
+ center: "@vibedev-si/dsh-ecosystem",
1354
+ film: "dsh-film",
1355
+ viewer: "@vibedev-si/dsh-media-viewer"
1356
+ };
1357
+ /** The package a person can add on the Plugins page when nothing can open the centre. */
1358
+ const CENTER_SPEC = SUITE.center;
1359
+ /** The main panel the VibeDev plugin centre registers its page under. */
1360
+ const CENTER_PANEL = "vibedev-center";
1361
+ /** A property value read as a record, without trusting its declared type. */
1362
+ function asRecord(value) {
1363
+ return typeof value === "object" && value !== null ? value : void 0;
1364
+ }
1365
+ /**
1366
+ * One service of the client context, read optionally.
1367
+ * `ctx.get(name)` answers for an absent or inactive service without the strict property read;
1368
+ * a Host, or a service, whose own getter throws answers undefined here rather than failing the plugin.
1369
+ * @param ctx - the client context.
1370
+ * @param name - the service name.
1371
+ * @returns the service, or undefined when this Host does not serve it.
1372
+ */
1373
+ function service(ctx, name) {
1374
+ const record = asRecord(ctx);
1375
+ if (record === void 0) return void 0;
1376
+ const get = record.get;
1377
+ if (typeof get === "function") try {
1378
+ const value = get.call(record, name);
1379
+ if (value !== void 0) return value;
1380
+ } catch (_error) {}
1381
+ try {
1382
+ return record[name];
1383
+ } catch (_error) {
1384
+ return;
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Whether the Host has a bundle: the profile installed it, or the application provides it itself.
1389
+ * @param bundle - one inventory row.
1390
+ * @returns whether the component is usable without installing anything.
1391
+ */
1392
+ function bundlePresent(bundle) {
1393
+ return bundle.installed === true || bundle.removable === false && !bundle.source;
1394
+ }
1395
+ /**
1396
+ * The present package names of one inventory answer's rows.
1397
+ * @param answer - the Host's answer to `listBundles`.
1398
+ * @returns the names, empty when the answer carries no list.
1399
+ */
1400
+ function presentFrom(answer) {
1401
+ const rows = asRecord(answer)?.value;
1402
+ if (!Array.isArray(rows)) return /* @__PURE__ */ new Set();
1403
+ const names = /* @__PURE__ */ new Set();
1404
+ for (const row of rows) {
1405
+ const bundle = asRecord(row);
1406
+ if (bundle === void 0 || !bundlePresent(bundle)) continue;
1407
+ if (typeof bundle.name === "string" && bundle.name !== "") names.add(bundle.name);
1408
+ }
1409
+ return names;
1410
+ }
1411
+ /**
1412
+ * What one inventory answer means. Only an accepted envelope carrying a list is an inventory:
1413
+ * a refused answer (`ok: false` with an error) and a value that is not a list are both unread,
1414
+ * never an empty Host, so nothing is ever reported missing on the strength of a failure.
1415
+ * A Host that answers with the list itself is accepted as well.
1416
+ * @param answer - the Host's answer to `listBundles`.
1417
+ * @returns the read, with `ok` false when nothing could be learned.
1418
+ */
1419
+ function presentFromAnswer(answer) {
1420
+ const record = asRecord(answer);
1421
+ if (record === void 0) return {
1422
+ ok: false,
1423
+ present: /* @__PURE__ */ new Set()
1424
+ };
1425
+ if (record.ok === true && Array.isArray(record.value)) return {
1426
+ ok: true,
1427
+ present: presentFrom(record)
1428
+ };
1429
+ if (Array.isArray(answer)) return {
1430
+ ok: true,
1431
+ present: presentFrom({ value: answer })
1432
+ };
1433
+ return {
1434
+ ok: false,
1435
+ present: /* @__PURE__ */ new Set()
1436
+ };
1437
+ }
1438
+ /**
1439
+ * Read the Host's plugin inventory once.
1440
+ * @param ctx - the client context.
1441
+ * @returns what is present, or `ok: false` when no inventory could be read.
1442
+ */
1443
+ async function readPresence(ctx) {
1444
+ const manager = asRecord(service(service(ctx, "remote"), "pluginManager"));
1445
+ const listBundles = manager?.listBundles;
1446
+ if (typeof listBundles !== "function") return {
1447
+ ok: false,
1448
+ present: /* @__PURE__ */ new Set()
1449
+ };
1450
+ try {
1451
+ return presentFromAnswer(await listBundles.call(manager));
1452
+ } catch (_error) {
1453
+ return {
1454
+ ok: false,
1455
+ present: /* @__PURE__ */ new Set()
1456
+ };
1457
+ }
1458
+ }
1459
+ /**
1460
+ * Hear about installs, removals and activations.
1461
+ * @param ctx - the client context.
1462
+ * @param listener - called after every change.
1463
+ * @returns the unsubscribe, a no-op on a Host without the event bus.
1464
+ */
1465
+ function observePresence(ctx, listener) {
1466
+ const remote = asRecord(service(ctx, "remote"));
1467
+ const on = remote?.$on;
1468
+ if (typeof on !== "function") return () => {};
1469
+ try {
1470
+ const off = on.call(remote, "plugin-manager/changed", listener);
1471
+ if (typeof off !== "function") return () => {};
1472
+ const stop = off;
1473
+ return () => {
1474
+ stop();
1475
+ };
1476
+ } catch (_error) {
1477
+ return () => {};
1478
+ }
1479
+ }
1480
+ /**
1481
+ * The centre's main panel, where this Host registered one.
1482
+ * A slots entry keeps its identity in `options` (`options.key` is the panel id the layout
1483
+ * selects); older shapes kept `key`/`id` at the top level, which is still read as a fallback.
1484
+ * @param ctx - the client context.
1485
+ * @returns the panel id the layout selects, or undefined when there is no centre page.
1486
+ */
1487
+ function centerPanel(ctx) {
1488
+ const slots = asRecord(service(ctx, "slots"));
1489
+ const entries = slots?.entries;
1490
+ if (typeof entries !== "function") return void 0;
1491
+ const listed = entries.call(slots, "main");
1492
+ if (!Array.isArray(listed)) return void 0;
1493
+ for (const entry of listed) {
1494
+ const record = asRecord(entry);
1495
+ const options = asRecord(record?.options);
1496
+ if ((options?.key ?? record?.key ?? options?.id ?? record?.id) === CENTER_PANEL) return CENTER_PANEL;
1497
+ }
1498
+ }
1499
+ /**
1500
+ * Send the person to the VibeDev plugin centre, or to the Host's own way of installing it.
1501
+ * A panel that is gone, a layout that refuses, and a missing navigation contract each fall
1502
+ * through to the next, and the last answer is the spec to paste.
1503
+ * @param ctx - the client context.
1504
+ * @returns what was opened.
1505
+ */
1506
+ function openCenter(ctx) {
1507
+ const panel = centerPanel(ctx);
1508
+ const selectPanel = asRecord(service(ctx, "layout"))?.selectPanel;
1509
+ if (panel !== void 0 && typeof selectPanel === "function") try {
1510
+ selectPanel.call(service(ctx, "layout"), panel);
1511
+ return { kind: "center" };
1512
+ } catch (_error) {}
1513
+ const navigation = asRecord(service(ctx, "pluginNavigation"));
1514
+ const openBundle = navigation?.openBundle;
1515
+ if (typeof openBundle === "function") try {
1516
+ openBundle.call(navigation, SUITE.center);
1517
+ return { kind: "plugins" };
1518
+ } catch (_error) {}
1519
+ const openInstall = navigation?.openInstall;
1520
+ if (typeof openInstall === "function") try {
1521
+ openInstall.call(navigation, { spec: CENTER_SPEC });
1522
+ return { kind: "plugins" };
1523
+ } catch (_error) {}
1524
+ return {
1525
+ kind: "manual",
1526
+ spec: CENTER_SPEC
1527
+ };
1528
+ }
1529
+ /**
1530
+ * Which of `wanted` this Host does not have yet.
1531
+ * @param present - what the Host has.
1532
+ * @param wanted - the components, in the order the surface lists them.
1533
+ * @returns the absent ones.
1534
+ */
1535
+ function missingFrom(present, wanted) {
1536
+ return wanted.filter((want) => !present.has(want.package));
1537
+ }
1538
+ /**
1539
+ * What a surface says: the reasons something is absent, or the one way to the rest.
1540
+ * An inventory that could not be read claims nothing and offers the centre.
1541
+ * @param read - the inventory read.
1542
+ * @param wanted - the components this surface leans on.
1543
+ * @param translate - the entry's bound dictionary, so a card renders lines rather than keys.
1544
+ * @returns the hint the card renders.
1545
+ */
1546
+ function suiteHint(read, wanted, translate) {
1547
+ if (!read.ok) return {
1548
+ kind: "tools",
1549
+ missing: []
1550
+ };
1551
+ const missing = missingFrom(read.present, wanted);
1552
+ if (missing.length === 0) return {
1553
+ kind: "tools",
1554
+ missing: []
1555
+ };
1556
+ return {
1557
+ kind: "missing",
1558
+ missing: missing.map((want) => ({
1559
+ package: want.package,
1560
+ line: translate(want.key)
1561
+ }))
1562
+ };
1563
+ }
1564
+ /** Write text through the browser's clipboard, where the page has one. */
1565
+ async function writeClipboard(text) {
1566
+ const clipboard = asRecord(asRecord(globalThis)?.navigator)?.clipboard;
1567
+ const writeText = asRecord(clipboard)?.writeText;
1568
+ if (typeof writeText !== "function") return false;
1569
+ try {
1570
+ await writeText.call(clipboard, text);
1571
+ return true;
1572
+ } catch (_error) {
1573
+ return false;
1574
+ }
1575
+ }
1576
+ /**
1577
+ * The entry's read of what this Host has, shared by every card it hands to the workbench.
1578
+ * @param ctx - the client context.
1579
+ * @param wanted - the components this plugin's own work leans on.
1580
+ * @param translate - the entry's bound dictionary.
1581
+ * @returns the store: subscribe and render through {@link SuiteView}, and start it inside an effect.
1582
+ */
1583
+ function createSuiteStore(ctx, wanted, translate) {
1584
+ let snapshot = {
1585
+ read: false,
1586
+ found: {
1587
+ ok: false,
1588
+ present: /* @__PURE__ */ new Set()
1589
+ },
1590
+ hint: {
1591
+ kind: "tools",
1592
+ missing: []
1593
+ }
1594
+ };
1595
+ const listeners = /* @__PURE__ */ new Set();
1596
+ const publish = (next) => {
1597
+ snapshot = next;
1598
+ for (const listener of [...listeners]) listener();
1599
+ };
1600
+ const read = async () => {
1601
+ const found = await readPresence(ctx);
1602
+ publish({
1603
+ read: true,
1604
+ found,
1605
+ hint: suiteHint(found, wanted, translate)
1606
+ });
1607
+ };
1608
+ return {
1609
+ subscribe(listener) {
1610
+ listeners.add(listener);
1611
+ return () => {
1612
+ listeners.delete(listener);
1613
+ };
1614
+ },
1615
+ getSnapshot: () => snapshot,
1616
+ open: () => openCenter(ctx),
1617
+ copy: writeClipboard,
1618
+ start: () => {
1619
+ read();
1620
+ return observePresence(ctx, () => {
1621
+ read();
1622
+ });
1623
+ }
1624
+ };
1625
+ }
1626
+ //#endregion
1207
1627
  //#region src/client/index.ts
1208
1628
  /**
1209
1629
  * dsh-vibedev browser half:
@@ -1238,6 +1658,18 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
1238
1658
  const PRIMARY_SECTION_ORDER = -20;
1239
1659
  /** Below the default the DeepSeek account launcher registers with, so this row is the one the foot shows. */
1240
1660
  const LAUNCHER_PRIORITY = -1;
1661
+ /**
1662
+ * The creator tools that work with this account, offered under the account's own settings.
1663
+ * The account itself is never listed: it is what the person is already looking at (and the
1664
+ * VibeDev app ships with it built in), so only what is missing from the Host is named.
1665
+ */
1666
+ const CREATOR_WANTED = [{
1667
+ package: SUITE.film,
1668
+ key: "tools.film.missing"
1669
+ }, {
1670
+ package: SUITE.viewer,
1671
+ key: "tools.viewer.missing"
1672
+ }];
1241
1673
  const disposer = (value) => typeof value === "function" ? value : () => {};
1242
1674
  const name = "dsh-vibedev";
1243
1675
  const inject = ["slots", "locale"];
@@ -1270,6 +1702,8 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
1270
1702
  const t = ctx.locale.bind(LOCALE_NAMESPACE);
1271
1703
  const account = new AccountStore(windowOptions());
1272
1704
  ctx.effect(() => account.start(), "dsh-vibedev: account reads");
1705
+ const creatorTools = createSuiteStore(ctx, CREATOR_WANTED, t);
1706
+ ctx.effect(() => creatorTools.start(), "dsh-vibedev: creator tools presence");
1273
1707
  const section = (order) => disposer(ctx.slots.inject("settings.section", () => ctx.slots.register({
1274
1708
  name: "settings.section",
1275
1709
  id: "vibedev-account",
@@ -1279,7 +1713,8 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
1279
1713
  }, () => (0, react.createElement)(AccountSection, {
1280
1714
  t,
1281
1715
  store: account,
1282
- routeName: ROUTE_NAME
1716
+ routeName: ROUTE_NAME,
1717
+ suite: creatorTools
1283
1718
  }))));
1284
1719
  const launcher = () => disposer(ctx.slots.inject("settings.launcher", () => ctx.slots.register({
1285
1720
  name: "settings.launcher",
@@ -1360,6 +1795,7 @@ window.__ModuleLoader__.load({ id: "@vibedev-si/dsh-vibedev", factory: (require)
1360
1795
  });
1361
1796
  }
1362
1797
  //#endregion
1798
+ exports.CREATOR_WANTED = CREATOR_WANTED;
1363
1799
  exports.apply = apply;
1364
1800
  exports.inject = inject;
1365
1801
  exports.name = name;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibedev-si/dsh-vibedev",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "VibeDev 账号与模型:在 DeepSeek Harness 里登录 VibeDev,与 DeepSeek 账号并存,使用 VibeDev 网关的模型(Claude、GPT 等),并生成图片、视频、音乐和播客 · VibeDev account and models for DeepSeek Harness: sign in to VibeDev next to the DeepSeek account and use the VibeDev gateway's models (Claude, GPT and more), plus image, video, music and podcast generation",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",