wepscli 0.1.0

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 (130) hide show
  1. package/README.md +293 -0
  2. package/dist/WEPSCLI-shell/agent-runtime.js +824 -0
  3. package/dist/WEPSCLI-shell/agent-runtime.js.map +1 -0
  4. package/dist/WEPSCLI-shell/approval-overlay.js +275 -0
  5. package/dist/WEPSCLI-shell/approval-overlay.js.map +1 -0
  6. package/dist/WEPSCLI-shell/chat-components.js +760 -0
  7. package/dist/WEPSCLI-shell/chat-components.js.map +1 -0
  8. package/dist/WEPSCLI-shell/components.js +850 -0
  9. package/dist/WEPSCLI-shell/components.js.map +1 -0
  10. package/dist/WEPSCLI-shell/config-overlays.js +205 -0
  11. package/dist/WEPSCLI-shell/config-overlays.js.map +1 -0
  12. package/dist/WEPSCLI-shell/debug-log.js +16 -0
  13. package/dist/WEPSCLI-shell/debug-log.js.map +1 -0
  14. package/dist/WEPSCLI-shell/file-change-preview.js +261 -0
  15. package/dist/WEPSCLI-shell/file-change-preview.js.map +1 -0
  16. package/dist/WEPSCLI-shell/helpers.js +112 -0
  17. package/dist/WEPSCLI-shell/helpers.js.map +1 -0
  18. package/dist/WEPSCLI-shell/index.js +3 -0
  19. package/dist/WEPSCLI-shell/index.js.map +1 -0
  20. package/dist/WEPSCLI-shell/provider-add-flow.js +406 -0
  21. package/dist/WEPSCLI-shell/provider-add-flow.js.map +1 -0
  22. package/dist/WEPSCLI-shell/run-wepscli-shell.js +21 -0
  23. package/dist/WEPSCLI-shell/run-wepscli-shell.js.map +1 -0
  24. package/dist/WEPSCLI-shell/runtime-recovery.js +37 -0
  25. package/dist/WEPSCLI-shell/runtime-recovery.js.map +1 -0
  26. package/dist/WEPSCLI-shell/runtime-status.js +66 -0
  27. package/dist/WEPSCLI-shell/runtime-status.js.map +1 -0
  28. package/dist/WEPSCLI-shell/shell-app.js +1047 -0
  29. package/dist/WEPSCLI-shell/shell-app.js.map +1 -0
  30. package/dist/WEPSCLI-shell/shell-modes.js +77 -0
  31. package/dist/WEPSCLI-shell/shell-modes.js.map +1 -0
  32. package/dist/WEPSCLI-shell/slash-commands.js +135 -0
  33. package/dist/WEPSCLI-shell/slash-commands.js.map +1 -0
  34. package/dist/WEPSCLI-shell/theme.js +19 -0
  35. package/dist/WEPSCLI-shell/theme.js.map +1 -0
  36. package/dist/WEPSCLI-shell/tool-approval.js +85 -0
  37. package/dist/WEPSCLI-shell/tool-approval.js.map +1 -0
  38. package/dist/WEPSCLI-shell/tool-diff.js +76 -0
  39. package/dist/WEPSCLI-shell/tool-diff.js.map +1 -0
  40. package/dist/WEPSCLI-shell/tool-file-changes.js +268 -0
  41. package/dist/WEPSCLI-shell/tool-file-changes.js.map +1 -0
  42. package/dist/WEPSCLI-shell/tool-message-detail.js +138 -0
  43. package/dist/WEPSCLI-shell/tool-message-detail.js.map +1 -0
  44. package/dist/WEPSCLI-shell/tool-messages.js +145 -0
  45. package/dist/WEPSCLI-shell/tool-messages.js.map +1 -0
  46. package/dist/WEPSCLI-shell/transcript-panel.js +372 -0
  47. package/dist/WEPSCLI-shell/transcript-panel.js.map +1 -0
  48. package/dist/WEPSCLI-shell/transcript-state.js +62 -0
  49. package/dist/WEPSCLI-shell/transcript-state.js.map +1 -0
  50. package/dist/WEPSCLI-shell/types.js +1 -0
  51. package/dist/WEPSCLI-shell/types.js.map +1 -0
  52. package/dist/cli.js +11 -0
  53. package/dist/cli.js.map +1 -0
  54. package/dist/config.js +40 -0
  55. package/dist/config.js.map +1 -0
  56. package/dist/index.js +4 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/main.js +140 -0
  59. package/dist/main.js.map +1 -0
  60. package/dist/onboarding/action-screen.js +90 -0
  61. package/dist/onboarding/action-screen.js.map +1 -0
  62. package/dist/onboarding/framed-screen.js +35 -0
  63. package/dist/onboarding/framed-screen.js.map +1 -0
  64. package/dist/onboarding/onboarding-app.js +312 -0
  65. package/dist/onboarding/onboarding-app.js.map +1 -0
  66. package/dist/onboarding/run-onboarding.js +23 -0
  67. package/dist/onboarding/run-onboarding.js.map +1 -0
  68. package/dist/onboarding/select-screen.js +21 -0
  69. package/dist/onboarding/select-screen.js.map +1 -0
  70. package/dist/onboarding/summary-screen.js +23 -0
  71. package/dist/onboarding/summary-screen.js.map +1 -0
  72. package/dist/onboarding/text-input-screen.js +55 -0
  73. package/dist/onboarding/text-input-screen.js.map +1 -0
  74. package/dist/onboarding/theme.js +26 -0
  75. package/dist/onboarding/theme.js.map +1 -0
  76. package/dist/provider-profiles/api-key-store.js +51 -0
  77. package/dist/provider-profiles/api-key-store.js.map +1 -0
  78. package/dist/provider-profiles/defaults.js +18 -0
  79. package/dist/provider-profiles/defaults.js.map +1 -0
  80. package/dist/provider-profiles/fetch-models.js +53 -0
  81. package/dist/provider-profiles/fetch-models.js.map +1 -0
  82. package/dist/provider-profiles/index.js +6 -0
  83. package/dist/provider-profiles/index.js.map +1 -0
  84. package/dist/provider-profiles/provider-profile-service.js +223 -0
  85. package/dist/provider-profiles/provider-profile-service.js.map +1 -0
  86. package/dist/provider-profiles/providers-config-store.js +17 -0
  87. package/dist/provider-profiles/providers-config-store.js.map +1 -0
  88. package/dist/provider-profiles/types.js +1 -0
  89. package/dist/provider-profiles/types.js.map +1 -0
  90. package/dist/session-history/session-history-service.js +142 -0
  91. package/dist/session-history/session-history-service.js.map +1 -0
  92. package/dist/shell/animator.js +30 -0
  93. package/dist/shell/animator.js.map +1 -0
  94. package/dist/shell/clickables.js +101 -0
  95. package/dist/shell/clickables.js.map +1 -0
  96. package/dist/shell/dashboard-shell.js +292 -0
  97. package/dist/shell/dashboard-shell.js.map +1 -0
  98. package/dist/shell/index.js +5 -0
  99. package/dist/shell/index.js.map +1 -0
  100. package/dist/shell/keymap.js +14 -0
  101. package/dist/shell/keymap.js.map +1 -0
  102. package/dist/shell/mouse.js +39 -0
  103. package/dist/shell/mouse.js.map +1 -0
  104. package/dist/shell/render.js +122 -0
  105. package/dist/shell/render.js.map +1 -0
  106. package/dist/shell/run-shell.js +36 -0
  107. package/dist/shell/run-shell.js.map +1 -0
  108. package/dist/shell/theme.js +56 -0
  109. package/dist/shell/theme.js.map +1 -0
  110. package/dist/storage/locked-json-file.js +88 -0
  111. package/dist/storage/locked-json-file.js.map +1 -0
  112. package/dist/workbench/animator.js +30 -0
  113. package/dist/workbench/animator.js.map +1 -0
  114. package/dist/workbench/index.js +6 -0
  115. package/dist/workbench/index.js.map +1 -0
  116. package/dist/workbench/mouse.js +39 -0
  117. package/dist/workbench/mouse.js.map +1 -0
  118. package/dist/workbench/render.js +82 -0
  119. package/dist/workbench/render.js.map +1 -0
  120. package/dist/workbench/renderer.js +364 -0
  121. package/dist/workbench/renderer.js.map +1 -0
  122. package/dist/workbench/run-workbench.js +36 -0
  123. package/dist/workbench/run-workbench.js.map +1 -0
  124. package/dist/workbench/theme.js +63 -0
  125. package/dist/workbench/theme.js.map +1 -0
  126. package/dist/workbench/types.js +1 -0
  127. package/dist/workbench/types.js.map +1 -0
  128. package/dist/workbench/workbench-shell.js +649 -0
  129. package/dist/workbench/workbench-shell.js.map +1 -0
  130. package/package.json +65 -0
