@vibedev-si/dsh-vibedev 0.2.1 → 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 +16 -2
- package/THIRD-PARTY-NOTICES.txt +1 -1
- package/client/client.js +442 -6
- package/lib/auth/credentials.d.ts +1 -0
- package/lib/auth/login.d.ts +6 -1
- package/lib/index.js +104 -17
- package/package.json +1 -1
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.
|
|
32
|
+
新版本在 npm 上发布后的 24 小时内,只写包名可能装到上一个版本(pnpm 默认不装发布未满一天的版本),想马上用新版就写明版本号,例如 `@vibedev-si/dsh-vibedev@0.2.4`。
|
|
33
33
|
|
|
34
34
|
## 登录 VibeDev
|
|
35
35
|
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
|
|
38
38
|
「设置 → VibeDev 账号」里能看到当前账号、余额和 VibeDev 模型的数量,可以充值、查看用量明细、退出登录。登录会自动续期;退出后 VibeDev 的模型会从选择器里消失,DeepSeek 账号不受影响。
|
|
39
39
|
|
|
40
|
+
0.2.3 起修复了重启后再次要求登录的问题:启动时凭据服务未就绪或临时读取失败,会在服务就绪后恢复,不把空结果永久缓存;正常关闭应用保留登录。过去写到本地备用文件的会话在宿主凭据服务可用后迁入,宿主已有记录优先,显式退出会清理备用会话。VibeDev 应用内置的插件需要随应用升级到修复版;单独安装在 DeepSeek Harness 中的插件可直接升级。
|
|
41
|
+
|
|
42
|
+
0.2.4 起在「设置 → VibeDev 账号」页尾加了「创作工具」一行:缺影视工作台或媒体预览时说明用途,都能用了就只留一个进入 VibeDev 插件中心的入口。是否显示按宿主真实的安装状态判断,已装或应用内置的组件不会再推荐安装;这里不会自动安装任何东西,装上、升级都由插件中心或插件页在你自己点击后完成。升级插件不影响已保存的剧本、分镜和作品文件,也不影响现有登录。
|
|
43
|
+
|
|
40
44
|
## VibeDev 的模型
|
|
41
45
|
|
|
42
46
|
- 模型列表来自 VibeDev 网关,按你的账号下发,所以**未登录时不显示**,登录后立即出现,不用重启。
|
|
@@ -128,10 +132,14 @@ Sign in to VibeDev in DeepSeek Harness, next to the DeepSeek account:
|
|
|
128
132
|
|
|
129
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`.
|
|
130
134
|
|
|
131
|
-
**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.
|
|
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.
|
|
132
136
|
|
|
133
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.
|
|
134
138
|
|
|
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.
|
|
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
|
+
|
|
135
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.
|
|
136
144
|
|
|
137
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.
|
|
@@ -150,6 +158,12 @@ Everything is paid from your VibeDev balance; the DeepSeek account stays DeepSee
|
|
|
150
158
|
|
|
151
159
|
**Third-party code**: the model route in `src/llm/` is ported from the VibeDev app; `context.ts`, `replay.ts` and `stream.ts` are copied from DeepSeek Harness's `@deepseek-ai/dsh-llm-pi-ai` (MIT). The licences of the bundled pi-ai, Anthropic SDK, OpenAI SDK and their dependencies are in `THIRD-PARTY-NOTICES.txt`.
|
|
152
160
|
|
|
161
|
+
## Maintainer releases
|
|
162
|
+
|
|
163
|
+
Build, typecheck and test locally, pack the tested archive, tag the matching source version, and publish a GitHub Release with `vibedev-si-dsh-vibedev-<version>.tgz` attached. `.github/workflows/publish.yml` publishes that exact archive with npm OIDC; it does not rebuild the package. The job checks the source/tag/archive identities and Release asset digest, refuses an existing version with different bytes, and skips an identical already-published version. It verifies the complete npm tarball after publication. For a manual retry of an unpublished version, run the workflow on the version's tag ref.
|
|
164
|
+
|
|
165
|
+
Configure npm Trusted Publisher with GitHub owner `VibeDev-Si`, repository `dsh-vibedev`, workflow `publish.yml`, no environment, and **Allow npm publish**. Enable **Allow npm dist-tag** as well to use the manual `dist-tag` operation for `latest` or `next`. No npm token is stored in this repository. Trusted publishing does not authorize `npm deprecate`; that maintenance operation still uses interactive authentication.
|
|
166
|
+
|
|
153
167
|
## License
|
|
154
168
|
|
|
155
169
|
MIT
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
THIRD-PARTY NOTICES · @vibedev-si/dsh-vibedev 0.2.
|
|
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
|
|
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;
|
|
@@ -36,6 +36,7 @@ export declare class CredentialChain {
|
|
|
36
36
|
*/
|
|
37
37
|
reject(credential: GatewayCredential): Promise<void>;
|
|
38
38
|
}
|
|
39
|
+
export declare const GRANT_KEY: import("@deepseek-ai/dsh-credentials").CredentialKey;
|
|
39
40
|
/**
|
|
40
41
|
* Grant storage: the host's credential store when one is loaded (a grant
|
|
41
42
|
* record the store keeps verbatim), else a private file in the state
|
package/lib/auth/login.d.ts
CHANGED
|
@@ -88,7 +88,9 @@ export declare function openInBrowser(url: string): Promise<boolean>;
|
|
|
88
88
|
export declare class PluginLogin {
|
|
89
89
|
private readonly options;
|
|
90
90
|
private grant;
|
|
91
|
-
private
|
|
91
|
+
private reading;
|
|
92
|
+
private revision;
|
|
93
|
+
private readFailureReported;
|
|
92
94
|
private refreshing;
|
|
93
95
|
private pending;
|
|
94
96
|
private readonly listeners;
|
|
@@ -98,7 +100,10 @@ export declare class PluginLogin {
|
|
|
98
100
|
* @param options - gateway origin, storage and test seams.
|
|
99
101
|
*/
|
|
100
102
|
constructor(options: PluginLoginOptions);
|
|
103
|
+
private readStored;
|
|
101
104
|
private load;
|
|
105
|
+
/** Re-read after the credential provider becomes ready or its record changes, without starting a browser sign-in. */
|
|
106
|
+
restore(): Promise<void>;
|
|
102
107
|
/** Take a grant as current and tell listeners when it changed. */
|
|
103
108
|
private adopt;
|
|
104
109
|
private save;
|
package/lib/index.js
CHANGED
|
@@ -569,27 +569,77 @@ function grantStorage(stateDir, credentials) {
|
|
|
569
569
|
const deviceFile = join(stateDir, "device.json");
|
|
570
570
|
let device;
|
|
571
571
|
const readFileGrant = async () => {
|
|
572
|
-
|
|
572
|
+
let text;
|
|
573
|
+
try {
|
|
574
|
+
text = await readFile(sessionFile, "utf8");
|
|
575
|
+
} catch (error) {
|
|
576
|
+
if (error.code === "ENOENT") return void 0;
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
579
|
+
const parsed = JSON.parse(text);
|
|
573
580
|
return isGrant(parsed) ? parsed : void 0;
|
|
574
581
|
};
|
|
575
582
|
const writeFileGrant = (grant) => writeFileAtomic(sessionFile, new TextEncoder().encode(grant === void 0 ? "null" : JSON.stringify(grant)), { mode: 384 });
|
|
583
|
+
const clearFileGrant = async () => {
|
|
584
|
+
try {
|
|
585
|
+
await stat(sessionFile);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (error.code === "ENOENT") return;
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
await writeFileGrant(void 0);
|
|
591
|
+
};
|
|
592
|
+
const clearFileGrantQuietly = async () => {
|
|
593
|
+
try {
|
|
594
|
+
await clearFileGrant();
|
|
595
|
+
} catch {}
|
|
596
|
+
};
|
|
597
|
+
const readHostGrant = async (store) => {
|
|
598
|
+
const stored = await store.readRecord(GRANT_KEY);
|
|
599
|
+
if (stored !== void 0) {
|
|
600
|
+
const grant = stored.kind === "grant" && isGrant(stored.payload) ? stored.payload : void 0;
|
|
601
|
+
if (grant !== void 0) await clearFileGrantQuietly();
|
|
602
|
+
return grant;
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
if ((await stat(sessionFile)).size <= 4) return void 0;
|
|
606
|
+
} catch (error) {
|
|
607
|
+
if (error.code === "ENOENT") return void 0;
|
|
608
|
+
throw error;
|
|
609
|
+
}
|
|
610
|
+
let adopted;
|
|
611
|
+
await store.modifyRecord(GRANT_KEY, async (current) => {
|
|
612
|
+
if (current !== void 0) {
|
|
613
|
+
adopted = current.kind === "grant" && isGrant(current.payload) ? current.payload : void 0;
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
adopted = await readFileGrant();
|
|
617
|
+
return adopted === void 0 ? void 0 : {
|
|
618
|
+
kind: "grant",
|
|
619
|
+
payload: adopted
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
if (adopted !== void 0) await clearFileGrantQuietly();
|
|
623
|
+
return adopted;
|
|
624
|
+
};
|
|
576
625
|
return {
|
|
577
626
|
async read() {
|
|
578
627
|
const store = credentials();
|
|
579
|
-
|
|
580
|
-
const stored = await store.readRecord(GRANT_KEY);
|
|
581
|
-
return stored?.kind === "grant" && isGrant(stored.payload) ? stored.payload : void 0;
|
|
582
|
-
}
|
|
583
|
-
return readFileGrant();
|
|
628
|
+
return store === void 0 ? readFileGrant() : readHostGrant(store);
|
|
584
629
|
},
|
|
585
630
|
async write(grant) {
|
|
586
631
|
const store = credentials();
|
|
587
632
|
if (store !== void 0) {
|
|
588
|
-
if (grant === void 0)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
633
|
+
if (grant === void 0) {
|
|
634
|
+
await clearFileGrant();
|
|
635
|
+
await store.deleteRecord(GRANT_KEY);
|
|
636
|
+
} else {
|
|
637
|
+
await store.modifyRecord(GRANT_KEY, () => Promise.resolve({
|
|
638
|
+
kind: "grant",
|
|
639
|
+
payload: grant
|
|
640
|
+
}));
|
|
641
|
+
await clearFileGrantQuietly();
|
|
642
|
+
}
|
|
593
643
|
return;
|
|
594
644
|
}
|
|
595
645
|
await writeFileGrant(grant);
|
|
@@ -597,6 +647,7 @@ function grantStorage(stateDir, credentials) {
|
|
|
597
647
|
async update(change) {
|
|
598
648
|
const store = credentials();
|
|
599
649
|
if (store !== void 0) {
|
|
650
|
+
await readHostGrant(store);
|
|
600
651
|
let remove = false;
|
|
601
652
|
await store.modifyRecord(GRANT_KEY, async (current) => {
|
|
602
653
|
const result = await change(current?.kind === "grant" && isGrant(current.payload) ? current.payload : void 0);
|
|
@@ -606,7 +657,10 @@ function grantStorage(stateDir, credentials) {
|
|
|
606
657
|
};
|
|
607
658
|
remove = result.kind === "delete";
|
|
608
659
|
});
|
|
609
|
-
if (remove)
|
|
660
|
+
if (remove) {
|
|
661
|
+
await clearFileGrant();
|
|
662
|
+
await store.deleteRecord(GRANT_KEY);
|
|
663
|
+
}
|
|
610
664
|
return;
|
|
611
665
|
}
|
|
612
666
|
const result = await change(await readFileGrant());
|
|
@@ -709,7 +763,9 @@ const PAGE = (title, body) => `<!doctype html><html><head><meta charset="utf-8">
|
|
|
709
763
|
var PluginLogin = class {
|
|
710
764
|
options;
|
|
711
765
|
grant;
|
|
712
|
-
|
|
766
|
+
reading;
|
|
767
|
+
revision = 0;
|
|
768
|
+
readFailureReported = false;
|
|
713
769
|
refreshing;
|
|
714
770
|
pending;
|
|
715
771
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -723,14 +779,36 @@ var PluginLogin = class {
|
|
|
723
779
|
this.now = options.now ?? Date.now;
|
|
724
780
|
this.origin = options.origin.trim().replace(/\/+$/, "");
|
|
725
781
|
}
|
|
782
|
+
readStored(notify) {
|
|
783
|
+
if (this.reading !== void 0) return this.reading;
|
|
784
|
+
const revision = this.revision;
|
|
785
|
+
this.reading = this.options.storage.read().then((grant) => {
|
|
786
|
+
this.readFailureReported = false;
|
|
787
|
+
if (this.revision !== revision) return;
|
|
788
|
+
if (notify) this.adopt(grant);
|
|
789
|
+
else {
|
|
790
|
+
this.grant = grant;
|
|
791
|
+
this.revision++;
|
|
792
|
+
}
|
|
793
|
+
}, () => {
|
|
794
|
+
if (!this.readFailureReported) this.options.log?.("dsh-vibedev: stored sign-in is temporarily unreadable; it will be checked again");
|
|
795
|
+
this.readFailureReported = true;
|
|
796
|
+
}).finally(() => {
|
|
797
|
+
this.reading = void 0;
|
|
798
|
+
});
|
|
799
|
+
return this.reading;
|
|
800
|
+
}
|
|
726
801
|
load() {
|
|
727
|
-
this.
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
802
|
+
return this.grant === void 0 ? this.readStored(false) : Promise.resolve();
|
|
803
|
+
}
|
|
804
|
+
/** Re-read after the credential provider becomes ready or its record changes, without starting a browser sign-in. */
|
|
805
|
+
async restore() {
|
|
806
|
+
await this.reading;
|
|
807
|
+
await this.readStored(true);
|
|
731
808
|
}
|
|
732
809
|
/** Take a grant as current and tell listeners when it changed. */
|
|
733
810
|
adopt(grant) {
|
|
811
|
+
this.revision++;
|
|
734
812
|
const changed = this.grant?.accessToken !== grant?.accessToken;
|
|
735
813
|
this.grant = grant;
|
|
736
814
|
if (!changed) return;
|
|
@@ -7168,6 +7246,15 @@ function apply(ctx, config) {
|
|
|
7168
7246
|
client: config.client.trim() || "vibedev-plugin",
|
|
7169
7247
|
openBrowser: (url) => config.openBrowserOnSignIn ? openInBrowser(url) : Promise.resolve(false)
|
|
7170
7248
|
});
|
|
7249
|
+
ctx.inject(["credentials"], (scoped) => {
|
|
7250
|
+
scoped.on("credentials/record-updated", (key) => {
|
|
7251
|
+
if (key === GRANT_KEY) login.restore();
|
|
7252
|
+
});
|
|
7253
|
+
scoped.effect(async () => {
|
|
7254
|
+
await login.restore();
|
|
7255
|
+
return () => {};
|
|
7256
|
+
}, "dsh-vibedev: restore stored sign-in");
|
|
7257
|
+
});
|
|
7171
7258
|
const chain = new CredentialChain({
|
|
7172
7259
|
origin,
|
|
7173
7260
|
plugin: login,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibedev-si/dsh-vibedev",
|
|
3
|
-
"version": "0.2.
|
|
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",
|