dsh-ssh-tui 0.3.4 → 0.3.6

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;
@@ -0,0 +1,26 @@
1
+ export interface SessionLockInfo {
2
+ pid: number;
3
+ sessionId: string;
4
+ startedAt: string;
5
+ tty?: string;
6
+ }
7
+ export declare class SessionLockHeldError extends Error {
8
+ readonly lock: SessionLockInfo;
9
+ readonly path: string;
10
+ constructor(lock: SessionLockInfo, path: string);
11
+ }
12
+ export declare function sessionLockPath(sessionId: string, dshHome?: string): string;
13
+ export declare function parseSessionLock(raw: string): SessionLockInfo | undefined;
14
+ /** True when `pid` still exists on this machine (best-effort). */
15
+ export declare function processIsAlive(pid: number): boolean;
16
+ export declare function formatLockHeldMessage(lock: SessionLockInfo): string;
17
+ export declare function sessionLockDisabled(env?: NodeJS.ProcessEnv): boolean;
18
+ export declare function acquireSessionLock(sessionId: string, options?: {
19
+ pid?: number;
20
+ tty?: string | null;
21
+ dshHome?: string;
22
+ }): Promise<{
23
+ path: string;
24
+ info: SessionLockInfo;
25
+ }>;
26
+ export declare function releaseSessionLock(path: string, pid?: number): Promise<void>;
@@ -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;
@@ -303,6 +376,23 @@ export declare function formatQuotaSnapshot(snapshot: QuotaSnapshot): string;
303
376
  export declare function tightestQuotaWindow(snapshot: QuotaSnapshot): QuotaWindow | undefined;
304
377
  /** Render the OpenCode Go quota payload as a transcript block. */
305
378
  export declare function formatOpenCodeGoUsage(payload: unknown, source: OpenCodeSource): string;
379
+ export interface AccountBalanceLine {
380
+ label: string;
381
+ amount: string;
382
+ currency?: string;
383
+ }
384
+ export interface AccountBalanceSnapshot {
385
+ provider: string;
386
+ plan: string;
387
+ available?: boolean;
388
+ lines: AccountBalanceLine[];
389
+ sourcePath?: string;
390
+ }
391
+ export declare function joinUrl(base: string, path: string): string;
392
+ export declare function parseDeepSeekBalance(payload: unknown, provider?: string): AccountBalanceSnapshot;
393
+ /** Best-effort parse of OpenAI-compatible credit/balance JSON. */
394
+ export declare function parseOpenAiCompatibleBalance(payload: unknown, provider: string, path: string): AccountBalanceSnapshot | undefined;
395
+ export declare function formatAccountBalance(snapshot: AccountBalanceSnapshot): string;
306
396
  /** Whether `text` could still grow into a recognized escape sequence. */
307
397
  export declare function isEscapePrefix(text: string): boolean;
308
398
  /** Parse a Device Status Report cursor reply (`CSI row;col R`). */
@@ -417,11 +507,6 @@ export declare function parseExitStatus(text: string): {
417
507
  exitCode?: number;
418
508
  signal?: string;
419
509
  };
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
510
  /** Owns one interactive terminal channel and its agent event wiring. */
