dank-ai 1.0.30 → 1.0.32

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.
@@ -1140,23 +1140,48 @@ class DockerManager {
1140
1140
  }
1141
1141
  }
1142
1142
 
1143
+ /**
1144
+ * Normalize Docker image name/tag components
1145
+ * Docker allows: lowercase letters, digits, underscores, periods, and hyphens
1146
+ * Cannot start or end with period or hyphen, max 128 chars for tags
1147
+ */
1148
+ normalizeDockerName(name) {
1149
+ if (!name || typeof name !== 'string') return 'invalid';
1150
+ const lower = String(name).toLowerCase();
1151
+ let sanitized = lower.replace(/[^a-z0-9_.-]/g, '-');
1152
+ sanitized = sanitized.replace(/\.{2,}/g, '.'); // Replace multiple periods with single
1153
+ sanitized = sanitized.replace(/-{2,}/g, '-'); // Replace multiple hyphens with single
1154
+ sanitized = sanitized.replace(/^[.-]+/, '').replace(/[.-]+$/, ''); // Remove leading/trailing . or -
1155
+ if (!sanitized || !/^[a-z0-9]/.test(sanitized)) sanitized = `a${sanitized || ''}`;
1156
+ if (sanitized.length > 128) sanitized = sanitized.slice(0, 128);
1157
+ return sanitized;
1158
+ }
1159
+
1143
1160
  /**
1144
1161
  * Build agent-specific image
1145
1162
  */
