@nforma.ai/nforma 0.2.1

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 (215) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +1024 -0
  3. package/agents/qgsd-codebase-mapper.md +764 -0
  4. package/agents/qgsd-debugger.md +1201 -0
  5. package/agents/qgsd-executor.md +472 -0
  6. package/agents/qgsd-integration-checker.md +443 -0
  7. package/agents/qgsd-phase-researcher.md +502 -0
  8. package/agents/qgsd-plan-checker.md +643 -0
  9. package/agents/qgsd-planner.md +1182 -0
  10. package/agents/qgsd-project-researcher.md +621 -0
  11. package/agents/qgsd-quorum-orchestrator.md +628 -0
  12. package/agents/qgsd-quorum-slot-worker.md +41 -0
  13. package/agents/qgsd-quorum-synthesizer.md +133 -0
  14. package/agents/qgsd-quorum-test-worker.md +37 -0
  15. package/agents/qgsd-quorum-worker.md +161 -0
  16. package/agents/qgsd-research-synthesizer.md +239 -0
  17. package/agents/qgsd-roadmapper.md +660 -0
  18. package/agents/qgsd-verifier.md +628 -0
  19. package/bin/accept-debug-invariant.cjs +165 -0
  20. package/bin/account-manager.cjs +719 -0
  21. package/bin/aggregate-requirements.cjs +466 -0
  22. package/bin/analyze-assumptions.cjs +757 -0
  23. package/bin/analyze-state-space.cjs +921 -0
  24. package/bin/attribute-trace-divergence.cjs +150 -0
  25. package/bin/auth-drivers/gh-cli.cjs +93 -0
  26. package/bin/auth-drivers/index.cjs +46 -0
  27. package/bin/auth-drivers/pool.cjs +67 -0
  28. package/bin/auth-drivers/simple.cjs +95 -0
  29. package/bin/autoClosePtoF.cjs +110 -0
  30. package/bin/blessed-terminal.cjs +350 -0
  31. package/bin/build-phase-index.cjs +472 -0
  32. package/bin/call-quorum-slot.cjs +541 -0
  33. package/bin/ccr-secure-config.cjs +99 -0
  34. package/bin/ccr-secure-start.cjs +83 -0
  35. package/bin/check-bundled-sdks.cjs +177 -0
  36. package/bin/check-coverage-guard.cjs +112 -0
  37. package/bin/check-liveness-fairness.cjs +95 -0
  38. package/bin/check-mcp-health.cjs +123 -0
  39. package/bin/check-provider-health.cjs +395 -0
  40. package/bin/check-results-exit.cjs +24 -0
  41. package/bin/check-spec-sync.cjs +360 -0
  42. package/bin/check-trace-redaction.cjs +271 -0
  43. package/bin/check-trace-schema-drift.cjs +99 -0
  44. package/bin/compareDrift.cjs +21 -0
  45. package/bin/conformance-schema.cjs +12 -0
  46. package/bin/count-scenarios.cjs +420 -0
  47. package/bin/debt-dedup.cjs +144 -0
  48. package/bin/debt-ledger.cjs +61 -0
  49. package/bin/debt-retention.cjs +76 -0
  50. package/bin/debt-state-machine.cjs +80 -0
  51. package/bin/detect-coverage-gaps.cjs +204 -0
  52. package/bin/detect-project-intent.cjs +362 -0
  53. package/bin/export-prism-constants.cjs +164 -0
  54. package/bin/extract-annotations.cjs +633 -0
  55. package/bin/extractFormalExpected.cjs +104 -0
  56. package/bin/fingerprint-drift.cjs +24 -0
  57. package/bin/fingerprint-issue.cjs +46 -0
  58. package/bin/formal-core.cjs +519 -0
  59. package/bin/formal-ref-linker.cjs +141 -0
  60. package/bin/formal-test-sync.cjs +788 -0
  61. package/bin/generate-formal-specs.cjs +588 -0
  62. package/bin/generate-petri-net.cjs +397 -0
  63. package/bin/generate-phase-spec.cjs +249 -0
  64. package/bin/generate-proposed-changes.cjs +194 -0
  65. package/bin/generate-tla-cfg.cjs +122 -0
  66. package/bin/generate-traceability-matrix.cjs +701 -0
  67. package/bin/generate-triage-bundle.cjs +300 -0
  68. package/bin/gh-account-rotate.cjs +34 -0
  69. package/bin/initialize-model-registry.cjs +105 -0
  70. package/bin/install-formal-tools.cjs +382 -0
  71. package/bin/install.js +2424 -0
  72. package/bin/isNumericThreshold.cjs +34 -0
  73. package/bin/issue-classifier.cjs +151 -0
  74. package/bin/levenshtein.cjs +74 -0
  75. package/bin/lint-formal-models.cjs +580 -0
  76. package/bin/load-baseline-requirements.cjs +275 -0
  77. package/bin/manage-agents-core.cjs +815 -0
  78. package/bin/migrate-formal-dir.cjs +172 -0
  79. package/bin/migrate-planning.cjs +206 -0
  80. package/bin/migrate-to-slots.cjs +255 -0
  81. package/bin/nForma.cjs +2726 -0
  82. package/bin/observe-config.cjs +353 -0
  83. package/bin/observe-debt-writer.cjs +140 -0
  84. package/bin/observe-handler-grafana.cjs +128 -0
  85. package/bin/observe-handler-internal.cjs +301 -0
  86. package/bin/observe-handler-logstash.cjs +153 -0
  87. package/bin/observe-handler-prometheus.cjs +185 -0
  88. package/bin/observe-handlers.cjs +436 -0
  89. package/bin/observe-registry.cjs +131 -0
  90. package/bin/observe-render.cjs +168 -0
  91. package/bin/planning-paths.cjs +167 -0
  92. package/bin/polyrepo.cjs +560 -0
  93. package/bin/prism-priority.cjs +153 -0
  94. package/bin/probe-quorum-slots.cjs +167 -0
  95. package/bin/promote-model.cjs +225 -0
  96. package/bin/propose-debug-invariants.cjs +165 -0
  97. package/bin/providers.json +392 -0
  98. package/bin/pty-proxy.py +129 -0
  99. package/bin/qgsd-solve.cjs +2477 -0
  100. package/bin/quorum-consensus-gate.cjs +238 -0
  101. package/bin/quorum-formal-context.cjs +183 -0
  102. package/bin/quorum-slot-dispatch.cjs +934 -0
  103. package/bin/read-policy.cjs +60 -0
  104. package/bin/requirement-map.cjs +63 -0
  105. package/bin/requirements-core.cjs +247 -0
  106. package/bin/resolve-cli.cjs +101 -0
  107. package/bin/review-mcp-logs.cjs +294 -0
  108. package/bin/run-account-manager-tlc.cjs +188 -0
  109. package/bin/run-account-pool-alloy.cjs +158 -0
  110. package/bin/run-alloy.cjs +153 -0
  111. package/bin/run-audit-alloy.cjs +187 -0
  112. package/bin/run-breaker-tlc.cjs +181 -0
  113. package/bin/run-formal-check.cjs +395 -0
  114. package/bin/run-formal-verify.cjs +701 -0
  115. package/bin/run-installer-alloy.cjs +188 -0
  116. package/bin/run-oauth-rotation-prism.cjs +132 -0
  117. package/bin/run-oscillation-tlc.cjs +202 -0
  118. package/bin/run-phase-tlc.cjs +228 -0
  119. package/bin/run-prism.cjs +446 -0
  120. package/bin/run-protocol-tlc.cjs +201 -0
  121. package/bin/run-quorum-composition-alloy.cjs +155 -0
  122. package/bin/run-sensitivity-sweep.cjs +231 -0
  123. package/bin/run-stop-hook-tlc.cjs +188 -0
  124. package/bin/run-tlc.cjs +467 -0
  125. package/bin/run-transcript-alloy.cjs +173 -0
  126. package/bin/run-uppaal.cjs +264 -0
  127. package/bin/secrets.cjs +134 -0
  128. package/bin/sensitivity-report.cjs +219 -0
  129. package/bin/sensitivity-sweep-feedback.cjs +194 -0
  130. package/bin/set-secret.cjs +29 -0
  131. package/bin/setup-telemetry-cron.sh +36 -0
  132. package/bin/sweepPtoF.cjs +63 -0
  133. package/bin/sync-baseline-requirements.cjs +290 -0
  134. package/bin/task-envelope.cjs +360 -0
  135. package/bin/telemetry-collector.cjs +229 -0
  136. package/bin/unified-mcp-server.mjs +735 -0
  137. package/bin/update-agents.cjs +369 -0
  138. package/bin/update-scoreboard.cjs +1134 -0
  139. package/bin/validate-debt-entry.cjs +207 -0
  140. package/bin/validate-invariant.cjs +419 -0
  141. package/bin/validate-memory.cjs +389 -0
  142. package/bin/validate-requirements-haiku.cjs +435 -0
  143. package/bin/validate-traces.cjs +438 -0
  144. package/bin/verify-formal-results.cjs +124 -0
  145. package/bin/verify-quorum-health.cjs +273 -0
  146. package/bin/write-check-result.cjs +106 -0
  147. package/bin/xstate-to-tla.cjs +483 -0
  148. package/bin/xstate-trace-walker.cjs +205 -0
  149. package/commands/qgsd/add-phase.md +43 -0
  150. package/commands/qgsd/add-requirement.md +24 -0
  151. package/commands/qgsd/add-todo.md +47 -0
  152. package/commands/qgsd/audit-milestone.md +37 -0
  153. package/commands/qgsd/check-todos.md +45 -0
  154. package/commands/qgsd/cleanup.md +18 -0
  155. package/commands/qgsd/close-formal-gaps.md +33 -0
  156. package/commands/qgsd/complete-milestone.md +136 -0
  157. package/commands/qgsd/debug.md +166 -0
  158. package/commands/qgsd/discuss-phase.md +83 -0
  159. package/commands/qgsd/execute-phase.md +117 -0
  160. package/commands/qgsd/fix-tests.md +27 -0
  161. package/commands/qgsd/formal-test-sync.md +32 -0
  162. package/commands/qgsd/health.md +22 -0
  163. package/commands/qgsd/help.md +22 -0
  164. package/commands/qgsd/insert-phase.md +32 -0
  165. package/commands/qgsd/join-discord.md +18 -0
  166. package/commands/qgsd/list-phase-assumptions.md +46 -0
  167. package/commands/qgsd/map-codebase.md +71 -0
  168. package/commands/qgsd/map-requirements.md +20 -0
  169. package/commands/qgsd/mcp-restart.md +176 -0
  170. package/commands/qgsd/mcp-set-model.md +134 -0
  171. package/commands/qgsd/mcp-setup.md +1371 -0
  172. package/commands/qgsd/mcp-status.md +274 -0
  173. package/commands/qgsd/mcp-update.md +238 -0
  174. package/commands/qgsd/new-milestone.md +44 -0
  175. package/commands/qgsd/new-project.md +42 -0
  176. package/commands/qgsd/observe.md +260 -0
  177. package/commands/qgsd/pause-work.md +38 -0
  178. package/commands/qgsd/plan-milestone-gaps.md +34 -0
  179. package/commands/qgsd/plan-phase.md +44 -0
  180. package/commands/qgsd/polyrepo.md +50 -0
  181. package/commands/qgsd/progress.md +24 -0
  182. package/commands/qgsd/queue.md +54 -0
  183. package/commands/qgsd/quick.md +133 -0
  184. package/commands/qgsd/quorum-test.md +275 -0
  185. package/commands/qgsd/quorum.md +707 -0
  186. package/commands/qgsd/reapply-patches.md +110 -0
  187. package/commands/qgsd/remove-phase.md +31 -0
  188. package/commands/qgsd/research-phase.md +189 -0
  189. package/commands/qgsd/resume-work.md +40 -0
  190. package/commands/qgsd/set-profile.md +34 -0
  191. package/commands/qgsd/settings.md +39 -0
  192. package/commands/qgsd/solve.md +565 -0
  193. package/commands/qgsd/sync-baselines.md +119 -0
  194. package/commands/qgsd/triage.md +233 -0
  195. package/commands/qgsd/update.md +37 -0
  196. package/commands/qgsd/verify-work.md +38 -0
  197. package/hooks/dist/config-loader.js +297 -0
  198. package/hooks/dist/conformance-schema.cjs +12 -0
  199. package/hooks/dist/gsd-context-monitor.js +64 -0
  200. package/hooks/dist/qgsd-check-update.js +62 -0
  201. package/hooks/dist/qgsd-circuit-breaker.js +682 -0
  202. package/hooks/dist/qgsd-precompact.js +156 -0
  203. package/hooks/dist/qgsd-prompt.js +653 -0
  204. package/hooks/dist/qgsd-session-start.js +122 -0
  205. package/hooks/dist/qgsd-slot-correlator.js +58 -0
  206. package/hooks/dist/qgsd-spec-regen.js +86 -0
  207. package/hooks/dist/qgsd-statusline.js +91 -0
  208. package/hooks/dist/qgsd-stop.js +553 -0
  209. package/hooks/dist/qgsd-token-collector.js +133 -0
  210. package/hooks/dist/unified-mcp-server.mjs +669 -0
  211. package/package.json +95 -0
  212. package/scripts/build-hooks.js +46 -0
  213. package/scripts/postinstall.js +48 -0
  214. package/scripts/secret-audit.sh +45 -0
  215. package/templates/qgsd.json +49 -0
