@nemus-cli/nemus 0.2.11 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.12] - 2026-08-27
11
+
12
+ ### Added
13
+
14
+ - **`--json` for more read-only reporting commands**, extending 0.2.11's set
15
+ (`list`/`status`/`doctor`) to `suite list`, `sessions`, and `analyze-deps`.
16
+ Each emits exactly one JSON document to stdout (no tables, no interactive
17
+ prompt): `suite list --json` (saved suites + entries), `sessions --json`
18
+ (workspace sessions, list-only — no resume), `analyze-deps --json` (per-repo
19
+ dependencies/dependents/missing + circular deps + suggested missing repos;
20
+ requires an explicit workspace). `--json` errors are parseable
21
+ `{ ok:false, error }` on stdout + exit 1, consistent with 0.2.11.
22
+
10
23
  ## [0.2.11] - 2026-08-27
11
24
 
12
25
  ### Added
package/README.md CHANGED
@@ -225,18 +225,23 @@ shared-lib main ✓ Clean ↓3 -
225
225
 
226
226
  ### Scripting: `--json`
227
227
 
228
- `list`, `status`, and `doctor` accept `--json` for stable, machine-readable
229
- output. Diagnostics go to stderr, so stdout is a single JSON document you can
230
- pipe straight into `jq` or a CI step:
228
+ The read-only reporting commands accept `--json` for stable, machine-readable
229
+ output: `list`, `status`, `doctor`, `suite list`, `sessions`, and
230
+ `analyze-deps`. Diagnostics go to stderr, so stdout is a single JSON document
231
+ you can pipe straight into `jq` or a CI step:
231
232
 
232
233
  ```bash
233
234
  nemus list --json | jq -r '.workspaces[].name'
234
235
  nemus status my-workspace --json | jq '.clean'
235
236
  nemus doctor my-workspace --json | jq '.score'
237
+ nemus suite list --json | jq -r '.suites[].name'
238
+ nemus sessions --json | jq -r '.sessions[].workspaceName'
239
+ nemus analyze-deps my-workspace --json | jq '.circularDependencies'
236
240
  ```
237
241
 
238
- `status`/`doctor` with `--json` need an explicit workspace name (they never
239
- prompt).
242
+ The workspace-scoped ones (`status`/`doctor`/`analyze-deps`) need an explicit
243
+ workspace name with `--json` (they never prompt). On failure, `--json` prints a
244
+ parseable `{ "ok": false, "error": … }` to stdout and exits non-zero.
240
245
 
241
246
  ### Suites (reusable repo collections)
242
247
 
@@ -42,6 +42,7 @@ const config_1 = require("../utils/config");
42
42
  const workspace_meta_1 = require("../utils/workspace-meta");
43
43
  const dependency_analyzer_1 = require("../utils/dependency-analyzer");
44
44
  const logger_1 = require("../utils/logger");
45
+ const output_1 = require("../utils/output");
45
46
  const colors_1 = require("../utils/colors");
46
47
  const inquirer_1 = __importDefault(require("inquirer"));
47
48
  const command_helpers_1 = require("../utils/command-helpers");
