@hieplp/pi-account-switcher 0.2.1 → 0.2.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 (56) hide show
  1. package/README.md +32 -22
  2. package/package.json +12 -8
  3. package/src/commands/accounts/add.ts +1 -1
  4. package/src/commands/accounts/edit.ts +2 -2
  5. package/src/commands/accounts/index.ts +3 -1
  6. package/src/commands/accounts/list.ts +1 -1
  7. package/src/commands/accounts/oauth.ts +1 -1
  8. package/src/commands/accounts/remove.ts +1 -1
  9. package/src/commands/accounts/shared/base.ts +34 -3
  10. package/src/commands/accounts/shared/prompts.ts +36 -12
  11. package/src/commands/accounts/switch.ts +6 -3
  12. package/src/commands/accounts/verify.ts +298 -0
  13. package/src/commands/base.ts +11 -5
  14. package/src/commands/index.ts +1 -1
  15. package/src/commands/models/add.ts +1 -1
  16. package/src/commands/models/index.ts +1 -1
  17. package/src/commands/models/list.ts +16 -6
  18. package/src/commands/models/remove.ts +2 -2
  19. package/src/commands/models/shared/base.ts +7 -3
  20. package/src/commands/models/shared/prompts.ts +1 -1
  21. package/src/commands/models/shared/select.ts +1 -1
  22. package/src/commands/providers/add.ts +1 -1
  23. package/src/commands/providers/edit.ts +1 -1
  24. package/src/commands/providers/index.ts +1 -1
  25. package/src/commands/providers/list.ts +5 -2
  26. package/src/commands/providers/remove.ts +1 -1
  27. package/src/commands/providers/shared/base.ts +1 -1
  28. package/src/commands/providers/shared/prompts.ts +24 -25
  29. package/src/commands/providers/shared/select.ts +1 -1
  30. package/src/commands/system/export.ts +1 -2
  31. package/src/commands/system/import.ts +9 -3
  32. package/src/commands/system/index.ts +1 -1
  33. package/src/commands/system/reset.ts +1 -1
  34. package/src/constants/commands.ts +5 -0
  35. package/src/constants/env.ts +1 -0
  36. package/src/constants/index.ts +1 -0
  37. package/src/constants/paths.ts +1 -1
  38. package/src/extension.ts +2 -4
  39. package/src/index.ts +1 -1
  40. package/src/runtime/account-switcher-runtime.ts +23 -13
  41. package/src/runtime/account-switcher.ts +2 -1
  42. package/src/runtime/index.ts +2 -4
  43. package/src/schemas/accounts.ts +1 -2
  44. package/src/schemas/providers.ts +2 -6
  45. package/src/services/models.ts +2 -2
  46. package/src/services/providers.ts +3 -9
  47. package/src/types/context.ts +1 -1
  48. package/src/types/index.ts +1 -1
  49. package/src/utils/accounts.ts +2 -2
  50. package/src/utils/commands.ts +10 -0
  51. package/src/utils/common.ts +15 -0
  52. package/src/utils/errors.ts +1 -3
  53. package/src/utils/filterable-selector.ts +123 -3
  54. package/src/utils/index.ts +1 -0
  55. package/src/utils/models.ts +4 -2
  56. package/src/utils/ui.ts +36 -2
@@ -1,7 +1,7 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import type { AccountConfig, AccountSwitcherContext, ProviderConfig } from "@/types";
4
- import { providerUtil, uiUtil } from "@/utils";
4
+ import { commandUtil, providerUtil, uiUtil } from "@/utils";
5
5
 
