@goodandready/dsh-subscriptions 0.6.1 → 0.6.2

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
@@ -195,10 +195,15 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
195
195
  ```
196
196
 
197
197
  > [!IMPORTANT]
198
- > Restart DSH Web UI after installation (`systemctl --user restart dsh-web`) and navigate to **Settings → Subscriptions** to link your accounts.
198
+ > Restart DSH Web UI after installation (`systemctl --user restart dsh-web`) and navigate to **Settings → Plugins → Plugin Settings → Subscriptions** to link your accounts.
199
199
 
200
200
  ---
201
201
 
202
+
203
+ > [!TIP]
204
+ > **UI Settings Card vs YAML Overrides**:
205
+ > All common options (account slots, autoLoopback, privacyMask, composerQuota, expiryNotifyDays, codexFastMode, codexVerbosity, ollamaFallback/baseUrl/model, and cooldown/probe intervals) can be managed directly in the Web UI card (**Settings → Plugins → Plugin Settings → Subscriptions**). Low-level parameters such as OAuth client IDs/redirect URIs, API base URL overrides, and custom vendors are configured in `settings.yaml`.
206
+
202
207
  ## ⚙️ Configuration Reference (`settings.yaml`)
203
208
 
204
209
  ```yaml
package/README.ru.md CHANGED
@@ -172,6 +172,11 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
172
172
 
173
173
  ---
174
174
 
175
+
176
+ > [!TIP]
177
+ > **Карточка настроек в UI vs Настройки в YAML**:
178
+ > Все основные параметры (слоты аккаунтов, перехват autoLoopback, privacyMask, индикатор composerQuota, оповещение expiryNotifyDays, codexFastMode, codexVerbosity, ollamaFallback/baseUrl/model, интервалы cooldown и probe) настраиваются напрямую в Web UI: **«Настройки → Плагины → Настройки плагинов → Subscriptions»**. Низкоуровневые оверрайды (собственные OAuth Client ID / Redirect URI, Base URL для прокси, кастомные вендоры) задаются в `settings.yaml`.
179
+
175
180
  ## ⚙️ Пример конфигурации (`settings.yaml`)
176
181
 
