@moda-ai/cli 1.1.1 → 1.3.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 +745 -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 +1111 -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 +389 -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/README.md CHANGED
@@ -66,6 +66,14 @@ moda runs --class-id=cron:helpscout-triage-hourly # Scoped to one workflow class
66
66
  moda run <run_id> # One execution + its side-effect receipts
67
67
  moda loops # Currently-active loop detections
68
68
  moda loop <detection_id> # One loop detail (signature + blast radius)
69
+
70
+ # Prompt management
71
+ moda prompts init # Create .moda/prompts.yml
72
+ moda prompts status # Read-only local prompt status
73
+ moda prompts diff # Read-only local prompt diff/status
74
+ moda prompts sync # Upload changed prompt versions
75
+ moda prompts sync --watch # Sync when local prompt hashes change
76
+ moda prompts promote support.triage --label=prod --version=pver_abc123
69
77
  ```
70
78
 
71
79
  All commands output JSON. Pipe to `jq` for filtering:
@@ -78,6 +86,34 @@ moda loops | jq '.loops | sort_by(-.cycle_visits) | .[0:5] | .[].loop_signature_
78
86
  moda workflows | jq '.workflows | sort_by(.health) | .[] | select(.health < 0.8)'
79
87
  ```
80
88
 
89
+ ## Prompt Management
90
+
91
+ Prompt management is code-first. Keep prompts in `prompts/**/*.prompt.md` or
92
+ `prompts/**/*.prompt.json`, sync them with the CLI, and render them through the
93
+ SDK so LLM spans include prompt metadata.
94
+
95
+ ```bash
96
+ moda prompts init
97
+ moda prompts status
98
+ moda prompts sync
99
+ ```
100
+
101
+ `status` and `diff` are read-only. `sync` uploads changed versions and writes
102
+ `.moda/prompts.lock.json`. `promote` moves a remote `dev`, `staging`, or `prod`
103
+ label.
104
+
105
+ ```typescript
106
+ const rendered = Moda.prompt("support.triage").render({
107
+ ticket: { text: userMessage },
108
+ });
109
+ ```
110
+
111
+ ```python
112
+ rendered = moda.prompt("support.triage").render({
113
+ "ticket": {"text": user_message},
114
+ })
115
+ ```
116
+
81
117
  ## Anchors and message windows
82
118
 
83
119
  Rows returned by `moda frustrations` and `moda tool-failure-detail` carry
