@yemi33/minions 0.1.1567 → 0.1.1568

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1568 (2026-04-27)
4
+
5
+ ### Fixes
6
+ - smooth project add and scan UX
7
+
3
8
  ## 0.1.1567 (2026-04-27)
4
9
 
5
10
  ### Other
@@ -21,6 +21,28 @@ function renderProjects(projects) {
21
21
 
22
22
  }
23
23
 
24
+ function _projectCachePath(project) {
25
+ return String((project && (project.localPath || project.path)) || '').replace(/\\/g, '/');
26
+ }
27
+
28
+ function optimisticallyAddProject(project) {
29
+ if (!project || !project.name) return;
30
+ const next = {
31
+ name: project.name,
32
+ description: project.description || '',
33
+ localPath: _projectCachePath(project),
34
+ path: _projectCachePath(project),
35
+ };
36
+ if (!window._lastStatus) window._lastStatus = {};
37
+ const current = Array.isArray(window._lastStatus.projects) ? window._lastStatus.projects.slice() : [];
38
+ const nextPath = _projectCachePath(next);
39
+ if (current.some(function(p) { return p.name === next.name || (nextPath && _projectCachePath(p) === nextPath); })) return;
40
+ current.push(next);
41
+ window._lastStatus.projects = current;
42
+ if (typeof cmdUpdateProjectList === 'function') cmdUpdateProjectList(current);
43
+ renderProjects(current);
44
+ }
45
+
24
46
  async function projectChipRemove(name) {
25
47
  if (!confirm('Remove project "' + name + '"? Pending work cancels, active agents are killed, data dir is archived to projects/.archived/.')) return;
26
48
  showToast('cmd-toast', 'Removing project "' + name + '"...', true);
@@ -385,7 +407,18 @@ async function _addSelectedProjects() {
385
407
  method: 'POST', headers: { 'Content-Type': 'application/json' },
386
408
  body: JSON.stringify({ path: repo.path })
387
409
  });
388
- if (res.ok) { added++; cb.disabled = true; cb.closest('label').style.opacity = '0.5'; }
410
+ var data = await res.json().catch(function() { return {}; });
411
+ if (res.ok) {
412
+ added++;
413
+ optimisticallyAddProject({
414
+ name: data.name || repo.name,
415
+ description: (data.detected && data.detected.description) || repo.description || '',
416
+ path: data.path || repo.path,
417
+ localPath: data.path || repo.path,
418
+ });
419
+ cb.disabled = true;
420
+ cb.closest('label').style.opacity = '0.5';
421
+ }
389
422
  } catch { /* continue with next */ }
390
423
  }
391
424
  if (added > 0) {
@@ -394,4 +427,4 @@ async function _addSelectedProjects() {
394
427
  }
395
428
  }
396
429
 
397
- window.MinionsOther = { renderProjects, projectChipRemove, renderMcpServers, renderMetrics, renderLlmPerf, renderTokenUsage, openScanProjectsModal };
430
+ window.MinionsOther = { renderProjects, optimisticallyAddProject, projectChipRemove, renderMcpServers, renderMetrics, renderLlmPerf, renderTokenUsage, openScanProjectsModal };
@@ -384,6 +384,19 @@ async function addProject() {
384
384
  });
385
385
  const addData = await addRes.json();
386
386
  if (!addRes.ok) { alert('Failed: ' + (addData.error || 'unknown')); return; }
387
+ const addedProject = {
388
+ name: addData.name,
389
+ description: (addData.detected && addData.detected.description) || '',
390
+ path: addData.path,
391
+ localPath: addData.path,
392
+ };
393
+ if (_settingsData && Array.isArray(_settingsData.projects)) {
394
+ const exists = _settingsData.projects.some(function(p) {
395
+ return p.name === addedProject.name || String(p.localPath || p.path || '').replace(/\\/g, '/') === String(addedProject.localPath || '').replace(/\\/g, '/');
396
+ });
397
+ if (!exists) _settingsData.projects = _settingsData.projects.concat([addedProject]);
398
+ }
399
+ if (typeof optimisticallyAddProject === 'function') optimisticallyAddProject(addedProject);
387
400
  try { showToast('cmd-toast', 'Project "' + addData.name + '" added — restart engine to pick it up', true); } catch { /* expected */ }
