@modesto_ma/dsh-guanxiang 0.1.0-beta.3 → 0.2.0-beta.1

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
@@ -96,6 +96,7 @@ npm 发行包已包含构建产物;本地 `link:` 安装前必须先执行 `pn
96
96
  branch: release # 可选,默认跟随远端默认分支
97
97
  gitCredentials: # https 仓库的访问令牌,只写令牌名称,不写令牌值
98
98
  - url: https://gitlab.example.com # 地址前缀;其下的 https 仓库使用该令牌
99
+ alias: guanxiang # 可选,面板显示的名称
99
100
  username: oauth2 # 可选,默认 oauth2
100
101
  tokenEnv: GITLAB_TOKEN # DSH 凭据库中的令牌名称,也可以是同名环境变量
101
102
  fetchIntervalMs: 0 # 加载 /guanxiang 时距上次拉取超过该时间的仓库先 fetch;0 = 每次
@@ -142,7 +143,7 @@ DSH Web 左侧栏的「观象」入口打开整页设置面板:
142
143
  | 分页 | 内容 |
143
144
  |---|---|
144
145
  | 应用仓库 | git 地址列表(分支可选)、每个仓库的拉取状态、提交号、应用描述与不可用原因,单个或全部重新拉取 |
145
- | 访问令牌 | https 地址前缀、用户名与令牌名称;保存后填入令牌,显示是否已设置及来源,可清除 |
146
+ | 访问令牌 | 填 GitLab 地址、令牌别名和令牌三项即可添加;用户名固定为 oauth2,凭据库键名自动分配(`GUANXIANG_GITLAB_TOKEN`、`GUANXIANG_GITLAB_TOKEN_2`……)。每行显示令牌是否已设置及来源,可替换或清除 |
146
147
  | 日志下载 | 允许下载的域名、单文件大小上限、下载超时 |
147
148
  | 高级 | 拉取目录、更新间隔、单次应用数上限、派发超时、SKILL 改进开关与提议上限,恢复 cordis.yml 配置 |
148
149
 
package/lib/client.js CHANGED
@@ -223,6 +223,7 @@ window.__ModuleLoader__.load({
223
223
  }),
224
224
  gitCredentials: draft.gitCredentials.map((credential) => ({
225
225
  url: credential.url.trim(),
226
+ ...credential.alias === void 0 || credential.alias.trim() === "" ? {} : { alias: credential.alias.trim() },
226
227
  username: credential.username.trim(),
227
228
  tokenEnv: credential.tokenEnv.trim()
228
229
  })),
@@ -336,18 +337,19 @@ window.__ModuleLoader__.load({
336
337
  setBusy(true);
337
338
  setNotice(void 0);
338
339
  try {
339
- adopt(await action(), resetDraft);
340
+ const next = await action();
341
+ adopt(next, resetDraft);
340
342
  setNotice({
341
343
  kind: "ok",
342
344
  text: success
343
345
  });
344
- return true;
346
+ return next;
345
347
  } catch (error) {
346
348
  setNotice({
347
349
  kind: "error",
348
350
  text: messageOf(error)
349
351
  });
350
- return false;
352
+ return;
351
353
  } finally {
352
354
  setBusy(false);
353
355
  }
@@ -461,8 +463,22 @@ window.__ModuleLoader__.load({
461
463
  setDraft,
462
464
  state,
463
465
  busy,
464
- onSave: (tokenEnv, value) => run(() => saveToken(tokenEnv, value), `已保存 ${tokenEnv},使用它的仓库会重新拉取`, false),
465
- onClear: (tokenEnv) => run(() => clearToken(tokenEnv), `已清除面板保存的 ${tokenEnv}`, false)
466
+ onAdd: async (credential, token) => {
467
+ const added = (await run(() => saveSettings({
468
+ ...state.settings,
469
+ gitCredentials: [...state.settings.gitCredentials, credential]
470
+ }), token === "" ? `已添加 ${credential.url},请填入令牌` : `已添加 ${credential.url}`, false))?.settings.gitCredentials[state.settings.gitCredentials.length];
471
+ if (added === void 0) return false;
472
+ setDraft((current) => current === void 0 || current.gitCredentials.some((item) => item.url === added.url) ? current : {
473
+ ...current,
474
+ gitCredentials: [...current.gitCredentials, added]
475
+ });
476
+ return token === "" || await run(() => saveToken(added.tokenEnv, token), `已添加 ${added.url} 并保存令牌,使用它的仓库会重新拉取`, false) !== void 0;
477
+ },
478
+ onSave: async (tokenEnv, value) => await run(() => saveToken(tokenEnv, value), `已保存 ${tokenEnv},使用它的仓库会重新拉取`, false) !== void 0,
479
+ onClear: (tokenEnv) => {
480
+ run(() => clearToken(tokenEnv), `已清除面板保存的 ${tokenEnv}`, false);
481
+ }
466
482
  }) : null,
467
483
  tab === "download" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DownloadTab, {
468
484
  draft,
@@ -698,7 +714,8 @@ window.__ModuleLoader__.load({
698
714
  });
699
715
  }
700
716
  const DEFAULT_USERNAME = "oauth2";
701
- const DEFAULT_TOKEN_ENV = "GITLAB_TOKEN";
717
+ /** Prefix of the credential keys the panel allocates; namespaced so an unrelated `GITLAB_TOKEN` in the environment is not picked up. */
718
+ const DEFAULT_TOKEN_ENV = "GUANXIANG_GITLAB_TOKEN";
702
719
  /** Credential store layers of `@deepseek-ai/dsh-credentials-local` other than its own file. */
703
720
  const TOKEN_SOURCES = {
704
721
  "env": "启动环境变量",
@@ -728,29 +745,55 @@ window.__ModuleLoader__.load({
728
745
  ]
729
746
  });
730
747
  }
731
- function TokensTab({ draft, setDraft, state, busy, onSave, onClear }) {
748
+ /**
749
+ * First of `GUANXIANG_GITLAB_TOKEN`, `GUANXIANG_GITLAB_TOKEN_2`, … that no
750
+ * saved or drafted entry uses.
751
+ * @param used - keys already in use.
752
+ * @returns a free key.
753
+ */
754
+ function freeTokenEnv(used) {
755
+ if (!used.has(DEFAULT_TOKEN_ENV)) return DEFAULT_TOKEN_ENV;
756
+ let index = 2;
757
+ while (used.has(`${DEFAULT_TOKEN_ENV}_${index}`)) index += 1;
758
+ return `${DEFAULT_TOKEN_ENV}_${index}`;
759
+ }
760
+ function TokensTab({ draft, setDraft, state, busy, onAdd, onSave, onClear }) {
732
761
  const [url, setUrl] = (0, react.useState)("");
733
- const [username, setUsername] = (0, react.useState)(DEFAULT_USERNAME);
734
- const [tokenEnv, setTokenEnv] = (0, react.useState)(DEFAULT_TOKEN_ENV);
762
+ const [alias, setAlias] = (0, react.useState)("");
763
+ const [token, setToken] = (0, react.useState)("");
764
+ const [addError, setAddError] = (0, react.useState)();
765
+ const [adding, setAdding] = (0, react.useState)(false);
735
766
  const [values, setValues] = (0, react.useState)({});
736
- const tokens = (0, react.useMemo)(() => new Map(state.credentials.tokens.map((token) => [token.tokenEnv, token])), [state.credentials.tokens]);
767
+ const tokens = (0, react.useMemo)(() => new Map(state.credentials.tokens.map((item) => [item.tokenEnv, item])), [state.credentials.tokens]);
737
768
  const saved = (0, react.useMemo)(() => new Set(state.settings.gitCredentials.map((credential) => credential.tokenEnv)), [state.settings.gitCredentials]);
769
+ const complete = url.trim() !== "" && alias.trim() !== "" && token.trim() !== "";
738
770
  const add = () => {
739
- const trimmed = url.trim();
740
- if (trimmed === "" || draft.gitCredentials.some((credential) => credential.url === trimmed)) return;
741
- setDraft({
742
- ...draft,
743
- gitCredentials: [...draft.gitCredentials, {
744
- url: trimmed,
745
- username: username.trim() === "" ? DEFAULT_USERNAME : username.trim(),
746
- tokenEnv: tokenEnv.trim() === "" ? DEFAULT_TOKEN_ENV : tokenEnv.trim()
747
- }]
771
+ if (!complete || busy || adding) return;
772
+ const address = url.trim();
773
+ if (state.settings.gitCredentials.some((credential) => credential.url === address)) {
774
+ setAddError("这个地址已经添加过了,请在上面的列表里更新它的令牌。");
775
+ return;
776
+ }
777
+ setAddError(void 0);
778
+ setAdding(true);
779
+ const tokenEnv = freeTokenEnv(/* @__PURE__ */ new Set([...saved, ...draft.gitCredentials.map((credential) => credential.tokenEnv)]));
780
+ onAdd({
781
+ url: address,
782
+ alias: alias.trim(),
783
+ username: DEFAULT_USERNAME,
784
+ tokenEnv
785
+ }, token.trim()).then((ok) => {
786
+ if (!ok) return;
787
+ setUrl("");
788
+ setAlias("");
789
+ setToken("");
790
+ }).finally(() => {
791
+ setAdding(false);
748
792
  });
749
- setUrl("");
750
793
  };
751
794
  const save = (name) => {
752
795
  const value = values[name]?.trim() ?? "";
753
- if (value === "") return;
796
+ if (value === "" || busy || adding) return;
754
797
  onSave(name, value).then((ok) => {
755
798
  if (ok) setValues((current) => ({
756
799
  ...current,
@@ -758,12 +801,15 @@ window.__ModuleLoader__.load({
758
801
  }));
759
802
  });
760
803
  };
804
+ const submitOnEnter = (event) => {
805
+ if (event.key === "Enter") add();
806
+ };
761
807
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
762
808
  className: "gx__sec",
763
809
  children: [
764
810
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
765
811
  className: "gx__secTitle",
766
- children: ["HTTPS 访问令牌 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overridden, {
812
+ children: ["GitLab 访问令牌 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overridden, {
767
813
  keys: state.overridden,
768
814
  name: "gitCredentials"
769
815
  })]
@@ -777,10 +823,10 @@ window.__ModuleLoader__.load({
777
823
  children: [
778
824
  draft.gitCredentials.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
779
825
  className: "gx__emptyRow",
780
- children: "还没有访问令牌。用 https 地址拉取 GitLab 等私有仓库时,添加服务器地址后保存,再填入个人访问令牌。"
826
+ children: "还没有访问令牌。用 https 地址拉取 GitLab 私有仓库时,在下面填入 GitLab 地址、令牌别名和令牌。"
781
827
  }) : null,
782
828
  draft.gitCredentials.map((credential, index) => {
783
- const token = saved.has(credential.tokenEnv) ? tokens.get(credential.tokenEnv) : void 0;
829
+ const status = saved.has(credential.tokenEnv) ? tokens.get(credential.tokenEnv) : void 0;
784
830
  const value = values[credential.tokenEnv] ?? "";
785
831
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
832
  className: "gx__row gx__row--top",
@@ -789,30 +835,17 @@ window.__ModuleLoader__.load({
789
835
  children: [
790
836
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
791
837
  className: "gx__rowLabel",
792
- children: [
793
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
794
- className: "gx__mono",
795
- children: credential.url
796
- }),
797
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
798
- className: "gx__tag",
799
- children: ["用户名 ", credential.username]
800
- }),
801
- tokenPill(token)
802
- ]
838
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: credential.alias ?? credential.url }), tokenPill(status)]
803
839
  }),
804
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
805
- className: "gx__rowSummary",
806
- children: ["令牌名称 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
807
- className: "gx__mono",
808
- children: credential.tokenEnv
809
- })]
840
+ credential.alias === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
841
+ className: "gx__rowSummary gx__mono",
842
+ children: credential.url
810
843
  }),
811
- token?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
844
+ status?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
812
845
  className: "gx__rowError",
813
- children: token.error
846
+ children: status.error
814
847
  }),
815
- token !== void 0 && token.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
848
+ status !== void 0 && status.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
816
849
  className: "gx__addRow gx__addRow--flush",
817
850
  children: [
818
851
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -820,7 +853,7 @@ window.__ModuleLoader__.load({
820
853
  type: "password",
821
854
  autoComplete: "new-password",
822
855
  spellCheck: false,
823
- placeholder: token.configured ? "输入新令牌以替换" : "粘贴个人访问令牌,例如 glpat-…",
856
+ placeholder: status.configured ? "输入新令牌以替换" : "粘贴令牌",
824
857
  value,
825
858
  onChange: (event) => {
826
859
  setValues({
@@ -835,13 +868,13 @@ window.__ModuleLoader__.load({
835
868
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
836
869
  type: "button",
837
870
  className: "gx__btn",
838
- disabled: busy || value.trim() === "",
871
+ disabled: busy || adding || value.trim() === "",
839
872
  onClick: () => {
840
873
  save(credential.tokenEnv);
841
874
  },
842
875
  children: "保存令牌"
843
876
  }),
844
- token.configured && token.source === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
877
+ status.configured && status.source === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
845
878
  type: "button",
846
879
  className: "gx__btn gx__btn--ghost",
847
880
  disabled: busy,
@@ -870,55 +903,67 @@ window.__ModuleLoader__.load({
870
903
  }, credential.url);
871
904
  }),
872
905
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
873
- className: "gx__addRow",
874
- children: [
875
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
876
- className: "gx__input",
877
- placeholder: "服务器地址,例如 https://gitlab.example.com",
878
- value: url,
879
- onChange: (event) => {
880
- setUrl(event.target.value);
881
- },
882
- onKeyDown: (event) => {
883
- if (event.key === "Enter") add();
884
- }
885
- }),
886
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
887
- className: "gx__input gx__input--branch",
888
- placeholder: `用户名(默认 ${DEFAULT_USERNAME})`,
889
- value: username,
890
- onChange: (event) => {
891
- setUsername(event.target.value);
892
- },
893
- onKeyDown: (event) => {
894
- if (event.key === "Enter") add();
895
- }
896
- }),
897
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
898
- className: "gx__input gx__input--branch gx__mono",
899
- placeholder: "令牌名称",
900
- value: tokenEnv,
901
- onChange: (event) => {
902
- setTokenEnv(event.target.value);
903
- },
904
- onKeyDown: (event) => {
905
- if (event.key === "Enter") add();
906
- }
907
- }),
908
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
909
- type: "button",
910
- className: "gx__btn",
911
- disabled: url.trim() === "",
912
- onClick: add,
913
- children: "添加"
914
- })
915
- ]
906
+ className: "gx__addForm",
907
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
908
+ className: "gx__addRow gx__addRow--flush",
909
+ children: [
910
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
911
+ className: "gx__field",
912
+ children: ["GitLab 地址", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
913
+ className: "gx__input",
914
+ placeholder: "https://gitlab.example.com",
915
+ value: url,
916
+ onChange: (event) => {
917
+ setUrl(event.target.value);
918
+ },
919
+ onKeyDown: submitOnEnter
920
+ })]
921
+ }),
922
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
923
+ className: "gx__field",
924
+ children: ["令牌别名", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
925
+ className: "gx__input",
926
+ placeholder: "例如 guanxiang",
927
+ value: alias,
928
+ onChange: (event) => {
929
+ setAlias(event.target.value);
930
+ },
931
+ onKeyDown: submitOnEnter
932
+ })]
933
+ }),
934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
935
+ className: "gx__field",
936
+ children: ["令牌", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
937
+ className: "gx__input gx__mono",
938
+ type: "password",
939
+ autoComplete: "new-password",
940
+ spellCheck: false,
941
+ placeholder: "粘贴个人访问令牌",
942
+ value: token,
943
+ onChange: (event) => {
944
+ setToken(event.target.value);
945
+ },
946
+ onKeyDown: submitOnEnter
947
+ })]
948
+ }),
949
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
950
+ type: "button",
951
+ className: "gx__btn gx__btn--end",
952
+ disabled: busy || adding || !complete,
953
+ onClick: add,
954
+ children: "添加"
955
+ })
956
+ ]
957
+ }), addError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
958
+ className: "gx__rowError",
959
+ children: addError
960
+ })]
916
961
  })
917
962
  ]
918
963
  }),
919
964
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
920
965
  className: "gx__secFoot",
921
- children: "地址在这个前缀下的 https 仓库使用对应令牌拉取(多个匹配时取路径最长的),ssh 地址不受影响。GitLab 个人访问令牌需要 read_repository 权限,用户名填 oauth2 即可。令牌保存在 DSH 凭据库(默认 $DSH_HOME/.credentials.yaml,仅本机用户可读),面板只显示是否已设置;也可以设置与令牌名称同名的环境变量。拉取时令牌只经环境变量交给 git,不写入仓库配置、日志或模型上下文。"
966
+ children: "地址在这个前缀下的 https 仓库使用对应令牌(多个匹配时取路径最长的),ssh 地址不受影响。拉取需要 read_repository 权限,开启 SKILL 改进时还需要 write_repository。令牌保存在 DSH 凭据库(默认 $DSH_HOME/.credentials.yaml,仅本机用户可读),面板只显示是否已设置;令牌只经环境变量交给 git,不写入设置文件、仓库配置、日志或模型上下文。"
922
967
  })
923
968
  ]
924
969
  });
@@ -1297,6 +1342,10 @@ window.__ModuleLoader__.load({
1297
1342
  .gx__addRow .gx__input{flex:1 1 160px;width:auto}
1298
1343
  .gx__addRow .gx__input--branch{flex:0 1 140px}
1299
1344
  .gx__addRow--flush{padding:6px 0 0}
1345
+ .gx__addForm{display:flex;flex-direction:column;gap:6px;padding:10px 14px}
1346
+ .gx__field{display:flex;flex-direction:column;gap:2px;flex:1 1 160px;color:var(--gx-fg3);font-size:12px}
1347
+ .gx__field .gx__input{flex:none;width:100%}
1348
+ .gx__btn--end{align-self:flex-end;height:30px}
1300
1349
  .gx__emptyRow{padding:11px 14px;color:var(--gx-fg3);font-size:12px}
1301
1350
  .gx__btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 12px;border:1px solid var(--gx-line);border-radius:7px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}
1302
1351
  .gx__btn:hover:not(:disabled){background:var(--gx-hover)}