symphony-github 0.1.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.
Files changed (166) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +341 -0
  3. package/config.example.yaml +101 -0
  4. package/dist/agents/launcher.d.ts +24 -0
  5. package/dist/agents/launcher.d.ts.map +1 -0
  6. package/dist/agents/launcher.js +152 -0
  7. package/dist/agents/launcher.js.map +1 -0
  8. package/dist/agents/registry.d.ts +10 -0
  9. package/dist/agents/registry.d.ts.map +1 -0
  10. package/dist/agents/registry.js +324 -0
  11. package/dist/agents/registry.js.map +1 -0
  12. package/dist/agents/runner.d.ts +58 -0
  13. package/dist/agents/runner.d.ts.map +1 -0
  14. package/dist/agents/runner.js +1190 -0
  15. package/dist/agents/runner.js.map +1 -0
  16. package/dist/app.d.ts +11 -0
  17. package/dist/app.d.ts.map +1 -0
  18. package/dist/app.js +829 -0
  19. package/dist/app.js.map +1 -0
  20. package/dist/components/ActivityView.d.ts +9 -0
  21. package/dist/components/ActivityView.d.ts.map +1 -0
  22. package/dist/components/ActivityView.js +73 -0
  23. package/dist/components/ActivityView.js.map +1 -0
  24. package/dist/components/Header.d.ts +12 -0
  25. package/dist/components/Header.d.ts.map +1 -0
  26. package/dist/components/Header.js +44 -0
  27. package/dist/components/Header.js.map +1 -0
  28. package/dist/components/IssueList.d.ts +10 -0
  29. package/dist/components/IssueList.d.ts.map +1 -0
  30. package/dist/components/IssueList.js +119 -0
  31. package/dist/components/IssueList.js.map +1 -0
  32. package/dist/components/Onboarding.d.ts +26 -0
  33. package/dist/components/Onboarding.d.ts.map +1 -0
  34. package/dist/components/Onboarding.js +948 -0
  35. package/dist/components/Onboarding.js.map +1 -0
  36. package/dist/components/PaneView.d.ts +9 -0
  37. package/dist/components/PaneView.d.ts.map +1 -0
  38. package/dist/components/PaneView.js +74 -0
  39. package/dist/components/PaneView.js.map +1 -0
  40. package/dist/components/StartupRecoveryView.d.ts +13 -0
  41. package/dist/components/StartupRecoveryView.d.ts.map +1 -0
  42. package/dist/components/StartupRecoveryView.js +85 -0
  43. package/dist/components/StartupRecoveryView.js.map +1 -0
  44. package/dist/components/StatusBar.d.ts +9 -0
  45. package/dist/components/StatusBar.d.ts.map +1 -0
  46. package/dist/components/StatusBar.js +70 -0
  47. package/dist/components/StatusBar.js.map +1 -0
  48. package/dist/components/TableView.d.ts +8 -0
  49. package/dist/components/TableView.d.ts.map +1 -0
  50. package/dist/components/TableView.js +87 -0
  51. package/dist/components/TableView.js.map +1 -0
  52. package/dist/config/index.d.ts +18 -0
  53. package/dist/config/index.d.ts.map +1 -0
  54. package/dist/config/index.js +357 -0
  55. package/dist/config/index.js.map +1 -0
  56. package/dist/git/merge.d.ts +23 -0
  57. package/dist/git/merge.d.ts.map +1 -0
  58. package/dist/git/merge.js +131 -0
  59. package/dist/git/merge.js.map +1 -0
  60. package/dist/git/utils.d.ts +34 -0
  61. package/dist/git/utils.d.ts.map +1 -0
  62. package/dist/git/utils.js +214 -0
  63. package/dist/git/utils.js.map +1 -0
  64. package/dist/git/worktree.d.ts +23 -0
  65. package/dist/git/worktree.d.ts.map +1 -0
  66. package/dist/git/worktree.js +116 -0
  67. package/dist/git/worktree.js.map +1 -0
  68. package/dist/index.d.ts +3 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +225 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/paths.d.ts +21 -0
  73. package/dist/paths.d.ts.map +1 -0
  74. package/dist/paths.js +59 -0
  75. package/dist/paths.js.map +1 -0
  76. package/dist/runModes.d.ts +7 -0
  77. package/dist/runModes.d.ts.map +1 -0
  78. package/dist/runModes.js +36 -0
  79. package/dist/runModes.js.map +1 -0
  80. package/dist/services/daemon.d.ts +85 -0
  81. package/dist/services/daemon.d.ts.map +1 -0
  82. package/dist/services/daemon.js +836 -0
  83. package/dist/services/daemon.js.map +1 -0
  84. package/dist/services/github.d.ts +101 -0
  85. package/dist/services/github.d.ts.map +1 -0
  86. package/dist/services/github.js +367 -0
  87. package/dist/services/github.js.map +1 -0
  88. package/dist/services/githubProgressReporter.d.ts +33 -0
  89. package/dist/services/githubProgressReporter.d.ts.map +1 -0
  90. package/dist/services/githubProgressReporter.js +272 -0
  91. package/dist/services/githubProgressReporter.js.map +1 -0
  92. package/dist/services/runtime.d.ts +43 -0
  93. package/dist/services/runtime.d.ts.map +1 -0
  94. package/dist/services/runtime.js +126 -0
  95. package/dist/services/runtime.js.map +1 -0
  96. package/dist/services/state.d.ts +43 -0
  97. package/dist/services/state.d.ts.map +1 -0
  98. package/dist/services/state.js +176 -0
  99. package/dist/services/state.js.map +1 -0
  100. package/dist/services/tmux.d.ts +50 -0
  101. package/dist/services/tmux.d.ts.map +1 -0
  102. package/dist/services/tmux.js +157 -0
  103. package/dist/services/tmux.js.map +1 -0
  104. package/dist/swarm/backlog.d.ts +25 -0
  105. package/dist/swarm/backlog.d.ts.map +1 -0
  106. package/dist/swarm/backlog.js +83 -0
  107. package/dist/swarm/backlog.js.map +1 -0
  108. package/dist/swarm/config.d.ts +14 -0
  109. package/dist/swarm/config.d.ts.map +1 -0
  110. package/dist/swarm/config.js +112 -0
  111. package/dist/swarm/config.js.map +1 -0
  112. package/dist/swarm/dependencies.d.ts +36 -0
  113. package/dist/swarm/dependencies.d.ts.map +1 -0
  114. package/dist/swarm/dependencies.js +141 -0
  115. package/dist/swarm/dependencies.js.map +1 -0
  116. package/dist/swarm/director.d.ts +67 -0
  117. package/dist/swarm/director.d.ts.map +1 -0
  118. package/dist/swarm/director.js +358 -0
  119. package/dist/swarm/director.js.map +1 -0
  120. package/dist/swarm/directorPrompt.d.ts +15 -0
  121. package/dist/swarm/directorPrompt.d.ts.map +1 -0
  122. package/dist/swarm/directorPrompt.js +60 -0
  123. package/dist/swarm/directorPrompt.js.map +1 -0
  124. package/dist/swarm/index.d.ts +7 -0
  125. package/dist/swarm/index.d.ts.map +1 -0
  126. package/dist/swarm/index.js +6 -0
  127. package/dist/swarm/index.js.map +1 -0
  128. package/dist/swarm/proposals.d.ts +29 -0
  129. package/dist/swarm/proposals.d.ts.map +1 -0
  130. package/dist/swarm/proposals.js +141 -0
  131. package/dist/swarm/proposals.js.map +1 -0
  132. package/dist/swarm/types.d.ts +65 -0
  133. package/dist/swarm/types.d.ts.map +1 -0
  134. package/dist/swarm/types.js +3 -0
  135. package/dist/swarm/types.js.map +1 -0
  136. package/dist/theme.d.ts +64 -0
  137. package/dist/theme.d.ts.map +1 -0
  138. package/dist/theme.js +161 -0
  139. package/dist/theme.js.map +1 -0
  140. package/dist/triggers/index.d.ts +17 -0
  141. package/dist/triggers/index.d.ts.map +1 -0
  142. package/dist/triggers/index.js +124 -0
  143. package/dist/triggers/index.js.map +1 -0
  144. package/dist/types.d.ts +327 -0
  145. package/dist/types.d.ts.map +1 -0
  146. package/dist/types.js +6 -0
  147. package/dist/types.js.map +1 -0
  148. package/dist/utils/duplicateDetection.d.ts +14 -0
  149. package/dist/utils/duplicateDetection.d.ts.map +1 -0
  150. package/dist/utils/duplicateDetection.js +45 -0
  151. package/dist/utils/duplicateDetection.js.map +1 -0
  152. package/dist/utils/shell.d.ts +46 -0
  153. package/dist/utils/shell.d.ts.map +1 -0
  154. package/dist/utils/shell.js +79 -0
  155. package/dist/utils/shell.js.map +1 -0
  156. package/dist/utils/slug.d.ts +13 -0
  157. package/dist/utils/slug.d.ts.map +1 -0
  158. package/dist/utils/slug.js +32 -0
  159. package/dist/utils/slug.js.map +1 -0
  160. package/dist/version.d.ts +28 -0
  161. package/dist/version.d.ts.map +1 -0
  162. package/dist/version.js +105 -0
  163. package/dist/version.js.map +1 -0
  164. package/examples/run-claude.example.sh +11 -0
  165. package/examples/run-codex.example.sh +11 -0
  166. package/package.json +68 -0
