@nimblebrain/mpak 0.0.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -0
- package/CLAUDE.md +117 -0
- package/LICENSE +201 -0
- package/README.md +206 -0
- package/dist/commands/packages/pull.d.ts +11 -0
- package/dist/commands/packages/pull.d.ts.map +1 -0
- package/dist/commands/packages/pull.js +72 -0
- package/dist/commands/packages/pull.js.map +1 -0
- package/dist/commands/packages/search.d.ts +12 -0
- package/dist/commands/packages/search.d.ts.map +1 -0
- package/dist/commands/packages/search.js +63 -0
- package/dist/commands/packages/search.js.map +1 -0
- package/dist/commands/packages/show.d.ts +8 -0
- package/dist/commands/packages/show.d.ts.map +1 -0
- package/dist/commands/packages/show.js +121 -0
- package/dist/commands/packages/show.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api/registry-client.d.ts +63 -0
- package/dist/lib/api/registry-client.d.ts.map +1 -0
- package/dist/lib/api/registry-client.js +167 -0
- package/dist/lib/api/registry-client.js.map +1 -0
- package/dist/program.d.ts +8 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +69 -0
- package/dist/program.js.map +1 -0
- package/dist/utils/config-manager.d.ts +23 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +65 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/errors.d.ts +12 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +27 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/version.d.ts +5 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +19 -0
- package/dist/utils/version.js.map +1 -0
- package/eslint.config.js +63 -0
- package/package.json +48 -0
- package/src/commands/packages/pull.ts +96 -0
- package/src/commands/packages/search.ts +83 -0
- package/src/commands/packages/show.ts +138 -0
- package/src/index.ts +15 -0
- package/src/lib/api/registry-client.ts +223 -0
- package/src/lib/api/schema.d.ts +548 -0
- package/src/program.test.ts +24 -0
- package/src/program.ts +76 -0
- package/src/utils/config-manager.test.ts +60 -0
- package/src/utils/config-manager.ts +81 -0
- package/src/utils/errors.test.ts +25 -0
- package/src/utils/errors.ts +33 -0
- package/src/utils/version.test.ts +16 -0
- package/src/utils/version.ts +18 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { ConfigManager } from '../../utils/config-manager.js';
|
|
2
|
+
import type { paths } from './schema.js';
|
|
3
|
+
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Types derived from OpenAPI schema (generated by openapi-typescript)
|
|
6
|
+
// =============================================================================
|
|
7
|
+
|
|
8
|
+
// Helper to extract response type from path
|
|
9
|
+
type ResponseOf<T> = T extends { responses: { 200: { content: { 'application/json': infer R } } } } ? R : never;
|
|
10
|
+
|
|
11
|
+
// V1 API Response Types
|
|
12
|
+
export type BundleSearchResponse = ResponseOf<paths['/v1/bundles/search']['get']>;
|
|
13
|
+
export type BundleDetailResponse = ResponseOf<paths['/v1/bundles/@{scope}/{package}']['get']>;
|
|
14
|
+
export type VersionsResponse = ResponseOf<paths['/v1/bundles/@{scope}/{package}/versions']['get']>;
|
|
15
|
+
export type DownloadInfoResponse = ResponseOf<paths['/v1/bundles/@{scope}/{package}/versions/{version}/download']['get']>;
|
|
16
|
+
|
|
17
|
+
// Convenience aliases
|
|
18
|
+
export type Bundle = BundleSearchResponse['bundles'][number];
|
|
19
|
+
export type BundleDetail = BundleDetailResponse;
|
|
20
|
+
export type VersionInfo = VersionsResponse['versions'][number];
|
|
21
|
+
export type DownloadInfo = DownloadInfoResponse;
|
|
22
|
+
|
|
23
|
+
/** Platform identifier (os + arch) */
|
|
24
|
+
export interface Platform {
|
|
25
|
+
os: string; // darwin, linux, win32, any
|
|
26
|
+
arch: string; // x64, arm64, any
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Registry Client (v1 API only)
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Client for interacting with the mpak registry v1 API
|
|
35
|
+
*
|
|
36
|
+
* All methods use the public /v1/bundles API (unauthenticated)
|
|
37
|
+
*/
|
|
38
|
+
export class RegistryClient {
|
|
39
|
+
private baseUrl: string;
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
const configManager = new ConfigManager();
|
|
43
|
+
this.baseUrl = configManager.getRegistryUrl();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Search bundles
|
|
48
|
+
*/
|
|
49
|
+
async searchBundles(query: string, options: {
|
|
50
|
+
type?: string;
|
|
51
|
+
sort?: 'downloads' | 'recent' | 'name';
|
|
52
|
+
limit?: number;
|
|
53
|
+
offset?: number;
|
|
54
|
+
} = {}): Promise<BundleSearchResponse> {
|
|
55
|
+
const params = new URLSearchParams();
|
|
56
|
+
if (query) params.set('q', query);
|
|
57
|
+
if (options.type) params.set('type', options.type);
|
|
58
|
+
if (options.sort) params.set('sort', options.sort);
|
|
59
|
+
if (options.limit) params.set('limit', options.limit.toString());
|
|
60
|
+
if (options.offset) params.set('offset', options.offset.toString());
|
|
61
|
+
|
|
62
|
+
const url = `${this.baseUrl}/v1/bundles/search?${params.toString()}`;
|
|
63
|
+
const response = await fetch(url);
|
|
64
|
+
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const error: any = await response.json().catch(() => ({ error: response.statusText }));
|
|
67
|
+
const errorMessage = typeof error.error === 'string'
|
|
68
|
+
? error.error
|
|
69
|
+
: error.error?.message || `Failed to search bundles: ${response.statusText}`;
|
|
70
|
+
throw new Error(errorMessage);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return response.json() as Promise<BundleSearchResponse>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get bundle details
|
|
78
|
+
*/
|
|
79
|
+
async getBundle(packageName: string): Promise<BundleDetail> {
|
|
80
|
+
if (!packageName.startsWith('@')) {
|
|
81
|
+
throw new Error('Package name must be scoped (e.g., @username/package-name)');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const url = `${this.baseUrl}/v1/bundles/${packageName}`;
|
|
85
|
+
const response = await fetch(url);
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
if (response.status === 404) {
|
|
89
|
+
throw new Error(`Bundle not found: ${packageName}`);
|
|
90
|
+
}
|
|
91
|
+
const error: any = await response.json().catch(() => ({ error: response.statusText }));
|
|
92
|
+
const errorMessage = typeof error.error === 'string'
|
|
93
|
+
? error.error
|
|
94
|
+
: error.error?.message || `Failed to get bundle details: ${response.statusText}`;
|
|
95
|
+
throw new Error(errorMessage);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return response.json() as Promise<BundleDetail>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get versions with platform info
|
|
103
|
+
*/
|
|
104
|
+
async getVersions(packageName: string): Promise<VersionsResponse> {
|
|
105
|
+
if (!packageName.startsWith('@')) {
|
|
106
|
+
throw new Error('Package name must be scoped (e.g., @username/package-name)');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const url = `${this.baseUrl}/v1/bundles/${packageName}/versions`;
|
|
110
|
+
const response = await fetch(url);
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
if (response.status === 404) {
|
|
114
|
+
throw new Error(`Bundle not found: ${packageName}`);
|
|
115
|
+
}
|
|
116
|
+
const error: any = await response.json().catch(() => ({ error: response.statusText }));
|
|
117
|
+
const errorMessage = typeof error.error === 'string'
|
|
118
|
+
? error.error
|
|
119
|
+
: error.error?.message || `Failed to get versions: ${response.statusText}`;
|
|
120
|
+
throw new Error(errorMessage);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return response.json() as Promise<VersionsResponse>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Detect the current platform
|
|
128
|
+
*/
|
|
129
|
+
static detectPlatform(): Platform {
|
|
130
|
+
const platform = process.platform;
|
|
131
|
+
const arch = process.arch;
|
|
132
|
+
|
|
133
|
+
// Map Node.js platform names to MCPB spec names
|
|
134
|
+
let os: string;
|
|
135
|
+
switch (platform) {
|
|
136
|
+
case 'darwin':
|
|
137
|
+
os = 'darwin';
|
|
138
|
+
break;
|
|
139
|
+
case 'win32':
|
|
140
|
+
os = 'win32';
|
|
141
|
+
break;
|
|
142
|
+
case 'linux':
|
|
143
|
+
os = 'linux';
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
os = 'any';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Map Node.js arch names to MCPB spec names
|
|
150
|
+
let mcpbArch: string;
|
|
151
|
+
switch (arch) {
|
|
152
|
+
case 'x64':
|
|
153
|
+
mcpbArch = 'x64';
|
|
154
|
+
break;
|
|
155
|
+
case 'arm64':
|
|
156
|
+
mcpbArch = 'arm64';
|
|
157
|
+
break;
|
|
158
|
+
default:
|
|
159
|
+
mcpbArch = 'any';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { os, arch: mcpbArch };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get download info for a bundle version
|
|
167
|
+
*/
|
|
168
|
+
async getDownloadInfo(packageName: string, version?: string, platform?: Platform): Promise<DownloadInfo> {
|
|
169
|
+
if (!packageName.startsWith('@')) {
|
|
170
|
+
throw new Error('Package name must be scoped (e.g., @username/package-name)');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const versionPath = version ? `/versions/${version}` : '/versions/latest';
|
|
174
|
+
const params = new URLSearchParams();
|
|
175
|
+
|
|
176
|
+
if (platform) {
|
|
177
|
+
params.set('os', platform.os);
|
|
178
|
+
params.set('arch', platform.arch);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const queryString = params.toString();
|
|
182
|
+
const url = `${this.baseUrl}/v1/bundles/${packageName}${versionPath}/download${queryString ? `?${queryString}` : ''}`;
|
|
183
|
+
|
|
184
|
+
const response = await fetch(url, {
|
|
185
|
+
headers: {
|
|
186
|
+
Accept: 'application/json',
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
if (response.status === 404) {
|
|
192
|
+
throw new Error(version
|
|
193
|
+
? `Bundle version not found: ${packageName}@${version}`
|
|
194
|
+
: `Bundle not found: ${packageName}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
const error: any = await response.json().catch(() => ({ error: response.statusText }));
|
|
198
|
+
const errorMessage = typeof error.error === 'string'
|
|
199
|
+
? error.error
|
|
200
|
+
: error.error?.message || `Failed to get download info: ${response.statusText}`;
|
|
201
|
+
throw new Error(errorMessage);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return response.json() as Promise<DownloadInfo>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Download a bundle to a file
|
|
209
|
+
*/
|
|
210
|
+
async downloadBundle(downloadUrl: string, outputPath: string): Promise<void> {
|
|
211
|
+
const response = await fetch(downloadUrl);
|
|
212
|
+
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new Error(`Failed to download bundle: ${response.statusText}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
218
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
219
|
+
|
|
220
|
+
const fs = await import('fs');
|
|
221
|
+
fs.writeFileSync(outputPath, buffer);
|
|
222
|
+
}
|
|
223
|
+
}
|