@ian2018cs/agenthub 0.1.5 → 0.1.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ian2018cs/agenthub",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A web-based UI for AI Agents",
5
5
  "type": "module",
6
6
  "main": "server/index.js",
@@ -13,7 +13,7 @@ const router = express.Router();
13
13
  const SKILL_NAME_REGEX = /^[a-zA-Z0-9_-]{1,100}$/;
14
14
 
15
15
  // Trusted git hosting domains
16
- const TRUSTED_GIT_HOSTS = ['github.com', 'gitlab.com', 'bitbucket.org'];
16
+ const TRUSTED_GIT_HOSTS = ['github.com', 'gitlab.com', 'bitbucket.org', 'git.amberweather.com'];
17
17
 
18
18
  // Configure multer for zip file uploads
19
19
  const upload = multer({
@@ -84,6 +84,30 @@ async function isValidSkill(skillPath) {
84
84
  }
85
85
  }
86
86
 
87
+ /**
88
+ * Check if URL is SSH format (git@host:path)
89
+ */
90
+ function isSshUrl(url) {
91
+ return /^[a-zA-Z0-9_-]+@[a-zA-Z0-9.-]+:.+$/.test(url);
92
+ }
93
+
94
+ /**
95
+ * Parse SSH URL format (git@host:owner/repo.git)
96
+ */
97
+ function parseSshUrl(url) {
98
+ const match = url.match(/^[a-zA-Z0-9_-]+@([a-zA-Z0-9.-]+):(.+)$/);
99
+ if (!match) return null;
100
+
101
+ const host = match[1].toLowerCase();
102
+ const pathPart = match[2].replace(/\.git$/, '');
103
+ const pathParts = pathPart.split('/');
104
+
105
+ if (pathParts.length >= 2) {
106
+ return { host, owner: pathParts[0], repo: pathParts[1] };
107
+ }
108
+ return { host, owner: null, repo: null };
109
+ }
110
+
87
111
  /**
88
112
  * Validate GitHub URL
89
113
  */
@@ -92,6 +116,23 @@ function validateGitUrl(url) {
92
116
  return { valid: false, error: 'Repository URL is required' };
93
117
  }
94
118
 
119
+ // Handle SSH format: git@host:owner/repo.git
120
+ if (isSshUrl(url)) {
121
+ const parsed = parseSshUrl(url);
122
+ if (!parsed) {
123
+ return { valid: false, error: 'Invalid SSH URL format' };
124
+ }
125
+
126
+ if (!TRUSTED_GIT_HOSTS.includes(parsed.host)) {
127
+ return {
128
+ valid: false,
129
+ error: `Only trusted git hosts are allowed: ${TRUSTED_GIT_HOSTS.join(', ')}`
130
+ };
131
+ }
132
+
133
+ return { valid: true, isSsh: true };
134
+ }
135
+
95
136
  let parsedUrl;
96
137
  try {
97
138
  parsedUrl = new URL(url);
@@ -99,11 +140,20 @@ function validateGitUrl(url) {
99
140
  return { valid: false, error: 'Invalid URL format' };
100
141
  }
101
142
 
102
- if (parsedUrl.protocol !== 'https:') {
103
- return { valid: false, error: 'Only HTTPS URLs are allowed' };
143
+ const host = parsedUrl.hostname.toLowerCase();
144
+ const allowedProtocols = ['https:', 'http:'];
145
+
146
+ // Allow http protocol only for git.amberweather.com
147
+ if (host === 'git.amberweather.com') {
148
+ if (!allowedProtocols.includes(parsedUrl.protocol)) {
149
+ return { valid: false, error: 'Only HTTPS or HTTP URLs are allowed for this host' };
150
+ }
151
+ } else {
152
+ if (parsedUrl.protocol !== 'https:') {
153
+ return { valid: false, error: 'Only HTTPS URLs are allowed' };
154
+ }
104
155
  }
105
156
 
106
- const host = parsedUrl.hostname.toLowerCase();
107
157
  if (!TRUSTED_GIT_HOSTS.includes(host)) {
108
158
  return {
109
159
  valid: false,
@@ -118,6 +168,15 @@ function validateGitUrl(url) {
118
168
  * Extract owner and repo from git URL
119
169
  */
120
170
  function parseGitUrl(url) {
171
+ // Handle SSH format
172
+ if (isSshUrl(url)) {
173
+ const parsed = parseSshUrl(url);
174
+ if (parsed && parsed.owner && parsed.repo) {
175
+ return { owner: parsed.owner, repo: parsed.repo };
176
+ }
177
+ return null;
178
+ }
179
+
121
180
  const parsedUrl = new URL(url);
122
181
  const pathParts = parsedUrl.pathname.replace(/^\//, '').replace(/\.git$/, '').split('/');
123
182
  if (pathParts.length >= 2) {