@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.
Files changed (58) hide show
  1. package/.env.example +13 -0
  2. package/CLAUDE.md +117 -0
  3. package/LICENSE +201 -0
  4. package/README.md +206 -0
  5. package/dist/commands/packages/pull.d.ts +11 -0
  6. package/dist/commands/packages/pull.d.ts.map +1 -0
  7. package/dist/commands/packages/pull.js +72 -0
  8. package/dist/commands/packages/pull.js.map +1 -0
  9. package/dist/commands/packages/search.d.ts +12 -0
  10. package/dist/commands/packages/search.d.ts.map +1 -0
  11. package/dist/commands/packages/search.js +63 -0
  12. package/dist/commands/packages/search.js.map +1 -0
  13. package/dist/commands/packages/show.d.ts +8 -0
  14. package/dist/commands/packages/show.d.ts.map +1 -0
  15. package/dist/commands/packages/show.js +121 -0
  16. package/dist/commands/packages/show.js.map +1 -0
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +12 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/api/registry-client.d.ts +63 -0
  22. package/dist/lib/api/registry-client.d.ts.map +1 -0
  23. package/dist/lib/api/registry-client.js +167 -0
  24. package/dist/lib/api/registry-client.js.map +1 -0
  25. package/dist/program.d.ts +8 -0
  26. package/dist/program.d.ts.map +1 -0
  27. package/dist/program.js +69 -0
  28. package/dist/program.js.map +1 -0
  29. package/dist/utils/config-manager.d.ts +23 -0
  30. package/dist/utils/config-manager.d.ts.map +1 -0
  31. package/dist/utils/config-manager.js +65 -0
  32. package/dist/utils/config-manager.js.map +1 -0
  33. package/dist/utils/errors.d.ts +12 -0
  34. package/dist/utils/errors.d.ts.map +1 -0
  35. package/dist/utils/errors.js +27 -0
  36. package/dist/utils/errors.js.map +1 -0
  37. package/dist/utils/version.d.ts +5 -0
  38. package/dist/utils/version.d.ts.map +1 -0
  39. package/dist/utils/version.js +19 -0
  40. package/dist/utils/version.js.map +1 -0
  41. package/eslint.config.js +63 -0
  42. package/package.json +48 -0
  43. package/src/commands/packages/pull.ts +96 -0
  44. package/src/commands/packages/search.ts +83 -0
  45. package/src/commands/packages/show.ts +138 -0
  46. package/src/index.ts +15 -0
  47. package/src/lib/api/registry-client.ts +223 -0
  48. package/src/lib/api/schema.d.ts +548 -0
  49. package/src/program.test.ts +24 -0
  50. package/src/program.ts +76 -0
  51. package/src/utils/config-manager.test.ts +60 -0
  52. package/src/utils/config-manager.ts +81 -0
  53. package/src/utils/errors.test.ts +25 -0
  54. package/src/utils/errors.ts +33 -0
  55. package/src/utils/version.test.ts +16 -0
  56. package/src/utils/version.ts +18 -0
  57. package/tsconfig.json +25 -0
  58. 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
+ }