dsh-ssh-tui 0.3.5 → 0.3.7

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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Remember the last model/effort per provider so /model and /setup can switch
3
+ * routes without wiping the previous provider's choice.
4
+ */
5
+ import z from '@deepseek-ai/schemastery';
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ export declare const ROUTE_MEMORY_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
8
+ /** Settings schema for `$DSH_HOME/settings.yaml` under ssh-tui-routes. */
9
+ export declare const ROUTE_MEMORY_SCHEMA: z<Schemastery.ObjectS<{
10
+ providers: z<import("@deepseek-ai/cosmokit").Dict<{
11
+ model?: string | null | undefined;
12
+ reasoningEffort?: string | null | undefined;
13
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
14
+ model: z<string, string>;
15
+ reasoningEffort: z<string, string>;
16
+ }>, string>>;
17
+ }>, Schemastery.ObjectT<{
18
+ providers: z<import("@deepseek-ai/cosmokit").Dict<{
19
+ model?: string | null | undefined;
20
+ reasoningEffort?: string | null | undefined;
21
+ } & import("@deepseek-ai/cosmokit").Dict, string>, import("@deepseek-ai/cosmokit").Dict<Schemastery.ObjectT<{
22
+ model: z<string, string>;
23
+ reasoningEffort: z<string, string>;
24
+ }>, string>>;
25
+ }>>;
26
+ export interface RememberedRoute {
27
+ model: string;
28
+ reasoningEffort?: string;
29
+ }
30
+ export type RouteMemoryMap = Record<string, RememberedRoute>;
31
+ export declare function parseRouteMemory(raw: unknown): RouteMemoryMap;
32
+ export declare function rememberedRouteFor(memory: RouteMemoryMap, provider: string): RememberedRoute | undefined;
33
+ /** Register the route-memory settings section so /model and /provider can persist. */
34
+ export declare function installRouteMemory(ctx: Context): void;
35
+ export declare function upsertRememberedRoute(memory: RouteMemoryMap, provider: string, route: RememberedRoute): RouteMemoryMap;
@@ -173,6 +173,11 @@ export interface TuiController {
173
173
  dispose(): Promise<void>;
174
174
  }
175
175
  export type PaintLinkKind = 'local' | 'ssh';
176
+ /** Compact token count, matching the web stats line (517 / 12.2K / 1.2M). */
177
+ export declare function formatTokens(n: number): string;
178
+ /** Compact duration, matching the web stats line (45.2s / 2m42s). */
179
+ export declare function formatDuration(ms: number): string;
180
+ export declare function formatTokensPerSecond(tokensPerSecond: number): string;
176
181
  /**
177
182
  * Explicit env/config always wins. Otherwise local TTYs stay snappy and SSH
178
183
  * sessions pick a tier from a measured round-trip (CSI 6n), falling back to
@@ -187,6 +192,73 @@ export declare function detectSshSession(env?: NodeJS.ProcessEnv): boolean;
187
192
  /** Map a CSI-6n round-trip to a paint cadence. Unknown RTT uses the SSH default. */
188
193
  export declare function paintIntervalForRtt(rttMs: number | undefined): number;
189
194
  export declare function paintLinkLabel(kind: PaintLinkKind, intervalMs: number, probed: boolean): string;
