@nemus-cli/nemus 0.5.0 → 0.9.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.0] - 2026-09-01
11
+
12
+ ### Added
13
+
14
+ - **`nemus config edit`** opens the config file in `$VISUAL`/`$EDITOR` (seeding
15
+ it with the current resolved config on first run) and re-validates the JSON
16
+ afterward, warning about unrecognized keys. Refuses to run without an
17
+ interactive terminal.
18
+ - **Environment-variable reference** in the README documenting every variable
19
+ Nemus reads (`NEMUS_*`, `WORKSPACE_*`, `NO_COLOR`/`FORCE_COLOR`,
20
+ `VISUAL`/`EDITOR`).
21
+
22
+ ## [0.8.0] - 2026-09-01
23
+
24
+ ### Added
25
+
26
+ - **`nemus reflect history`** and **`nemus reflect show [id]`** to review saved
27
+ reports (under `~/.nemus/reflect/`) without re-running the judge. `show`
28
+ defaults to the latest, accepts an id or id-prefix, and supports
29
+ `--json` / `--markdown` / `--group-by`.
30
+ - **`nemus reflect --group-by kind|priority`** (default `priority`) to control
31
+ how recommendations are grouped in both the human and Markdown output.
32
+
33
+ ## [0.7.0] - 2026-09-01
34
+
35
+ ### Added
36
+
37
+ - **`nemus reflect --markdown`** — render the reflection report as clean,
38
+ severity-grouped Markdown to stdout (pipe into a file or an issue:
39
+ `nemus reflect --markdown > reflection.md`). Fenced example snippets are
40
+ escaped so they can't break out of their own code block.
41
+ - The human `reflect` report now prints a **severity summary line**
42
+ (e.g. `2 high · 1 medium`).
43
+
44
+ ## [0.6.0] - 2026-09-01
45
+
46
+ ### Added
47
+
48
+ - **`nemus config` command** for non-interactive configuration:
49
+ `config get [key]`, `set <key> <value>`, `unset <key>`, `list` (alias `ls`),
50
+ and `path`. Values are validated and coerced per field (booleans accept
51
+ `true/false/yes/no/on/off/1/0`; enums like `cloneProtocol` are checked), an
52
+ unknown key or invalid value exits non-zero with a clear message, and
53
+ `get`/`list` support `--json`. Complements the interactive `configure` wizard
54
+ and pairs well with `--quiet` for scripting.
55
+
10
56
  ## [0.5.0] - 2026-09-01
11
57
 
12
58
  ### Added
package/README.md CHANGED
@@ -243,6 +243,42 @@ The workspace-scoped ones (`status`/`doctor`/`analyze-deps`) need an explicit
243
243
  workspace name with `--json` (they never prompt). On failure, `--json` prints a
244
244
  parseable `{ "ok": false, "error": … }` to stdout and exits non-zero.
245
245
 
