instar 0.24.19 → 0.24.21

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.
Files changed (41) hide show
  1. package/.claude/settings.json +120 -0
  2. package/.claude/skills/setup-wizard/skill.md +2 -2
  3. package/dashboard/index.html +146 -4
  4. package/dist/cli.js +0 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +22 -4
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/server.d.ts.map +1 -1
  9. package/dist/commands/server.js +18 -1
  10. package/dist/commands/server.js.map +1 -1
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/commands/setup.js +23 -2
  13. package/dist/commands/setup.js.map +1 -1
  14. package/dist/core/GitSync.d.ts +3 -2
  15. package/dist/core/GitSync.d.ts.map +1 -1
  16. package/dist/core/GitSync.js +19 -4
  17. package/dist/core/GitSync.js.map +1 -1
  18. package/dist/core/SessionManager.d.ts +1 -1
  19. package/dist/core/SessionManager.d.ts.map +1 -1
  20. package/dist/core/SessionManager.js.map +1 -1
  21. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
  22. package/dist/lifeline/ServerSupervisor.js +31 -0
  23. package/dist/lifeline/ServerSupervisor.js.map +1 -1
  24. package/dist/messaging/slack/SlackAdapter.d.ts +25 -4
  25. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  26. package/dist/messaging/slack/SlackAdapter.js +156 -12
  27. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  28. package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -1
  29. package/dist/messaging/slack/SocketModeClient.js +11 -4
  30. package/dist/messaging/slack/SocketModeClient.js.map +1 -1
  31. package/dist/messaging/slack/types.d.ts +30 -0
  32. package/dist/messaging/slack/types.d.ts.map +1 -1
  33. package/dist/messaging/slack/types.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/data/builtin-manifest.json +4 -4
  36. package/upgrades/0.24.14.md +26 -0
  37. package/upgrades/0.24.18-beta.0.md +35 -0
  38. package/upgrades/0.24.18.md +26 -26
  39. package/upgrades/0.24.21.md +25 -0
  40. package/upgrades/NEXT.md +45 -0
  41. /package/.claude/skills/secret-setup/{skill.md → SKILL.md} +0 -0
@@ -67,6 +67,126 @@
67
67
  }
68
68
  ]
69
69
  }
70
+ ],
71
+ "PostToolUse": [
72
+ {
73
+ "matcher": "",
74
+ "hooks": [
75
+ {
76
+ "type": "command",
77
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
78
+ "timeout": 3000
79
+ }
80
+ ]
81
+ }
82
+ ],
83
+ "SubagentStart": [
84
+ {
85
+ "matcher": "",
86
+ "hooks": [
87
+ {
88
+ "type": "command",
89
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
90
+ "timeout": 3000
91
+ }
92
+ ]
93
+ }
94
+ ],
95
+ "SubagentStop": [
96
+ {
97
+ "matcher": "",
98
+ "hooks": [
99
+ {
100
+ "type": "command",
101
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
102
+ "timeout": 3000
103
+ }
104
+ ]
105
+ }
106
+ ],
107
+ "Stop": [
108
+ {
109
+ "matcher": "",
110
+ "hooks": [
111
+ {
112
+ "type": "command",
113
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
114
+ "timeout": 3000
115
+ }
116
+ ]
117
+ }
118
+ ],
119
+ "WorktreeCreate": [
120
+ {
121
+ "matcher": "",
122
+ "hooks": [
123
+ {
124
+ "type": "command",
125
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
126
+ "timeout": 3000
127
+ }
128
+ ]
129
+ }
130
+ ],
131
+ "WorktreeRemove": [
132
+ {
133
+ "matcher": "",
134
+ "hooks": [
135
+ {
136
+ "type": "command",
137
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
138
+ "timeout": 3000
139
+ }
140
+ ]
141
+ }
142
+ ],
143
+ "TaskCompleted": [
144
+ {
145
+ "matcher": "",
146
+ "hooks": [
147
+ {
148
+ "type": "command",
149
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
150
+ "timeout": 3000
151
+ }
152
+ ]
153
+ }
154
+ ],
155
+ "SessionEnd": [
156
+ {
157
+ "matcher": "",
158
+ "hooks": [
159
+ {
160
+ "type": "command",
161
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
162
+ "timeout": 3000
163
+ }
164
+ ]
165
+ }
166
+ ],
167
+ "PreCompact": [
168
+ {
169
+ "matcher": "",
170
+ "hooks": [
171
+ {
172
+ "type": "command",
173
+ "command": "node .instar/hooks/instar/hook-event-reporter.js",
174
+ "timeout": 3000
175
+ }
176
+ ]
177
+ }
178
+ ],
179
+ "PermissionRequest": [
180
+ {
181
+ "matcher": "",
182
+ "hooks": [
183
+ {
184
+ "type": "command",
185
+ "command": "node .instar/hooks/instar/auto-approve-permissions.js",
186
+ "timeout": 5000
187
+ }
188
+ ]
189
+ }
70
190
  ]