195
+ export type LinkQuality = 'local' | 'good' | 'ok' | 'slow' | 'poor' | 'unknown';
196
+ /** Signal-bar quality from a measured SSH round-trip, or local TTY. */
197
+ export declare function linkQualityOf(kind: PaintLinkKind, rttMs: number | undefined): LinkQuality;
198
+ /** How many filled signal pips: 4 local/fast, 3 ok, 2 slow, 1 poor, 0 unknown. */
199
+ export declare function linkSignalPips(quality: LinkQuality): number;
200
+ /** Compact footer chip: `SSH ●●●○ 90ms` — 1 pip red, 2 yellow, 3+ green. */
201
+ export declare function formatLinkQualityChip(kind: PaintLinkKind, intervalMs: number, rttMs: number | undefined, probed: boolean, color?: boolean): string;
202
+ export declare function providerShortCode(provider: string): string;
203
+ export interface FooterStatsInput {
204
+ turns: number;
205
+ steps: number;
206
+ llmMs: number;
207
+ toolMs: number;
208
+ ttftMs: number;
209
+ ttftSteps: number;
210
+ decodeMs: number;
211
+ decodeTokens: number;
212
+ inputTokens: number;
213
+ outputTokens: number;
214
+ cacheReadTokens: number;
215
+ cacheWriteTokens: number;
216
+ }
217
+ /** Stats groups in drop order (last is dropped first when the row is too wide). */
218
+ export declare function footerStatsGroups(stats: FooterStatsInput): string[];
219
+ export declare function fitFooterStatsLine(chip: string, groups: readonly string[], width: number): string;
220
+ export type FooterActivityKind = 'plan-review' | 'waiting' | 'compacting' | 'retry' | 'subagents' | 'tools' | 'plan-open' | 'plan-pending' | 'goal' | 'waiting-llm' | 'idle';
221
+ export interface FooterStatusInput {
222
+ running: boolean;
223
+ planReview: boolean;
224
+ waitingQuestion: boolean;
225
+ compacting: boolean;
226
+ retry?: {
227
+ retry: number;
228
+ maxRetries: number;
229
+ };
230
+ subagents: number;
231
+ tools: number;
232
+ planLeftOpen: boolean;
233
+ planPending: boolean;
234
+ planActive: boolean;
235
+ goalPhase?: 'active' | 'paused' | 'blocked';
236
+ idleMs: number;
237
+ model: string;
238
+ effort?: string;
239
+ preset?: string;
240
+ provider: string;
241
+ parentModel: string;
242
+ subModel: string;
243
+ subDiffers: boolean;
244
+ quotaCode?: string;
245
+ quotaPercent?: number;
246
+ search?: {
247
+ index: number;
248
+ total: number;
249
+ };
250
+ foldedInput: boolean;
251
+ multiLineInput: boolean;
252
+ queued: number;
253
+ }
254
+ export declare function footerActivity(input: FooterStatusInput): {
255
+ kind: FooterActivityKind;
256
+ text: string;
257
+ };
258
+ /** Short remaining-quota bar: 8 pips, filled from the left. */
259
+ export declare function formatQuotaBar(remainingPercent: number, width?: number): string;
260
+ export declare function footerIdentityParts(input: FooterStatusInput): string[];
261
+ export declare function fitFooterStatusLine(activity: string, identity: readonly string[], width: number): string;
190
262
  /** One incremental paint as a single stdout write (one SSH packet when corked). */
