create-byan-agent 2.8.0 → 2.9.0

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.
@@ -0,0 +1,257 @@
1
+ const https = require('https');
2
+ const { execFile } = require('child_process');
3
+
4
+ class GistClient {
5
+ async exportToGist(agentPackage) {
6
+ if (!agentPackage || !agentPackage.metadata) {
7
+ throw new Error('Invalid agent package: missing metadata');
8
+ }
9
+
10
+ const name = agentPackage.metadata.name || 'agent';
11
+ const displayName = agentPackage.metadata.displayName || name;
12
+ const filename = `${sanitizeFilename(name)}.byan-agent`;
13
+ const content = JSON.stringify(agentPackage, null, 2);
14
+ const description = `BYAN Agent: ${displayName}`;
15
+
16
+ if (await this.isGHAvailable()) {
17
+ return this._exportWithGH(filename, content, description);
18
+ }
19
+
20
+ const token = process.env.GITHUB_TOKEN;
21
+ if (token) {
22
+ return this._exportWithAPI(filename, content, description, token);
23
+ }
24
+
25
+ throw new Error(
26
+ 'No GitHub authentication available. Install gh CLI and run "gh auth login", or set GITHUB_TOKEN env var.'
27
+ );
28
+ }
29
+
30
+ async importFromGist(url) {
31
+ const gistId = extractGistId(url);
32
+ if (!gistId) {
33
+ throw new Error(`Cannot extract gist ID from URL: ${url}`);
34
+ }
35
+
36
+ const gistData = await this._fetchJSON(`https://api.github.com/gists/${gistId}`);
37
+ if (!gistData.files) {
38
+ throw new Error('Gist has no files');
39
+ }
40
+
41
+ const byanFile = Object.keys(gistData.files).find(f => f.endsWith('.byan-agent'));
42
+ if (!byanFile) {
43
+ throw new Error('No .byan-agent file found in gist');
44
+ }
45
+
46
+ const fileObj = gistData.files[byanFile];
47
+ let content = fileObj.content;
48
+
49
+ if (fileObj.truncated && fileObj.raw_url) {
50
+ content = await this._fetchRaw(fileObj.raw_url);
51
+ }
52
+
53
+ try {
54
+ return JSON.parse(content);
55
+ } catch {
56
+ throw new Error('Failed to parse .byan-agent content from gist');
57
+ }
58
+ }
59
+
60
+ async importFromURL(url) {
61
+ const urlStr = String(url).trim();
62
+ if (!urlStr.startsWith('https://') && !urlStr.startsWith('http://')) {
63
+ throw new Error('URL must start with http:// or https://');
64
+ }
65
+
66
+ const content = await this._fetchRaw(urlStr);
67
+
68
+ try {
69
+ return JSON.parse(content);
70
+ } catch {
71
+ throw new Error('Failed to parse .byan-agent content from URL');
72
+ }
73
+ }
74
+
75
+ async isGHAvailable() {
76
+ try {
77
+ await execCommand('gh', ['auth', 'status']);
78
+ return true;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+
84
+ async _exportWithGH(filename, content, description) {
85
+ const tempContent = content;
86
+ const result = await execCommand('gh', [
87
+ 'gist', 'create',
88
+ '--desc', description,
89
+ '--filename', filename,
90
+ '-'
91
+ ], { input: tempContent });
92
+
93
+ const gistUrl = result.stdout.trim();
94
+ if (!gistUrl.startsWith('https://')) {
95
+ throw new Error(`Unexpected gh output: ${gistUrl}`);
96
+ }
97
+ return gistUrl;
98
+ }
99
+
100
+ async _exportWithAPI(filename, content, description, token) {
101
+ const payload = JSON.stringify({
102
+ description,
103
+ public: false,
104
+ files: {
105
+ [filename]: { content }
106
+ }
107
+ });
108
+
109
+ const response = await this._apiRequest('POST', 'https://api.github.com/gists', payload, token);
110
+ if (!response.html_url) {
111
+ throw new Error('GitHub API did not return a gist URL');
112
+ }
113
+ return response.html_url;
114
+ }
115
+
116
+ _fetchJSON(url) {
117
+ return new Promise((resolve, reject) => {
118
+ const options = {
119
+ headers: {
120
+ 'User-Agent': 'BYAN-Exchange/1.0',
121
+ 'Accept': 'application/json'
122
+ }
123
+ };
124
+
125
+ const token = process.env.GITHUB_TOKEN;
126
+ if (token) {
127
+ options.headers['Authorization'] = `token ${token}`;
128
+ }
129
+
130
+ https.get(url, options, (res) => {
131
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
132
+ this._fetchJSON(res.headers.location).then(resolve, reject);
133
+ return;
134
+ }
135
+ if (res.statusCode !== 200) {
136
+ reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
137
+ return;
138
+ }
139
+
140
+ let data = '';
141
+ res.on('data', chunk => { data += chunk; });
142
+ res.on('end', () => {
143
+ try {
144
+ resolve(JSON.parse(data));
145
+ } catch {
146
+ reject(new Error('Invalid JSON response'));
147
+ }
148
+ });
149
+ res.on('error', reject);
150
+ }).on('error', reject);
151
+ });
152
+ }
153
+
154
+ _fetchRaw(url) {
155
+ return new Promise((resolve, reject) => {
156
+ const protocol = url.startsWith('https') ? https : require('http');
157
+ const options = {
158
+ headers: { 'User-Agent': 'BYAN-Exchange/1.0' }
159
+ };
160
+
161
+ const token = process.env.GITHUB_TOKEN;
162
+ if (token && url.includes('github')) {
163
+ options.headers['Authorization'] = `token ${token}`;
164
+ }
165
+
166
+ protocol.get(url, options, (res) => {
167
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
168
+ this._fetchRaw(res.headers.location).then(resolve, reject);
169
+ return;
170
+ }
171
+ if (res.statusCode !== 200) {
172
+ reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
173
+ return;
174
+ }
175
+
176
+ let data = '';
177
+ res.on('data', chunk => { data += chunk; });
178
+ res.on('end', () => resolve(data));
179
+ res.on('error', reject);
180
+ }).on('error', reject);
181
+ });
182
+ }
183
+
184
+ _apiRequest(method, url, body, token) {
185
+ return new Promise((resolve, reject) => {
186
+ const parsed = new URL(url);
187
+ const options = {
188
+ hostname: parsed.hostname,
189
+ path: parsed.pathname,
190
+ method,
191
+ headers: {
192
+ 'User-Agent': 'BYAN-Exchange/1.0',
193
+ 'Accept': 'application/vnd.github.v3+json',
194
+ 'Content-Type': 'application/json',
195
+ 'Content-Length': Buffer.byteLength(body),
196
+ 'Authorization': `token ${token}`
197
+ }
198
+ };
199
+
200
+ const req = https.request(options, (res) => {
201
+ let data = '';
202
+ res.on('data', chunk => { data += chunk; });
203
+ res.on('end', () => {
204
+ if (res.statusCode >= 400) {
205
+ reject(new Error(`GitHub API error ${res.statusCode}: ${data}`));
206
+ return;
207
+ }
208
+ try {
209
+ resolve(JSON.parse(data));
210
+ } catch {
211
+ reject(new Error('Invalid JSON from GitHub API'));
212
+ }
213
+ });
214
+ res.on('error', reject);
215
+ });
216
+
217
+ req.on('error', reject);
218
+ req.write(body);
219
+ req.end();
220
+ });
221
+ }
222
+ }
223
+
224
+ function sanitizeFilename(name) {
225
+ return String(name).replace(/[^a-zA-Z0-9_\-]/g, '').substring(0, 100) || 'agent';
226
+ }
227
+
228
+ function extractGistId(url) {
229
+ const str = String(url).trim();
230
+ const match = str.match(/gist\.github\.com\/(?:[^/]+\/)?([a-f0-9]+)/);
231
+ if (match) return match[1];
232
+ const idMatch = str.match(/^([a-f0-9]{20,})$/);
233
+ if (idMatch) return idMatch[1];
234
+ return null;
235
+ }
236
+
237
+ function execCommand(cmd, args, options = {}) {
238
+ return new Promise((resolve, reject) => {
239
+ const proc = execFile(cmd, args, {
240
+ timeout: 30000,
241
+ maxBuffer: 1024 * 1024
242
+ }, (err, stdout, stderr) => {
243
+ if (err) {
244
+ reject(new Error(`${cmd} failed: ${err.message}`));
245
+ return;
246
+ }
247
+ resolve({ stdout, stderr });
248
+ });
249
+
250
+ if (options.input && proc.stdin) {
251
+ proc.stdin.write(options.input);
252
+ proc.stdin.end();
253
+ }
254
+ });
255
+ }
256
+
257
+ module.exports = GistClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "description": "BYAN v2.2.2 - Intelligent AI agent installer with multi-platform native support (GitHub Copilot CLI, Claude Code, Codex/OpenCode)",
5
5
  "bin": {
6
6
  "create-byan-agent": "bin/create-byan-agent-v2.js"