@cpajs/cpa 0.0.0-stage → 0.0.1-alpha-2

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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -2
  3. package/dist/client/errors.d.ts +70 -0
  4. package/dist/client/errors.d.ts.map +1 -0
  5. package/dist/client/errors.js +91 -0
  6. package/dist/client/errors.js.map +1 -0
  7. package/dist/client/index.d.ts +46 -0
  8. package/dist/client/index.d.ts.map +1 -0
  9. package/dist/client/index.js +65 -0
  10. package/dist/client/index.js.map +1 -0
  11. package/dist/client/resources/config.d.ts +61 -0
  12. package/dist/client/resources/config.d.ts.map +1 -0
  13. package/dist/client/resources/config.js +80 -0
  14. package/dist/client/resources/config.js.map +1 -0
  15. package/dist/client/resources/credentials.d.ts +64 -0
  16. package/dist/client/resources/credentials.d.ts.map +1 -0
  17. package/dist/client/resources/credentials.js +94 -0
  18. package/dist/client/resources/credentials.js.map +1 -0
  19. package/dist/client/resources/download.d.ts +8 -0
  20. package/dist/client/resources/download.d.ts.map +1 -0
  21. package/dist/client/resources/download.js +17 -0
  22. package/dist/client/resources/download.js.map +1 -0
  23. package/dist/client/resources/logs.d.ts +23 -0
  24. package/dist/client/resources/logs.d.ts.map +1 -0
  25. package/dist/client/resources/logs.js +37 -0
  26. package/dist/client/resources/logs.js.map +1 -0
  27. package/dist/client/resources/oauth.d.ts +43 -0
  28. package/dist/client/resources/oauth.d.ts.map +1 -0
  29. package/dist/client/resources/oauth.js +70 -0
  30. package/dist/client/resources/oauth.js.map +1 -0
  31. package/dist/client/resources/plugins.d.ts +25 -0
  32. package/dist/client/resources/plugins.d.ts.map +1 -0
  33. package/dist/client/resources/plugins.js +47 -0
  34. package/dist/client/resources/plugins.js.map +1 -0
  35. package/dist/client/resources/requests.d.ts +14 -0
  36. package/dist/client/resources/requests.d.ts.map +1 -0
  37. package/dist/client/resources/requests.js +16 -0
  38. package/dist/client/resources/requests.js.map +1 -0
  39. package/dist/client/resources/routing.d.ts +12 -0
  40. package/dist/client/resources/routing.d.ts.map +1 -0
  41. package/dist/client/resources/routing.js +20 -0
  42. package/dist/client/resources/routing.js.map +1 -0
  43. package/dist/client/resources/server.d.ts +17 -0
  44. package/dist/client/resources/server.d.ts.map +1 -0
  45. package/dist/client/resources/server.js +49 -0
  46. package/dist/client/resources/server.js.map +1 -0
  47. package/dist/client/resources/usage.d.ts +16 -0
  48. package/dist/client/resources/usage.d.ts.map +1 -0
  49. package/dist/client/resources/usage.js +20 -0
  50. package/dist/client/resources/usage.js.map +1 -0
  51. package/dist/client/transport.d.ts +60 -0
  52. package/dist/client/transport.d.ts.map +1 -0
  53. package/dist/client/transport.js +170 -0
  54. package/dist/client/transport.js.map +1 -0
  55. package/dist/client/types/common.d.ts +38 -0
  56. package/dist/client/types/common.d.ts.map +1 -0
  57. package/dist/client/types/common.js +4 -0
  58. package/dist/client/types/common.js.map +1 -0
  59. package/dist/client/types/config.d.ts +407 -0
  60. package/dist/client/types/config.d.ts.map +1 -0
  61. package/dist/client/types/config.js +9 -0
  62. package/dist/client/types/config.js.map +1 -0
  63. package/dist/client/types/credentials.d.ts +240 -0
  64. package/dist/client/types/credentials.d.ts.map +1 -0
  65. package/dist/client/types/credentials.js +4 -0
  66. package/dist/client/types/credentials.js.map +1 -0
  67. package/dist/client/types/index.d.ts +10 -0
  68. package/dist/client/types/index.d.ts.map +1 -0
  69. package/dist/client/types/index.js +2 -0
  70. package/dist/client/types/index.js.map +1 -0
  71. package/dist/client/types/oauth.d.ts +89 -0
  72. package/dist/client/types/oauth.d.ts.map +1 -0
  73. package/dist/client/types/oauth.js +4 -0
  74. package/dist/client/types/oauth.js.map +1 -0
  75. package/dist/client/types/observability.d.ts +111 -0
  76. package/dist/client/types/observability.d.ts.map +1 -0
  77. package/dist/client/types/observability.js +4 -0
  78. package/dist/client/types/observability.js.map +1 -0
  79. package/dist/client/types/plugins.d.ts +171 -0
  80. package/dist/client/types/plugins.d.ts.map +1 -0
  81. package/dist/client/types/plugins.js +4 -0
  82. package/dist/client/types/plugins.js.map +1 -0
  83. package/dist/client/types/requests.d.ts +16 -0
  84. package/dist/client/types/requests.d.ts.map +1 -0
  85. package/dist/client/types/requests.js +4 -0
  86. package/dist/client/types/requests.js.map +1 -0
  87. package/dist/client/types/routing.d.ts +50 -0
  88. package/dist/client/types/routing.d.ts.map +1 -0
  89. package/dist/client/types/routing.js +4 -0
  90. package/dist/client/types/routing.js.map +1 -0
  91. package/dist/client/types/server.d.ts +15 -0
  92. package/dist/client/types/server.d.ts.map +1 -0
  93. package/dist/client/types/server.js +4 -0
  94. package/dist/client/types/server.js.map +1 -0
  95. package/dist/index.d.ts +3 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +3 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/server/binary.d.ts +35 -0
  100. package/dist/server/binary.d.ts.map +1 -0
  101. package/dist/server/binary.js +108 -0
  102. package/dist/server/binary.js.map +1 -0
  103. package/dist/server/config.d.ts +37 -0
  104. package/dist/server/config.d.ts.map +1 -0
  105. package/dist/server/config.js +103 -0
  106. package/dist/server/config.js.map +1 -0
  107. package/dist/server/cpa-version.d.ts +12 -0
  108. package/dist/server/cpa-version.d.ts.map +1 -0
  109. package/dist/server/cpa-version.js +12 -0
  110. package/dist/server/cpa-version.js.map +1 -0
  111. package/dist/server/errors.d.ts +36 -0
  112. package/dist/server/errors.d.ts.map +1 -0
  113. package/dist/server/errors.js +26 -0
  114. package/dist/server/errors.js.map +1 -0
  115. package/dist/server/index.d.ts +18 -0
  116. package/dist/server/index.d.ts.map +1 -0
  117. package/dist/server/index.js +208 -0
  118. package/dist/server/index.js.map +1 -0
  119. package/dist/server/process.d.ts +27 -0
  120. package/dist/server/process.d.ts.map +1 -0
  121. package/dist/server/process.js +114 -0
  122. package/dist/server/process.js.map +1 -0
  123. package/dist/server/readiness.d.ts +13 -0
  124. package/dist/server/readiness.d.ts.map +1 -0
  125. package/dist/server/readiness.js +56 -0
  126. package/dist/server/readiness.js.map +1 -0
  127. package/dist/server/redact.d.ts +3 -0
  128. package/dist/server/redact.d.ts.map +1 -0
  129. package/dist/server/redact.js +16 -0
  130. package/dist/server/redact.js.map +1 -0
  131. package/dist/server/targets.d.ts +48 -0
  132. package/dist/server/targets.d.ts.map +1 -0
  133. package/dist/server/targets.js +90 -0
  134. package/dist/server/targets.js.map +1 -0
  135. package/dist/server/types.d.ts +87 -0
  136. package/dist/server/types.d.ts.map +1 -0
  137. package/dist/server/types.js +2 -0
  138. package/dist/server/types.js.map +1 -0
  139. package/package.json +83 -4
  140. package/src/client/errors.ts +104 -0
  141. package/src/client/index.ts +95 -0
  142. package/src/client/resources/config.ts +124 -0
  143. package/src/client/resources/credentials.ts +133 -0
  144. package/src/client/resources/download.ts +21 -0
  145. package/src/client/resources/logs.ts +46 -0
  146. package/src/client/resources/oauth.ts +105 -0
  147. package/src/client/resources/plugins.ts +70 -0
  148. package/src/client/resources/requests.ts +20 -0
  149. package/src/client/resources/routing.ts +29 -0
  150. package/src/client/resources/server.ts +52 -0
  151. package/src/client/resources/usage.ts +27 -0
  152. package/src/client/transport.ts +215 -0
  153. package/src/client/types/common.ts +38 -0
  154. package/src/client/types/config.ts +248 -0
  155. package/src/client/types/credentials.ts +195 -0
  156. package/src/client/types/index.ts +9 -0
  157. package/src/client/types/oauth.ts +71 -0
  158. package/src/client/types/observability.ts +93 -0
  159. package/src/client/types/plugins.ts +136 -0
  160. package/src/client/types/requests.ts +19 -0
  161. package/src/client/types/routing.ts +44 -0
  162. package/src/client/types/server.ts +18 -0
  163. package/src/index.ts +2 -0
  164. package/src/server/binary.ts +128 -0
  165. package/src/server/config.ts +128 -0
  166. package/src/server/cpa-version.ts +11 -0
  167. package/src/server/errors.ts +48 -0
  168. package/src/server/index.ts +236 -0
  169. package/src/server/process.ts +124 -0
  170. package/src/server/readiness.ts +58 -0
  171. package/src/server/redact.ts +14 -0
  172. package/src/server/targets.ts +133 -0
  173. package/src/server/types.ts +93 -0
