@pulsemcp/air-provider-github 0.0.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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @pulsemcp/air-provider-github
2
+
3
+ AIR catalog provider for GitHub. Resolves `github://` URIs in `air.json` by fetching file content from the GitHub REST API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @pulsemcp/air-provider-github
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### In air.json
14
+
15
+ Reference remote artifact indexes using `github://` URIs:
16
+
17
+ ```json
18
+ {
19
+ "name": "my-team",
20
+ "skills": [
21
+ "github://acme/air-org/skills/skills.json",
22
+ "./skills/skills.json"
23
+ ],
24
+ "mcp": [
25
+ "github://acme/air-org/mcp/mcp.json",
26
+ "./mcp/mcp.json"
27
+ ]
28
+ }
29
+ ```
30
+
31
+ ### Programmatic
32
+
33
+ ```typescript
34
+ import { resolveArtifacts } from "@pulsemcp/air-core";
35
+ import { GitHubCatalogProvider } from "@pulsemcp/air-provider-github";
36
+
37
+ const provider = new GitHubCatalogProvider();
38
+ // Or with a token for private repos:
39
+ // const provider = new GitHubCatalogProvider({ token: "ghp_..." });
40
+
41
+ const artifacts = await resolveArtifacts("./air.json", {
42
+ providers: [provider],
43
+ });
44
+ ```
45
+
46
+ ## URI Format
47
+
48
+ ```
49
+ github://owner/repo/path/to/file.json
50
+ github://owner/repo/path/to/file.json@ref
51
+ ```
52
+
53
+ | Component | Description |
54
+ |-----------|-------------|
55
+ | `owner` | GitHub organization or user |
56
+ | `repo` | Repository name |
57
+ | `path` | Path to the JSON file within the repo |
58
+ | `@ref` | Optional git ref (branch, tag, commit SHA) |
59
+
60
+ Examples:
61
+ - `github://acme/air-org/skills/skills.json` — latest from default branch
62
+ - `github://acme/air-org/mcp/mcp.json@v1.0.0` — pinned to a tag
63
+ - `github://acme/air-org/mcp/mcp.json@main` — explicit branch
64
+
65
+ ## Authentication
66
+
67
+ | Scenario | Auth Required? | How |
68
+ |----------|---------------|-----|
69
+ | Public repository | No | Works out of the box |
70
+ | Private repository | Yes | Set `AIR_GITHUB_TOKEN` env var or pass `token` option |
71
+ | Higher rate limits | Optional | Authenticated requests get 5,000 req/hr vs 60 |
72
+
73
+ ```bash
74
+ export AIR_GITHUB_TOKEN=ghp_your_token_here
75
+ ```
76
+
77
+ ## Caching
78
+
79
+ Fetched files are cached locally at `~/.air/cache/github/{owner}/{repo}/{ref}/{path}`. Delete the cache directory to force a re-fetch.
@@ -0,0 +1,43 @@
1
+ import type { CatalogProvider } from "@pulsemcp/air-core";
2
+ export interface GitHubUri {
3
+ owner: string;
4
+ repo: string;
5
+ path: string;
6
+ ref?: string;
7
+ }
8
+ export interface GitHubProviderOptions {
9
+ /**
10
+ * GitHub personal access token for authenticating API requests.
11
+ * Required for private repositories. Optional for public repos
12
+ * (unauthenticated requests have lower rate limits).
13
+ *
14
+ * Can also be set via the AIR_GITHUB_TOKEN environment variable.
15
+ */
16
+ token?: string;
17
+ }
18
+ /**
19
+ * Parse a github:// URI into its components.
20
+ *
21
+ * Format: github://owner/repo/path/to/file.json
22
+ * With ref: github://owner/repo/path/to/file.json@ref
23
+ */
24
+ export declare function parseGitHubUri(uri: string): GitHubUri;
25
+ /**
26
+ * Get the local cache directory for GitHub content.
27
+ */
28
+ export declare function getCacheDir(): string;
29
+ /**
30
+ * GitHub catalog provider — resolves github:// URIs by fetching
31
+ * file content from the GitHub REST API.
32
+ *
33
+ * Works without authentication for public repositories. Pass a token
34
+ * (or set AIR_GITHUB_TOKEN) for private repos or higher rate limits.
35
+ */
36
+ export declare class GitHubCatalogProvider implements CatalogProvider {
37
+ scheme: string;
38
+ private token;
39
+ constructor(options?: GitHubProviderOptions);
40
+ resolve(uri: string, _baseDir: string): Promise<Record<string, unknown>>;
41
+ private fetchFromGitHub;
42
+ }
43
+ //# sourceMappingURL=github-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-provider.d.ts","sourceRoot":"","sources":["../src/github-provider.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAuBrD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,MAAM,SAAY;IAClB,OAAO,CAAC,KAAK,CAAqB;gBAEtB,OAAO,CAAC,EAAE,qBAAqB;IAIrC,OAAO,CACX,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAuBrB,eAAe;CAkD9B"}
@@ -0,0 +1,104 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { resolve } from "path";
3
+ /**
4
+ * Parse a github:// URI into its components.
5
+ *
6
+ * Format: github://owner/repo/path/to/file.json
7
+ * With ref: github://owner/repo/path/to/file.json@ref
8
+ */
9
+ export function parseGitHubUri(uri) {
10
+ const withoutScheme = uri.replace(/^github:\/\//, "");
11
+ const parts = withoutScheme.split("/");
12
+ if (parts.length < 3) {
13
+ throw new Error(`Invalid github:// URI: "${uri}". Expected format: github://owner/repo/path/to/file.json`);
14
+ }
15
+ const owner = parts[0];
16
+ const repo = parts[1];
17
+ let filePath = parts.slice(2).join("/");
18
+ // Extract optional @ref from the last segment
19
+ let ref;
20
+ const atIndex = filePath.lastIndexOf("@");
21
+ if (atIndex > 0) {
22
+ ref = filePath.slice(atIndex + 1);
23
+ filePath = filePath.slice(0, atIndex);
24
+ }
25
+ return { owner, repo, path: filePath, ref };
26
+ }
27
+ /**
28
+ * Get the local cache directory for GitHub content.
29
+ */
30
+ export function getCacheDir() {
31
+ const home = process.env.HOME || process.env.USERPROFILE || "~";
32
+ return resolve(home, ".air", "cache", "github");
33
+ }
34
+ /**
35
+ * GitHub catalog provider — resolves github:// URIs by fetching
36
+ * file content from the GitHub REST API.
37
+ *
38
+ * Works without authentication for public repositories. Pass a token
39
+ * (or set AIR_GITHUB_TOKEN) for private repos or higher rate limits.
40
+ */
41
+ export class GitHubCatalogProvider {
42
+ scheme = "github";
43
+ token;
44
+ constructor(options) {
45
+ this.token = options?.token || process.env.AIR_GITHUB_TOKEN;
46
+ }
47
+ async resolve(uri, _baseDir) {
48
+ const parsed = parseGitHubUri(uri);
49
+ const ref = parsed.ref || "HEAD";
50
+ const cacheDir = getCacheDir();
51
+ const cacheKey = `${parsed.owner}/${parsed.repo}/${ref}/${parsed.path}`;
52
+ const cachePath = resolve(cacheDir, cacheKey);
53
+ // Check cache first
54
+ if (existsSync(cachePath)) {
55
+ const content = readFileSync(cachePath, "utf-8");
56
+ return JSON.parse(content);
57
+ }
58
+ const content = await this.fetchFromGitHub(parsed);
59
+ // Write to cache
60
+ const cacheFileDir = resolve(cachePath, "..");
61
+ mkdirSync(cacheFileDir, { recursive: true });
62
+ writeFileSync(cachePath, content);
63
+ return JSON.parse(content);
64
+ }
65
+ async fetchFromGitHub(parsed) {
66
+ const repoSlug = `${parsed.owner}/${parsed.repo}`;
67
+ let url = `https://api.github.com/repos/${repoSlug}/contents/${parsed.path}`;
68
+ if (parsed.ref) {
69
+ url += `?ref=${encodeURIComponent(parsed.ref)}`;
70
+ }
71
+ const headers = {
72
+ Accept: "application/vnd.github.v3+json",
73
+ "User-Agent": "air-provider-github",
74
+ };
75
+ if (this.token) {
76
+ headers["Authorization"] = `Bearer ${this.token}`;
77
+ }
78
+ let response;
79
+ try {
80
+ response = await fetch(url, { headers });
81
+ }
82
+ catch (err) {
83
+ throw new Error(`Network error fetching ${url}.\n` +
84
+ (err instanceof Error ? ` Error: ${err.message}` : ""));
85
+ }
86
+ if (!response.ok) {
87
+ const hint = response.status === 404
88
+ ? " (repository may be private — set AIR_GITHUB_TOKEN or pass token option)"
89
+ : response.status === 403
90
+ ? " (rate limit exceeded — set AIR_GITHUB_TOKEN for higher limits)"
91
+ : "";
92
+ throw new Error(`GitHub API returned ${response.status} for ${url}${hint}\n` +
93
+ ` Repository: ${repoSlug}\n` +
94
+ ` Path: ${parsed.path}`);
95
+ }
96
+ const data = (await response.json());
97
+ if (!data.content || data.encoding !== "base64") {
98
+ throw new Error(`Unexpected response format from GitHub API for ${url}.\n` +
99
+ ` Expected base64-encoded content, got encoding="${data.encoding}".`);
100
+ }
101
+ return Buffer.from(data.content, "base64").toString("utf-8");
102
+ }
103
+ }
104
+ //# sourceMappingURL=github-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-provider.js","sourceRoot":"","sources":["../src/github-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { AirExtension } from "@pulsemcp/air-core";
2
+ export { GitHubCatalogProvider, parseGitHubUri, getCacheDir } from "./github-provider.js";
3
+ export type { GitHubUri, GitHubProviderOptions } from "./github-provider.js";
4
+ declare const extension: AirExtension;
5
+ export default extension;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC1F,YAAY,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7E,QAAA,MAAM,SAAS,EAAE,YAIhB,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import { GitHubCatalogProvider } from "./github-provider.js";
2
+ export { GitHubCatalogProvider, parseGitHubUri, getCacheDir } from "./github-provider.js";
3
+ const extension = {
4
+ name: "github",
5
+ type: "provider",
6
+ provider: new GitHubCatalogProvider(),
7
+ };
8
+ export default extension;
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG1F,MAAM,SAAS,GAAiB;IAC9B,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,UAAU;IAChB,QAAQ,EAAE,IAAI,qBAAqB,EAAE;CACtC,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@pulsemcp/air-provider-github",
3
+ "version": "0.0.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/pulsemcp/air.git",
10
+ "directory": "packages/extensions/provider-github"
11
+ },
12
+ "description": "AIR catalog provider for GitHub — resolves github:// URIs to artifact indexes",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist/"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "lint": "tsc --noEmit"
30
+ },
31
+ "dependencies": {
32
+ "@pulsemcp/air-core": "0.0.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.10.0",
36
+ "typescript": "^5.7.0",
37
+ "vitest": "^2.1.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ }
42
+ }