ftown 0.25.2 → 0.25.3

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.
Files changed (71) hide show
  1. package/dist/command-rpc.d.ts +3 -0
  2. package/dist/command-rpc.js +41 -0
  3. package/dist/command-rpc.js.map +1 -1
  4. package/dist/ftown-cli.js +5 -2
  5. package/dist/ftown-cli.js.map +1 -1
  6. package/dist/index.js +3 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/quota/agy.d.ts +16 -0
  9. package/dist/quota/agy.js +287 -0
  10. package/dist/quota/agy.js.map +1 -0
  11. package/dist/quota/annotate.d.ts +5 -0
  12. package/dist/quota/annotate.js +56 -0
  13. package/dist/quota/annotate.js.map +1 -0
  14. package/dist/quota/claude.d.ts +27 -0
  15. package/dist/quota/claude.js +265 -0
  16. package/dist/quota/claude.js.map +1 -0
  17. package/dist/quota/codex.d.ts +25 -0
  18. package/dist/quota/codex.js +378 -0
  19. package/dist/quota/codex.js.map +1 -0
  20. package/dist/quota/format.d.ts +6 -0
  21. package/dist/quota/format.js +113 -0
  22. package/dist/quota/format.js.map +1 -0
  23. package/dist/quota/grok.d.ts +97 -0
  24. package/dist/quota/grok.js +563 -0
  25. package/dist/quota/grok.js.map +1 -0
  26. package/dist/quota/models-agy.d.ts +10 -0
  27. package/dist/quota/models-agy.js +144 -0
  28. package/dist/quota/models-agy.js.map +1 -0
  29. package/dist/quota/models-claude.d.ts +10 -0
  30. package/dist/quota/models-claude.js +227 -0
  31. package/dist/quota/models-claude.js.map +1 -0
  32. package/dist/quota/models-codex.d.ts +18 -0
  33. package/dist/quota/models-codex.js +305 -0
  34. package/dist/quota/models-codex.js.map +1 -0
  35. package/dist/quota/models-grok.d.ts +19 -0
  36. package/dist/quota/models-grok.js +272 -0
  37. package/dist/quota/models-grok.js.map +1 -0
  38. package/dist/quota/models-muse.d.ts +13 -0
  39. package/dist/quota/models-muse.js +200 -0
  40. package/dist/quota/models-muse.js.map +1 -0
  41. package/dist/quota/models-opencode.d.ts +51 -0
  42. package/dist/quota/models-opencode.js +400 -0
  43. package/dist/quota/models-opencode.js.map +1 -0
  44. package/dist/quota/muse.d.ts +38 -0
  45. package/dist/quota/muse.js +225 -0
  46. package/dist/quota/muse.js.map +1 -0
  47. package/dist/quota/opencode.d.ts +7 -0
  48. package/dist/quota/opencode.js +47 -0
  49. package/dist/quota/opencode.js.map +1 -0
  50. package/dist/quota/routing-config.d.ts +18 -0
  51. package/dist/quota/routing-config.js +438 -0
  52. package/dist/quota/routing-config.js.map +1 -0
  53. package/dist/quota/routing-service.d.ts +24 -0
  54. package/dist/quota/routing-service.js +118 -0
  55. package/dist/quota/routing-service.js.map +1 -0
  56. package/dist/quota/routing.d.ts +10 -0
  57. package/dist/quota/routing.js +180 -0
  58. package/dist/quota/routing.js.map +1 -0
  59. package/dist/quota/types.d.ts +304 -0
  60. package/dist/quota/types.js +45 -0
  61. package/dist/quota/types.js.map +1 -0
  62. package/dist/quota-cli.d.ts +80 -0
  63. package/dist/quota-cli.js +462 -0
  64. package/dist/quota-cli.js.map +1 -0
  65. package/dist/security/bridge/permissions.js +4 -2
  66. package/dist/security/bridge/permissions.js.map +1 -1
  67. package/dist/types.d.ts +2 -2
  68. package/package.json +3 -2
  69. package/skills/ftown/SKILL.md +2 -1
  70. package/skills/ftown/references/orchestrator.md +5 -0
  71. package/skills/ftown/references/quota.md +177 -0
