@moda-ai/cli 1.1.1 → 1.2.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 (59) hide show
  1. package/README.md +36 -0
  2. package/dist/agent.d.ts +5 -0
  3. package/dist/agent.js +477 -0
  4. package/dist/analyst-tui.d.ts +81 -0
  5. package/dist/analyst-tui.js +314 -0
  6. package/dist/api-client.d.ts +4 -0
  7. package/dist/api-client.js +20 -3
  8. package/dist/auth.d.ts +22 -3
  9. package/dist/auth.js +230 -66
  10. package/dist/cli.d.ts +20 -3
  11. package/dist/cli.js +768 -93
  12. package/dist/command-context.d.ts +34 -0
  13. package/dist/command-context.js +38 -0
  14. package/dist/command-registry.d.ts +22 -0
  15. package/dist/command-registry.js +15 -0
  16. package/dist/config.d.ts +40 -1
  17. package/dist/config.js +70 -3
  18. package/dist/doctor.d.ts +79 -0
  19. package/dist/doctor.js +1102 -0
  20. package/dist/errors.d.ts +5 -0
  21. package/dist/errors.js +9 -0
  22. package/dist/harness-report.d.ts +138 -0
  23. package/dist/harness-report.js +1464 -0
  24. package/dist/harness.d.ts +233 -0
  25. package/dist/harness.js +3264 -0
  26. package/dist/init/index.d.ts +30 -1
  27. package/dist/init/index.js +383 -23
  28. package/dist/init/rules.d.ts +5 -1
  29. package/dist/init/rules.js +22 -16
  30. package/dist/init/sdk-guide.d.ts +3 -0
  31. package/dist/init/sdk-guide.js +397 -0
  32. package/dist/init/tenant.js +12 -3
  33. package/dist/keys.d.ts +47 -0
  34. package/dist/keys.js +136 -0
  35. package/dist/output.d.ts +118 -0
  36. package/dist/output.js +228 -0
  37. package/dist/production-intelligence.d.ts +134 -0
  38. package/dist/production-intelligence.js +1113 -0
  39. package/dist/profile-commands.d.ts +10 -0
  40. package/dist/profile-commands.js +252 -0
  41. package/dist/profiles.d.ts +90 -0
  42. package/dist/profiles.js +432 -0
  43. package/dist/prompts.d.ts +6 -0
  44. package/dist/prompts.js +368 -0
  45. package/dist/schemas.d.ts +4 -4
  46. package/dist/skills.d.ts +25 -0
  47. package/dist/skills.js +468 -0
  48. package/dist/sync.d.ts +5 -0
  49. package/dist/sync.js +27 -0
  50. package/dist/telemetry.d.ts +18 -0
  51. package/dist/telemetry.js +63 -0
  52. package/dist/tui.d.ts +40 -0
  53. package/dist/tui.js +387 -0
  54. package/dist/workspace.d.ts +12 -0
  55. package/dist/workspace.js +1294 -0
  56. package/hooks/moda-snapshot.sh +41 -0
  57. package/package.json +10 -7
  58. package/skills/moda-cli/SKILL.md +48 -4
  59. package/skills/integrate-moda/SKILL.md +0 -63
