@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,65 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ /**
5
+ * Configuration manager for CLI settings in ~/.mpak/config.json
6
+ */
7
+ export class ConfigManager {
8
+ configDir;
9
+ configFile;
10
+ config = null;
11
+ constructor() {
12
+ this.configDir = join(homedir(), '.mpak');
13
+ this.configFile = join(this.configDir, 'config.json');
14
+ this.ensureConfigDir();
15
+ }
16
+ ensureConfigDir() {
17
+ if (!existsSync(this.configDir)) {
18
+ mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
19
+ }
20
+ }
21
+ loadConfig() {
22
+ if (this.config) {
23
+ return this.config;
24
+ }
25
+ if (!existsSync(this.configFile)) {
26
+ this.config = {
27
+ version: '1.0.0',
28
+ lastUpdated: new Date().toISOString(),
29
+ };
30
+ this.saveConfig();
31
+ return this.config;
32
+ }
33
+ try {
34
+ const configJson = readFileSync(this.configFile, 'utf8');
35
+ this.config = JSON.parse(configJson);
36
+ return this.config;
37
+ }
38
+ catch {
39
+ this.config = {
40
+ version: '1.0.0',
41
+ lastUpdated: new Date().toISOString(),
42
+ };
43
+ this.saveConfig();
44
+ return this.config;
45
+ }
46
+ }
47
+ saveConfig() {
48
+ if (!this.config) {
49
+ return;
50
+ }
51
+ this.config.lastUpdated = new Date().toISOString();
52
+ const configJson = JSON.stringify(this.config, null, 2);
53
+ writeFileSync(this.configFile, configJson, { mode: 0o600 });
54
+ }
55
+ setRegistryUrl(url) {
56
+ const config = this.loadConfig();
57
+ config.registryUrl = url;
58
+ this.saveConfig();
59
+ }
60
+ getRegistryUrl() {
61
+ const config = this.loadConfig();
62
+ return config.registryUrl || process.env.MPAK_REGISTRY_URL || 'https://api.mpak.dev';
63
+ }
64
+ }
65
+ //# sourceMappingURL=config-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-manager.js","sourceRoot":"","sources":["../../src/utils/config-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B;;GAEG;AACH,MAAM,OAAO,aAAa;IAChB,SAAS,CAAS;IAClB,UAAU,CAAS;IACnB,MAAM,GAAsB,IAAI,CAAC;IAEzC;QACE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACtD,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,GAAG;gBACZ,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACtC,CAAC;YACF,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAe,CAAC;YACnD,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,GAAG;gBACZ,OAAO,EAAE,OAAO;gBAChB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACtC,CAAC;YACF,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;IACH,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACnD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACxD,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,cAAc,CAAC,GAAW;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACjC,MAAM,CAAC,WAAW,GAAG,GAAG,CAAC;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED,cAAc;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,sBAAsB,CAAC;IACvF,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Custom error class for CLI errors
3
+ */
4
+ export declare class CLIError extends Error {
5
+ readonly exitCode: number;
6
+ constructor(message: string, exitCode?: number);
7
+ }
8
+ /**
9
+ * Handles errors gracefully and exits with appropriate code
10
+ */
11
+ export declare function handleError(error: unknown): never;
12
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,QAAQ,EAAE,MAAM,CAAC;gBAG/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAU;CAMvB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAajD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Custom error class for CLI errors
3
+ */
4
+ export class CLIError extends Error {
5
+ exitCode;
6
+ constructor(message, exitCode = 1) {
7
+ super(message);
8
+ this.name = 'CLIError';
9
+ this.exitCode = exitCode;
10
+ }
11
+ }
12
+ /**
13
+ * Handles errors gracefully and exits with appropriate code
14
+ */
15
+ export function handleError(error) {
16
+ if (error instanceof CLIError) {
17
+ console.error(`Error: ${error.message}`);
18
+ process.exit(error.exitCode);
19
+ }
20
+ if (error instanceof Error) {
21
+ console.error(`Error: ${error.message}`);
22
+ process.exit(1);
23
+ }
24
+ console.error('An unexpected error occurred');
25
+ process.exit(1);
26
+ }
27
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjB,QAAQ,CAAS;IAEjC,YACE,OAAe,EACf,WAAmB,CAAC;QAEpB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Gets the current version from package.json
3
+ */
4
+ export declare function getVersion(): string;
5
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAUnC"}
@@ -0,0 +1,19 @@
1
+ import { readFileSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ /**
5
+ * Gets the current version from package.json
6
+ */
7
+ export function getVersion() {
8
+ try {
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const packageJsonPath = join(__dirname, '../../package.json');
12
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
13
+ return packageJson.version;
14
+ }
15
+ catch (_error) {
16
+ return 'unknown';
17
+ }
18
+ }
19
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QACvE,OAAO,WAAW,CAAC,OAAO,CAAC;IAC7B,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,63 @@
1
+ import js from '@eslint/js';
2
+ import typescript from '@typescript-eslint/eslint-plugin';
3
+ import typescriptParser from '@typescript-eslint/parser';
4
+
5
+ export default [
6
+ js.configs.recommended,
7
+ {
8
+ files: ['src/**/*.ts'],
9
+ languageOptions: {
10
+ parser: typescriptParser,
11
+ parserOptions: {
12
+ ecmaVersion: 2022,
13
+ sourceType: 'module'
14
+ },
15
+ globals: {
16
+ console: 'readonly',
17
+ process: 'readonly',
18
+ Buffer: 'readonly',
19
+ __dirname: 'readonly',
20
+ __filename: 'readonly',
21
+ global: 'readonly',
22
+ fetch: 'readonly',
23
+ setTimeout: 'readonly',
24
+ setInterval: 'readonly',
25
+ clearTimeout: 'readonly',
26
+ clearInterval: 'readonly',
27
+ setImmediate: 'readonly',
28
+ Blob: 'readonly',
29
+ FormData: 'readonly',
30
+ URLSearchParams: 'readonly',
31
+ NodeJS: 'readonly'
32
+ }
33
+ },
34
+ plugins: {
35
+ '@typescript-eslint': typescript
36
+ },
37
+ rules: {
38
+ // TypeScript specific rules
39
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'all', caughtErrorsIgnorePattern: '^_' }],
40
+ 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'all', caughtErrorsIgnorePattern: '^_' }],
41
+ '@typescript-eslint/no-explicit-any': 'warn',
42
+
43
+ // General JavaScript rules
44
+ 'no-console': 'off', // CLI needs console output
45
+ 'prefer-const': 'error',
46
+ 'no-var': 'error',
47
+ 'eqeqeq': 'error',
48
+ 'no-trailing-spaces': 'error',
49
+ 'no-multiple-empty-lines': ['error', { max: 2 }],
50
+
51
+ // Import/export rules
52
+ 'no-duplicate-imports': 'error'
53
+ }
54
+ },
55
+ {
56
+ ignores: [
57
+ 'dist/**',
58
+ 'node_modules/**',
59
+ '*.js',
60
+ 'examples/**'
61
+ ]
62
+ }
63
+ ];
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@nimblebrain/mpak",
3
+ "version": "0.0.1-beta.1",
4
+ "description": "CLI for downloading MCPB bundles from the mpak registry",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "mpak": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "generate:types": "openapi-typescript http://localhost:3200/documentation/json -o src/lib/api/schema.d.ts",
14
+ "lint": "eslint src",
15
+ "lint:fix": "eslint src --fix",
16
+ "prepublishOnly": "npm run build",
17
+ "start": "node dist/index.js",
18
+ "test": "vitest",
19
+ "test:ui": "vitest --ui",
20
+ "test:coverage": "vitest --coverage",
21
+ "typecheck": "tsc --noEmit"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "mcpb",
26
+ "anthropic",
27
+ "cli",
28
+ "package-manager"
29
+ ],
30
+ "author": "NimbleBrain Inc",
31
+ "license": "Apache-2.0",
32
+ "dependencies": {
33
+ "commander": "^14.0.2",
34
+ "dotenv": "^17.2.3"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^25.0.3",
38
+ "@vitest/ui": "^4.0.16",
39
+ "openapi-typescript": "^7.10.1",
40
+ "tsx": "^4.21.0",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^4.0.16"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ }
48
+ }
@@ -0,0 +1,96 @@
1
+ import { resolve } from 'path';
2
+ import { RegistryClient } from '../../lib/api/registry-client.js';
3
+
4
+ export interface PullOptions {
5
+ output?: string;
6
+ json?: boolean;
7
+ os?: string;
8
+ arch?: string;
9
+ }
10
+
11
+ /**
12
+ * Parse package specification into name and version
13
+ * Examples:
14
+ * @scope/name -> { name: '@scope/name', version: undefined }
15
+ * @scope/name@1.0.0 -> { name: '@scope/name', version: '1.0.0' }
16
+ */
17
+ function parsePackageSpec(spec: string): { name: string; version?: string } {
18
+ // Find the last @ which separates version from package name
19
+ // Package names start with @, so we need to find the second @
20
+ const lastAtIndex = spec.lastIndexOf('@');
21
+
22
+ if (lastAtIndex <= 0) {
23
+ // No version specified or invalid format
24
+ return { name: spec };
25
+ }
26
+
27
+ const name = spec.substring(0, lastAtIndex);
28
+ const version = spec.substring(lastAtIndex + 1);
29
+
30
+ // Validate that the name still starts with @
31
+ if (!name.startsWith('@')) {
32
+ // This means the @ was part of the package name, not a version separator
33
+ return { name: spec };
34
+ }
35
+
36
+ return { name, version };
37
+ }
38
+
39
+ /**
40
+ * Pull (download) a package from the registry
41
+ */
42
+ export async function handlePull(
43
+ packageSpec: string,
44
+ options: PullOptions = {}
45
+ ): Promise<void> {
46
+ try {
47
+ const { name, version } = parsePackageSpec(packageSpec);
48
+
49
+ const client = new RegistryClient();
50
+
51
+ // Detect platform (or use explicit overrides)
52
+ const detectedPlatform = RegistryClient.detectPlatform();
53
+ const platform = {
54
+ os: options.os || detectedPlatform.os,
55
+ arch: options.arch || detectedPlatform.arch,
56
+ };
57
+
58
+ console.log(`=> Fetching ${version ? `${name}@${version}` : `${name} (latest)`}...`);
59
+ console.log(` Platform: ${platform.os}-${platform.arch}`);
60
+
61
+ // Get download info with platform
62
+ const downloadInfo = await client.getDownloadInfo(name, version, platform);
63
+
64
+ if (options.json) {
65
+ console.log(JSON.stringify(downloadInfo, null, 2));
66
+ return;
67
+ }
68
+
69
+ const bundle = downloadInfo.bundle;
70
+ console.log(` Version: ${bundle.version}`);
71
+ console.log(` Artifact: ${bundle.platform.os}-${bundle.platform.arch}`);
72
+ console.log(` Size: ${(bundle.size / (1024 * 1024)).toFixed(2)} MB`);
73
+
74
+ // Determine output filename (include platform in name)
75
+ const platformSuffix = `${bundle.platform.os}-${bundle.platform.arch}`;
76
+ const defaultFilename = `${name.replace('@', '').replace('/', '-')}-${bundle.version}-${platformSuffix}.mcpb`;
77
+ const outputPath = options.output
78
+ ? resolve(options.output)
79
+ : resolve(defaultFilename);
80
+
81
+ console.log(`\n=> Downloading to ${outputPath}...`);
82
+
83
+ // Download the bundle
84
+ await client.downloadBundle(downloadInfo.url, outputPath);
85
+
86
+ console.log(`\n=> Bundle downloaded successfully!`);
87
+ console.log(` File: ${outputPath}`);
88
+ console.log(` SHA256: ${bundle.sha256.substring(0, 16)}...`);
89
+ } catch (error) {
90
+ console.error('\n=> Failed to pull bundle');
91
+ if (error instanceof Error) {
92
+ console.error(` ${error.message}`);
93
+ }
94
+ process.exit(1);
95
+ }
96
+ }
@@ -0,0 +1,83 @@
1
+ import { RegistryClient } from '../../lib/api/registry-client.js';
2
+
3
+ export interface SearchOptions {
4
+ type?: string;
5
+ sort?: 'downloads' | 'recent' | 'name';
6
+ limit?: number;
7
+ offset?: number;
8
+ json?: boolean;
9
+ }
10
+
11
+ /**
12
+ * Search bundles (v1 API)
13
+ */
14
+ export async function handleSearch(
15
+ query: string,
16
+ options: SearchOptions = {}
17
+ ): Promise<void> {
18
+ try {
19
+ const client = new RegistryClient();
20
+ const result = await client.searchBundles(query, {
21
+ type: options.type,
22
+ sort: options.sort,
23
+ limit: options.limit,
24
+ offset: options.offset,
25
+ });
26
+
27
+ if (options.json) {
28
+ console.log(JSON.stringify(result, null, 2));
29
+ return;
30
+ }
31
+
32
+ if (result.bundles.length === 0) {
33
+ console.log(`\nNo bundles found for "${query}"`);
34
+ return;
35
+ }
36
+
37
+ console.log(`\nFound ${result.total} bundle(s) for "${query}":\n`);
38
+
39
+ for (const bundle of result.bundles) {
40
+ const verified = bundle.verified ? '✓' : ' ';
41
+ const provenance = bundle.provenance ? '🔒' : '';
42
+
43
+ console.log(`${verified} ${bundle.name} v${bundle.latest_version} ${provenance}`);
44
+
45
+ if (bundle.description) {
46
+ console.log(` ${bundle.description}`);
47
+ }
48
+
49
+ const details = [];
50
+ if (bundle.downloads) details.push(`${bundle.downloads} downloads`);
51
+ if (bundle.server_type) details.push(bundle.server_type);
52
+ if (bundle.author?.name) details.push(`by ${bundle.author.name}`);
53
+
54
+ if (details.length > 0) {
55
+ console.log(` ${details.join(' • ')}`);
56
+ }
57
+
58
+ if (bundle.tools && bundle.tools.length > 0) {
59
+ const toolNames = bundle.tools.slice(0, 3).map((t) => t.name);
60
+ const toolsDisplay =
61
+ bundle.tools.length > 3
62
+ ? `${toolNames.join(', ')} +${bundle.tools.length - 3} more`
63
+ : toolNames.join(', ');
64
+ console.log(` Tools: ${toolsDisplay}`);
65
+ }
66
+
67
+ console.log();
68
+ }
69
+
70
+ if (result.pagination.has_more) {
71
+ const nextOffset = (options.offset || 0) + (options.limit || 20);
72
+ console.log(`More results available. Use --offset ${nextOffset} to see more.`);
73
+ }
74
+
75
+ console.log(`Use "mpak show <bundle>" for more details`);
76
+ } catch (error) {
77
+ console.error('=> Failed to search bundles');
78
+ if (error instanceof Error) {
79
+ console.error(` ${error.message}`);
80
+ }
81
+ process.exit(1);
82
+ }
83
+ }
@@ -0,0 +1,138 @@
1
+ import { RegistryClient } from '../../lib/api/registry-client.js';
2
+
3
+ export interface ShowOptions {
4
+ json?: boolean;
5
+ }
6
+
7
+ /**
8
+ * Show detailed information about a bundle (v1 API)
9
+ */
10
+ export async function handleShow(
11
+ packageName: string,
12
+ options: ShowOptions = {}
13
+ ): Promise<void> {
14
+ try {
15
+ const client = new RegistryClient();
16
+
17
+ // Fetch bundle details and versions in parallel
18
+ const [bundle, versionsInfo] = await Promise.all([
19
+ client.getBundle(packageName),
20
+ client.getVersions(packageName),
21
+ ]);
22
+
23
+ if (options.json) {
24
+ console.log(JSON.stringify({ ...bundle, versions_detail: versionsInfo.versions }, null, 2));
25
+ return;
26
+ }
27
+
28
+ // Header
29
+ const verified = bundle.verified ? '✓ ' : '';
30
+ const provenance = bundle.provenance ? '🔒 ' : '';
31
+ console.log(`\n${verified}${provenance}${bundle.display_name || bundle.name} v${bundle.latest_version}\n`);
32
+
33
+ // Description
34
+ if (bundle.description) {
35
+ console.log(bundle.description);
36
+ console.log();
37
+ }
38
+
39
+ // Basic info
40
+ console.log('Bundle Information:');
41
+ console.log(` Name: ${bundle.name}`);
42
+ if (bundle.author?.name) {
43
+ console.log(` Author: ${bundle.author.name}`);
44
+ }
45
+ if (bundle.server_type) {
46
+ console.log(` Type: ${bundle.server_type}`);
47
+ }
48
+ if (bundle.license) {
49
+ console.log(` License: ${bundle.license}`);
50
+ }
51
+ if (bundle.homepage) {
52
+ console.log(` Homepage: ${bundle.homepage}`);
53
+ }
54
+ console.log();
55
+
56
+ // Provenance info
57
+ if (bundle.provenance) {
58
+ console.log('Provenance:');
59
+ console.log(` Repository: ${bundle.provenance.repository}`);
60
+ console.log(` Commit: ${bundle.provenance.sha.substring(0, 12)}`);
61
+ console.log(` Provider: ${bundle.provenance.provider}`);
62
+ console.log();
63
+ }
64
+
65
+ // Stats
66
+ console.log('Statistics:');
67
+ console.log(` Downloads: ${bundle.downloads.toLocaleString()}`);
68
+ console.log(` Published: ${new Date(bundle.published_at).toLocaleDateString()}`);
69
+ console.log();
70
+
71
+ // GitHub info
72
+ if (bundle.github) {
73
+ console.log('GitHub:');
74
+ console.log(` Repository: ${bundle.github.repo}`);
75
+ if (bundle.github.stars != null) console.log(` Stars: ${bundle.github.stars}`);
76
+ if (bundle.github.forks != null) console.log(` Forks: ${bundle.github.forks}`);
77
+ if (bundle.github.watchers != null) console.log(` Watchers: ${bundle.github.watchers}`);
78
+ console.log();
79
+ }
80
+
81
+ // Tools
82
+ if (bundle.tools && bundle.tools.length > 0) {
83
+ console.log(`Tools (${bundle.tools.length}):`);
84
+ for (const tool of bundle.tools) {
85
+ console.log(` - ${tool.name}`);
86
+ if (tool.description) {
87
+ console.log(` ${tool.description}`);
88
+ }
89
+ }
90
+ console.log();
91
+ }
92
+
93
+ // Versions with platforms
94
+ if (versionsInfo.versions && versionsInfo.versions.length > 0) {
95
+ console.log(`Versions (${versionsInfo.versions.length}):`);
96
+ const recentVersions = versionsInfo.versions.slice(0, 5);
97
+ for (const version of recentVersions) {
98
+ const date = new Date(version.published_at).toLocaleDateString();
99
+ const downloads = version.downloads.toLocaleString();
100
+ const isLatest = version.version === versionsInfo.latest ? ' (latest)' : '';
101
+ const provTag = version.provenance ? ' 🔒' : '';
102
+
103
+ // Format platforms
104
+ const platformStrs = version.platforms.map((p) => `${p.os}-${p.arch}`);
105
+ const platformsDisplay = platformStrs.length > 0 ? ` [${platformStrs.join(', ')}]` : '';
106
+
107
+ console.log(` ${version.version}${isLatest}${provTag} - ${date} - ${downloads} downloads${platformsDisplay}`);
108
+ }
109
+ if (versionsInfo.versions.length > 5) {
110
+ console.log(` ... and ${versionsInfo.versions.length - 5} more`);
111
+ }
112
+ console.log();
113
+ }
114
+
115
+ // Available platforms for latest version
116
+ const latestVersion = versionsInfo.versions.find((v) => v.version === versionsInfo.latest);
117
+ if (latestVersion && latestVersion.platforms.length > 0) {
118
+ console.log('Available Platforms:');
119
+ for (const platform of latestVersion.platforms) {
120
+ console.log(` - ${platform.os}-${platform.arch}`);
121
+ }
122
+ console.log();
123
+ }
124
+
125
+ // Install instructions
126
+ console.log('Install:');
127
+ console.log(` mpak install ${bundle.name}`);
128
+ console.log();
129
+ console.log('Pull (download only):');
130
+ console.log(` mpak pull ${bundle.name}`);
131
+ } catch (error) {
132
+ console.error('=> Failed to get bundle details');
133
+ if (error instanceof Error) {
134
+ console.error(` ${error.message}`);
135
+ }
136
+ process.exit(1);
137
+ }
138
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { config } from 'dotenv';
4
+ import { createProgram } from './program.js';
5
+ import { handleError } from './utils/errors.js';
6
+
7
+ // Load environment variables from .env file
8
+ config();
9
+
10
+ async function main() {
11
+ const program = createProgram();
12
+ await program.parseAsync(process.argv);
13
+ }
14
+
15
+ main().catch(handleError);