1146
1163
  async buildAgentImage(agent, options = {}) {
1147
- const imageName = `dank-agent-${agent.name.toLowerCase()}`;
1164
+ const normalizedName = this.normalizeDockerName(agent.name);
1165
+ const imageName = `dank-agent-${normalizedName}`;
1148
1166
  this.logger.info(`Building image for agent: ${agent.name}`);
1149
1167
 
1150
1168
  try {
1151
1169
  const buildContext = await this.createAgentBuildContext(agent);
1152
-
1153
- const stream = await this.docker.buildImage(buildContext, {
1154
- t: imageName,
1155
- dockerfile: "Dockerfile",
1156
- nocache: options.rebuild || options.noCache || false,
1157
- });
1158
-
1159
- await this.followBuildProgress(stream, `Agent ${agent.name} build`);
1170
+ const dockerCmd = await this.resolveDockerCommand();
1171
+
1172
+ const buildCommand = [
1173
+ dockerCmd,
1174
+ 'buildx',
1175
+ 'build',
1176
+ '--platform', 'linux/amd64',
1177
+ '--tag', imageName,
1178
+ '--file', path.join(buildContext, 'Dockerfile'),
1179
+ '--load',
1180
+ ...(options.rebuild || options.noCache ? ['--no-cache'] : []),
1181
+ buildContext
1182
+ ].join(' ');
1183
+
1184
+ await this.runCommand(buildCommand, `Agent ${agent.name} build`);
1160
1185
 
1161
1186
  this.logger.info(`Agent image '${imageName}' built successfully`);
1162
1187
 
@@ -1182,30 +1207,24 @@ class DockerManager {
1182
1207
  push = false,
1183
1208
  } = options;
1184
1209
 
1185
- // Normalize a Docker tag from agent name when tagByAgent is enabled
1186
- const normalizeTag = (name) => {
1187
- const lower = String(name || '').toLowerCase();
1188
- let sanitized = lower.replace(/[^a-z0-9_.-]/g, '-');
1189
- sanitized = sanitized.replace(/^-+/, '').replace(/-+$/, '');
1190
- if (!sanitized || !/^[a-z0-9]/.test(sanitized)) sanitized = `a${sanitized || ''}`;
1191
- if (sanitized.length > 128) sanitized = sanitized.slice(0, 128);
1192
- return sanitized;
1193
- };
1210
+ // Normalize all components
1211
+ const normalizedAgentName = this.normalizeDockerName(agent.name);
1212
+ const normalizedTag = this.normalizeDockerName(tag);
1194
1213
 
1195
1214
  //construct full repo name
1196
1215
  let repoName;
1197
1216
  if(!tagByAgent){
1198
-
1199
- repoName = `${registry?`${registry}/`:''}${namespace?`${namespace}/`:''}${repoName}`;
1217
+ // Per-agent repository: {registry}/{namespace}/{agent-name}
1218
+ repoName = `${registry?`${registry}/`:''}${namespace?`${namespace}/`:''}${normalizedAgentName}`;
1200
1219
  }else{
1220
+ // Common repository: {registry}/{namespace}
1201
1221
  repoName = `${registry?`${registry}/`:''}${namespace?`${namespace}/`:''}`;
1202
1222
  }
1203
1223
 
1204
1224
  repoName = repoName.replace(/\/+$/, '');
1205
1225
 
1206
-
1207
- // Final tag selection
1208
- const finalTag = tagByAgent ? normalizeTag(agent.name) : tag;
1226
+ // Final tag selection - normalize both agent name tags and user-provided tags
1227
+ const finalTag = tagByAgent ? normalizedAgentName : normalizedTag;
1209
1228
  const imageName = `${repoName}:${finalTag}`;
1210
1229
 
1211
1230
  this.logger.info(
@@ -1214,54 +1233,33 @@ class DockerManager {
1214
1233
 
1215
1234
  try {
1216
1235
  const buildContext = await this.createAgentBuildContext(agent);
1217
-
1218
- const stream = await this.docker.buildImage(buildContext, {
1219
- t: imageName,
1220
- dockerfile: "Dockerfile",
1221
- nocache: force,
1222
- });
1223
-
1224
- await this.followBuildProgress(
1225
- stream,
1226
- `Production build for ${agent.name}`
1227
- );
1236
+ const dockerCmd = await this.resolveDockerCommand();
1237
+
1238
+ const buildCommand = [
1239
+ dockerCmd,
1240
+ 'buildx',
1241
+ 'build',
1242
+ '--platform', 'linux/amd64',
1243
+ '--tag', imageName,
1244
+ '--file', path.join(buildContext, 'Dockerfile'),
1245
+ ...(push ? ['--push'] : ['--load']),
1246
+ ...(force ? ['--no-cache'] : []),
1247
+ buildContext
1248
+ ].join(' ');
1249
+
1250
+ await this.runCommand(buildCommand, `Production build for ${agent.name}`);
1228
1251
 
1229
1252
  this.logger.info(`Production image '${imageName}' built successfully`);
1253
+ if (push) {
1254
+ this.logger.info(`Successfully pushed image: ${imageName}`);
1255
+ }
1230
1256
 
1231
1257
  // Clean up build context
1232
1258
  await fs.remove(buildContext);
1233
1259
 
1234
- let pushed = false;
1235
-
1236
- // Push to registry if requested (use docker CLI for reliability)
1237
- if (push) {
1238
- const dockerCmd = await this.resolveDockerCommand();
1239
- const maxAttempts = 3;
1240
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1241
- try {
1242
- this.logger.info(`Pushing image to registry (attempt ${attempt}/${maxAttempts}): ${imageName}`);
1243
- await this.runCommand(`${dockerCmd} push "${imageName}"`, `Docker push ${imageName}`);
1244
- this.logger.info(`Successfully pushed image: ${imageName}`);
1245
- pushed = true;
1246
- break;
1247
- } catch (pushError) {
1248
- const msg = pushError?.message || '';
1249
- this.logger.warn(`Push attempt ${attempt} failed: ${msg}`);
1250
- if (msg.match(/denied|unauthorized|authentication required/i)) {
1251
- this.logger.warn("Authentication issue detected. Please ensure you're logged in: docker login <registry>");
1252
- break; // don't retry auth failures automatically
1253
- }
1254
- if (attempt < maxAttempts) {
1255
- await this.sleep(2000 * attempt);
1256
- continue;
1257
- }
1258
- }
1259
- }
1260
- }
1261
-
1262
1260
  return {
1263
1261
  imageName,
1264
- pushed,
1262
+ pushed: push,
1265
1263
  };
1266
1264
  } catch (error) {
1267
1265
  throw new Error(`Failed to build production image: ${error.message}`);
@@ -1623,20 +1621,8 @@ module.exports = {
1623
1621
 
1624
1622
  await fs.writeFile(path.join(agentCodeDir, "index.js"), agentCode);
1625
1623
 
1626
- // Create tarball
1627
- const tarPath = path.join(
1628
- __dirname,
1629
- `../../.agent-${agent.name}-context.tar`
1630
- );
1631
- await tar.create(
1632
- {
1633
- file: tarPath,
1634
- cwd: contextDir,
1635
- },
1636
- ["."]
1637
- );
1638
-
1639
- return tarPath;
1624
+ // Return directory path (CLI build commands work with directories directly)
1625
+ return contextDir;
1640
1626
  }
1641
1627
 
1642
1628
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dank-ai",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "Dank Agent Service - Docker-based AI agent orchestration platform",
5
5
  "main": "lib/index.js",
6
6
  "exports": {