dsh-ssh-tui 0.6.3 → 0.6.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.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Route context-window sizing for hand-declared gateways.
3
+ *
4
+ * A gateway whose `GET /models` discloses no capacity leaves every model on the
5
+ * route at the harness default (262,144), so a long session compacts far
6
+ * earlier than the endpoint requires. `/setup` closes that gap from two
7
+ * sources, in order: the listing's own `context_length`, then the installed
8
+ * pi-ai catalog, which sizes 1,200-odd models under its own provider ids.
9
+ *
10
+ * A gateway id is rarely a catalog id verbatim — `gemini-claude-sonnet-4-6`
11
+ * proxies `claude-sonnet-4-6`, `deepseek/deepseek-v4.1-flash` is namespaced,
12
+ * `gemini-3.6-flash-high` names its thinking level — so the lookup normalizes
13
+ * before it matches, and reports nothing rather than guessing a window it
14
+ * never found.
15
+ *
16
+ * @module dsh-ssh-tui/context-window
17
+ */
18
+ import type { CatalogPreset } from './provider-catalog.js';
19
+ /**
20
+ * Catalog ids to try for one gateway model id, most specific first.
21
+ * @param raw - the id the endpoint advertises.
22
+ * @returns lowercased candidates, deduplicated and order-preserving.
23
+ */
24
+ export declare function catalogIdCandidates(raw: string): string[];
25
+ /**
26
+ * Every catalog window, keyed by lowercased model id. The first provider to
27
+ * size an id wins, so a shared id keeps one stable answer.
28
+ * @param presets - the read catalog presets.
29
+ * @returns the lookup index.
30
+ */
31
+ export declare function catalogWindowIndex(presets: readonly CatalogPreset[]): Map<string, number>;
32
+ /**
33
+ * The catalog window for one gateway model id.
34
+ * @param id - the id the endpoint advertises.
35
+ * @param index - {@link catalogWindowIndex} for the loaded catalog.
36
+ * @returns the window, or `undefined` when no candidate names a sized model.
37
+ */
38
+ export declare function catalogContextWindow(id: string, index: ReadonlyMap<string, number>): number | undefined;
39
+ /**
40
+ * The route-level default to persist: the smallest window discovered for the
41
+ * route's selected models. A model neither the listing nor the catalog sizes
42
+ * then inherits a value the route already proved it serves, never more.
43
+ * @param windows - the per-model windows discovered so far.
44
+ * @returns the window, or `undefined` when nothing was discovered.
45
+ */
46
+ export declare function suggestedRouteContextWindow(windows: Iterable<number>): number | undefined;
@@ -67,10 +67,11 @@ export type QuestionSubmit = {
67
67
  kind: 'none';
68
68
  };
69
69
  /**
70
- * What Enter means for a question dialog. A single-select list with nothing
71
- * highlighted cancels (the user must not accidentally answer with the first
72
- * option); a multi-select list may answer with an empty selection. With no
73
- * options at all, the typed text is the answer.
70
+ * What Enter means for a question dialog. A single-select list answers with the
71
+ * option its highlight sits on: the list opens on the first option, so Enter
72
+ * without arrowing answers that default instead of cancelling, and Esc stays the
73
+ * explicit cancel. A multi-select list may answer with an empty selection. With
74
+ * no options at all, the typed text is the answer.
74
75
  */
75
76
  export declare function questionSubmit(dialog: QuestionDialog, input: string): QuestionSubmit;