@@ -0,0 +1,180 @@
1
+ import { formatDuration } from './format.js';
2
+ import { QUOTA_PROVIDER_IDS } from './types.js';
3
+ const MINUTE_MS = 60_000;
4
+ const IMMINENT_RESET_MINUTES = 10;
5
+ const RUNWAY_LOW = 0.5;
6
+ const RUNWAY_CAP = 99;
7
+ /** Higher = more binding (worse for routing). unknown never binds. */
8
+ const SEVERITY = {
9
+ available: 0,
10
+ low: 1,
11
+ exhausted: 2,
12
+ unknown: -1,
13
+ };
14
+ /** Sort rank for routing advice, best first. */
15
+ const RANK = {
16
+ available: 0,
17
+ low: 1,
18
+ unknown: 2,
19
+ exhausted: 3,
20
+ };
21
+ const KIND_ORDER = {
22
+ five_hour: 0,
23
+ weekly: 1,
24
+ };
25
+ const LABEL = {
26
+ five_hour: '5-hour',
27
+ weekly: 'weekly',
28
+ };
29
+ /** Assesses one window against `now`. Pure: never reads the clock. */
30
+ export function assessWindow(window, now) {
31
+ const used = window.usedPercent;
32
+ const measuredLeft = used !== null && Number.isFinite(used)
33
+ ? Math.round(Math.min(100, Math.max(0, 100 - used)))
34
+ : null;
35
+ const resetMs = window.resetsAt !== null ? Date.parse(window.resetsAt) : Number.NaN;
36
+ const resetsInMinutes = Number.isNaN(resetMs)
37
+ ? null
38
+ : Math.max(0, Math.floor((resetMs - now.getTime()) / MINUTE_MS));
39
+ if (measuredLeft !== null &&
40
+ resetsInMinutes !== null &&
41
+ resetsInMinutes <= IMMINENT_RESET_MINUTES) {
42
+ // About to reset: the current figure is irrelevant, so it counts as fully available.
43
+ return {
44
+ availability: 'available',
45
+ leftPercent: 100,
46
+ resetsInMinutes,
47
+ runway: null,
48
+ resetting: true,
49
+ };
50
+ }
51
+ const windowMinutes = window.windowMinutes;
52
+ let runway = null;
53
+ if (measuredLeft !== null &&
54
+ windowMinutes !== null &&
55
+ Number.isFinite(windowMinutes) &&
56
+ windowMinutes > 0 &&
57
+ resetsInMinutes !== null &&
58
+ resetsInMinutes > 0) {
59
+ const raw = (measuredLeft / 100) / (resetsInMinutes / windowMinutes);
60
+ runway = Math.min(RUNWAY_CAP, Math.round(raw * 100) / 100);
61
+ }
62
+ let availability;
63
+ if (measuredLeft === null) {
64
+ availability = 'unknown';
65
+ }
66
+ else if (measuredLeft <= 1) {
67
+ availability = 'exhausted';
68
+ }
69
+ else if (measuredLeft < 15) {
70
+ availability = 'low';
71
+ }
72
+ else if (runway !== null && runway < RUNWAY_LOW) {
73
+ availability = 'low';
74
+ }
75
+ else {
76
+ availability = 'available';
77
+ }
78
+ return {
79
+ availability,
80
+ leftPercent: measuredLeft,
81
+ resetsInMinutes,
82
+ runway,
83
+ resetting: false,
84
+ };
85
+ }
86
+ /** True when `a` should bind over `b`: worse availability, then less left, then kind order. */
87
+ function isMoreBinding(a, b) {
88
+ const severityA = SEVERITY[a.availability];
89
+ const severityB = SEVERITY[b.availability];
90
+ if (severityA !== severityB)
91
+ return severityA > severityB;
92
+ if (a.leftPercent !== b.leftPercent)
93
+ return a.leftPercent < b.leftPercent;
94
+ return KIND_ORDER[a.window.kind] < KIND_ORDER[b.window.kind];
95
+ }
96
+ function unknownRouting(row) {
97
+ const message = row.message?.trim() ?? '';
98
+ return {
99
+ provider: row.provider,
100
+ availability: 'unknown',
101
+ leftPercent: null,
102
+ bindingWindow: null,
103
+ resetsAt: null,
104
+ resetsInMinutes: null,
105
+ runway: null,
106
+ reason: message.length > 0 ? message : 'no usage numbers available',
107
+ };
108
+ }
109
+ function reasonFor(binding) {
110
+ const label = LABEL[binding.window.kind];
111
+ const left = binding.leftPercent;
112
+ const duration = binding.resetsInMinutes !== null
113
+ ? formatDuration(binding.resetsInMinutes * MINUTE_MS)
114
+ : null;
115
+ const resetClause = duration !== null ? `, resets in ${duration}` : '';
116
+ const runwayClause = binding.runway !== null && binding.runway > 0 ? ` (runway ${binding.runway}x)` : '';
117
+ if (binding.resetting) {
118
+ return duration !== null
119
+ ? `${label} window resets in ${duration}`
120
+ : `${label} window resets soon`;
121
+ }
122
+ if (binding.availability === 'exhausted') {
123
+ return `${label} window used up${resetClause}`;
124
+ }
125
+ if (binding.availability === 'low' && left < 15) {
126
+ return `only ${left}% of the ${label} window left${resetClause}${runwayClause}`;
127
+ }
128
+ if (binding.availability === 'low') {
129
+ const untilReset = duration !== null ? ` but ${duration} until reset` : '';
130
+ return `${left}% of the ${label} window left${untilReset}${runwayClause}`;
131
+ }
132
+ return `${left}% of the ${label} window left${resetClause}${runwayClause}`;
133
+ }
134
+ function adviseRow(row, now) {
135
+ const usable = [];
136
+ for (const window of row.windows) {
137
+ const assessed = assessWindow(window, now);
138
+ if (assessed.availability !== 'unknown' && assessed.leftPercent !== null) {
139
+ usable.push({
140
+ window,
141
+ availability: assessed.availability,
142
+ leftPercent: assessed.leftPercent,
143
+ resetsInMinutes: assessed.resetsInMinutes,
144
+ runway: assessed.runway,
145
+ resetting: assessed.resetting,
146
+ });
147
+ }
148
+ }
149
+ if (row.status !== 'ok' || usable.length === 0) {
150
+ return unknownRouting(row);
151
+ }
152
+ const binding = usable.reduce((best, candidate) => isMoreBinding(candidate, best) ? candidate : best);
153
+ return {
154
+ provider: row.provider,
155
+ availability: binding.availability,
156
+ leftPercent: binding.leftPercent,
157
+ bindingWindow: binding.window.kind,
158
+ resetsAt: binding.window.resetsAt,
159
+ resetsInMinutes: binding.resetsInMinutes,
160
+ runway: binding.resetting ? null : binding.runway,
161
+ reason: reasonFor(binding),
162
+ };
163
+ }
164
+ /** Quota left scaled by pace: 0% left always scores 0; null left scores -1. */
165
+ function score(advice) {
166
+ if (advice.leftPercent === null)
167
+ return -1;
168
+ return (advice.leftPercent / 100) * Math.min(1, advice.runway ?? 1);
169
+ }
170
+ function compareRouting(a, b) {
171
+ const rankDiff = RANK[a.availability] - RANK[b.availability];
172
+ if (rankDiff !== 0)
173
+ return rankDiff;
174
+ const scoreDiff = score(b) - score(a);
175
+ if (scoreDiff !== 0)
176
+ return scoreDiff;
177
+ return QUOTA_PROVIDER_IDS.indexOf(a.provider) - QUOTA_PROVIDER_IDS.indexOf(b.provider);
178
+ }
179
+ export const adviseQuotaRouting = (rows, now) => rows.map((row) => adviseRow(row, now)).sort(compareRouting);
180
+ //# sourceMappingURL=routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/quota/routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Frozen contract for the subscription-quota tool (`ftown-quota`).
3
+ *
4
+ * "Quota" here means SUBSCRIPTION PLAN limits (the short ~5-hour window and
5
+ * the weekly window), not per-session token counts (see usage-collector.ts).
6
+ *
7
+ * This file is types only. Do not add logic and do not edit it from a task.
8
+ */
9
+ export type QuotaProviderId = 'claude' | 'codex' | 'grok' | 'muse' | 'agy' | 'opencode';
10
+ export declare const QUOTA_PROVIDER_IDS: readonly QuotaProviderId[];
11
+ /**
12
+ * `five_hour` is the provider's short rolling block (its real length is in
13
+ * `windowMinutes` when the provider states it). `weekly` is the 7-day block.
14
+ */
15
+ export type QuotaWindowKind = 'five_hour' | 'weekly';
16
+ export interface QuotaWindow {
17
+ kind: QuotaWindowKind;
18
+ /**
19
+ * Percent of the window already used, 0-100 scale (may exceed 100).
20
+ * null when the provider reports the window but no number for it.
21
+ */
22
+ usedPercent: number | null;
23
+ /** ISO-8601 UTC instant the window resets, or null when unknown. */
24
+ resetsAt: string | null;
25
+ /** Window length in minutes as stated by the provider, or null. */
26
+ windowMinutes: number | null;
27
+ /** Short human note for this window (e.g. "no active window"), or null. */
28
+ note: string | null;
29
+ }
30
+ /**
31
+ * ok - at least one window with data.
32
+ * no_data - provider reachable but it reported no allowance right now.
33
+ * auth - credentials missing or expired; `message` says how to fix.
34
+ * unavailable - the provider has no usable source on this machine right now.
35
+ * error - unexpected failure; `message` carries a short reason.
36
+ */
37
+ export type QuotaStatus = 'ok' | 'no_data' | 'auth' | 'unavailable' | 'error';
38
+ export interface ProviderQuota {
39
+ provider: QuotaProviderId;
40
+ status: QuotaStatus;
41
+ /** Plan / tier name as reported by the provider, or null. */
42
+ plan: string | null;
43
+ /**
44
+ * Only windows the provider actually reported. A kind missing from this
45
+ * array means the provider did not report that window (for example the
46
+ * plan has no 5-hour window). At most one entry per kind.
47
+ */
48
+ windows: QuotaWindow[];
49
+ /** live = asked the provider just now; cached = read from a local file. */
50
+ source: 'live' | 'cached';
51
+ /** ISO-8601 UTC instant the numbers were observed by the provider/file. */
52
+ observedAt: string;
53
+ /** Short single-line explanation for any non-ok status, else null. */
54
+ message: string | null;
55
+ /**
56
+ * Extra limits that apply to only part of the plan (for example a weekly
57
+ * limit for one model family). Omitted or empty when the provider reports none.
58
+ */
59
+ scopedLimits?: ScopedLimit[];
60
+ }
61
+ /** A limit that applies to a subset of the plan, e.g. one model family. */
62
+ export interface ScopedLimit {
63
+ /** What the limit applies to, as the provider names it (e.g. "Fable"). */
64
+ scope: string;
65
+ kind: QuotaWindowKind;
66
+ /** Percent used, 0-100 scale, or null when not reported. */
67
+ usedPercent: number | null;
68
+ /** ISO-8601 UTC reset instant, or null. */
69
+ resetsAt: string | null;
70
+ }
71
+ export interface QuotaFetchOptions {
72
+ /** Hard cap for the whole fetch of one provider, in milliseconds. */
73
+ timeoutMs: number;
74
+ /** Clock, injectable for tests. */
75
+ now: () => Date;
76
+ /** Home directory to resolve provider state under (os.homedir() in prod). */
77
+ homeDir: string;
78
+ }
79
+ /**
80
+ * Every provider module exports one of these. It MUST NOT throw or reject:
81
+ * any failure becomes a ProviderQuota with a non-ok status. It MUST NOT
82
+ * print anything, and MUST NOT include secrets in `message`.
83
+ */
84
+ export type QuotaFetcher = (options: QuotaFetchOptions) => Promise<ProviderQuota>;
85
+ /** Signature of the table renderer in format.ts. Returns text without a trailing newline. */
86
+ export type QuotaTableFormatter = (rows: readonly ProviderQuota[], now: Date) => string;
87
+ /**
88
+ * available - enough quota left for normal work.
89
+ * low - usable, but close to its limit or burning faster than the window allows.
90
+ * exhausted - effectively no quota left until the window resets.
91
+ * unknown - no usable numbers (provider status not ok, or no numeric window).
92
+ */
93
+ export type QuotaAvailability = 'available' | 'low' | 'exhausted' | 'unknown';
94
+ export interface ProviderRouting {
95
+ provider: QuotaProviderId;
96
+ availability: QuotaAvailability;
97
+ /**
98
+ * Percent LEFT (0-100) in the binding window: the window with the least
99
+ * left among those that have a number. null when unknown.
100
+ */
101
+ leftPercent: number | null;
102
+ /** Which window is the binding one, or null when unknown. */
103
+ bindingWindow: QuotaWindowKind | null;
104
+ /** When the binding window resets (ISO-8601 UTC), or null. */
105
+ resetsAt: string | null;
106
+ /** Whole minutes until the binding window resets (>= 0), or null. */
107
+ resetsInMinutes: number | null;
108
+ /**
109
+ * Quota left relative to time left in the binding window:
110
+ * (leftPercent / 100) / (minutes until reset / windowMinutes).
111
+ * 1 = exactly on pace, > 1 = spare capacity, < 1 = will run out before the
112
+ * reset at an even pace. null when the window length or reset is unknown.
113
+ */
114
+ runway: number | null;
115
+ /** One short sentence an agent can read to understand the verdict. */
116
+ reason: string;
117
+ }
118
+ /** The `--json` output of ftown-quota. */
119
+ export interface QuotaReport {
120
+ /** ISO-8601 UTC instant the report was produced. */
121
+ generatedAt: string;
122
+ /** Raw per-provider readings, in canonical provider order. */
123
+ providers: ProviderQuota[];
124
+ /** Routing advice, best provider to use FIRST. Same providers as above. */
125
+ routing: ProviderRouting[];
126
+ /** Models each provider offers, in canonical provider order. */
127
+ models: ProviderModels[];
128
+ /** State of the user's model-routing file on this machine. */
129
+ routingConfig: RoutingConfigState;
130
+ /** The user's tiers, each pick annotated with live availability. Empty unless routingConfig.status is "ok". */
131
+ tiers: AnnotatedTier[];
132
+ /** The user's free-text guidance for the router agent, passed through verbatim ("" when none). */
133
+ steering: string;
134
+ }
135
+ /** Signature of the advisor in routing.ts. Returns advice sorted best-first. */
136
+ export type QuotaRoutingAdvisor = (rows: readonly ProviderQuota[], now: Date) => ProviderRouting[];
137
+ /**
138
+ * What a model costs the logged-in account. free = zero-priced on a
139
+ * pay-per-use provider; included = covered by a subscription plan the user
140
+ * holds; paid = billed per use.
141
+ */
142
+ export type ModelPricing = 'free' | 'included' | 'paid';
143
+ export interface HarnessModel {
144
+ /** Exact value to pass to the harness's model flag. */
145
+ id: string;
146
+ /** Human display name, or null. */
147
+ name: string | null;
148
+ /** True for the model a new session uses when no model flag is given. */
149
+ isDefault: boolean;
150
+ /** Provider's own one-line description, or null. */
151
+ description: string | null;
152
+ /** Largest context window in tokens the provider states, or null. */
153
+ contextTokens: number | null;
154
+ /** Reasoning-effort levels the model supports, in the provider's order. Empty when none/unknown. */
155
+ efforts: string[];
156
+ /** Effort used when none is given, or null when unknown. */
157
+ defaultEffort: string | null;
158
+ /** Cost class when the harness states prices; omitted when unknown or not applicable. */
159
+ pricing?: ModelPricing;
160
+ }
161
+ export interface ProviderModels {
162
+ provider: QuotaProviderId;
163
+ /** ok = list read; unavailable = no source on this machine; error = unexpected failure. */
164
+ status: 'ok' | 'unavailable' | 'error';
165
+ /** Models a user can select (hidden/internal ones excluded). Empty unless status is ok. */
166
+ models: HarnessModel[];
167
+ /** live = asked the harness just now; cached = read from a file the harness maintains. */
168
+ source: 'live' | 'cached';
169
+ /** ISO-8601 UTC instant the list was produced or last refreshed by the harness. */
170
+ observedAt: string;
171
+ /** Short single-line explanation for any non-ok status, else null. */
172
+ message: string | null;
173
+ /** How to select a model when launching the harness, e.g. "--model <id>". */
174
+ modelFlag: string;
175
+ /** How to select a reasoning effort when launching, e.g. "--effort <level>", or null. */
176
+ effortFlag: string | null;
177
+ }
178
+ /**
179
+ * Every models module exports one of these. Same rules as QuotaFetcher:
180
+ * never throw or reject, never print, no secrets in `message`, never start
181
+ * a model turn.
182
+ */
183
+ export type ModelsFetcher = (options: QuotaFetchOptions) => Promise<ProviderModels>;
184
+ /** Location of the routing file, relative to the home directory. */
185
+ export declare const MODEL_ROUTING_RELATIVE_PATH = ".ftown/model-routing.json";
186
+ /** Limits enforced when validating a routing config. */
187
+ export declare const MODEL_ROUTING_LIMITS: {
188
+ readonly maxTiers: 12;
189
+ readonly maxPicksPerTier: 12;
190
+ readonly maxTierNameLength: 40;
191
+ readonly maxTierDescriptionLength: 200;
192
+ readonly maxTierGuidanceLength: 1000;
193
+ readonly maxSteeringLength: 4000;
194
+ };
195
+ /** Tier names are lowercase slugs so a router agent can refer to them unambiguously. */
196
+ export declare const ROUTING_TIER_NAME_PATTERN: RegExp;
197
+ export interface RoutingPick {
198
+ /** Harness CLI. */
199
+ provider: QuotaProviderId;
200
+ /** Model id exactly as that harness's model list gives it. */
201
+ model: string;
202
+ /** Reasoning effort to request, or null to use the harness default. */
203
+ effort: string | null;
204
+ }
205
+ export interface RoutingTier {
206
+ /** Unique within the config; matches ROUTING_TIER_NAME_PATTERN. */
207
+ name: string;
208
+ /** What kind of work belongs in this tier, in the user's words ("" when none). */
209
+ description: string;
210
+ /** Preferred entries, most preferred first. May be empty. */
211
+ picks: RoutingPick[];
212
+ /**
213
+ * Free-text instruction for this tier, interpreted by the router agent and
214
+ * never by the tool, e.g. "otherwise any free opencode model" ("" when none).
215
+ */
216
+ guidance: string;
217
+ }
218
+ export interface ModelRoutingConfig {
219
+ version: 1;
220
+ /** In the user's order. */
221
+ tiers: RoutingTier[];
222
+ /** Free-text guidance for the router agent, e.g. "don't fully exhaust Claude". */
223
+ steering: string;
224
+ }
225
+ /** The tiers offered when no routing file exists yet. Picks start empty: the user fills them in. */
226
+ export declare const DEFAULT_ROUTING_TIERS: readonly Readonly<Pick<RoutingTier, 'name' | 'description'>>[];
227
+ export interface RoutingConfigState {
228
+ /** ok = loaded; missing = no file yet; invalid = the file has errors. */
229
+ status: 'ok' | 'missing' | 'invalid';
230
+ /** Absolute path of the routing file. */
231
+ path: string;
232
+ /** Validation errors when invalid, else []. */
233
+ errors: string[];
234
+ }
235
+ /** Result of validating a routing config document. Never a partially valid config. */
236
+ export type RoutingConfigParseResult = {
237
+ ok: true;
238
+ config: ModelRoutingConfig;
239
+ } | {
240
+ ok: false;
241
+ errors: string[];
242
+ };
243
+ export interface AnnotatedPick extends RoutingPick {
244
+ /** Display name of the model from the live list, or null. */
245
+ modelName: string | null;
246
+ /** The provider's current availability (from the routing advice). */
247
+ availability: QuotaAvailability;
248
+ /** Percent left in the provider's binding window, or null. */
249
+ leftPercent: number | null;
250
+ /** When that binding window resets (ISO-8601 UTC), or null. */
251
+ resetsAt: string | null;
252
+ /** The provider's partial limits (e.g. per model family), for the router to weigh. */
253
+ scopedLimits: ScopedLimit[];
254
+ /**
255
+ * Non-null when the pick cannot be used as configured: the model is no
256
+ * longer offered by the harness, or the effort is not one it supports.
257
+ * Null when the pick is valid or could not be checked (no model list).
258
+ */
259
+ problem: string | null;
260
+ /** false when the harness's model list could not be read, so the pick was not checked. */
261
+ checked: boolean;
262
+ }
263
+ export interface AnnotatedTier {
264
+ name: string;
265
+ description: string;
266
+ /** In the user's order of preference. */
267
+ picks: AnnotatedPick[];
268
+ /** The tier's free-text guidance, verbatim. */
269
+ guidance: string;
270
+ }
271
+ export declare const MODEL_ROUTING_SCHEMA = 1;
272
+ /** A `refresh: true` request arriving sooner than this after the last collection is served from cache. */
273
+ export declare const MODEL_ROUTING_MIN_REFRESH_MS = 15000;
274
+ /** Payload of the `get_model_routing` command. */
275
+ export interface GetModelRoutingPayload {
276
+ /** true = bypass the bridge's cache and read quota and model lists again. */
277
+ refresh?: boolean;
278
+ }
279
+ /** Result of `get_model_routing`. */
280
+ export interface ModelRoutingSnapshot {
281
+ schema: 1;
282
+ /** The stored config, or a starter config (default tiers, no picks, no steering) when none is stored or it is invalid. */
283
+ config: ModelRoutingConfig;
284
+ /** Full report: quota, routing advice, model lists, and the annotated tiers. */
285
+ report: QuotaReport;
286
+ /** true when the quota/model part of `report` came from the bridge's cache. */
287
+ cached: boolean;
288
+ }
289
+ /** Payload of the `set_model_routing` command. */
290
+ export interface SetModelRoutingPayload {
291
+ config: ModelRoutingConfig;
292
+ /** Must be true to replace a routing file that exists but is invalid. */
293
+ overwriteInvalid?: boolean;
294
+ }
295
+ /** Result of `set_model_routing`. */
296
+ export interface SetModelRoutingResult {
297
+ schema: 1;
298
+ /** false when validation failed; nothing was written. */
299
+ saved: boolean;
300
+ /** Validation errors, each a short sentence naming the offending field; [] when saved. */
301
+ errors: string[];
302
+ /** true when nothing was written because the existing file is invalid and overwriteInvalid was not set. */
303
+ invalidFileBlocked?: boolean;
304
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Frozen contract for the subscription-quota tool (`ftown-quota`).
3
+ *
4
+ * "Quota" here means SUBSCRIPTION PLAN limits (the short ~5-hour window and
5
+ * the weekly window), not per-session token counts (see usage-collector.ts).
6
+ *
7
+ * This file is types only. Do not add logic and do not edit it from a task.
8
+ */
9
+ export const QUOTA_PROVIDER_IDS = ['claude', 'codex', 'grok', 'muse', 'agy', 'opencode'];
10
+ // ---------------------------------------------------------------------------
11
+ // Model routing: the user's own tiers. For each tier the user picks, in order
12
+ // of preference, concrete (harness CLI + model + effort) entries on a
13
+ // per-bridge screen in the dashboard, plus free-text steering. The tool never
14
+ // ranks models itself and never chooses: it joins the picks with live quota so
15
+ // that a ROUTER AGENT can choose the CLI, model and effort for a given task.
16
+ // Stored on the bridge machine; one file per machine.
17
+ // ---------------------------------------------------------------------------
18
+ /** Location of the routing file, relative to the home directory. */
19
+ export const MODEL_ROUTING_RELATIVE_PATH = '.ftown/model-routing.json';
20
+ /** Limits enforced when validating a routing config. */
21
+ export const MODEL_ROUTING_LIMITS = {
22
+ maxTiers: 12,
23
+ maxPicksPerTier: 12,
24
+ maxTierNameLength: 40,
25
+ maxTierDescriptionLength: 200,
26
+ maxTierGuidanceLength: 1000,
27
+ maxSteeringLength: 4000,
28
+ };
29
+ /** Tier names are lowercase slugs so a router agent can refer to them unambiguously. */
30
+ export const ROUTING_TIER_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
31
+ /** The tiers offered when no routing file exists yet. Picks start empty: the user fills them in. */
32
+ export const DEFAULT_ROUTING_TIERS = [
33
+ { name: 'advisory', description: 'Planning, architecture, review and second opinions' },
34
+ { name: 'complex', description: 'Hard development and debugging' },
35
+ { name: 'moderate', description: 'Ordinary development tasks' },
36
+ { name: 'simple', description: 'Research, lookups and mechanical edits' },
37
+ ];
38
+ // ---------------------------------------------------------------------------
39
+ // Dashboard <-> bridge protocol for model routing (bridge command RPC).
40
+ // The dashboard imports these types from this file directly; do not copy them.
41
+ // ---------------------------------------------------------------------------
42
+ export const MODEL_ROUTING_SCHEMA = 1;
43
+ /** A `refresh: true` request arriving sooner than this after the last collection is served from cache. */
44
+ export const MODEL_ROUTING_MIN_REFRESH_MS = 15_000;
45
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/quota/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,CAAC,MAAM,kBAAkB,GAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;AAwMrH,8EAA8E;AAC9E,8EAA8E;AAC9E,sEAAsE;AACtE,8EAA8E;AAC9E,+EAA+E;AAC/E,6EAA6E;AAC7E,sDAAsD;AACtD,8EAA8E;AAE9E,oEAAoE;AACpE,MAAM,CAAC,MAAM,2BAA2B,GAAG,2BAA2B,CAAC;AAEvE,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE,EAAE;IACnB,iBAAiB,EAAE,EAAE;IACrB,wBAAwB,EAAE,GAAG;IAC7B,qBAAqB,EAAE,IAAI;IAC3B,iBAAiB,EAAE,IAAI;CACf,CAAC;AAEX,wFAAwF;AACxF,MAAM,CAAC,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAiCjE,oGAAoG;AACpG,MAAM,CAAC,MAAM,qBAAqB,GAAmE;IACnG,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,oDAAoD,EAAE;IACvF,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,gCAAgC,EAAE;IAClE,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B,EAAE;IAC/D,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wCAAwC,EAAE;CAC1E,CAAC;AA8CF,8EAA8E;AAC9E,wEAAwE;AACxE,+EAA+E;AAC/E,8EAA8E;AAE9E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAEtC,0GAA0G;AAC1G,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC"}
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import { type AnnotatedTier, type ModelRoutingConfig, type ModelsFetcher, type ProviderModels, type ProviderQuota, type ProviderRouting, type QuotaFetcher, type QuotaProviderId, type QuotaReport, type QuotaRoutingAdvisor, type QuotaTableFormatter, type RoutingConfigState } from './quota/types.js';
3
+ export declare const QUOTA_DEFAULT_TIMEOUT_SECONDS = 15;
4
+ /** Allowed --timeout range in seconds; keeps timeoutMs + grace well inside the timer limit. */
5
+ export declare const QUOTA_MIN_TIMEOUT_SECONDS = 1;
6
+ export declare const QUOTA_MAX_TIMEOUT_SECONDS = 600;
7
+ /** Extra time past the provider timeout before the CLI gives up on a fetcher that never settles. */
8
+ export declare const QUOTA_GRACE_MS = 2000;
9
+ export declare const QUOTA_HELP_TEXT: string;
10
+ /** Table mode shows at most this many models per provider; JSON is never truncated. */
11
+ export declare const MODEL_LIST_TABLE_LIMIT = 20;
12
+ /** `default`: models in JSON only; `show`: models in table mode too; `skip`: no model fetchers at all. */
13
+ export type QuotaModelsMode = 'default' | 'show' | 'skip';
14
+ export type QuotaArgs = {
15
+ ok: true;
16
+ providers: QuotaProviderId[];
17
+ json: boolean;
18
+ timeoutMs: number;
19
+ help: boolean;
20
+ models: QuotaModelsMode;
21
+ } | {
22
+ ok: false;
23
+ error: string;
24
+ };
25
+ /** Reads the user's routing file; see quota/routing-config.ts. */
26
+ export type RoutingLoader = (homeDir: string) => Promise<{
27
+ state: RoutingConfigState;
28
+ config: ModelRoutingConfig | null;
29
+ }>;
30
+ /** Joins the user's tiers with live quota, routing advice and model lists; see quota/annotate.ts. */
31
+ export type TierAnnotator = (config: ModelRoutingConfig, quota: readonly ProviderQuota[], routing: readonly ProviderRouting[], models: readonly ProviderModels[]) => AnnotatedTier[];
32
+ export interface QuotaDeps {
33
+ fetchers: Record<QuotaProviderId, QuotaFetcher>;
34
+ modelFetchers: Record<QuotaProviderId, ModelsFetcher>;
35
+ advise: QuotaRoutingAdvisor;
36
+ format: QuotaTableFormatter;
37
+ loadRouting: RoutingLoader;
38
+ annotate: TierAnnotator;
39
+ now: () => Date;
40
+ homeDir: string;
41
+ /** Grace past the provider timeout before a hung fetcher becomes an error entry. Default QUOTA_GRACE_MS. */
42
+ graceMs?: number;
43
+ }
44
+ export interface QuotaRunResult {
45
+ exitCode: number;
46
+ stdout: string;
47
+ stderr: string;
48
+ /** True when at least one quota or model fetcher did not settle in time and was replaced. */
49
+ hung: boolean;
50
+ }
51
+ export interface CollectQuotaInput {
52
+ providers: QuotaProviderId[];
53
+ timeoutMs: number;
54
+ /** Put the model lists in the report. */
55
+ includeModels: boolean;
56
+ /** Also fetch model lists when the routing config has picks, so the tiers can be checked against them. */
57
+ fetchModelsForRouting?: boolean;
58
+ }
59
+ export interface CollectedQuotaReport {
60
+ report: QuotaReport;
61
+ hung: boolean;
62
+ /** Short stderr lines for non-fatal problems (routing advice, routing file). */
63
+ warnings: string[];
64
+ }
65
+ export declare function parseQuotaArgs(argv: string[]): QuotaArgs;
66
+ /**
67
+ * Gathers everything the report needs: guarded quota and model fetches, routing
68
+ * advice, and the annotated routing tiers. Never prints and never exits.
69
+ */
70
+ export declare function collectQuotaReport(input: CollectQuotaInput, deps: QuotaDeps): Promise<CollectedQuotaReport>;
71
+ export declare function runQuota(argv: string[], deps: QuotaDeps): Promise<QuotaRunResult>;
72
+ /** The real fetchers, models, routing, formatter and clock: what the bridge passes to collectQuotaReport. */
73
+ export declare function loadRealQuotaDeps(): Promise<QuotaDeps>;
74
+ /**
75
+ * Entry for `ftown-quota` and the umbrella `ftown` CLI: runs with the real
76
+ * modules and writes stdout/stderr. Returns the exit code, except when a
77
+ * fetcher hung: then the process exits with that code after output is flushed,
78
+ * because a hung fetcher's handle would otherwise keep the event loop alive.
79
+ */
80
+ export declare function runQuotaCli(argv: string[]): Promise<number>;