@@ -71,22 +72,51 @@ function registerAnalyzeDepsCommand(parent) {
71
72
  .alias('ad')
72
73
  .description('Analyze inter-repo dependencies')
73
74
  .argument('[workspace]', 'Workspace name')
74
- .action(async (workspace) => {
75
- await handleAnalyzeDeps(workspace);
75
+ .option('--json', 'Output as JSON (no interactive save)')
76
+ .action(async (workspace, opts) => {
77
+ await handleAnalyzeDeps(workspace, opts);
76
78
  });
77
79
  }
78
- async function handleAnalyzeDeps(workspaceArg) {
80
+ async function handleAnalyzeDeps(workspaceArg, opts = {}) {
79
81
  try {
82
+ // JSON mode is non-interactive: require an explicit workspace rather than prompt.
83
+ if (opts.json && !workspaceArg) {
84
+ (0, output_1.outputJsonError)('analyze-deps --json requires a workspace name');
85
+ process.exit(1);
86
+ }
80
87
  const selectedWorkspace = await (0, command_helpers_1.resolveWorkspace)(workspaceArg);
81
88
  const workspacePath = path.join(config_1.WORKSPACES_DIR, selectedWorkspace);
82
89
  const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
83
90
  if (!metadata) {
84
- (0, logger_1.logError)(`Workspace metadata not found for: ${selectedWorkspace}`);
91
+ if (opts.json)
92
+ (0, output_1.outputJsonError)(`Workspace metadata not found for: ${selectedWorkspace}`);
93
+ else
94
+ (0, logger_1.logError)(`Workspace metadata not found for: ${selectedWorkspace}`);
85
95
  process.exit(1);
86
96
  }
97
+ const repoNames = metadata.repositories.map(r => r.name);
98
+ if (opts.json) {
99
+ const analyses = await (0, dependency_analyzer_1.analyzeDependencies)(workspacePath, repoNames);
100
+ const cycles = (0, dependency_analyzer_1.detectCircularDependencies)(analyses);
101
+ const missing = new Set();
102
+ for (const [, a] of analyses)
103
+ for (const dep of a.missingDependencies)
104
+ missing.add(dep);
105
+ (0, output_1.outputJson)({
106
+ workspace: selectedWorkspace,
107
+ repositories: Array.from(analyses.entries()).map(([name, a]) => ({
108
+ name,
109
+ dependencies: a.dependencies,
110
+ dependents: a.dependents,
111
+ missingDependencies: a.missingDependencies,
112
+ })),
113
+ circularDependencies: cycles,
114
+ missingRepositories: Array.from(missing),
115
+ });
116
+ return;
117
+ }
87
118
  (0, logger_1.logStep)(`Analyzing dependencies for workspace: ${(0, colors_1.colorize)(selectedWorkspace, 'cyan')}`);
88
119
  (0, logger_1.logInfo)('Scanning package.json, Dockerfile, and docker-compose.yml files...');
89
- const repoNames = metadata.repositories.map(r => r.name);
90
120
  const analyses = await (0, dependency_analyzer_1.analyzeDependencies)(workspacePath, repoNames);
91
121
  displayDependencyAnalysis(analyses);
92
122
  const cycles = (0, dependency_analyzer_1.detectCircularDependencies)(analyses);
@@ -129,9 +159,13 @@ async function handleAnalyzeDeps(workspaceArg) {
129
159
  }
130
160
  }
131
161
  catch (error) {
132
- (0, logger_1.logError)('Failed to analyze dependencies');
133
- if (error instanceof Error) {
134
- (0, logger_1.logError)(error.message);
162
+ if (opts.json) {
163
+ (0, output_1.outputJsonError)(error instanceof Error ? error.message : 'Failed to analyze dependencies');
164
+ }
165
+ else {
166
+ (0, logger_1.logError)('Failed to analyze dependencies');
167
+ if (error instanceof Error)
168
+ (0, logger_1.logError)(error.message);
135
169
  }
136
170
  process.exit(1);
137
171
  }
@@ -43,6 +43,7 @@ const fs = __importStar(require("fs/promises"));
43
43
  const claude_sessions_1 = require("../utils/claude-sessions");
44
44
  const workspace_meta_1 = require("../utils/workspace-meta");
45
45
  const logger_1 = require("../utils/logger");
46
+ const output_1 = require("../utils/output");
46
47
  const colors_1 = require("../utils/colors");
47
48
  const inquirer_1 = __importDefault(require("inquirer"));
48
49
  const inquirer_autocomplete_prompt_1 = __importDefault(require("inquirer-autocomplete-prompt"));
@@ -55,14 +56,19 @@ function registerSessionsCommand(parent) {
55
56
  .command('sessions')
56
57
  .alias('ses')
57
58
  .description('Resume a Claude session in a workspace')
58
- .action(async () => {
59
- await handleSessions();
59
+ .option('--json', 'List sessions as JSON (no interactive resume)')
60
+ .action(async (opts) => {
61
+ await handleSessions(opts);
60
62
  });
61
63
  }
62
- async function handleSessions() {
64
+ async function handleSessions(opts = {}) {
63
65
  try {
64
66
  const sessions = await (0, claude_sessions_1.getWorkspaceSessions)();
65
67
  if (sessions.length === 0) {
68
+ if (opts.json) {
69
+ (0, output_1.outputJson)({ count: 0, sessions: [] });
70
+ return;
71
+ }
66
72
  (0, logger_1.logInfo)('No workspace sessions found.');
67
73
  console.log('\nYou can create a workspace with: nemus create');
68
74
  console.log('Or navigate to one with: w go');
@@ -76,6 +82,22 @@ async function handleSessions() {
76
82
  const repoLabel = repoCount > 0 ? `${repoCount} repos` : 'no repos';
77
83
  return { session, repoLabel };
78
84
  });
85
+ // JSON mode: list sessions to stdout, no resume prompt / temp-file writes.
86
+ if (opts.json) {
87
+ (0, output_1.outputJson)({
88
+ count: items.length,
89
+ sessions: items.map(i => ({
90
+ workspaceName: i.session.workspaceName,
91
+ workspacePath: i.session.workspacePath,
92
+ sessionId: i.session.sessionId,
93
+ agentType: i.session.agentType ?? null,
94
+ lastActive: i.session.lastActiveLabel,
95
+ lastActiveAt: i.session.lastActiveAt.toISOString(),
96
+ repoCount: workspaceMap.get(i.session.workspaceName)?.metadata?.repositories?.length ?? 0,
97
+ })),
98
+ });
99
+ return;
100
+ }
79
101
  const maxNameLen = Math.max(...items.map(i => i.session.workspaceName.length));
80
102
  console.log('');
81
103
  console.log((0, colors_1.colorize)(' Workspace Sessions', 'bright') + (0, colors_1.colorize)(' (sorted by last active)', 'dim'));
@@ -120,9 +142,14 @@ async function handleSessions() {
120
142
  catch (error) {
121
143
  if (error?.name === 'ExitPromptError')
122
144
  return;
123
- (0, logger_1.logError)('Failed to list sessions');
124
- if (error instanceof Error) {
125
- (0, logger_1.logError)(error.message);
145
+ if (opts.json) {
146
+ (0, output_1.outputJsonError)(error instanceof Error ? error.message : 'Failed to list sessions');
147
+ }
148
+ else {
149
+ (0, logger_1.logError)('Failed to list sessions');
150
+ if (error instanceof Error) {
151
+ (0, logger_1.logError)(error.message);
152
+ }
126
153
  }
127
154
  process.exit(1);
128
155
  }
@@ -49,9 +49,10 @@ function registerSuiteCommands(parent) {
49
49
  suite
50
50
  .command('list')
51
51
  .description('List all saved suites')
52
- .action(async () => {
52
+ .option('--json', 'Output as JSON')
53
+ .action(async (opts) => {
53
54
  const { main } = await Promise.resolve().then(() => __importStar(require('./list')));
54
- await main();
55
+ await main(opts);
55
56
  });
56
57
  suite
57
58
  .command('delete')
@@ -4,13 +4,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.main = main;
5
5
  const suite_1 = require("../../utils/suite");
6
6
  const logger_1 = require("../../utils/logger");
7
+ const output_1 = require("../../utils/output");
7
8
  const colors_1 = require("../../utils/colors");
8
- async function main() {
9
- console.log('\n' + '='.repeat(60));
10
- console.log((0, colors_1.colorize)('Saved Suites', 'bright'));
11
- console.log('='.repeat(60) + '\n');
9
+ async function main(opts = {}) {
12
10
  try {
13
11
  const suites = await (0, suite_1.listSuites)();
12
+ if (opts.json) {
13
+ (0, output_1.outputJson)({
14
+ count: suites.length,
15
+ suites: suites.map(s => ({
16
+ name: s.name,
17
+ description: s.description ?? null,
18
+ repoCount: s.entries.length,
19
+ entries: s.entries.map(e => ({ directoryName: e.directoryName, repoName: e.repoName })),
20
+ createdAt: s.createdAt,
21
+ updatedAt: s.updatedAt,
22
+ })),
23
+ });
24
+ return;
25
+ }
26
+ console.log('\n' + '='.repeat(60));
27
+ console.log((0, colors_1.colorize)('Saved Suites', 'bright'));
28
+ console.log('='.repeat(60) + '\n');
14
29
  if (suites.length === 0) {
15
30
  (0, logger_1.logInfo)('No suites found');
16
31
  console.log('\nCreate a new suite with:');
@@ -38,9 +53,13 @@ async function main() {
38
53
  console.log('='.repeat(60) + '\n');
39
54
  }
40
55
  catch (error) {
41
- (0, logger_1.logError)('Failed to list suites');
42
- if (error instanceof Error) {
43
- (0, logger_1.logError)(error.message);
56
+ if (opts.json) {
57
+ (0, output_1.outputJsonError)(error instanceof Error ? error.message : 'Failed to list suites');
58
+ }
59
+ else {
60
+ (0, logger_1.logError)('Failed to list suites');
61
+ if (error instanceof Error)
62
+ (0, logger_1.logError)(error.message);
44
63
  }
45
64
  process.exit(1);
46
65
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nemus-cli/nemus",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "workspaces": [
5
5
  "packages/*"
6
6
  ],
@@ -9,6 +9,7 @@ import {
9
9
  updateWorkspaceMetadata,
10
10
  } from '../utils/dependency-analyzer';
11
11
  import { logError, logInfo, logStep, logSuccess } from '../utils/logger';
12
+ import { outputJson, outputJsonError } from '../utils/output';
12
13
  import { colorize } from '../utils/colors';
13
14
  import inquirer from 'inquirer';
14
15
  import { resolveWorkspace } from '../utils/command-helpers';
@@ -42,26 +43,53 @@ export function registerAnalyzeDepsCommand(parent: Command) {
42
43
  .alias('ad')
43
44
  .description('Analyze inter-repo dependencies')
44
45
  .argument('[workspace]', 'Workspace name')
45
- .action(async (workspace) => {
46
- await handleAnalyzeDeps(workspace);
46
+ .option('--json', 'Output as JSON (no interactive save)')
47
+ .action(async (workspace, opts) => {
48
+ await handleAnalyzeDeps(workspace, opts);
47
49
  });
48
50
  }
49
51
 
50
- async function handleAnalyzeDeps(workspaceArg?: string) {
52
+ async function handleAnalyzeDeps(workspaceArg?: string, opts: { json?: boolean } = {}) {
51
53
  try {
54
+ // JSON mode is non-interactive: require an explicit workspace rather than prompt.
55
+ if (opts.json && !workspaceArg) {
56
+ outputJsonError('analyze-deps --json requires a workspace name');
57
+ process.exit(1);
58
+ }
52
59
  const selectedWorkspace = await resolveWorkspace(workspaceArg);
53
60
  const workspacePath = path.join(WORKSPACES_DIR, selectedWorkspace);
54
61
  const metadata = await loadMetadata(workspacePath);
55
62
 
56
63
  if (!metadata) {
57
- logError(`Workspace metadata not found for: ${selectedWorkspace}`);
64
+ if (opts.json) outputJsonError(`Workspace metadata not found for: ${selectedWorkspace}`);
65
+ else logError(`Workspace metadata not found for: ${selectedWorkspace}`);
58
66
  process.exit(1);
59
67
  }
60
68
 
69
+ const repoNames = metadata.repositories.map(r => r.name);
70
+
71
+ if (opts.json) {
72
+ const analyses = await analyzeDependencies(workspacePath, repoNames);
73
+ const cycles = detectCircularDependencies(analyses);
74
+ const missing = new Set<string>();
75
+ for (const [, a] of analyses) for (const dep of a.missingDependencies) missing.add(dep);
76
+ outputJson({
77
+ workspace: selectedWorkspace,
78
+ repositories: Array.from(analyses.entries()).map(([name, a]) => ({
79
+ name,
80
+ dependencies: a.dependencies,
81
+ dependents: a.dependents,
82
+ missingDependencies: a.missingDependencies,
83
+ })),
84
+ circularDependencies: cycles,
85
+ missingRepositories: Array.from(missing),
86
+ });
87
+ return;
88
+ }
89
+
61
90
  logStep(`Analyzing dependencies for workspace: ${colorize(selectedWorkspace, 'cyan')}`);
62
91
  logInfo('Scanning package.json, Dockerfile, and docker-compose.yml files...');
63
92
 
64
- const repoNames = metadata.repositories.map(r => r.name);
65
93
  const analyses = await analyzeDependencies(workspacePath, repoNames);
66
94
 
67
95
  displayDependencyAnalysis(analyses);
@@ -110,9 +138,11 @@ async function handleAnalyzeDeps(workspaceArg?: string) {
110
138
  }
111
139
  }
112
140
  } catch (error) {
113
- logError('Failed to analyze dependencies');
114
- if (error instanceof Error) {
115
- logError(error.message);
141
+ if (opts.json) {
142
+ outputJsonError(error instanceof Error ? error.message : 'Failed to analyze dependencies');
143
+ } else {
144
+ logError('Failed to analyze dependencies');
145
+ if (error instanceof Error) logError(error.message);
116
146
  }
117
147
  process.exit(1);
118
148
  }
@@ -5,6 +5,7 @@ import * as fs from 'fs/promises';
5
5
  import { getWorkspaceSessions, WorkspaceSession } from '../utils/claude-sessions';
6
6
  import { listWorkspaces } from '../utils/workspace-meta';
7
7
  import { logError, logInfo } from '../utils/logger';
8
+ import { outputJson, outputJsonError } from '../utils/output';
8
9
  import { colorize } from '../utils/colors';
9
10
  import inquirer from 'inquirer';
10
11
  import autocompletePrompt from 'inquirer-autocomplete-prompt';
@@ -20,16 +21,21 @@ export function registerSessionsCommand(parent: Command) {
20
21
  .command('sessions')
21
22
  .alias('ses')
22
23
  .description('Resume a Claude session in a workspace')
23
- .action(async () => {
24
- await handleSessions();
24
+ .option('--json', 'List sessions as JSON (no interactive resume)')
25
+ .action(async (opts) => {
26
+ await handleSessions(opts);
25
27
  });
26
28
  }
27
29
 
28
- async function handleSessions() {
30
+ async function handleSessions(opts: { json?: boolean } = {}) {
29
31
  try {
30
32
  const sessions = await getWorkspaceSessions();
31
33
 
32
34
  if (sessions.length === 0) {
35
+ if (opts.json) {
36
+ outputJson({ count: 0, sessions: [] });
37
+ return;
38
+ }
33
39
  logInfo('No workspace sessions found.');
34
40
  console.log('\nYou can create a workspace with: nemus create');
35
41
  console.log('Or navigate to one with: w go');
@@ -46,6 +52,23 @@ async function handleSessions() {
46
52
  return { session, repoLabel };
47
53
  });
48
54
 
55
+ // JSON mode: list sessions to stdout, no resume prompt / temp-file writes.
56
+ if (opts.json) {
57
+ outputJson({
58
+ count: items.length,
59
+ sessions: items.map(i => ({
60
+ workspaceName: i.session.workspaceName,
61
+ workspacePath: i.session.workspacePath,
62
+ sessionId: i.session.sessionId,
63
+ agentType: i.session.agentType ?? null,
64
+ lastActive: i.session.lastActiveLabel,
65
+ lastActiveAt: i.session.lastActiveAt.toISOString(),
66
+ repoCount: workspaceMap.get(i.session.workspaceName)?.metadata?.repositories?.length ?? 0,
67
+ })),
68
+ });
69
+ return;
70
+ }
71
+
49
72
  const maxNameLen = Math.max(...items.map(i => i.session.workspaceName.length));
50
73
 
51
74
  console.log('');
@@ -92,8 +115,12 @@ async function handleSessions() {
92
115
  console.log(`\n${colorize('Resuming:', 'green')} ${session.workspaceName} (last active ${session.lastActiveLabel})`);
93
116
  } catch (error) {
94
117
  if ((error as any)?.name === 'ExitPromptError') return;
95
- logError('Failed to list sessions');
96
- if (error instanceof Error) { logError(error.message); }
118
+ if (opts.json) {
119
+ outputJsonError(error instanceof Error ? error.message : 'Failed to list sessions');
120
+ } else {
121
+ logError('Failed to list sessions');
122
+ if (error instanceof Error) { logError(error.message); }
123
+ }
97
124
  process.exit(1);
98
125
  }
99
126
  }
@@ -17,9 +17,10 @@ export function registerSuiteCommands(parent: Command) {
17
17
  suite
18
18
  .command('list')
19
19
  .description('List all saved suites')
20
- .action(async () => {
20
+ .option('--json', 'Output as JSON')
21
+ .action(async (opts) => {
21
22
  const { main } = await import('./list');
22
- await main();
23
+ await main(opts);
23
24
  });
24
25
 
25
26
  suite
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ const { mockListSuites } = vi.hoisted(() => ({ mockListSuites: vi.fn() }));
4
+
5
+ vi.mock('../../utils/suite', () => ({ listSuites: mockListSuites }));
6
+ vi.mock('../../utils/logger', () => ({ logInfo: vi.fn(), logError: vi.fn() }));
7
+ vi.mock('../../utils/colors', () => ({ colorize: (t: string) => t }));
8
+
9
+ import { main } from './list';
10
+
11
+ function makeSuite(name: string, entries = 1) {
12
+ return {
13
+ name,
14
+ description: `${name} desc`,
15
+ entries: Array.from({ length: entries }, (_, i) => ({ directoryName: `repo-${i}`, repoName: `repo-${i}` })),
16
+ createdAt: '2026-01-01T00:00:00.000Z',
17
+ updatedAt: '2026-01-02T00:00:00.000Z',
18
+ };
19
+ }
20
+
21
+ describe('suite list --json', () => {
22
+ let writeSpy: ReturnType<typeof vi.spyOn>;
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
26
+ vi.spyOn(console, 'log').mockImplementation(() => {});
27
+ });
28
+ afterEach(() => vi.restoreAllMocks());
29
+
30
+ it('emits one valid JSON document with normalized suites', async () => {
31
+ mockListSuites.mockResolvedValueOnce([makeSuite('fees', 2), makeSuite('platform', 1)]);
32
+ await main({ json: true });
33
+ expect(writeSpy).toHaveBeenCalledTimes(1);
34
+ const payload = JSON.parse(writeSpy.mock.calls[0][0] as string);
35
+ expect(payload.count).toBe(2);
36
+ expect(payload.suites[0]).toEqual({
37
+ name: 'fees',
38
+ description: 'fees desc',
39
+ repoCount: 2,
40
+ entries: [
41
+ { directoryName: 'repo-0', repoName: 'repo-0' },
42
+ { directoryName: 'repo-1', repoName: 'repo-1' },
43
+ ],
44
+ createdAt: '2026-01-01T00:00:00.000Z',
45
+ updatedAt: '2026-01-02T00:00:00.000Z',
46
+ });
47
+ });
48
+
49
+ it('empty list emits count 0 (no header/log noise)', async () => {
50
+ mockListSuites.mockResolvedValueOnce([]);
51
+ await main({ json: true });
52
+ const payload = JSON.parse(writeSpy.mock.calls[0][0] as string);
53
+ expect(payload).toEqual({ count: 0, suites: [] });
54
+ });
55
+
56
+ it('non-json mode does not write to the stdout data channel', async () => {
57
+ mockListSuites.mockResolvedValueOnce([makeSuite('fees')]);
58
+ await main();
59
+ // human output goes through console.log (mocked), not process.stdout.write
60
+ expect(writeSpy).not.toHaveBeenCalled();
61
+ });
62
+ });
@@ -2,16 +2,32 @@
2
2
 
3
3
  import { listSuites } from '../../utils/suite';
4
4
  import { logInfo, logError } from '../../utils/logger';
5
+ import { outputJson, outputJsonError } from '../../utils/output';
5
6
  import { colorize } from '../../utils/colors';
6
7
 
7
- export async function main() {
8
- console.log('\n' + '='.repeat(60));
9
- console.log(colorize('Saved Suites', 'bright'));
10
- console.log('='.repeat(60) + '\n');
11
-
8
+ export async function main(opts: { json?: boolean } = {}) {
12
9
  try {
13
10
  const suites = await listSuites();
14
11
 
12
+ if (opts.json) {
13
+ outputJson({
14
+ count: suites.length,
15
+ suites: suites.map(s => ({
16
+ name: s.name,
17
+ description: s.description ?? null,
18
+ repoCount: s.entries.length,
19
+ entries: s.entries.map(e => ({ directoryName: e.directoryName, repoName: e.repoName })),
20
+ createdAt: s.createdAt,
21
+ updatedAt: s.updatedAt,
22
+ })),
23
+ });
24
+ return;
25
+ }
26
+
27
+ console.log('\n' + '='.repeat(60));
28
+ console.log(colorize('Saved Suites', 'bright'));
29
+ console.log('='.repeat(60) + '\n');
30
+
15
31
  if (suites.length === 0) {
16
32
  logInfo('No suites found');
17
33
  console.log('\nCreate a new suite with:');
@@ -43,9 +59,11 @@ export async function main() {
43
59
 
44
60
  console.log('='.repeat(60) + '\n');
45
61
  } catch (error) {
46
- logError('Failed to list suites');
47
- if (error instanceof Error) {
48
- logError(error.message);
62
+ if (opts.json) {
63
+ outputJsonError(error instanceof Error ? error.message : 'Failed to list suites');
64
+ } else {
65
+ logError('Failed to list suites');
66
+ if (error instanceof Error) logError(error.message);
49
67
  }
50
68
  process.exit(1);
51
69
  }