71
191
  },
72
192
  "mcpServers": {
@@ -1621,8 +1621,8 @@ Build the app manifest JSON with minimal Phase 1 scopes:
1621
1621
  "oauth_config": {
1622
1622
  "scopes": {
1623
1623
  "bot": [
1624
- "channels:history", "channels:manage", "channels:read",
1625
- "chat:write", "im:history", "im:read", "im:write",
1624
+ "channels:history", "channels:join", "channels:manage", "channels:read",
1625
+ "chat:write", "files:read", "groups:history", "im:history", "im:read", "im:write",
1626
1626
  "pins:write", "reactions:read", "reactions:write", "users:read"
1627
1627
  ]
1628
1628
  }
@@ -2004,6 +2004,19 @@
2004
2004
  .systems-event-time { color: var(--text-dim); white-space: nowrap; font-size: 11px; min-width: 60px; }
2005
2005
  .systems-event-text { color: var(--text); flex: 1; }
2006
2006
 
2007
+ /* Content Browser (browsable stats) */
2008
+ .cap-content-panel { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; margin-top: 12px; overflow: hidden; }
2009
+ .cap-content-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--border); font-size: 12px; font-weight: 600; color: var(--text-bright); }
2010
+ .cap-content-body { max-height: 400px; overflow-y: auto; padding: 8px; }
2011
+ .cap-content-item { padding: 10px 12px; border: 1px solid var(--border); border-radius: 6px; margin-bottom: 6px; background: var(--bg-panel); }
2012
+ .cap-content-item-title { font-size: 13px; font-weight: 500; color: var(--text-bright); margin-bottom: 4px; }
2013
+ .cap-content-item-body { font-size: 12px; color: var(--text-dim); line-height: 1.5; word-break: break-word; }
2014
+ .cap-content-item-time { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
2015
+ .cap-content-tag { display: inline-block; font-size: 10px; padding: 2px 8px; border-radius: 3px; background: rgba(255,255,255,0.06); color: var(--text-dim); margin-right: 4px; margin-top: 4px; }
2016
+ .cap-content-tag.applied { background: rgba(76,175,80,0.15); color: var(--accent); }
2017
+ .cap-content-empty { padding: 16px; text-align: center; color: var(--text-dim); font-size: 12px; }
2018
+ .cap-stat-card:hover { border-color: var(--accent); transition: border-color 0.15s; }
2019
+
2007
2020
  .discovery-container {
2008
2021
  grid-column: 1 / -1;
2009
2022
  overflow-y: auto;
@@ -5484,7 +5497,7 @@
5484
5497
  document.getElementById('systemsDetailView').classList.add('active');
5485
5498
  const content = document.getElementById('systemsDetailContent');
5486
5499
 
5487
- const statCards = buildStatCards(cap.stats || {});
5500
+ const statCards = buildStatCards(cap.stats || {}, capId);
5488
5501
  const processRows = (cap.processes || []).map(p => {
5489
5502
  const dotColor = p.status === 'running' ? 'var(--accent)' : 'var(--red)';
5490
5503
  return `<div class="cap-process-row">
@@ -5506,7 +5519,7 @@
5506
5519
  <div class="cap-detail-description">${esc(cap.description)}</div>
5507
5520
  ${lastAct}
5508
5521
  </div>
5509
- ${statCards ? '<div class="cap-detail-section"><div class="cap-detail-section-title">Metrics</div><div class="cap-stats-grid">' + statCards + '</div></div>' : ''}
5522
+ ${statCards ? '<div class="cap-detail-section"><div class="cap-detail-section-title">Metrics</div><div class="cap-stats-grid">' + statCards + '</div><div id="capContentBrowser"></div></div>' : ''}
5510
5523
  <div class="cap-detail-section">
5511
5524
  <div class="cap-detail-section-title">Components (${(cap.processes || []).length})</div>
5512
5525
  <div class="cap-process-list">${processRows}</div>
@@ -5517,7 +5530,31 @@
5517
5530
  </div>`;
5518
5531
  }
5519
5532
 
5520
- function buildStatCards(stats) {
5533
+ // Map stat keys to browsable content endpoints
5534
+ const BROWSABLE_STATS = {
5535
+ // Evolution
5536
+ learnings: { endpoint: '/evolution/learnings', label: 'Learnings', renderer: 'renderLearnings' },
5537
+ proposals: { endpoint: '/evolution/proposals', label: 'Proposals', renderer: 'renderProposals' },
5538
+ gaps: { endpoint: '/evolution/gaps', label: 'Capability Gaps', renderer: 'renderGaps' },
5539
+ actions: { endpoint: '/evolution/actions', label: 'Actions', renderer: 'renderActions' },
5540
+ overdueActions: { endpoint: '/evolution/actions/overdue', label: 'Overdue Actions', renderer: 'renderActions' },
5541
+ // Health
5542
+ coherenceChecks: { endpoint: '/health/coherence', label: 'Coherence Report', renderer: 'renderCoherence' },
5543
+ coherencePassed: { endpoint: '/health/coherence', label: 'Coherence Report', renderer: 'renderCoherence' },
5544
+ coherenceFailed: { endpoint: '/health/coherence', label: 'Coherence Report', renderer: 'renderCoherence' },
5545
+ // Recovery
5546
+ totalTriages: { endpoint: '/triage/history?limit=20', label: 'Triage History', renderer: 'renderTriageHistory' },
5547
+ interventions: { endpoint: '/watchdog/status', label: 'Watchdog Status', renderer: 'renderWatchdog' },
5548
+ recoveries: { endpoint: '/watchdog/status', label: 'Watchdog Status', renderer: 'renderWatchdog' },
5549
+ // Jobs
5550
+ totalJobs: { endpoint: '/jobs', label: 'Jobs', renderer: 'renderJobs' },
5551
+ enabledJobs: { endpoint: '/jobs', label: 'Jobs', renderer: 'renderJobs' },
5552
+ // Telegram
5553
+ totalMessages: { endpoint: '/telegram/log-stats', label: 'Message Stats', renderer: 'renderTelegramStats' },
5554
+ topicMappings: { endpoint: '/telegram/log-stats', label: 'Message Stats', renderer: 'renderTelegramStats' },
5555
+ };
5556
+
5557
+ function buildStatCards(stats, capId) {
5521
5558
  if (!stats || typeof stats !== 'object') return '';
5522
5559
  const map = {
5523
5560
  interventions: 'Interventions', recoveries: 'Recoveries', sessionDeaths: 'Deaths',
@@ -5535,12 +5572,117 @@
5535
5572
  const cards = [];
5536
5573
  for (const [key, label] of Object.entries(map)) {
5537
5574
  if (stats[key] != null && typeof stats[key] === 'number') {
5538
- cards.push(`<div class="cap-stat-card"><div class="cap-stat-card-value">${stats[key]}</div><div class="cap-stat-card-label">${esc(label)}</div></div>`);
5575
+ const browsable = BROWSABLE_STATS[key];
5576
+ const clickAttr = browsable ? ` onclick="browseStatContent('${key}')" style="cursor:pointer"` : '';
5577
+ const browseHint = browsable ? '<div style="font-size:9px;color:var(--accent);margin-top:2px">click to view</div>' : '';
5578
+ cards.push(`<div class="cap-stat-card"${clickAttr}><div class="cap-stat-card-value">${stats[key]}</div><div class="cap-stat-card-label">${esc(label)}</div>${browseHint}</div>`);
5539
5579
  }
5540
5580
  }
5541
5581
  return cards.join('');
5542
5582
  }
5543
5583
 
5584
+ async function browseStatContent(statKey) {
5585
+ const info = BROWSABLE_STATS[statKey];
5586
+ if (!info) return;
5587
+ const panel = document.getElementById('capContentBrowser');
5588
+ if (!panel) return;
5589
+ panel.innerHTML = '<div style="padding:12px;color:var(--text-dim);font-size:12px">Loading ' + esc(info.label) + '...</div>';
5590
+ try {
5591
+ const data = await apiFetch(info.endpoint);
5592
+ panel.innerHTML = '<div class="cap-content-panel">' +
5593
+ '<div class="cap-content-header"><span>' + esc(info.label) + '</span><button onclick="document.getElementById(\'capContentBrowser\').innerHTML=\'\'" style="border:none;background:none;color:var(--text-dim);cursor:pointer;font-size:14px">&times;</button></div>' +
5594
+ '<div class="cap-content-body">' + renderBrowsableContent(info.renderer, data) + '</div></div>';
5595
+ } catch (e) {
5596
+ panel.innerHTML = '<div style="padding:12px;color:var(--red);font-size:12px">Failed to load: ' + esc(e.message) + '</div>';
5597
+ }
5598
+ }
5599
+
5600
+ function renderBrowsableContent(renderer, data) {
5601
+ switch (renderer) {
5602
+ case 'renderLearnings': {
5603
+ const items = data.learnings || data || [];
5604
+ if (!Array.isArray(items) || items.length === 0) return '<div class="cap-content-empty">No learnings recorded</div>';
5605
+ return items.map(l => `<div class="cap-content-item">
5606
+ <div class="cap-content-item-title">${esc(l.title || l.insight || l.id || 'Untitled')}</div>
5607
+ ${l.description || l.context ? '<div class="cap-content-item-body">' + esc(l.description || l.context || '') + '</div>' : ''}
5608
+ ${l.category ? '<span class="cap-content-tag">' + esc(l.category) + '</span>' : ''}
5609
+ ${l.applied ? '<span class="cap-content-tag applied">Applied</span>' : ''}
5610
+ ${l.createdAt ? '<div class="cap-content-item-time">' + esc(timeAgo(l.createdAt)) + '</div>' : ''}
5611
+ </div>`).join('');
5612
+ }
5613
+ case 'renderProposals': {
5614
+ const items = data.proposals || data || [];
5615
+ if (!Array.isArray(items) || items.length === 0) return '<div class="cap-content-empty">No proposals</div>';
5616
+ return items.map(p => `<div class="cap-content-item">
5617
+ <div class="cap-content-item-title">${esc(p.title || p.id || 'Untitled')}</div>
5618
+ ${p.description ? '<div class="cap-content-item-body">' + esc(p.description) + '</div>' : ''}
5619
+ ${p.status ? '<span class="cap-content-tag">' + esc(p.status) + '</span>' : ''}
5620
+ ${p.type ? '<span class="cap-content-tag">' + esc(p.type) + '</span>' : ''}
5621
+ </div>`).join('');
5622
+ }
5623
+ case 'renderGaps': {
5624
+ const items = data.gaps || data || [];
5625
+ if (!Array.isArray(items) || items.length === 0) return '<div class="cap-content-empty">No gaps detected</div>';
5626
+ return items.map(g => `<div class="cap-content-item">
5627
+ <div class="cap-content-item-title">${esc(g.title || g.capability || g.id || 'Untitled')}</div>
5628
+ ${g.description ? '<div class="cap-content-item-body">' + esc(g.description) + '</div>' : ''}
5629
+ ${g.severity ? '<span class="cap-content-tag">' + esc(g.severity) + '</span>' : ''}
5630
+ </div>`).join('');
5631
+ }
5632
+ case 'renderActions': {
5633
+ const items = data.actions || data || [];
5634
+ if (!Array.isArray(items) || items.length === 0) return '<div class="cap-content-empty">No actions</div>';
5635
+ return items.map(a => `<div class="cap-content-item">
5636
+ <div class="cap-content-item-title">${esc(a.title || a.action || a.id || 'Untitled')}</div>
5637
+ ${a.status ? '<span class="cap-content-tag">' + esc(a.status) + '</span>' : ''}
5638
+ ${a.dueAt ? '<div class="cap-content-item-time">Due: ' + esc(timeAgo(a.dueAt)) + '</div>' : ''}
5639
+ </div>`).join('');
5640
+ }
5641
+ case 'renderCoherence': {
5642
+ const report = data.lastReport || data;
5643
+ if (!report || !report.checks) return '<div class="cap-content-empty">No coherence data</div>';
5644
+ return report.checks.map(c => `<div class="cap-content-item" style="border-left:3px solid ${c.passed ? 'var(--accent)' : 'var(--red)'}">
5645
+ <div class="cap-content-item-title">${c.passed ? '&check;' : '&cross;'} ${esc(c.name)}</div>
5646
+ <div class="cap-content-item-body">${esc(c.message)}</div>
5647
+ </div>`).join('');
5648
+ }
5649
+ case 'renderTriageHistory': {
5650
+ const items = data.history || data || [];
5651
+ if (!Array.isArray(items) || items.length === 0) return '<div class="cap-content-empty">No triage history</div>';
5652
+ return items.map(t => `<div class="cap-content-item">
5653
+ <div class="cap-content-item-title">Topic ${t.topicId} — ${esc(t.sessionName || 'unknown')}</div>
5654
+ <div class="cap-content-item-body">${t.result ? esc(JSON.stringify(t.result.actionsTaken || t.result, null, 0).substring(0, 200)) : ''}</div>
5655
+ ${t.timestamp ? '<div class="cap-content-item-time">' + esc(timeAgo(t.timestamp)) + '</div>' : ''}
5656
+ </div>`).join('');
5657
+ }
5658
+ case 'renderWatchdog': {
5659
+ const hist = data.interventionHistory || [];
5660
+ if (hist.length === 0) return '<div class="cap-content-empty">No interventions recorded</div>';
5661
+ return hist.map(h => `<div class="cap-content-item">
5662
+ <div class="cap-content-item-title">PID ${h.stuckPid || '?'} — ${esc(h.level || h.outcome || 'intervention')}</div>
5663
+ ${h.outcome ? '<span class="cap-content-tag">' + esc(h.outcome) + '</span>' : ''}
5664
+ ${h.timestamp ? '<div class="cap-content-item-time">' + esc(timeAgo(h.timestamp)) + '</div>' : ''}
5665
+ </div>`).join('');
5666
+ }
5667
+ case 'renderJobs': {
5668
+ const jobs = data.jobs || data || [];
5669
+ if (!Array.isArray(jobs) || jobs.length === 0) return '<div class="cap-content-empty">No jobs</div>';
5670
+ return jobs.slice(0, 20).map(j => `<div class="cap-content-item">
5671
+ <div class="cap-content-item-title">${esc(j.slug || j.name || 'unnamed')}</div>
5672
+ <div class="cap-content-item-body">${esc(j.cron || '')} ${j.enabled === false ? '(disabled)' : ''}</div>
5673
+ </div>`).join('');
5674
+ }
5675
+ case 'renderTelegramStats': {
5676
+ return `<div class="cap-content-item">
5677
+ <div class="cap-content-item-title">Message Log</div>
5678
+ <div class="cap-content-item-body">Total messages: ${data.totalMessages || '?'}<br>Log size: ${data.logSizeBytes ? Math.round(data.logSizeBytes / 1024) + ' KB' : '?'}</div>
5679
+ </div>`;
5680
+ }
5681
+ default:
5682
+ return '<div class="cap-content-empty">No viewer for this content</div>';
5683
+ }
5684
+ }
5685
+
5544
5686
  function showSystemsOverview() {
5545
5687
  document.getElementById('systemsOverview').style.display = '';
5546
5688
  document.getElementById('systemsDetailView').classList.remove('active');
package/dist/cli.js CHANGED
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA6BH,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAgzFD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAiBlF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+CH,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAgzFD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAiBlF"}
@@ -31,7 +31,7 @@ import os from 'node:os';
31
31
  import path from 'node:path';
32
32
  import pc from 'picocolors';
33
33
  import { randomUUID } from 'node:crypto';
34
- import { execFileSync } from 'node:child_process';
34
+ import { execFileSync, execSync } from 'node:child_process';
35
35
  import { detectTmuxPath, detectClaudePath, detectGitPath, detectGhPath, ensureStateDir, standaloneAgentsDir, getInstarVersion } from '../core/Config.js';
36
36
  import { ensurePrerequisites } from '../core/Prerequisites.js';
37
37
  import { allocatePort, registerAgent, validateAgentName } from '../core/AgentRegistry.js';
@@ -44,6 +44,24 @@ import { CanonicalState } from '../core/CanonicalState.js';
44
44
  import { ManifestIntegrity } from '../security/ManifestIntegrity.js';
45
45
  import { buildHttpHookSettings } from '../data/http-hook-templates.js';
46
46
  import { generateAgentMd, generateUserMd, generateMemoryMd, generateClaudeMd, generateSoulMd, } from '../scaffold/templates.js';
47
+ /**
48
+ * Find a free port in the default range (4040-4099) by checking if anything
49
+ * is listening. Used as fallback when allocatePort() fails (e.g., registry
50
+ * is corrupted or locked).
51
+ */
52
+ function findFreePortFallback() {
53
+ for (let port = 4040; port <= 4099; port++) {
54
+ try {
55
+ execSync(`lsof -iTCP:${port} -sTCP:LISTEN -P -n`, { stdio: 'ignore' });
56
+ // lsof found a listener — port is in use
57
+ }
58
+ catch {
59
+ // lsof found nothing — port is free
60
+ return port;
61
+ }
62
+ }
63
+ return 4040; // All ports in range are busy — return default and let server fail with a clear error
64
+ }
47
65
  /**
48
66
  * Main init entry point. Handles both fresh and existing project modes.
49
67
  */
@@ -124,7 +142,7 @@ async function initFreshProject(projectName, options) {
124
142
  console.log(` ${pc.green('✓')} Auto-allocated port ${port} (from ~/.instar/registry.json)`);
125
143
  }
126
144
  catch {
127
- port = 4040; // Fallback to default
145
+ port = findFreePortFallback();
128
146
  }
129
147
  }
130
148
  // Generate identity (non-interactive for init, interactive for setup)
@@ -401,7 +419,7 @@ async function initExistingProject(options) {
401
419
  port = allocatePort(projectDir);
402
420
  }
403
421
  catch {
404
- port = 4040;
422
+ port = findFreePortFallback();
405
423
  }
406
424
  }
407
425
  console.log(pc.bold(`\nInitializing instar in: ${pc.cyan(projectDir)}`));
@@ -732,7 +750,7 @@ async function initStandaloneAgent(agentName, options) {
732
750
  console.log(` ${pc.green('✓')} Auto-allocated port ${port}`);
733
751
  }
734
752
  catch {
735
- port = 4040;
753
+ port = findFreePortFallback();
736
754
  }
737
755
  }
738
756
  // Create directory structure