191
263
  export declare function composePaintOutput(options: {
192
264
  width: number;
@@ -196,6 +268,7 @@ export declare function composePaintOutput(options: {
196
268
  sizeChanged: boolean;
197
269
  chromeChanged: boolean;
198
270
  chromeStart: number;
271
+ previousChromeStart?: number;
199
272
  cursorRow: number;
200
273
  cursorColumn: number;
201
274
  }): string;
@@ -286,16 +359,22 @@ export interface QuotaSnapshot {
286
359
  windows: QuotaWindow[];
287
360
  }
288
361
  export declare function remainingPercentFromUsed(usedPercent: number): number;
289
- /** Cross a remaining-percent threshold from above (50 / 25 / 10 / 5). */
362
+ /** Cross a remaining-percent threshold from above (50 / 25 / 10 / 5).
363
+ * Only the tightest (lowest) crossed threshold is returned, so one drop
364
+ * never paints 50/25/10 as three identical warnings. */
290
365
  export declare function crossedQuotaThresholds(previousRemaining: number | undefined, remaining: number): number[];
291
366
  export declare function quotaAlertText(snapshot: QuotaSnapshot, window: QuotaWindow): string;
292
367
  /**
293
368
  * How often to re-fetch quota, based on the tightest window.
294
- * Hourly/5h: every 10 turns, every 4 when near a threshold.
295
- * Weekly: every 50 turns, every 10 when near.
296
- * Monthly: every 80 turns, every 20 when near.
369
+ * Counted in model steps (not conversation turns): a turn with several
370
+ * tool/LLM steps should refresh more often because it spends more quota.
371
+ * Hourly/5h: every 10 steps, every 4 when near a threshold.
372
+ * Weekly: every 50 steps, every 10 when near.
373
+ * Monthly: every 80 steps, every 20 when near.
297
374
  */
298
- export declare function quotaRefreshEveryTurns(window: QuotaWindow | undefined): number;
375
+ export declare function quotaRefreshEverySteps(window: QuotaWindow | undefined): number;
376
+ /** @deprecated Same cadence as {@link quotaRefreshEverySteps}; the name predates step accounting. */
377
+ export declare const quotaRefreshEveryTurns: typeof quotaRefreshEverySteps;
299
378
  export declare function parseSuperGrokBilling(payload: unknown): QuotaSnapshot;
300
379
  export declare function parseOpenCodeGoQuota(payload: unknown, provider: string): QuotaSnapshot;
301
380
  export declare function formatQuotaSnapshot(snapshot: QuotaSnapshot): string;
@@ -303,6 +382,23 @@ export declare function formatQuotaSnapshot(snapshot: QuotaSnapshot): string;
303
382
  export declare function tightestQuotaWindow(snapshot: QuotaSnapshot): QuotaWindow | undefined;
304
383
  /** Render the OpenCode Go quota payload as a transcript block. */
305
384
  export declare function formatOpenCodeGoUsage(payload: unknown, source: OpenCodeSource): string;
385
+ export interface AccountBalanceLine {
386
+ label: string;
387
+ amount: string;
388
+ currency?: string;
389
+ }
390
+ export interface AccountBalanceSnapshot {
391
+ provider: string;
392
+ plan: string;
393
+ available?: boolean;
394
+ lines: AccountBalanceLine[];
395
+ sourcePath?: string;
396
+ }
397
+ export declare function joinUrl(base: string, path: string): string;
398
+ export declare function parseDeepSeekBalance(payload: unknown, provider?: string): AccountBalanceSnapshot;
399
+ /** Best-effort parse of OpenAI-compatible credit/balance JSON. */
400
+ export declare function parseOpenAiCompatibleBalance(payload: unknown, provider: string, path: string): AccountBalanceSnapshot | undefined;
401
+ export declare function formatAccountBalance(snapshot: AccountBalanceSnapshot): string;
306
402
  /** Whether `text` could still grow into a recognized escape sequence. */
307
403
  export declare function isEscapePrefix(text: string): boolean;
308
404
  /** Parse a Device Status Report cursor reply (`CSI row;col R`). */
@@ -417,11 +513,6 @@ export declare function parseExitStatus(text: string): {
417
513
  exitCode?: number;
418
514
  signal?: string;
419
515
  };
420
- /** Compact token count, matching the web stats line (517 / 12.2K / 1.2M). */
421
- export declare function formatTokens(n: number): string;
422
- /** Compact duration, matching the web stats line (45.2s / 2m42s). */
423
- export declare function formatDuration(ms: number): string;
424
- export declare function formatTokensPerSecond(tokensPerSecond: number): string;
425
516
  /** Owns one interactive terminal channel and its agent event wiring. */
426
517
  export declare class SshTui {
427
518
  private readonly ctx;
@@ -491,14 +582,17 @@ export declare class SshTui {
491
582
  private lastChromeKey;
492
583
  private lastPaintWidth;
493
584
  private lastPaintHeight;
585
+ private lastChromeStart;
586
+ private lastTranscriptStart;
494
587
  private paintIntervalMs;
495
588
  private paintLink;
496
589
  private paintProbed;
590
+ private paintRttMs;
497
591
  private sessionTitle;
498
592
  private llmRetry;
499
593
  private quotaSnapshot;
500
594
  private quotaAlerted;
501
- private quotaTurnsSinceRefresh;
595
+ private quotaStepsSinceRefresh;
502
596
  private quotaRefreshInFlight;
503
597
  private searchHits;
504
598
  private searchIndex;
@@ -564,7 +658,7 @@ export declare class SshTui {
564
658
  private currentSelectionLabel;
565
659
  /** Replace one step's usage sample so a repeated report never double counts. */
566
660
  private recordUsage;
567
- /** The web-aligned session stats strip: counts, timings, cache, tokens. */
661
+ /** Compact session stats groups for the first footer row. */
568
662
  private statsText;
569
663
  /** Refresh the terminal window title (throttled while running). */
570
664
  private updateTerminalTitle;
@@ -641,9 +735,14 @@ export declare class SshTui {
641
735
  private listSelectableProviders;
642
736
  /** Built-in SuperGrok catalog used when the live adapter list is still warming up. */
643
737
  private static readonly XAI_FALLBACK_MODELS;
644
- /** /model: stay on the current provider by default; switching providers is opt-in. */
738
+ private loadModelOptions;
739
+ /** /model: models and effort for the current provider only. */
645
740
  private runModelCommand;
741
+ /** /provider: pick a provider, then its model (remembered route pre-filled). */
742
+ private runProviderCommand;
646
743
  /** Persist a provider/model/effort choice and keep the subagent on the same family. */
744
+ private rememberedRoute;
745
+ private rememberRoute;
647
746
  private applyModelSelection;
648
747
  /** Provider route the next subagent request should use. */
649
748
  private effectiveSubagentProvider;
@@ -653,6 +752,8 @@ export declare class SshTui {
653
752
  * switching to xAI is treated as stale.
654
753
  */
655
754
  private syncSubagentToProvider;
755
+ private promptSubagentAfterProviderSwitch;
756
+ private clearQuotaForProvider;
656
757
  /** Persist one subagent selection and publish it to the live request waterfall. */
657
758
  private saveSubagentSelection;
658
759
  /** Resolve the picker model list for one provider (endpoint first, then catalog). */
@@ -661,7 +762,7 @@ export declare class SshTui {
661
762
  private runSubmodelCommand;
662
763
  /** /subeffort: pick the reasoning effort subagent children use. */
663
764
  private runSubeffortCommand;
664
- /** /mode: pick an agent preset (standard / minimal / code / cordis / routing-suite / ...). */
765
+ /** /mode: pick an agent preset (standard / minimal / ptc / cordis / routing-suite / ...). */
665
766
  private runModeCommand;
666
767
  /** /resume: switch to a past session, or open a picker when no id is given. */
667
768
  private runResumeCommand;
@@ -673,10 +774,11 @@ export declare class SshTui {
673
774
  private fetchOpenCodeGoUsage;
674
775
  /** Explain Zen metered billing instead of pretending it has a quota. */
675
776
  private zenUsageText;
676
- /** /usage and /quota: remaining quota for the current subscription provider. */
777
+ /** /usage and /balance: remaining quota or prepaid balance for the current provider. */
677
778
  private runUsageCommand;
678
779
  private applyQuotaSnapshot;
679
780
  private refreshQuota;
781
+ private fetchAccountBalance;
680
782
  private fetchQuotaSnapshot;
681
783
  private resolveSuperGrokToken;
682
784
  private fetchJson;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ssh-tui",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -80,20 +80,20 @@
80
80
  },
81
81
  "peerDependencies": {
82
82
  "@deepseek-ai/cordis": "^4.0.1",
83
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
84
- "@deepseek-ai/dsh-agent-default-model": "^0.1.1-rc.2",
85
- "@deepseek-ai/dsh-agent-loop": "^0.1.1-rc.2",
86
- "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2",
87
- "@deepseek-ai/dsh-cmdline": "^0.1.1-rc.2",
88
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
89
- "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
90
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
91
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
92
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
93
- "@deepseek-ai/dsh-subagent": "^0.1.1-rc.2",
94
- "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2",
95
- "@deepseek-ai/dsh-user-questions": "^0.1.1-rc.2",
96
- "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2"
83
+ "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2",
84
+ "@deepseek-ai/dsh-agent-default-model": ">=0.1.1-rc.2",
85
+ "@deepseek-ai/dsh-agent-loop": ">=0.1.1-rc.2",
86
+ "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2",
87
+ "@deepseek-ai/dsh-cmdline": ">=0.1.1-rc.2",
88
+ "@deepseek-ai/dsh-commands": ">=0.1.1-rc.2",
89
+ "@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2",
90
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2",
91
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
92
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2",
93
+ "@deepseek-ai/dsh-subagent": ">=0.1.1-rc.2",
94
+ "@deepseek-ai/dsh-user-approval": ">=0.1.1-rc.2",
95
+ "@deepseek-ai/dsh-user-questions": ">=0.1.1-rc.2",
96
+ "@deepseek-ai/dsh-session-persistence": ">=0.1.1-rc.2"
97
97
  },
98
98
  "peerDependenciesMeta": {
99
99
  "@deepseek-ai/dsh-user-approval": {
@@ -111,21 +111,21 @@
111
111
  },
112
112
  "devDependencies": {
113
113
  "@deepseek-ai/cordis": "^4.0.1",
114
- "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
115
- "@deepseek-ai/dsh-agent-default-model": "^0.1.1-rc.2",
116
- "@deepseek-ai/dsh-agent-loop": "^0.1.1-rc.2",
117
- "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2",
118
- "@deepseek-ai/dsh-cmdline": "^0.1.1-rc.2",
119
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
120
- "@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
121
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
122
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
123
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
124
- "@deepseek-ai/dsh-subagent": "^0.1.1-rc.2",
125
- "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2",
126
- "@deepseek-ai/dsh-user-questions": "^0.1.1-rc.2",
114
+ "@deepseek-ai/dsh-agent": ">=0.1.1-rc.2",
115
+ "@deepseek-ai/dsh-agent-default-model": ">=0.1.1-rc.2",
116
+ "@deepseek-ai/dsh-agent-loop": ">=0.1.1-rc.2",
117
+ "@deepseek-ai/dsh-agent-presets": ">=0.1.1-rc.2",
118
+ "@deepseek-ai/dsh-cmdline": ">=0.1.1-rc.2",
119
+ "@deepseek-ai/dsh-commands": ">=0.1.1-rc.2",
120
+ "@deepseek-ai/dsh-credentials": ">=0.1.1-rc.2",
121
+ "@deepseek-ai/dsh-llm": ">=0.1.1-rc.2",
122
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
123
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2",
124
+ "@deepseek-ai/dsh-subagent": ">=0.1.1-rc.2",
125
+ "@deepseek-ai/dsh-user-approval": ">=0.1.1-rc.2",
126
+ "@deepseek-ai/dsh-user-questions": ">=0.1.1-rc.2",
127
127
  "@types/node": "^24.0.0",
128
128
  "typescript": "^5.9.0",
129
- "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2"
129
+ "@deepseek-ai/dsh-session-persistence": ">=0.1.1-rc.2"
130
130
  }
131
131
  }