package/dist/tui.js ADDED
@@ -0,0 +1,387 @@
1
+ const STATUS_LABELS = {
2
+ pass: 'ok',
3
+ warning: 'warn',
4
+ fail: 'fail',
5
+ skipped: 'skip',
6
+ info: 'info',
7
+ run: 'run',
8
+ };
9
+ const STATUS_COLORS = {
10
+ pass: 32,
11
+ warning: 33,
12
+ fail: 31,
13
+ skipped: 90,
14
+ info: 36,
15
+ run: 35,
16
+ };
17
+ export function renderDoctorHuman(report, options = {}) {
18
+ const ui = createTerminalUi(options);
19
+ const lines = [];
20
+ lines.push(ui.title('Moda doctor'));
21
+ lines.push(ui.dim(`Profile ${report.profile.active}${report.profile.requested ? ` (requested ${report.profile.requested})` : ''}`));
22
+ lines.push('');
23
+ lines.push(renderSummary(ui, report));
24
+ for (const [group, checks] of groupChecks(report.checks)) {
25
+ lines.push('');
26
+ lines.push(ui.section(titleCase(group)));
27
+ for (const check of checks) {
28
+ lines.push(renderCheck(ui, check));
29
+ }
30
+ }
31
+ if (report.nextCommands.length > 0) {
32
+ lines.push('');
33
+ lines.push(ui.section('Next commands'));
34
+ for (const command of report.nextCommands.slice(0, 8)) {
35
+ lines.push(` ${ui.command(command)}`);
36
+ }
37
+ }
38
+ if (report.fixPlan.length > 0) {
39
+ lines.push('');
40
+ lines.push(ui.section('Repair plan'));
41
+ for (const item of report.fixPlan.slice(0, 8)) {
42
+ const command = item.command ? ` ${ui.command(item.command)}` : '';
43
+ lines.push(ui.wrap(` - ${item.action}${command}`, 4));
44
+ }
45
+ }
46
+ return lines.join('\n');
47
+ }
48
+ export function renderStatusHuman(status, options = {}) {
49
+ const ui = createTerminalUi(options);
50
+ const lines = [];
51
+ lines.push(ui.title('Moda status'));
52
+ lines.push(`${ui.status(status.status)} ${ui.dim(`profile ${status.activeProfile}`)}`);
53
+ lines.push('');
54
+ lines.push(`Auth ${status.auth.apiKey}`);
55
+ lines.push(`Tenant ${status.workspace.tenantId ?? 'missing'}`);
56
+ lines.push(`Base URL ${status.workspace.baseUrl}`);
57
+ lines.push(`Ingest URL ${status.workspace.ingestUrl}`);
58
+ lines.push('');
59
+ lines.push(ui.section('Readiness'));
60
+ lines.push(` Data flow ${ui.status(status.dataFlow)}`);
61
+ lines.push(` Harness ${ui.status(status.harness)}`);
62
+ lines.push(` Harness sync ${ui.status(status.harnessSync)}`);
63
+ lines.push(` Harness report ${ui.status(status.harnessReport)}`);
64
+ lines.push(` Shadow Spark ${ui.status(status.shadowSpark)}`);
65
+ lines.push(` Prompts ${ui.status(status.prompts)}`);
66
+ lines.push(` Skills ${ui.status(status.skills)}`);
67
+ if (status.topBrokenCheck) {
68
+ lines.push('');
69
+ lines.push(ui.section('Attention'));
70
+ lines.push(` ${ui.status(status.topBrokenCheck.status)} ${status.topBrokenCheck.id}`);
71
+ if (status.topBrokenCheck.nextCommand) {
72
+ lines.push(` Next: ${ui.command(status.topBrokenCheck.nextCommand)}`);
73
+ }
74
+ }
75
+ if (status.nextCommands.length > 0) {
76
+ lines.push('');
77
+ lines.push(ui.section('Next commands'));
78
+ for (const command of status.nextCommands) {
79
+ lines.push(` ${ui.command(command)}`);
80
+ }
81
+ }
82
+ return lines.join('\n');
83
+ }
84
+ export function renderInitBanner() {
85
+ const ui = createTerminalUi();
86
+ return [
87
+ ui.title('Moda init'),
88
+ ui.dim('LLM observability for agent harnesses'),
89
+ '',
90
+ ` ${ui.status('info')} Human-in-the-loop setup. Secrets stay untouched and file changes are shown before completion.`,
91
+ ].join('\n');
92
+ }
93
+ export function renderInitStep(index, total, title, detail) {
94
+ const ui = createTerminalUi();
95
+ return [
96
+ '',
97
+ ui.section(`Step ${index}/${total}: ${title}`),
98
+ ...(detail ? [ui.wrap(` ${detail}`, 2)] : []),
99
+ ].join('\n');
100
+ }
101
+ export function renderInitNote(message, status = 'pass') {
102
+ const ui = createTerminalUi();
103
+ return ` ${ui.status(status)} ${message}`;
104
+ }
105
+ export function renderInitSetupPlan(actions) {
106
+ const ui = createTerminalUi();
107
+ const lines = actions.map((action) => {
108
+ const status = action.approved
109
+ ? 'pass'
110
+ : action.status === 'manual'
111
+ ? 'skipped'
112
+ : 'warning';
113
+ const suffix = action.reason ? ` - ${action.reason}` : '';
114
+ return ui.wrap(` ${ui.status(status)} ${action.title}${suffix}`, 4);
115
+ });
116
+ return lines.join('\n');
117
+ }
118
+ export function renderInitComplete(options) {
119
+ const ui = createTerminalUi();
120
+ const lines = [];
121
+ lines.push('');
122
+ lines.push(ui.title(`Setup complete for ${options.tenantName}`));
123
+ lines.push('');
124
+ lines.push(ui.section('Phases'));
125
+ for (const phase of options.phases) {
126
+ lines.push(` ${ui.status(phase.status === 'warning' ? 'warning' : phase.status === 'skipped' ? 'skipped' : 'pass')} ${phase.title}${phase.details ? ` - ${phase.details}` : ''}`);
127
+ }
128
+ lines.push('');
129
+ lines.push(ui.section('Files changed'));
130
+ if (options.filesChanged.length === 0) {
131
+ lines.push(` ${ui.dim('none')}`);
132
+ }
133
+ else {
134
+ for (const file of options.filesChanged)
135
+ lines.push(` ${file}`);
136
+ }
137
+ if (options.skipped.length > 0) {
138
+ lines.push('');
139
+ lines.push(ui.section('Skipped or manual'));
140
+ for (const action of options.skipped) {
141
+ lines.push(ui.wrap(` - ${action.title}: ${action.reason ?? action.status}`, 4));
142
+ }
143
+ }
144
+ lines.push('');
145
+ lines.push(ui.section('Next commands'));
146
+ lines.push(` ${ui.command('moda status')}`);
147
+ lines.push(` ${ui.command('moda doctor')}`);
148
+ lines.push(` ${ui.command('moda harness sync --summary')}`);
149
+ lines.push('');
150
+ lines.push(ui.section('Agent handoff'));
151
+ lines.push(' Integrate the Moda SDK into this project using .moda/SDK_INTEGRATION.md');
152
+ lines.push(' and .moda/HARNESS.md. Package installs and source edits stay manual.');
153
+ lines.push('');
154
+ lines.push('Dashboard: https://moda.dev');
155
+ return lines.join('\n');
156
+ }
157
+ export function renderHarnessProgress(message, status = 'run') {
158
+ const ui = createTerminalUi();
159
+ return `${ui.status(status)} ${message}`;
160
+ }
161
+ export function renderHarnessAnalystPreflight(options) {
162
+ const ui = createTerminalUi();
163
+ const lines = [];
164
+ lines.push(ui.section('Analyst read plan'));
165
+ lines.push(` ${ui.status('info')} Moda Analyst will inspect this project before any sync.`);
166
+ lines.push(` Root ${options.root}`);
167
+ lines.push(ui.wrap(` Reads ${options.willRead.join(', ')}`, 12));
168
+ lines.push(ui.wrap(` Skips ${options.willSkip.join(', ')}`, 12));
169
+ lines.push(` Writes ${ui.dim('none during investigation; Moda writes .moda artifacts after validation')}`);
170
+ return lines.join('\n');
171
+ }
172
+ export function renderHarnessAnalystLaunch(options) {
173
+ const ui = createTerminalUi();
174
+ const lines = [];
175
+ lines.push(ui.section('Moda Analyst'));
176
+ lines.push(` ${ui.status('run')} Launching ${options.adapterName}`);
177
+ lines.push(` Command ${options.command}`);
178
+ lines.push(` Mode read-only repository access`);
179
+ lines.push(` Root ${options.root}`);
180
+ lines.push(` Output ${options.reportPath}`);
181
+ lines.push(` Boundary Agent can inspect files and print a cited report. Moda validates it, then writes .moda artifacts.`);
182
+ return lines.join('\n');
183
+ }
184
+ export function renderHarnessAnalystChoice(options) {
185
+ const ui = createTerminalUi();
186
+ return [
187
+ ui.section('Detected coding agents'),
188
+ ` ${ui.status('info')} ${options.detected.join(', ')}`,
189
+ ' Pick the agent that should investigate this repo in read-only mode.',
190
+ ].join('\n');
191
+ }
192
+ export function renderHarnessAnalyzeHumanResult(data) {
193
+ const ui = createTerminalUi();
194
+ const value = asRecord(data);
195
+ const summary = asRecord(value?.summary);
196
+ const validation = asRecord(value?.validation);
197
+ const written = Array.isArray(value?.written) ? value.written.map(String) : [];
198
+ const lines = [];
199
+ lines.push(ui.title('Moda Analyst report'));
200
+ lines.push(`${ui.status(validation?.status === 'fail' ? 'fail' : 'pass')} validation ${String(validation?.status ?? 'unknown')}`);
201
+ if (summary) {
202
+ lines.push('');
203
+ lines.push(ui.section('Harness summary'));
204
+ lines.push(` Runtime agents ${numberOrZero(summary.runtimeAgentCount)}`);
205
+ lines.push(` Agent families ${numberOrZero(summary.agentFamilyCount)}`);
206
+ lines.push(` Artifacts ${numberOrZero(summary.artifactCount ?? summary.sharedArtifactCount)}`);
207
+ lines.push(` Providers ${numberOrZero(summary.modelProviderCount)}`);
208
+ lines.push(` Unknowns ${numberOrZero(summary.unknownCount)}`);
209
+ }
210
+ if (written.length > 0) {
211
+ lines.push('');
212
+ lines.push(ui.section('Artifacts'));
213
+ for (const file of written.slice(0, 12))
214
+ lines.push(` ${file}`);
215
+ }
216
+ lines.push('');
217
+ lines.push(ui.section('Next commands'));
218
+ lines.push(` ${ui.command('moda harness approve --yes')}`);
219
+ lines.push(` ${ui.command('moda harness sync --from-report .moda/harness-report.json --dry-run')}`);
220
+ return lines.join('\n');
221
+ }
222
+ export function renderOverviewHuman(data) {
223
+ const ui = createTerminalUi();
224
+ const record = asRecord(data);
225
+ const lines = [];
226
+ lines.push(ui.title('Moda overview'));
227
+ lines.push(ui.dim('Production health briefing'));
228
+ lines.push('');
229
+ lines.push(ui.section('Signals'));
230
+ const signals = overviewSignals(record);
231
+ if (signals.length === 0) {
232
+ lines.push(' No aggregate signal fields found in the response.');
233
+ }
234
+ else {
235
+ for (const [label, value] of signals)
236
+ lines.push(` ${label.padEnd(24)} ${value}`);
237
+ }
238
+ const recent = recentActivity(record);
239
+ if (recent.length > 0) {
240
+ lines.push('');
241
+ lines.push(ui.section('Recent activity'));
242
+ for (const item of recent.slice(0, 5))
243
+ lines.push(ui.wrap(` - ${item}`, 4));
244
+ }
245
+ lines.push('');
246
+ lines.push(ui.section('Next commands'));
247
+ lines.push(` ${ui.command('moda doctor --online')}`);
248
+ lines.push(` ${ui.command('moda conversations --limit=10')}`);
249
+ lines.push(` ${ui.command('moda tool-failures')}`);
250
+ return lines.join('\n');
251
+ }
252
+ function renderSummary(ui, report) {
253
+ return [
254
+ ui.status(report.summary.status),
255
+ `pass ${report.summary.pass}`,
256
+ `warn ${report.summary.warning}`,
257
+ `fail ${report.summary.fail}`,
258
+ `skip ${report.summary.skipped}`,
259
+ ].join(' ');
260
+ }
261
+ function renderCheck(ui, check) {
262
+ const lines = [];
263
+ lines.push(` ${ui.status(check.status)} ${check.title}`);
264
+ for (const evidence of check.evidence.slice(0, 4)) {
265
+ lines.push(ui.wrap(` ${evidence.label}: ${evidence.value}`, 6));
266
+ }
267
+ if (check.remediation) {
268
+ lines.push(ui.wrap(` fix: ${check.remediation}`, 6));
269
+ }
270
+ if (check.nextCommand) {
271
+ lines.push(` next: ${ui.command(check.nextCommand)}`);
272
+ }
273
+ return lines.join('\n');
274
+ }
275
+ function groupChecks(checks) {
276
+ const groups = new Map();
277
+ for (const check of checks) {
278
+ const existing = groups.get(check.group) ?? [];
279
+ existing.push(check);
280
+ groups.set(check.group, existing);
281
+ }
282
+ return [...groups.entries()];
283
+ }
284
+ function titleCase(value) {
285
+ return value
286
+ .split(/[-_\s]+/)
287
+ .filter(Boolean)
288
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
289
+ .join(' ');
290
+ }
291
+ function overviewSignals(record) {
292
+ if (!record)
293
+ return [];
294
+ const candidates = [
295
+ ['Conversations', valueAt(record, ['conversations', 'total']) ?? record.conversations],
296
+ ['Recent activity', Array.isArray(record.recent_activity) ? record.recent_activity.length : undefined],
297
+ ['Tool failures', valueAt(record, ['tool_failures', 'total']) ?? valueAt(record, ['toolFailures', 'total']) ?? record.tool_failures],
298
+ ['Frustrations', valueAt(record, ['frustrations', 'total']) ?? record.frustrations],
299
+ ['Runs', valueAt(record, ['runs', 'total']) ?? record.runs],
300
+ ['Workflows', valueAt(record, ['workflows', 'total']) ?? record.workflows],
301
+ ['Loops', valueAt(record, ['loops', 'active']) ?? valueAt(record, ['loops', 'total']) ?? record.loops],
302
+ ];
303
+ return candidates
304
+ .map(([label, value]) => [label, metricText(value)])
305
+ .filter((item) => item[1] !== undefined);
306
+ }
307
+ function recentActivity(record) {
308
+ const activity = Array.isArray(record?.recent_activity) ? record.recent_activity : [];
309
+ return activity.flatMap((item) => {
310
+ const value = asRecord(item);
311
+ if (!value)
312
+ return [];
313
+ const id = stringValue(value.conversation_id) ?? stringValue(value.run_id) ?? stringValue(value.id);
314
+ const timestamp = stringValue(value.timestamp) ?? stringValue(value.created_at);
315
+ const summary = stringValue(value.summary) ?? stringValue(value.title) ?? stringValue(value.event);
316
+ return [[summary, id, timestamp].filter(Boolean).join(' - ')];
317
+ });
318
+ }
319
+ function metricText(value) {
320
+ if (typeof value === 'number' && Number.isFinite(value))
321
+ return String(value);
322
+ if (typeof value === 'string' && value.trim())
323
+ return value;
324
+ if (Array.isArray(value))
325
+ return String(value.length);
326
+ if (isRecord(value)) {
327
+ const total = value.total ?? value.count ?? value.active;
328
+ if (typeof total === 'number' || typeof total === 'string')
329
+ return String(total);
330
+ }
331
+ return undefined;
332
+ }
333
+ function valueAt(record, path) {
334
+ let cursor = record;
335
+ for (const key of path) {
336
+ if (!isRecord(cursor))
337
+ return undefined;
338
+ cursor = cursor[key];
339
+ }
340
+ return cursor;
341
+ }
342
+ function numberOrZero(value) {
343
+ return typeof value === 'number' && Number.isFinite(value) ? String(value) : '0';
344
+ }
345
+ function asRecord(value) {
346
+ return isRecord(value) ? value : undefined;
347
+ }
348
+ function isRecord(value) {
349
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
350
+ }
351
+ function stringValue(value) {
352
+ return typeof value === 'string' && value.trim() ? value : undefined;
353
+ }
354
+ function createTerminalUi(options = {}) {
355
+ const width = Math.max(48, Math.min(options.width ?? process.stdout.columns ?? 96, 140));
356
+ const color = options.color ?? Boolean(process.stdout.isTTY && process.env.NO_COLOR !== '1');
357
+ const paint = (code, text) => color ? `\u001b[${code}m${text}\u001b[0m` : text;
358
+ return {
359
+ title: (text) => paint(1, text),
360
+ section: (text) => paint(36, text),
361
+ status: (status) => paint(STATUS_COLORS[status], `[${STATUS_LABELS[status]}]`),
362
+ command: (text) => paint(36, text),
363
+ dim: (text) => paint(90, text),
364
+ wrap: (text, indent) => wrapLine(text, width, indent),
365
+ };
366
+ }
367
+ function wrapLine(text, width, indent) {
368
+ if (text.length <= width)
369
+ return text;
370
+ const words = text.split(/\s+/);
371
+ const lines = [];
372
+ let current = '';
373
+ for (const word of words) {
374
+ const next = current ? `${current} ${word}` : word;
375
+ if (next.length > width && current) {
376
+ lines.push(current);
377
+ current = `${' '.repeat(indent)}${word}`;
378
+ }
379
+ else {
380
+ current = next;
381
+ }
382
+ }
383
+ if (current)
384
+ lines.push(current);
385
+ return lines.join('\n');
386
+ }
387
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1,12 @@
1
+ import type { ParsedArgs } from './cli.js';
2
+ import type { CommandContext } from './command-context.js';
3
+ interface WorkspaceOptions {
4
+ cwd?: string;
5
+ }
6
+ export declare function runWorkspaceCommand(args: ParsedArgs, options?: WorkspaceOptions): Promise<number>;
7
+ export declare function runSparkCommand(context: CommandContext): Promise<{
8
+ exitCode: number;
9
+ }>;
10
+ export declare function runHooksInstall(args: ParsedArgs): Promise<number>;
11
+ export {};
12
+ //# sourceMappingURL=workspace.d.ts.map