6
6
  function deduplicateLabels(labels: string[]): string[] {
7
7
  const seen = new Map<string, number>();
@@ -31,7 +31,7 @@ export abstract class BaseCommand implements Command {
31
31
  protected readonly runtime: AccountSwitcher,
32
32
  meta: CommandMeta,
33
33
  ) {
34
- this.name = meta.name;
34
+ this.name = commandUtil.name(meta.name);
35
35
  this.description = meta.description;
36
36
  }
37
37
 
@@ -73,8 +73,14 @@ export abstract class BaseCommand implements Command {
73
73
  return providerUtil.normalizeProvider(active?.provider ?? "") === provider.id;
74
74
  }
75
75
 
76
- protected isActiveModel(ctx: AccountSwitcherContext, modelId: string): boolean {
77
- return ctx.model?.id === modelId;
76
+ protected isActiveModel(ctx: AccountSwitcherContext, modelId: string, provider?: string): boolean {
77
+ if (ctx.model?.id !== modelId) return false;
78
+ if (!provider) return true;
79
+ const providers = this.runtime.getProviders();
80
+ return (
81
+ providerUtil.normalizeProviderWithCustom(ctx.model.provider, providers) ===
82
+ providerUtil.normalizeProviderWithCustom(provider, providers)
83
+ );
78
84
  }
79
85
 
80
86
  abstract handler(ctx: AccountSwitcherContext, args?: string): Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import useProviderCommands from "./providers";
4
4
  import useAccountCommands from "./accounts";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import type { AccountSwitcherContext } from "@/types";
4
4
  import { COMMANDS } from "@/constants";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { useListModelsCommand } from "./list";
4
4
  import { useAddModelCommand } from "./add";
@@ -1,8 +1,8 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import type { AccountSwitcherContext } from "@/types";
4
4
  import { COMMANDS } from "@/constants";
5
- import { errorUtil } from "@/utils";
5
+ import { errorUtil, providerUtil } from "@/utils";
6
6
  import { ModelCommand } from "./shared";
7
7
 
8
8
  export const useListModelsCommand = (pi: ExtensionAPI, runtime: AccountSwitcher) => {
@@ -18,9 +18,16 @@ class ListModelsCommand extends ModelCommand {
18
18
  try {
19
19
  await this.runtime.load();
20
20
 
21
- const provider = ctx.model?.provider;
21
+ const providers = this.runtime.getProviders();
22
+ const activeAccount = this.runtime.getActiveAccount();
23
+ const activeProvider =
24
+ activeAccount?.piAuth?.provider ??
25
+ (activeAccount
26
+ ? (providerUtil.findProvider(activeAccount.provider, providers)?.piAuthProvider ?? activeAccount.provider)
27
+ : undefined);
28
+ const provider = activeProvider ?? ctx.model?.provider;
22
29
  if (!provider) {
23
- ctx.ui.notify("No active model.", "info");
30
+ ctx.ui.notify("No active account or model.", "info");
24
31
  return;
25
32
  }
26
33
 
@@ -30,8 +37,11 @@ class ListModelsCommand extends ModelCommand {
30
37
  return;
31
38
  }
32
39
 
33
- const currentId = ctx.model?.id;
34
- const model = await this.pick(ctx, `Models (${provider})`, models, (m) =>
40
+ const normalizedProvider = providerUtil.normalizeProviderWithCustom(provider, providers);
41
+ const currentBelongsToProvider =
42
+ ctx.model && providerUtil.normalizeProviderWithCustom(ctx.model.provider, providers) === normalizedProvider;
43
+ const currentId = currentBelongsToProvider ? ctx.model?.id : undefined;
44
+ const model = await this.pick(ctx, `Models (${normalizedProvider})`, models, (m) =>
35
45
  m.id === currentId ? `${m.id} ✓` : m.id,
36
46
  );
37
47
  if (!model) return;
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import type { AccountSwitcherContext } from "@/types";
4
4
  import { COMMANDS } from "@/constants";
@@ -19,7 +19,7 @@ class RemoveModelCommand extends ModelCommand {
19
19
  const providerConfig = await this.loadProvider(ctx);
20
20
  if (!providerConfig) return;
21
21
 
22
- const removable = (providerConfig.models ?? []).filter((m) => !this.isActiveModel(ctx, m.id));
22
+ const removable = (providerConfig.models ?? []).filter((m) => !this.isActiveModel(ctx, m.id, providerConfig.id));
23
23
  if (removable.length === 0) {
24
24
  ctx.ui.notify(`Provider "${providerConfig.id}" has no removable model configs.`, "info");
25
25
  return;
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import type { AccountSwitcherContext, ProviderConfig } from "@/types";
4
4
  import { BaseCommand, type CommandMeta } from "../../base";
@@ -13,13 +13,17 @@ export abstract class ModelCommand extends BaseCommand {
13
13
  protected async loadProvider(ctx: AccountSwitcherContext): Promise<ProviderConfig | undefined> {
14
14
  await this.runtime.load();
15
15
 
16
- const provider = ctx.model?.provider;
16
+ // Prefer the active account's provider so that switching to an account whose
17
+ // provider has no models yet still targets the correct (new) provider.
18
+ const activeAccount = this.runtime.getActiveAccount();
19
+ const providers = this.runtime.getProviders();
20
+ const provider = activeAccount?.provider ?? ctx.model?.provider;
17
21
  if (!provider) {
18
22
  ctx.ui.notify("No active model. Use models:list to select one first.", "info");
19
23
  return undefined;
20
24
  }
21
25
 
22
- const config = providerUtil.findProvider(provider, this.runtime.getProviders());
26
+ const config = providerUtil.findProvider(provider, providers);
23
27
  if (!config) {
24
28
  ctx.ui.notify(`"${provider}" is a built-in provider. This command only works with custom providers.`, "info");
25
29
  return undefined;
@@ -1,4 +1,4 @@
1
- import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProviderModelConfig } from "@/types";
3
3
  import { uiUtil } from "@/utils";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { Api, Model } from "@mariozechner/pi-ai";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
2
  import type { ProviderConfig } from "@/types";
3
3
  import { providerUtil } from "@/utils";
4
4
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { COMMANDS } from "@/constants";
4
4
  import type { AccountSwitcherContext } from "@/types";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { COMMANDS } from "@/constants";
4
4
  import type { AccountSwitcherContext } from "@/types";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { useAddProviderCommand } from "./add";
4
4
  import { useEditProviderCommand } from "./edit";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { COMMANDS } from "@/constants";
4
4
  import { ProviderCommand } from "./shared";
@@ -18,7 +18,10 @@ class ListProvidersCommand extends ProviderCommand {
18
18
  try {
19
19
  const providers = await this.loadProviders(ctx);
20
20
  if (!providers) return;
21
- await ctx.ui.select("Providers", providers.map((p) => this.format(p)));
21
+ await ctx.ui.select(
22
+ "Providers",
23
+ providers.map((p) => this.format(p)),
24
+ );
22
25
  } catch (e) {
23
26
  ctx.ui.notify(`Failed to list providers: ${errorUtil.format(e)}`, "error");
24
27
  }
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { COMMANDS } from "@/constants";
4
4
  import type { AccountSwitcherContext } from "@/types";
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import type { AccountSwitcherContext, ProviderConfig } from "@/types";
4
4
  import { BaseCommand, type CommandMeta } from "../../base";
@@ -1,4 +1,4 @@
1
- import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProviderApi, ProviderConfig } from "@/types";
3
3
  import { PROVIDER_API_TYPES } from "@/constants";
4
4
  import { providerUtil, uiUtil } from "@/utils";
@@ -48,22 +48,22 @@ export class ProviderConfigBuilder {
48
48
 
49
49
  async withId(): Promise<this> {
50
50
  const raw = await this.prompt("Provider id", this.defaults.id).asText();
51
- if (!raw) throw new Error("Provider id is required");
52
- this.config.id = providerUtil.normalizeProvider(raw);
51
+ this.config.id = providerUtil.normalizeProvider(raw || this.defaults.id);
52
+ if (!this.config.id) throw new Error("Provider id is required");
53
53
  return this;
54
54
  }
55
55
 
56
56
  async withLabel(): Promise<this> {
57
57
  const id = this.config.id ?? "";
58
- this.config.label = await this.prompt("Provider label", this.defaults.label ?? id).asText();
58
+ const hint = this.defaults.label ?? id;
59
+ this.config.label = (await this.prompt("Provider label", hint).asText()) ?? hint;
59
60
  return this;
60
61
  }
61
62
 
62
63
  async withBaseUrl(): Promise<this> {
63
- this.config.baseUrl = await this.prompt(
64
- "Base URL (blank for account-only provider)",
65
- this.defaults.baseUrl,
66
- ).asText();
64
+ this.config.baseUrl =
65
+ (await this.prompt("Base URL (blank for account-only provider)", this.defaults.baseUrl).asText()) ??
66
+ this.defaults.baseUrl;
67
67
  return this;
68
68
  }
69
69
 
@@ -78,26 +78,23 @@ export class ProviderConfigBuilder {
78
78
 
79
79
  async withApiKey(): Promise<this> {
80
80
  const id = this.config.id ?? "";
81
- this.config.apiKey = await this.prompt(
82
- "Pi apiKey env var/name or raw key",
83
- this.defaults.apiKey ?? `${id.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`,
84
- ).asText();
81
+ const hint = this.defaults.apiKey ?? `${id.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
82
+ this.config.apiKey =
83
+ (await this.prompt("Pi apiKey env var/name or raw key", hint).asText()) ?? this.defaults.apiKey;
85
84
  return this;
86
85
  }
87
86
 
88
87
  async withEnvKeys(): Promise<this> {
89
88
  const { apiKey } = this.config;
90
- this.config.envKeys = await this.prompt(
91
- "Env key suggestions (comma-separated)",
92
- (this.defaults.envKeys ?? (apiKey ? [apiKey] : [DEFAULTS.envKey])).join(", "),
93
- ).asCsv();
89
+ const defaultKeys = this.defaults.envKeys ?? (apiKey ? [apiKey] : [DEFAULTS.envKey]);
90
+ const csv = await this.prompt("Env key suggestions (comma-separated)", defaultKeys.join(", ")).asCsv();
91
+ this.config.envKeys = csv.length > 0 ? csv : defaultKeys;
94
92
  return this;
95
93
  }
96
94
 
97
95
  async withAliases(): Promise<this> {
98
- this.config.aliases = (
99
- await this.prompt("Aliases (comma-separated, optional)", this.defaults.aliases.join(", ")).asCsv()
100
- ).map(providerUtil.normalizeProvider);
96
+ const csv = await this.prompt("Aliases (comma-separated, optional)", this.defaults.aliases.join(", ")).asCsv();
97
+ this.config.aliases = csv.length > 0 ? csv.map(providerUtil.normalizeProvider) : this.defaults.aliases;
101
98
  return this;
102
99
  }
103
100
 
@@ -109,15 +106,16 @@ export class ProviderConfigBuilder {
109
106
  if (rawModels?.some((item) => typeof item !== "object" || item === null || Array.isArray(item))) {
110
107
  throw new Error("models must be an array of objects");
111
108
  }
112
- this.config.models = rawModels as ProviderConfig["models"];
109
+ this.config.models = (rawModels as ProviderConfig["models"]) ?? this.defaults.models;
113
110
  return this;
114
111
  }
115
112
 
116
113
  async withCompat(): Promise<this> {
117
- this.config.compat = await this.prompt(
118
- "Compat JSON object (optional)",
119
- this.defaults.compat ? JSON.stringify(this.defaults.compat) : "",
120
- ).asJsonRecord("compat");
114
+ this.config.compat =
115
+ (await this.prompt(
116
+ "Compat JSON object (optional)",
117
+ this.defaults.compat ? JSON.stringify(this.defaults.compat) : "",
118
+ ).asJsonRecord("compat")) ?? this.defaults.compat;
121
119
  return this;
122
120
  }
123
121
 
@@ -127,7 +125,8 @@ export class ProviderConfigBuilder {
127
125
  "Configure Pi OAuth provider id?",
128
126
  "Only choose yes if this provider maps to a Pi /login auth entry.",
129
127
  ))
130
- ? await this.prompt("Pi auth provider id", this.defaults.piAuthProvider ?? id).asText()
128
+ ? ((await this.prompt("Pi auth provider id", this.defaults.piAuthProvider ?? id).asText()) ??
129
+ this.defaults.piAuthProvider)
131
130
  : this.defaults.piAuthProvider;
132
131
  return this;
133
132
  }
@@ -1,4 +1,4 @@
1
- import type { ExtensionUIContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { ProviderConfig } from "@/types";
3
3
 
4
4
  export async function selectProvider(
@@ -1,12 +1,11 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import type { AccountSwitcher } from "@/runtime";
4
4
  import { COMMANDS, DEFAULT_EXPORT_PATH, STATE_PATH } from "@/constants";
5
5
  import type { AccountSwitcherContext } from "@/types";
6
6
  import { BaseCommand } from "../base";
7
7
  import { errorUtil, fileUtil } from "@/utils";
8
8
 
9
-
10
9
  export const useExportCommand = (pi: ExtensionAPI, runtime: AccountSwitcher) => {
11
10
  new ExportCommand(pi, runtime).register();
12
11
  };
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import z from "zod";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import type { AccountSwitcher } from "@/runtime";
5
5
  import { ACCOUNTS_PATH, COMMANDS, DEFAULT_EXPORT_PATH, PROVIDERS_PATH, STATE_PATH } from "@/constants";
6
6
  import { accountSchema, providerSchema } from "@/schemas";
@@ -78,8 +78,14 @@ class ImportCommand extends BaseCommand {
78
78
 
79
79
  function parseImportBundle(raw: unknown): ImportBundle {
80
80
  const parsed = exportBundleSchema.parse(raw);
81
- assertNoDuplicateIds("account", parsed.accounts.map((account) => account.id));
82
- assertNoDuplicateIds("provider", parsed.providers.map((provider) => provider.id));
81
+ assertNoDuplicateIds(
82
+ "account",
83
+ parsed.accounts.map((account) => account.id),
84
+ );
85
+ assertNoDuplicateIds(
86
+ "provider",
87
+ parsed.providers.map((provider) => provider.id),
88
+ );
83
89
  return parsed;
84
90
  }
85
91
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcher } from "@/runtime";
3
3
  import { useExportCommand } from "./export";
4
4
  import { useImportCommand } from "./import";
@@ -1,5 +1,5 @@
1
1
  import { rm } from "node:fs/promises";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import type { AccountSwitcher } from "@/runtime";
4
4
  import { ACCOUNTS_PATH, COMMANDS, PROVIDERS_PATH, STATE_PATH } from "@/constants";
5
5
  import type { AccountSwitcherContext } from "@/types";
@@ -24,6 +24,11 @@ export const COMMANDS = {
24
24
  name: "accounts:oauth",
25
25
  description: "Import Pi /login OAuth credentials as a switchable account",
26
26
  },
27
+ verify: {
28
+ name: "accounts:verify",
29
+ description:
30
+ "Verify secrets for one or all accounts without activating them (pass 'all'; add 'ping' to send a test request)",
31
+ },
27
32
  },
28
33
  providers: {
29
34
  add: {
@@ -0,0 +1 @@
1
+ export const COMMAND_PREFIX_ENV = "PI_ACCOUNT_SWITCHER_COMMAND_PREFIX";
@@ -1,4 +1,5 @@
1
1
  export * from "./commands";
2
2
  export * from "./config";
3
+ export * from "./env";
3
4
  export * from "./paths";
4
5
  export * from "./providers";
@@ -6,4 +6,4 @@ export const ACCOUNTS_PATH = join(APP_DIR, "accounts.json");
6
6
  export const PROVIDERS_PATH = join(APP_DIR, "providers.json");
7
7
  export const STATE_PATH = join(APP_DIR, "state.json");
8
8
  export const PI_AUTH_PATH = join(homedir(), ".pi", "agent", "auth.json");
9
- export const DEFAULT_EXPORT_PATH = "~/pi-account-switcher-export.json";
9
+ export const DEFAULT_EXPORT_PATH = "~/pi-account-switcher-export.json";
package/src/extension.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createJiti } from "@mariozechner/jiti";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { dirname } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
@@ -11,9 +11,7 @@ export default async function accountSwitcherBootstrap(pi: ExtensionAPI) {
11
11
  },
12
12
  });
13
13
 
14
- const extension = await jiti.import<
15
- (pi: ExtensionAPI) => void | Promise<void>
16
- >("./index", {
14
+ const extension = await jiti.import<(pi: ExtensionAPI) => void | Promise<void>>("./index", {
17
15
  default: true,
18
16
  });
19
17
 
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { AccountSwitcherContext } from "@/types";
3
3
  import { useAccountSwitcher, type AccountSwitcher } from "./runtime";
4
4
  import { registerAllCommands } from "./commands";
@@ -1,7 +1,7 @@
1
1
  import AccountSwitcher from "./account-switcher";
2
2
  import { ACCOUNTS_PATH, PROVIDERS_PATH, STATE_PATH } from "@/constants";
3
- import type { Api, Model } from "@mariozechner/pi-ai";
4
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { Api, Model } from "@earendil-works/pi-ai";
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
5
  import type { AccountConfig, AccountSwitcherContext, PiAuthEntry, ProviderConfig } from "@/types";
6
6
  import type { AccountService, ModelService, PiAuthService, ProviderService } from "@/services";
7
7
  import { useAccountService, useModelService, usePiAuthService, useProviderService } from "@/services";
@@ -13,6 +13,10 @@ function resolveAuthProvider(account: AccountConfig, providers: ProviderConfig[]
13
13
  return provider?.piAuthProvider ?? providerUtil.normalizeProvider(account.provider);
14
14
  }
15
15
 
16
+ function resolveAccountProvider(account: AccountConfig, providers: ProviderConfig[]): string {
17
+ return providerUtil.normalizeProviderWithCustom(resolveAuthProvider(account, providers), providers);
18
+ }
19
+
16
20
  export default class AccountSwitcherRuntime implements AccountSwitcher {
17
21
  private accountService: AccountService;
18
22
  private modelService: ModelService;
@@ -49,8 +53,16 @@ export default class AccountSwitcherRuntime implements AccountSwitcher {
49
53
  // leave Pi's default model selection untouched.
50
54
  const modelState = this.accountService.getActiveModelState();
51
55
  if (modelState) {
52
- const model = ctx.modelRegistry.find(modelState.provider, modelState.id);
53
- if (model) await this.modelService.applyModel(model, ctx);
56
+ const providers = this.providerService.getProviders();
57
+ const activeProvider = active ? resolveAccountProvider(active, providers) : undefined;
58
+ const savedProvider = providerUtil.normalizeProviderWithCustom(modelState.provider, providers);
59
+
60
+ // Only restore a saved model when it belongs to the active account's provider.
61
+ // Otherwise Pi can start with credentials for one provider and a model from another.
62
+ if (!activeProvider || savedProvider === activeProvider) {
63
+ const model = ctx.modelRegistry.find(modelState.provider, modelState.id);
64
+ if (model) await this.modelService.applyModel(model, ctx);
65
+ }
54
66
  }
55
67
  }
56
68
 
@@ -94,10 +106,7 @@ export default class AccountSwitcherRuntime implements AccountSwitcher {
94
106
  findAccountsByProvider(provider: string): AccountConfig[] {
95
107
  const providers = this.providerService.getProviders();
96
108
  const normalized = providerUtil.normalizeProviderWithCustom(provider, providers);
97
- return this.accountService.getAccounts().filter((a) => {
98
- const accountProvider = providerUtil.normalizeProviderWithCustom(resolveAuthProvider(a, providers), providers);
99
- return accountProvider === normalized;
100
- });
109
+ return this.accountService.getAccounts().filter((a) => resolveAccountProvider(a, providers) === normalized);
101
110
  }
102
111
 
103
112
  getActiveAccount(): AccountConfig | undefined {
@@ -123,17 +132,14 @@ export default class AccountSwitcherRuntime implements AccountSwitcher {
123
132
 
124
133
  // piAuth accounts authenticate via a separate provider (e.g. github-copilot),
125
134
  // so use that for model lookup rather than the account's own provider field.
126
- const accountProvider = providerUtil.normalizeProviderWithCustom(
127
- resolveAuthProvider(account, providers),
128
- providers,
129
- );
135
+ const accountProvider = resolveAccountProvider(account, providers);
130
136
  const currentProvider = ctx.model
131
137
  ? providerUtil.normalizeProviderWithCustom(ctx.model.provider, providers)
132
138
  : undefined;
133
139
 
134
140
  // Skip model selection if the active model already belongs to the same provider.
135
141
  if (accountProvider !== currentProvider) {
136
- const model = await modelUtil.pickModel(ctx, account, providers);
142
+ const model = await modelUtil.pickModel(ctx, account, providers, accountProvider);
137
143
  if (model) await this.applyModel(model, ctx);
138
144
  }
139
145
 
@@ -174,6 +180,10 @@ export default class AccountSwitcherRuntime implements AccountSwitcher {
174
180
  return this.providerService.getProviders();
175
181
  }
176
182
 
183
+ registerProvider(provider: ProviderConfig): void {
184
+ this.providerService.registerProvider(provider);
185
+ }
186
+
177
187
  async addProvider(provider: ProviderConfig): Promise<void> {
178
188
  return this.providerService.addProvider(provider);
179
189
  }
@@ -1,4 +1,4 @@
1
- import type { Api, Model } from "@mariozechner/pi-ai";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
2
  import type { AccountConfig, AccountSwitcherContext, PiAuthEntry, ProviderConfig } from "@/types";
3
3
 
4
4
  export default interface AccountSwitcher {
@@ -26,6 +26,7 @@ export default interface AccountSwitcher {
26
26
 
27
27
  // Provider
28
28
  getProviders(): ProviderConfig[];
29
+ registerProvider(provider: ProviderConfig): void;
29
30
  addProvider(config: ProviderConfig): Promise<void>;
30
31
  editProvider(original: ProviderConfig, updated: ProviderConfig): Promise<void>;
31
32
  removeProvider(provider: ProviderConfig): Promise<void>;
@@ -1,10 +1,8 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type AccountSwitcher from "./account-switcher";
3
3
  import AccountSwitcherRuntime from "./account-switcher-runtime";
4
4
 
5
- function useAccountSwitcher(
6
- pi: Pick<ExtensionAPI, "registerProvider" | "setModel">,
7
- ): AccountSwitcher {
5
+ function useAccountSwitcher(pi: Pick<ExtensionAPI, "registerProvider" | "setModel">): AccountSwitcher {
8
6
  return new AccountSwitcherRuntime(pi);
9
7
  }
10
8
 
@@ -44,7 +44,6 @@ export const accountSchema = z
44
44
  account.usesProviderApiKey ||
45
45
  account.piAuth,
46
46
  {
47
- message:
48
- "Account must define env credentials, providerApiKey, provider apiKey, or piAuth credentials",
47
+ message: "Account must define env credentials, providerApiKey, provider apiKey, or piAuth credentials",
49
48
  },
50
49
  );
@@ -20,9 +20,7 @@ export const providerModelSchema = z
20
20
  })
21
21
  .optional(),
22
22
  compat: jsonRecordSchema.optional(),
23
- thinkingLevelMap: z
24
- .record(z.string(), z.union([z.string(), z.null()]))
25
- .optional(),
23
+ thinkingLevelMap: z.record(z.string(), z.union([z.string(), z.null()])).optional(),
26
24
  headers: z.record(z.string(), z.string()).optional(),
27
25
  })
28
26
  .passthrough();
@@ -42,9 +40,7 @@ export const providerSchema = z
42
40
  authHeader: z.boolean().optional(),
43
41
  compat: jsonRecordSchema.optional(),
44
42
  models: z.array(providerModelSchema).optional(),
45
- modelOverrides: z
46
- .record(z.string(), providerModelSchema.partial())
47
- .optional(),
43
+ modelOverrides: z.record(z.string(), providerModelSchema.partial()).optional(),
48
44
  })
49
45
  .passthrough();
50
46
 
@@ -1,5 +1,5 @@
1
- import type { Api, Model } from "@mariozechner/pi-ai";
2
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import type { AccountSwitcherContext } from "@/types";
4
4
 
5
5
  type ProviderModel = Model<Api>;
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { type ProviderStore, useProviderStore } from "@/storage";
3
3
  import type { ProviderConfig, ProviderModelConfig } from "@/types";
4
4
  import { commonUtil } from "@/utils";
@@ -72,10 +72,7 @@ class ProviderServiceImpl implements ProviderService {
72
72
  registerProvider(provider: ProviderConfig): void {
73
73
  const config = this.toPiProvider(provider);
74
74
  if (!config) return;
75
- this.pi.registerProvider(
76
- provider.id,
77
- config as Parameters<ExtensionAPI["registerProvider"]>[1],
78
- );
75
+ this.pi.registerProvider(provider.id, config as Parameters<ExtensionAPI["registerProvider"]>[1]);
79
76
  }
80
77
 
81
78
  private toPiProvider(provider: ProviderConfig): Record<string, unknown> | undefined {
@@ -104,10 +101,7 @@ class ProviderServiceImpl implements ProviderService {
104
101
  });
105
102
  }
106
103
 
107
- private toPiModel(
108
- provider: ProviderConfig,
109
- model: ProviderModelConfig,
110
- ): Record<string, unknown> | undefined {
104
+ private toPiModel(provider: ProviderConfig, model: ProviderModelConfig): Record<string, unknown> | undefined {
111
105
  return commonUtil.omitUndefined({
112
106
  ...model,
113
107
  api: model.api ?? provider.api,
@@ -1,3 +1,3 @@
1
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  export type AccountSwitcherContext = ExtensionCommandContext;
@@ -1,4 +1,4 @@
1
1
  export * from "./config";
2
2
  export * from "./accounts";
3
3
  export * from "./providers";
4
- export * from "./context";
4
+ export * from "./context";