388
401
  refresh();
389
402
  } catch (e) { alert('Error: ' + e.message); }
package/dashboard.js CHANGED
@@ -3807,6 +3807,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3807
3807
  return jsonReply(res, 200, { confirmToken: token, ttlMs: PROJECT_CONFIRM_TOKEN_TTL_MS });
3808
3808
  }
3809
3809
 
3810
+ function _execGitInRepo(repoPath, args, timeoutMs) {
3811
+ const { execFileSync } = require('child_process');
3812
+ return execFileSync('git', args, {
3813
+ cwd: repoPath,
3814
+ encoding: 'utf8',
3815
+ timeout: timeoutMs || 5000,
3816
+ stdio: ['ignore', 'pipe', 'pipe'],
3817
+ windowsHide: true,
3818
+ }).trim();
3819
+ }
3820
+
3810
3821
  async function handleProjectsAdd(req, res) {
3811
3822
  try {
3812
3823
  const body = await readBody(req);
@@ -3840,14 +3851,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3840
3851
  }
3841
3852
 
3842
3853
  // Auto-discover from git repo
3843
- const { execSync: ex } = require('child_process');
3844
3854
  const detected = { name: path.basename(target), _found: [] };
3845
3855
  try {
3846
- const head = ex('git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || git symbolic-ref HEAD', { cwd: target, encoding: 'utf8', timeout: 5000 }).trim();
3856
+ let head = '';
3857
+ try { head = _execGitInRepo(target, ['symbolic-ref', 'refs/remotes/origin/HEAD'], 5000); }
3858
+ catch { head = _execGitInRepo(target, ['symbolic-ref', 'HEAD'], 5000); }
3859
+ if (!head) throw new Error('empty git ref');
3847
3860
  detected.mainBranch = head.replace('refs/remotes/origin/', '').replace('refs/heads/', '');
3848
3861
  } catch { detected.mainBranch = 'main'; }
3849
3862
  try {
3850
- const remoteUrl = ex('git remote get-url origin', { cwd: target, encoding: 'utf8', timeout: 5000 }).trim();
3863
+ const remoteUrl = _execGitInRepo(target, ['remote', 'get-url', 'origin'], 5000);
3851
3864
  if (remoteUrl.includes('github.com')) {
3852
3865
  detected.repoHost = 'github';
3853
3866
  const m = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
@@ -3906,6 +3919,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3906
3919
  config.projects.push(project);
3907
3920
  safeWrite(configPath, config);
3908
3921
  reloadConfig(); // Update in-memory project list immediately
3922
+ invalidateStatusCache();
3909
3923
 
3910
3924
  // Create project-local state files
3911
3925
  const minionsDir = path.join(target, '.minions');
@@ -3969,7 +3983,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
3969
3983
  const results = repos.map(repoPath => {
3970
3984
  const result = { path: repoPath.replace(/\\/g, '/'), name: path.basename(repoPath), host: 'git', linked: existingPaths.has(path.resolve(repoPath)) };
3971
3985
  try {
3972
- const remoteUrl = require('child_process').execSync('git remote get-url origin', { cwd: repoPath, encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
3986
+ const remoteUrl = _execGitInRepo(repoPath, ['remote', 'get-url', 'origin'], 3000);
3973
3987
  const gh = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
3974
3988
  const ado = remoteUrl.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/\s]+)/) || remoteUrl.match(/([^.]+)\.visualstudio\.com.*?\/([^/]+)\/_git\/([^/\s]+)/);
3975
3989
  if (gh) { result.host = 'GitHub'; result.org = gh[1]; result.name = gh[2]; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1567",
3
+ "version": "0.1.1568",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"