@@ -0,0 +1,369 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * update-agents.cjs — Detect, display, and update sub-coding agent CLIs.
5
+ *
6
+ * Exports:
7
+ * updateAgents() — interactive update flow (display table + prompt)
8
+ * getUpdateStatuses() — parallel version detection, returns Map<name, {current, latest, status}>
9
+ */
10
+
11
+ const path = require('path');
12
+ const { spawnSync } = require('child_process');
13
+ const inquirer = require('inquirer');
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // CLI metadata map — keyed by binary basename
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const CLI_META = {
20
+ codex: { installType: 'npm-global', pkg: '@openai/codex' },
21
+ gemini: { installType: 'npm-global', pkg: '@google/gemini-cli' },
22
+ opencode: { installType: 'npm-global', pkg: 'opencode' },
23
+ copilot: { installType: 'gh-extension', ext: 'github/gh-copilot' },
24
+ ccr: { installType: 'npm-global', pkg: 'claude-code-router' },
25
+ };
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Build CLI list from providers.json
29
+ // ---------------------------------------------------------------------------
30
+
31
+ function buildCliList() {
32
+ let providers = [];
33
+ try {
34
+ const data = require('./providers.json');
35
+ providers = data.providers || [];
36
+ } catch (_) {
37
+ return [];
38
+ }
39
+
40
+ // Deduplicate by binary basename
41
+ const seen = new Set();
42
+ const list = [];
43
+ for (const p of providers) {
44
+ if (!p.cli) continue;
45
+ const binName = path.basename(p.cli);
46
+ if (seen.has(binName)) continue;
47
+ seen.add(binName);
48
+ const meta = CLI_META[binName];
49
+ if (!meta) continue; // skip unknown CLIs
50
+ list.push({ name: binName, meta });
51
+ }
52
+ return list;
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Semver comparison (no external package)
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Returns true if version a >= version b (strips leading 'v').
61
+ */
62
+ function semverGte(a, b) {
63
+ if (!a || !b) return false;
64
+ const parse = (v) =>
65
+ String(v)
66
+ .replace(/^v/, '')
67
+ .split('.')
68
+ .map((n) => parseInt(n, 10) || 0);
69
+ const pa = parse(a);
70
+ const pb = parse(b);
71
+ const len = Math.max(pa.length, pb.length);
72
+ for (let i = 0; i < len; i++) {
73
+ const na = pa[i] || 0;
74
+ const nb = pb[i] || 0;
75
+ if (na > nb) return true;
76
+ if (na < nb) return false;
77
+ }
78
+ return true; // equal
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Derive status string
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * Returns 'up-to-date' | 'update-available' | 'unknown'
87
+ */
88
+ function deriveStatus(current, latest) {
89
+ if (!current || !latest) return 'unknown';
90
+ return semverGte(current, latest) ? 'up-to-date' : 'update-available';
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Detect current installed version
95
+ // ---------------------------------------------------------------------------
96
+
97
+ async function detectCurrent(meta) {
98
+ try {
99
+ if (meta.installType === 'npm-global') {
100
+ const res = spawnSync('npm', ['list', '-g', meta.pkg, '--depth=0', '--json'], {
101
+ encoding: 'utf8',
102
+ timeout: 8000,
103
+ });
104
+ if (res.status === 0 && res.stdout) {
105
+ const parsed = JSON.parse(res.stdout);
106
+ const dep = parsed.dependencies && parsed.dependencies[meta.pkg];
107
+ if (dep && dep.version) return dep.version;
108
+ }
109
+ } else if (meta.installType === 'gh-extension') {
110
+ const res = spawnSync('gh', ['extension', 'list'], {
111
+ encoding: 'utf8',
112
+ timeout: 6000,
113
+ });
114
+ if (res.status === 0 && res.stdout) {
115
+ const lines = res.stdout.split('\n');
116
+ for (const line of lines) {
117
+ // Format: github/gh-copilot v1.2.3 or similar tab-delimited
118
+ if (line.includes('copilot')) {
119
+ // Extract version token (starts with v or is a semver-like number)
120
+ const match = line.match(/\b(v?\d+\.\d+[\.\d]*)\b/);
121
+ if (match) return match[1].replace(/^v/, '');
122
+ }
123
+ }
124
+ }
125
+ }
126
+ } catch (_) {
127
+ // fall through
128
+ }
129
+ return null;
130
+ }
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Detect latest available version
134
+ // ---------------------------------------------------------------------------
135
+
136
+ async function detectLatest(meta) {
137
+ try {
138
+ if (meta.installType === 'npm-global') {
139
+ const res = spawnSync('npm', ['view', meta.pkg, 'version'], {
140
+ encoding: 'utf8',
141
+ timeout: 8000,
142
+ });
143
+ if (res.status === 0 && res.stdout) {
144
+ const v = res.stdout.trim();
145
+ if (v) return v;
146
+ }
147
+ } else if (meta.installType === 'gh-extension') {
148
+ const res = spawnSync(
149
+ 'gh',
150
+ ['extension', 'upgrade', '--dry-run', 'copilot'],
151
+ { encoding: 'utf8', stderr: 'pipe', timeout: 8000 }
152
+ );
153
+ const combined = (res.stdout || '') + (res.stderr || '');
154
+ if (/already up.?to.?date/i.test(combined)) {
155
+ // Return current version as latest (they're the same)
156
+ return await detectCurrent(meta);
157
+ }
158
+ // Try to parse a version from the output
159
+ const match = combined.match(/\b(v?\d+\.\d+[\.\d]*)\b/);
160
+ if (match) return match[1].replace(/^v/, '');
161
+ // Could not determine latest
162
+ return null;
163
+ }
164
+ } catch (_) {
165
+ // fall through
166
+ }
167
+ return null;
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // ANSI helpers
172
+ // ---------------------------------------------------------------------------
173
+
174
+ const RESET = '\x1b[0m';
175
+ const YELLOW = '\x1b[33m';
176
+ const GREEN = '\x1b[32m';
177
+ const DIM = '\x1b[90m';
178
+
179
+ function colorStatus(status) {
180
+ if (status === 'update-available') return YELLOW + '\u2191 update available' + RESET;
181
+ if (status === 'up-to-date') return GREEN + '\u2713 up to date' + RESET;
182
+ return DIM + '? unknown' + RESET;
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // getUpdateStatuses() — parallel, exported
187
+ // ---------------------------------------------------------------------------
188
+
189
+ async function getUpdateStatuses() {
190
+ try {
191
+ const cliList = buildCliList();
192
+ const results = await Promise.all(
193
+ cliList.map(async ({ name, meta }) => {
194
+ try {
195
+ const current = await detectCurrent(meta);
196
+ const latest = await detectLatest(meta);
197
+ const status = deriveStatus(current, latest);
198
+ return [name, { current, latest, status }];
199
+ } catch (_) {
200
+ return [name, { current: null, latest: null, status: 'unknown' }];
201
+ }
202
+ })
203
+ );
204
+ return new Map(results);
205
+ } catch (_) {
206
+ return new Map();
207
+ }
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // Print version table
212
+ // ---------------------------------------------------------------------------
213
+
214
+ function printTable(rows) {
215
+ const W = { cli: 10, installType: 13, current: 10, latest: 10 };
216
+
217
+ const header =
218
+ ' ' +
219
+ 'CLI'.padEnd(W.cli) +
220
+ 'Install'.padEnd(W.installType) +
221
+ 'Current'.padEnd(W.current) +
222
+ 'Latest'.padEnd(W.latest) +
223
+ 'Status';
224
+
225
+ const sep =
226
+ ' ' +
227
+ '\u2500'.repeat(W.cli - 1) +
228
+ ' ' +
229
+ '\u2500'.repeat(W.installType - 1) +
230
+ ' ' +
231
+ '\u2500'.repeat(W.current - 1) +
232
+ ' ' +
233
+ '\u2500'.repeat(W.latest - 1) +
234
+ ' ' +
235
+ '\u2500'.repeat(18);
236
+
237
+ console.log('\n' + header);
238
+ console.log(sep);
239
+
240
+ for (const row of rows) {
241
+ const line =
242
+ ' ' +
243
+ row.name.padEnd(W.cli) +
244
+ row.meta.installType.padEnd(W.installType) +
245
+ (row.current || '—').padEnd(W.current) +
246
+ (row.latest || '—').padEnd(W.latest) +
247
+ colorStatus(row.status);
248
+ console.log(line);
249
+ }
250
+ console.log('');
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Run update for one CLI
255
+ // ---------------------------------------------------------------------------
256
+
257
+ function runUpdate({ name, meta }) {
258
+ try {
259
+ let result;
260
+ if (meta.installType === 'npm-global') {
261
+ result = spawnSync('npm', ['install', '-g', `${meta.pkg}@latest`], {
262
+ stdio: 'inherit',
263
+ timeout: 60000,
264
+ });
265
+ } else if (meta.installType === 'gh-extension') {
266
+ result = spawnSync('gh', ['extension', 'upgrade', 'copilot'], {
267
+ stdio: 'inherit',
268
+ timeout: 30000,
269
+ });
270
+ }
271
+ if (result && result.status === 0) {
272
+ console.log(`\n Updated: ${name}`);
273
+ } else {
274
+ console.log(`\n \x1b[31mError updating ${name} (exit ${result ? result.status : '?'})\x1b[0m`);
275
+ }
276
+ } catch (err) {
277
+ console.log(`\n \x1b[31mError updating ${name}: ${err.message}\x1b[0m`);
278
+ }
279
+ }
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // updateAgents() — interactive, exported
283
+ // ---------------------------------------------------------------------------
284
+
285
+ async function updateAgents() {
286
+ const cliList = buildCliList();
287
+
288
+ if (cliList.length === 0) {
289
+ console.log('\n No known agent CLIs found in providers.json.\n');
290
+ return;
291
+ }
292
+
293
+ // Detect versions sequentially for display (consistent with user-visible flow)
294
+ const rows = [];
295
+ for (const { name, meta } of cliList) {
296
+ let current = null;
297
+ let latest = null;
298
+ let status = 'unknown';
299
+ try {
300
+ current = await detectCurrent(meta);
301
+ latest = await detectLatest(meta);
302
+ status = deriveStatus(current, latest);
303
+ } catch (_) {
304
+ // keep defaults
305
+ }
306
+ rows.push({ name, meta, current, latest, status });
307
+ }
308
+
309
+ printTable(rows);
310
+
311
+ const outdated = rows.filter((r) => r.status === 'update-available');
312
+
313
+ if (outdated.length === 0) {
314
+ console.log(' All agents are up to date.\n');
315
+ return;
316
+ }
317
+
318
+ const { action } = await inquirer.prompt([
319
+ {
320
+ type: 'list',
321
+ name: 'action',
322
+ message: 'Update options:',
323
+ choices: [
324
+ { name: 'Update all outdated', value: 'all' },
325
+ { name: 'Select individual agents', value: 'select' },
326
+ { name: 'Skip', value: 'skip' },
327
+ ],
328
+ },
329
+ ]);
330
+
331
+ if (action === 'skip') {
332
+ console.log('\n Skipped.\n');
333
+ return;
334
+ }
335
+
336
+ let toUpdate = outdated;
337
+
338
+ if (action === 'select') {
339
+ const { chosen } = await inquirer.prompt([
340
+ {
341
+ type: 'checkbox',
342
+ name: 'chosen',
343
+ message: 'Select agents to update:',
344
+ choices: outdated.map((r) => ({
345
+ name: `${r.name} (${r.current || '?'} -> ${r.latest || '?'})`,
346
+ value: r.name,
347
+ })),
348
+ },
349
+ ]);
350
+ toUpdate = outdated.filter((r) => chosen.includes(r.name));
351
+ }
352
+
353
+ if (toUpdate.length === 0) {
354
+ console.log('\n Nothing selected.\n');
355
+ return;
356
+ }
357
+
358
+ for (const row of toUpdate) {
359
+ runUpdate(row);
360
+ }
361
+
362
+ console.log('');
363
+ }
364
+
365
+ // ---------------------------------------------------------------------------
366
+ // Exports
367
+ // ---------------------------------------------------------------------------
368
+
369
+ module.exports = { updateAgents, getUpdateStatuses };