@@ -0,0 +1,136 @@
1
+ // Types for /v8/management/plugins.
2
+ // Derived from CLIProxyAPI v8.0.43; see docs/api-coverage.md.
3
+
4
+ // ---------- plugins ----------
5
+ export type PluginConfigFieldType = "string" | "number" | "integer" | "boolean" | "enum" | "array" | "object";
6
+ export interface PluginConfigFieldInfo {
7
+ default: unknown;
8
+ name: string;
9
+ type: PluginConfigFieldType | string;
10
+ enum_values: string[] | null;
11
+ description: string;
12
+ }
13
+ export interface PluginMenuInfo { path: string; menu: string; description: string }
14
+ export interface PluginMetadataInfo {
15
+ name: string;
16
+ version: string;
17
+ author: string;
18
+ github_repository: string;
19
+ logo: string;
20
+ config_fields: PluginConfigFieldInfo[];
21
+ }
22
+ export interface PluginListEntry {
23
+ id: string;
24
+ path: string;
25
+ configured: boolean;
26
+ registered: boolean;
27
+ enabled: boolean;
28
+ effective_enabled: boolean;
29
+ supports_oauth: boolean;
30
+ oauth_provider: string;
31
+ supports_quota: boolean;
32
+ quota_provider?: string;
33
+ logo: string;
34
+ config_fields: PluginConfigFieldInfo[];
35
+ menus: PluginMenuInfo[];
36
+ metadata: PluginMetadataInfo | null;
37
+ }
38
+ export interface PluginListResponse { plugins_enabled: boolean; plugins_dir: string; plugins: PluginListEntry[] }
39
+
40
+ export interface DeletePluginResponse {
41
+ status: "deleted";
42
+ id: string;
43
+ path: string;
44
+ file_deleted: boolean;
45
+ configured_removed: boolean;
46
+ restart_required: false;
47
+ }
48
+
49
+ // ---------- plugins/store ----------
50
+ export interface PluginStoreSource { id: string; name: string; url: string }
51
+ export interface PluginStoreSourceError { source_id: string; source_name: string; source_url: string; message: string }
52
+ export interface PluginStorePlatform { goos: string; goarch: string }
53
+ export type PluginInstallType = "github-release" | "direct";
54
+ export interface PluginStoreListEntry {
55
+ store_id: string; // "<source_id>/<id>"
56
+ source_id: string;
57
+ source_name: string;
58
+ source_url: string;
59
+ id: string;
60
+ name: string;
61
+ description: string;
62
+ author: string;
63
+ version: string;
64
+ repository: string;
65
+ install_type: PluginInstallType | string;
66
+ auth_required: boolean;
67
+ auth_configured: boolean;
68
+ platforms?: PluginStorePlatform[];
69
+ logo?: string;
70
+ homepage?: string;
71
+ license?: string;
72
+ tags?: string[];
73
+ installed: boolean;
74
+ installed_version: string;
75
+ installed_source_id?: string;
76
+ install_source_status?: "matched" | "different" | "unknown" | "assumed";
77
+ path: string;
78
+ configured: boolean;
79
+ registered: boolean;
80
+ enabled: boolean;
81
+ effective_enabled: boolean;
82
+ update_available: boolean;
83
+ }
84
+ export interface PluginStoreListResponse {
85
+ plugins_enabled: boolean;
86
+ plugins_dir: string;
87
+ sources: PluginStoreSource[];
88
+ source_errors?: PluginStoreSourceError[];
89
+ plugins: PluginStoreListEntry[];
90
+ }
91
+ export interface PluginInstallQuery { source?: string; version?: string }
92
+ export interface PluginInstallRequest { version?: string }
93
+ export interface PluginInstallResponse {
94
+ status: "installed";
95
+ source_id: string;
96
+ source_name: string;
97
+ source_url: string;
98
+ id: string;
99
+ version: string;
100
+ install_type: PluginInstallType | string;
101
+ path: string;
102
+ plugins_enabled: boolean;
103
+ restart_required: boolean; // currently always false on success
104
+ }
105
+ export interface PluginStoreRateLimitedError {
106
+ error: "plugin_store_rate_limited";
107
+ message: string;
108
+ retry_after: number;
109
+ retry_at: string;
110
+ }
111
+
112
+ // ---------- plugins/:id/quota ----------
113
+ export interface PluginQuotaQuery { auth_index: string } // "authIndex" also accepted
114
+ export interface PluginQuotaBody { // POST / DELETE body
115
+ auth_index?: string;
116
+ authIndex?: string;
117
+ AuthIndex?: string;
118
+ }
119
+ export interface QuotaSubscription { plan?: string; tierName?: string; tierId?: string }
120
+ export interface QuotaBucket { window?: string; remainingFraction: number; resetTime?: string; description?: string }
121
+ export interface QuotaGroup { displayName?: string; buckets?: QuotaBucket[] }
122
+ export interface QuotaMetric {
123
+ key: string;
124
+ label: string;
125
+ value: number;
126
+ unit?: string;
127
+ format?: "number" | "currency";
128
+ currency?: string;
129
+ }
130
+ export interface QuotaFetchResponse {
131
+ subscription?: QuotaSubscription;
132
+ summary?: QuotaMetric[];
133
+ serverTimeOffsetMs?: number;
134
+ groups?: QuotaGroup[];
135
+ }
136
+ export interface PluginQuotaResetResponse { status: "ok"; auth_index: string; message?: string }
@@ -0,0 +1,19 @@
1
+ // Types for /v8/management/requests.
2
+ // Derived from CLIProxyAPI v8.0.43; see docs/api-coverage.md.
3
+
4
+ // ---------- requests/api-call ----------
5
+ export interface ApiCallRequest {
6
+ auth_index?: string; // also accepted: authIndex, AuthIndex
7
+ authIndex?: string;
8
+ AuthIndex?: string;
9
+ method: string; // required
10
+ url: string; // required, absolute
11
+ proxy_url?: string; // http(s)://, socks5://, socks5h://, "direct", "none"
12
+ header?: Record<string, string>; // "$TOKEN$" substituted; "Host" overrides req.Host
13
+ data?: string; // raw body; "$TOKEN$" substituted
14
+ }
15
+ export interface ApiCallResponse {
16
+ status_code: number;
17
+ header: Record<string, string[]>;
18
+ body: string;
19
+ }
@@ -0,0 +1,44 @@
1
+ // Types for /v8/management/routing.
2
+ // Derived from CLIProxyAPI v8.0.43; see docs/api-coverage.md.
3
+
4
+ // ---------- routing/cooldown/reset ----------
5
+ export interface CooldownResetRequest { auth_index: string }
6
+ export interface CooldownResetResponse { status: "ok"; auth_index: string; models: string[] }
7
+
8
+ // ---------- routing/model-definitions/:channel ----------
9
+ export type StaticModelChannel =
10
+ | "claude" | "gemini" | "gemini-interactions" | "vertex" | "aistudio" | "codex"
11
+ | "kimi" | "kimi-ai" | "kimi.ai" | "kimi.com" | "antigravity"
12
+ | "xai" | "x-ai" | "grok" | "devin" | "meta" | "muse";
13
+ export interface ThinkingSupport {
14
+ min?: number;
15
+ max?: number;
16
+ zero_allowed?: boolean;
17
+ dynamic_allowed?: boolean;
18
+ disable_type?: string;
19
+ levels?: string[];
20
+ }
21
+ export interface ModelInfo {
22
+ id: string;
23
+ object: string;
24
+ created: number;
25
+ owned_by: string;
26
+ type: string;
27
+ display_name?: string;
28
+ name?: string;
29
+ version?: string;
30
+ description?: string;
31
+ inputTokenLimit?: number;
32
+ outputTokenLimit?: number;
33
+ supportedGenerationMethods?: string[];
34
+ context_length?: number;
35
+ max_completion_tokens?: number;
36
+ supported_parameters?: string[];
37
+ supportedInputModalities?: string[];
38
+ supportedOutputModalities?: string[];
39
+ supports_web_search?: boolean;
40
+ thinking?: ThinkingSupport;
41
+ config?: { override_header?: Record<string, string> };
42
+ }
43
+ export interface ModelDefinitionsResponse { channel: string; models: ModelInfo[] }
44
+ export interface UnknownChannelError { error: "unknown channel"; channel: string }
@@ -0,0 +1,18 @@
1
+ // Types for /healthz and /v8/management/server.
2
+ // Derived from CLIProxyAPI v8.0.43; see docs/api-coverage.md.
3
+
4
+ // ---------- /healthz ----------
5
+ export interface HealthzResponse { status: "ok" }
6
+
7
+ // ---------- server/latest-version ----------
8
+ export interface LatestVersionResponse { "latest-version": string } // raw GitHub tag, e.g. "v8.0.43"
9
+ export type LatestVersionErrorCode =
10
+ | "request_create_failed" | "request_failed" | "unexpected_status" | "decode_failed" | "invalid_response";
11
+
12
+ /** Response headers set by the management auth middleware (handler.go:325-328). */
13
+ export interface ServerInfo {
14
+ version: string; // X-CPA-VERSION (e.g. "8.0.43"; "dev" for unversioned builds)
15
+ commit: string; // X-CPA-COMMIT
16
+ buildDate: string; // X-CPA-BUILD-DATE ("unknown" when not stamped)
17
+ supportPlugin: boolean; // X-CPA-SUPPORT-PLUGIN === "1"
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './client/index.ts';
2
+ export * from './server/index.ts';
@@ -0,0 +1,128 @@
1
+ import { accessSync, chmodSync, constants, existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { CpaBinaryError } from './errors.ts';
5
+ import { TARGETS, UNSUPPORTED_PLATFORMS, binaryName, packageName, type Target } from './targets.ts';
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+ export type Libc = 'glibc' | 'musl';
10
+
11
+ export interface HostPlatform {
12
+ os: NodeJS.Platform;
13
+ cpu: NodeJS.Architecture;
14
+ /** Only meaningful on Linux. */
15
+ libc?: Libc;
16
+ }
17
+
18
+ export interface ResolvedBinary {
19
+ path: string;
20
+ /** The platform package the binary came from, or undefined for `binaryPath` overrides. */
21
+ packageName?: string;
22
+ target?: Target;
23
+ }
24
+
25
+ /** Detects the C library of the running Linux system. */
26
+ export function detectLibc(): Libc | undefined {
27
+ if (process.platform !== 'linux') return undefined;
28
+ try {
29
+ const report = process.report?.getReport() as { header?: { glibcVersionRuntime?: string } } | undefined;
30
+ if (report?.header) return report.header.glibcVersionRuntime ? 'glibc' : 'musl';
31
+ } catch {
32
+ // Fall through to the filesystem probe; some runtimes do not implement process.report.
33
+ }
34
+ try {
35
+ if (readdirSync('/lib').some((name) => name.startsWith('ld-musl-'))) return 'musl';
36
+ } catch {
37
+ // Ignore unreadable /lib.
38
+ }
39
+ return 'glibc';
40
+ }
41
+
42
+ export function currentPlatform(): HostPlatform {
43
+ const libc = detectLibc();
44
+ return libc ? { os: process.platform, cpu: process.arch, libc } : { os: process.platform, cpu: process.arch };
45
+ }
46
+
47
+ /**
48
+ * Returns the targets that can run on a host, best match first.
49
+ *
50
+ * On Linux the libc-specific build is preferred, followed by the static
51
+ * portable build of the other libc, which runs anywhere on the same CPU.
52
+ */
53
+ export function candidateTargets(host: HostPlatform): Target[] {
54
+ const matches = TARGETS.filter((t) => t.os === host.os && t.cpu === host.cpu);
55
+ const score = (t: Target): number => {
56
+ if (t.libc === undefined || t.libc === host.libc) return 0;
57
+ return t.build === 'portable' ? 1 : Number.POSITIVE_INFINITY;
58
+ };
59
+ return matches.filter((t) => Number.isFinite(score(t))).sort((a, b) => score(a) - score(b));
60
+ }
61
+
62
+ function ensureExecutable(path: string, source: string): void {
63
+ if (!existsSync(path)) {
64
+ throw new CpaBinaryError('missing', `CLIProxyAPI binary not found at ${path} (${source}).`);
65
+ }
66
+ if (!statSync(path).isFile()) {
67
+ throw new CpaBinaryError('missing', `CLIProxyAPI binary path ${path} is not a file (${source}).`);
68
+ }
69
+ if (process.platform === 'win32') return;
70
+ try {
71
+ accessSync(path, constants.X_OK);
72
+ } catch {
73
+ // Some package managers drop the executable bit on extraction; restore it once.
74
+ try {
75
+ chmodSync(path, 0o755);
76
+ accessSync(path, constants.X_OK);
77
+ } catch {
78
+ throw new CpaBinaryError('not-executable', `CLIProxyAPI binary at ${path} is not executable (${source}).`);
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Locates the CLIProxyAPI executable.
85
+ *
86
+ * `binaryPath` overrides the lookup. Otherwise the platform package installed
87
+ * through the main package's optionalDependencies is used.
88
+ */
89
+ export function resolveBinary(options: { binaryPath?: string; platform?: HostPlatform } = {}): ResolvedBinary {
90
+ if (options.binaryPath) {
91
+ ensureExecutable(options.binaryPath, 'binaryPath option');
92
+ return { path: options.binaryPath };
93
+ }
94
+
95
+ const host = options.platform ?? currentPlatform();
96
+ const candidates = candidateTargets(host);
97
+ const hostLabel = `${host.os}-${host.cpu}${host.libc ? ` (${host.libc})` : ''}`;
98
+ if (candidates.length === 0) {
99
+ const supported = TARGETS.map((t) => t.id).join(', ');
100
+ const reason = UNSUPPORTED_PLATFORMS[`${host.os}-${host.cpu}`] ?? UNSUPPORTED_PLATFORMS[`${host.os}-*`];
101
+ throw new CpaBinaryError(
102
+ 'unsupported-platform',
103
+ `No prebuilt CLIProxyAPI binary for ${hostLabel}${reason ? ` (${reason})` : ''}. ` +
104
+ `Supported targets: ${supported}. Pass binaryPath to use your own build.`,
105
+ );
106
+ }
107
+
108
+ for (const target of candidates) {
109
+ const name = packageName(target);
110
+ let manifest: string;
111
+ try {
112
+ manifest = require.resolve(`${name}/package.json`);
113
+ } catch {
114
+ continue;
115
+ }
116
+ const path = join(dirname(manifest), 'bin', binaryName(target));
117
+ ensureExecutable(path, name);
118
+ return { path, packageName: name, target };
119
+ }
120
+
121
+ const expected = candidates.map(packageName).join(' or ');
122
+ throw new CpaBinaryError(
123
+ 'package-not-installed',
124
+ `The CLIProxyAPI binary package for ${hostLabel} is not installed (expected ${expected}). ` +
125
+ 'It is an optional dependency of @cpajs/cpa; reinstall without --no-optional / --omit=optional, ' +
126
+ 'make sure your lockfile was not created on another platform without it, or pass binaryPath.',
127
+ );
128
+ }
@@ -0,0 +1,128 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import type { CpaConfig } from '../client/types/config.ts';
5
+ import { CpaStartError } from './errors.ts';
6
+
7
+ export const DEFAULT_PORT = 8317;
8
+ export const MANAGED_CONFIG_FILE = 'config.yaml';
9
+
10
+ export interface ListenSettings {
11
+ /** The `server.host` value as configured; empty means all interfaces. */
12
+ host: string;
13
+ port: number;
14
+ tls: boolean;
15
+ }
16
+
17
+ type Json = Record<string, unknown>;
18
+
19
+ function isRecord(value: unknown): value is Json {
20
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
21
+ }
22
+
23
+ /**
24
+ * Reads listener settings from a parsed config document. The v8 `server.*`
25
+ * keys win over the legacy top-level `host`/`port`/`tls` keys, mirroring CPA.
26
+ */
27
+ export function readListenSettings(doc: unknown): ListenSettings {
28
+ const root = isRecord(doc) ? doc : {};
29
+ const server = isRecord(root.server) ? root.server : {};
30
+ const pick = (key: string): unknown => (key in server ? server[key] : root[key]);
31
+
32
+ const host = pick('host');
33
+ const port = pick('port');
34
+ const tls = pick('tls');
35
+ const parsedPort = typeof port === 'number' ? port : typeof port === 'string' ? Number(port) : Number.NaN;
36
+ return {
37
+ host: typeof host === 'string' ? host.trim() : '',
38
+ // CPA falls back to 8317 when the port is missing or not positive.
39
+ port: Number.isInteger(parsedPort) && parsedPort > 0 ? parsedPort : port === 0 ? 0 : DEFAULT_PORT,
40
+ tls: isRecord(tls) && tls.enable === true,
41
+ };
42
+ }
43
+
44
+ const SECRET_FIELDS = new Set(['secret-key', 'api-key', 'github-token', 'credential', 'access_token', 'refresh_token']);
45
+ const SECRET_LISTS = new Set(['api-keys']);
46
+
47
+ /** Secrets that appear in a config document and must never reach logs. */
48
+ export function configSecrets(doc: unknown): string[] {
49
+ const out: string[] = [];
50
+ const walk = (value: unknown, key: string | undefined): void => {
51
+ if (typeof value === 'string') {
52
+ if (key !== undefined && SECRET_FIELDS.has(key) && value !== '') out.push(value);
53
+ return;
54
+ }
55
+ if (Array.isArray(value)) {
56
+ for (const item of value) {
57
+ if (typeof item === 'string' && key !== undefined && SECRET_LISTS.has(key) && item !== '') out.push(item);
58
+ else walk(item, undefined);
59
+ }
60
+ return;
61
+ }
62
+ if (isRecord(value)) for (const [k, v] of Object.entries(value)) walk(v, k);
63
+ };
64
+ walk(doc, undefined);
65
+ return out;
66
+ }
67
+
68
+ export interface PreparedConfig {
69
+ configPath: string;
70
+ listen: ListenSettings;
71
+ secrets: string[];
72
+ /** True when the file was written by this package. */
73
+ managed: boolean;
74
+ }
75
+
76
+ /**
77
+ * Writes a `config` object to the managed config file in `stateDir`.
78
+ *
79
+ * The object is serialised as JSON, which is valid YAML and is read by CPA's
80
+ * YAML loader unchanged. Listener and auth directory defaults are filled in so
81
+ * the process stays inside `stateDir` and binds to loopback unless told otherwise.
82
+ */
83
+ export async function writeManagedConfig(
84
+ config: CpaConfig,
85
+ stateDir: string,
86
+ listen: { host: string; port: number },
87
+ ): Promise<PreparedConfig> {
88
+ const doc = structuredClone(config) as Json;
89
+ const server = isRecord(doc.server) ? doc.server : {};
90
+ doc.server = { ...server, host: listen.host, port: listen.port };
91
+ if (doc['config-version'] === undefined) doc['config-version'] = 8;
92
+ const oauth = isRecord(doc.oauth) ? doc.oauth : {};
93
+ // Keep credentials inside stateDir unless the config chooses a location (v8 or legacy key).
94
+ if (oauth['auth-dir'] === undefined && doc['auth-dir'] === undefined) {
95
+ const authDir = join(stateDir, 'auths');
96
+ await mkdir(authDir, { recursive: true });
97
+ doc.oauth = { ...oauth, 'auth-dir': authDir };
98
+ }
99
+
100
+ const configPath = join(stateDir, MANAGED_CONFIG_FILE);
101
+ await writeFile(configPath, `${JSON.stringify(doc, null, 2)}\n`, { mode: 0o600 });
102
+ return { configPath, listen: readListenSettings(doc), secrets: configSecrets(doc), managed: true };
103
+ }
104
+
105
+ /** Reads an existing config file without modifying it. */
106
+ export async function inspectConfigFile(configPath: string): Promise<PreparedConfig> {
107
+ const absolute = resolve(configPath);
108
+ let text: string;
109
+ try {
110
+ text = await readFile(absolute, 'utf8');
111
+ } catch (cause) {
112
+ throw new CpaStartError('invalid-options', `Cannot read configPath ${absolute}.`, { cause });
113
+ }
114
+ let doc: unknown;
115
+ try {
116
+ doc = parseYaml(text);
117
+ } catch (cause) {
118
+ throw new CpaStartError('invalid-options', `configPath ${absolute} is not valid YAML.`, { cause });
119
+ }
120
+ const listen = readListenSettings(doc);
121
+ if (listen.port === 0) {
122
+ throw new CpaStartError(
123
+ 'invalid-options',
124
+ `configPath ${absolute} sets port 0. CLIProxyAPI does not report the port it picks, so set a fixed port or pass a config object instead.`,
125
+ );
126
+ }
127
+ return { configPath: absolute, listen, secrets: configSecrets(doc), managed: false };
128
+ }
@@ -0,0 +1,11 @@
1
+ /** The CLIProxyAPI release this package version is built and verified against. */
2
+ export const CPA_RELEASE = {
3
+ version: '8.0.43',
4
+ tag: 'v8.0.43',
5
+ commit: '307803510bce87f986fcf7b1a84190f105c7b4aa',
6
+ /** Commit timestamp, used as the reproducible build date. */
7
+ commitDate: '2026-10-25T14:45:00Z',
8
+ repository: 'https://github.com/router-for-me/CLIProxyAPI',
9
+ /** Go toolchain required by the pinned go.mod. */
10
+ goVersion: '1.26.10',
11
+ } as const;
@@ -0,0 +1,48 @@
1
+ export type CpaBinaryErrorCode = 'unsupported-platform' | 'package-not-installed' | 'missing' | 'not-executable';
2
+
3
+ /** The CLIProxyAPI executable could not be located or used. */
4
+ export class CpaBinaryError extends Error {
5
+ override readonly name = 'CpaBinaryError';
6
+ readonly code: CpaBinaryErrorCode;
7
+
8
+ constructor(code: CpaBinaryErrorCode, message: string) {
9
+ super(message);
10
+ this.code = code;
11
+ }
12
+ }
13
+
14
+ export type CpaStartErrorCode =
15
+ /** Invalid `startServer` options. */
16
+ | 'invalid-options'
17
+ /** The configured port is already taken. */
18
+ | 'port-in-use'
19
+ /** The process exited before it became ready. */
20
+ | 'exited'
21
+ /** The process could not be spawned. */
22
+ | 'spawn-failed'
23
+ /** Readiness was not reached within `startupTimeoutMs`. */
24
+ | 'timeout'
25
+ /** The `signal` passed to `startServer` was aborted. */
26
+ | 'aborted';
27
+
28
+ /** `startServer` failed. Secrets are redacted from `message` and `output`. */
29
+ export class CpaStartError extends Error {
30
+ override readonly name = 'CpaStartError';
31
+ readonly code: CpaStartErrorCode;
32
+ /** Recent process output (redacted), useful for diagnosing failures. */
33
+ readonly output: string;
34
+ readonly exitCode: number | null;
35
+ readonly signal: NodeJS.Signals | null;
36
+
37
+ constructor(
38
+ code: CpaStartErrorCode,
39
+ message: string,
40
+ details: { output?: string; exitCode?: number | null; signal?: NodeJS.Signals | null; cause?: unknown } = {},
41
+ ) {
42
+ super(message, details.cause === undefined ? undefined : { cause: details.cause });
43
+ this.code = code;
44
+ this.output = details.output ?? '';
45
+ this.exitCode = details.exitCode ?? null;
46
+ this.signal = details.signal ?? null;
47
+ }
48
+ }