@@ -0,0 +1,5 @@
1
+ import type { CommandContext } from './command-context.js';
2
+ export declare function runAgentCommand(context: CommandContext): Promise<{
3
+ exitCode: number;
4
+ }>;
5
+ //# sourceMappingURL=agent.d.ts.map
package/dist/agent.js ADDED
@@ -0,0 +1,477 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { join, relative, resolve } from 'node:path';
3
+ import { resolveApiKey, resolveIngestUrl, resolveModaBaseUrl, resolveTenantId } from './config.js';
4
+ import { buildDoctorReport } from './doctor.js';
5
+ import { CliInputError } from './errors.js';
6
+ import { HARNESS_REPORT_APPROVAL_PATH, HARNESS_REPORT_PATH, hashHarnessReport, readHarnessReport, validateHarnessReport, } from './harness-report.js';
7
+ import { readHarnessSyncState, graphForSync, summarizeHarness, } from './harness.js';
8
+ import { hashStableJson } from './harness-report.js';
9
+ const MODA_DIR = '.moda';
10
+ const HARNESS_PATH = join(MODA_DIR, 'harness.json');
11
+ const HARNESS_REPORT_MARKDOWN_PATH = join(MODA_DIR, 'HARNESS_REPORT.md');
12
+ const HARNESS_SUMMARY_PATH = join(MODA_DIR, 'HARNESS.md');
13
+ const MAX_AGENT_EVIDENCE_BYTES = 24_000;
14
+ export async function runAgentCommand(context) {
15
+ const subcommand = context.positionals[0] ?? 'context';
16
+ switch (subcommand) {
17
+ case 'context': {
18
+ const loaded = await buildAgentContext(context);
19
+ context.output.writeData(loaded.payload, agentWriteOptions(loaded));
20
+ return { exitCode: 0 };
21
+ }
22
+ case 'investigate': {
23
+ const loaded = await buildAgentContext(context);
24
+ const question = context.positionals.slice(1).join(' ').trim() || context.flags.question;
25
+ context.output.writeData({
26
+ schemaVersion: 'agent_investigation.v0.1',
27
+ generatedAt: loaded.payload.generatedAt,
28
+ mode: 'local',
29
+ question: question || null,
30
+ status: loaded.payload.doctor.status,
31
+ findings: loaded.payload.doctor.topFindings,
32
+ recommendedActions: loaded.payload.recommendedActions,
33
+ evidenceRefs: loaded.payload.evidenceRefs,
34
+ limitations: [
35
+ ...loaded.payload.limitations,
36
+ 'Production investigation is not yet implemented in P08; use P09/cloud commands for production-rooted analysis once shipped.',
37
+ ],
38
+ }, agentWriteOptions(loaded, {
39
+ summary: summarizeAgentContext(loaded.payload, 'investigation'),
40
+ }));
41
+ return { exitCode: 0 };
42
+ }
43
+ case 'next-action':
44
+ case 'next-actions': {
45
+ const loaded = await buildAgentContext(context);
46
+ context.output.writeData({
47
+ schemaVersion: 'agent_next_actions.v0.1',
48
+ generatedAt: loaded.payload.generatedAt,
49
+ status: loaded.payload.doctor.status,
50
+ recommendedActions: loaded.payload.recommendedActions,
51
+ nextCommands: loaded.payload.nextCommands,
52
+ }, agentWriteOptions(loaded, {
53
+ summary: `${loaded.payload.recommendedActions.length} recommended action(s) are ready for an agent to evaluate.`,
54
+ }));
55
+ return { exitCode: 0 };
56
+ }
57
+ case 'evidence': {
58
+ const ref = context.positionals[1] ?? context.flags.id ?? context.flags.ref ?? context.flags.finding;
59
+ if (!ref) {
60
+ throw new CliInputError('<ref> is required', 'Usage: moda agent evidence <ref>');
61
+ }
62
+ const loaded = await buildAgentContext(context);
63
+ const evidence = expandEvidenceRef(ref, loaded, context.cwd);
64
+ context.output.writeData(evidence, {
65
+ summary: {
66
+ text: `Expanded evidence reference ${ref}.`,
67
+ confidence: 'high',
68
+ },
69
+ evidenceRefs: [evidence.ref],
70
+ artifacts: evidence.artifact ? [evidence.artifact] : [],
71
+ });
72
+ return { exitCode: 0 };
73
+ }
74
+ case 'ask': {
75
+ const loaded = await buildAgentContext(context);
76
+ const question = context.positionals.slice(1).join(' ').trim() || context.flags.question;
77
+ context.output.writeData({
78
+ schemaVersion: 'agent_ask.v0.1',
79
+ generatedAt: loaded.payload.generatedAt,
80
+ question: question || null,
81
+ answer: 'Moda CLI can provide local harness context today. Cloud intelligence for free-form production questions belongs to the future Moda Ask layer.',
82
+ recommendedActions: loaded.payload.recommendedActions,
83
+ nextCommands: loaded.payload.nextCommands,
84
+ evidenceRefs: loaded.payload.evidenceRefs,
85
+ }, agentWriteOptions(loaded, {
86
+ summary: 'Returned local Moda context and next commands for the requested agent question.',
87
+ }));
88
+ return { exitCode: 0 };
89
+ }
90
+ default:
91
+ throw new CliInputError(`Unknown agent subcommand '${subcommand}'`, 'Usage: moda agent context|investigate|next-action|evidence');
92
+ }
93
+ }
94
+ async function buildAgentContext(context) {
95
+ const doctor = await buildDoctorReport(context, {
96
+ offline: context.flags.online !== 'true',
97
+ });
98
+ const graph = readHarnessGraphIfPresent(context.cwd);
99
+ const reportState = readHarnessReportState(context.cwd);
100
+ const syncState = readHarnessSyncState(context.cwd);
101
+ const topFindings = doctor.checks
102
+ .filter((check) => check.status === 'fail' || check.status === 'warning')
103
+ .slice(0, 12)
104
+ .map(checkToFinding);
105
+ const evidenceRefs = [
106
+ ...doctor.checks.map(checkToEvidenceRef),
107
+ ...reportEvidenceRefs(reportState.report).slice(0, 60),
108
+ ];
109
+ const artifacts = existingArtifacts(context.cwd);
110
+ const recommendedActions = recommendedActionsForDoctor(doctor);
111
+ const nextCommands = nextCommandsForActions(recommendedActions, doctor.nextCommands);
112
+ const payload = {
113
+ schemaVersion: 'agent_context.v0.1',
114
+ generatedAt: new Date().toISOString(),
115
+ mode: 'local',
116
+ workspace: {
117
+ cwd: context.cwd,
118
+ profile: context.profile,
119
+ tenantId: resolveTenantId({ profile: context.profile, env: context.env }) ?? null,
120
+ apiKey: resolveApiKey({ profile: context.profile, env: context.env }) ? 'configured' : 'missing',
121
+ baseUrl: resolveModaBaseUrl({ profile: context.profile, env: context.env }),
122
+ ingestUrl: resolveIngestUrl({ profile: context.profile, env: context.env }),
123
+ },
124
+ doctor: {
125
+ status: doctor.summary.status,
126
+ summary: doctor.summary,
127
+ topFindings,
128
+ },
129
+ harness: {
130
+ manifest: {
131
+ path: HARNESS_PATH,
132
+ exists: graph !== undefined,
133
+ summary: graph ? summarizeHarness(graph) : null,
134
+ },
135
+ report: reportState.state,
136
+ sync: syncState
137
+ ? {
138
+ path: join(MODA_DIR, 'sync-state.json'),
139
+ exists: true,
140
+ source: syncState.source,
141
+ updatedAt: syncState.updatedAt,
142
+ stale: graph ? syncState.localGraphHash !== hashStableJson(graphForSync(graph)) : undefined,
143
+ remoteHarnessId: syncState.remote.harnessId,
144
+ remoteVersionId: syncState.remote.versionId,
145
+ }
146
+ : {
147
+ path: join(MODA_DIR, 'sync-state.json'),
148
+ exists: false,
149
+ },
150
+ },
151
+ recommendedActions,
152
+ evidenceRefs,
153
+ artifacts,
154
+ nextCommands,
155
+ limitations: [
156
+ 'This context is local and bounded. It does not upload source content or prove production behavior.',
157
+ 'Use evidence refs to expand claims instead of asking agents to scrape human output.',
158
+ ],
159
+ };
160
+ return {
161
+ payload,
162
+ doctor,
163
+ graph,
164
+ report: reportState.report,
165
+ reportValidation: reportState.validation,
166
+ reportApproval: reportState.approval,
167
+ syncState,
168
+ };
169
+ }
170
+ function agentWriteOptions(loaded, overrides = {}) {
171
+ return {
172
+ summary: overrides.summary ?? summarizeAgentContext(loaded.payload, 'context'),
173
+ findings: loaded.payload.doctor.topFindings,
174
+ evidenceRefs: loaded.payload.evidenceRefs,
175
+ recommendedActions: loaded.payload.recommendedActions,
176
+ nextCommands: loaded.payload.nextCommands,
177
+ artifacts: loaded.payload.artifacts,
178
+ warnings: [
179
+ ...(loaded.payload.harness.report.warnings ?? []),
180
+ ...loaded.payload.limitations,
181
+ ].slice(0, 40),
182
+ };
183
+ }
184
+ function summarizeAgentContext(payload, noun) {
185
+ const fail = payload.doctor.summary.fail;
186
+ const warning = payload.doctor.summary.warning;
187
+ const agents = payload.harness.manifest.summary?.runtimeAgentCount ?? 0;
188
+ if (fail > 0) {
189
+ return `Local Moda ${noun} found ${fail} failing check(s), ${warning} warning(s), and ${agents} runtime agent(s).`;
190
+ }
191
+ if (warning > 0) {
192
+ return `Local Moda ${noun} is usable with ${warning} warning(s) and ${agents} runtime agent(s).`;
193
+ }
194
+ return `Local Moda ${noun} is healthy with ${agents} runtime agent(s).`;
195
+ }
196
+ function readHarnessGraphIfPresent(cwd) {
197
+ const path = join(cwd, HARNESS_PATH);
198
+ if (!existsSync(path))
199
+ return undefined;
200
+ try {
201
+ return JSON.parse(readFileSync(path, 'utf8'));
202
+ }
203
+ catch {
204
+ return undefined;
205
+ }
206
+ }
207
+ function readHarnessReportState(cwd) {
208
+ const path = join(cwd, HARNESS_REPORT_PATH);
209
+ if (!existsSync(path)) {
210
+ return {
211
+ state: {
212
+ path: HARNESS_REPORT_PATH,
213
+ exists: false,
214
+ status: 'missing',
215
+ },
216
+ };
217
+ }
218
+ try {
219
+ const report = readHarnessReport(path);
220
+ const validation = validateHarnessReport(report, cwd);
221
+ const approval = readApproval(cwd);
222
+ const reportHash = hashHarnessReport(report);
223
+ return {
224
+ state: {
225
+ path: HARNESS_REPORT_PATH,
226
+ exists: true,
227
+ status: validation.status === 'pass' ? 'valid' : 'invalid',
228
+ reportId: report.reportId,
229
+ reportHash,
230
+ validationStatus: validation.status,
231
+ approved: approval?.approved === true && approval.reportHash === reportHash,
232
+ claimCount: report.claims.length,
233
+ citationCount: report.citations.length,
234
+ warnings: validation.warnings,
235
+ errors: validation.errors,
236
+ },
237
+ report,
238
+ validation,
239
+ approval,
240
+ };
241
+ }
242
+ catch (error) {
243
+ return {
244
+ state: {
245
+ path: HARNESS_REPORT_PATH,
246
+ exists: true,
247
+ status: 'invalid',
248
+ errors: [error instanceof Error ? error.message : String(error)],
249
+ },
250
+ };
251
+ }
252
+ }
253
+ function readApproval(cwd) {
254
+ const path = join(cwd, HARNESS_REPORT_APPROVAL_PATH);
255
+ if (!existsSync(path))
256
+ return undefined;
257
+ try {
258
+ return JSON.parse(readFileSync(path, 'utf8'));
259
+ }
260
+ catch {
261
+ return undefined;
262
+ }
263
+ }
264
+ function checkToFinding(check) {
265
+ return {
266
+ id: check.id,
267
+ title: check.title,
268
+ status: check.status,
269
+ severity: check.severity,
270
+ description: check.remediation ?? `Doctor check ${check.id} is ${check.status}.`,
271
+ evidence_ref_ids: [`doctor:${check.id}`],
272
+ };
273
+ }
274
+ function checkToEvidenceRef(check) {
275
+ return {
276
+ id: `doctor:${check.id}`,
277
+ kind: 'doctor_check',
278
+ label: check.title,
279
+ status: check.status,
280
+ };
281
+ }
282
+ function reportEvidenceRefs(report) {
283
+ if (!report)
284
+ return [];
285
+ return [
286
+ ...report.claims.map((claim) => ({
287
+ id: `harness-report:claim:${claim.id}`,
288
+ kind: 'harness_claim',
289
+ label: claim.title,
290
+ status: claim.inference,
291
+ })),
292
+ ...report.citations.map((citation) => ({
293
+ id: `harness-report:citation:${citation.id}`,
294
+ kind: citation.type,
295
+ label: citation.reason,
296
+ path: citation.path,
297
+ line: citation.line,
298
+ })),
299
+ ];
300
+ }
301
+ function recommendedActionsForDoctor(report) {
302
+ return report.fixPlan.slice(0, 12).map((item, index) => ({
303
+ id: `action_${index + 1}_${item.checkId}`,
304
+ title: item.action,
305
+ command: item.command,
306
+ rationale: `Doctor check ${item.checkId} is not passing.`,
307
+ mutability: item.command ? inferMutability(item.command) : 'unknown',
308
+ requires_approval: item.command ? commandRequiresApproval(item.command) : true,
309
+ }));
310
+ }
311
+ function nextCommandsForActions(actions, doctorNextCommands) {
312
+ const seen = new Set();
313
+ const commands = [
314
+ ...actions.map((action) => action.command).filter((command) => Boolean(command)),
315
+ ...doctorNextCommands,
316
+ ].filter((command) => {
317
+ if (seen.has(command))
318
+ return false;
319
+ seen.add(command);
320
+ return true;
321
+ });
322
+ return commands.slice(0, 12).map((command) => ({
323
+ command,
324
+ purpose: purposeForCommand(command),
325
+ mutability: inferMutability(command),
326
+ requires_approval: commandRequiresApproval(command),
327
+ }));
328
+ }
329
+ function existingArtifacts(cwd) {
330
+ return [
331
+ [HARNESS_PATH, 'harness_manifest', 'Local harness graph manifest.'],
332
+ [HARNESS_REPORT_PATH, 'harness_report', 'Cited harness analyst report.'],
333
+ [HARNESS_REPORT_MARKDOWN_PATH, 'harness_report_markdown', 'Human-readable harness analyst report.'],
334
+ [HARNESS_SUMMARY_PATH, 'harness_summary', 'Human-readable harness graph summary.'],
335
+ [join(MODA_DIR, 'sync-state.json'), 'sync_state', 'Last local harness sync state.'],
336
+ [join(MODA_DIR, 'prompts.yml'), 'prompt_manifest', 'Local prompt discovery manifest.'],
337
+ ].flatMap(([path, kind, description]) => existsSync(join(cwd, path))
338
+ ? [{ path, kind, description }]
339
+ : []);
340
+ }
341
+ function expandEvidenceRef(ref, loaded, cwd) {
342
+ if (ref.startsWith('doctor:')) {
343
+ const checkId = ref.slice('doctor:'.length);
344
+ const check = loaded.doctor.checks.find((candidate) => candidate.id === checkId);
345
+ if (!check)
346
+ throw missingEvidence(ref);
347
+ return {
348
+ schemaVersion: 'agent_evidence.v0.1',
349
+ ref: checkToEvidenceRef(check),
350
+ value: check,
351
+ };
352
+ }
353
+ if (ref.startsWith('harness-report:claim:')) {
354
+ const claimId = ref.slice('harness-report:claim:'.length);
355
+ const claim = loaded.report?.claims.find((candidate) => candidate.id === claimId);
356
+ if (!claim)
357
+ throw missingEvidence(ref);
358
+ return {
359
+ schemaVersion: 'agent_evidence.v0.1',
360
+ ref: claimToEvidenceRef(claim),
361
+ value: claim,
362
+ artifact: { kind: 'harness_report', path: HARNESS_REPORT_PATH },
363
+ };
364
+ }
365
+ if (ref.startsWith('harness-report:citation:')) {
366
+ const citationId = ref.slice('harness-report:citation:'.length);
367
+ const citation = loaded.report?.citations.find((candidate) => candidate.id === citationId);
368
+ if (!citation)
369
+ throw missingEvidence(ref);
370
+ return {
371
+ schemaVersion: 'agent_evidence.v0.1',
372
+ ref: citationToEvidenceRef(citation),
373
+ value: {
374
+ citation,
375
+ currentExcerpt: citation.path ? readCitationExcerpt(cwd, citation) : undefined,
376
+ },
377
+ artifact: citation.path ? { kind: 'citation_source', path: citation.path } : undefined,
378
+ };
379
+ }
380
+ if (ref.startsWith('artifact:')) {
381
+ const path = ref.slice('artifact:'.length);
382
+ const artifact = loaded.payload.artifacts.find((candidate) => candidate.path === path);
383
+ if (!artifact)
384
+ throw missingEvidence(ref);
385
+ return {
386
+ schemaVersion: 'agent_evidence.v0.1',
387
+ ref: {
388
+ id: ref,
389
+ kind: artifact.kind,
390
+ label: artifact.description ?? artifact.path,
391
+ path: artifact.path,
392
+ },
393
+ value: readBoundedModaArtifact(cwd, artifact.path),
394
+ artifact,
395
+ };
396
+ }
397
+ throw missingEvidence(ref);
398
+ }
399
+ function claimToEvidenceRef(claim) {
400
+ return {
401
+ id: `harness-report:claim:${claim.id}`,
402
+ kind: 'harness_claim',
403
+ label: claim.title,
404
+ status: claim.inference,
405
+ };
406
+ }
407
+ function citationToEvidenceRef(citation) {
408
+ return {
409
+ id: `harness-report:citation:${citation.id}`,
410
+ kind: citation.type,
411
+ label: citation.reason,
412
+ path: citation.path,
413
+ line: citation.line,
414
+ };
415
+ }
416
+ function readCitationExcerpt(cwd, citation) {
417
+ if (!citation.path || citation.type !== 'file' && citation.type !== 'generated_doc')
418
+ return citation.excerpt;
419
+ const absolute = safeResolve(cwd, citation.path);
420
+ if (!absolute || !existsSync(absolute))
421
+ return citation.excerpt;
422
+ const text = readFileSync(absolute, 'utf8');
423
+ if (citation.line && citation.line > 0) {
424
+ return text.split(/\r?\n/)[citation.line - 1]?.slice(0, 1_000) ?? citation.excerpt;
425
+ }
426
+ return text.slice(0, 1_000);
427
+ }
428
+ function readBoundedModaArtifact(cwd, artifactPath) {
429
+ if (!artifactPath.startsWith('.moda/')) {
430
+ throw new CliInputError(`Artifact '${artifactPath}' is outside .moda and cannot be expanded directly.`, 'Use a citation evidence ref for source-file evidence.');
431
+ }
432
+ const absolute = safeResolve(cwd, artifactPath);
433
+ if (!absolute || !existsSync(absolute))
434
+ throw missingEvidence(`artifact:${artifactPath}`);
435
+ const size = statSync(absolute).size;
436
+ const text = readFileSync(absolute, 'utf8');
437
+ return {
438
+ path: artifactPath,
439
+ bytes: size,
440
+ truncated: size > MAX_AGENT_EVIDENCE_BYTES,
441
+ text: text.slice(0, MAX_AGENT_EVIDENCE_BYTES),
442
+ };
443
+ }
444
+ function safeResolve(cwd, path) {
445
+ const absolute = resolve(cwd, path);
446
+ const rel = relative(cwd, absolute);
447
+ if (rel.startsWith('..') || rel === '..' || rel.startsWith('/'))
448
+ return undefined;
449
+ return absolute;
450
+ }
451
+ function missingEvidence(ref) {
452
+ return new CliInputError(`Evidence reference '${ref}' was not found.`, 'Run `moda agent context` to list available evidence_refs.');
453
+ }
454
+ function inferMutability(command) {
455
+ if (/\bsync\b/.test(command))
456
+ return 'sync';
457
+ if (/\b(init|install|approve|pull|gen|promote|remove)\b/.test(command))
458
+ return 'write';
459
+ return 'read';
460
+ }
461
+ function commandRequiresApproval(command) {
462
+ return inferMutability(command) !== 'read' && !command.includes('--dry-run');
463
+ }
464
+ function purposeForCommand(command) {
465
+ if (command.includes('harness approve'))
466
+ return 'Approve the current harness report after human review.';
467
+ if (command.includes('harness sync'))
468
+ return 'Sync approved harness state to Moda Cloud.';
469
+ if (command.includes('harness analyze'))
470
+ return 'Produce or refresh a cited harness report.';
471
+ if (command.includes('doctor'))
472
+ return 'Re-run local diagnostics.';
473
+ if (command.includes('init'))
474
+ return 'Complete local Moda onboarding.';
475
+ return 'Inspect or repair local Moda state.';
476
+ }
477
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1,81 @@
1
+ export type AnalystTuiValidationStatus = 'pending' | 'running' | 'pass' | 'fail';
2
+ export interface AnalystTuiEvent {
3
+ kind: 'agent_text' | 'rate_limit' | 'tool' | 'command' | 'system';
4
+ message: string;
5
+ toolName?: string;
6
+ }
7
+ export interface AnalystTuiSummary {
8
+ runtimeAgentCount?: number;
9
+ agentFamilyCount?: number;
10
+ artifactCount?: number;
11
+ modelProviderCount?: number;
12
+ unknownCount?: number;
13
+ claimCount?: number;
14
+ citationCount?: number;
15
+ relationshipCount?: number;
16
+ }
17
+ export interface AnalystTuiTimelineItem {
18
+ time: string;
19
+ label: string;
20
+ message: string;
21
+ }
22
+ export interface AnalystTuiState {
23
+ adapterName: string;
24
+ command: string;
25
+ root: string;
26
+ reportPath: string;
27
+ startedAt: number;
28
+ phase: string;
29
+ sawOutput: boolean;
30
+ lastOutputAt: number;
31
+ eventCount: number;
32
+ rateLimitCount: number;
33
+ validation: AnalystTuiValidationStatus;
34
+ artifacts: string[];
35
+ summary: AnalystTuiSummary;
36
+ timeline: AnalystTuiTimelineItem[];
37
+ toolCounts: Map<string, number>;
38
+ completed: boolean;
39
+ }
40
+ export interface AnalystTuiOptions {
41
+ adapterName: string;
42
+ command: string;
43
+ root: string;
44
+ reportPath: string;
45
+ startedAt?: number;
46
+ color?: boolean;
47
+ columns?: number;
48
+ rows?: number;
49
+ }
50
+ export interface AnalystTuiFrameOptions {
51
+ color?: boolean;
52
+ columns?: number;
53
+ rows?: number;
54
+ now?: number;
55
+ spinnerFrame?: string;
56
+ }
57
+ export declare class AnalystTui {
58
+ private state;
59
+ private active;
60
+ private closed;
61
+ private lastRenderAt;
62
+ private spinnerIndex;
63
+ constructor(options: AnalystTuiOptions);
64
+ start(): void;
65
+ recordEvent(event: AnalystTuiEvent): void;
66
+ heartbeat(options: {
67
+ elapsedSeconds: number;
68
+ quietForSeconds: number;
69
+ sawOutput: boolean;
70
+ }): void;
71
+ setValidation(status: AnalystTuiValidationStatus): void;
72
+ setSummary(summary: AnalystTuiSummary): void;
73
+ setArtifacts(artifacts: string[]): void;
74
+ complete(status: Extract<AnalystTuiValidationStatus, 'pass' | 'fail'>): void;
75
+ stop(): void;
76
+ private pushTimeline;
77
+ private render;
78
+ }
79
+ export declare function renderAnalystTuiFrame(state: AnalystTuiState, options?: AnalystTuiFrameOptions): string;
80
+ export declare function shouldUseAnalystTui(flags: Record<string, string>, outputMode: string): boolean;
81
+ //# sourceMappingURL=analyst-tui.d.ts.map