246
+ ### Configuration
247
+
248
+ Run `nemus configure` for the interactive wizard, or manage settings
249
+ non-interactively (handy for scripts and dotfiles):
250
+
251
+ ```bash
252
+ nemus config list # all keys, values, and descriptions
253
+ nemus config get cloneProtocol # print one value (raw, for scripts)
254
+ nemus config set cloneProtocol https # validated + coerced per key
255
+ nemus config set autoReportBugs yes # booleans accept true/false/yes/no/on/off/1/0
256
+ nemus config unset githubOrg # reset a key to its default
257
+ nemus config path # print the config file location
258
+ ```
259
+
260
+ `get`/`list` also accept `--json`. An unknown key or an invalid value exits
261
+ non-zero with a clear message (e.g. `cloneProtocol must be one of: ssh, https`).
262
+ `config edit` opens the file in `$VISUAL`/`$EDITOR` (seeding it with the current
263
+ resolved config first) and re-validates the JSON afterward.
264
+
265
+ ### Environment variables
266
+
267
+ Everything Nemus reads from the environment (all optional):
268
+
269
+ | Variable | Effect |
270
+ | --- | --- |
271
+ | `NEMUS_DIR` | Override where workspaces are created (also `WORKSPACE_MANAGER_DIR`). |
272
+ | `NEMUS_CACHE_DIR` | Override the cache/config/state dir, default `~/.nemus` (also `WORKSPACE_MANAGER_CACHE_DIR`). |
273
+ | `NEMUS_JUDGE_MODEL` | Model for the `reflect` judge (overrides `--model`'s default). |
274
+ | `NEMUS_JUDGE_THINKING` | Thinking level for the `reflect` judge on pi (`off`…`max`). |
275
+ | `NEMUS_JUDGE_TIMEOUT_MS` | Timeout for the `reflect` judge call. |
276
+ | `NEMUS_BUG_REPORT_REPO` | Repo that `report-bug` files issues against. |
277
+ | `NEMUS_SKIP_CONFIGURE` | Skip the one-time post-install `configure` prompt. |
278
+ | `WORKSPACE_CLONE_TIMEOUT_MS` | Git clone timeout (default 15 min). |
279
+ | `NO_COLOR` / `FORCE_COLOR` | Disable / force ANSI color (see [Global flags](#global-flags)). |
280
+ | `VISUAL` / `EDITOR` | Editor launched by `nemus config edit`. |
281
+
246
282
  ### Global flags
247
283
 
248
284
  - `--no-color` — disable ANSI color. Nemus also honors the standard
@@ -294,9 +330,25 @@ nemus snapshot restore <id> # (sr)
294
330
  nemus reflect # (retro) analyze your last 10 workspaces' sessions
295
331
  nemus reflect --limit 5 # narrow the window
296
332
  nemus reflect --json # structured report for tooling
333
+ nemus reflect --markdown # Markdown report (grouped by severity) to paste/save
334
+ nemus reflect --group-by kind # group recommendations by kind instead of priority
297
335
  nemus reflect --dry-run # show what the judge sees, without calling the agent
298
336
  ```
299
337
 
338
+ `--markdown` writes a clean, severity-grouped report to stdout — pipe it into a
339
+ file or an issue: `nemus reflect --markdown > reflection.md`. `--group-by
340
+ kind|priority` (default `priority`) controls how recommendations are grouped in
341
+ both the human and Markdown output.
342
+
343
+ Every run is saved under `~/.nemus/reflect/`. Review past reports without
344
+ re-running the judge:
345
+
346
+ ```bash
347
+ nemus reflect history # list saved reports, newest first (--json)
348
+ nemus reflect show # print the latest saved report
349
+ nemus reflect show <id> # a specific one (--markdown / --json / --group-by)
350
+ ```
351
+
300
352
  `reflect` reads your recent agent **session transcripts** (Claude + pi), distills
301
353
  the prompts you sent, the failures the agent hit, and the tools it used, then asks
302
354
  **your own configured agent** (LLM-as-a-judge — no extra API key) to recommend
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.registerConfigCommand = registerConfigCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const config_1 = require("../utils/config");
39
+ const editor_1 = require("../utils/editor");
40
+ const config_schema_1 = require("../utils/config-schema");
41
+ const output_1 = require("../utils/output");
42
+ const logger_1 = require("../utils/logger");
43
+ const colors_1 = require("../utils/colors");
44
+ /**
45
+ * Non-interactive config management: `nemus config get/set/unset/list/path`.
46
+ * Complements the interactive `configure` wizard and is script-friendly —
47
+ * `get`/`list` write DATA to stdout (raw value, or JSON with --json), logs go to
48
+ * stderr. Values are validated/coerced against config-schema.ts.
49
+ */
50
+ function registerConfigCommand(parent) {
51
+ const config = parent.command('config').description('Get or set Nemus configuration');
52
+ config
53
+ .command('get')
54
+ .description('Print a config value (or all values with no key)')
55
+ .argument('[key]', 'Config key')
56
+ .option('--json', 'Output as JSON')
57
+ .action((key, opts) => {
58
+ const cfg = (0, config_1.getUserConfig)();
59
+ if (key === undefined) {
60
+ printAll(cfg, opts.json);
61
+ return;
62
+ }
63
+ if (!(0, config_schema_1.isConfigKey)(key)) {
64
+ if (opts.json)
65
+ (0, output_1.outputJsonError)(`Unknown config key "${key}"`);
66
+ else
67
+ (0, logger_1.logError)(`Unknown config key "${key}". Run "nemus config list" to see valid keys.`);
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+ const value = cfg[key];
72
+ if (opts.json)
73
+ (0, output_1.outputJson)({ key, value });
74
+ else
75
+ process.stdout.write((0, config_schema_1.formatConfigValue)(value) + '\n');
76
+ });
77
+ config
78
+ .command('set')
79
+ .description('Set a config value')
80
+ .argument('<key>', 'Config key')
81
+ .argument('<value>', 'New value')
82
+ .option('--json', 'Output as JSON')
83
+ .action((key, value, opts) => {
84
+ const result = (0, config_schema_1.applyConfigSet)((0, config_1.getUserConfig)(), key, value);
85
+ if (!result.ok) {
86
+ if (opts.json)
87
+ (0, output_1.outputJsonError)(result.error);
88
+ else
89
+ (0, logger_1.logError)(result.error);
90
+ process.exitCode = 1;
91
+ return;
92
+ }
93
+ (0, config_1.saveUserConfig)(result.next);
94
+ if (opts.json)
95
+ (0, output_1.outputJson)({ ok: true, key, value: result.value });
96
+ else
97
+ (0, logger_1.logSuccess)(`Set ${(0, colors_1.colorize)(key, 'cyan')} = ${(0, config_schema_1.formatConfigValue)(result.value)}`);
98
+ });
99
+ config
100
+ .command('unset')
101
+ .description('Reset a config value to its default')
102
+ .argument('<key>', 'Config key')
103
+ .option('--json', 'Output as JSON')
104
+ .action((key, opts) => {
105
+ const result = (0, config_schema_1.applyConfigUnset)((0, config_1.getUserConfig)(), key);
106
+ if (!result.ok) {
107
+ if (opts.json)
108
+ (0, output_1.outputJsonError)(result.error);
109
+ else
110
+ (0, logger_1.logError)(result.error);
111
+ process.exitCode = 1;
112
+ return;
113
+ }
114
+ (0, config_1.saveUserConfig)(result.next);
115
+ if (opts.json)
116
+ (0, output_1.outputJson)({ ok: true, key, value: result.value });
117
+ else
118
+ (0, logger_1.logSuccess)(`Reset ${(0, colors_1.colorize)(key, 'cyan')} to default (${(0, config_schema_1.formatConfigValue)(result.value)})`);
119
+ });
120
+ config
121
+ .command('list')
122
+ .alias('ls')
123
+ .description('List all config keys and current values')
124
+ .option('--json', 'Output as JSON')
125
+ .action((opts) => printAll((0, config_1.getUserConfig)(), opts.json));
126
+ config
127
+ .command('path')
128
+ .description('Print the path to the config file')
129
+ .action(() => {
130
+ process.stdout.write(config_1.CONFIG_PATH + '\n');
131
+ });
132
+ config
133
+ .command('edit')
134
+ .description('Open the config file in $EDITOR (or $VISUAL)')
135
+ .action(() => {
136
+ handleEdit();
137
+ });
138
+ }
139
+ function handleEdit() {
140
+ if (!process.stdout.isTTY) {
141
+ (0, logger_1.logError)('`config edit` needs an interactive terminal. Use `config set <key> <value>` in scripts.');
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ // Seed the file with the fully-resolved config so there's something complete
146
+ // to edit on a first run (getUserConfig merges defaults + any overrides).
147
+ if (!fs.existsSync(config_1.CONFIG_PATH))
148
+ (0, config_1.saveUserConfig)((0, config_1.getUserConfig)());
149
+ const result = (0, editor_1.openInEditor)(config_1.CONFIG_PATH);
150
+ if (!result.ok) {
151
+ (0, logger_1.logError)(result.error ?? `editor exited with code ${result.code}`);
152
+ process.exitCode = 1;
153
+ return;
154
+ }
155
+ // Re-validate: a hand-edit can produce invalid JSON or invalid values, which
156
+ // getUserConfig would silently ignore (falling back to defaults). Surface that
157
+ // instead, using the SAME schema `config set` uses so both write paths agree.
158
+ const review = (0, config_schema_1.reviewConfigFileText)(fs.readFileSync(config_1.CONFIG_PATH, 'utf-8'));
159
+ if (review.parseError) {
160
+ (0, logger_1.logError)(`${config_1.CONFIG_PATH} is not valid JSON after editing — changes are kept, but Nemus will use defaults until it parses.`);
161
+ process.exitCode = 1;
162
+ return;
163
+ }
164
+ if (review.notObject) {
165
+ (0, logger_1.logError)(`${config_1.CONFIG_PATH} must contain a JSON object — changes are kept, but Nemus will use defaults until it does.`);
166
+ process.exitCode = 1;
167
+ return;
168
+ }
169
+ if (review.unknownKeys.length > 0)
170
+ (0, logger_1.logWarning)(`Ignoring unrecognized key(s): ${review.unknownKeys.join(', ')}`);
171
+ if (!review.ok) {
172
+ for (const e of review.invalid)
173
+ (0, logger_1.logWarning)(e);
174
+ (0, logger_1.logError)('Some values are invalid and will fall back to their defaults until fixed.');
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ (0, logger_1.logSuccess)('Config saved.');
179
+ }
180
+ function printAll(cfg, json) {
181
+ if (json) {
182
+ const values = {};
183
+ for (const key of config_schema_1.CONFIG_KEYS)
184
+ values[key] = cfg[key];
185
+ (0, output_1.outputJson)({ path: config_1.CONFIG_PATH, values });
186
+ return;
187
+ }
188
+ const width = Math.max(...config_schema_1.CONFIG_KEYS.map((k) => k.length));
189
+ console.log((0, colors_1.colorize)('Nemus configuration', 'bright') + (0, colors_1.colorize)(` (${config_1.CONFIG_PATH})`, 'dim'));
190
+ for (const key of config_schema_1.CONFIG_KEYS) {
191
+ const val = (0, config_schema_1.formatConfigValue)(cfg[key]);
192
+ const shown = val === '' ? (0, colors_1.colorize)('(empty)', 'dim') : val;
193
+ console.log(` ${key.padEnd(width)} ${shown} ${(0, colors_1.colorize)(config_schema_1.CONFIG_SCHEMA[key].describe, 'dim')}`);
194
+ }
195
+ }
@@ -11,22 +11,55 @@ function registerReflectCommand(parent) {
11
11
  parent
12
12
  .command('reflect')
13
13
  .alias('retro')
14
- .description('Analyze your recent workspace sessions and suggest skill/prompt/context improvements (LLM-as-a-judge)')
14
+ .description('Analyze recent sessions for improvements, or review saved reports ("history"/"show")')
15
+ .argument('[subcommand]', '"history" or "show" — omit to run a new analysis')
16
+ .argument('[id]', 'report id when using "show" (default: latest)')
15
17
  .option('-n, --limit <n>', 'How many recent workspaces to analyze', '10')
16
18
  .option('-w, --workspace <name>', 'Analyze a single workspace by name (ignores --limit)')
17
19
  .option('--model <model>', 'Judge model override (agent-native pattern/id)')
18
20
  .option('--thinking <level>', 'Judge thinking level for pi: off|minimal|low|medium|high|xhigh|max')
19
21
  .option('--json', 'Output the report as JSON')
22
+ .option('--markdown', 'Output the report as Markdown (paste into an issue/PR)')
23
+ .option('--group-by <how>', 'Group recommendations by: priority | kind', 'priority')
20
24
  .option('--no-save', 'Do not save the report to ~/.nemus/reflect/')
21
25
  .option('--dry-run', 'Print the assembled corpus + judge prompt without calling the agent')
22
- .action(async (opts) => {
23
- await handleReflect(opts);
26
+ .addHelpText('after', '\nSaved reports:\n nemus reflect history List saved reports (newest first)\n nemus reflect show [id] Print a saved report (default: latest)\n')
27
+ // history/show are positional (not commander subcommands) on purpose: as
28
+ // subcommands they would share --json/--markdown/--group-by with this parent
29
+ // command, and the only commander fix (enablePositionalOptions on the root)
30
+ // breaks global flags placed after a subcommand (e.g. `nemus list --quiet`).
31
+ .action(async (subcommand, id, opts) => {
32
+ if (subcommand === 'history')
33
+ return handleHistory(opts);
34
+ if (subcommand === 'show')
35
+ return handleShow(id, opts);
36
+ if (subcommand !== undefined) {
37
+ const msg = `Unknown reflect subcommand "${subcommand}" (expected "history" or "show").`;
38
+ if (opts.json)
39
+ (0, output_1.outputJsonError)(msg);
40
+ else
41
+ (0, logger_1.logError)(msg);
42
+ process.exit(1);
43
+ }
44
+ return handleReflect(opts);
24
45
  });
25
46
  }
47
+ /** Validate --group-by; returns the value or exits non-zero with a clear error. */
48
+ function resolveGroupBy(raw, json) {
49
+ if (raw === undefined || raw === 'priority' || raw === 'kind')
50
+ return (raw ?? 'priority');
51
+ const msg = `--group-by must be "priority" or "kind"; got "${raw}"`;
52
+ if (json)
53
+ (0, output_1.outputJsonError)(msg);
54
+ else
55
+ (0, logger_1.logError)(msg);
56
+ process.exit(1);
57
+ }
26
58
  async function handleReflect(opts) {
27
59
  const limit = Math.max(1, Number.parseInt(opts.limit ?? '10', 10) || 10);
60
+ const groupBy = resolveGroupBy(opts.groupBy, opts.json);
28
61
  try {
29
- const showProgress = !opts.json && !opts.dryRun;
62
+ const showProgress = !opts.json && !opts.markdown && !opts.dryRun;
30
63
  if (showProgress) {
31
64
  (0, logger_1.logStep)(opts.workspace
32
65
  ? `Analyzing workspace ${(0, colors_1.colorize)(opts.workspace, 'cyan')}…`
@@ -66,7 +99,7 @@ async function handleReflect(opts) {
66
99
  const timeoutMs = Number.parseInt(process.env.NEMUS_JUDGE_TIMEOUT_MS ?? '', 10) || undefined;
67
100
  const model = opts.model ?? process.env.NEMUS_JUDGE_MODEL ?? undefined;
68
101
  const thinking = opts.thinking ?? process.env.NEMUS_JUDGE_THINKING ?? undefined;
69
- const stopSpinner = opts.json
102
+ const stopSpinner = opts.json || opts.markdown
70
103
  ? () => { }
71
104
  : startSpinner(`Judging ${withSessions} session(s) with your configured agent (this can take a minute)…`);
72
105
  let parsed;
@@ -95,7 +128,20 @@ async function handleReflect(opts) {
95
128
  (0, output_1.outputJson)({ analyzed: withSessions, workspaces: corpus.workspaces.length, ...report, savedTo });
96
129
  return;
97
130
  }
98
- printReport(report, corpus.workspaces.length, withSessions);
131
+ if (opts.markdown) {
132
+ // DATA channel: markdown to stdout, nothing else (a 'Saved report' note
133
+ // would corrupt a redirected .md file), so surface the path on stderr.
134
+ process.stdout.write((0, reflect_1.renderReportMarkdown)(report, {
135
+ analyzed: withSessions,
136
+ workspaces: corpus.workspaces.length,
137
+ workspace: opts.workspace,
138
+ generatedAt: new Date().toISOString(),
139
+ }, groupBy));
140
+ if (savedTo)
141
+ (0, logger_1.logInfo)(`Saved report to ${(0, colors_1.colorize)(savedTo, 'dim')}`);
142
+ return;
143
+ }
144
+ printReport(report, corpus.workspaces.length, withSessions, groupBy);
99
145
  if (savedTo)
100
146
  (0, logger_1.logInfo)(`Saved report to ${(0, colors_1.colorize)(savedTo, 'dim')}`);
101
147
  }
@@ -164,7 +210,7 @@ function priorityBadge(p) {
164
210
  return (0, colors_1.colorize)('● med', 'yellow');
165
211
  return (0, colors_1.colorize)('● low', 'gray');
166
212
  }
167
- function printReport(report, workspaces, analyzed) {
213
+ function printReport(report, workspaces, analyzed, groupBy = 'priority') {
168
214
  console.log('');
169
215
  console.log((0, colors_1.colorize)(' Reflection', 'bright') + (0, colors_1.colorize)(` (${analyzed} sessions across ${workspaces} workspaces)`, 'dim'));
170
216
  console.log((0, colors_1.colorize)(' ' + '─'.repeat(56), 'dim'));
@@ -175,19 +221,94 @@ function printReport(report, workspaces, analyzed) {
175
221
  console.log('\n ' + (0, colors_1.colorize)('No specific recommendations — looks solid.', 'green') + '\n');
176
222
  return;
177
223
  }
178
- // High priority first.
179
- const order = { high: 0, medium: 1, low: 2 };
180
- const recs = [...report.recommendations].sort((a, b) => order[a.priority] - order[b.priority]);
181
- console.log('');
182
- for (const r of recs) {
183
- const target = r.target ? (0, colors_1.colorize)(` [${r.target}]`, 'cyan') : '';
184
- console.log(` ${priorityBadge(r.priority)} ${(0, colors_1.colorize)(KIND_LABEL[r.kind], 'bright')} ${r.title}${target}`);
185
- if (r.detail)
186
- console.log(` ${r.detail.replace(/\n/g, '\n ')}`);
187
- if (r.example) {
188
- console.log((0, colors_1.colorize)(' example:', 'dim'));
189
- console.log((0, colors_1.colorize)(r.example.replace(/^/gm, ' '), 'dim'));
224
+ console.log('\n ' + (0, colors_1.colorize)((0, reflect_1.severitySummary)(report.recommendations), 'dim'));
225
+ for (const group of (0, reflect_1.groupRecommendations)(report.recommendations, groupBy)) {
226
+ console.log('\n ' + (0, colors_1.colorize)(group.heading, 'bright'));
227
+ for (const r of group.recs) {
228
+ const target = r.target ? (0, colors_1.colorize)(` [${r.target}]`, 'cyan') : '';
229
+ // Under a kind heading show the priority badge; under a priority heading
230
+ // show the kind label (the heading conveys the other axis).
231
+ const lead = groupBy === 'kind' ? priorityBadge(r.priority) : (0, colors_1.colorize)(KIND_LABEL[r.kind], 'bright');
232
+ console.log(` ${lead} ${r.title}${target}`);
233
+ if (r.detail)
234
+ console.log(` ${r.detail.replace(/\n/g, '\n ')}`);
235
+ if (r.example) {
236
+ console.log((0, colors_1.colorize)(' example:', 'dim'));
237
+ console.log((0, colors_1.colorize)(r.example.replace(/^/gm, ' '), 'dim'));
238
+ }
190
239
  }
191
- console.log('');
192
240
  }
241
+ console.log('');
242
+ }
243
+ async function handleHistory(opts) {
244
+ const reports = await (0, reflect_1.listSavedReports)();
245
+ if (opts.json) {
246
+ (0, output_1.outputJson)({
247
+ count: reports.length,
248
+ reports: reports.map((r) => ({
249
+ id: r.id,
250
+ generatedAt: r.generatedAt,
251
+ analyzed: r.analyzed,
252
+ workspaces: r.workspaces,
253
+ workspace: r.workspace,
254
+ recommendations: r.report.recommendations.length,
255
+ severity: (0, reflect_1.severitySummary)(r.report.recommendations),
256
+ })),
257
+ });
258
+ return;
259
+ }
260
+ if (reports.length === 0) {
261
+ (0, logger_1.logInfo)('No saved reflection reports yet. Run `nemus reflect` to create one.');
262
+ return;
263
+ }
264
+ console.log('');
265
+ console.log((0, colors_1.colorize)(' Saved reflections', 'bright') + (0, colors_1.colorize)(` (${reports.length})`, 'dim'));
266
+ console.log((0, colors_1.colorize)(' ' + '─'.repeat(56), 'dim'));
267
+ for (const r of reports) {
268
+ const when = r.generatedAt ? new Date(r.generatedAt).toLocaleString() : r.id;
269
+ const scope = r.workspace ? (0, colors_1.colorize)(` ${r.workspace}`, 'cyan') : (0, colors_1.colorize)(` ${r.analyzed} sessions`, 'dim');
270
+ const sev = r.report.recommendations.length
271
+ ? (0, colors_1.colorize)(` ${(0, reflect_1.severitySummary)(r.report.recommendations)}`, 'dim')
272
+ : (0, colors_1.colorize)(' no recs', 'green');
273
+ console.log(` ${(0, colors_1.colorize)(r.id, 'bright')}${scope}${sev}`);
274
+ console.log((0, colors_1.colorize)(` ${when}`, 'dim'));
275
+ }
276
+ console.log('');
277
+ console.log((0, colors_1.colorize)(' nemus reflect show <id> (or `latest`)', 'dim'));
278
+ }
279
+ async function handleShow(id, opts) {
280
+ const groupBy = resolveGroupBy(opts.groupBy, opts.json);
281
+ const matches = (0, reflect_1.findSavedMatches)(await (0, reflect_1.listSavedReports)(), id);
282
+ const saved = matches[0];
283
+ if (!saved) {
284
+ const msg = id && id !== 'latest'
285
+ ? `No saved report matching "${id}". Run "nemus reflect history" to list them.`
286
+ : 'No saved reflection reports yet. Run "nemus reflect" to create one.';
287
+ if (opts.json)
288
+ (0, output_1.outputJsonError)(msg);
289
+ else
290
+ (0, logger_1.logError)(msg);
291
+ process.exit(1);
292
+ }
293
+ // An id-prefix that matches several reports resolves to the newest — say so
294
+ // (stderr only, so --json/--markdown stdout stays clean) rather than quietly
295
+ // showing a possibly-unintended report. An exact id / "latest" never multi-matches.
296
+ if (matches.length > 1 && !opts.json) {
297
+ (0, logger_1.logWarning)(`"${id}" matched ${matches.length} reports; showing the newest (${saved.id}). Use a longer id to disambiguate.`);
298
+ }
299
+ const meta = {
300
+ analyzed: saved.analyzed,
301
+ workspaces: saved.workspaces,
302
+ workspace: saved.workspace,
303
+ generatedAt: saved.generatedAt,
304
+ };
305
+ if (opts.json) {
306
+ (0, output_1.outputJson)({ id: saved.id, ...meta, ...saved.report });
307
+ return;
308
+ }
309
+ if (opts.markdown) {
310
+ process.stdout.write((0, reflect_1.renderReportMarkdown)(saved.report, meta, groupBy));
311
+ return;
312
+ }
313
+ printReport(saved.report, saved.workspaces, saved.analyzed, groupBy);
193
314
  }
package/dist/program.js CHANGED
@@ -84,6 +84,7 @@ const archive_1 = require("./commands/archive");
84
84
  const sessions_1 = require("./commands/sessions");
85
85
  const generate_docs_1 = require("./commands/generate-docs");
86
86
  const configure_1 = require("./commands/configure");
87
+ const config_1 = require("./commands/config");
87
88
  const configure_claude_1 = require("./commands/configure-claude");
88
89
  const ghq_status_1 = require("./commands/ghq-status");
89
90
  const save_context_1 = require("./commands/save-context");
@@ -109,6 +110,7 @@ const reflect_1 = require("./commands/reflect");
109
110
  (0, sessions_1.registerSessionsCommand)(exports.program);
110
111
  (0, generate_docs_1.registerGenerateDocsCommand)(exports.program);
111
112
  (0, configure_1.registerConfigureCommand)(exports.program);
113
+ (0, config_1.registerConfigCommand)(exports.program);
112
114
  (0, configure_claude_1.registerConfigureClaudeCommand)(exports.program);
113
115
  (0, ghq_status_1.registerGhqStatusCommand)(exports.program);
114
116
  (0, save_context_1.registerSaveContextCommand)(exports.program);