@@ -0,0 +1,948 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { Box, Text, useApp, useInput, useStdout } from 'ink';
3
+ import BigText from 'ink-big-text';
4
+ import SelectInput from 'ink-select-input';
5
+ import Spinner from 'ink-spinner';
6
+ import { runSafe } from '../utils/shell.js';
7
+ import { colors, gradient, progressBar, GradientText } from '../theme.js';
8
+ import { buildOnboardingConfigYaml } from '../config/index.js';
9
+ const RUN_MODES = [
10
+ { value: 'auto', label: 'Auto', desc: 'fully autonomous: run, push, resolve conflicts, and merge PRs' },
11
+ { value: 'auto_manual_conflicts', label: 'Auto (manual conflicts)', desc: 'run, push, open PRs, and merge automatically; stop for merge conflicts' },
12
+ { value: 'auto_manual_merge', label: 'Auto (manual merge)', desc: 'run, push, and open PRs automatically; stop before the final merge' },
13
+ ];
14
+ const TRIGGER_MODES = [
15
+ { value: 'mention_or_label', label: 'Mention or label', desc: 'agent mention or "agent" label' },
16
+ { value: 'mention_only', label: 'Mention only', desc: 'only configured agent mentions' },
17
+ { value: 'label_only', label: 'Label only', desc: 'only "agent" label on the issue' },
18
+ { value: 'mention_and_label', label: 'Mention and label', desc: 'require both an agent mention and label' },
19
+ { value: 'yolo', label: 'Every issue', desc: 'run on all open issues' },
20
+ { value: 'disabled', label: 'Disabled', desc: 'pause automatic issue triggering' },
21
+ ];
22
+ const AUTOMATION_LEVELS = [
23
+ { value: 'bypassPermissions', label: 'Hands-off', desc: 'let the agent act autonomously' },
24
+ { value: 'acceptEdits', label: 'Review commands', desc: 'auto-accept edits, ask before risky commands' },
25
+ { value: 'plan', label: 'Plan only', desc: 'inspect and propose changes only' },
26
+ { value: '', label: 'Use agent defaults', desc: 'each agent decides its own permission level' },
27
+ ];
28
+ const STARTUP_SCAN_OPTIONS = [
29
+ { value: 'disabled', label: 'No existing-issue scan on start', desc: 'default; only react to new updates after startup' },
30
+ { value: 'enabled', label: 'Scan existing open issues on start', desc: 'check selected repos for already-open matching issues and start them on launch' },
31
+ ];
32
+ const GITHUB_COMMENT_OPTIONS = [
33
+ { value: 'key_updates', label: 'Just key updates', desc: 'post milestones like start, review, merge, and final result' },
34
+ { value: 'without_thinking', label: 'Without thinking traces', desc: 'post progress and final status without raw terminal reasoning output' },
35
+ { value: 'full', label: 'Full verbosity', desc: 'mirror live progress with terminal excerpts, including thinking traces' },
36
+ { value: 'silent', label: 'Silent', desc: 'do not post Symphony progress comments back to GitHub' },
37
+ ];
38
+ const SCOPE_OPTIONS = [
39
+ { value: 'all', label: 'Watch all repositories', desc: 'include every repository for this owner' },
40
+ { value: 'manual', label: 'Choose repositories manually', desc: 'pick only specific repositories' },
41
+ ];
42
+ const DEFAULT_POLL_INTERVAL_SEC = 2;
43
+ function targetLabel(target) {
44
+ return target.kind === 'user' ? `${target.login} (your account)` : target.login;
45
+ }
46
+ function buildDefaultMentionItems(defaultAgent, installedAgents) {
47
+ const items = [{ mention: '@agent', agent: defaultAgent }];
48
+ const seen = new Set(['@agent']);
49
+ for (const agent of installedAgents) {
50
+ const mention = `@${agent}`;
51
+ if (seen.has(mention))
52
+ continue;
53
+ seen.add(mention);
54
+ items.push({ mention, agent });
55
+ }
56
+ return items;
57
+ }
58
+ function unique(values) {
59
+ return Array.from(new Set(values));
60
+ }
61
+ function inferAutomationMode(settings) {
62
+ if (!settings)
63
+ return '';
64
+ const modes = unique(Object.values(settings.agents)
65
+ .map(agent => agent.permission_mode ?? '')
66
+ .filter((mode) => mode === '' || mode === 'plan' || mode === 'acceptEdits' || mode === 'bypassPermissions'));
67
+ return modes.length === 1 ? modes[0] : '';
68
+ }
69
+ function buildMentionItemsFromSettings(settings) {
70
+ if (!settings)
71
+ return [];
72
+ const items = [];
73
+ const seen = new Set();
74
+ const orderedMentions = [
75
+ '@agent',
76
+ ...Object.keys(settings.routing.mention_map).filter(mention => mention !== '@agent'),
77
+ ...settings.trigger.mentions,
78
+ ];
79
+ for (const mention of orderedMentions) {
80
+ const normalized = normalizeMention(mention);
81
+ if (!normalized || seen.has(normalized))
82
+ continue;
83
+ seen.add(normalized);
84
+ items.push({
85
+ mention: normalized,
86
+ agent: settings.routing.mention_map[normalized] || settings.default_agent,
87
+ });
88
+ }
89
+ return items;
90
+ }
91
+ function normalizeMention(raw) {
92
+ const trimmed = raw.trim();
93
+ if (!trimmed)
94
+ return '';
95
+ return trimmed.startsWith('@') ? trimmed : `@${trimmed}`;
96
+ }
97
+ function flattenSelectedRepos(targets) {
98
+ return targets
99
+ .filter(target => target.selected)
100
+ .flatMap(target => target.repos.filter(repo => repo.selected).map(repo => repo.name));
101
+ }
102
+ function uniqueMentionItems(items) {
103
+ const seen = new Set();
104
+ const unique = [];
105
+ for (const item of items) {
106
+ const mention = normalizeMention(item.mention);
107
+ if (!mention || seen.has(mention))
108
+ continue;
109
+ seen.add(mention);
110
+ unique.push({ mention, agent: item.agent });
111
+ }
112
+ return unique;
113
+ }
114
+ // ── Custom SelectInput components ──────────────────────────────────
115
+ const SelectIndicator = ({ isSelected }) => (React.createElement(Text, { color: colors.primaryLight }, isSelected ? ' ▸ ' : ' '));
116
+ const SelectItem = ({ isSelected, label }) => (React.createElement(Text, { color: isSelected ? colors.primaryLight : colors.text, bold: isSelected }, label));
117
+ export const Onboarding = ({ configPath, flow = 'setup', initialSettings, baseConfig, onComplete, onExit, }) => {
118
+ const { exit } = useApp();
119
+ const { stdout } = useStdout();
120
+ const termWidth = stdout?.columns || 80;
121
+ const configuredAgentNames = useMemo(() => unique([
122
+ ...Object.keys(initialSettings?.agents || {}),
123
+ initialSettings?.default_agent || '',
124
+ ...Object.values(initialSettings?.routing.mention_map || {}),
125
+ ].filter(Boolean)), [initialSettings]);
126
+ const initialMentionItems = useMemo(() => buildMentionItemsFromSettings(initialSettings), [initialSettings]);
127
+ const initialReposByOwner = useMemo(() => {
128
+ const reposByOwner = new Map();
129
+ for (const repoSetting of initialSettings?.repos || []) {
130
+ const [owner] = repoSetting.repo.split('/', 1);
131
+ if (!owner)
132
+ continue;
133
+ if (!reposByOwner.has(owner))
134
+ reposByOwner.set(owner, new Set());
135
+ reposByOwner.get(owner).add(repoSetting.repo);
136
+ }
137
+ return reposByOwner;
138
+ }, [initialSettings]);
139
+ const initialSelectedOwners = useMemo(() => new Set(initialReposByOwner.keys()), [initialReposByOwner]);
140
+ const initialRunModeIdx = useMemo(() => {
141
+ const idx = RUN_MODES.findIndex(mode => mode.value === initialSettings?.mode);
142
+ return idx >= 0 ? idx : 2;
143
+ }, [initialSettings]);
144
+ const initialTriggerModeIdx = useMemo(() => {
145
+ const idx = TRIGGER_MODES.findIndex(mode => mode.value === initialSettings?.trigger.mode);
146
+ return idx >= 0 ? idx : 0;
147
+ }, [initialSettings]);
148
+ const initialAutomationIdx = useMemo(() => {
149
+ const mode = inferAutomationMode(initialSettings);
150
+ const idx = AUTOMATION_LEVELS.findIndex(option => option.value === mode);
151
+ return idx >= 0 ? idx : AUTOMATION_LEVELS.length - 1;
152
+ }, [initialSettings]);
153
+ const initialGitHubCommentIdx = useMemo(() => {
154
+ const idx = GITHUB_COMMENT_OPTIONS.findIndex(option => option.value === initialSettings?.github_comments.verbosity);
155
+ return idx >= 0 ? idx : 0;
156
+ }, [initialSettings]);
157
+ const [step, setStep] = useState('env');
158
+ const [envChecks, setEnvChecks] = useState([]);
159
+ const [envDone, setEnvDone] = useState(false);
160
+ const [scopeTargets, setScopeTargets] = useState([]);
161
+ const [scopeIdx, setScopeIdx] = useState(0);
162
+ const [currentTargetIdx, setCurrentTargetIdx] = useState(0);
163
+ const [repoIdx, setRepoIdx] = useState(0);
164
+ const [repoSearch, setRepoSearch] = useState('');
165
+ const [repoSearchActive, setRepoSearchActive] = useState(false);
166
+ const [installedAgents, setInstalledAgents] = useState([]);
167
+ const [defaultAgent, setDefaultAgent] = useState(initialSettings?.default_agent || 'claude');
168
+ const [mentionItems, setMentionItems] = useState(initialMentionItems);
169
+ const [mentionIdx, setMentionIdx] = useState(0);
170
+ const [mentionEditActive, setMentionEditActive] = useState(false);
171
+ const [mentionInput, setMentionInput] = useState('');
172
+ const [runModeIdx, setRunModeIdx] = useState(initialRunModeIdx);
173
+ const [triggerModeIdx, setTriggerModeIdx] = useState(initialTriggerModeIdx);
174
+ const [startupScanIdx, setStartupScanIdx] = useState(initialSettings?.trigger.include_existing_open_issues ? 1 : 0);
175
+ const [automationIdx, setAutomationIdx] = useState(initialAutomationIdx);
176
+ const [githubCommentIdx, setGitHubCommentIdx] = useState(initialGitHubCommentIdx);
177
+ const [doneIdx, setDoneIdx] = useState(0);
178
+ const [loading, setLoading] = useState('');
179
+ const currentTarget = scopeTargets[currentTargetIdx] || null;
180
+ const currentRepos = currentTarget?.repos || [];
181
+ const filteredRepos = repoSearch
182
+ ? currentRepos.filter(repo => repo.name.toLowerCase().includes(repoSearch.toLowerCase()))
183
+ : currentRepos;
184
+ const selectedRepos = flattenSelectedRepos(scopeTargets);
185
+ const draftRepos = selectedRepos.length > 0
186
+ ? selectedRepos
187
+ : (flow === 'reconfigure' && scopeTargets.length === 0
188
+ ? (initialSettings?.repos || []).map(repo => repo.repo)
189
+ : []);
190
+ const uniqueMentions = uniqueMentionItems(mentionItems.length > 0 ? mentionItems : buildDefaultMentionItems(defaultAgent, installedAgents));
191
+ const selectedTargetIndices = scopeTargets.reduce((acc, target, idx) => {
192
+ if (target.selected)
193
+ acc.push(idx);
194
+ return acc;
195
+ }, []);
196
+ const loadGitHubScopes = useCallback(async () => {
197
+ setLoading('Loading GitHub accounts and organizations...');
198
+ const [userResult, orgResult] = await Promise.all([
199
+ runSafe('gh', ['api', 'user', '--jq', '.login']),
200
+ runSafe('gh', ['api', 'user/orgs', '--jq', '.[].login']),
201
+ ]);
202
+ setLoading('');
203
+ if (userResult.exitCode !== 0 || !userResult.stdout.trim()) {
204
+ setLoading('Unable to load GitHub scopes. Continuing without repositories.');
205
+ setTimeout(() => {
206
+ setLoading('');
207
+ setStep('agent-default');
208
+ }, 1500);
209
+ return;
210
+ }
211
+ const login = userResult.stdout.trim();
212
+ const orgLogins = orgResult.exitCode === 0
213
+ ? orgResult.stdout.split('\n').map(line => line.trim()).filter(Boolean)
214
+ : [];
215
+ const targets = [
216
+ {
217
+ login,
218
+ kind: 'user',
219
+ selected: initialSelectedOwners.size > 0 ? initialSelectedOwners.has(login) : true,
220
+ watchAll: initialSelectedOwners.has(login) ? false : true,
221
+ reposLoaded: false,
222
+ repos: [],
223
+ },
224
+ ...orgLogins
225
+ .filter(org => org !== login)
226
+ .map(org => ({
227
+ login: org,
228
+ kind: 'org',
229
+ selected: initialSelectedOwners.has(org),
230
+ watchAll: initialSelectedOwners.has(org) ? false : true,
231
+ reposLoaded: false,
232
+ repos: [],
233
+ })),
234
+ ];
235
+ setScopeTargets(targets);
236
+ setScopeIdx(0);
237
+ if (targets.length === 1 && targets[0].kind === 'user') {
238
+ setCurrentTargetIdx(0);
239
+ setStep('scope-mode');
240
+ return;
241
+ }
242
+ setStep('scope-select');
243
+ }, [initialSelectedOwners]);
244
+ const fetchReposForTarget = useCallback(async (targetIdx, selectAll, preselectedRepos) => {
245
+ const target = scopeTargets[targetIdx];
246
+ if (!target)
247
+ return false;
248
+ setLoading(`Fetching repositories for ${target.login}...`);
249
+ const result = await runSafe('gh', [
250
+ 'repo', 'list', target.login,
251
+ '--limit', '1000',
252
+ '--json', 'nameWithOwner',
253
+ '-q', '.[].nameWithOwner',
254
+ ]);
255
+ setLoading('');
256
+ if (result.exitCode !== 0) {
257
+ setLoading(`Unable to load repositories for ${target.login}.`);
258
+ setTimeout(() => setLoading(''), 1500);
259
+ return false;
260
+ }
261
+ const existingSelections = new Set(target.repos.filter(repo => repo.selected).map(repo => repo.name));
262
+ for (const repo of preselectedRepos || []) {
263
+ existingSelections.add(repo);
264
+ }
265
+ const repos = result.stdout
266
+ .split('\n')
267
+ .map(line => line.trim())
268
+ .filter(Boolean)
269
+ .map(name => ({
270
+ name,
271
+ selected: selectAll || existingSelections.has(name),
272
+ }));
273
+ setScopeTargets(prev => prev.map((item, idx) => (idx === targetIdx
274
+ ? { ...item, watchAll: selectAll, reposLoaded: true, repos }
275
+ : item)));
276
+ setRepoIdx(0);
277
+ setRepoSearch('');
278
+ setRepoSearchActive(false);
279
+ return true;
280
+ }, [scopeTargets]);
281
+ const goToTarget = useCallback((targetIdx) => {
282
+ const target = scopeTargets[targetIdx];
283
+ if (!target)
284
+ return;
285
+ setCurrentTargetIdx(targetIdx);
286
+ setStep('scope-mode');
287
+ }, [scopeTargets]);
288
+ const goToNextTarget = useCallback((fromTargetIdx) => {
289
+ const nextIdx = selectedTargetIndices.find(idx => idx > fromTargetIdx);
290
+ if (nextIdx === undefined) {
291
+ setStep('agent-default');
292
+ return;
293
+ }
294
+ goToTarget(nextIdx);
295
+ }, [goToTarget, selectedTargetIndices]);
296
+ const includeAllReposForSelectedTargets = useCallback(async () => {
297
+ const selectedIndices = scopeTargets.reduce((acc, target, idx) => {
298
+ if (target.selected)
299
+ acc.push(idx);
300
+ return acc;
301
+ }, []);
302
+ if (selectedIndices.length === 0) {
303
+ setLoading('Select at least one account or organization.');
304
+ setTimeout(() => setLoading(''), 1500);
305
+ return;
306
+ }
307
+ for (const idx of selectedIndices) {
308
+ const target = scopeTargets[idx];
309
+ if (!target)
310
+ continue;
311
+ const alreadyLoadedAll = target.reposLoaded
312
+ && target.watchAll
313
+ && target.repos.length > 0
314
+ && target.repos.every(repo => repo.selected);
315
+ if (!alreadyLoadedAll) {
316
+ const ok = await fetchReposForTarget(idx, true);
317
+ if (!ok)
318
+ return;
319
+ }
320
+ }
321
+ setStep('agent-default');
322
+ }, [fetchReposForTarget, scopeTargets]);
323
+ useEffect(() => {
324
+ (async () => {
325
+ const checks = [];
326
+ const git = await runSafe('git', ['--version']);
327
+ checks.push({ name: 'git', ok: git.exitCode === 0, detail: git.exitCode === 0 ? git.stdout.split(' ')[2] : undefined });
328
+ const gh = await runSafe('gh', ['auth', 'status']);
329
+ checks.push({ name: 'gh', ok: gh.exitCode === 0, detail: gh.exitCode === 0 ? 'authenticated' : 'not authenticated' });
330
+ const tmux = await runSafe('tmux', ['-V']);
331
+ checks.push({ name: 'tmux', ok: tmux.exitCode === 0, detail: tmux.exitCode === 0 ? tmux.stdout : 'not installed' });
332
+ const agentBins = [
333
+ { name: 'claude', cmd: 'command -v claude' },
334
+ { name: 'codex', cmd: 'command -v codex' },
335
+ { name: 'opencode', cmd: 'command -v opencode' },
336
+ { name: 'gemini', cmd: 'command -v gemini' },
337
+ { name: 'cline', cmd: 'command -v cline' },
338
+ { name: 'copilot', cmd: 'command -v copilot' },
339
+ ];
340
+ const found = [];
341
+ for (const agent of agentBins) {
342
+ const result = await runSafe('sh', ['-c', agent.cmd]);
343
+ const ok = result.exitCode === 0;
344
+ checks.push({ name: agent.name, ok, detail: ok ? undefined : 'not found' });
345
+ if (ok)
346
+ found.push(agent.name);
347
+ }
348
+ const resolvedAgents = unique([
349
+ ...configuredAgentNames,
350
+ ...(found.length > 0 ? found : ['claude']),
351
+ ]);
352
+ const initialAgent = initialSettings?.default_agent && resolvedAgents.includes(initialSettings.default_agent)
353
+ ? initialSettings.default_agent
354
+ : (resolvedAgents.includes('claude') ? 'claude' : resolvedAgents[0]);
355
+ setInstalledAgents(resolvedAgents);
356
+ setDefaultAgent(initialAgent);
357
+ setMentionItems(initialMentionItems.length > 0
358
+ ? initialMentionItems
359
+ : buildDefaultMentionItems(initialAgent, resolvedAgents));
360
+ setEnvChecks(checks);
361
+ setEnvDone(true);
362
+ })();
363
+ }, [configuredAgentNames, initialMentionItems, initialSettings?.default_agent]);
364
+ const buildDraft = useCallback(() => ({
365
+ defaultAgent,
366
+ configuredAgents: unique([
367
+ ...configuredAgentNames,
368
+ ...(flow === 'setup' && !initialSettings ? installedAgents : []),
369
+ defaultAgent,
370
+ ...uniqueMentions.map(item => item.agent),
371
+ ]),
372
+ repos: draftRepos,
373
+ mentionItems: uniqueMentions,
374
+ runMode: RUN_MODES[runModeIdx].value,
375
+ triggerMode: TRIGGER_MODES[triggerModeIdx].value,
376
+ includeExistingOpenIssues: STARTUP_SCAN_OPTIONS[startupScanIdx].value === 'enabled',
377
+ automationMode: AUTOMATION_LEVELS[automationIdx].value,
378
+ githubCommentVerbosity: GITHUB_COMMENT_OPTIONS[githubCommentIdx].value,
379
+ }), [
380
+ automationIdx,
381
+ configuredAgentNames,
382
+ defaultAgent,
383
+ flow,
384
+ githubCommentIdx,
385
+ initialSettings,
386
+ installedAgents,
387
+ runModeIdx,
388
+ draftRepos,
389
+ startupScanIdx,
390
+ triggerModeIdx,
391
+ uniqueMentions,
392
+ ]);
393
+ const buildConfigYaml = useCallback(() => {
394
+ const draft = buildDraft();
395
+ return buildOnboardingConfigYaml(baseConfig, draft);
396
+ }, [baseConfig, buildDraft]);
397
+ // ── Keyboard: only handle Esc (back), q (quit), and custom steps ──
398
+ useInput(useCallback((input, key) => {
399
+ // Mention editing mode
400
+ if (mentionEditActive && step === 'agent-mentions') {
401
+ if (key.escape) {
402
+ setMentionEditActive(false);
403
+ setMentionInput('');
404
+ return;
405
+ }
406
+ if (key.return) {
407
+ const normalized = normalizeMention(mentionInput);
408
+ if (normalized) {
409
+ setMentionItems(prev => prev.map((item, idx) => idx === mentionIdx ? { ...item, mention: normalized } : item));
410
+ }
411
+ setMentionEditActive(false);
412
+ setMentionInput('');
413
+ return;
414
+ }
415
+ if (key.backspace || key.delete) {
416
+ setMentionInput(prev => prev.slice(0, -1));
417
+ return;
418
+ }
419
+ if (input && !key.ctrl && !key.meta) {
420
+ setMentionInput(prev => prev + input);
421
+ return;
422
+ }
423
+ return;
424
+ }
425
+ // Repo search mode
426
+ if (repoSearchActive && step === 'repo-select') {
427
+ if (key.escape) {
428
+ setRepoSearchActive(false);
429
+ setRepoSearch('');
430
+ return;
431
+ }
432
+ if (key.return) {
433
+ setRepoSearchActive(false);
434
+ return;
435
+ }
436
+ if (key.backspace || key.delete) {
437
+ setRepoSearch(prev => prev.slice(0, -1));
438
+ return;
439
+ }
440
+ if (input && !key.ctrl && !key.meta) {
441
+ setRepoSearch(prev => prev + input);
442
+ setRepoIdx(0);
443
+ return;
444
+ }
445
+ return;
446
+ }
447
+ if (input === 'q' && step !== 'done') {
448
+ onExit();
449
+ if (flow !== 'reconfigure')
450
+ exit();
451
+ return;
452
+ }
453
+ // Esc navigation (back)
454
+ if (key.escape) {
455
+ switch (step) {
456
+ case 'scope-select':
457
+ setStep('env');
458
+ break;
459
+ case 'scope-mode':
460
+ if (scopeTargets.length > 1)
461
+ setStep('scope-select');
462
+ else
463
+ setStep('env');
464
+ break;
465
+ case 'repo-select':
466
+ setStep('scope-mode');
467
+ break;
468
+ case 'agent-default':
469
+ if (selectedTargetIndices.length > 0)
470
+ goToTarget(selectedTargetIndices[selectedTargetIndices.length - 1]);
471
+ else if (scopeTargets.length > 0)
472
+ setStep('scope-select');
473
+ else
474
+ setStep('env');
475
+ break;
476
+ case 'agent-mentions':
477
+ setStep('agent-default');
478
+ break;
479
+ case 'run-mode':
480
+ setStep('agent-mentions');
481
+ break;
482
+ case 'trigger-mode':
483
+ setStep('run-mode');
484
+ break;
485
+ case 'startup-scan':
486
+ setStep('trigger-mode');
487
+ break;
488
+ case 'automation':
489
+ setStep('startup-scan');
490
+ break;
491
+ case 'github-comments':
492
+ setStep('automation');
493
+ break;
494
+ case 'done':
495
+ setStep('github-comments');
496
+ break;
497
+ }
498
+ return;
499
+ }
500
+ // Step-specific keyboard (only for steps not handled by SelectInput/MultiSelect)
501
+ switch (step) {
502
+ case 'env':
503
+ if (key.return && envDone)
504
+ void loadGitHubScopes();
505
+ break;
506
+ case 'scope-select':
507
+ if (key.upArrow)
508
+ setScopeIdx(prev => Math.max(0, prev - 1));
509
+ if (key.downArrow)
510
+ setScopeIdx(prev => Math.min(scopeTargets.length - 1, prev + 1));
511
+ if (input === ' ') {
512
+ const target = scopeTargets[scopeIdx];
513
+ if (!target)
514
+ break;
515
+ setScopeTargets(prev => prev.map((item, idx) => (idx === scopeIdx ? { ...item, selected: !item.selected } : item)));
516
+ }
517
+ if (key.return) {
518
+ void includeAllReposForSelectedTargets();
519
+ }
520
+ if (input === 'm') {
521
+ if (selectedTargetIndices.length === 0) {
522
+ setLoading('Select at least one account or organization.');
523
+ setTimeout(() => setLoading(''), 1500);
524
+ break;
525
+ }
526
+ const selectedLogins = new Set(scopeTargets
527
+ .filter(target => target.selected)
528
+ .map(target => target.login));
529
+ const firstSelectedIdx = scopeTargets.findIndex(target => selectedLogins.has(target.login));
530
+ if (firstSelectedIdx === -1) {
531
+ setStep('agent-default');
532
+ }
533
+ else {
534
+ goToTarget(firstSelectedIdx);
535
+ }
536
+ }
537
+ break;
538
+ case 'repo-select':
539
+ if (key.upArrow)
540
+ setRepoIdx(prev => Math.max(0, prev - 1));
541
+ if (key.downArrow)
542
+ setRepoIdx(prev => Math.min(filteredRepos.length - 1, prev + 1));
543
+ if (input === ' ') {
544
+ const repo = filteredRepos[repoIdx];
545
+ if (!repo)
546
+ break;
547
+ setScopeTargets(prev => prev.map((target, idx) => (idx !== currentTargetIdx ? target : {
548
+ ...target,
549
+ repos: target.repos.map(item => item.name === repo.name ? { ...item, selected: !item.selected } : item),
550
+ })));
551
+ }
552
+ if (input === 'a') {
553
+ const visibleNames = new Set(filteredRepos.map(repo => repo.name));
554
+ const allVisibleSelected = filteredRepos.length > 0 && filteredRepos.every(repo => repo.selected);
555
+ setScopeTargets(prev => prev.map((target, idx) => (idx !== currentTargetIdx ? target : {
556
+ ...target,
557
+ repos: target.repos.map(repo => visibleNames.has(repo.name) ? { ...repo, selected: !allVisibleSelected } : repo),
558
+ })));
559
+ }
560
+ if (input === '/') {
561
+ setRepoSearchActive(true);
562
+ setRepoSearch('');
563
+ }
564
+ if (key.return)
565
+ goToNextTarget(currentTargetIdx);
566
+ break;
567
+ case 'agent-mentions':
568
+ if (key.upArrow)
569
+ setMentionIdx(prev => Math.max(0, prev - 1));
570
+ if (key.downArrow)
571
+ setMentionIdx(prev => Math.min(mentionItems.length - 1, prev + 1));
572
+ if (input === 'e' && mentionItems[mentionIdx]) {
573
+ setMentionEditActive(true);
574
+ setMentionInput(mentionItems[mentionIdx].mention);
575
+ }
576
+ if (input === 'r') {
577
+ setMentionItems(buildDefaultMentionItems(defaultAgent, installedAgents));
578
+ setMentionIdx(0);
579
+ }
580
+ if (key.return)
581
+ setStep('run-mode');
582
+ break;
583
+ }
584
+ }, [
585
+ currentTargetIdx, defaultAgent, envDone, exit, fetchReposForTarget,
586
+ filteredRepos, goToNextTarget, goToTarget, includeAllReposForSelectedTargets, installedAgents, loadGitHubScopes,
587
+ mentionEditActive, mentionIdx, mentionInput, mentionItems, onComplete, onExit,
588
+ repoIdx, repoSearchActive, scopeIdx, scopeTargets, selectedTargetIndices, step,
589
+ ]));
590
+ // ── SelectInput callbacks ────────────────────────────────────────
591
+ const handleScopeModeSelect = useCallback((item) => {
592
+ if (!currentTarget)
593
+ return;
594
+ if (item.value === 'all') {
595
+ void (async () => {
596
+ const ok = await fetchReposForTarget(currentTargetIdx, true);
597
+ if (ok)
598
+ goToNextTarget(currentTargetIdx);
599
+ })();
600
+ }
601
+ else {
602
+ void (async () => {
603
+ const ok = currentTarget.reposLoaded || await fetchReposForTarget(currentTargetIdx, false, initialReposByOwner.get(currentTarget.login));
604
+ if (ok) {
605
+ setScopeTargets(prev => prev.map((target, idx) => (idx === currentTargetIdx ? { ...target, watchAll: false } : target)));
606
+ setRepoIdx(0);
607
+ setRepoSearch('');
608
+ setRepoSearchActive(false);
609
+ setStep('repo-select');
610
+ }
611
+ })();
612
+ }
613
+ }, [currentTarget, currentTargetIdx, fetchReposForTarget, goToNextTarget, initialReposByOwner]);
614
+ const handleAgentSelect = useCallback((item) => {
615
+ setDefaultAgent(item.value);
616
+ setMentionItems(buildDefaultMentionItems(item.value, installedAgents));
617
+ setMentionIdx(0);
618
+ setStep('agent-mentions');
619
+ }, [installedAgents]);
620
+ const handleRunModeSelect = useCallback((item) => {
621
+ const idx = RUN_MODES.findIndex(m => m.value === item.value);
622
+ if (idx >= 0)
623
+ setRunModeIdx(idx);
624
+ setStep('trigger-mode');
625
+ }, []);
626
+ const handleTriggerSelect = useCallback((item) => {
627
+ const idx = TRIGGER_MODES.findIndex(m => m.value === item.value);
628
+ if (idx >= 0)
629
+ setTriggerModeIdx(idx);
630
+ setStep('startup-scan');
631
+ }, []);
632
+ const handleStartupScanSelect = useCallback((item) => {
633
+ const idx = STARTUP_SCAN_OPTIONS.findIndex(m => m.value === item.value);
634
+ if (idx >= 0)
635
+ setStartupScanIdx(idx);
636
+ setStep('automation');
637
+ }, []);
638
+ const handleAutomationSelect = useCallback((item) => {
639
+ const idx = AUTOMATION_LEVELS.findIndex(m => m.value === item.value);
640
+ if (idx >= 0)
641
+ setAutomationIdx(idx);
642
+ setStep('github-comments');
643
+ }, []);
644
+ const handleGitHubCommentSelect = useCallback((item) => {
645
+ const idx = GITHUB_COMMENT_OPTIONS.findIndex(m => m.value === item.value);
646
+ if (idx >= 0)
647
+ setGitHubCommentIdx(idx);
648
+ setStep('done');
649
+ }, []);
650
+ const handleDoneSelect = useCallback((item) => {
651
+ if (item.value === 'discard') {
652
+ onExit();
653
+ if (flow !== 'reconfigure')
654
+ exit();
655
+ return;
656
+ }
657
+ const draft = buildDraft();
658
+ if (draft.repos.length === 0) {
659
+ setLoading('Select at least one repository before saving.');
660
+ setTimeout(() => setLoading(''), 2000);
661
+ return;
662
+ }
663
+ const yaml = buildConfigYaml();
664
+ const action = item.value === 'start'
665
+ ? 'start'
666
+ : item.value === 'save'
667
+ ? 'save'
668
+ : 'exit';
669
+ onComplete({ action, yaml, draft });
670
+ if (flow !== 'reconfigure')
671
+ exit();
672
+ }, [buildConfigYaml, buildDraft, exit, flow, onComplete, onExit]);
673
+ // ── Step tracking ────────────────────────────────────────────────
674
+ const allSteps = ['env', 'scope-select', 'agent-default', 'run-mode', 'trigger-mode', 'startup-scan', 'automation', 'github-comments', 'done'];
675
+ const stepLabels = ['Environment', 'Repos', 'Agent', 'Mode', 'Trigger', 'Startup', 'Automation', 'GitHub', 'Confirm'];
676
+ const currentMainStep = allSteps.indexOf(['scope-mode', 'repo-select'].includes(step) ? 'scope-select'
677
+ : step === 'agent-mentions' ? 'agent-default'
678
+ : step);
679
+ const selectedScopes = scopeTargets.filter(target => target.selected);
680
+ const useBigText = termWidth >= 80;
681
+ return (React.createElement(Box, { flexDirection: "column", paddingX: 1 },
682
+ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, useBigText ? (React.createElement(BigText, { text: "Symphony", font: "chrome", colors: [gradient.blue6, gradient.blue3] })) : (React.createElement(Box, null, GradientText({ text: 'Symphony GitHub', from: gradient.blue6, to: gradient.blue2, bold: true })))),
683
+ React.createElement(Box, { paddingX: 2, marginBottom: 0 },
684
+ React.createElement(Text, { color: colors.primary }, progressBar(currentMainStep, allSteps.length - 1, 30)),
685
+ React.createElement(Text, { color: colors.muted },
686
+ " ",
687
+ Math.round((currentMainStep / (allSteps.length - 1)) * 100),
688
+ "%")),
689
+ React.createElement(Box, { paddingX: 2, marginBottom: 1 }, stepLabels.map((label, idx) => (React.createElement(React.Fragment, { key: label },
690
+ idx > 0 && React.createElement(Text, { color: colors.border },
691
+ " ",
692
+ '>',
693
+ " "),
694
+ React.createElement(Text, { color: idx === currentMainStep ? colors.primaryLight : idx < currentMainStep ? colors.success : colors.muted, bold: idx === currentMainStep },
695
+ idx < currentMainStep ? '\u2713 ' : '',
696
+ label))))),
697
+ loading && (React.createElement(Box, { paddingX: 2 },
698
+ React.createElement(Text, { color: colors.warning },
699
+ React.createElement(Spinner, { type: "dots" }),
700
+ " ",
701
+ loading))),
702
+ step === 'env' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
703
+ React.createElement(Text, { bold: true, color: colors.text }, "Environment"),
704
+ React.createElement(Box, { flexDirection: "column", marginTop: 1 },
705
+ envChecks.length === 0 && React.createElement(Text, { color: colors.muted },
706
+ React.createElement(Spinner, { type: "dots" }),
707
+ " Checking..."),
708
+ envChecks.map(check => (React.createElement(Text, { key: check.name },
709
+ React.createElement(Text, { color: check.ok ? colors.success : colors.muted }, check.ok ? ' \u2713 ' : ' \u25CB '),
710
+ React.createElement(Text, { color: check.ok ? colors.text : colors.muted }, check.name),
711
+ check.detail && React.createElement(Text, { color: colors.muted },
712
+ " ",
713
+ check.detail))))),
714
+ envDone && (React.createElement(Box, { marginTop: 1 },
715
+ React.createElement(Text, { color: colors.muted }, "Press "),
716
+ React.createElement(Text, { color: colors.key }, "Enter"),
717
+ React.createElement(Text, { color: colors.muted }, " to continue"))))),
718
+ step === 'scope-select' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
719
+ React.createElement(Text, { bold: true, color: colors.text }, "Select GitHub accounts and organizations"),
720
+ React.createElement(Text, { color: colors.muted }, "Space to toggle. Enter includes all repos by default; manual selection is optional."),
721
+ selectedScopes.length > 0 && (React.createElement(Box, { marginTop: 1 },
722
+ React.createElement(Text, { color: colors.success },
723
+ "Selected: ",
724
+ selectedScopes.length))),
725
+ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, scopeTargets.map((target, idx) => {
726
+ const isSelected = target.selected;
727
+ const isFocused = idx === scopeIdx;
728
+ return (React.createElement(Text, { key: target.login, color: isFocused ? colors.primaryLight : colors.text, bold: isFocused },
729
+ isFocused ? ' ▸ ' : ' ',
730
+ React.createElement(Text, { color: isSelected ? colors.success : colors.muted },
731
+ "[",
732
+ isSelected ? 'x' : ' ',
733
+ "]"),
734
+ ' ',
735
+ targetLabel(target)));
736
+ })),
737
+ React.createElement(Box, { marginTop: 1 },
738
+ React.createElement(Text, { color: colors.muted },
739
+ React.createElement(Text, { color: colors.key }, "\u2191\u2193"),
740
+ " move ",
741
+ ' ',
742
+ React.createElement(Text, { color: colors.key }, "Space"),
743
+ " toggle ",
744
+ React.createElement(Text, { color: colors.key }, "Enter"),
745
+ " all repos ",
746
+ React.createElement(Text, { color: colors.key }, "m"),
747
+ " manual ",
748
+ React.createElement(Text, { color: colors.key }, "Esc"),
749
+ " back")))),
750
+ step === 'scope-mode' && currentTarget && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
751
+ React.createElement(Text, { bold: true, color: colors.text },
752
+ "Repository scope for ",
753
+ targetLabel(currentTarget)),
754
+ React.createElement(Box, { marginTop: 1 },
755
+ React.createElement(SelectInput, { items: SCOPE_OPTIONS.map(o => ({ label: `${o.label} - ${o.desc}`, value: o.value })), initialIndex: currentTarget.watchAll ? 0 : 1, onSelect: handleScopeModeSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'scope-mode' })),
756
+ currentTarget.reposLoaded && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
757
+ React.createElement(Text, { color: colors.muted },
758
+ "Loaded: ",
759
+ currentTarget.repos.length,
760
+ " repos"),
761
+ React.createElement(Text, { color: colors.muted },
762
+ "Selected: ",
763
+ currentTarget.repos.filter(r => r.selected).length))),
764
+ React.createElement(Box, { marginTop: 1 },
765
+ React.createElement(Text, { color: colors.muted },
766
+ React.createElement(Text, { color: colors.key }, "Esc"),
767
+ " back")))),
768
+ step === 'repo-select' && currentTarget && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
769
+ React.createElement(Text, { bold: true, color: colors.text },
770
+ "Choose repositories for ",
771
+ targetLabel(currentTarget)),
772
+ repoSearchActive ? (React.createElement(Box, { marginTop: 1 },
773
+ React.createElement(Text, { color: colors.key }, "Search: "),
774
+ React.createElement(Text, { color: colors.text },
775
+ repoSearch,
776
+ React.createElement(Text, { color: colors.primaryLight }, "_")))) : repoSearch ? (React.createElement(Box, { marginTop: 1 },
777
+ React.createElement(Text, { color: colors.muted },
778
+ "Filter: ",
779
+ repoSearch))) : null,
780
+ React.createElement(Box, { flexDirection: "column", marginTop: 1, height: Math.min(Math.max(filteredRepos.length, 1), 20) }, filteredRepos.length === 0 ? (React.createElement(Text, { color: colors.muted }, "No repositories match the current filter.")) : filteredRepos.slice(Math.max(0, repoIdx - 15), Math.max(0, repoIdx - 15) + 20).map((repo, idx) => {
781
+ const actualIdx = Math.max(0, repoIdx - 15) + idx;
782
+ return (React.createElement(Text, { key: repo.name, color: actualIdx === repoIdx ? colors.primaryLight : colors.text, bold: actualIdx === repoIdx },
783
+ actualIdx === repoIdx ? ' \u25B8' : ' ',
784
+ " [",
785
+ repo.selected ? 'x' : ' ',
786
+ "] ",
787
+ repo.name));
788
+ })),
789
+ React.createElement(Box, { marginTop: 1 },
790
+ React.createElement(Text, { color: colors.success },
791
+ "Selected: ",
792
+ currentTarget.repos.filter(r => r.selected).length)),
793
+ React.createElement(Box, null,
794
+ React.createElement(Text, { color: colors.muted },
795
+ React.createElement(Text, { color: colors.key }, "Space"),
796
+ " toggle ",
797
+ React.createElement(Text, { color: colors.key }, "a"),
798
+ " all ",
799
+ React.createElement(Text, { color: colors.key }, "/"),
800
+ " search ",
801
+ React.createElement(Text, { color: colors.key }, "Enter"),
802
+ " continue ",
803
+ React.createElement(Text, { color: colors.key }, "Esc"),
804
+ " back")))),
805
+ step === 'agent-default' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
806
+ React.createElement(Text, { bold: true, color: colors.text }, "Choose the default agent for @agent"),
807
+ React.createElement(Box, { marginTop: 1 },
808
+ React.createElement(SelectInput, { items: installedAgents.map(a => ({ label: a, value: a })), initialIndex: Math.max(0, installedAgents.findIndex(agent => agent === defaultAgent)), onSelect: handleAgentSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'agent-default' })),
809
+ React.createElement(Box, { marginTop: 1 },
810
+ React.createElement(Text, { color: colors.muted },
811
+ React.createElement(Text, { color: colors.key }, "Esc"),
812
+ " back")))),
813
+ step === 'agent-mentions' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
814
+ React.createElement(Text, { bold: true, color: colors.text }, "Default mention aliases"),
815
+ React.createElement(Text, { color: colors.muted }, "Press Enter to keep these, or edit any alias."),
816
+ mentionEditActive ? (React.createElement(Box, { marginTop: 1 },
817
+ React.createElement(Text, { color: colors.key }, "Mention: "),
818
+ React.createElement(Text, { color: colors.text },
819
+ mentionInput,
820
+ React.createElement(Text, { color: colors.primaryLight }, "_")))) : (React.createElement(Box, { flexDirection: "column", marginTop: 1 }, mentionItems.map((item, idx) => (React.createElement(Text, { key: `${item.agent}-${idx}`, color: idx === mentionIdx ? colors.primaryLight : colors.text, bold: idx === mentionIdx },
821
+ idx === mentionIdx ? ' \u25B8 ' : ' ',
822
+ item.mention,
823
+ " ",
824
+ React.createElement(Text, { color: colors.muted },
825
+ "-> ",
826
+ item.agent)))))),
827
+ React.createElement(Box, { marginTop: 1 },
828
+ React.createElement(Text, { color: colors.muted }, mentionEditActive ? (React.createElement(React.Fragment, null,
829
+ React.createElement(Text, { color: colors.key }, "Enter"),
830
+ " save ",
831
+ React.createElement(Text, { color: colors.key }, "Esc"),
832
+ " cancel")) : (React.createElement(React.Fragment, null,
833
+ React.createElement(Text, { color: colors.key }, '\u2191\u2193'),
834
+ " navigate ",
835
+ React.createElement(Text, { color: colors.key }, "e"),
836
+ " edit ",
837
+ React.createElement(Text, { color: colors.key }, "r"),
838
+ " reset ",
839
+ React.createElement(Text, { color: colors.key }, "Enter"),
840
+ " continue ",
841
+ React.createElement(Text, { color: colors.key }, "Esc"),
842
+ " back")))))),
843
+ step === 'run-mode' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
844
+ React.createElement(Text, { bold: true, color: colors.text }, "How should runs be handled by default?"),
845
+ React.createElement(Text, { color: colors.muted }, "This selects PR merge behavior only. Swarm / Director auto-evolution is configured separately in `swarm` after onboarding."),
846
+ React.createElement(Box, { marginTop: 1 },
847
+ React.createElement(SelectInput, { items: RUN_MODES.map(m => ({ label: `${m.label} - ${m.desc}`, value: m.value })), initialIndex: runModeIdx, onSelect: handleRunModeSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'run-mode' })),
848
+ React.createElement(Box, { marginTop: 1 },
849
+ React.createElement(Text, { color: colors.muted },
850
+ React.createElement(Text, { color: colors.key }, "Esc"),
851
+ " back")))),
852
+ step === 'trigger-mode' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
853
+ React.createElement(Text, { bold: true, color: colors.text }, "When should agents trigger?"),
854
+ React.createElement(Box, { marginTop: 1 },
855
+ React.createElement(SelectInput, { items: TRIGGER_MODES.map(m => ({ label: `${m.label} - ${m.desc}`, value: m.value })), initialIndex: triggerModeIdx, onSelect: handleTriggerSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'trigger-mode' })),
856
+ React.createElement(Box, { marginTop: 1 },
857
+ React.createElement(Text, { color: colors.muted },
858
+ React.createElement(Text, { color: colors.key }, "Esc"),
859
+ " back")))),
860
+ step === 'automation' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
861
+ React.createElement(Text, { bold: true, color: colors.text }, "Automation level"),
862
+ React.createElement(Box, { marginTop: 1 },
863
+ React.createElement(SelectInput, { items: AUTOMATION_LEVELS.map(m => ({ label: `${m.label} - ${m.desc}`, value: m.value })), initialIndex: automationIdx, onSelect: handleAutomationSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'automation' })),
864
+ React.createElement(Box, { marginTop: 1 },
865
+ React.createElement(Text, { color: colors.muted },
866
+ React.createElement(Text, { color: colors.key }, "Esc"),
867
+ " back")))),
868
+ step === 'github-comments' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
869
+ React.createElement(Text, { bold: true, color: colors.text }, "GitHub progress comments"),
870
+ React.createElement(Box, { marginTop: 1 },
871
+ React.createElement(SelectInput, { items: GITHUB_COMMENT_OPTIONS.map(option => ({ label: `${option.label} - ${option.desc}`, value: option.value })), initialIndex: githubCommentIdx, onSelect: handleGitHubCommentSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'github-comments' })),
872
+ React.createElement(Box, { marginTop: 1 },
873
+ React.createElement(Text, { color: colors.muted }, "This controls whether Symphony mirrors agent progress, terminal traces, and the final result back into issue and PR comments.")),
874
+ React.createElement(Box, { marginTop: 1 },
875
+ React.createElement(Text, { color: colors.muted },
876
+ React.createElement(Text, { color: colors.key }, "Esc"),
877
+ " back")))),
878
+ step === 'startup-scan' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
879
+ React.createElement(Text, { bold: true, color: colors.text }, "Existing open issues on startup"),
880
+ React.createElement(Box, { marginTop: 1 },
881
+ React.createElement(SelectInput, { items: STARTUP_SCAN_OPTIONS.map(option => ({ label: `${option.label} - ${option.desc}`, value: option.value })), initialIndex: startupScanIdx, onSelect: handleStartupScanSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'startup-scan' })),
882
+ React.createElement(Box, { marginTop: 1 },
883
+ React.createElement(Text, { color: colors.muted }, "When enabled, Symphony caches the last startup scan and only rechecks issues/comments updated since then.")),
884
+ React.createElement(Box, { marginTop: 1 },
885
+ React.createElement(Text, { color: colors.muted },
886
+ React.createElement(Text, { color: colors.key }, "Esc"),
887
+ " back")))),
888
+ step === 'done' && (React.createElement(Box, { flexDirection: "column", paddingX: 2 },
889
+ React.createElement(Text, { bold: true, color: colors.text }, flow === 'reconfigure' ? 'Ready to apply changes' : 'Ready to write config'),
890
+ React.createElement(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: colors.border, paddingX: 1, paddingY: 0 },
891
+ React.createElement(Box, null,
892
+ React.createElement(Text, { color: colors.muted }, 'Owners:'.padEnd(14)),
893
+ React.createElement(Text, { color: colors.text }, selectedScopes.length > 0 ? selectedScopes.map(t => t.login).join(', ') : '(none)')),
894
+ React.createElement(Box, null,
895
+ React.createElement(Text, { color: colors.muted }, 'Repos:'.padEnd(14)),
896
+ React.createElement(Text, { color: colors.text }, selectedRepos.length)),
897
+ selectedScopes.map(target => (React.createElement(Box, { key: target.login },
898
+ React.createElement(Text, { color: colors.muted }, `${target.login}:`.padEnd(14)),
899
+ React.createElement(Text, { color: colors.text }, target.watchAll ? `all ${target.repos.length} repos` : `${target.repos.filter(r => r.selected).length} selected`)))),
900
+ React.createElement(Box, null,
901
+ React.createElement(Text, { color: colors.muted }, 'Default agent:'.padEnd(14)),
902
+ React.createElement(Text, { color: colors.accent, bold: true }, defaultAgent)),
903
+ React.createElement(Box, null,
904
+ React.createElement(Text, { color: colors.muted }, 'Mentions:'.padEnd(14)),
905
+ React.createElement(Text, { color: colors.text }, uniqueMentions.map(i => `${i.mention} -> ${i.agent}`).join(', '))),
906
+ React.createElement(Box, null,
907
+ React.createElement(Text, { color: colors.muted }, 'Mode:'.padEnd(14)),
908
+ React.createElement(Text, { color: colors.text }, RUN_MODES[runModeIdx].label)),
909
+ React.createElement(Box, null,
910
+ React.createElement(Text, { color: colors.muted }, 'Trigger:'.padEnd(14)),
911
+ React.createElement(Text, { color: colors.text }, TRIGGER_MODES[triggerModeIdx].label)),
912
+ React.createElement(Box, null,
913
+ React.createElement(Text, { color: colors.muted }, 'Startup scan:'.padEnd(14)),
914
+ React.createElement(Text, { color: colors.text }, STARTUP_SCAN_OPTIONS[startupScanIdx].label)),
915
+ React.createElement(Box, null,
916
+ React.createElement(Text, { color: colors.muted }, 'Automation:'.padEnd(14)),
917
+ React.createElement(Text, { color: colors.text }, AUTOMATION_LEVELS[automationIdx].label)),
918
+ React.createElement(Box, null,
919
+ React.createElement(Text, { color: colors.muted }, 'GitHub notes:'.padEnd(14)),
920
+ React.createElement(Text, { color: colors.text }, GITHUB_COMMENT_OPTIONS[githubCommentIdx].label)),
921
+ React.createElement(Box, null,
922
+ React.createElement(Text, { color: colors.muted }, 'Poll:'.padEnd(14)),
923
+ React.createElement(Text, { color: colors.text },
924
+ DEFAULT_POLL_INTERVAL_SEC,
925
+ "s")),
926
+ React.createElement(Box, null,
927
+ React.createElement(Text, { color: colors.muted }, 'Path:'.padEnd(14)),
928
+ React.createElement(Text, { color: colors.text }, configPath))),
929
+ React.createElement(Box, { flexDirection: "column", marginTop: 1 },
930
+ React.createElement(Text, { bold: true, color: colors.text }, "What next?"),
931
+ React.createElement(Box, { marginTop: 1 },
932
+ React.createElement(SelectInput, { items: flow === 'reconfigure'
933
+ ? [
934
+ { label: 'Apply changes', value: 'save' },
935
+ { label: 'Cancel without saving', value: 'discard' },
936
+ ]
937
+ : [
938
+ { label: 'Start Symphony now', value: 'start' },
939
+ { label: 'Exit (write config only)', value: 'exit' },
940
+ ], onSelect: handleDoneSelect, indicatorComponent: SelectIndicator, itemComponent: SelectItem, isFocused: step === 'done' }))),
941
+ flow !== 'reconfigure' && process.env.TMUX && doneIdx === 0 && (React.createElement(Box, { marginTop: 1 },
942
+ React.createElement(Text, { color: colors.muted }, "Will create a new tmux session and switch to it."))),
943
+ React.createElement(Box, { marginTop: 1 },
944
+ React.createElement(Text, { color: colors.muted },
945
+ React.createElement(Text, { color: colors.key }, "Esc"),
946
+ " back"))))));
947
+ };
948
+ //# sourceMappingURL=Onboarding.js.map