@traits-dev/cli 0.1.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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/traits.js +1501 -0
  4. package/package.json +54 -0
package/dist/traits.js ADDED
@@ -0,0 +1,1501 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin/traits.ts
4
+ import fs4 from "fs";
5
+ import path6 from "path";
6
+ import { fileURLToPath } from "url";
7
+
8
+ // src/commands/compile.ts
9
+ import path from "path";
10
+ import { compileProfile } from "@traits-dev/core";
11
+ import {
12
+ formatValidationResult,
13
+ toValidationResultObject
14
+ } from "@traits-dev/core/internal";
15
+ function printCompileUsage(out = process.stderr) {
16
+ out.write(
17
+ [
18
+ "Usage:",
19
+ " traits compile <profile-path> --model <model> [options]",
20
+ "",
21
+ "Options:",
22
+ " --model <model> Model target (required)",
23
+ " --json Output structured JSON",
24
+ " --strict Treat warnings as compile-blocking",
25
+ " --explain Include compilation trace output",
26
+ " --context key=value Activate context adaptation (repeatable)",
27
+ " --knowledge-base-dir Directory containing compiler pattern files",
28
+ " --bundled-profiles-dir Directory for bundled starter profiles",
29
+ " --verbose Include additional command metadata",
30
+ " --no-color Disable colorized output",
31
+ ""
32
+ ].join("\n")
33
+ );
34
+ }
35
+ function parseContextArg(value) {
36
+ const [key, rawValue] = String(value).split("=", 2);
37
+ const normalizedKey = String(key).trim();
38
+ if (!normalizedKey) return { error: `Invalid context value "${value}"` };
39
+ if (rawValue == null) return { key: normalizedKey, value: true };
40
+ const normalizedValue = String(rawValue).trim().toLowerCase();
41
+ if (normalizedValue === "true" || normalizedValue === "1") {
42
+ return { key: normalizedKey, value: true };
43
+ }
44
+ if (normalizedValue === "false" || normalizedValue === "0") {
45
+ return { key: normalizedKey, value: false };
46
+ }
47
+ return { key: normalizedKey, value: rawValue };
48
+ }
49
+ function parseCompileArgs(args) {
50
+ const result = {
51
+ profilePath: null,
52
+ model: null,
53
+ strict: false,
54
+ json: false,
55
+ explain: false,
56
+ verbose: false,
57
+ noColor: false,
58
+ knowledgeBaseDir: null,
59
+ bundledProfilesDir: null,
60
+ context: {}
61
+ };
62
+ const positionals = [];
63
+ for (let index = 0; index < args.length; index += 1) {
64
+ const arg = args[index];
65
+ if (arg === "--strict") {
66
+ result.strict = true;
67
+ continue;
68
+ }
69
+ if (arg === "--json") {
70
+ result.json = true;
71
+ continue;
72
+ }
73
+ if (arg === "--explain") {
74
+ result.explain = true;
75
+ continue;
76
+ }
77
+ if (arg === "--verbose") {
78
+ result.verbose = true;
79
+ continue;
80
+ }
81
+ if (arg === "--no-color") {
82
+ result.noColor = true;
83
+ continue;
84
+ }
85
+ if (arg === "--model" || arg === "--bundled-profiles-dir" || arg === "--context" || arg === "--knowledge-base-dir") {
86
+ const value = args[index + 1];
87
+ if (!value) return { error: `Missing value for "${arg}"` };
88
+ if (arg === "--model") {
89
+ result.model = value;
90
+ } else if (arg === "--bundled-profiles-dir") {
91
+ result.bundledProfilesDir = value;
92
+ } else if (arg === "--knowledge-base-dir") {
93
+ result.knowledgeBaseDir = value;
94
+ } else {
95
+ const parsedContext = parseContextArg(value);
96
+ if ("error" in parsedContext) return { error: parsedContext.error };
97
+ result.context[parsedContext.key] = parsedContext.value;
98
+ }
99
+ index += 1;
100
+ continue;
101
+ }
102
+ if (arg.startsWith("--")) {
103
+ return { error: `Unknown option "${arg}"` };
104
+ }
105
+ positionals.push(arg);
106
+ }
107
+ if (positionals.length !== 1) {
108
+ return { error: "Expected exactly one profile path argument" };
109
+ }
110
+ result.profilePath = positionals[0];
111
+ if (!result.model) {
112
+ return { error: 'Missing required option "--model"' };
113
+ }
114
+ return { value: result };
115
+ }
116
+ function runCompile(args, io = process) {
117
+ const parsed = parseCompileArgs(args);
118
+ if ("error" in parsed) {
119
+ io.stderr.write(`Error: ${parsed.error}
120
+
121
+ `);
122
+ printCompileUsage(io.stderr);
123
+ return 1;
124
+ }
125
+ const options = parsed.value;
126
+ if (!options.profilePath || !options.model) {
127
+ io.stderr.write("Error: Missing required arguments\n\n");
128
+ printCompileUsage(io.stderr);
129
+ return 1;
130
+ }
131
+ const profilePath = path.resolve(io.cwd(), options.profilePath);
132
+ const bundledProfilesDir = options.bundledProfilesDir ? path.resolve(io.cwd(), options.bundledProfilesDir) : path.resolve(io.cwd(), "profiles");
133
+ const knowledgeBaseDir = options.knowledgeBaseDir ? path.resolve(io.cwd(), options.knowledgeBaseDir) : path.resolve(io.cwd(), "knowledge-base");
134
+ if (options.verbose) {
135
+ io.stderr.write(`Compiling: ${profilePath}
136
+ `);
137
+ io.stderr.write(`Model: ${options.model}
138
+ `);
139
+ io.stderr.write(`Strict mode: ${options.strict ? "on" : "off"}
140
+ `);
141
+ }
142
+ try {
143
+ const compiled = compileProfile(profilePath, {
144
+ model: options.model,
145
+ strict: options.strict,
146
+ explain: options.explain,
147
+ context: options.context,
148
+ bundledProfilesDir,
149
+ knowledgeBaseDir
150
+ });
151
+ const warningCount = compiled.validation?.warnings?.length ?? 0;
152
+ if (warningCount > 0 && !options.json) {
153
+ io.stderr.write(`Validation warnings: ${warningCount}
154
+ `);
155
+ }
156
+ if (options.json) {
157
+ io.stdout.write(`${JSON.stringify(compiled, null, 2)}
158
+ `);
159
+ return 0;
160
+ }
161
+ io.stdout.write(`${compiled.text}
162
+ `);
163
+ if (options.explain && compiled.trace) {
164
+ io.stdout.write(`
165
+ [TRACE]
166
+ ${JSON.stringify(compiled.trace, null, 2)}
167
+ `);
168
+ }
169
+ return 0;
170
+ } catch (error) {
171
+ const typedError = error;
172
+ if (typedError.code === "E_COMPILE_VALIDATION" && typedError.validation) {
173
+ if (options.json) {
174
+ io.stdout.write(
175
+ `${JSON.stringify(
176
+ {
177
+ error: typedError.message,
178
+ code: typedError.code,
179
+ validation: toValidationResultObject(typedError.validation)
180
+ },
181
+ null,
182
+ 2
183
+ )}
184
+ `
185
+ );
186
+ } else {
187
+ io.stderr.write(`${formatValidationResult(typedError.validation)}
188
+ `);
189
+ }
190
+ return typedError.validation.exitCode ?? 2;
191
+ }
192
+ io.stderr.write(
193
+ `Error: ${error instanceof Error ? error.message : String(error)}
194
+ `
195
+ );
196
+ return 2;
197
+ }
198
+ }
199
+ function compileHelp(out = process.stdout) {
200
+ printCompileUsage(out);
201
+ }
202
+
203
+ // src/commands/eval.ts
204
+ import fs from "fs";
205
+ import path2 from "path";
206
+ import {
207
+ runTier1EvaluationForProfile,
208
+ runTier2EvaluationForProfile,
209
+ runTier3EvaluationForProfile
210
+ } from "@traits-dev/core";
211
+ import {
212
+ detectEvalTierAvailability,
213
+ formatValidationResult as formatValidationResult2,
214
+ resolveTierExecution,
215
+ runOfflineBaselineScaffold,
216
+ toValidationResultObject as toValidationResultObject2
217
+ } from "@traits-dev/core/internal";
218
+ function printEvalUsage(out = process.stderr) {
219
+ out.write(
220
+ [
221
+ "Usage:",
222
+ " traits eval <profile-path> --model <model> [options]",
223
+ "",
224
+ "Options:",
225
+ " --model <model> Model target (required)",
226
+ " --tier <1|2|3> Highest tier to run (default: highest available)",
227
+ " --provider <name> Judge provider for Tier 3: auto|openai|anthropic",
228
+ " --embedding-model <name> Embedding model for Tier 2 (OpenAI)",
229
+ " --judge-model <name> Judge model for Tier 3 provider",
230
+ " --openai-base-url <url> Override OpenAI API base URL",
231
+ " --anthropic-base-url <url> Override Anthropic API base URL",
232
+ " --timeout-ms <ms> Provider request timeout (default: 20000)",
233
+ " --max-retries <count> Provider retry attempts (default: 2)",
234
+ " --retry-base-ms <ms> Base backoff delay (default: 250)",
235
+ " --response <text> Assistant response sample (repeatable)",
236
+ " --samples <path> JSON file with samples: [{ id, response }]",
237
+ " --scenarios <path> Alias for --samples in this scaffold",
238
+ " --json Output structured JSON",
239
+ " --strict Treat validation warnings as errors",
240
+ " --verbose Include command metadata output",
241
+ " --no-color Disable colorized output",
242
+ " --no-baselines Skip offline baseline scaffold comparison",
243
+ " --no-helpfulness Skip helpfulness checks in scoring",
244
+ " --constraint-impact Reserved flag (accepted, no-op in scaffold)",
245
+ ""
246
+ ].join("\n")
247
+ );
248
+ }
249
+ function parseEvalArgs(args) {
250
+ const result = {
251
+ profilePath: null,
252
+ model: null,
253
+ tier: null,
254
+ provider: "auto",
255
+ embeddingModel: null,
256
+ judgeModel: null,
257
+ openaiBaseUrl: null,
258
+ anthropicBaseUrl: null,
259
+ timeoutMs: null,
260
+ maxRetries: null,
261
+ retryBaseMs: null,
262
+ json: false,
263
+ strict: false,
264
+ verbose: false,
265
+ noColor: false,
266
+ responses: [],
267
+ samplesPath: null,
268
+ noBaselines: false,
269
+ noHelpfulness: false,
270
+ constraintImpact: false
271
+ };
272
+ const positionals = [];
273
+ for (let index = 0; index < args.length; index += 1) {
274
+ const arg = args[index];
275
+ if (arg === "--json") {
276
+ result.json = true;
277
+ continue;
278
+ }
279
+ if (arg === "--strict") {
280
+ result.strict = true;
281
+ continue;
282
+ }
283
+ if (arg === "--verbose") {
284
+ result.verbose = true;
285
+ continue;
286
+ }
287
+ if (arg === "--no-color") {
288
+ result.noColor = true;
289
+ continue;
290
+ }
291
+ if (arg === "--no-baselines") {
292
+ result.noBaselines = true;
293
+ continue;
294
+ }
295
+ if (arg === "--no-helpfulness") {
296
+ result.noHelpfulness = true;
297
+ continue;
298
+ }
299
+ if (arg === "--constraint-impact") {
300
+ result.constraintImpact = true;
301
+ continue;
302
+ }
303
+ if (arg === "--model" || arg === "--tier" || arg === "--provider" || arg === "--embedding-model" || arg === "--judge-model" || arg === "--openai-base-url" || arg === "--anthropic-base-url" || arg === "--timeout-ms" || arg === "--max-retries" || arg === "--retry-base-ms" || arg === "--response" || arg === "--samples" || arg === "--scenarios") {
304
+ const value = args[index + 1];
305
+ if (!value) return { error: `Missing value for "${arg}"` };
306
+ if (arg === "--model") result.model = value;
307
+ if (arg === "--tier") result.tier = Number(value);
308
+ if (arg === "--provider") {
309
+ result.provider = String(value).toLowerCase();
310
+ }
311
+ if (arg === "--embedding-model") result.embeddingModel = value;
312
+ if (arg === "--judge-model") result.judgeModel = value;
313
+ if (arg === "--openai-base-url") result.openaiBaseUrl = value;
314
+ if (arg === "--anthropic-base-url") result.anthropicBaseUrl = value;
315
+ if (arg === "--timeout-ms") result.timeoutMs = Number(value);
316
+ if (arg === "--max-retries") result.maxRetries = Number(value);
317
+ if (arg === "--retry-base-ms") result.retryBaseMs = Number(value);
318
+ if (arg === "--response") result.responses.push(value);
319
+ if (arg === "--samples" || arg === "--scenarios") result.samplesPath = value;
320
+ index += 1;
321
+ continue;
322
+ }
323
+ if (arg.startsWith("--")) {
324
+ return { error: `Unknown option "${arg}"` };
325
+ }
326
+ positionals.push(arg);
327
+ }
328
+ if (positionals.length !== 1) {
329
+ return { error: "Expected exactly one profile path argument" };
330
+ }
331
+ result.profilePath = positionals[0];
332
+ if (!result.model) {
333
+ return { error: 'Missing required option "--model"' };
334
+ }
335
+ if (result.tier != null && ![1, 2, 3].includes(result.tier)) {
336
+ return { error: 'Invalid "--tier" value. Expected 1, 2, or 3.' };
337
+ }
338
+ if (!["auto", "openai", "anthropic"].includes(result.provider)) {
339
+ return { error: 'Invalid "--provider" value. Expected auto, openai, or anthropic.' };
340
+ }
341
+ if (result.timeoutMs != null && (!Number.isInteger(result.timeoutMs) || result.timeoutMs < 0)) {
342
+ return { error: 'Invalid "--timeout-ms" value. Expected a non-negative integer.' };
343
+ }
344
+ if (result.maxRetries != null && (!Number.isInteger(result.maxRetries) || result.maxRetries < 0)) {
345
+ return { error: 'Invalid "--max-retries" value. Expected a non-negative integer.' };
346
+ }
347
+ if (result.retryBaseMs != null && (!Number.isInteger(result.retryBaseMs) || result.retryBaseMs < 0)) {
348
+ return { error: 'Invalid "--retry-base-ms" value. Expected a non-negative integer.' };
349
+ }
350
+ return { value: result };
351
+ }
352
+ function loadSamples(options, cwd) {
353
+ if (options.samplesPath) {
354
+ const sampleFile = path2.resolve(cwd, options.samplesPath);
355
+ const parsed = JSON.parse(fs.readFileSync(sampleFile, "utf8"));
356
+ if (!Array.isArray(parsed)) {
357
+ throw new Error("Sample file must be a JSON array");
358
+ }
359
+ return parsed.map((item, index) => {
360
+ if (typeof item === "string") {
361
+ return { id: `sample-${index + 1}`, response: item };
362
+ }
363
+ if (!item || typeof item !== "object") {
364
+ return { id: `sample-${index + 1}`, response: "" };
365
+ }
366
+ const sample = item;
367
+ return {
368
+ id: sample.id != null ? String(sample.id) : `sample-${index + 1}`,
369
+ response: String(sample.response ?? ""),
370
+ prompt: sample.prompt != null ? String(sample.prompt) : void 0
371
+ };
372
+ });
373
+ }
374
+ return options.responses.map((response, index) => ({
375
+ id: `sample-${index + 1}`,
376
+ response
377
+ }));
378
+ }
379
+ function writeProgress(io, options, message) {
380
+ if (options.json) return;
381
+ io.stderr.write(`${message}
382
+ `);
383
+ }
384
+ async function runEval(args, io = process) {
385
+ const parsed = parseEvalArgs(args);
386
+ if ("error" in parsed) {
387
+ io.stderr.write(`Error: ${parsed.error}
388
+
389
+ `);
390
+ printEvalUsage(io.stderr);
391
+ return 1;
392
+ }
393
+ const options = parsed.value;
394
+ if (!options.profilePath || !options.model) {
395
+ io.stderr.write("Error: Missing required arguments\n\n");
396
+ printEvalUsage(io.stderr);
397
+ return 1;
398
+ }
399
+ const profilePath = path2.resolve(io.cwd(), options.profilePath);
400
+ const bundledProfilesDir = path2.resolve(io.cwd(), "profiles");
401
+ const knowledgeBaseDir = path2.resolve(io.cwd(), "knowledge-base");
402
+ let samples;
403
+ try {
404
+ samples = loadSamples(options, io.cwd());
405
+ } catch (error) {
406
+ io.stderr.write(
407
+ `Error loading samples: ${error instanceof Error ? error.message : String(error)}
408
+ `
409
+ );
410
+ return 1;
411
+ }
412
+ if (samples.length === 0) {
413
+ io.stderr.write("Error: Provide at least one --response or a --samples file.\n");
414
+ return 1;
415
+ }
416
+ if (options.verbose) {
417
+ io.stderr.write(`Eval profile: ${profilePath}
418
+ `);
419
+ io.stderr.write(`Model: ${options.model}
420
+ `);
421
+ io.stderr.write(
422
+ `Requested tier: ${options.tier != null ? options.tier : "auto-highest"}
423
+ `
424
+ );
425
+ io.stderr.write(`Provider preference: ${options.provider}
426
+ `);
427
+ if (options.embeddingModel) {
428
+ io.stderr.write(`Embedding model: ${options.embeddingModel}
429
+ `);
430
+ }
431
+ if (options.judgeModel) {
432
+ io.stderr.write(`Judge model: ${options.judgeModel}
433
+ `);
434
+ }
435
+ }
436
+ const availability = detectEvalTierAvailability(process.env, {
437
+ provider: options.provider
438
+ });
439
+ const autoRequestedTier = [3, 2, 1].find(
440
+ (tier) => availability?.[tier]?.available && availability?.[tier]?.implemented
441
+ ) ?? 1;
442
+ const requestedTier = options.tier ?? autoRequestedTier;
443
+ const tierResolution = resolveTierExecution(requestedTier, availability);
444
+ if (tierResolution.blocked.length > 0 && !options.json) {
445
+ for (const blocked of tierResolution.blocked) {
446
+ io.stderr.write(`Tier ${blocked.tier} unavailable: ${blocked.reason}
447
+ `);
448
+ }
449
+ }
450
+ try {
451
+ const tierReports = {};
452
+ let baselineReport = null;
453
+ let evaluation = null;
454
+ if (tierResolution.tiers_run.includes(1)) {
455
+ writeProgress(io, options, "Running Tier 1 checks...");
456
+ evaluation = runTier1EvaluationForProfile(profilePath, samples, {
457
+ strict: options.strict,
458
+ bundledProfilesDir,
459
+ includeHelpfulness: !options.noHelpfulness
460
+ });
461
+ tierReports.tier1 = evaluation.report;
462
+ writeProgress(io, options, "Tier 1 complete.");
463
+ }
464
+ if (tierResolution.tiers_run.includes(2)) {
465
+ writeProgress(io, options, "Running Tier 2 checks...");
466
+ const tier2 = await runTier2EvaluationForProfile(profilePath, samples, {
467
+ strict: options.strict,
468
+ bundledProfilesDir,
469
+ knowledgeBaseDir,
470
+ modelTarget: options.model,
471
+ openaiApiKey: process.env.TRAITS_OPENAI_API_KEY,
472
+ embeddingModel: options.embeddingModel ?? void 0,
473
+ openaiBaseUrl: options.openaiBaseUrl ?? void 0,
474
+ includeHelpfulness: !options.noHelpfulness,
475
+ fetchTimeoutMs: options.timeoutMs ?? void 0,
476
+ fetchMaxRetries: options.maxRetries ?? void 0,
477
+ fetchRetryBaseMs: options.retryBaseMs ?? void 0
478
+ });
479
+ tierReports.tier2 = tier2.report;
480
+ writeProgress(io, options, "Tier 2 complete.");
481
+ }
482
+ if (tierResolution.tiers_run.includes(3)) {
483
+ writeProgress(io, options, "Running Tier 3 checks...");
484
+ const tier3 = await runTier3EvaluationForProfile(profilePath, samples, {
485
+ strict: options.strict,
486
+ bundledProfilesDir,
487
+ provider: options.provider,
488
+ judgeModel: options.judgeModel ?? void 0,
489
+ openaiApiKey: process.env.TRAITS_OPENAI_API_KEY,
490
+ anthropicApiKey: process.env.TRAITS_ANTHROPIC_API_KEY,
491
+ openaiBaseUrl: options.openaiBaseUrl ?? void 0,
492
+ anthropicBaseUrl: options.anthropicBaseUrl ?? void 0,
493
+ includeHelpfulness: !options.noHelpfulness,
494
+ fetchTimeoutMs: options.timeoutMs ?? void 0,
495
+ fetchMaxRetries: options.maxRetries ?? void 0,
496
+ fetchRetryBaseMs: options.retryBaseMs ?? void 0
497
+ });
498
+ tierReports.tier3 = tier3.report;
499
+ writeProgress(io, options, "Tier 3 complete.");
500
+ }
501
+ if (!options.noBaselines && evaluation?.validation?.profile) {
502
+ writeProgress(io, options, "Running offline baseline scaffold...");
503
+ baselineReport = runOfflineBaselineScaffold(evaluation.validation.profile, samples, {
504
+ includeHelpfulness: !options.noHelpfulness,
505
+ compiledTier1Report: tierReports.tier1
506
+ });
507
+ writeProgress(io, options, "Offline baseline scaffold complete.");
508
+ }
509
+ const allScores = [
510
+ tierReports.tier1?.average_score,
511
+ tierReports.tier2?.average_score,
512
+ tierReports.tier3?.average_score
513
+ ].filter((score) => Number.isFinite(score));
514
+ const overallScore = allScores.length > 0 ? allScores.reduce((sum, value) => sum + value, 0) / allScores.length : 0;
515
+ const payload = {
516
+ profile: profilePath,
517
+ model: options.model,
518
+ tier_requested: requestedTier,
519
+ tier_executed: tierResolution.tier_executed,
520
+ tier_resolution: tierResolution,
521
+ tier_availability: availability,
522
+ report: {
523
+ overall_score: overallScore,
524
+ ...tierReports,
525
+ ...baselineReport ? { baselines: baselineReport } : {}
526
+ }
527
+ };
528
+ if (evaluation?.validation) {
529
+ payload.validation = {
530
+ warnings: evaluation.validation.warnings.length,
531
+ errors: evaluation.validation.errors.length
532
+ };
533
+ }
534
+ if (options.json) {
535
+ io.stdout.write(`${JSON.stringify(payload, null, 2)}
536
+ `);
537
+ return 0;
538
+ }
539
+ if (tierReports.tier1) {
540
+ io.stdout.write(`Tier 1 average score: ${tierReports.tier1.average_score.toFixed(3)}
541
+ `);
542
+ for (const sample of tierReports.tier1.samples) {
543
+ io.stdout.write(`- ${sample.id}: ${sample.score.toFixed(3)}
544
+ `);
545
+ }
546
+ }
547
+ if (tierReports.tier2) {
548
+ io.stdout.write(`Tier 2 average score: ${tierReports.tier2.average_score.toFixed(3)}
549
+ `);
550
+ }
551
+ if (tierReports.tier3) {
552
+ io.stdout.write(`Tier 3 average score: ${tierReports.tier3.average_score.toFixed(3)}
553
+ `);
554
+ }
555
+ if (baselineReport?.tier1) {
556
+ io.stdout.write(
557
+ `Baseline (none) Tier 1 avg: ${baselineReport.tier1.none.average_score.toFixed(3)}
558
+ `
559
+ );
560
+ io.stdout.write(
561
+ `Baseline (basic) Tier 1 avg: ${baselineReport.tier1.basic.average_score.toFixed(3)}
562
+ `
563
+ );
564
+ const deltas = baselineReport.tier1.deltas ?? {};
565
+ const compiledVsNone = deltas.compiled_vs_none;
566
+ if (typeof compiledVsNone === "number" && Number.isFinite(compiledVsNone)) {
567
+ io.stdout.write(`Delta vs none baseline: ${compiledVsNone.toFixed(3)}
568
+ `);
569
+ }
570
+ const compiledVsBasic = deltas.compiled_vs_basic;
571
+ if (typeof compiledVsBasic === "number" && Number.isFinite(compiledVsBasic)) {
572
+ io.stdout.write(`Delta vs basic baseline: ${compiledVsBasic.toFixed(3)}
573
+ `);
574
+ }
575
+ }
576
+ io.stdout.write(`Overall eval score: ${overallScore.toFixed(3)}
577
+ `);
578
+ return 0;
579
+ } catch (error) {
580
+ const typedError = error;
581
+ if ((typedError.code === "E_EVAL_TIER2_UNAVAILABLE" || typedError.code === "E_EVAL_TIER3_UNAVAILABLE") && !options.json) {
582
+ io.stderr.write(`Error: ${typedError.message ?? "Evaluation tier unavailable."}
583
+ `);
584
+ return 2;
585
+ }
586
+ if ((typedError.code === "E_EVAL_TIER2_UNAVAILABLE" || typedError.code === "E_EVAL_TIER3_UNAVAILABLE") && options.json) {
587
+ io.stdout.write(
588
+ `${JSON.stringify(
589
+ {
590
+ error: typedError.message,
591
+ code: typedError.code
592
+ },
593
+ null,
594
+ 2
595
+ )}
596
+ `
597
+ );
598
+ return 2;
599
+ }
600
+ const validation = typedError.validation;
601
+ if (typedError.code === "E_EVAL_VALIDATION" && validation) {
602
+ if (options.json) {
603
+ io.stdout.write(
604
+ `${JSON.stringify(
605
+ {
606
+ error: typedError.message,
607
+ code: typedError.code,
608
+ validation: toValidationResultObject2(validation)
609
+ },
610
+ null,
611
+ 2
612
+ )}
613
+ `
614
+ );
615
+ } else {
616
+ io.stderr.write(`${formatValidationResult2(validation)}
617
+ `);
618
+ }
619
+ return validation.exitCode ?? 2;
620
+ }
621
+ io.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
622
+ `);
623
+ return 2;
624
+ }
625
+ }
626
+ function evalHelp(out = process.stdout) {
627
+ printEvalUsage(out);
628
+ }
629
+
630
+ // src/commands/init.ts
631
+ import fs2 from "fs";
632
+ import os from "os";
633
+ import path3 from "path";
634
+ import readline from "readline/promises";
635
+ import { validateProfile } from "@traits-dev/core";
636
+ var TONE_PRESETS = {
637
+ balanced: {
638
+ formality: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
639
+ warmth: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
640
+ verbosity: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
641
+ directness: "medium",
642
+ empathy: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
643
+ humor: { target: "very-low", style: "none" }
644
+ },
645
+ warm: {
646
+ formality: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
647
+ warmth: { target: "high", adapt: true, floor: "medium", ceiling: "very-high" },
648
+ verbosity: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
649
+ directness: "medium",
650
+ empathy: { target: "high", adapt: true, floor: "medium", ceiling: "very-high" },
651
+ humor: { target: "very-low", style: "none" }
652
+ },
653
+ direct: {
654
+ formality: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
655
+ warmth: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
656
+ verbosity: { target: "low", adapt: true, floor: "low", ceiling: "medium" },
657
+ directness: "high",
658
+ empathy: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
659
+ humor: { target: "very-low", style: "none" }
660
+ },
661
+ formal: {
662
+ formality: { target: "high", adapt: true, floor: "medium", ceiling: "very-high" },
663
+ warmth: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
664
+ verbosity: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
665
+ directness: "medium",
666
+ empathy: { target: "medium", adapt: true, floor: "low", ceiling: "high" },
667
+ humor: { target: "very-low", style: "none" }
668
+ }
669
+ };
670
+ function printInitUsage(out = process.stderr) {
671
+ out.write(
672
+ [
673
+ "Usage:",
674
+ " traits init [output-path] [options]",
675
+ "",
676
+ "Options:",
677
+ " --name <name> Profile name",
678
+ " --domain <domain> Domain or vertical",
679
+ " --model <model> Primary model target metadata (tag only)",
680
+ " --tone <tone> Tone preset: balanced|warm|direct|formal",
681
+ " --template <profile> Start from a starter profile (e.g., resolve)",
682
+ " --verbose Include additional command metadata",
683
+ " --no-color Disable colorized output",
684
+ " --force Overwrite output file if it exists",
685
+ ""
686
+ ].join("\n")
687
+ );
688
+ }
689
+ function slugify(value) {
690
+ return String(value).trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
691
+ }
692
+ function toTitleWords(value) {
693
+ return String(value).split(/[-_\s]+/g).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
694
+ }
695
+ function renderDimensionBlock(key, value) {
696
+ if (typeof value === "string") {
697
+ return [` ${key}: "${value}"`];
698
+ }
699
+ const lines = [` ${key}:`];
700
+ lines.push(` target: "${value.target}"`);
701
+ if (value.adapt != null) lines.push(` adapt: ${value.adapt ? "true" : "false"}`);
702
+ if (value.floor != null) lines.push(` floor: "${value.floor}"`);
703
+ if (value.ceiling != null) lines.push(` ceiling: "${value.ceiling}"`);
704
+ if (value.style != null) lines.push(` style: "${value.style}"`);
705
+ return lines;
706
+ }
707
+ function renderScaffold({
708
+ name,
709
+ domain,
710
+ model,
711
+ tone
712
+ }) {
713
+ const domainLabel = String(domain).trim();
714
+ const preset = TONE_PRESETS[tone] ?? TONE_PRESETS.balanced;
715
+ const role = `${toTitleWords(domainLabel)} assistant`;
716
+ const expertise = `${toTitleWords(domainLabel)} workflows and support`;
717
+ return [
718
+ "# traits.dev personality scaffold",
719
+ "# Edit fields to match your product and voice requirements.",
720
+ 'schema: "v1.4"',
721
+ "",
722
+ "meta:",
723
+ ` name: "${name}"`,
724
+ ' version: "1.0.0"',
725
+ ` description: "${toTitleWords(domainLabel)} assistant profile scaffold"`,
726
+ " tags:",
727
+ ` - "${slugify(domainLabel) || "general"}"`,
728
+ ` - "model:${model}"`,
729
+ ' target_audience: "Users who need reliable help"',
730
+ "",
731
+ "identity:",
732
+ ` role: "${role}"`,
733
+ " backstory: >",
734
+ " You are a practical, trustworthy assistant that helps users",
735
+ " complete tasks with clear next steps and explicit boundaries.",
736
+ " expertise_domains:",
737
+ ` - "${expertise}"`,
738
+ "",
739
+ "voice:",
740
+ " # Levels: very-low | low | medium | high | very-high",
741
+ ...renderDimensionBlock("formality", preset.formality),
742
+ ...renderDimensionBlock("warmth", preset.warmth),
743
+ ...renderDimensionBlock("verbosity", preset.verbosity),
744
+ ...renderDimensionBlock("directness", preset.directness),
745
+ ...renderDimensionBlock("empathy", preset.empathy),
746
+ ...renderDimensionBlock("humor", preset.humor),
747
+ "",
748
+ "vocabulary:",
749
+ " preferred_terms:",
750
+ ` - "Here's what I can do"`,
751
+ " forbidden_terms:",
752
+ ' - "always"',
753
+ "",
754
+ "behavioral_rules:",
755
+ ' - "Answer with concrete steps before optional background details"',
756
+ ' - "State uncertainty explicitly when information is incomplete"',
757
+ "",
758
+ "context_adaptations:",
759
+ ' - when: "frustrated_user"',
760
+ " adjustments:",
761
+ " warmth:",
762
+ ' target: "high"',
763
+ " adapt: false",
764
+ " inject:",
765
+ ' - "Acknowledge frustration first, then provide one clear next step"',
766
+ "",
767
+ "# Reserved sections for future use:",
768
+ "# localization: {}",
769
+ "# channel_adaptations: {}",
770
+ ""
771
+ ].join("\n");
772
+ }
773
+ function parseInitArgs(args) {
774
+ const result = {
775
+ outputPath: null,
776
+ name: null,
777
+ domain: null,
778
+ model: null,
779
+ tone: null,
780
+ template: null,
781
+ verbose: false,
782
+ noColor: false,
783
+ force: false
784
+ };
785
+ const positionals = [];
786
+ for (let index = 0; index < args.length; index += 1) {
787
+ const arg = args[index];
788
+ if (arg === "--force") {
789
+ result.force = true;
790
+ continue;
791
+ }
792
+ if (arg === "--verbose") {
793
+ result.verbose = true;
794
+ continue;
795
+ }
796
+ if (arg === "--no-color") {
797
+ result.noColor = true;
798
+ continue;
799
+ }
800
+ if (arg === "--name" || arg === "--domain" || arg === "--model" || arg === "--tone" || arg === "--template") {
801
+ const value = args[index + 1];
802
+ if (!value) return { error: `Missing value for "${arg}"` };
803
+ if (arg === "--name") result.name = value;
804
+ if (arg === "--domain") result.domain = value;
805
+ if (arg === "--model") result.model = value;
806
+ if (arg === "--tone") result.tone = value;
807
+ if (arg === "--template") result.template = value;
808
+ index += 1;
809
+ continue;
810
+ }
811
+ if (arg.startsWith("--")) {
812
+ return { error: `Unknown option "${arg}"` };
813
+ }
814
+ positionals.push(arg);
815
+ }
816
+ if (positionals.length > 1) {
817
+ return { error: "Expected at most one output path argument" };
818
+ }
819
+ result.outputPath = positionals[0] ?? null;
820
+ if (result.tone && !Object.hasOwn(TONE_PRESETS, result.tone)) {
821
+ return {
822
+ error: `Invalid tone "${result.tone}". Expected one of: ${Object.keys(TONE_PRESETS).join(", ")}`
823
+ };
824
+ }
825
+ return { value: result };
826
+ }
827
+ function resolveTemplatePath(template, cwd) {
828
+ const candidateValues = [template];
829
+ if (!template.endsWith(".yaml")) {
830
+ candidateValues.push(`${template}.yaml`);
831
+ }
832
+ for (const candidate of candidateValues) {
833
+ const absoluteCandidate = path3.isAbsolute(candidate) ? candidate : path3.resolve(cwd, candidate);
834
+ if (fs2.existsSync(absoluteCandidate)) return absoluteCandidate;
835
+ const starterCandidate = path3.resolve(cwd, "profiles", candidate);
836
+ if (fs2.existsSync(starterCandidate)) return starterCandidate;
837
+ }
838
+ return null;
839
+ }
840
+ async function promptIfMissing(config, io) {
841
+ const out = { ...config };
842
+ const stdout = io.stdout;
843
+ if (!io.stdin.isTTY || !stdout.isTTY) {
844
+ out.name = out.name ?? "my-profile";
845
+ out.domain = out.domain ?? "general-assistant";
846
+ out.model = out.model ?? "claude-sonnet";
847
+ out.tone = out.tone ?? "balanced";
848
+ return out;
849
+ }
850
+ const rl = readline.createInterface({ input: io.stdin, output: stdout });
851
+ try {
852
+ const nameInput = out.name ?? await rl.question("Profile name (my-profile): ");
853
+ out.name = nameInput.trim() || "my-profile";
854
+ const domainInput = out.domain ?? await rl.question("Domain or vertical (general-assistant): ");
855
+ out.domain = domainInput.trim() || "general-assistant";
856
+ const modelInput = out.model ?? await rl.question("Model target tag (claude-sonnet): ");
857
+ out.model = modelInput.trim() || "claude-sonnet";
858
+ const toneInput = out.tone ?? await rl.question("Tone preset [balanced|warm|direct|formal] (balanced): ");
859
+ out.tone = toneInput.trim() || "balanced";
860
+ if (!Object.hasOwn(TONE_PRESETS, out.tone)) {
861
+ out.tone = "balanced";
862
+ }
863
+ } finally {
864
+ rl.close();
865
+ }
866
+ return out;
867
+ }
868
+ function resolveOutputPath(cwd, options) {
869
+ if (options.outputPath) return path3.resolve(cwd, options.outputPath);
870
+ if (options.template) {
871
+ const base = slugify(path3.basename(options.template, path3.extname(options.template)));
872
+ return path3.resolve(cwd, `${base || "profile"}-custom.yaml`);
873
+ }
874
+ return path3.resolve(cwd, `${slugify(options.name ?? "") || "profile"}.yaml`);
875
+ }
876
+ function ensureWritableTarget(filePath, force) {
877
+ if (!fs2.existsSync(filePath)) return { ok: true };
878
+ if (force) return { ok: true };
879
+ return {
880
+ ok: false,
881
+ message: `File already exists: ${filePath}. Use --force to overwrite.`
882
+ };
883
+ }
884
+ function writeFileAtomic(filePath, contents) {
885
+ fs2.mkdirSync(path3.dirname(filePath), { recursive: true });
886
+ const tempFile = path3.join(
887
+ path3.dirname(filePath),
888
+ `.${path3.basename(filePath)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
889
+ );
890
+ fs2.writeFileSync(tempFile, contents, "utf8");
891
+ fs2.renameSync(tempFile, filePath);
892
+ }
893
+ async function runInit(args, io = process) {
894
+ const parsed = parseInitArgs(args);
895
+ if ("error" in parsed) {
896
+ io.stderr.write(`Error: ${parsed.error}
897
+
898
+ `);
899
+ printInitUsage(io.stderr);
900
+ return 1;
901
+ }
902
+ let options = parsed.value;
903
+ const cwd = io.cwd();
904
+ if (options.template) {
905
+ const templatePath = resolveTemplatePath(options.template, cwd);
906
+ if (!templatePath) {
907
+ io.stderr.write(`Error: Unable to resolve template "${options.template}"
908
+ `);
909
+ return 1;
910
+ }
911
+ const outputPath2 = resolveOutputPath(cwd, options);
912
+ const writableCheck2 = ensureWritableTarget(outputPath2, options.force);
913
+ if (!writableCheck2.ok) {
914
+ io.stderr.write(`Error: ${writableCheck2.message}
915
+ `);
916
+ return 1;
917
+ }
918
+ const templateContent = fs2.readFileSync(templatePath, "utf8");
919
+ writeFileAtomic(outputPath2, templateContent);
920
+ if (options.verbose) {
921
+ io.stdout.write(`Template source: ${templatePath}
922
+ `);
923
+ }
924
+ io.stdout.write(`Created profile from template at ${outputPath2}
925
+ `);
926
+ return 0;
927
+ }
928
+ options = await promptIfMissing(options, io);
929
+ const outputPath = resolveOutputPath(cwd, options);
930
+ const writableCheck = ensureWritableTarget(outputPath, options.force);
931
+ if (!writableCheck.ok) {
932
+ io.stderr.write(`Error: ${writableCheck.message}
933
+ `);
934
+ return 1;
935
+ }
936
+ const content = renderScaffold({
937
+ name: options.name ?? "my-profile",
938
+ domain: options.domain ?? "general-assistant",
939
+ model: options.model ?? "claude-sonnet",
940
+ tone: options.tone ?? "balanced"
941
+ });
942
+ writeFileAtomic(outputPath, content);
943
+ if (options.verbose) {
944
+ io.stdout.write(
945
+ `Scaffold config: name=${options.name ?? "my-profile"}, domain=${options.domain ?? "general-assistant"}, model=${options.model ?? "claude-sonnet"}, tone=${options.tone ?? "balanced"}
946
+ `
947
+ );
948
+ }
949
+ const validation = validateProfile(outputPath, {
950
+ bundledProfilesDir: path3.resolve(cwd, "profiles")
951
+ });
952
+ if (validation.errors.length > 0) {
953
+ io.stderr.write(
954
+ `Error: Generated profile failed validation (${validation.errors.length} errors).
955
+ `
956
+ );
957
+ return 2;
958
+ }
959
+ io.stdout.write(`Created profile scaffold at ${outputPath}
960
+ `);
961
+ if (validation.warnings.length > 0) {
962
+ io.stdout.write(`Validation warnings: ${validation.warnings.length}
963
+ `);
964
+ }
965
+ return 0;
966
+ }
967
+ function initHelp(out = process.stdout) {
968
+ printInitUsage(out);
969
+ }
970
+
971
+ // src/commands/import.ts
972
+ import fs3 from "fs";
973
+ import path4 from "path";
974
+ import { runImportAnalysis, validateResolvedProfile } from "@traits-dev/core";
975
+ import {
976
+ formatValidationResult as formatValidationResult3,
977
+ toValidationResultObject as toValidationResultObject3
978
+ } from "@traits-dev/core/internal";
979
+ function printImportUsage(out = process.stderr) {
980
+ out.write(
981
+ [
982
+ "Usage:",
983
+ " traits import [prompt-path] [options]",
984
+ "",
985
+ "Options:",
986
+ " --provider <name> Import provider: auto|openai|anthropic (default: auto)",
987
+ " --model <model> Import analysis model override",
988
+ " --name <profile-name> Output profile meta.name override",
989
+ " --output <path> Write generated YAML profile to file",
990
+ " --openai-base-url <url> Override OpenAI API base URL",
991
+ " --anthropic-base-url <url> Override Anthropic API base URL",
992
+ " --timeout-ms <ms> Provider request timeout (default: 20000)",
993
+ " --max-retries <count> Provider retry attempts (default: 2)",
994
+ " --retry-base-ms <ms> Base backoff delay (default: 250)",
995
+ " --strict Treat validation warnings as errors",
996
+ " --json Output structured JSON",
997
+ " --verbose Include command metadata output",
998
+ " --no-color Disable colorized output",
999
+ "",
1000
+ "Input:",
1001
+ " prompt-path Existing system prompt text file",
1002
+ " (stdin) Pipe prompt text when prompt-path is omitted",
1003
+ ""
1004
+ ].join("\n")
1005
+ );
1006
+ }
1007
+ function parseImportArgs(args) {
1008
+ const result = {
1009
+ promptPath: null,
1010
+ provider: "auto",
1011
+ model: null,
1012
+ profileName: null,
1013
+ outputPath: null,
1014
+ openaiBaseUrl: null,
1015
+ anthropicBaseUrl: null,
1016
+ timeoutMs: null,
1017
+ maxRetries: null,
1018
+ retryBaseMs: null,
1019
+ strict: false,
1020
+ json: false,
1021
+ verbose: false,
1022
+ noColor: false
1023
+ };
1024
+ const positionals = [];
1025
+ for (let index = 0; index < args.length; index += 1) {
1026
+ const arg = args[index];
1027
+ if (arg === "--strict") {
1028
+ result.strict = true;
1029
+ continue;
1030
+ }
1031
+ if (arg === "--json") {
1032
+ result.json = true;
1033
+ continue;
1034
+ }
1035
+ if (arg === "--verbose") {
1036
+ result.verbose = true;
1037
+ continue;
1038
+ }
1039
+ if (arg === "--no-color") {
1040
+ result.noColor = true;
1041
+ continue;
1042
+ }
1043
+ if (arg === "--provider" || arg === "--model" || arg === "--name" || arg === "--output" || arg === "--openai-base-url" || arg === "--anthropic-base-url" || arg === "--timeout-ms" || arg === "--max-retries" || arg === "--retry-base-ms") {
1044
+ const value = args[index + 1];
1045
+ if (!value) return { error: `Missing value for "${arg}"` };
1046
+ if (arg === "--provider") result.provider = String(value).toLowerCase();
1047
+ if (arg === "--model") result.model = value;
1048
+ if (arg === "--name") result.profileName = value;
1049
+ if (arg === "--output") result.outputPath = value;
1050
+ if (arg === "--openai-base-url") result.openaiBaseUrl = value;
1051
+ if (arg === "--anthropic-base-url") result.anthropicBaseUrl = value;
1052
+ if (arg === "--timeout-ms") result.timeoutMs = Number(value);
1053
+ if (arg === "--max-retries") result.maxRetries = Number(value);
1054
+ if (arg === "--retry-base-ms") result.retryBaseMs = Number(value);
1055
+ index += 1;
1056
+ continue;
1057
+ }
1058
+ if (arg.startsWith("--")) {
1059
+ return { error: `Unknown option "${arg}"` };
1060
+ }
1061
+ positionals.push(arg);
1062
+ }
1063
+ if (positionals.length > 1) {
1064
+ return { error: "Expected zero or one prompt path argument" };
1065
+ }
1066
+ result.promptPath = positionals[0] ?? null;
1067
+ if (!["auto", "openai", "anthropic"].includes(result.provider)) {
1068
+ return { error: 'Invalid "--provider" value. Expected auto, openai, or anthropic.' };
1069
+ }
1070
+ if (result.timeoutMs != null && (!Number.isInteger(result.timeoutMs) || result.timeoutMs < 0)) {
1071
+ return { error: 'Invalid "--timeout-ms" value. Expected a non-negative integer.' };
1072
+ }
1073
+ if (result.maxRetries != null && (!Number.isInteger(result.maxRetries) || result.maxRetries < 0)) {
1074
+ return { error: 'Invalid "--max-retries" value. Expected a non-negative integer.' };
1075
+ }
1076
+ if (result.retryBaseMs != null && (!Number.isInteger(result.retryBaseMs) || result.retryBaseMs < 0)) {
1077
+ return { error: 'Invalid "--retry-base-ms" value. Expected a non-negative integer.' };
1078
+ }
1079
+ return { value: result };
1080
+ }
1081
+ async function readStream(stream) {
1082
+ const chunks = [];
1083
+ for await (const chunk of stream) {
1084
+ chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
1085
+ }
1086
+ return chunks.join("");
1087
+ }
1088
+ async function loadPromptSource(options, io) {
1089
+ if (options.promptPath) {
1090
+ const promptPath = path4.resolve(io.cwd(), options.promptPath);
1091
+ return {
1092
+ source: promptPath,
1093
+ text: fs3.readFileSync(promptPath, "utf8")
1094
+ };
1095
+ }
1096
+ const stdin = io.stdin ?? process.stdin;
1097
+ if (stdin.isTTY) {
1098
+ throw new Error("Provide a prompt file path or pipe prompt text via stdin.");
1099
+ }
1100
+ return {
1101
+ source: "stdin",
1102
+ text: await readStream(stdin)
1103
+ };
1104
+ }
1105
+ async function runImport(args, io = process) {
1106
+ const parsed = parseImportArgs(args);
1107
+ if ("error" in parsed) {
1108
+ io.stderr.write(`Error: ${parsed.error}
1109
+
1110
+ `);
1111
+ printImportUsage(io.stderr);
1112
+ return 1;
1113
+ }
1114
+ const options = parsed.value;
1115
+ let promptSource;
1116
+ try {
1117
+ promptSource = await loadPromptSource(options, io);
1118
+ } catch (error) {
1119
+ io.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
1120
+ `);
1121
+ return 1;
1122
+ }
1123
+ const promptText = String(promptSource.text ?? "").trim();
1124
+ if (!promptText) {
1125
+ io.stderr.write("Error: Prompt source is empty.\n");
1126
+ return 1;
1127
+ }
1128
+ if (options.verbose) {
1129
+ io.stderr.write(`Import source: ${promptSource.source}
1130
+ `);
1131
+ io.stderr.write(`Provider preference: ${options.provider}
1132
+ `);
1133
+ if (options.model) {
1134
+ io.stderr.write(`Model override: ${options.model}
1135
+ `);
1136
+ }
1137
+ }
1138
+ try {
1139
+ const imported = await runImportAnalysis(promptText, {
1140
+ provider: options.provider,
1141
+ model: options.model ?? void 0,
1142
+ profileName: options.profileName ?? void 0,
1143
+ openaiApiKey: process.env.TRAITS_OPENAI_API_KEY,
1144
+ anthropicApiKey: process.env.TRAITS_ANTHROPIC_API_KEY,
1145
+ openaiBaseUrl: options.openaiBaseUrl ?? void 0,
1146
+ anthropicBaseUrl: options.anthropicBaseUrl ?? void 0,
1147
+ fetchTimeoutMs: options.timeoutMs ?? void 0,
1148
+ fetchMaxRetries: options.maxRetries ?? void 0,
1149
+ fetchRetryBaseMs: options.retryBaseMs ?? void 0
1150
+ });
1151
+ const validation = validateResolvedProfile(imported.profile, {
1152
+ strict: options.strict
1153
+ });
1154
+ const outputPath = options.outputPath ? path4.resolve(io.cwd(), options.outputPath) : null;
1155
+ if (outputPath) {
1156
+ fs3.writeFileSync(outputPath, imported.yaml, "utf8");
1157
+ }
1158
+ if (options.json) {
1159
+ io.stdout.write(
1160
+ `${JSON.stringify(
1161
+ {
1162
+ provider: imported.provider,
1163
+ source: promptSource.source,
1164
+ output_path: outputPath,
1165
+ profile: imported.profile,
1166
+ analysis: imported.analysis,
1167
+ validation: toValidationResultObject3(validation),
1168
+ yaml: outputPath ? void 0 : imported.yaml
1169
+ },
1170
+ null,
1171
+ 2
1172
+ )}
1173
+ `
1174
+ );
1175
+ return validation.exitCode ?? 0;
1176
+ }
1177
+ if (!outputPath) {
1178
+ io.stdout.write(`${imported.yaml.trimEnd()}
1179
+ `);
1180
+ } else {
1181
+ io.stdout.write(`Wrote imported profile to ${outputPath}
1182
+ `);
1183
+ }
1184
+ io.stderr.write(`
1185
+ [Validation]
1186
+ ${formatValidationResult3(validation)}
1187
+ `);
1188
+ io.stderr.write("\nNext: run `traits eval <profile-path> --model <model>` to verify fit.\n");
1189
+ return validation.exitCode ?? 0;
1190
+ } catch (error) {
1191
+ const typedError = error;
1192
+ if (typedError.code === "E_IMPORT_PROVIDER_UNAVAILABLE") {
1193
+ if (options.json) {
1194
+ io.stdout.write(
1195
+ `${JSON.stringify(
1196
+ {
1197
+ error: typedError.message,
1198
+ code: typedError.code
1199
+ },
1200
+ null,
1201
+ 2
1202
+ )}
1203
+ `
1204
+ );
1205
+ } else {
1206
+ io.stderr.write(`Error: ${typedError.message ?? "Import provider unavailable."}
1207
+ `);
1208
+ }
1209
+ return 2;
1210
+ }
1211
+ io.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}
1212
+ `);
1213
+ return 2;
1214
+ }
1215
+ }
1216
+ function importHelp(out = process.stdout) {
1217
+ printImportUsage(out);
1218
+ }
1219
+
1220
+ // src/commands/validate.ts
1221
+ import path5 from "path";
1222
+ import { validateProfile as validateProfile2 } from "@traits-dev/core";
1223
+ import {
1224
+ formatValidationResult as formatValidationResult4,
1225
+ toValidationResultObject as toValidationResultObject4
1226
+ } from "@traits-dev/core/internal";
1227
+ function printValidateUsage(out = process.stderr) {
1228
+ out.write(
1229
+ [
1230
+ "Usage:",
1231
+ " traits validate <profile-path> [--json] [--strict] [--bundled-profiles-dir <dir>]",
1232
+ "",
1233
+ "Options:",
1234
+ " --json Output structured JSON",
1235
+ " --strict Promote warnings to errors",
1236
+ " --verbose Include additional command metadata",
1237
+ " --no-color Disable colorized output",
1238
+ " --bundled-profiles-dir Directory for bundled starter profiles",
1239
+ ""
1240
+ ].join("\n")
1241
+ );
1242
+ }
1243
+ function parseValidateArgs(args) {
1244
+ const result = {
1245
+ profilePath: null,
1246
+ strict: false,
1247
+ json: false,
1248
+ verbose: false,
1249
+ noColor: false,
1250
+ bundledProfilesDir: null
1251
+ };
1252
+ const positionals = [];
1253
+ for (let index = 0; index < args.length; index += 1) {
1254
+ const arg = args[index];
1255
+ if (arg === "--strict") {
1256
+ result.strict = true;
1257
+ continue;
1258
+ }
1259
+ if (arg === "--json") {
1260
+ result.json = true;
1261
+ continue;
1262
+ }
1263
+ if (arg === "--verbose") {
1264
+ result.verbose = true;
1265
+ continue;
1266
+ }
1267
+ if (arg === "--no-color") {
1268
+ result.noColor = true;
1269
+ continue;
1270
+ }
1271
+ if (arg === "--bundled-profiles-dir") {
1272
+ const nextValue = args[index + 1];
1273
+ if (!nextValue) {
1274
+ return { error: 'Missing value for "--bundled-profiles-dir"' };
1275
+ }
1276
+ result.bundledProfilesDir = nextValue;
1277
+ index += 1;
1278
+ continue;
1279
+ }
1280
+ if (arg.startsWith("--")) {
1281
+ return { error: `Unknown option "${arg}"` };
1282
+ }
1283
+ positionals.push(arg);
1284
+ }
1285
+ if (positionals.length !== 1) {
1286
+ return { error: "Expected exactly one profile path argument" };
1287
+ }
1288
+ result.profilePath = positionals[0];
1289
+ return { value: result };
1290
+ }
1291
+ function runValidate(args, io = process) {
1292
+ const parsed = parseValidateArgs(args);
1293
+ if ("error" in parsed) {
1294
+ io.stderr.write(`Error: ${parsed.error}
1295
+
1296
+ `);
1297
+ printValidateUsage(io.stderr);
1298
+ return 1;
1299
+ }
1300
+ const options = parsed.value;
1301
+ if (!options.profilePath) {
1302
+ io.stderr.write("Error: Missing profile path\n\n");
1303
+ printValidateUsage(io.stderr);
1304
+ return 1;
1305
+ }
1306
+ const bundledProfilesDir = options.bundledProfilesDir ? path5.resolve(io.cwd(), options.bundledProfilesDir) : path5.resolve(io.cwd(), "profiles");
1307
+ const profilePath = path5.resolve(io.cwd(), options.profilePath);
1308
+ const result = validateProfile2(profilePath, {
1309
+ strict: options.strict,
1310
+ bundledProfilesDir
1311
+ });
1312
+ if (options.json) {
1313
+ io.stdout.write(`${JSON.stringify(toValidationResultObject4(result), null, 2)}
1314
+ `);
1315
+ } else {
1316
+ if (options.verbose) {
1317
+ io.stdout.write(`Profile path: ${profilePath}
1318
+ `);
1319
+ io.stdout.write(`Bundled profiles dir: ${bundledProfilesDir}
1320
+ `);
1321
+ io.stdout.write(`Strict mode: ${options.strict ? "on" : "off"}
1322
+
1323
+ `);
1324
+ }
1325
+ io.stdout.write(`${formatValidationResult4(result)}
1326
+ `);
1327
+ }
1328
+ return result.exitCode;
1329
+ }
1330
+ function validateHelp(out = process.stdout) {
1331
+ printValidateUsage(out);
1332
+ }
1333
+
1334
+ // src/bin/traits.ts
1335
+ var CLI_DIR = path6.dirname(fileURLToPath(import.meta.url));
1336
+ var PACKAGE_JSON_CANDIDATES = [
1337
+ path6.resolve(CLI_DIR, "../package.json"),
1338
+ path6.resolve(CLI_DIR, "../../package.json")
1339
+ ];
1340
+ function resolvePackageJsonPath() {
1341
+ for (const candidate of PACKAGE_JSON_CANDIDATES) {
1342
+ if (fs4.existsSync(candidate)) return candidate;
1343
+ }
1344
+ return PACKAGE_JSON_CANDIDATES[0];
1345
+ }
1346
+ var PACKAGE_JSON_PATH = resolvePackageJsonPath();
1347
+ function printRootUsage(out = process.stdout) {
1348
+ out.write(
1349
+ [
1350
+ "traits.dev CLI",
1351
+ "",
1352
+ "Usage:",
1353
+ " traits [global-options] <command> [options]",
1354
+ "",
1355
+ "Commands:",
1356
+ " init [output-path] Create a profile scaffold",
1357
+ " compile <profile-path> Compile a profile for a target model",
1358
+ " eval <profile-path> Evaluate profile responses (Tier 1 scaffold)",
1359
+ " import [prompt-path] Import a profile from an existing system prompt",
1360
+ " validate <profile-path> Validate a personality profile",
1361
+ "",
1362
+ "Global flags:",
1363
+ " --json Output JSON where supported",
1364
+ " --verbose Print command metadata",
1365
+ " --no-color Disable colorized output",
1366
+ " --version Show CLI version",
1367
+ " --help, -h Show help",
1368
+ "",
1369
+ "Examples:",
1370
+ " traits init my-profile.yaml --domain customer-support --tone warm",
1371
+ " traits validate profiles/resolve.yaml",
1372
+ " traits --json validate profiles/haven.yaml --strict",
1373
+ ""
1374
+ ].join("\n")
1375
+ );
1376
+ }
1377
+ function readCliVersion() {
1378
+ try {
1379
+ const raw = fs4.readFileSync(PACKAGE_JSON_PATH, "utf8");
1380
+ const pkg = JSON.parse(raw);
1381
+ return String(pkg.version ?? "0.0.0");
1382
+ } catch {
1383
+ return "0.0.0";
1384
+ }
1385
+ }
1386
+ function parseGlobalFlags(args) {
1387
+ const flags = { json: false, verbose: false, noColor: false };
1388
+ let index = 0;
1389
+ while (index < args.length) {
1390
+ const arg = args[index];
1391
+ if (arg === "--json") {
1392
+ flags.json = true;
1393
+ index += 1;
1394
+ continue;
1395
+ }
1396
+ if (arg === "--verbose") {
1397
+ flags.verbose = true;
1398
+ index += 1;
1399
+ continue;
1400
+ }
1401
+ if (arg === "--no-color") {
1402
+ flags.noColor = true;
1403
+ index += 1;
1404
+ continue;
1405
+ }
1406
+ if (arg.startsWith("-")) {
1407
+ return { error: `Unknown global option "${arg}"` };
1408
+ }
1409
+ break;
1410
+ }
1411
+ return {
1412
+ flags,
1413
+ command: args[index] ?? null,
1414
+ commandArgs: args.slice(index + 1)
1415
+ };
1416
+ }
1417
+ function withGlobalFlags(command, commandArgs, flags) {
1418
+ const args = [...commandArgs];
1419
+ if (flags.verbose && !args.includes("--verbose")) {
1420
+ args.push("--verbose");
1421
+ }
1422
+ if (flags.noColor && !args.includes("--no-color")) {
1423
+ args.push("--no-color");
1424
+ }
1425
+ if ((command === "validate" || command === "compile" || command === "eval" || command === "import") && flags.json && !args.includes("--json")) {
1426
+ args.push("--json");
1427
+ }
1428
+ return args;
1429
+ }
1430
+ async function run(argv, io = process) {
1431
+ const args = [...argv];
1432
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
1433
+ printRootUsage(io.stdout);
1434
+ return 0;
1435
+ }
1436
+ if (args[0] === "--version") {
1437
+ io.stdout.write(`${readCliVersion()}
1438
+ `);
1439
+ return 0;
1440
+ }
1441
+ const parsed = parseGlobalFlags(args);
1442
+ if ("error" in parsed) {
1443
+ io.stderr.write(`Error: ${parsed.error}
1444
+
1445
+ `);
1446
+ printRootUsage(io.stderr);
1447
+ return 1;
1448
+ }
1449
+ const command = parsed.command;
1450
+ if (!command) {
1451
+ io.stderr.write("Error: Missing command\n\n");
1452
+ printRootUsage(io.stderr);
1453
+ return 1;
1454
+ }
1455
+ const commandArgs = withGlobalFlags(command, parsed.commandArgs, parsed.flags);
1456
+ if (command === "validate") {
1457
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
1458
+ validateHelp(io.stdout);
1459
+ return 0;
1460
+ }
1461
+ return runValidate(commandArgs, io);
1462
+ }
1463
+ if (command === "init") {
1464
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
1465
+ initHelp(io.stdout);
1466
+ return 0;
1467
+ }
1468
+ return runInit(commandArgs, io);
1469
+ }
1470
+ if (command === "compile") {
1471
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
1472
+ compileHelp(io.stdout);
1473
+ return 0;
1474
+ }
1475
+ return runCompile(commandArgs, io);
1476
+ }
1477
+ if (command === "eval") {
1478
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
1479
+ evalHelp(io.stdout);
1480
+ return 0;
1481
+ }
1482
+ return runEval(commandArgs, io);
1483
+ }
1484
+ if (command === "import") {
1485
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
1486
+ importHelp(io.stdout);
1487
+ return 0;
1488
+ }
1489
+ return runImport(commandArgs, io);
1490
+ }
1491
+ io.stderr.write(`Error: Unknown command "${command}"
1492
+
1493
+ `);
1494
+ printRootUsage(io.stderr);
1495
+ return 1;
1496
+ }
1497
+ var code = await run(process.argv.slice(2), process);
1498
+ process.exitCode = code;
1499
+ export {
1500
+ run
1501
+ };