@@ -0,0 +1,18 @@
1
+ export const PROVIDERS_CONFIG_VERSION = 1;
2
+ export function createDefaultProvidersConfig() {
3
+ return {
4
+ version: PROVIDERS_CONFIG_VERSION,
5
+ profiles: []
6
+ };
7
+ }
8
+ export function getDefaultDialect(family) {
9
+ switch (family) {
10
+ case "openai":
11
+ return "openai-responses";
12
+ case "anthropic":
13
+ return "anthropic-messages";
14
+ }
15
+ }
16
+ export function trimTrailingSlash(value) {
17
+ return value.replace(/\/+$/, "");
18
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"names":["PROVIDERS_CONFIG_VERSION","createDefaultProvidersConfig","version","profiles","getDefaultDialect","family","trimTrailingSlash","value","replace"],"sources":["defaults.ts"],"sourcesContent":["import type { ProviderApiDialect, ProviderFamily, ProvidersConfig } from \"./types.js\";\r\n\r\nexport const PROVIDERS_CONFIG_VERSION = 1;\r\n\r\nexport function createDefaultProvidersConfig(): ProvidersConfig {\r\n\treturn {\r\n\t\tversion: PROVIDERS_CONFIG_VERSION,\r\n\t\tprofiles: [],\r\n\t};\r\n}\r\n\r\nexport function getDefaultDialect(family: ProviderFamily): ProviderApiDialect {\r\n\tswitch (family) {\r\n\t\tcase \"openai\":\r\n\t\t\treturn \"openai-responses\";\r\n\t\tcase \"anthropic\":\r\n\t\t\treturn \"anthropic-messages\";\r\n\t}\r\n}\r\n\r\nexport function trimTrailingSlash(value: string): string {\r\n\treturn value.replace(/\\/+$/, \"\");\r\n}\r\n"],"mappings":"AAEA,OAAO,MAAMA,wBAAwB,GAAG,CAAC;AAEzC,OAAO,SAASC,4BAA4BA,CAAA,EAAoB;EAC/D,OAAO;IACNC,OAAO,EAAEF,wBAAwB;IACjCG,QAAQ,EAAE;EACX,CAAC;AACF;AAEA,OAAO,SAASC,iBAAiBA,CAACC,MAAsB,EAAsB;EAC7E,QAAQA,MAAM;IACb,KAAK,QAAQ;MACZ,OAAO,kBAAkB;IAC1B,KAAK,WAAW;MACf,OAAO,oBAAoB;EAC7B;AACD;AAEA,OAAO,SAASC,iBAAiBA,CAACC,KAAa,EAAU;EACxD,OAAOA,KAAK,CAACC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;AACjC","ignoreList":[]}
@@ -0,0 +1,53 @@
1
+ import { trimTrailingSlash } from "./defaults.js";
2
+ function getModelsUrl(baseUrl) {
3
+ const normalized = trimTrailingSlash(baseUrl.trim());
4
+ if (normalized.endsWith("/models")) {
5
+ return normalized;
6
+ }
7
+ return `${normalized}/models`;
8
+ }
9
+ function normalizeOpenAiModels(payload) {
10
+ const items = payload.data ?? [];
11
+ return items.filter(item => typeof item.id === "string" && item.id.length > 0).map(item => ({
12
+ id: item.id,
13
+ name: item.id,
14
+ family: "openai",
15
+ raw: item
16
+ }));
17
+ }
18
+ function normalizeAnthropicModels(payload) {
19
+ const items = payload.data ?? [];
20
+ return items.filter(item => typeof item.id === "string" && item.id.length > 0).map(item => ({
21
+ id: item.id,
22
+ name: item.display_name || item.id,
23
+ family: "anthropic",
24
+ raw: item
25
+ }));
26
+ }
27
+ function buildHeaders(profile, apiKey) {
28
+ if (profile.family === "anthropic") {
29
+ return {
30
+ "x-api-key": apiKey,
31
+ "anthropic-version": "2023-06-01"
32
+ };
33
+ }
34
+ return {
35
+ Authorization: `Bearer ${apiKey}`
36
+ };
37
+ }
38
+ export async function fetchModelsForProfile(profile, apiKey, fetchImpl = fetch) {
39
+ const response = await fetchImpl(getModelsUrl(profile.baseUrl), {
40
+ method: "GET",
41
+ headers: buildHeaders(profile, apiKey)
42
+ });
43
+ if (!response.ok) {
44
+ const text = await response.text();
45
+ throw new Error(`Model listing failed (${response.status} ${response.statusText}): ${text || "empty response"}`);
46
+ }
47
+ const payload = await response.json();
48
+ const models = profile.family === "anthropic" ? normalizeAnthropicModels(payload) : normalizeOpenAiModels(payload);
49
+ if (models.length === 0) {
50
+ throw new Error("Model listing returned no usable models");
51
+ }
52
+ return models;
53
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"names":["trimTrailingSlash","getModelsUrl","baseUrl","normalized","trim","endsWith","normalizeOpenAiModels","payload","items","data","filter","item","id","length","map","name","family","raw","normalizeAnthropicModels","display_name","buildHeaders","profile","apiKey","Authorization","fetchModelsForProfile","fetchImpl","fetch","response","method","headers","ok","text","Error","status","statusText","json","models"],"sources":["fetch-models.ts"],"sourcesContent":["import { trimTrailingSlash } from \"./defaults.js\";\r\nimport type { DiscoveredModel, ProviderProfile } from \"./types.js\";\r\n\r\ninterface OpenAiListModelsResponse {\r\n\tdata?: Array<{\r\n\t\tid?: string;\r\n\t\towned_by?: string;\r\n\t}>;\r\n}\r\n\r\ninterface AnthropicListModelsResponse {\r\n\tdata?: Array<{\r\n\t\tid?: string;\r\n\t\tdisplay_name?: string;\r\n\t}>;\r\n}\r\n\r\nfunction getModelsUrl(baseUrl: string): string {\r\n\tconst normalized = trimTrailingSlash(baseUrl.trim());\r\n\tif (normalized.endsWith(\"/models\")) {\r\n\t\treturn normalized;\r\n\t}\r\n\treturn `${normalized}/models`;\r\n}\r\n\r\nfunction normalizeOpenAiModels(payload: OpenAiListModelsResponse): DiscoveredModel[] {\r\n\tconst items = payload.data ?? [];\r\n\treturn items\r\n\t\t.filter((item): item is { id: string; owned_by?: string } => typeof item.id === \"string\" && item.id.length > 0)\r\n\t\t.map((item) => ({\r\n\t\t\tid: item.id,\r\n\t\t\tname: item.id,\r\n\t\t\tfamily: \"openai\",\r\n\t\t\traw: item,\r\n\t\t}));\r\n}\r\n\r\nfunction normalizeAnthropicModels(payload: AnthropicListModelsResponse): DiscoveredModel[] {\r\n\tconst items = payload.data ?? [];\r\n\treturn items\r\n\t\t.filter((item): item is { id: string; display_name?: string } => typeof item.id === \"string\" && item.id.length > 0)\r\n\t\t.map((item) => ({\r\n\t\t\tid: item.id,\r\n\t\t\tname: item.display_name || item.id,\r\n\t\t\tfamily: \"anthropic\",\r\n\t\t\traw: item,\r\n\t\t}));\r\n}\r\n\r\nfunction buildHeaders(profile: ProviderProfile, apiKey: string): Record<string, string> {\r\n\tif (profile.family === \"anthropic\") {\r\n\t\treturn {\r\n\t\t\t\"x-api-key\": apiKey,\r\n\t\t\t\"anthropic-version\": \"2023-06-01\",\r\n\t\t};\r\n\t}\r\n\r\n\treturn {\r\n\t\tAuthorization: `Bearer ${apiKey}`,\r\n\t};\r\n}\r\n\r\nexport async function fetchModelsForProfile(\r\n\tprofile: ProviderProfile,\r\n\tapiKey: string,\r\n\tfetchImpl: typeof fetch = fetch,\r\n): Promise<DiscoveredModel[]> {\r\n\tconst response = await fetchImpl(getModelsUrl(profile.baseUrl), {\r\n\t\tmethod: \"GET\",\r\n\t\theaders: buildHeaders(profile, apiKey),\r\n\t});\r\n\r\n\tif (!response.ok) {\r\n\t\tconst text = await response.text();\r\n\t\tthrow new Error(`Model listing failed (${response.status} ${response.statusText}): ${text || \"empty response\"}`);\r\n\t}\r\n\r\n\tconst payload = (await response.json()) as OpenAiListModelsResponse | AnthropicListModelsResponse;\r\n\tconst models = profile.family === \"anthropic\" ? normalizeAnthropicModels(payload) : normalizeOpenAiModels(payload);\r\n\r\n\tif (models.length === 0) {\r\n\t\tthrow new Error(\"Model listing returned no usable models\");\r\n\t}\r\n\r\n\treturn models;\r\n}\r\n"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,eAAe;AAiBjD,SAASC,YAAYA,CAACC,OAAe,EAAU;EAC9C,MAAMC,UAAU,GAAGH,iBAAiB,CAACE,OAAO,CAACE,IAAI,CAAC,CAAC,CAAC;EACpD,IAAID,UAAU,CAACE,QAAQ,CAAC,SAAS,CAAC,EAAE;IACnC,OAAOF,UAAU;EAClB;EACA,OAAO,GAAGA,UAAU,SAAS;AAC9B;AAEA,SAASG,qBAAqBA,CAACC,OAAiC,EAAqB;EACpF,MAAMC,KAAK,GAAGD,OAAO,CAACE,IAAI,IAAI,EAAE;EAChC,OAAOD,KAAK,CACVE,MAAM,CAAEC,IAAI,IAAgD,OAAOA,IAAI,CAACC,EAAE,KAAK,QAAQ,IAAID,IAAI,CAACC,EAAE,CAACC,MAAM,GAAG,CAAC,CAAC,CAC9GC,GAAG,CAAEH,IAAI,KAAM;IACfC,EAAE,EAAED,IAAI,CAACC,EAAE;IACXG,IAAI,EAAEJ,IAAI,CAACC,EAAE;IACbI,MAAM,EAAE,QAAQ;IAChBC,GAAG,EAAEN;EACN,CAAC,CAAC,CAAC;AACL;AAEA,SAASO,wBAAwBA,CAACX,OAAoC,EAAqB;EAC1F,MAAMC,KAAK,GAAGD,OAAO,CAACE,IAAI,IAAI,EAAE;EAChC,OAAOD,KAAK,CACVE,MAAM,CAAEC,IAAI,IAAoD,OAAOA,IAAI,CAACC,EAAE,KAAK,QAAQ,IAAID,IAAI,CAACC,EAAE,CAACC,MAAM,GAAG,CAAC,CAAC,CAClHC,GAAG,CAAEH,IAAI,KAAM;IACfC,EAAE,EAAED,IAAI,CAACC,EAAE;IACXG,IAAI,EAAEJ,IAAI,CAACQ,YAAY,IAAIR,IAAI,CAACC,EAAE;IAClCI,MAAM,EAAE,WAAW;IACnBC,GAAG,EAAEN;EACN,CAAC,CAAC,CAAC;AACL;AAEA,SAASS,YAAYA,CAACC,OAAwB,EAAEC,MAAc,EAA0B;EACvF,IAAID,OAAO,CAACL,MAAM,KAAK,WAAW,EAAE;IACnC,OAAO;MACN,WAAW,EAAEM,MAAM;MACnB,mBAAmB,EAAE;IACtB,CAAC;EACF;EAEA,OAAO;IACNC,aAAa,EAAE,UAAUD,MAAM;EAChC,CAAC;AACF;AAEA,OAAO,eAAeE,qBAAqBA,CAC1CH,OAAwB,EACxBC,MAAc,EACdG,SAAuB,GAAGC,KAAK,EACF;EAC7B,MAAMC,QAAQ,GAAG,MAAMF,SAAS,CAACxB,YAAY,CAACoB,OAAO,CAACnB,OAAO,CAAC,EAAE;IAC/D0B,MAAM,EAAE,KAAK;IACbC,OAAO,EAAET,YAAY,CAACC,OAAO,EAAEC,MAAM;EACtC,CAAC,CAAC;EAEF,IAAI,CAACK,QAAQ,CAACG,EAAE,EAAE;IACjB,MAAMC,IAAI,GAAG,MAAMJ,QAAQ,CAACI,IAAI,CAAC,CAAC;IAClC,MAAM,IAAIC,KAAK,CAAC,yBAAyBL,QAAQ,CAACM,MAAM,IAAIN,QAAQ,CAACO,UAAU,MAAMH,IAAI,IAAI,gBAAgB,EAAE,CAAC;EACjH;EAEA,MAAMxB,OAAO,GAAI,MAAMoB,QAAQ,CAACQ,IAAI,CAAC,CAA4D;EACjG,MAAMC,MAAM,GAAGf,OAAO,CAACL,MAAM,KAAK,WAAW,GAAGE,wBAAwB,CAACX,OAAO,CAAC,GAAGD,qBAAqB,CAACC,OAAO,CAAC;EAElH,IAAI6B,MAAM,CAACvB,MAAM,KAAK,CAAC,EAAE;IACxB,MAAM,IAAImB,KAAK,CAAC,yCAAyC,CAAC;EAC3D;EAEA,OAAOI,MAAM;AACd","ignoreList":[]}
@@ -0,0 +1,6 @@
1
+ export * from "./api-key-store.js";
2
+ export * from "./defaults.js";
3
+ export * from "./fetch-models.js";
4
+ export * from "./provider-profile-service.js";
5
+ export * from "./providers-config-store.js";
6
+ export * from "./types.js";
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["index.ts"],"sourcesContent":["export * from \"./api-key-store.js\";\r\nexport * from \"./defaults.js\";\r\nexport * from \"./fetch-models.js\";\r\nexport * from \"./provider-profile-service.js\";\r\nexport * from \"./providers-config-store.js\";\r\nexport * from \"./types.js\";\r\n"],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,eAAe;AAC7B,cAAc,mBAAmB;AACjC,cAAc,+BAA+B;AAC7C,cAAc,6BAA6B;AAC3C,cAAc,YAAY","ignoreList":[]}
@@ -0,0 +1,223 @@
1
+ import { randomUUID } from "crypto";
2
+ import { getDefaultDialect } from "./defaults.js";
3
+ import { fetchModelsForProfile } from "./fetch-models.js";
4
+ import { ApiKeyStore } from "./api-key-store.js";
5
+ import { ProvidersConfigStore } from "./providers-config-store.js";
6
+ function slugify(value) {
7
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8
+ }
9
+ function createProfileId(label) {
10
+ const slug = slugify(label) || "provider";
11
+ return `${slug}-${randomUUID().slice(0, 8)}`;
12
+ }
13
+ function nowIso() {
14
+ return new Date().toISOString();
15
+ }
16
+ function cloneProfile(profile) {
17
+ return structuredClone(profile);
18
+ }
19
+ export class ProviderProfileService {
20
+ constructor(configStore = new ProvidersConfigStore(), apiKeyStore = new ApiKeyStore()) {
21
+ this.configStore = configStore;
22
+ this.apiKeyStore = apiKeyStore;
23
+ }
24
+ ensureStorage() {
25
+ this.configStore.save(this.configStore.load());
26
+ this.apiKeyStore.ensureFile();
27
+ }
28
+ listProfiles() {
29
+ return this.configStore.load().profiles.map(cloneProfile);
30
+ }
31
+ getActiveSelection() {
32
+ const config = this.configStore.load();
33
+ return {
34
+ profileId: config.activeProfileId,
35
+ modelId: config.activeModelId
36
+ };
37
+ }
38
+ getProfile(profileId) {
39
+ const profile = this.configStore.load().profiles.find(item => item.id === profileId);
40
+ return profile ? cloneProfile(profile) : undefined;
41
+ }
42
+ createProfile(input) {
43
+ const timestamp = nowIso();
44
+ const profile = {
45
+ id: createProfileId(input.label),
46
+ label: input.label.trim(),
47
+ family: input.family,
48
+ apiDialect: input.apiDialect ?? getDefaultDialect(input.family),
49
+ baseUrl: input.baseUrl.trim(),
50
+ enabled: input.enabled ?? true,
51
+ models: [],
52
+ createdAt: timestamp,
53
+ updatedAt: timestamp,
54
+ lastValidationStatus: "unknown"
55
+ };
56
+ this.configStore.update(current => ({
57
+ result: undefined,
58
+ next: {
59
+ ...current,
60
+ activeProfileId: current.activeProfileId ?? profile.id,
61
+ profiles: [...current.profiles, profile]
62
+ }
63
+ }));
64
+ if (input.apiKey) {
65
+ this.apiKeyStore.setApiKey(profile.id, input.apiKey);
66
+ }
67
+ return cloneProfile(profile);
68
+ }
69
+ updateProfile(profileId, input) {
70
+ const updated = this.configStore.update(current => {
71
+ const index = current.profiles.findIndex(item => item.id === profileId);
72
+ if (index === -1) {
73
+ throw new Error(`Unknown provider profile: ${profileId}`);
74
+ }
75
+ const existing = current.profiles[index];
76
+ const nextProfile = {
77
+ ...existing,
78
+ label: input.label?.trim() ?? existing.label,
79
+ baseUrl: input.baseUrl?.trim() ?? existing.baseUrl,
80
+ apiDialect: input.apiDialect ?? existing.apiDialect,
81
+ enabled: input.enabled ?? existing.enabled,
82
+ updatedAt: nowIso()
83
+ };
84
+ const nextProfiles = [...current.profiles];
85
+ nextProfiles[index] = nextProfile;
86
+ return {
87
+ result: nextProfile,
88
+ next: {
89
+ ...current,
90
+ profiles: nextProfiles
91
+ }
92
+ };
93
+ });
94
+ if (input.apiKey) {
95
+ this.apiKeyStore.setApiKey(profileId, input.apiKey);
96
+ }
97
+ return cloneProfile(updated);
98
+ }
99
+ removeProfile(profileId) {
100
+ this.configStore.update(current => {
101
+ const nextProfiles = current.profiles.filter(item => item.id !== profileId);
102
+ const activeProfileId = current.activeProfileId === profileId ? nextProfiles[0]?.id : current.activeProfileId;
103
+ const activeModelId = activeProfileId === current.activeProfileId ? current.activeModelId : undefined;
104
+ return {
105
+ result: undefined,
106
+ next: {
107
+ ...current,
108
+ activeProfileId,
109
+ activeModelId,
110
+ profiles: nextProfiles
111
+ }
112
+ };
113
+ });
114
+ this.apiKeyStore.removeApiKey(profileId);
115
+ }
116
+ setActiveSelection(profileId, modelId) {
117
+ this.configStore.update(current => {
118
+ const profile = current.profiles.find(item => item.id === profileId);
119
+ if (!profile) {
120
+ throw new Error(`Unknown provider profile: ${profileId}`);
121
+ }
122
+ return {
123
+ result: undefined,
124
+ next: {
125
+ ...current,
126
+ activeProfileId: profileId,
127
+ activeModelId: modelId
128
+ }
129
+ };
130
+ });
131
+ }
132
+ getApiKey(profileId) {
133
+ return this.apiKeyStore.getApiKey(profileId);
134
+ }
135
+ replaceModels(profileId, models, validation = {
136
+ status: "unknown"
137
+ }) {
138
+ const updated = this.configStore.update(current => {
139
+ const index = current.profiles.findIndex(item => item.id === profileId);
140
+ if (index === -1) {
141
+ throw new Error(`Unknown provider profile: ${profileId}`);
142
+ }
143
+ const existing = current.profiles[index];
144
+ const nextProfile = {
145
+ ...existing,
146
+ models: structuredClone(models),
147
+ lastValidatedAt: nowIso(),
148
+ lastValidationStatus: validation.status,
149
+ lastValidationMessage: validation.message,
150
+ updatedAt: nowIso()
151
+ };
152
+ const nextProfiles = [...current.profiles];
153
+ nextProfiles[index] = nextProfile;
154
+ const nextActiveModelId = current.activeProfileId === profileId ? current.activeModelId ?? models[0]?.id : current.activeModelId;
155
+ return {
156
+ result: nextProfile,
157
+ next: {
158
+ ...current,
159
+ activeModelId: nextActiveModelId,
160
+ profiles: nextProfiles
161
+ }
162
+ };
163
+ });
164
+ return cloneProfile(updated);
165
+ }
166
+ async refreshModels(profileId, fetchImpl = fetch) {
167
+ const profile = this.getProfile(profileId);
168
+ if (!profile) {
169
+ throw new Error(`Unknown provider profile: ${profileId}`);
170
+ }
171
+ const apiKey = this.apiKeyStore.getApiKey(profileId);
172
+ if (!apiKey) {
173
+ throw new Error(`No API key configured for provider profile: ${profileId}`);
174
+ }
175
+ try {
176
+ const models = await fetchModelsForProfile(profile, apiKey, fetchImpl);
177
+ const updatedProfile = this.configStore.update(current => {
178
+ const nextProfiles = current.profiles.map(item => item.id === profileId ? {
179
+ ...item,
180
+ models,
181
+ lastValidatedAt: nowIso(),
182
+ lastValidationStatus: "ok",
183
+ lastValidationMessage: undefined,
184
+ updatedAt: nowIso()
185
+ } : item);
186
+ return {
187
+ result: nextProfiles.find(item => item.id === profileId),
188
+ next: {
189
+ ...current,
190
+ activeModelId: current.activeProfileId === profileId && !current.activeModelId ? models[0]?.id : current.activeModelId,
191
+ profiles: nextProfiles
192
+ }
193
+ };
194
+ });
195
+ return {
196
+ profile: cloneProfile(updatedProfile),
197
+ models: structuredClone(models)
198
+ };
199
+ } catch (error) {
200
+ const message = error instanceof Error ? error.message : String(error);
201
+ const updatedProfile = this.configStore.update(current => {
202
+ const nextProfiles = current.profiles.map(item => item.id === profileId ? {
203
+ ...item,
204
+ lastValidatedAt: nowIso(),
205
+ lastValidationStatus: "error",
206
+ lastValidationMessage: message,
207
+ updatedAt: nowIso()
208
+ } : item);
209
+ return {
210
+ result: nextProfiles.find(item => item.id === profileId),
211
+ next: {
212
+ ...current,
213
+ profiles: nextProfiles
214
+ }
215
+ };
216
+ });
217
+ return {
218
+ profile: cloneProfile(updatedProfile),
219
+ error: message
220
+ };
221
+ }
222
+ }
223
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"names":["randomUUID","getDefaultDialect","fetchModelsForProfile","ApiKeyStore","ProvidersConfigStore","slugify","value","trim","toLowerCase","replace","createProfileId","label","slug","slice","nowIso","Date","toISOString","cloneProfile","profile","structuredClone","ProviderProfileService","constructor","configStore","apiKeyStore","ensureStorage","save","load","ensureFile","listProfiles","profiles","map","getActiveSelection","config","profileId","activeProfileId","modelId","activeModelId","getProfile","find","item","id","undefined","createProfile","input","timestamp","family","apiDialect","baseUrl","enabled","models","createdAt","updatedAt","lastValidationStatus","update","current","result","next","apiKey","setApiKey","updateProfile","updated","index","findIndex","Error","existing","nextProfile","nextProfiles","removeProfile","filter","removeApiKey","setActiveSelection","getApiKey","replaceModels","validation","status","lastValidatedAt","lastValidationMessage","message","nextActiveModelId","refreshModels","fetchImpl","fetch","updatedProfile","error","String"],"sources":["provider-profile-service.ts"],"sourcesContent":["import { randomUUID } from \"crypto\";\r\nimport { getDefaultDialect } from \"./defaults.js\";\r\nimport { fetchModelsForProfile } from \"./fetch-models.js\";\r\nimport { ApiKeyStore } from \"./api-key-store.js\";\r\nimport { ProvidersConfigStore } from \"./providers-config-store.js\";\r\nimport type {\r\n\tCreateProviderProfileInput,\r\n\tDiscoveredModel,\r\n\tFailedRefreshModelsResult,\r\n\tProviderProfile,\r\n\tRefreshModelsResult,\r\n\tUpdateProviderProfileInput,\r\n} from \"./types.js\";\r\n\r\nfunction slugify(value: string): string {\r\n\treturn value\r\n\t\t.trim()\r\n\t\t.toLowerCase()\r\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\r\n\t\t.replace(/^-+|-+$/g, \"\");\r\n}\r\n\r\nfunction createProfileId(label: string): string {\r\n\tconst slug = slugify(label) || \"provider\";\r\n\treturn `${slug}-${randomUUID().slice(0, 8)}`;\r\n}\r\n\r\nfunction nowIso(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\nfunction cloneProfile(profile: ProviderProfile): ProviderProfile {\r\n\treturn structuredClone(profile);\r\n}\r\n\r\nexport class ProviderProfileService {\r\n\tconstructor(\r\n\t\tprivate readonly configStore: ProvidersConfigStore = new ProvidersConfigStore(),\r\n\t\tprivate readonly apiKeyStore: ApiKeyStore = new ApiKeyStore(),\r\n\t) {}\r\n\r\n\tensureStorage(): void {\r\n\t\tthis.configStore.save(this.configStore.load());\r\n\t\tthis.apiKeyStore.ensureFile();\r\n\t}\r\n\r\n\tlistProfiles(): ProviderProfile[] {\r\n\t\treturn this.configStore.load().profiles.map(cloneProfile);\r\n\t}\r\n\r\n\tgetActiveSelection(): { profileId?: string; modelId?: string } {\r\n\t\tconst config = this.configStore.load();\r\n\t\treturn {\r\n\t\t\tprofileId: config.activeProfileId,\r\n\t\t\tmodelId: config.activeModelId,\r\n\t\t};\r\n\t}\r\n\r\n\tgetProfile(profileId: string): ProviderProfile | undefined {\r\n\t\tconst profile = this.configStore.load().profiles.find((item) => item.id === profileId);\r\n\t\treturn profile ? cloneProfile(profile) : undefined;\r\n\t}\r\n\r\n\tcreateProfile(input: CreateProviderProfileInput): ProviderProfile {\r\n\t\tconst timestamp = nowIso();\r\n\t\tconst profile: ProviderProfile = {\r\n\t\t\tid: createProfileId(input.label),\r\n\t\t\tlabel: input.label.trim(),\r\n\t\t\tfamily: input.family,\r\n\t\t\tapiDialect: input.apiDialect ?? getDefaultDialect(input.family),\r\n\t\t\tbaseUrl: input.baseUrl.trim(),\r\n\t\t\tenabled: input.enabled ?? true,\r\n\t\t\tmodels: [],\r\n\t\t\tcreatedAt: timestamp,\r\n\t\t\tupdatedAt: timestamp,\r\n\t\t\tlastValidationStatus: \"unknown\",\r\n\t\t};\r\n\r\n\t\tthis.configStore.update((current) => ({\r\n\t\t\tresult: undefined,\r\n\t\t\tnext: {\r\n\t\t\t\t...current,\r\n\t\t\t\tactiveProfileId: current.activeProfileId ?? profile.id,\r\n\t\t\t\tprofiles: [...current.profiles, profile],\r\n\t\t\t},\r\n\t\t}));\r\n\r\n\t\tif (input.apiKey) {\r\n\t\t\tthis.apiKeyStore.setApiKey(profile.id, input.apiKey);\r\n\t\t}\r\n\r\n\t\treturn cloneProfile(profile);\r\n\t}\r\n\r\n\tupdateProfile(profileId: string, input: UpdateProviderProfileInput): ProviderProfile {\r\n\t\tconst updated = this.configStore.update((current) => {\r\n\t\t\tconst index = current.profiles.findIndex((item) => item.id === profileId);\r\n\t\t\tif (index === -1) {\r\n\t\t\t\tthrow new Error(`Unknown provider profile: ${profileId}`);\r\n\t\t\t}\r\n\r\n\t\t\tconst existing = current.profiles[index];\r\n\t\t\tconst nextProfile: ProviderProfile = {\r\n\t\t\t\t...existing,\r\n\t\t\t\tlabel: input.label?.trim() ?? existing.label,\r\n\t\t\t\tbaseUrl: input.baseUrl?.trim() ?? existing.baseUrl,\r\n\t\t\t\tapiDialect: input.apiDialect ?? existing.apiDialect,\r\n\t\t\t\tenabled: input.enabled ?? existing.enabled,\r\n\t\t\t\tupdatedAt: nowIso(),\r\n\t\t\t};\r\n\r\n\t\t\tconst nextProfiles = [...current.profiles];\r\n\t\t\tnextProfiles[index] = nextProfile;\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: nextProfile,\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tprofiles: nextProfiles,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\r\n\t\tif (input.apiKey) {\r\n\t\t\tthis.apiKeyStore.setApiKey(profileId, input.apiKey);\r\n\t\t}\r\n\r\n\t\treturn cloneProfile(updated);\r\n\t}\r\n\r\n\tremoveProfile(profileId: string): void {\r\n\t\tthis.configStore.update((current) => {\r\n\t\t\tconst nextProfiles = current.profiles.filter((item) => item.id !== profileId);\r\n\t\t\tconst activeProfileId = current.activeProfileId === profileId ? nextProfiles[0]?.id : current.activeProfileId;\r\n\t\t\tconst activeModelId = activeProfileId === current.activeProfileId ? current.activeModelId : undefined;\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: undefined,\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tactiveProfileId,\r\n\t\t\t\t\tactiveModelId,\r\n\t\t\t\t\tprofiles: nextProfiles,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\r\n\t\tthis.apiKeyStore.removeApiKey(profileId);\r\n\t}\r\n\r\n\tsetActiveSelection(profileId: string, modelId?: string): void {\r\n\t\tthis.configStore.update((current) => {\r\n\t\t\tconst profile = current.profiles.find((item) => item.id === profileId);\r\n\t\t\tif (!profile) {\r\n\t\t\t\tthrow new Error(`Unknown provider profile: ${profileId}`);\r\n\t\t\t}\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: undefined,\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tactiveProfileId: profileId,\r\n\t\t\t\t\tactiveModelId: modelId,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}\r\n\r\n\tgetApiKey(profileId: string): string | undefined {\r\n\t\treturn this.apiKeyStore.getApiKey(profileId);\r\n\t}\r\n\r\n\treplaceModels(\r\n\t\tprofileId: string,\r\n\t\tmodels: DiscoveredModel[],\r\n\t\tvalidation: { status: \"unknown\" | \"ok\" | \"error\"; message?: string } = { status: \"unknown\" },\r\n\t): ProviderProfile {\r\n\t\tconst updated = this.configStore.update((current) => {\r\n\t\t\tconst index = current.profiles.findIndex((item) => item.id === profileId);\r\n\t\t\tif (index === -1) {\r\n\t\t\t\tthrow new Error(`Unknown provider profile: ${profileId}`);\r\n\t\t\t}\r\n\r\n\t\t\tconst existing = current.profiles[index];\r\n\t\t\tconst nextProfile: ProviderProfile = {\r\n\t\t\t\t...existing,\r\n\t\t\t\tmodels: structuredClone(models),\r\n\t\t\t\tlastValidatedAt: nowIso(),\r\n\t\t\t\tlastValidationStatus: validation.status,\r\n\t\t\t\tlastValidationMessage: validation.message,\r\n\t\t\t\tupdatedAt: nowIso(),\r\n\t\t\t};\r\n\r\n\t\t\tconst nextProfiles = [...current.profiles];\r\n\t\t\tnextProfiles[index] = nextProfile;\r\n\r\n\t\t\tconst nextActiveModelId =\r\n\t\t\t\tcurrent.activeProfileId === profileId\r\n\t\t\t\t\t? current.activeModelId ?? models[0]?.id\r\n\t\t\t\t\t: current.activeModelId;\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: nextProfile,\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tactiveModelId: nextActiveModelId,\r\n\t\t\t\t\tprofiles: nextProfiles,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\r\n\t\treturn cloneProfile(updated);\r\n\t}\r\n\r\n\tasync refreshModels(\r\n\t\tprofileId: string,\r\n\t\tfetchImpl: typeof fetch = fetch,\r\n\t): Promise<RefreshModelsResult | FailedRefreshModelsResult> {\r\n\t\tconst profile = this.getProfile(profileId);\r\n\t\tif (!profile) {\r\n\t\t\tthrow new Error(`Unknown provider profile: ${profileId}`);\r\n\t\t}\r\n\r\n\t\tconst apiKey = this.apiKeyStore.getApiKey(profileId);\r\n\t\tif (!apiKey) {\r\n\t\t\tthrow new Error(`No API key configured for provider profile: ${profileId}`);\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst models = await fetchModelsForProfile(profile, apiKey, fetchImpl);\r\n\t\t\tconst updatedProfile = this.configStore.update((current) => {\r\n\t\t\t\tconst nextProfiles = current.profiles.map((item) =>\r\n\t\t\t\t\titem.id === profileId\r\n\t\t\t\t\t\t? {\r\n\t\t\t\t\t\t\t\t...item,\r\n\t\t\t\t\t\t\t\tmodels,\r\n\t\t\t\t\t\t\t\tlastValidatedAt: nowIso(),\r\n\t\t\t\t\t\t\t\tlastValidationStatus: \"ok\" as const,\r\n\t\t\t\t\t\t\t\tlastValidationMessage: undefined,\r\n\t\t\t\t\t\t\t\tupdatedAt: nowIso(),\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t: item,\r\n\t\t\t\t);\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\tresult: nextProfiles.find((item) => item.id === profileId)!,\r\n\t\t\t\t\tnext: {\r\n\t\t\t\t\t\t...current,\r\n\t\t\t\t\t\tactiveModelId:\r\n\t\t\t\t\t\t\tcurrent.activeProfileId === profileId && !current.activeModelId ? models[0]?.id : current.activeModelId,\r\n\t\t\t\t\t\tprofiles: nextProfiles,\r\n\t\t\t\t\t},\r\n\t\t\t\t};\r\n\t\t\t});\r\n\r\n\t\t\treturn {\r\n\t\t\t\tprofile: cloneProfile(updatedProfile),\r\n\t\t\t\tmodels: structuredClone(models),\r\n\t\t\t};\r\n\t\t} catch (error) {\r\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\r\n\t\t\tconst updatedProfile = this.configStore.update((current) => {\r\n\t\t\t\tconst nextProfiles = current.profiles.map((item) =>\r\n\t\t\t\t\titem.id === profileId\r\n\t\t\t\t\t\t? {\r\n\t\t\t\t\t\t\t\t...item,\r\n\t\t\t\t\t\t\t\tlastValidatedAt: nowIso(),\r\n\t\t\t\t\t\t\t\tlastValidationStatus: \"error\" as const,\r\n\t\t\t\t\t\t\t\tlastValidationMessage: message,\r\n\t\t\t\t\t\t\t\tupdatedAt: nowIso(),\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t: item,\r\n\t\t\t\t);\r\n\r\n\t\t\t\treturn {\r\n\t\t\t\t\tresult: nextProfiles.find((item) => item.id === profileId)!,\r\n\t\t\t\t\tnext: {\r\n\t\t\t\t\t\t...current,\r\n\t\t\t\t\t\tprofiles: nextProfiles,\r\n\t\t\t\t\t},\r\n\t\t\t\t};\r\n\t\t\t});\r\n\r\n\t\t\treturn {\r\n\t\t\t\tprofile: cloneProfile(updatedProfile),\r\n\t\t\t\terror: message,\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n}\r\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,17 @@
1
+ import { getProvidersPath } from "../config.js";
2
+ import { LockedJsonFile } from "../storage/locked-json-file.js";
3
+ import { createDefaultProvidersConfig } from "./defaults.js";
4
+ export class ProvidersConfigStore {
5
+ constructor(providersPath = getProvidersPath()) {
6
+ this.storage = new LockedJsonFile(providersPath, createDefaultProvidersConfig());
7
+ }
8
+ load() {
9
+ return this.storage.read();
10
+ }
11
+ save(next) {
12
+ this.storage.write(next);
13
+ }
14
+ update(fn) {
15
+ return this.storage.withLock(fn);
16
+ }
17
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"names":["getProvidersPath","LockedJsonFile","createDefaultProvidersConfig","ProvidersConfigStore","constructor","providersPath","storage","load","read","save","next","write","update","fn","withLock"],"sources":["providers-config-store.ts"],"sourcesContent":["import { getProvidersPath } from \"../config.js\";\r\nimport { LockedJsonFile } from \"../storage/locked-json-file.js\";\r\nimport { createDefaultProvidersConfig } from \"./defaults.js\";\r\nimport type { ProvidersConfig } from \"./types.js\";\r\n\r\nexport class ProvidersConfigStore {\r\n\tprivate readonly storage: LockedJsonFile<ProvidersConfig>;\r\n\r\n\tconstructor(providersPath: string = getProvidersPath()) {\r\n\t\tthis.storage = new LockedJsonFile<ProvidersConfig>(providersPath, createDefaultProvidersConfig());\r\n\t}\r\n\r\n\tload(): ProvidersConfig {\r\n\t\treturn this.storage.read();\r\n\t}\r\n\r\n\tsave(next: ProvidersConfig): void {\r\n\t\tthis.storage.write(next);\r\n\t}\r\n\r\n\tupdate<TResult>(fn: (current: ProvidersConfig) => { result: TResult; next?: ProvidersConfig }): TResult {\r\n\t\treturn this.storage.withLock(fn);\r\n\t}\r\n}\r\n"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,cAAc;AAC/C,SAASC,cAAc,QAAQ,gCAAgC;AAC/D,SAASC,4BAA4B,QAAQ,eAAe;AAG5D,OAAO,MAAMC,oBAAoB,CAAC;EAGjCC,WAAWA,CAACC,aAAqB,GAAGL,gBAAgB,CAAC,CAAC,EAAE;IACvD,IAAI,CAACM,OAAO,GAAG,IAAIL,cAAc,CAAkBI,aAAa,EAAEH,4BAA4B,CAAC,CAAC,CAAC;EAClG;EAEAK,IAAIA,CAAA,EAAoB;IACvB,OAAO,IAAI,CAACD,OAAO,CAACE,IAAI,CAAC,CAAC;EAC3B;EAEAC,IAAIA,CAACC,IAAqB,EAAQ;IACjC,IAAI,CAACJ,OAAO,CAACK,KAAK,CAACD,IAAI,CAAC;EACzB;EAEAE,MAAMA,CAAUC,EAA6E,EAAW;IACvG,OAAO,IAAI,CAACP,OAAO,CAACQ,QAAQ,CAACD,EAAE,CAAC;EACjC;AACD","ignoreList":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["types.ts"],"sourcesContent":["export type ProviderFamily = \"openai\" | \"anthropic\";\r\n\r\nexport type ProviderApiDialect = \"openai-responses\" | \"openai-completions\" | \"anthropic-messages\";\r\n\r\nexport type ValidationStatus = \"unknown\" | \"ok\" | \"error\";\r\n\r\nexport interface DiscoveredModel {\r\n\tid: string;\r\n\tname: string;\r\n\tfamily: ProviderFamily;\r\n\traw?: unknown;\r\n}\r\n\r\nexport interface ProviderProfile {\r\n\tid: string;\r\n\tlabel: string;\r\n\tfamily: ProviderFamily;\r\n\tapiDialect: ProviderApiDialect;\r\n\tbaseUrl: string;\r\n\tenabled: boolean;\r\n\tmodels: DiscoveredModel[];\r\n\tcreatedAt: string;\r\n\tupdatedAt: string;\r\n\tlastValidatedAt?: string;\r\n\tlastValidationStatus: ValidationStatus;\r\n\tlastValidationMessage?: string;\r\n}\r\n\r\nexport interface ProvidersConfig {\r\n\tversion: 1;\r\n\tactiveProfileId?: string;\r\n\tactiveModelId?: string;\r\n\tprofiles: ProviderProfile[];\r\n}\r\n\r\nexport interface CreateProviderProfileInput {\r\n\tlabel: string;\r\n\tfamily: ProviderFamily;\r\n\tbaseUrl: string;\r\n\tapiDialect?: ProviderApiDialect;\r\n\tenabled?: boolean;\r\n\tapiKey?: string;\r\n}\r\n\r\nexport interface UpdateProviderProfileInput {\r\n\tlabel?: string;\r\n\tbaseUrl?: string;\r\n\tapiDialect?: ProviderApiDialect;\r\n\tenabled?: boolean;\r\n\tapiKey?: string;\r\n}\r\n\r\nexport interface RefreshModelsResult {\r\n\tprofile: ProviderProfile;\r\n\tmodels: DiscoveredModel[];\r\n}\r\n\r\nexport interface FailedRefreshModelsResult {\r\n\tprofile: ProviderProfile;\r\n\terror: string;\r\n}\r\n"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,142 @@
1
+ import { randomUUID } from "crypto";
2
+ import { getSessionsPath } from "../config.js";
3
+ import { LockedJsonFile } from "../storage/locked-json-file.js";
4
+ function nowIso() {
5
+ return new Date().toISOString();
6
+ }
7
+ function createDefaultSessionsConfig() {
8
+ return {
9
+ version: 1,
10
+ sessions: []
11
+ };
12
+ }
13
+ function cloneSession(record) {
14
+ return structuredClone(record);
15
+ }
16
+ export class SessionHistoryService {
17
+ constructor(filePath = getSessionsPath()) {
18
+ this.storage = new LockedJsonFile(filePath, createDefaultSessionsConfig());
19
+ }
20
+ listSessions() {
21
+ const config = this.storage.read();
22
+ return config.sessions.slice().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map(cloneSession);
23
+ }
24
+ ensureSeed(seed) {
25
+ return this.storage.withLock(current => {
26
+ if (current.sessions.length > 0) {
27
+ return {
28
+ result: current.sessions.slice().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map(cloneSession)
29
+ };
30
+ }
31
+ const timestamp = nowIso();
32
+ const sessions = seed.map(item => ({
33
+ id: randomUUID(),
34
+ title: item.title,
35
+ summary: item.summary,
36
+ state: item.state ?? "ready",
37
+ createdAt: timestamp,
38
+ updatedAt: timestamp,
39
+ providerProfileId: item.providerProfileId,
40
+ providerLabel: item.providerLabel,
41
+ modelId: item.modelId,
42
+ lastPrompt: item.lastPrompt,
43
+ runtimeSessionFile: item.runtimeSessionFile
44
+ }));
45
+ return {
46
+ result: sessions.map(cloneSession),
47
+ next: {
48
+ ...current,
49
+ sessions
50
+ }
51
+ };
52
+ });
53
+ }
54
+ createSession(input) {
55
+ return this.storage.withLock(current => {
56
+ const timestamp = nowIso();
57
+ const session = {
58
+ id: randomUUID(),
59
+ title: input.title,
60
+ summary: input.summary,
61
+ state: input.state ?? "ready",
62
+ createdAt: timestamp,
63
+ updatedAt: timestamp,
64
+ providerProfileId: input.providerProfileId,
65
+ providerLabel: input.providerLabel,
66
+ modelId: input.modelId,
67
+ lastPrompt: input.lastPrompt,
68
+ runtimeSessionFile: input.runtimeSessionFile
69
+ };
70
+ return {
71
+ result: cloneSession(session),
72
+ next: {
73
+ ...current,
74
+ sessions: [session, ...current.sessions].slice(0, 50)
75
+ }
76
+ };
77
+ });
78
+ }
79
+ updateSession(sessionId, input) {
80
+ return this.storage.withLock(current => {
81
+ const index = current.sessions.findIndex(session => session.id === sessionId);
82
+ if (index === -1) {
83
+ return {
84
+ result: undefined
85
+ };
86
+ }
87
+ const existing = current.sessions[index];
88
+ const nextRecord = {
89
+ ...existing,
90
+ title: input.title ?? existing.title,
91
+ summary: input.summary ?? existing.summary,
92
+ state: input.state ?? existing.state,
93
+ providerProfileId: input.providerProfileId ?? existing.providerProfileId,
94
+ providerLabel: input.providerLabel ?? existing.providerLabel,
95
+ modelId: input.modelId ?? existing.modelId,
96
+ lastPrompt: input.lastPrompt ?? existing.lastPrompt,
97
+ runtimeSessionFile: input.runtimeSessionFile ?? existing.runtimeSessionFile,
98
+ updatedAt: nowIso()
99
+ };
100
+ const sessions = current.sessions.slice();
101
+ sessions[index] = nextRecord;
102
+ return {
103
+ result: cloneSession(nextRecord),
104
+ next: {
105
+ ...current,
106
+ sessions
107
+ }
108
+ };
109
+ });
110
+ }
111
+ markActive(sessionId) {
112
+ return this.storage.withLock(current => {
113
+ let activeRecord;
114
+ const timestamp = nowIso();
115
+ const sessions = current.sessions.map(session => {
116
+ if (session.id === sessionId) {
117
+ activeRecord = {
118
+ ...session,
119
+ state: "active",
120
+ updatedAt: timestamp
121
+ };
122
+ return activeRecord;
123
+ }
124
+ if (session.state === "active") {
125
+ return {
126
+ ...session,
127
+ state: "recent",
128
+ updatedAt: session.updatedAt
129
+ };
130
+ }
131
+ return session;
132
+ });
133
+ return {
134
+ result: activeRecord ? cloneSession(activeRecord) : undefined,
135
+ next: activeRecord ? {
136
+ ...current,
137
+ sessions
138
+ } : undefined
139
+ };
140
+ });
141
+ }
142
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"names":["randomUUID","getSessionsPath","LockedJsonFile","nowIso","Date","toISOString","createDefaultSessionsConfig","version","sessions","cloneSession","record","structuredClone","SessionHistoryService","constructor","filePath","storage","listSessions","config","read","slice","sort","left","right","updatedAt","localeCompare","map","ensureSeed","seed","withLock","current","length","result","timestamp","item","id","title","summary","state","createdAt","providerProfileId","providerLabel","modelId","lastPrompt","runtimeSessionFile","next","createSession","input","session","updateSession","sessionId","index","findIndex","undefined","existing","nextRecord","markActive","activeRecord"],"sources":["session-history-service.ts"],"sourcesContent":["import { randomUUID } from \"crypto\";\r\nimport { getSessionsPath } from \"../config.js\";\r\nimport { LockedJsonFile } from \"../storage/locked-json-file.js\";\r\n\r\nexport type ShellSessionState = \"active\" | \"ready\" | \"recent\";\r\n\r\nexport interface ShellSessionRecord {\r\n\tid: string;\r\n\ttitle: string;\r\n\tsummary: string;\r\n\tstate: ShellSessionState;\r\n\tcreatedAt: string;\r\n\tupdatedAt: string;\r\n\tproviderProfileId?: string;\r\n\tproviderLabel?: string;\r\n\tmodelId?: string;\r\n\tlastPrompt?: string;\r\n\truntimeSessionFile?: string;\r\n}\r\n\r\ninterface SessionsConfig {\r\n\tversion: 1;\r\n\tsessions: ShellSessionRecord[];\r\n}\r\n\r\ninterface CreateShellSessionInput {\r\n\ttitle: string;\r\n\tsummary: string;\r\n\tstate?: ShellSessionState;\r\n\tproviderProfileId?: string;\r\n\tproviderLabel?: string;\r\n\tmodelId?: string;\r\n\tlastPrompt?: string;\r\n\truntimeSessionFile?: string;\r\n}\r\n\r\ninterface UpdateShellSessionInput {\r\n\ttitle?: string;\r\n\tsummary?: string;\r\n\tstate?: ShellSessionState;\r\n\tproviderProfileId?: string;\r\n\tproviderLabel?: string;\r\n\tmodelId?: string;\r\n\tlastPrompt?: string;\r\n\truntimeSessionFile?: string;\r\n}\r\n\r\nfunction nowIso(): string {\r\n\treturn new Date().toISOString();\r\n}\r\n\r\nfunction createDefaultSessionsConfig(): SessionsConfig {\r\n\treturn {\r\n\t\tversion: 1,\r\n\t\tsessions: [],\r\n\t};\r\n}\r\n\r\nfunction cloneSession(record: ShellSessionRecord): ShellSessionRecord {\r\n\treturn structuredClone(record);\r\n}\r\n\r\nexport class SessionHistoryService {\r\n\tprivate readonly storage: LockedJsonFile<SessionsConfig>;\r\n\r\n\tconstructor(filePath: string = getSessionsPath()) {\r\n\t\tthis.storage = new LockedJsonFile<SessionsConfig>(filePath, createDefaultSessionsConfig());\r\n\t}\r\n\r\n\tlistSessions(): ShellSessionRecord[] {\r\n\t\tconst config = this.storage.read();\r\n\t\treturn config.sessions\r\n\t\t\t.slice()\r\n\t\t\t.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))\r\n\t\t\t.map(cloneSession);\r\n\t}\r\n\r\n\tensureSeed(seed: CreateShellSessionInput[]): ShellSessionRecord[] {\r\n\t\treturn this.storage.withLock((current) => {\r\n\t\t\tif (current.sessions.length > 0) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tresult: current.sessions\r\n\t\t\t\t\t\t.slice()\r\n\t\t\t\t\t\t.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))\r\n\t\t\t\t\t\t.map(cloneSession),\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tconst timestamp = nowIso();\r\n\t\t\tconst sessions = seed.map((item) => ({\r\n\t\t\t\tid: randomUUID(),\r\n\t\t\t\ttitle: item.title,\r\n\t\t\t\tsummary: item.summary,\r\n\t\t\t\tstate: item.state ?? \"ready\",\r\n\t\t\t\tcreatedAt: timestamp,\r\n\t\t\t\tupdatedAt: timestamp,\r\n\t\t\t\tproviderProfileId: item.providerProfileId,\r\n\t\t\t\tproviderLabel: item.providerLabel,\r\n\t\t\t\tmodelId: item.modelId,\r\n\t\t\t\tlastPrompt: item.lastPrompt,\r\n\t\t\t\truntimeSessionFile: item.runtimeSessionFile,\r\n\t\t\t}));\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: sessions.map(cloneSession),\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tsessions,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}\r\n\r\n\tcreateSession(input: CreateShellSessionInput): ShellSessionRecord {\r\n\t\treturn this.storage.withLock((current) => {\r\n\t\t\tconst timestamp = nowIso();\r\n\t\t\tconst session: ShellSessionRecord = {\r\n\t\t\t\tid: randomUUID(),\r\n\t\t\t\ttitle: input.title,\r\n\t\t\t\tsummary: input.summary,\r\n\t\t\t\tstate: input.state ?? \"ready\",\r\n\t\t\t\tcreatedAt: timestamp,\r\n\t\t\t\tupdatedAt: timestamp,\r\n\t\t\t\tproviderProfileId: input.providerProfileId,\r\n\t\t\t\tproviderLabel: input.providerLabel,\r\n\t\t\t\tmodelId: input.modelId,\r\n\t\t\t\tlastPrompt: input.lastPrompt,\r\n\t\t\t\truntimeSessionFile: input.runtimeSessionFile,\r\n\t\t\t};\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: cloneSession(session),\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tsessions: [session, ...current.sessions].slice(0, 50),\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}\r\n\r\n\tupdateSession(sessionId: string, input: UpdateShellSessionInput): ShellSessionRecord | undefined {\r\n\t\treturn this.storage.withLock((current) => {\r\n\t\t\tconst index = current.sessions.findIndex((session) => session.id === sessionId);\r\n\t\t\tif (index === -1) {\r\n\t\t\t\treturn { result: undefined };\r\n\t\t\t}\r\n\r\n\t\t\tconst existing = current.sessions[index]!;\r\n\t\t\tconst nextRecord: ShellSessionRecord = {\r\n\t\t\t\t...existing,\r\n\t\t\t\ttitle: input.title ?? existing.title,\r\n\t\t\t\tsummary: input.summary ?? existing.summary,\r\n\t\t\t\tstate: input.state ?? existing.state,\r\n\t\t\t\tproviderProfileId: input.providerProfileId ?? existing.providerProfileId,\r\n\t\t\t\tproviderLabel: input.providerLabel ?? existing.providerLabel,\r\n\t\t\t\tmodelId: input.modelId ?? existing.modelId,\r\n\t\t\t\tlastPrompt: input.lastPrompt ?? existing.lastPrompt,\r\n\t\t\t\truntimeSessionFile: input.runtimeSessionFile ?? existing.runtimeSessionFile,\r\n\t\t\t\tupdatedAt: nowIso(),\r\n\t\t\t};\r\n\r\n\t\t\tconst sessions = current.sessions.slice();\r\n\t\t\tsessions[index] = nextRecord;\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: cloneSession(nextRecord),\r\n\t\t\t\tnext: {\r\n\t\t\t\t\t...current,\r\n\t\t\t\t\tsessions,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}\r\n\r\n\tmarkActive(sessionId: string): ShellSessionRecord | undefined {\r\n\t\treturn this.storage.withLock((current) => {\r\n\t\t\tlet activeRecord: ShellSessionRecord | undefined;\r\n\t\t\tconst timestamp = nowIso();\r\n\t\t\tconst sessions: ShellSessionRecord[] = current.sessions.map((session): ShellSessionRecord => {\r\n\t\t\t\tif (session.id === sessionId) {\r\n\t\t\t\t\tactiveRecord = {\r\n\t\t\t\t\t\t...session,\r\n\t\t\t\t\t\tstate: \"active\",\r\n\t\t\t\t\t\tupdatedAt: timestamp,\r\n\t\t\t\t\t};\r\n\t\t\t\t\treturn activeRecord;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (session.state === \"active\") {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\t...session,\r\n\t\t\t\t\t\tstate: \"recent\",\r\n\t\t\t\t\t\tupdatedAt: session.updatedAt,\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn session;\r\n\t\t\t});\r\n\r\n\t\t\treturn {\r\n\t\t\t\tresult: activeRecord ? cloneSession(activeRecord) : undefined,\r\n\t\t\t\tnext: activeRecord\r\n\t\t\t\t\t? {\r\n\t\t\t\t\t\t\t...current,\r\n\t\t\t\t\t\t\tsessions,\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t: undefined,\r\n\t\t\t};\r\n\t\t});\r\n\t}\r\n}\r\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,30 @@
1
+ export class ShellAnimator {
2
+ frame = 0;
3
+ startedAt = Date.now();
4
+ focusPulseUntil = 0;
5
+ constructor(ui) {
6
+ this.ui = ui;
7
+ this.interval = setInterval(() => {
8
+ this.frame += 1;
9
+ this.ui.requestRender();
10
+ }, 120);
11
+ }
12
+ dispose() {
13
+ if (!this.interval) return;
14
+ clearInterval(this.interval);
15
+ this.interval = undefined;
16
+ }
17
+ markFocusPulse() {
18
+ this.focusPulseUntil = Date.now() + 500;
19
+ }
20
+ getSnapshot() {
21
+ const elapsed = Date.now() - this.startedAt;
22
+ return {
23
+ frame: this.frame,
24
+ showMain: elapsed >= 180,
25
+ showStatus: elapsed >= 360,
26
+ showDock: elapsed >= 540,
27
+ focusPulseActive: Date.now() < this.focusPulseUntil
28
+ };
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"names":["ShellAnimator","frame","startedAt","Date","now","focusPulseUntil","constructor","ui","interval","setInterval","requestRender","dispose","clearInterval","undefined","markFocusPulse","getSnapshot","elapsed","showMain","showStatus","showDock","focusPulseActive"],"sources":["animator.ts"],"sourcesContent":["import type { TUI } from \"@mariozechner/pi-tui\";\r\n\r\nexport interface ShellAnimationSnapshot {\r\n\tframe: number;\r\n\tshowMain: boolean;\r\n\tshowStatus: boolean;\r\n\tshowDock: boolean;\r\n\tfocusPulseActive: boolean;\r\n}\r\n\r\nexport class ShellAnimator {\r\n\tprivate frame = 0;\r\n\tprivate interval: NodeJS.Timeout | undefined;\r\n\tprivate readonly startedAt = Date.now();\r\n\tprivate focusPulseUntil = 0;\r\n\r\n\tconstructor(private readonly ui: TUI) {\r\n\t\tthis.interval = setInterval(() => {\r\n\t\t\tthis.frame += 1;\r\n\t\t\tthis.ui.requestRender();\r\n\t\t}, 120);\r\n\t}\r\n\r\n\tdispose(): void {\r\n\t\tif (!this.interval) return;\r\n\t\tclearInterval(this.interval);\r\n\t\tthis.interval = undefined;\r\n\t}\r\n\r\n\tmarkFocusPulse(): void {\r\n\t\tthis.focusPulseUntil = Date.now() + 500;\r\n\t}\r\n\r\n\tgetSnapshot(): ShellAnimationSnapshot {\r\n\t\tconst elapsed = Date.now() - this.startedAt;\r\n\t\treturn {\r\n\t\t\tframe: this.frame,\r\n\t\t\tshowMain: elapsed >= 180,\r\n\t\t\tshowStatus: elapsed >= 360,\r\n\t\t\tshowDock: elapsed >= 540,\r\n\t\t\tfocusPulseActive: Date.now() < this.focusPulseUntil,\r\n\t\t};\r\n\t}\r\n}\r\n"],"mappings":"AAUA,OAAO,MAAMA,aAAa,CAAC;EAClBC,KAAK,GAAG,CAAC;EAEAC,SAAS,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;EAC/BC,eAAe,GAAG,CAAC;EAE3BC,WAAWA,CAAkBC,EAAO,EAAE;IAAA,KAATA,EAAO,GAAPA,EAAO;IACnC,IAAI,CAACC,QAAQ,GAAGC,WAAW,CAAC,MAAM;MACjC,IAAI,CAACR,KAAK,IAAI,CAAC;MACf,IAAI,CAACM,EAAE,CAACG,aAAa,CAAC,CAAC;IACxB,CAAC,EAAE,GAAG,CAAC;EACR;EAEAC,OAAOA,CAAA,EAAS;IACf,IAAI,CAAC,IAAI,CAACH,QAAQ,EAAE;IACpBI,aAAa,CAAC,IAAI,CAACJ,QAAQ,CAAC;IAC5B,IAAI,CAACA,QAAQ,GAAGK,SAAS;EAC1B;EAEAC,cAAcA,CAAA,EAAS;IACtB,IAAI,CAACT,eAAe,GAAGF,IAAI,CAACC,GAAG,CAAC,CAAC,GAAG,GAAG;EACxC;EAEAW,WAAWA,CAAA,EAA2B;IACrC,MAAMC,OAAO,GAAGb,IAAI,CAACC,GAAG,CAAC,CAAC,GAAG,IAAI,CAACF,SAAS;IAC3C,OAAO;MACND,KAAK,EAAE,IAAI,CAACA,KAAK;MACjBgB,QAAQ,EAAED,OAAO,IAAI,GAAG;MACxBE,UAAU,EAAEF,OAAO,IAAI,GAAG;MAC1BG,QAAQ,EAAEH,OAAO,IAAI,GAAG;MACxBI,gBAAgB,EAAEjB,IAAI,CAACC,GAAG,CAAC,CAAC,GAAG,IAAI,CAACC;IACrC,CAAC;EACF;AACD","ignoreList":[]}