@jameslovespancakes/pi-plus 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +190 -190
- package/config/pi-plus.example.json +60 -60
- package/config/skills/model-routing/SKILL.md +86 -86
- package/images/pi-plus.svg +10 -10
- package/package.json +67 -67
- package/server/board-server.mjs +641 -641
- package/server/package.json +17 -17
- package/src/core/accounts/registry.ts +93 -93
- package/src/core/anthropic/client-identity.ts +241 -241
- package/src/core/catalog/quality.ts +314 -314
- package/src/core/config.ts +169 -169
- package/src/core/env.ts +58 -58
- package/src/core/exec/process.ts +146 -146
- package/src/core/exec/ssh-config.ts +157 -157
- package/src/core/policy/policy.ts +183 -183
- package/src/core/quota/pool.ts +64 -64
- package/src/core/quota/usage-source.ts +289 -289
- package/src/core/store.ts +43 -43
- package/src/domains/agents/board-setup.ts +409 -409
- package/src/domains/agents/index.ts +462 -462
- package/src/domains/models/catalog-tool.ts +361 -361
- package/src/domains/models/index.ts +14 -14
- package/src/domains/models/policy-gate.ts +169 -169
- package/src/domains/models/provider-picker.ts +207 -207
- package/src/domains/remote/config-path.ts +41 -41
- package/src/domains/remote/index.ts +866 -866
- package/src/domains/remote/setup.ts +425 -425
- package/src/domains/setup/index.ts +220 -220
- package/src/domains/subscriptions/accounts.ts +242 -242
- package/src/domains/subscriptions/footer.ts +182 -182
- package/src/domains/subscriptions/index.ts +42 -42
- package/src/domains/subscriptions/provider.ts +219 -219
- package/src/domains/subscriptions/providers/anthropic.ts +149 -149
- package/src/domains/subscriptions/providers/codex.ts +148 -148
- package/src/domains/subscriptions/routing.ts +72 -72
- package/src/services/usage-service.ts +186 -186
- package/src/ui/format.ts +73 -73
- package/src/ui/usage-bars.ts +154 -154
- package/src/vendor/anthropic.ts +109 -109
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { refreshUsage, startPolling, stopPolling, subscribe, usageState } from "../../services/usage-service.ts";
|
|
4
|
-
import { renderUsageLines, usageSummaryText } from "../../ui/usage-bars.ts";
|
|
5
|
-
import { formatTokens, sanitize } from "../../ui/format.ts";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Single-line session footer (no working-directory line) with the subscription
|
|
9
|
-
* bars rendered directly underneath it.
|
|
10
|
-
*
|
|
11
|
-
* The footer no longer owns the poll loop; it subscribes to usage-service and
|
|
12
|
-
* re-renders on change. That is what lets non-UI consumers get fresh data.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const SUBSCRIPTION_PROVIDERS = new Set(["anthropic", "openai-codex", "kimi-coding"]);
|
|
16
|
-
|
|
17
|
-
export function registerFooter(pi: ExtensionAPI): void {
|
|
18
|
-
let showUsage = true;
|
|
19
|
-
let requestRender: (() => void) | undefined;
|
|
20
|
-
let unsubscribe: (() => void) | undefined;
|
|
21
|
-
|
|
22
|
-
const apply = (ctx: any) => {
|
|
23
|
-
if (!ctx.hasUI) return;
|
|
24
|
-
if (!showUsage) {
|
|
25
|
-
ctx.ui.setFooter(undefined);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
ctx.ui.setFooter((tui: any, theme: any, footerData: any) => {
|
|
30
|
-
requestRender = () => tui.requestRender();
|
|
31
|
-
return {
|
|
32
|
-
invalidate() {},
|
|
33
|
-
render(rawWidth: number): string[] {
|
|
34
|
-
const width = Math.max(1, Math.floor(Number(rawWidth) || 0));
|
|
35
|
-
const lines: string[] = [];
|
|
36
|
-
|
|
37
|
-
let input = 0;
|
|
38
|
-
let output = 0;
|
|
39
|
-
let cacheRead = 0;
|
|
40
|
-
let cacheWrite = 0;
|
|
41
|
-
let cost = 0;
|
|
42
|
-
let latestHitRate: number | undefined;
|
|
43
|
-
|
|
44
|
-
for (const entry of ctx.sessionManager.getEntries()) {
|
|
45
|
-
const usage = entry.type === "message"
|
|
46
|
-
? (entry.message.role === "assistant" || entry.message.role === "toolResult" ? entry.message.usage : undefined)
|
|
47
|
-
: (entry.type === "branch_summary" || entry.type === "compaction" ? entry.usage : undefined);
|
|
48
|
-
if (!usage) continue;
|
|
49
|
-
input += usage.input ?? 0;
|
|
50
|
-
output += usage.output ?? 0;
|
|
51
|
-
cacheRead += usage.cacheRead ?? 0;
|
|
52
|
-
cacheWrite += usage.cacheWrite ?? 0;
|
|
53
|
-
cost += usage.cost?.total ?? 0;
|
|
54
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
55
|
-
const prompt = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
56
|
-
latestHitRate = prompt > 0 ? ((usage.cacheRead ?? 0) / prompt) * 100 : undefined;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const parts: string[] = [];
|
|
61
|
-
if (input) parts.push(`↑${formatTokens(input)}`);
|
|
62
|
-
if (output) parts.push(`↓${formatTokens(output)}`);
|
|
63
|
-
if (cacheRead) parts.push(`R${formatTokens(cacheRead)}`);
|
|
64
|
-
if (cacheWrite) parts.push(`W${formatTokens(cacheWrite)}`);
|
|
65
|
-
if ((cacheRead || cacheWrite) && latestHitRate !== undefined) parts.push(`CH${latestHitRate.toFixed(1)}%`);
|
|
66
|
-
|
|
67
|
-
const model = ctx.model;
|
|
68
|
-
const subscription = model ? SUBSCRIPTION_PROVIDERS.has(model.provider) : false;
|
|
69
|
-
if (cost || subscription) parts.push(`$${cost.toFixed(3)}${subscription ? " (sub)" : ""}`);
|
|
70
|
-
|
|
71
|
-
const contextUsage = ctx.getContextUsage();
|
|
72
|
-
const contextWindow = contextUsage?.contextWindow ?? model?.contextWindow ?? 0;
|
|
73
|
-
const percentValue = contextUsage?.percent ?? 0;
|
|
74
|
-
const percentText = contextUsage?.percent != null ? `${percentValue.toFixed(1)}%` : "?";
|
|
75
|
-
const contextText = `${percentText}/${formatTokens(contextWindow)}`;
|
|
76
|
-
parts.push(percentValue > 90
|
|
77
|
-
? theme.fg("error", contextText)
|
|
78
|
-
: percentValue > 70 ? theme.fg("warning", contextText) : contextText);
|
|
79
|
-
|
|
80
|
-
const branch = footerData.getGitBranch?.();
|
|
81
|
-
if (branch) parts.push(`⎇ ${branch}`);
|
|
82
|
-
|
|
83
|
-
let left = parts.join(" ");
|
|
84
|
-
if (visibleWidth(left) > width) left = truncateToWidth(left, width, "...");
|
|
85
|
-
let leftWidth = visibleWidth(left);
|
|
86
|
-
|
|
87
|
-
let right = model?.id ?? "no-model";
|
|
88
|
-
if (model?.reasoning) {
|
|
89
|
-
const level = ctx.thinkingLevel || "off";
|
|
90
|
-
right = level === "off" ? `${right} • thinking off` : `${right} • ${level}`;
|
|
91
|
-
}
|
|
92
|
-
if (footerData.getAvailableProviderCount?.() > 1 && model) {
|
|
93
|
-
const withProvider = `(${model.provider}) ${right}`;
|
|
94
|
-
if (leftWidth + 2 + visibleWidth(withProvider) <= width) right = withProvider;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Keep the composed line within `width`: shrink right, then left.
|
|
98
|
-
let rightWidth = visibleWidth(right);
|
|
99
|
-
if (rightWidth > width) {
|
|
100
|
-
right = truncateToWidth(right, width, "...");
|
|
101
|
-
rightWidth = visibleWidth(right);
|
|
102
|
-
}
|
|
103
|
-
if (leftWidth + 2 + rightWidth > width) {
|
|
104
|
-
const leftBudget = Math.max(0, width - rightWidth - 2);
|
|
105
|
-
left = leftBudget > 0 ? truncateToWidth(left, leftBudget, "...") : "";
|
|
106
|
-
leftWidth = visibleWidth(left);
|
|
107
|
-
}
|
|
108
|
-
const padding = " ".repeat(Math.max(0, width - leftWidth - rightWidth));
|
|
109
|
-
lines.push(truncateToWidth(theme.fg("dim", left) + theme.fg("dim", padding + right), width, ""));
|
|
110
|
-
|
|
111
|
-
const statuses = footerData.getExtensionStatuses?.() as Map<string, string> | undefined;
|
|
112
|
-
if (statuses && statuses.size > 0) {
|
|
113
|
-
const statusLine = Array.from(statuses.entries())
|
|
114
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
115
|
-
.map(([, text]) => sanitize(text))
|
|
116
|
-
.join(" ");
|
|
117
|
-
lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (showUsage) {
|
|
121
|
-
lines.push(...renderUsageLines(
|
|
122
|
-
usageState(),
|
|
123
|
-
theme,
|
|
124
|
-
width,
|
|
125
|
-
model?.provider === "anthropic" ? model.id : undefined,
|
|
126
|
-
));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Final safety net: never emit a line wider than the terminal.
|
|
130
|
-
return lines.map((line) => (visibleWidth(line) > width ? truncateToWidth(line, width, "") : line));
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
});
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
137
|
-
apply(ctx);
|
|
138
|
-
unsubscribe ??= subscribe(() => requestRender?.());
|
|
139
|
-
// Polling is started even without a UI so headless consumers stay current.
|
|
140
|
-
startPolling(ctx);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
pi.on("model_select", async (_event, ctx) => {
|
|
144
|
-
apply(ctx);
|
|
145
|
-
requestRender?.();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
pi.on("agent_settled", async (_event, ctx) => {
|
|
149
|
-
// Status polling is cosmetic: never hold up agent completion on auth/network.
|
|
150
|
-
if (ctx.hasUI && showUsage) void refreshUsage(ctx);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
pi.on("session_shutdown", async () => {
|
|
154
|
-
unsubscribe?.();
|
|
155
|
-
unsubscribe = undefined;
|
|
156
|
-
stopPolling();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
pi.registerCommand("usage", {
|
|
160
|
-
description: "Refresh, hide, or show the subscription bars (on | off | text)",
|
|
161
|
-
getArgumentCompletions: (prefix) =>
|
|
162
|
-
["on", "off", "text"]
|
|
163
|
-
.filter((option) => option.startsWith(prefix))
|
|
164
|
-
.map((option) => ({ value: option, label: option })),
|
|
165
|
-
handler: async (args, ctx) => {
|
|
166
|
-
const action = args.trim().toLowerCase();
|
|
167
|
-
if (action === "off") {
|
|
168
|
-
showUsage = false;
|
|
169
|
-
apply(ctx);
|
|
170
|
-
requestRender?.();
|
|
171
|
-
ctx.ui.notify("Usage bars hidden. Use /usage on to restore them.", "info");
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (action === "on") showUsage = true;
|
|
175
|
-
await refreshUsage(ctx, true);
|
|
176
|
-
apply(ctx);
|
|
177
|
-
if (action === "text" || !ctx.hasUI) ctx.ui.notify(usageSummaryText(usageState()), "info");
|
|
178
|
-
else if (usageState().errors.length > 0) ctx.ui.notify(usageState().errors.join("\n"), "warning");
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import { refreshUsage, startPolling, stopPolling, subscribe, usageState } from "../../services/usage-service.ts";
|
|
4
|
+
import { renderUsageLines, usageSummaryText } from "../../ui/usage-bars.ts";
|
|
5
|
+
import { formatTokens, sanitize } from "../../ui/format.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Single-line session footer (no working-directory line) with the subscription
|
|
9
|
+
* bars rendered directly underneath it.
|
|
10
|
+
*
|
|
11
|
+
* The footer no longer owns the poll loop; it subscribes to usage-service and
|
|
12
|
+
* re-renders on change. That is what lets non-UI consumers get fresh data.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SUBSCRIPTION_PROVIDERS = new Set(["anthropic", "openai-codex", "kimi-coding"]);
|
|
16
|
+
|
|
17
|
+
export function registerFooter(pi: ExtensionAPI): void {
|
|
18
|
+
let showUsage = true;
|
|
19
|
+
let requestRender: (() => void) | undefined;
|
|
20
|
+
let unsubscribe: (() => void) | undefined;
|
|
21
|
+
|
|
22
|
+
const apply = (ctx: any) => {
|
|
23
|
+
if (!ctx.hasUI) return;
|
|
24
|
+
if (!showUsage) {
|
|
25
|
+
ctx.ui.setFooter(undefined);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
ctx.ui.setFooter((tui: any, theme: any, footerData: any) => {
|
|
30
|
+
requestRender = () => tui.requestRender();
|
|
31
|
+
return {
|
|
32
|
+
invalidate() {},
|
|
33
|
+
render(rawWidth: number): string[] {
|
|
34
|
+
const width = Math.max(1, Math.floor(Number(rawWidth) || 0));
|
|
35
|
+
const lines: string[] = [];
|
|
36
|
+
|
|
37
|
+
let input = 0;
|
|
38
|
+
let output = 0;
|
|
39
|
+
let cacheRead = 0;
|
|
40
|
+
let cacheWrite = 0;
|
|
41
|
+
let cost = 0;
|
|
42
|
+
let latestHitRate: number | undefined;
|
|
43
|
+
|
|
44
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
45
|
+
const usage = entry.type === "message"
|
|
46
|
+
? (entry.message.role === "assistant" || entry.message.role === "toolResult" ? entry.message.usage : undefined)
|
|
47
|
+
: (entry.type === "branch_summary" || entry.type === "compaction" ? entry.usage : undefined);
|
|
48
|
+
if (!usage) continue;
|
|
49
|
+
input += usage.input ?? 0;
|
|
50
|
+
output += usage.output ?? 0;
|
|
51
|
+
cacheRead += usage.cacheRead ?? 0;
|
|
52
|
+
cacheWrite += usage.cacheWrite ?? 0;
|
|
53
|
+
cost += usage.cost?.total ?? 0;
|
|
54
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
55
|
+
const prompt = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
56
|
+
latestHitRate = prompt > 0 ? ((usage.cacheRead ?? 0) / prompt) * 100 : undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const parts: string[] = [];
|
|
61
|
+
if (input) parts.push(`↑${formatTokens(input)}`);
|
|
62
|
+
if (output) parts.push(`↓${formatTokens(output)}`);
|
|
63
|
+
if (cacheRead) parts.push(`R${formatTokens(cacheRead)}`);
|
|
64
|
+
if (cacheWrite) parts.push(`W${formatTokens(cacheWrite)}`);
|
|
65
|
+
if ((cacheRead || cacheWrite) && latestHitRate !== undefined) parts.push(`CH${latestHitRate.toFixed(1)}%`);
|
|
66
|
+
|
|
67
|
+
const model = ctx.model;
|
|
68
|
+
const subscription = model ? SUBSCRIPTION_PROVIDERS.has(model.provider) : false;
|
|
69
|
+
if (cost || subscription) parts.push(`$${cost.toFixed(3)}${subscription ? " (sub)" : ""}`);
|
|
70
|
+
|
|
71
|
+
const contextUsage = ctx.getContextUsage();
|
|
72
|
+
const contextWindow = contextUsage?.contextWindow ?? model?.contextWindow ?? 0;
|
|
73
|
+
const percentValue = contextUsage?.percent ?? 0;
|
|
74
|
+
const percentText = contextUsage?.percent != null ? `${percentValue.toFixed(1)}%` : "?";
|
|
75
|
+
const contextText = `${percentText}/${formatTokens(contextWindow)}`;
|
|
76
|
+
parts.push(percentValue > 90
|
|
77
|
+
? theme.fg("error", contextText)
|
|
78
|
+
: percentValue > 70 ? theme.fg("warning", contextText) : contextText);
|
|
79
|
+
|
|
80
|
+
const branch = footerData.getGitBranch?.();
|
|
81
|
+
if (branch) parts.push(`⎇ ${branch}`);
|
|
82
|
+
|
|
83
|
+
let left = parts.join(" ");
|
|
84
|
+
if (visibleWidth(left) > width) left = truncateToWidth(left, width, "...");
|
|
85
|
+
let leftWidth = visibleWidth(left);
|
|
86
|
+
|
|
87
|
+
let right = model?.id ?? "no-model";
|
|
88
|
+
if (model?.reasoning) {
|
|
89
|
+
const level = ctx.thinkingLevel || "off";
|
|
90
|
+
right = level === "off" ? `${right} • thinking off` : `${right} • ${level}`;
|
|
91
|
+
}
|
|
92
|
+
if (footerData.getAvailableProviderCount?.() > 1 && model) {
|
|
93
|
+
const withProvider = `(${model.provider}) ${right}`;
|
|
94
|
+
if (leftWidth + 2 + visibleWidth(withProvider) <= width) right = withProvider;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Keep the composed line within `width`: shrink right, then left.
|
|
98
|
+
let rightWidth = visibleWidth(right);
|
|
99
|
+
if (rightWidth > width) {
|
|
100
|
+
right = truncateToWidth(right, width, "...");
|
|
101
|
+
rightWidth = visibleWidth(right);
|
|
102
|
+
}
|
|
103
|
+
if (leftWidth + 2 + rightWidth > width) {
|
|
104
|
+
const leftBudget = Math.max(0, width - rightWidth - 2);
|
|
105
|
+
left = leftBudget > 0 ? truncateToWidth(left, leftBudget, "...") : "";
|
|
106
|
+
leftWidth = visibleWidth(left);
|
|
107
|
+
}
|
|
108
|
+
const padding = " ".repeat(Math.max(0, width - leftWidth - rightWidth));
|
|
109
|
+
lines.push(truncateToWidth(theme.fg("dim", left) + theme.fg("dim", padding + right), width, ""));
|
|
110
|
+
|
|
111
|
+
const statuses = footerData.getExtensionStatuses?.() as Map<string, string> | undefined;
|
|
112
|
+
if (statuses && statuses.size > 0) {
|
|
113
|
+
const statusLine = Array.from(statuses.entries())
|
|
114
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
115
|
+
.map(([, text]) => sanitize(text))
|
|
116
|
+
.join(" ");
|
|
117
|
+
lines.push(truncateToWidth(statusLine, width, theme.fg("dim", "...")));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (showUsage) {
|
|
121
|
+
lines.push(...renderUsageLines(
|
|
122
|
+
usageState(),
|
|
123
|
+
theme,
|
|
124
|
+
width,
|
|
125
|
+
model?.provider === "anthropic" ? model.id : undefined,
|
|
126
|
+
));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Final safety net: never emit a line wider than the terminal.
|
|
130
|
+
return lines.map((line) => (visibleWidth(line) > width ? truncateToWidth(line, width, "") : line));
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
137
|
+
apply(ctx);
|
|
138
|
+
unsubscribe ??= subscribe(() => requestRender?.());
|
|
139
|
+
// Polling is started even without a UI so headless consumers stay current.
|
|
140
|
+
startPolling(ctx);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
pi.on("model_select", async (_event, ctx) => {
|
|
144
|
+
apply(ctx);
|
|
145
|
+
requestRender?.();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
149
|
+
// Status polling is cosmetic: never hold up agent completion on auth/network.
|
|
150
|
+
if (ctx.hasUI && showUsage) void refreshUsage(ctx);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
pi.on("session_shutdown", async () => {
|
|
154
|
+
unsubscribe?.();
|
|
155
|
+
unsubscribe = undefined;
|
|
156
|
+
stopPolling();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
pi.registerCommand("usage", {
|
|
160
|
+
description: "Refresh, hide, or show the subscription bars (on | off | text)",
|
|
161
|
+
getArgumentCompletions: (prefix) =>
|
|
162
|
+
["on", "off", "text"]
|
|
163
|
+
.filter((option) => option.startsWith(prefix))
|
|
164
|
+
.map((option) => ({ value: option, label: option })),
|
|
165
|
+
handler: async (args, ctx) => {
|
|
166
|
+
const action = args.trim().toLowerCase();
|
|
167
|
+
if (action === "off") {
|
|
168
|
+
showUsage = false;
|
|
169
|
+
apply(ctx);
|
|
170
|
+
requestRender?.();
|
|
171
|
+
ctx.ui.notify("Usage bars hidden. Use /usage on to restore them.", "info");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (action === "on") showUsage = true;
|
|
175
|
+
await refreshUsage(ctx, true);
|
|
176
|
+
apply(ctx);
|
|
177
|
+
if (action === "text" || !ctx.hasUI) ctx.ui.notify(usageSummaryText(usageState()), "info");
|
|
178
|
+
else if (usageState().errors.length > 0) ctx.ui.notify(usageState().errors.join("\n"), "warning");
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
}
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { registerAccountProvider } from "../../core/accounts/registry.ts";
|
|
3
|
-
import { anthropicAccounts } from "./providers/anthropic.ts";
|
|
4
|
-
import { codexAccounts } from "./providers/codex.ts";
|
|
5
|
-
import { registerAnthropicProvider } from "./provider.ts";
|
|
6
|
-
import { registerAccountCommands } from "./accounts.ts";
|
|
7
|
-
import { registerRoutingCommands } from "./routing.ts";
|
|
8
|
-
import { registerFooter } from "./footer.ts";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Subscriptions domain: Claude OAuth accounts, routing mode, and the quota HUD.
|
|
12
|
-
*
|
|
13
|
-
* This replaces the old `claude-multi-account.ts` + `compact-footer.ts` pair.
|
|
14
|
-
* Account and routing commands are provider-agnostic: `/account <provider>` and
|
|
15
|
-
* `/routing` dispatch through core/accounts/registry.ts, so adding a second
|
|
16
|
-
* provider means writing one adapter, not new commands.
|
|
17
|
-
* Authentication and model catalogue are separate concerns here; they were only
|
|
18
|
-
* ever colocated because they shared an OAuth token.
|
|
19
|
-
*
|
|
20
|
-
* Relationship to @cortexkit/pi-anthropic-auth:
|
|
21
|
-
* That package stays installed and keeps ownership of the Anthropic provider,
|
|
22
|
-
* its stream implementation, and its own commands (/claude-account,
|
|
23
|
-
* /claude-routing, /claude-quota, ...). Absorbing it fully was evaluated and
|
|
24
|
-
* rejected, because its `dist/commands.js` exports only `registerCommands(pi)` as a
|
|
25
|
-
* unit, so taking over individual commands would mean copying ~130 lines of
|
|
26
|
-
* provider/model specs that drift on every vendor upgrade. This domain adds
|
|
27
|
-
* the multi-account surface the vendor does not provide, and everything it
|
|
28
|
-
* touches goes through src/vendor/anthropic.ts.
|
|
29
|
-
*/
|
|
30
|
-
export default function subscriptions(pi: ExtensionAPI) {
|
|
31
|
-
// Adapters register first so the generic commands can see them.
|
|
32
|
-
registerAccountProvider(anthropicAccounts);
|
|
33
|
-
registerAccountProvider(codexAccounts);
|
|
34
|
-
|
|
35
|
-
// Own the Anthropic provider unless explicitly told to defer to cortexkit.
|
|
36
|
-
// PI_PLUS_VENDOR_ANTHROPIC=1 restores the vendored package.
|
|
37
|
-
if (process.env.PI_PLUS_VENDOR_ANTHROPIC !== "1") registerAnthropicProvider(pi);
|
|
38
|
-
|
|
39
|
-
registerAccountCommands(pi);
|
|
40
|
-
registerRoutingCommands(pi);
|
|
41
|
-
registerFooter(pi);
|
|
42
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { registerAccountProvider } from "../../core/accounts/registry.ts";
|
|
3
|
+
import { anthropicAccounts } from "./providers/anthropic.ts";
|
|
4
|
+
import { codexAccounts } from "./providers/codex.ts";
|
|
5
|
+
import { registerAnthropicProvider } from "./provider.ts";
|
|
6
|
+
import { registerAccountCommands } from "./accounts.ts";
|
|
7
|
+
import { registerRoutingCommands } from "./routing.ts";
|
|
8
|
+
import { registerFooter } from "./footer.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Subscriptions domain: Claude OAuth accounts, routing mode, and the quota HUD.
|
|
12
|
+
*
|
|
13
|
+
* This replaces the old `claude-multi-account.ts` + `compact-footer.ts` pair.
|
|
14
|
+
* Account and routing commands are provider-agnostic: `/account <provider>` and
|
|
15
|
+
* `/routing` dispatch through core/accounts/registry.ts, so adding a second
|
|
16
|
+
* provider means writing one adapter, not new commands.
|
|
17
|
+
* Authentication and model catalogue are separate concerns here; they were only
|
|
18
|
+
* ever colocated because they shared an OAuth token.
|
|
19
|
+
*
|
|
20
|
+
* Relationship to @cortexkit/pi-anthropic-auth:
|
|
21
|
+
* That package stays installed and keeps ownership of the Anthropic provider,
|
|
22
|
+
* its stream implementation, and its own commands (/claude-account,
|
|
23
|
+
* /claude-routing, /claude-quota, ...). Absorbing it fully was evaluated and
|
|
24
|
+
* rejected, because its `dist/commands.js` exports only `registerCommands(pi)` as a
|
|
25
|
+
* unit, so taking over individual commands would mean copying ~130 lines of
|
|
26
|
+
* provider/model specs that drift on every vendor upgrade. This domain adds
|
|
27
|
+
* the multi-account surface the vendor does not provide, and everything it
|
|
28
|
+
* touches goes through src/vendor/anthropic.ts.
|
|
29
|
+
*/
|
|
30
|
+
export default function subscriptions(pi: ExtensionAPI) {
|
|
31
|
+
// Adapters register first so the generic commands can see them.
|
|
32
|
+
registerAccountProvider(anthropicAccounts);
|
|
33
|
+
registerAccountProvider(codexAccounts);
|
|
34
|
+
|
|
35
|
+
// Own the Anthropic provider unless explicitly told to defer to cortexkit.
|
|
36
|
+
// PI_PLUS_VENDOR_ANTHROPIC=1 restores the vendored package.
|
|
37
|
+
if (process.env.PI_PLUS_VENDOR_ANTHROPIC !== "1") registerAnthropicProvider(pi);
|
|
38
|
+
|
|
39
|
+
registerAccountCommands(pi);
|
|
40
|
+
registerRoutingCommands(pi);
|
|
41
|
+
registerFooter(pi);
|
|
42
|
+
}
|