76
77
  /** y/n/ctrl-c/esc answers for a confirm prompt. */
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Friendly display names for background jobs.
3
+ *
4
+ * `job_*` cards otherwise read as the raw tool vocabulary (`job_output`,
5
+ * `job_id: bash-1`), which is both untranslated and hard to talk about out
6
+ * loud. Each job id instead gets a two-word alias — "蔚蓝水獭", "azure otter" —
7
+ * derived from the id itself rather than from a random draw, so the same job
8
+ * keeps one name across its call card, its result card, and every later
9
+ * `job_output` / `job_kill` mention. The model-facing id stays authoritative;
10
+ * the alias is presentation only.
11
+ *
12
+ * @module dsh-ssh-tui/job-label
13
+ */
14
+ /**
15
+ * One stable alias for a background job, or `undefined` when the id is empty
16
+ * or the locale carries no vocabulary.
17
+ * @param jobId - the native job id (`bash-1`, `pwsh-2`, …).
18
+ * @returns the locale-formatted alias, e.g. `蔚蓝水獭` / `azure otter`.
19
+ */
20
+ export declare function jobAlias(jobId: string): string | undefined;
@@ -3,6 +3,8 @@ export interface CatalogPreset {
3
3
  name: string;
4
4
  baseUrl: string;
5
5
  modelIds: string[];
6
+ /** Context capacity the catalog records, by lowercased model id; absent when it sizes none. */
7
+ capacities?: Record<string, number>;
6
8
  }
7
9
  /** Filter presets by a case-insensitive substring match on id or name. */
8
10
  export declare function filterCatalogPresets(presets: readonly CatalogPreset[], query: string): CatalogPreset[];
@@ -27,6 +27,15 @@ export declare const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usag
27
27
  export declare const OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
28
28
  export declare const SUPERGROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
29
29
  export declare const DEEPSEEK_PUBLIC_BASE_URL = "https://api.deepseek.com";
30
+ /**
31
+ * Command Code's billing surface sits on the API root, not under the
32
+ * `/provider/v1` route its chat requests use, so these are fixed: a configured
33
+ * base URL only decides whether the canonical host is the right one to ask.
34
+ */
35
+ export declare const COMMAND_CODE_BASE_URL = "https://api.commandcode.ai";
36
+ export declare const COMMAND_CODE_CREDITS_URL = "https://api.commandcode.ai/alpha/billing/credits";
37
+ export declare const COMMAND_CODE_SUBSCRIPTIONS_URL = "https://api.commandcode.ai/alpha/billing/subscriptions";
38
+ export declare const COMMAND_CODE_USAGE_URL = "https://api.commandcode.ai/alpha/usage/summary";
30
39
  /** OpenAI-completions gateways: probe these relative to the configured base URL. */
31
40
  export declare const OPENAI_COMPAT_BALANCE_PATHS: readonly ["/user/balance", "/dashboard/billing/credit_grants", "/v1/dashboard/billing/credit_grants", "/v1/dashboard/billing/subscription"];
32
41
  /**
@@ -35,6 +44,23 @@ export declare const OPENAI_COMPAT_BALANCE_PATHS: readonly ["/user/balance", "/d
35
44
  * routes are recognized by their `opencode.ai` base URL.
36
45
  */
37
46
  export declare function openCodeSourceFor(provider: string, llmPiAiSection: unknown): OpenCodeSource | null;
47
+ /** A recognized Command Code route, used by the quota poller. */
48
+ export interface CommandCodeSource {
49
+ provider: string;
50
+ apiKeyEnv: string;
51
+ label: string;
52
+ }
53
+ /**
54
+ * Classify one route as Command Code's quota surface.
55
+ *
56
+ * Recognized by provider id or by a canonical base URL. The billing requests
57
+ * always target the canonical host, so a lookalike base URL never receives the
58
+ * credential even when the route keeps Command Code's provider id.
59
+ * @param provider - the selected provider route.
60
+ * @param llmPiAiSection - the `llm-pi-ai` settings section, if readable.
61
+ * @returns the credential reference to use, or `null` for another provider.
62
+ */
63
+ export declare function commandCodeSourceFor(provider: string, llmPiAiSection: unknown): CommandCodeSource | null;
38
64
  export type QuotaPeriod = 'hourly' | 'weekly' | 'monthly' | 'unknown';