426
511
  export declare class SshTui {
427
512
  private readonly ctx;
@@ -491,9 +576,11 @@ export declare class SshTui {
491
576
  private lastChromeKey;
492
577
  private lastPaintWidth;
493
578
  private lastPaintHeight;
579
+ private lastChromeStart;
494
580
  private paintIntervalMs;
495
581
  private paintLink;
496
582
  private paintProbed;
583
+ private paintRttMs;
497
584
  private sessionTitle;
498
585
  private llmRetry;
499
586
  private quotaSnapshot;
@@ -508,6 +595,7 @@ export declare class SshTui {
508
595
  constructor(ctx: Context, agent: Agent, config: TuiConfig);
509
596
  /** Enter raw mode, switch to the alternate screen, and start listening. */
510
597
  start(): void;
598
+ private notifyPluginUpdate;
511
599
  private startRenderTimer;
512
600
  private calibratePaintInterval;
513
601
  /** Replay the durable session log so a resumed session renders its history. */
@@ -563,7 +651,7 @@ export declare class SshTui {
563
651
  private currentSelectionLabel;
564
652
  /** Replace one step's usage sample so a repeated report never double counts. */
565
653
  private recordUsage;
566
- /** The web-aligned session stats strip: counts, timings, cache, tokens. */
654
+ /** Compact session stats groups for the first footer row. */
567
655
  private statsText;
568
656
  /** Refresh the terminal window title (throttled while running). */
569
657
  private updateTerminalTitle;
@@ -640,9 +728,14 @@ export declare class SshTui {
640
728
  private listSelectableProviders;
641
729
  /** Built-in SuperGrok catalog used when the live adapter list is still warming up. */
642
730
  private static readonly XAI_FALLBACK_MODELS;
643
- /** /model: stay on the current provider by default; switching providers is opt-in. */
731
+ private loadModelOptions;
732
+ /** /model: models and effort for the current provider only. */
644
733
  private runModelCommand;
734
+ /** /provider: pick a provider, then its model (remembered route pre-filled). */
735
+ private runProviderCommand;
645
736
  /** Persist a provider/model/effort choice and keep the subagent on the same family. */
737
+ private rememberedRoute;
738
+ private rememberRoute;
646
739
  private applyModelSelection;
647
740
  /** Provider route the next subagent request should use. */
648
741
  private effectiveSubagentProvider;
@@ -652,6 +745,8 @@ export declare class SshTui {
652
745
  * switching to xAI is treated as stale.
653
746
  */
654
747
  private syncSubagentToProvider;
748
+ private promptSubagentAfterProviderSwitch;
749
+ private clearQuotaForProvider;
655
750
  /** Persist one subagent selection and publish it to the live request waterfall. */
656
751
  private saveSubagentSelection;
657
752
  /** Resolve the picker model list for one provider (endpoint first, then catalog). */
@@ -672,10 +767,11 @@ export declare class SshTui {
672
767
  private fetchOpenCodeGoUsage;
673
768
  /** Explain Zen metered billing instead of pretending it has a quota. */
674
769
  private zenUsageText;
675
- /** /usage and /quota: remaining quota for the current subscription provider. */
770
+ /** /usage and /balance: remaining quota or prepaid balance for the current provider. */
676
771
  private runUsageCommand;
677
772
  private applyQuotaSnapshot;
678
773
  private refreshQuota;
774
+ private fetchAccountBalance;
679
775
  private fetchQuotaSnapshot;
680
776
  private resolveSuperGrokToken;
681
777
  private fetchJson;
@@ -0,0 +1,4 @@
1
+ export declare function compareSemver(a: string, b: string): number;
2
+ export declare function formatUpdateNotice(current: string, latest: string): string;
3
+ export declare function fetchLatestNpmVersion(fetchImpl?: typeof fetch): Promise<string | undefined>;
4
+ export declare function checkForPluginUpdate(current: string): Promise<string | undefined>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Best-effort npm latest check. Never auto-upgrades; failures stay quiet.
3
+ */
4
+ const NPM_REGISTRY = 'https://registry.npmjs.org/dsh-ssh-tui/latest';
5
+ const CHECK_TIMEOUT_MS = 4000;
6
+ export function compareSemver(a, b) {
7
+ const parse = (value) => {
8
+ const core = value.trim().replace(/^v/u, '').split('-')[0] ?? '0';
9
+ const parts = core.split('.').map(part => Number.parseInt(part, 10));
10
+ return [0, 1, 2].map(i => Number.isFinite(parts[i]) ? parts[i] : 0);
11
+ };
12
+ const left = parse(a);
13
+ const right = parse(b);
14
+ for (let i = 0; i < 3; i++) {
15
+ if (left[i] !== right[i])
16
+ return left[i] - right[i];
17
+ }
18
+ return 0;
19
+ }
20
+ export function formatUpdateNotice(current, latest) {
21
+ return `发现新版本 dsh-ssh-tui ${latest}(当前 ${current})。更新:dsh plugin --profile tui add dsh-ssh-tui`;
22
+ }
23
+ export async function fetchLatestNpmVersion(fetchImpl = fetch) {
24
+ try {
25
+ const response = await fetchImpl(NPM_REGISTRY, {
26
+ headers: { accept: 'application/json' },
27
+ signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
28
+ });
29
+ if (!response.ok)
30
+ return undefined;
31
+ const body = await response.json();
32
+ return typeof body.version === 'string' && body.version.trim() !== '' ? body.version.trim() : undefined;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ export async function checkForPluginUpdate(current) {
39
+ if (process.env.DSH_TUI_NO_UPDATE_CHECK === '1' || process.env.DSH_TUI_NO_UPDATE_CHECK === 'true') {
40
+ return undefined;
41
+ }
42
+ const latest = await fetchLatestNpmVersion();
43
+ if (latest === undefined)
44
+ return undefined;
45
+ if (compareSemver(latest, current) <= 0)
46
+ return undefined;
47
+ return formatUpdateNotice(current, latest);
48
+ }
49
+ //# sourceMappingURL=update-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,YAAY,GAAG,+CAA+C,CAAA;AACpE,MAAM,gBAAgB,GAAG,IAAI,CAAA;AAE7B,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,CAAS;IAChD,MAAM,KAAK,GAAG,CAAC,KAAa,EAAY,EAAE;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAA;QACjE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;QACpE,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtE,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACrB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,CAAC,CAAE,KAAK,KAAK,CAAC,CAAC,CAAE;YAAE,OAAO,IAAI,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,CAAE,CAAA;IACzD,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,MAAc;IAChE,OAAO,qBAAqB,MAAM,OAAO,OAAO,+CAA+C,CAAA;AACjG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,YAA0B,KAAK;IAE/B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE;YAC7C,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;SAC9C,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,SAAS,CAAA;QAClC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAA2B,CAAA;QAC3D,OAAO,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACzG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAe;IACxD,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,MAAM,EAAE,CAAC;QAClG,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,qBAAqB,EAAE,CAAA;IAC5C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC1C,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACzD,OAAO,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AAC5C,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "dsh-ssh-tui",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
8
+ "dsh-plugin",
8
9
  "plugin",
9
10
  "tui",
10
11
  "terminal",
11
- "ssh"
12
+ "ssh",
13
+ "cordis"
12
14
  ],
13
15
  "author": "cyjyyd",
14
16
  "repository": {