177
182
  ```yaml
package/lib/client.js CHANGED
@@ -103,6 +103,10 @@ window.__ModuleLoader__.load({
103
103
  '.dsub-heroHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}' +
104
104
  '.dsub-heroTitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}' +
105
105
  '.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
106
+ '.dsub-select{appearance:none;height:30px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 24px 0 8px;font-size:12px;cursor:pointer}' +
107
+ '.dsub-details{margin-top:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px;background:var(--dsw-alias-bg-layer-2)}' +
108
+ '.dsub-summary{cursor:pointer;font-weight:600;font-size:12.5px;color:var(--dsw-alias-label-secondary);user-select:none}' +
109
+ '.dsub-summary:hover{color:var(--dsw-alias-label-primary)}' +
106
110
  '.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
107
111
  '.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
108
112
  '.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
@@ -308,6 +312,37 @@ const cssId = 'dsh-subscriptions/settings.module.css'
308
312
  'useOriginHint': 'Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.',
309
313
  'privacyMask': 'Mask emails and account identifiers',
310
314
  'privacyMaskHint': 'For demos and screen sharing: emails show as j***n@example.com.',
315
+ 'autoLoopback': 'Auto Loopback',
316
+ 'autoLoopbackHint': 'Listen on local callback port to catch OAuth tokens automatically without manual copy-paste.',
317
+ 'hideDeprecated': 'Hide deprecated models',
318
+ 'hideDeprecatedHint': 'Hide older or superseded model variants from model dropdowns.',
319
+ 'composerQuota': 'Composer quota indicator',
320
+ 'composerQuotaHint': 'Display subscription usage indicator directly in the composer toolbar.',
321
+ 'composerQuotaOff': 'Disabled',
322
+ 'composerQuotaPercent': 'Percentage',
323
+ 'composerQuotaBar': 'Progress Bar',
324
+ 'composerQuotaForecast': 'Remaining Forecast',
325
+ 'expiryNotifyDays': 'Expiry warning threshold (days)',
326
+ 'expiryNotifyDaysHint': 'Notify when subscription renewal or token expiration is within this many days (0 to disable).',
327
+ 'codexFastMode': 'Codex fast mode (1.5x)',
328
+ 'codexFastModeHint': 'Enable high-throughput mode for OpenAI Codex models if supported by subscription.',
329
+ 'codexVerbosity': 'Codex streaming verbosity',
330
+ 'codexVerbosityHint': 'Control streaming chunking detail level from the Codex provider.',
331
+ 'codexVerbosityDefault': 'Default',
332
+ 'codexVerbosityLow': 'Low',
333
+ 'codexVerbosityMedium': 'Medium',
334
+ 'codexVerbosityHigh': 'High',
335
+ 'ollamaFallback': 'Ollama local fallback',
336
+ 'ollamaFallbackHint': 'Automatically route queries to a local Ollama instance when cloud quota is exhausted.',
337
+ 'ollamaBaseUrl': 'Ollama Base URL',
338
+ 'ollamaFallbackModel': 'Ollama Fallback Model',
339
+ 'advancedSettings': 'Advanced Settings & Monitoring',
340
+ 'cooldownMs': 'Failure cooldown (minutes)',
341
+ 'cooldownMsHint': 'How long to pause a failing or rate-limited account before retrying.',
342
+ 'probeIntervalMin': 'Health probe interval (minutes)',
343
+ 'probeIntervalMinHint': 'Interval for background checks to refresh account status and quota balance.',
344
+ 'notifyLimits': 'Notify on rate limits',
345
+ 'notifyLimitsHint': 'Show notification alerts when account usage reaches quota ceiling.',
311
346
  'resetCredits': 'Reset credits',
312
347
  'resetCreditsHint': 'ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.',
313
348
  'resetCreditsAction': 'Reset ChatGPT Limit',
@@ -847,6 +882,146 @@ const cssId = 'dsh-subscriptions/settings.module.css'
847
882
  ),
848
883
  React.createElement('div', { className: 'dsub-sub' },
849
884
  t('privacyMaskHint')),
885
+ React.createElement('label', { className: 'dsub-row' },
886
+ React.createElement('input', {
887
+ type: 'checkbox',
888
+ checked: !!draft.autoLoopback,
889
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { autoLoopback: e.target.checked })),
890
+ }),
891
+ React.createElement('span', null, t('autoLoopback')),
892
+ ),
893
+ React.createElement('div', { className: 'dsub-sub' },
894
+ t('autoLoopbackHint')),
895
+ React.createElement('label', { className: 'dsub-row' },
896
+ React.createElement('input', {
897
+ type: 'checkbox',
898
+ checked: !!draft.hideDeprecatedModels,
899
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { hideDeprecatedModels: e.target.checked })),
900
+ }),
901
+ React.createElement('span', null, t('hideDeprecated')),
902
+ ),
903
+ React.createElement('div', { className: 'dsub-sub' },
904
+ t('hideDeprecatedHint')),
905
+ React.createElement('div', { className: 'dsub-row' },
906
+ React.createElement('span', { className: 'dsub-h' }, t('composerQuota')),
907
+ React.createElement('select', {
908
+ className: 'dsub-select',
909
+ value: draft.composerQuota || 'percent',
910
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { composerQuota: e.target.value })),
911
+ },
912
+ React.createElement('option', { value: 'off' }, t('composerQuotaOff')),
913
+ React.createElement('option', { value: 'percent' }, t('composerQuotaPercent')),
914
+ React.createElement('option', { value: 'bar' }, t('composerQuotaBar')),
915
+ React.createElement('option', { value: 'forecast' }, t('composerQuotaForecast')),
916
+ ),
917
+ ),
918
+ React.createElement('div', { className: 'dsub-sub' },
919
+ t('composerQuotaHint')),
920
+ React.createElement('div', { className: 'dsub-row' },
921
+ React.createElement('span', { className: 'dsub-h' }, t('expiryNotifyDays')),
922
+ React.createElement('input', {
923
+ type: 'number',
924
+ className: 'dsub-inp',
925
+ style: { width: '80px', minWidth: '80px' },
926
+ min: 0,
927
+ max: 90,
928
+ value: draft.expiryNotifyDays != null ? draft.expiryNotifyDays : 7,
929
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { expiryNotifyDays: Number(e.target.value) || 0 })),
930
+ }),
931
+ ),
932
+ React.createElement('div', { className: 'dsub-sub' },
933
+ t('expiryNotifyDaysHint')),
934
+ React.createElement('label', { className: 'dsub-row' },
935
+ React.createElement('input', {
936
+ type: 'checkbox',
937
+ checked: !!draft.codexFastMode,
938
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { codexFastMode: e.target.checked })),
939
+ }),
940
+ React.createElement('span', null, t('codexFastMode')),
941
+ ),
942
+ React.createElement('div', { className: 'dsub-sub' },
943
+ t('codexFastModeHint')),
944
+ React.createElement('div', { className: 'dsub-row' },
945
+ React.createElement('span', { className: 'dsub-h' }, t('codexVerbosity')),
946
+ React.createElement('select', {
947
+ className: 'dsub-select',
948
+ value: draft.codexVerbosity || 'default',
949
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { codexVerbosity: e.target.value })),
950
+ },
951
+ React.createElement('option', { value: 'default' }, t('codexVerbosityDefault')),
952
+ React.createElement('option', { value: 'low' }, t('codexVerbosityLow')),
953
+ React.createElement('option', { value: 'medium' }, t('codexVerbosityMedium')),
954
+ React.createElement('option', { value: 'high' }, t('codexVerbosityHigh')),
955
+ ),
956
+ ),
957
+ React.createElement('div', { className: 'dsub-sub' },
958
+ t('codexVerbosityHint')),
959
+ React.createElement('label', { className: 'dsub-row' },
960
+ React.createElement('input', {
961
+ type: 'checkbox',
962
+ checked: !!draft.ollamaFallback,
963
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaFallback: e.target.checked })),
964
+ }),
965
+ React.createElement('span', null, t('ollamaFallback')),
966
+ ),
967
+ React.createElement('div', { className: 'dsub-sub' },
968
+ t('ollamaFallbackHint')),
969
+ draft.ollamaFallback ? React.createElement('div', { className: 'dsub-row' },
970
+ React.createElement('input', {
971
+ type: 'text',
972
+ className: 'dsub-inp dsub-grow',
973
+ placeholder: 'http://localhost:11434',
974
+ value: draft.ollamaBaseUrl || '',
975
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaBaseUrl: e.target.value })),
976
+ }),
977
+ React.createElement('input', {
978
+ type: 'text',
979
+ className: 'dsub-inp dsub-grow',
980
+ placeholder: 'llama3:latest',
981
+ value: draft.ollamaFallbackModel || '',
982
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { ollamaFallbackModel: e.target.value })),
983
+ }),
984
+ ) : null,
985
+ React.createElement('details', { className: 'dsub-details' },
986
+ React.createElement('summary', { className: 'dsub-summary' }, t('advancedSettings')),
987
+ React.createElement('div', { className: 'dsub-block' },
988
+ React.createElement('div', { className: 'dsub-row' },
989
+ React.createElement('span', { className: 'dsub-h' }, t('cooldownMs')),
990
+ React.createElement('input', {
991
+ type: 'number',
992
+ className: 'dsub-inp',
993
+ style: { width: '80px', minWidth: '80px' },
994
+ min: 1,
995
+ max: 120,
996
+ value: Math.round((draft.cooldownMs || 600000) / 60000),
997
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { cooldownMs: (Number(e.target.value) || 10) * 60000 })),
998
+ }),
999
+ ),
1000
+ React.createElement('div', { className: 'dsub-sub' }, t('cooldownMsHint')),
1001
+ React.createElement('div', { className: 'dsub-row' },
1002
+ React.createElement('span', { className: 'dsub-h' }, t('probeIntervalMin')),
1003
+ React.createElement('input', {
1004
+ type: 'number',
1005
+ className: 'dsub-inp',
1006
+ style: { width: '80px', minWidth: '80px' },
1007
+ min: 1,
1008
+ max: 1440,
1009
+ value: draft.probeIntervalMin != null ? draft.probeIntervalMin : 15,
1010
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { probeIntervalMin: Number(e.target.value) || 15 })),
1011
+ }),
1012
+ ),
1013
+ React.createElement('div', { className: 'dsub-sub' }, t('probeIntervalMinHint')),
1014
+ React.createElement('label', { className: 'dsub-row' },
1015
+ React.createElement('input', {
1016
+ type: 'checkbox',
1017
+ checked: draft.notifyLimits !== false,
1018
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { notifyLimits: e.target.checked })),
1019
+ }),
1020
+ React.createElement('span', null, t('notifyLimits')),
1021
+ ),
1022
+ React.createElement('div', { className: 'dsub-sub' }, t('notifyLimitsHint')),
1023
+ ),
1024
+ ),
850
1025
  ),
851
1026
  React.createElement('div', { className: 'dsub-block' },
852
1027
  React.createElement('div', { className: 'dsub-row' },
@@ -1258,9 +1433,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1258
1433
  // The canonical place is the Plugins tab: a card with its own header
1259
1434
  // and collapse instead of a row in the side list. The registration key must
1260
1435
  // equal the settings namespace, otherwise the tab silently hides the slot.
1261
- let moved = false
1262
1436
  try {
1263
- moved = !!ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
1437
+ ctx.slots.inject('settings.plugin.item', () => ctx.slots.register(
1264
1438
  {
1265
1439
  name: 'settings.plugin.item',
1266
1440
  key: NS,
@@ -1269,27 +1443,8 @@ const cssId = 'dsh-subscriptions/settings.module.css'
1269
1443
  },
1270
1444
  PluginCard,
1271
1445
  ))
1272
- } catch { moved = false }
1273
- if (moved) return
1274
- // #244: Fallback for harness cores before settings.plugin.item slot was
1275
- // introduced (< 0.1.2-rc.1). Once all production profiles run a core
1276
- // that has settings.plugin.item, this block can be safely removed.
1277
- // Tracked in Gitea #244 — do not remove without verifying minimum core.
1278
- try {
1279
- ctx.slots.inject('settings.section', () => ctx.slots.register(
1280
- {
1281
- name: 'settings.section',
1282
- id: '@goodandready/dsh-subscriptions',
1283
- order: 28,
1284
- locale: NS,
1285
- label: () => t('title'),
1286
- inject: () => ({ ctx: ctx }),
1287
- },
1288
- SubsSection,
1289
- ))
1290
1446
  } catch (e) {
1291
- // Neither slot system available — settings panel will not render.
1292
- // This is expected on headless / non-web profiles.
1447
+ // Slot system not available — expected on headless / non-web profiles.
1293
1448
  }
1294
1449
  }
1295
1450
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,9 +34,6 @@
34
34
  "bugs": {
35
35
  "url": "https://github.com/GooDAnDReaDY/dsh-subscriptions/issues"
36
36
  },
37
- "scripts": {
38
- "test": "eslint lib/ && node --test test/*.test.mjs"
39
- },
40
37
  "dsh": {
41
38
  "bundle": {
42
39
  "patch": "./cordis.patch.yml"
@@ -63,5 +60,8 @@
63
60
  },
64
61
  "devDependencies": {
65
62
  "@deepseek-ai/dsh-llm": "0.1.0-rc.8"
63
+ },
64
+ "scripts": {
65
+ "test": "eslint lib/ && node --test test/*.test.mjs"
66
66
  }
67
- }
67
+ }