39
65
  export interface QuotaWindow {
40
66
  label: string;
@@ -42,6 +68,8 @@ export interface QuotaWindow {
42
68
  /** Remaining percent of the window (100 = unused). */
43
69
  remainingPercent: number;
44
70
  resetsAt?: string;
71
+ /** Extra human-readable fact beside the percent, e.g. remaining USD credits. */
72
+ detail?: string;
45
73
  }
46
74
  export interface QuotaSnapshot {
47
75
  provider: string;
@@ -64,6 +92,27 @@ export declare function quotaRefreshEverySteps(window: QuotaWindow | undefined):
64
92
  export declare const quotaRefreshEveryTurns: typeof quotaRefreshEverySteps;
65
93
  export declare function parseSuperGrokBilling(payload: unknown): QuotaSnapshot;
66
94
  export declare function parseOpenCodeGoQuota(payload: unknown, provider: string): QuotaSnapshot;
95
+ /** The billing-period start used to scope the spend query, when reported. */
96
+ export declare function commandCodePeriodStart(subscription: unknown): string | undefined;
97
+ export interface CommandCodeQuotaPayload {
98
+ /** Required `/alpha/billing/credits` reply. */
99
+ credits: unknown;
100
+ /** Optional `/alpha/billing/subscriptions` reply, for the plan and period. */
101
+ subscription?: unknown;
102
+ /** Optional `/alpha/usage/summary` reply, for period spend. */
103
+ summary?: unknown;
104
+ }
105
+ /**
106
+ * Parse Command Code's credits reply into the shared quota snapshot.
107
+ *
108
+ * The rolling five-hour and weekly windows report used/cap; the credit pool
109
+ * reports a remaining USD balance, which becomes a third window whose percent
110
+ * divides period spend by spend plus remaining balance.
111
+ * @param payload - the billing replies already fetched.
112
+ * @param provider - the route the snapshot is labeled with.
113
+ * @throws Error when no window could be read.
114
+ */
115
+ export declare function parseCommandCodeQuota(payload: CommandCodeQuotaPayload, provider: string): QuotaSnapshot;
67
116
  export declare function formatQuotaSnapshot(snapshot: QuotaSnapshot): string;
68
117
  /** Compact `/status` quota line: tightest window first, then the rest. */
69
118
  export declare function formatQuotaStatusLine(snapshot: QuotaSnapshot | undefined): string;
@@ -46,15 +46,44 @@ export declare function wrapWaitDetails(detail: string, width: number, maxLines?
46
46
  * those ambiguous glyphs as two made `repeatToWidth('─', cols)` paint a
47
47
  * half-width rule and parked the input cursor half a cell past the text.
48
48
  *
49
- * Emoji-bearing symbols are the exception: an emoji font draws them two cells
50
- * wide even where wcwidth says one, and / from `npm test` spilled a row
51
- * into the next card. Variation selectors are zero-width, so ✔️ counts once
52
- * for the base plus nothing for VS16.
49
+ * Emoji-bearing symbols are the exception: a terminal with an emoji font draws
50
+ * a symbol the monospace font lacks with a colour glyph that is wider than the
51
+ * cell it advances, so they are budgeted two cells. {@link pinEmojiCells} makes
52
+ * the terminal actually spend both: VS15 asks for the narrow text form, and a
53
+ * reserving space clears the second cell when the run does not already end in
54
+ * one. Budgeting two cells without clearing the second is what left `✖`
55
+ * overlapping the `|` beside it while every `▶` row came up a cell short.
53
56
  *
54
57
  * Overflow into the input box is handled by clipping/padding painted rows to
55
58
  * the measured column count, not by inflating glyph width.
56
59
  */
57
60
  export declare function displayWidth(text: string): number;
61
+ /**
62
+ * Make a painted row spend the two cells {@link displayWidth} budgets for every
63
+ * BMP emoji symbol.
64
+ *
65
+ * The monospace font of the terminal this was measured on covers `▶` but not
66
+ * `✖` or `ℹ`, so the two fall back to a colour emoji glyph about 1.6 cells wide
67
+ * while still advancing one cell: the `|` and `^` beside them were drawn under
68
+ * the glyph, and the row ended short because the second budgeted cell was never
69
+ * spent. The fix has two halves:
70
+ *
71
+ * - VS15 asks for the narrow text form (`emoji-variation-sequences.txt`,
72
+ * Unicode 17.0, lists `2139 FE0E` and `2716 FE0E` among 371 such sequences).
73
+ * Terminals that ignore it fall back to the colour glyph, which the next
74
+ * bullet still contains.
75
+ * - A reserving space supplies the second cell, always emitted even when the run
76
+ * already has one. That keeps the budget and the terminal in step for every
77
+ * shape: the table counts the symbol as two cells plus every space the text
78
+ * carries, and the terminal spends one cell of glyph advance, one for the
79
+ * reserving space, and one for each of those spaces.
80
+ *
81
+ * A symbol whose Unicode default presentation is emoji already advances two
82
+ * cells, so it is left untouched.
83
+ * @param text - one already-padded, already-clipped row.
84
+ * @returns the row with the text request and the second cell filled in.
85
+ */
86
+ export declare function pinEmojiCells(text: string): string;
58
87
  /** Pad or clip one already-sanitized line so it occupies exactly `width` cells. */
59
88
  export declare function padToWidth(text: string, width: number): string;
60
89
  /**
@@ -6,6 +6,7 @@ import { type TextSegment } from './term-text.js';
6
6
  import type { DisplayKind, Row, ToolDiffHunk } from './transcript-types.js';
7
7
  export declare const SHELL_TOOL_NAMES: Set<string>;
8
8
  export declare const DIFF_TOOL_NAMES: Set<string>;
9
+ export declare const JOB_TOOL_NAMES: Set<string>;
9
10
  /** Format a model list compactly: show the first few entries and an ellipsis. */
10
11
  export declare function formatModelList(models: readonly string[], max?: number): string;
11
12
  /** Prefer the fields a human scans for; fall back to the first scalar pairs. */
@@ -29,7 +29,7 @@ export type { CollapsibleBlock, DisplayKind, DisconnectPolicyName, PlanTodoItem,
29
29
  export { clipAnsiToWidth, cursorVisualPosition, displayWidth, foldInputView, hrefAtColumn, osc52Clipboard, osc8Enabled, paintedLinkHits, fmtElapsedCompact, padAnsiToWidth, padToWidth, renderMarkdownLines, repeatToWidth, shimmerText, truncateToWidth, visibleWidth, waitCardCopy, waitSummaryFromReasoning, wrapWaitDetails, } from './term-text.js';
30
30
  export { captureHangupSignals, composePaintOutput, detectSshSession, formatLinkQualityChip, ignoreFurtherHangupSignals, isEscapePrefix, findCursorPositionReply, isHangupErrno, linkQualityOf, linkSignalPips, paintIntervalForRtt, paintLinkLabel, parseCursorPositionReply, pickerWindowStart, probeTerminalRttMs, releaseHangupSignals, resolvePaintIntervalMs, waitUntilIdleOrTimeout, writeBootSplash, type LinkQuality, type PaintLinkKind, } from './paint.js';
31
31
  export { CONTEXT_IDLE_COMPACT_RATIO, CONTEXT_PRESSURE_DANGER_RATIO, CONTEXT_PRESSURE_WARN_RATIO, CONTEXT_RING_EMPTY, CONTEXT_RING_SEGMENTS, contextPressureAlertText, contextPressureRingColor, contextPressureUsedTokens, contextPressureView, describeProviderRoute, dropFooterQuotaPlanName, fitFooterStatsLine, fitFooterStatusLine, footerActivity, footerIdentityParts, footerStatsGroups, formatContextPressureChip, formatContextPressureRing, formatContextPressureStatusLine, formatDuration, formatFooterQuota, formatQuotaBar, formatStatusReport, formatTokens, formatTokensPerSecond, parseContextPressure, promptPressureTokens, providerShortCode, providerUsesLocalOAuth, shouldIdleAutoCompact, subagentRouteLabel, type ContextPressureSample, type ContextPressureView, type FooterActivityKind, type FooterStatsInput, type FooterStatusInput, type StatusReportInput, } from './footer.js';
32
- export { crossedQuotaThresholds, formatAccountBalance, formatFooterBalance, formatOpenCodeGoUsage, formatQuotaSnapshot, formatQuotaStatusLine, joinUrl, openCodeSourceFor, parseDeepSeekBalance, parseOpenAiCompatibleBalance, parseOpenCodeGoQuota, parseSuperGrokBilling, quotaAlertText, quotaRefreshEverySteps, quotaRefreshEveryTurns, remainingPercentFromUsed, tightestQuotaWindow, type AccountBalanceLine, type AccountBalanceSnapshot, type OpenCodeFlavor, type OpenCodeSource, type QuotaPeriod, type QuotaSnapshot, type QuotaWindow, } from './quota.js';
32
+ export { commandCodePeriodStart, commandCodeSourceFor, crossedQuotaThresholds, formatAccountBalance, formatFooterBalance, formatOpenCodeGoUsage, formatQuotaSnapshot, formatQuotaStatusLine, joinUrl, openCodeSourceFor, parseCommandCodeQuota, parseDeepSeekBalance, parseOpenAiCompatibleBalance, parseOpenCodeGoQuota, parseSuperGrokBilling, quotaAlertText, quotaRefreshEverySteps, quotaRefreshEveryTurns, remainingPercentFromUsed, tightestQuotaWindow, type AccountBalanceLine, type AccountBalanceSnapshot, type CommandCodeSource, type OpenCodeFlavor, type OpenCodeSource, type QuotaPeriod, type QuotaSnapshot, type QuotaWindow, } from './quota.js';
33
33
  export { commandAcceptsAttachments, forEachSessionEvent, isAssistantStreamEvent, isTokenDeltaChunk, listPersistenceHeaders, inspectPersistenceSession, sessionEventType, sessionEvents, settingsNamespace, streamChunkOf, streamFirstTokenTime, streamFrameAttemptId, streamFrameOwner, } from './dsh-compat.js';
34
34
  export { applyTurnEndToPlan, askSummary, cardCategoryOf, compactionHeaderText, formatCompactCommandError, isPromptInjectionMessage, matchTranscriptRows, parseFindQuery, parsePlanTodos, planCloseNudgeText, planDockNote, planIsLive, planTitleFromMarkdown, planTurnLeftOpen, promptInjectionSources, promptInjectionTitle, subagentHeaderText, todoProgressLabel, todoSummary, type CardCategory, } from './plan.js';
35
35
  export { buildToolHeader, canMergeToolCall, compactEditPath, compactToolBursts, compactToolGroups, countDiffAddDel, countDiffLines, countOutputLines, diffMetaDiffs, diffStatToken, friendlyJsonLines, parseExitStatus, presentToolCall, READ_TOOL_NAMES, renderToolDiff, toolBodyFitsWorkspace, toolBodyLines, toolStateColor, toolStateLabel, wrappedToolBodyLineCount, } from './tool-present.js';
@@ -94,6 +94,31 @@ export interface TuiController {
94
94
  }
95
95
  export type WorkspaceView = 'detailed' | 'compact';
96
96
  export declare function parseDisconnectPolicy(raw: string): DisconnectPolicyName | undefined;
97
+ /**
98
+ * The `reasoningEfforts` map `/setup` writes for a hand-declared
99
+ * OpenAI-compatible route.
100
+ *
101
+ * Such a route is absent from pi-ai's catalog, so `defaultReasoningEffort`
102
+ * resolves nothing before the profile is saved and the model would land as a
103
+ * bare `{ id }` entry. The Harness then reports that model as non-reasoning
104
+ * (`levels [off]`) and refuses every effort the TUI itself offers for an
105
+ * undeclared model. Declaring the offered vocabulary up front keeps the model
106
+ * dispatchable; no parameter is sent until an effort is selected.
107
+ * @returns the level-to-wire map, with `off` meaning "send no parameter".
108
+ */
109
+ export declare function handDeclaredReasoningEfforts(): Record<string, string | null>;
110
+ /**
111
+ * The `reasoningEfforts` map `/setup` persists for one model entry.
112
+ *
113
+ * A hand-declared OpenAI-compatible completions route always receives the
114
+ * offered vocabulary; a catalog-backed route keeps whatever live model info
115
+ * resolved, preserving the existing behavior of redeclaring only the chosen
116
+ * default.
117
+ * @param providerType - the wizard template that produced the route.
118
+ * @param defaultEffort - the level resolved from live model info, if any.
119
+ * @returns the level-to-wire map, or `undefined` to declare nothing.
120
+ */
121
+ export declare function onboardingReasoningEfforts(providerType: string, defaultEffort: string | undefined): Record<string, string | null> | undefined;
97
122
  /** Parse `/effort high` / `/subeffort default`. Empty or unknown → undefined. */
98
123
  export declare function parseEffortArg(raw: string): {
99
124
  kind: 'default';
@@ -119,6 +144,8 @@ export declare class SshTui {
119
144
  /** Web-aligned provider presets from the host's pi-ai catalog (undefined until loaded / when unreachable). */
120
145
  private catalogPresets;
121
146
  private catalogLoad;
147
+ /** Memoized id→window index over {@link catalogPresets}. */
148
+ private catalogWindows;
122
149
  private input;
123
150
  private cursor;
124
151
  private inputFolded;
@@ -563,6 +590,20 @@ export declare class SshTui {
563
590
  private runSubmodelCommand;
564
591
  /** /effort: pick or set the reasoning effort for the current model. */
565
592
  private runEffortCommand;
593
+ /**
594
+ * Persist one explicitly chosen level as a model's `reasoningEfforts`.
595
+ *
596
+ * `/effort` offers `low`…`max` for a model whose route declares nothing, but
597
+ * the selection alone cannot make the request legal: the Harness still
598
+ * resolves the model as non-reasoning and refuses it. Writing the chosen
599
+ * level — merged with whatever the entry already declares — keeps the
600
+ * selection and the declaration consistent. Only a route that explicitly
601
+ * speaks OpenAI Completions is amended: its dialect omits the parameter for
602
+ * `off` and was verified against a gateway, while another protocol may
603
+ * materialize an unset level as a value its endpoint refuses.
604
+ * @returns whether the entry now declares the level.
605
+ */
606
+ private declareReasoningEffort;
566
607
  private setReasoningEffort;
567
608
  /** /subeffort: pick the reasoning effort subagent children use. */
568
609
  private runSubeffortCommand;
@@ -588,6 +629,16 @@ export declare class SshTui {
588
629
  private resolveCredential;
589
630
  /** Query the OpenCode Go quota endpoint. */
590
631
  private fetchOpenCodeGoUsage;
632
+ /**
633
+ * Command Code's quota: the rolling 5h/weekly windows and credit pool off
634
+ * `/alpha/billing/credits`, enriched by the subscription period and spend.
635
+ * Only the credits read is required; the two enrichment reads are
636
+ * best-effort so one failing endpoint cannot hide the windows.
637
+ * @param source - the recognized Command Code route.
638
+ */
639
+ private fetchCommandCodeQuota;
640
+ /** Best-effort JSON read for quota enrichment; a failure leaves the field out. */
641
+ private tryFetchJson;
591
642
  /** Explain Zen metered billing instead of pretending it has a quota. */
592
643
  private zenUsageText;
593
644
  /** /usage and /balance: remaining quota or prepaid balance for the current provider. */
@@ -614,6 +665,16 @@ export declare class SshTui {
614
665
  */
615
666
  private mergedProviderEntries;
616
667
  private moveProviderCursor;
668
+ /** The installed catalog's id→window index, built once per catalog load. */
669
+ private catalogWindowIndexSource;
670
+ /**
671
+ * Size the wizard's picked models from the installed catalog, filling in the
672
+ * capacities the endpoint's listing did not disclose and recording them on
673
+ * the wizard state so the saved entries carry them.
674
+ * @param state - the wizard state whose `models` are being sized.
675
+ * @returns the windows discovered for this pick, by model id.
676
+ */
677
+ private sizeOnboardingModels;
617
678
  private handleOnboardingChar;
618
679
  private advanceOnboarding;
619
680
  /** Fetch the endpoint's model list into the onboarding wizard's models step. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ssh-tui",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "SSH-friendly interactive terminal TUI plugin for DeepSeek Harness",
5
5
  "keywords": [
6
6
  "deepseek-harness",