@prism-d1/cli 1.0.66 → 1.0.68

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.
@@ -287,16 +287,21 @@ export class OtelCollectorConstruct extends Construct {
287
287
 
288
288
  // -------------------------------------------------------
289
289
  // Outputs — everything needed to run `codeburn sync setup <url>`
290
+ // Logical IDs are overridden so the deploy output keys match the
291
+ // README verbatim (construct nesting would otherwise mangle them to
292
+ // OtelCollectorOtelCollectorUrl<hash>).
290
293
  // -------------------------------------------------------
291
- new cdk.CfnOutput(this, 'OtelCollectorUrl', {
294
+ const urlOutput = new cdk.CfnOutput(this, 'OtelCollectorUrl', {
292
295
  value: this.httpApi.apiEndpoint,
293
296
  description: 'Run: codeburn sync setup <this URL>',
294
297
  });
298
+ urlOutput.overrideLogicalId('OtelCollectorUrl');
295
299
  if (this.userPool) {
296
- new cdk.CfnOutput(this, 'OtelUserPoolId', {
300
+ const poolOutput = new cdk.CfnOutput(this, 'OtelUserPoolId', {
297
301
  value: this.userPool.userPoolId,
298
302
  description: 'Create users: aws cognito-idp admin-create-user --user-pool-id <this> --username <email>',
299
303
  });
304
+ poolOutput.overrideLogicalId('OtelUserPoolId');
300
305
  }
301
306
  }
302
307
  }
@@ -187,7 +187,7 @@ export class DashboardStack extends cdk.Stack {
187
187
 
188
188
  teamDashboard.addWidgets(
189
189
  new cloudwatch.GraphWidget({
190
- title: 'AI Input Tokens per PR',
190
+ title: 'AI Input Tokens',
191
191
  left: [
192
192
  new cloudwatch.Metric({
193
193
  namespace: METRIC_NAMESPACE,
@@ -202,7 +202,7 @@ export class DashboardStack extends cdk.Stack {
202
202
  leftYAxis: { min: 0, label: 'Tokens' },
203
203
  }),
204
204
  new cloudwatch.GraphWidget({
205
- title: 'AI Output Tokens per PR',
205
+ title: 'AI Output Tokens',
206
206
  left: [
207
207
  new cloudwatch.Metric({
208
208
  namespace: METRIC_NAMESPACE,
@@ -217,7 +217,7 @@ export class DashboardStack extends cdk.Stack {
217
217
  leftYAxis: { min: 0, label: 'Tokens' },
218
218
  }),
219
219
  new cloudwatch.GraphWidget({
220
- title: 'AI Cost per PR (USD)',
220
+ title: 'AI Cost (USD)',
221
221
  left: [
222
222
  new cloudwatch.Metric({
223
223
  namespace: METRIC_NAMESPACE,
@@ -233,6 +233,48 @@ export class DashboardStack extends cdk.Stack {
233
233
  }),
234
234
  );
235
235
 
236
+ // Cost breakdowns by Tool and Model. SEARCH expressions auto-discover
237
+ // every dimension value, so new tools/models appear without dashboard
238
+ // changes. These series are populated by the otel-metrics-publisher
239
+ // (requires -c enableOtelCollector=true); the trailer path has no
240
+ // per-tool/per-model breakdown and leaves these widgets empty.
241
+ teamDashboard.addWidgets(
242
+ new cloudwatch.GraphWidget({
243
+ title: 'AI Cost by Tool (USD)',
244
+ left: [
245
+ new cloudwatch.MathExpression({
246
+ expression: `SEARCH('{${METRIC_NAMESPACE},Tool} MetricName="AICostUSD"', 'Sum')`,
247
+ usingMetrics: {},
248
+ // Dynamic label: each discovered series is legend-labeled with
249
+ // its Tool dimension value (e.g. "claude-code").
250
+ label: "${PROP('Dim.Tool')}",
251
+ period: DEFAULT_PERIOD,
252
+ }),
253
+ ],
254
+ width: 12,
255
+ height: 6,
256
+ leftYAxis: { min: 0, label: 'USD' },
257
+ legendPosition: cloudwatch.LegendPosition.RIGHT,
258
+ }),
259
+ new cloudwatch.GraphWidget({
260
+ title: 'AI Cost by Model (USD)',
261
+ left: [
262
+ new cloudwatch.MathExpression({
263
+ expression: `SEARCH('{${METRIC_NAMESPACE},Model} MetricName="AICostUSD"', 'Sum')`,
264
+ usingMetrics: {},
265
+ // Dynamic label: each discovered series is legend-labeled with
266
+ // its Model dimension value (e.g. "claude-sonnet-4-5-20250929").
267
+ label: "${PROP('Dim.Model')}",
268
+ period: DEFAULT_PERIOD,
269
+ }),
270
+ ],
271
+ width: 12,
272
+ height: 6,
273
+ leftYAxis: { min: 0, label: 'USD' },
274
+ legendPosition: cloudwatch.LegendPosition.RIGHT,
275
+ }),
276
+ );
277
+
236
278
  teamDashboard.addWidgets(
237
279
  new cloudwatch.TextWidget({
238
280
  markdown: '### Agent Operations',
@@ -441,11 +441,20 @@ async function publishCloudWatchMetrics(
441
441
  ['PostMergeDefectRate', detail.ai_dora.post_merge_defect_rate, StandardUnit.Percent, true],
442
442
  ['EvalGatePassRate', detail.ai_dora.eval_gate_pass_rate, StandardUnit.Percent, true],
443
443
  ['AITestCoverageDelta', detail.ai_dora.ai_test_coverage_delta, StandardUnit.Percent, true],
444
- ['AIInputTokens', detail.ai_dora.total_input_tokens, StandardUnit.Count, false],
445
- ['AIOutputTokens', detail.ai_dora.total_output_tokens, StandardUnit.Count, false],
446
- ['AICostUSD', detail.ai_dora.total_cost_usd, StandardUnit.None, false],
447
444
  ];
448
445
 
446
+ // Token & cost metrics come from git-trailer PR sums by default. When the
447
+ // OTEL collector is enabled (OTEL_ENABLED=true), the otel-metrics-publisher
448
+ // Lambda owns AIInputTokens / AIOutputTokens / AICostUSD (higher-fidelity
449
+ // per-span data via `codeburn sync`) — publishing both would double-count.
450
+ if (process.env.OTEL_ENABLED !== 'true') {
451
+ aiDoraMap.push(
452
+ ['AIInputTokens', detail.ai_dora.total_input_tokens, StandardUnit.Count, false],
453
+ ['AIOutputTokens', detail.ai_dora.total_output_tokens, StandardUnit.Count, false],
454
+ ['AICostUSD', detail.ai_dora.total_cost_usd, StandardUnit.None, false],
455
+ );
456
+ }
457
+
449
458
  for (const [name, value, unit, scaleToPercent] of aiDoraMap) {
450
459
  if (value != null) {
451
460
  const publishValue = scaleToPercent && value <= 1 ? value * 100 : value;
@@ -0,0 +1,185 @@
1
+ /**
2
+ * PRISM D1 — OTEL metrics publisher.
3
+ *
4
+ * Consumes the prism-d1-ai-usage DynamoDB stream (filtered at the event
5
+ * source to sk begins_with "OTEL#DAY#") and publishes token/cost deltas to
6
+ * CloudWatch under the same metric names the dashboards already read:
7
+ * AIInputTokens, AIOutputTokens, AICostUSD.
8
+ *
9
+ * Only active when the OTEL collector is enabled — the metrics-processor
10
+ * stops publishing trailer-sourced token metrics (OTEL_ENABLED=true) so
11
+ * these series are exclusively OTEL-fed and never double-counted.
12
+ *
13
+ * Scale guards (by design):
14
+ * - Deltas are exact: OTEL#DAY# items are ADD counters, so delta = NEW − OLD.
15
+ * - Metrics are timestamped with the SPAN day, not arrival time. Backfilled
16
+ * days older than the CloudWatch ingest window (14 days) are dropped —
17
+ * the DynamoDB aggregates and S3 OTLP archive still hold full history.
18
+ * - Published dimensionless (matches dashboard widgets) AND with Tool and
19
+ * Model dimensions (small, bounded cardinality) for per-tool / per-model
20
+ * slicing.
21
+ * - Single PutMetricData call per batch, chunked at the API limit.
22
+ */
23
+
24
+ import {
25
+ CloudWatchClient,
26
+ PutMetricDataCommand,
27
+ MetricDatum,
28
+ StandardUnit,
29
+ } from '@aws-sdk/client-cloudwatch';
30
+
31
+ const cloudwatch = new CloudWatchClient({});
32
+
33
+ const METRIC_NAMESPACE = process.env.METRIC_NAMESPACE || 'PRISM/D1/Velocity';
34
+ /** CloudWatch rejects datapoints older than 14 days. */
35
+ const MAX_METRIC_AGE_DAYS = 14;
36
+ /** PutMetricData accepts up to 1000 datums per call. */
37
+ const PUT_METRIC_BATCH_SIZE = 1000;
38
+
39
+ // ---- Types (DynamoDB stream event, trimmed to what we read) ----
40
+
41
+ interface AttributeValue {
42
+ S?: string;
43
+ N?: string;
44
+ }
45
+
46
+ interface StreamImage {
47
+ sk?: AttributeValue;
48
+ day?: AttributeValue;
49
+ tool?: AttributeValue;
50
+ model?: AttributeValue;
51
+ input_tokens?: AttributeValue;
52
+ output_tokens?: AttributeValue;
53
+ cost_usd?: AttributeValue;
54
+ }
55
+
56
+ interface StreamRecord {
57
+ eventName?: 'INSERT' | 'MODIFY' | 'REMOVE';
58
+ dynamodb?: {
59
+ Keys?: { pk?: AttributeValue; sk?: AttributeValue };
60
+ NewImage?: StreamImage;
61
+ OldImage?: StreamImage;
62
+ };
63
+ }
64
+
65
+ export interface StreamEvent {
66
+ Records?: StreamRecord[];
67
+ }
68
+
69
+ // ---- Helpers ----
70
+
71
+ function num(v: AttributeValue | undefined): number {
72
+ const n = Number(v?.N ?? 0);
73
+ return Number.isFinite(n) ? n : 0;
74
+ }
75
+
76
+ /** Resolve the metric timestamp from the aggregate's span day. Null = drop. */
77
+ export function resolveTimestamp(day: string, now: Date = new Date()): Date | null {
78
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) return null;
79
+ // Noon UTC keeps the datum inside the calendar day for all dashboard periods.
80
+ const ts = new Date(`${day}T12:00:00Z`);
81
+ if (isNaN(ts.getTime())) return null;
82
+ const ageMs = now.getTime() - ts.getTime();
83
+ // Too old for CloudWatch ingest (backfilled history) — drop, don't error.
84
+ if (ageMs > MAX_METRIC_AGE_DAYS * 24 * 60 * 60 * 1000) return null;
85
+ // Guard against clock skew: never publish a future timestamp.
86
+ return ts.getTime() > now.getTime() ? now : ts;
87
+ }
88
+
89
+ interface Delta {
90
+ day: string;
91
+ tool: string;
92
+ model: string;
93
+ inputTokens: number;
94
+ outputTokens: number;
95
+ costUsd: number;
96
+ }
97
+
98
+ /** Compute the counter delta for one OTEL#DAY# stream record. Null = skip. */
99
+ export function computeDelta(record: StreamRecord): Delta | null {
100
+ if (record.eventName !== 'INSERT' && record.eventName !== 'MODIFY') return null;
101
+
102
+ const newImage = record.dynamodb?.NewImage;
103
+ const sk = newImage?.sk?.S ?? record.dynamodb?.Keys?.sk?.S ?? '';
104
+ // Defense in depth — the event source filter should already guarantee this.
105
+ if (!sk.startsWith('OTEL#DAY#')) return null;
106
+
107
+ const oldImage = record.dynamodb?.OldImage;
108
+ const delta: Delta = {
109
+ day: newImage?.day?.S ?? sk.split('#')[2] ?? '',
110
+ tool: newImage?.tool?.S ?? sk.split('#')[3] ?? 'unknown',
111
+ model: newImage?.model?.S ?? sk.split('#')[4] ?? 'unknown',
112
+ inputTokens: num(newImage?.input_tokens) - num(oldImage?.input_tokens),
113
+ outputTokens: num(newImage?.output_tokens) - num(oldImage?.output_tokens),
114
+ costUsd: num(newImage?.cost_usd) - num(oldImage?.cost_usd),
115
+ };
116
+
117
+ // Nothing to publish (e.g. metadata-only update) or a counter went
118
+ // backwards (should not happen with ADD — treat defensively as no-op).
119
+ if (delta.inputTokens <= 0 && delta.outputTokens <= 0 && delta.costUsd <= 0) return null;
120
+ return delta;
121
+ }
122
+
123
+ /**
124
+ * Build datums for a delta across three dimension sets:
125
+ * dimensionless (dashboards), Tool, and Model. CloudWatch cannot roll up
126
+ * across dimensions, so each slice needs its own series. Tool and model
127
+ * cardinality are both small and bounded.
128
+ */
129
+ export function buildDatums(delta: Delta, timestamp: Date): MetricDatum[] {
130
+ const metrics: Array<[string, number, StandardUnit]> = [
131
+ ['AIInputTokens', delta.inputTokens, StandardUnit.Count],
132
+ ['AIOutputTokens', delta.outputTokens, StandardUnit.Count],
133
+ ['AICostUSD', delta.costUsd, StandardUnit.None],
134
+ ];
135
+
136
+ const dimensionSets = [
137
+ [],
138
+ [{ Name: 'Tool', Value: delta.tool }],
139
+ [{ Name: 'Model', Value: delta.model }],
140
+ ];
141
+
142
+ const datums: MetricDatum[] = [];
143
+ for (const [name, value, unit] of metrics) {
144
+ if (value <= 0) continue;
145
+ for (const dimensions of dimensionSets) {
146
+ datums.push({ MetricName: name, Value: value, Unit: unit, Timestamp: timestamp, Dimensions: dimensions });
147
+ }
148
+ }
149
+ return datums;
150
+ }
151
+
152
+ // ---- Handler ----
153
+
154
+ export async function handler(event: StreamEvent): Promise<void> {
155
+ const now = new Date();
156
+ const datums: MetricDatum[] = [];
157
+ let processed = 0;
158
+ let droppedOld = 0;
159
+
160
+ for (const record of event.Records ?? []) {
161
+ const delta = computeDelta(record);
162
+ if (!delta) continue;
163
+
164
+ const timestamp = resolveTimestamp(delta.day, now);
165
+ if (!timestamp) {
166
+ droppedOld++;
167
+ continue;
168
+ }
169
+
170
+ datums.push(...buildDatums(delta, timestamp));
171
+ processed++;
172
+ }
173
+
174
+ for (let i = 0; i < datums.length; i += PUT_METRIC_BATCH_SIZE) {
175
+ await cloudwatch.send(new PutMetricDataCommand({
176
+ Namespace: METRIC_NAMESPACE,
177
+ MetricData: datums.slice(i, i + PUT_METRIC_BATCH_SIZE),
178
+ }));
179
+ }
180
+
181
+ console.log(
182
+ `[otel-metrics-publisher] records=${event.Records?.length ?? 0} ` +
183
+ `published=${processed} droppedTooOld=${droppedOld} datums=${datums.length}`,
184
+ );
185
+ }
@@ -11,8 +11,8 @@
11
11
  * newly-seen spans, bucketed by SPAN date, not arrival date).
12
12
  *
13
13
  * Item shapes in the AI-usage table:
14
- * pk=USER#<identity> sk=SPAN#<timestamp>#<spanId> raw span, TTL 90d
15
- * pk=USER#<identity> sk=OTEL#DAY#<yyyy-mm-dd>#<tool> daily aggregate
14
+ * pk=USER#<identity> sk=SPAN#<timestamp>#<spanId> raw span, TTL 90d
15
+ * pk=USER#<identity> sk=OTEL#DAY#<yyyy-mm-dd>#<tool>#<model> daily aggregate
16
16
  */
17
17
 
18
18
  import {
@@ -272,15 +272,17 @@ async function writeSpanIfNew(user: string, s: ParsedSpan): Promise<boolean> {
272
272
  /** ADD-increment the daily aggregate for a newly-seen span (bucketed by SPAN date). */
273
273
  async function bumpDailyAggregate(user: string, s: ParsedSpan): Promise<void> {
274
274
  const day = s.timestamp.slice(0, 10);
275
+ // '#' is the key delimiter — strip it from the model to keep the sk parseable.
276
+ const model = (s.model || 'unknown').replace(/#/g, '');
275
277
  await dynamoClient.send(new UpdateItemCommand({
276
278
  TableName: AI_USAGE_TABLE,
277
279
  Key: {
278
280
  pk: { S: `USER#${user}` },
279
- sk: { S: `OTEL#DAY#${day}#${s.tool}` },
281
+ sk: { S: `OTEL#DAY#${day}#${s.tool}#${model}` },
280
282
  },
281
283
  UpdateExpression:
282
284
  'ADD input_tokens :in, output_tokens :out, cost_usd :cost, call_count :one ' +
283
- 'SET record_type = :rt, tool = :tool, #day = :day, updated_at = :now',
285
+ 'SET record_type = :rt, tool = :tool, model = :model, #day = :day, updated_at = :now',
284
286
  ExpressionAttributeNames: { '#day': 'day' },
285
287
  ExpressionAttributeValues: {
286
288
  ':in': { N: String(s.inputTokens) },
@@ -289,6 +291,7 @@ async function bumpDailyAggregate(user: string, s: ParsedSpan): Promise<void> {
289
291
  ':one': { N: '1' },
290
292
  ':rt': { S: 'OTEL_DAY' },
291
293
  ':tool': { S: s.tool },
294
+ ':model': { S: model },
292
295
  ':day': { S: day },
293
296
  ':now': { S: new Date().toISOString() },
294
297
  },
@@ -2,6 +2,8 @@ import * as cdk from 'aws-cdk-lib';
2
2
  import * as events from 'aws-cdk-lib/aws-events';
3
3
  import * as targets from 'aws-cdk-lib/aws-events-targets';
4
4
  import * as lambda from 'aws-cdk-lib/aws-lambda';
5
+ import * as eventsources from 'aws-cdk-lib/aws-lambda-event-sources';
6
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
5
7
  import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
6
8
  import * as iam from 'aws-cdk-lib/aws-iam';
7
9
  import * as logs from 'aws-cdk-lib/aws-logs';
@@ -133,10 +135,14 @@ export class MetricsPipelineStack extends cdk.Stack {
133
135
  // -------------------------------------------------------
134
136
  // DynamoDB AI-usage table — user-keyed, KMS encrypted
135
137
  // pk = USER#<email>
136
- // sk = COMMIT#<ts>#<sha>#<tool> | SPAN#<ts>#<spanId> | OTEL#DAY#<date>#<tool>
138
+ // sk = COMMIT#<ts>#<sha>#<tool> | SPAN#<ts>#<spanId> | OTEL#DAY#<date>#<tool>#<model>
137
139
  // Sparse GSIs (only commit-level items set them) invert the user key so the
138
140
  // same table answers by-repo / by-PR / by-commit / global-by-date.
139
141
  // -------------------------------------------------------
142
+ // Read the OTEL collector flag early — it also controls the table stream
143
+ // and which pipeline owns the token/cost CloudWatch metrics.
144
+ const enableOtelCollector = this.node.tryGetContext('enableOtelCollector') === 'true';
145
+
140
146
  this.aiUsageTable = new dynamodb.Table(this, 'AiUsageTable', {
141
147
  tableName: 'prism-d1-ai-usage',
142
148
  partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
@@ -147,6 +153,9 @@ export class MetricsPipelineStack extends cdk.Stack {
147
153
  timeToLiveAttribute: 'ttl',
148
154
  encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
149
155
  encryptionKey: prismKmsKey,
156
+ // NEW_AND_OLD_IMAGES so the otel-metrics-publisher can compute exact
157
+ // counter deltas (new − old) on OTEL#DAY# aggregate updates.
158
+ stream: enableOtelCollector ? dynamodb.StreamViewType.NEW_AND_OLD_IMAGES : undefined,
150
159
  });
151
160
  for (const [indexName, pkAttr, skAttr] of [
152
161
  ['by-repo', 'gsi_repo', 'gsi_repo_sk'],
@@ -222,6 +231,10 @@ export class MetricsPipelineStack extends cdk.Stack {
222
231
  EVENTS_TABLE: this.eventsTable.tableName,
223
232
  METADATA_TABLE: this.metadataTable.tableName,
224
233
  METRIC_NAMESPACE: 'PRISM/D1/Velocity',
234
+ // When the OTEL collector is enabled, the otel-metrics-publisher owns
235
+ // AIInputTokens/AIOutputTokens/AICostUSD — the processor skips its
236
+ // trailer-sourced copies to avoid double-counting.
237
+ OTEL_ENABLED: enableOtelCollector ? 'true' : 'false',
225
238
  },
226
239
  logRetention: logs.RetentionDays.ONE_MONTH,
227
240
  description: 'Processes PRISM D1 metric events from EventBridge into DynamoDB and CloudWatch',
@@ -295,7 +308,6 @@ export class MetricsPipelineStack extends cdk.Stack {
295
308
  // Per-user AI usage flows directly from codeburn sync to the
296
309
  // ai-usage table (spans + daily aggregates).
297
310
  // -------------------------------------------------------
298
- const enableOtelCollector = this.node.tryGetContext('enableOtelCollector') === 'true';
299
311
  if (enableOtelCollector) {
300
312
  this.otelCollector = new OtelCollectorConstruct(this, 'OtelCollector', {
301
313
  aiUsageTable: this.aiUsageTable,
@@ -305,6 +317,97 @@ export class MetricsPipelineStack extends cdk.Stack {
305
317
  externalClientId: this.node.tryGetContext('otelClientId') as string | undefined,
306
318
  identityClaim: this.node.tryGetContext('otelIdentityClaim') as string | undefined,
307
319
  });
320
+
321
+ // ---------------------------------------------------
322
+ // OTEL metrics publisher — DDB stream → CloudWatch.
323
+ // Replaces the trailer-sourced AIInputTokens/AIOutputTokens/AICostUSD
324
+ // series with per-span OTEL data (same metric names — dashboards
325
+ // require no changes). The event-source filter limits invocations to
326
+ // OTEL#DAY# aggregate updates, so span-volume writes never invoke it.
327
+ // ---------------------------------------------------
328
+ const otelPublisherDlq = new sqs.Queue(this, 'OtelPublisherDLQ', {
329
+ queueName: 'prism-d1-otel-publisher-dlq',
330
+ encryptionMasterKey: prismKmsKey,
331
+ enforceSSL: true,
332
+ retentionPeriod: cdk.Duration.days(14),
333
+ });
334
+
335
+ const otelMetricsPublisher = new lambda.Function(this, 'OtelMetricsPublisher', {
336
+ functionName: 'prism-d1-otel-metrics-publisher',
337
+ runtime: lambda.Runtime.NODEJS_22_X,
338
+ handler: 'otel-metrics-publisher.handler',
339
+ ...lambdaVpcProps,
340
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
341
+ bundling: {
342
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
343
+ command: [
344
+ 'bash', '-c',
345
+ [
346
+ 'npm init -y > /dev/null 2>&1',
347
+ 'npm install --save @aws-sdk/client-cloudwatch esbuild > /dev/null 2>&1',
348
+ 'npx esbuild otel-metrics-publisher.ts --bundle --platform=node --target=node22 --outfile=/asset-output/otel-metrics-publisher.js --external:@aws-sdk/*',
349
+ ].join(' && '),
350
+ ],
351
+ local: {
352
+ tryBundle(outputDir: string): boolean {
353
+ try {
354
+ const { execSync } = require('child_process');
355
+ execSync(
356
+ `npx esbuild ${path.join(__dirname, 'lambda', 'otel-metrics-publisher.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'otel-metrics-publisher.js')} --external:@aws-sdk/*`,
357
+ { stdio: 'pipe' },
358
+ );
359
+ return true;
360
+ } catch {
361
+ return false;
362
+ }
363
+ },
364
+ },
365
+ },
366
+ }),
367
+ timeout: cdk.Duration.seconds(60),
368
+ memorySize: 256,
369
+ environment: {
370
+ METRIC_NAMESPACE: 'PRISM/D1/Velocity',
371
+ },
372
+ logRetention: logs.RetentionDays.ONE_MONTH,
373
+ description: 'Publishes OTEL#DAY aggregate deltas from the ai-usage stream as CloudWatch token/cost metrics',
374
+ });
375
+
376
+ otelMetricsPublisher.addToRolePolicy(
377
+ new iam.PolicyStatement({
378
+ effect: iam.Effect.ALLOW,
379
+ actions: ['cloudwatch:PutMetricData'],
380
+ resources: ['*'],
381
+ conditions: {
382
+ StringEquals: {
383
+ 'cloudwatch:namespace': 'PRISM/D1/Velocity',
384
+ },
385
+ },
386
+ }),
387
+ );
388
+
389
+ otelMetricsPublisher.addEventSource(
390
+ new eventsources.DynamoEventSource(this.aiUsageTable, {
391
+ startingPosition: lambda.StartingPosition.LATEST,
392
+ batchSize: 100,
393
+ maxBatchingWindow: cdk.Duration.seconds(10),
394
+ bisectBatchOnError: true,
395
+ retryAttempts: 3,
396
+ onFailure: new eventsources.SqsDlq(otelPublisherDlq),
397
+ // Only OTEL#DAY# aggregate inserts/updates — SPAN# and COMMIT#
398
+ // writes (the overwhelming majority at scale) never invoke us.
399
+ filters: [
400
+ lambda.FilterCriteria.filter({
401
+ eventName: lambda.FilterRule.or('INSERT', 'MODIFY'),
402
+ dynamodb: {
403
+ Keys: {
404
+ sk: { S: lambda.FilterRule.beginsWith('OTEL#DAY#') },
405
+ },
406
+ },
407
+ }),
408
+ ],
409
+ }),
410
+ );
308
411
  }
309
412
 
310
413
  // -------------------------------------------------------
@@ -737,6 +840,15 @@ export class MetricsPipelineStack extends cdk.Stack {
737
840
  reason: 'CDK grantReadData on DynamoDB generates wildcard for GSI index ARNs via token reference',
738
841
  appliesTo: ['Resource::<EventsTableD24865E5.Arn>/index/*', 'Resource::<AiUsageTable79F37CE0.Arn>/index/*'],
739
842
  },
843
+ {
844
+ id: 'AwsSolutions-IAM5',
845
+ reason: 'DynamoDB stream event source mapping requires ListStreams which does not support resource-level scoping beyond the table stream wildcard',
846
+ appliesTo: ['Resource::<AiUsageTable79F37CE0.Arn>/stream/*', 'Resource::*'],
847
+ },
848
+ {
849
+ id: 'AwsSolutions-SQS3',
850
+ reason: 'The OTEL publisher queue is itself a DLQ for the DynamoDB stream event source',
851
+ },
740
852
  { id: 'AwsSolutions-L1', reason: 'All Lambdas use nodejs22.x which is the latest Node.js runtime available' },
741
853
  ]);
742
854
 
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^20.17.0",
20
+ "ts-node": "^10.9.2",
20
21
  "typescript": "~5.6.0",
21
22
  "aws-cdk": "^2.170.0"
22
23
  }
@@ -6,6 +6,7 @@ declare const _default: {
6
6
  }[];
7
7
  action(opts: {
8
8
  global?: boolean;
9
+ pinIds?: boolean;
9
10
  }): Promise<void>;
10
11
  };
11
12
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"setup-github-oidc.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":";;;;;;iBA4BqB;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE;;AALzC,wBA8JE"}
1
+ {"version":3,"file":"setup-github-oidc.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":";;;;;;iBAqDqB;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE;;AAN3D,wBAyNE"}
@@ -19,10 +19,38 @@ function run(cmd) {
19
19
  return { ok: false, stdout: '', stderr: (err.stderr || err.message || '').trim() };
20
20
  }
21
21
  }
22
+ /**
23
+ * Fetch a GitHub API path, trying progressively more authenticated clients:
24
+ * unauthenticated (public repos/users) → GITHUB_TOKEN env → gh CLI token.
25
+ * Returns the parsed JSON body, or null if every attempt fails.
26
+ */
27
+ async function githubApi(apiPath) {
28
+ const tokens = [undefined];
29
+ if (process.env.GITHUB_TOKEN)
30
+ tokens.push(process.env.GITHUB_TOKEN);
31
+ const ghToken = run('gh auth token');
32
+ if (ghToken.ok && ghToken.stdout)
33
+ tokens.push(ghToken.stdout);
34
+ for (const token of tokens) {
35
+ try {
36
+ const headers = { Accept: 'application/vnd.github+json' };
37
+ if (token)
38
+ headers.Authorization = `Bearer ${token}`;
39
+ const res = await fetch(`https://api.github.com${apiPath}`, { headers });
40
+ if (res.ok)
41
+ return await res.json();
42
+ }
43
+ catch {
44
+ // Network error — try the next credential source.
45
+ }
46
+ }
47
+ return null;
48
+ }
22
49
  export default {
23
50
  description: 'Set up GitHub OIDC identity provider and IAM role for GitHub Actions',
24
51
  options: [
25
52
  { flags: '--global', description: 'Create a single role for all repos in the org/user (wildcard sub claim)' },
53
+ { flags: '--pin-ids', description: 'Pin the trust policy to immutable GitHub owner/repo IDs (protects against namespace resurrection; may require gh CLI or GITHUB_TOKEN for private repos)' },
26
54
  ],
27
55
  async action(opts) {
28
56
  console.log('\n🔐 GitHub OIDC Setup for AWS\n');
@@ -34,6 +62,7 @@ export default {
34
62
  process.exit(1);
35
63
  }
36
64
  let repoPath;
65
+ let repoName;
37
66
  let roleName;
38
67
  if (opts.global) {
39
68
  repoPath = `${githubUsername}/*`;
@@ -41,11 +70,66 @@ export default {
41
70
  console.log(`\nConfiguring OIDC for ALL repos: ${githubUsername}/*`);
42
71
  }
43
72
  else {
44
- const repoName = await prompt('Repository name', 'prism-d1-velocity');
73
+ repoName = await prompt('Repository name', 'prism-d1-velocity');
45
74
  repoPath = `${githubUsername}/${repoName}`;
46
75
  roleName = `GitHubActions-${repoName}`;
47
76
  console.log(`\nConfiguring OIDC for: ${repoPath}`);
48
77
  }
78
+ // -----------------------------------------------------------------
79
+ // Build the OIDC sub patterns. GitHub repos created (or renamed /
80
+ // transferred) after mid-2026 present an immutable-ID sub format:
81
+ // repo:<owner>@<ownerId>/<repo>@<repoId>:<ref>
82
+ // Older repos keep the classic format:
83
+ // repo:<owner>/<repo>:<ref>
84
+ // We trust both. '@' is not a legal character in GitHub owner or repo
85
+ // names, so the wildcarded ID segments can only ever match numeric IDs —
86
+ // names cannot be crafted to collide. Wildcard IDs do forgo namespace-
87
+ // resurrection protection; use --pin-ids to lock to the exact IDs.
88
+ // -----------------------------------------------------------------
89
+ let ownerId;
90
+ let repoId;
91
+ if (opts.pinIds) {
92
+ console.log('\nResolving immutable GitHub IDs (--pin-ids)...');
93
+ if (opts.global) {
94
+ const user = await githubApi(`/users/${githubUsername}`);
95
+ if (user?.id)
96
+ ownerId = String(user.id);
97
+ }
98
+ else {
99
+ const repo = await githubApi(`/repos/${githubUsername}/${repoName}`);
100
+ if (repo?.id && repo?.owner?.id) {
101
+ repoId = String(repo.id);
102
+ ownerId = String(repo.owner.id);
103
+ }
104
+ }
105
+ if (!ownerId) {
106
+ console.log(' Could not resolve IDs automatically (private repo without gh CLI or GITHUB_TOKEN?).');
107
+ console.log(` You can find them with: gh api repos/${repoPath.replace('/*', '/<repo>')} --jq '"\\(.owner.id) \\(.id)"'`);
108
+ const pasted = await prompt(opts.global
109
+ ? 'GitHub owner ID (blank = fall back to wildcard IDs)'
110
+ : 'GitHub "<ownerId> <repoId>" (blank = fall back to wildcard IDs)');
111
+ const parts = pasted.split(/\s+/).filter(Boolean);
112
+ if (parts[0])
113
+ ownerId = parts[0];
114
+ if (parts[1])
115
+ repoId = parts[1];
116
+ }
117
+ if (ownerId) {
118
+ console.log(` ✓ Pinned: owner ID ${ownerId}${repoId ? `, repo ID ${repoId}` : ''}`);
119
+ }
120
+ else {
121
+ console.log(' Falling back to wildcard ID matching.');
122
+ }
123
+ }
124
+ const subPatterns = opts.global
125
+ ? [
126
+ `repo:${githubUsername}/*:*`,
127
+ `repo:${githubUsername}@${ownerId ?? '*'}/*:*`,
128
+ ]
129
+ : [
130
+ `repo:${githubUsername}/${repoName}:*`,
131
+ `repo:${githubUsername}@${ownerId ?? '*'}/${repoName}@${repoId ?? '*'}:*`,
132
+ ];
49
133
  // Check AWS credentials
50
134
  const sts = run('aws sts get-caller-identity --query Account --output text');
51
135
  if (!sts.ok) {
@@ -91,7 +175,7 @@ export default {
91
175
  'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
92
176
  },
93
177
  StringLike: {
94
- 'token.actions.githubusercontent.com:sub': `repo:${repoPath}:*`,
178
+ 'token.actions.githubusercontent.com:sub': subPatterns,
95
179
  },
96
180
  },
97
181
  },
@@ -156,6 +240,12 @@ export default {
156
240
  console.log('════════════════════════════════════════════════');
157
241
  console.log(`\n Role ARN: ${roleArn}`);
158
242
  console.log(` Repository: ${repoPath}`);
243
+ console.log(' Trusted sub patterns:');
244
+ for (const p of subPatterns)
245
+ console.log(` - ${p}`);
246
+ if (!opts.pinIds) {
247
+ console.log(' Tip: rerun with --pin-ids to lock the trust policy to immutable GitHub IDs.');
248
+ }
159
249
  console.log('\n Next step: Add a repository secret in GitHub');
160
250
  console.log('');
161
251
  console.log(' In your GitHub repository, go to:');
@@ -1 +1 @@
1
- {"version":3,"file":"setup-github-oidc.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup-github-oidc.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ declare const _default: {
2
+ description: string;
3
+ options: {
4
+ flags: string;
5
+ description: string;
6
+ }[];
7
+ action(opts: {
8
+ url?: string;
9
+ interval?: string;
10
+ status?: boolean;
11
+ remove?: boolean;
12
+ }): Promise<void>;
13
+ };
14
+ export default _default;
15
+ //# sourceMappingURL=setup-otel-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-otel-sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-otel-sync.ts"],"names":[],"mappings":";;;;;;iBAgPqB;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE;;AAR5F,wBAiIE"}
@@ -0,0 +1,332 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { createInterface } from 'node:readline';
3
+ import { platform, homedir } from 'node:os';
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ function prompt(question, defaultValue) {
7
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8
+ const suffix = defaultValue ? ` [${defaultValue}]` : '';
9
+ return new Promise((resolve) => {
10
+ rl.question(`${question}${suffix}: `, (answer) => {
11
+ rl.close();
12
+ resolve(answer.trim() || defaultValue || '');
13
+ });
14
+ });
15
+ }
16
+ function run(cmd) {
17
+ try {
18
+ const stdout = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
19
+ return { ok: true, stdout, stderr: '' };
20
+ }
21
+ catch (err) {
22
+ return { ok: false, stdout: '', stderr: (err.stderr || err.message || '').trim() };
23
+ }
24
+ }
25
+ // ---- Constants ----
26
+ const CRON_MARKER = '# prism-otel-sync';
27
+ const PLIST_LABEL = 'com.prism.otel-sync';
28
+ const SCHTASK_NAME = 'PrismOtelSync';
29
+ const LOG_DIR = join(homedir(), '.prism', 'logs');
30
+ // ---- Scheduler helpers per OS ----
31
+ function getCodeburnPath() {
32
+ const which = platform() === 'win32' ? 'where codeburn' : 'which codeburn';
33
+ const result = run(which);
34
+ return result.ok ? result.stdout.split('\n')[0] : null;
35
+ }
36
+ function buildSyncCommand(codeburnPath, since) {
37
+ return `${codeburnPath} sync push --since ${since}`;
38
+ }
39
+ // ---- Linux: user crontab ----
40
+ function linuxScheduleExists() {
41
+ const result = run('crontab -l');
42
+ return result.ok && result.stdout.includes(CRON_MARKER);
43
+ }
44
+ function linuxInstallSchedule(codeburnPath, intervalHours) {
45
+ const existing = run('crontab -l');
46
+ const lines = existing.ok ? existing.stdout.split('\n').filter(l => !l.includes(CRON_MARKER)) : [];
47
+ const logFile = join(LOG_DIR, 'otel-sync.log');
48
+ const cronExpr = `0 */${intervalHours} * * *`;
49
+ lines.push(`${CRON_MARKER}`);
50
+ lines.push(`${cronExpr} ${buildSyncCommand(codeburnPath, '7d')} >> ${logFile} 2>&1`);
51
+ const tmpFile = '/tmp/prism-crontab.tmp';
52
+ writeFileSync(tmpFile, lines.join('\n') + '\n');
53
+ const result = run(`crontab ${tmpFile}`);
54
+ if (!result.ok)
55
+ throw new Error(`Failed to install crontab: ${result.stderr}`);
56
+ }
57
+ function linuxRemoveSchedule() {
58
+ const existing = run('crontab -l');
59
+ if (!existing.ok)
60
+ return;
61
+ const lines = existing.stdout.split('\n').filter(l => !l.includes(CRON_MARKER) && !l.includes('codeburn sync push'));
62
+ const tmpFile = '/tmp/prism-crontab.tmp';
63
+ writeFileSync(tmpFile, lines.join('\n') + '\n');
64
+ run(`crontab ${tmpFile}`);
65
+ }
66
+ // ---- macOS: LaunchAgent ----
67
+ function darwinPlistPath() {
68
+ return join(homedir(), 'Library', 'LaunchAgents', `${PLIST_LABEL}.plist`);
69
+ }
70
+ function darwinScheduleExists() {
71
+ return existsSync(darwinPlistPath());
72
+ }
73
+ function darwinInstallSchedule(codeburnPath, intervalHours) {
74
+ const logFile = join(LOG_DIR, 'otel-sync.log');
75
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
76
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
77
+ <plist version="1.0">
78
+ <dict>
79
+ <key>Label</key>
80
+ <string>${PLIST_LABEL}</string>
81
+ <key>ProgramArguments</key>
82
+ <array>
83
+ <string>${codeburnPath}</string>
84
+ <string>sync</string>
85
+ <string>push</string>
86
+ <string>--since</string>
87
+ <string>7d</string>
88
+ </array>
89
+ <key>StartInterval</key>
90
+ <integer>${intervalHours * 3600}</integer>
91
+ <key>StandardOutPath</key>
92
+ <string>${logFile}</string>
93
+ <key>StandardErrorPath</key>
94
+ <string>${logFile}</string>
95
+ <key>RunAtLoad</key>
96
+ <true/>
97
+ </dict>
98
+ </plist>`;
99
+ const plistPath = darwinPlistPath();
100
+ mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });
101
+ writeFileSync(plistPath, plist);
102
+ run(`launchctl unload ${plistPath} 2>/dev/null`);
103
+ const result = run(`launchctl load ${plistPath}`);
104
+ if (!result.ok)
105
+ throw new Error(`Failed to load LaunchAgent: ${result.stderr}`);
106
+ }
107
+ function darwinRemoveSchedule() {
108
+ const plistPath = darwinPlistPath();
109
+ if (existsSync(plistPath)) {
110
+ run(`launchctl unload ${plistPath}`);
111
+ run(`rm ${plistPath}`);
112
+ }
113
+ }
114
+ // ---- Windows: schtasks ----
115
+ function windowsScheduleExists() {
116
+ const result = run(`schtasks /query /tn ${SCHTASK_NAME} /fo CSV`);
117
+ return result.ok;
118
+ }
119
+ function windowsInstallSchedule(codeburnPath, intervalHours) {
120
+ const logFile = join(LOG_DIR, 'otel-sync.log');
121
+ const cmd = `${codeburnPath} sync push --since 7d >> "${logFile}" 2>&1`;
122
+ const result = run(`schtasks /create /tn ${SCHTASK_NAME} /sc hourly /mo ${intervalHours} ` +
123
+ `/tr "cmd /c ${cmd.replace(/"/g, '\\"')}" /f`);
124
+ if (!result.ok)
125
+ throw new Error(`Failed to create scheduled task: ${result.stderr}`);
126
+ }
127
+ function windowsRemoveSchedule() {
128
+ run(`schtasks /delete /tn ${SCHTASK_NAME} /f`);
129
+ }
130
+ // ---- Unified scheduler interface ----
131
+ function scheduleExists() {
132
+ switch (platform()) {
133
+ case 'darwin': return darwinScheduleExists();
134
+ case 'win32': return windowsScheduleExists();
135
+ default: return linuxScheduleExists();
136
+ }
137
+ }
138
+ function installSchedule(codeburnPath, intervalHours) {
139
+ mkdirSync(LOG_DIR, { recursive: true });
140
+ switch (platform()) {
141
+ case 'darwin': return darwinInstallSchedule(codeburnPath, intervalHours);
142
+ case 'win32': return windowsInstallSchedule(codeburnPath, intervalHours);
143
+ default: return linuxInstallSchedule(codeburnPath, intervalHours);
144
+ }
145
+ }
146
+ function removeSchedule() {
147
+ switch (platform()) {
148
+ case 'darwin': return darwinRemoveSchedule();
149
+ case 'win32': return windowsRemoveSchedule();
150
+ default: return linuxRemoveSchedule();
151
+ }
152
+ }
153
+ function schedulerName() {
154
+ switch (platform()) {
155
+ case 'darwin': return 'LaunchAgent';
156
+ case 'win32': return 'Scheduled Task';
157
+ default: return 'crontab';
158
+ }
159
+ }
160
+ // ---- Status ----
161
+ function showStatus(codeburnPath) {
162
+ console.log('\n📊 OTEL Sync Status\n');
163
+ // Codeburn installed?
164
+ if (codeburnPath) {
165
+ console.log(` ✓ codeburn found: ${codeburnPath}`);
166
+ const ver = run(`${codeburnPath} --version`);
167
+ if (ver.ok)
168
+ console.log(` version: ${ver.stdout}`);
169
+ }
170
+ else {
171
+ console.log(' ✗ codeburn not found in PATH');
172
+ }
173
+ // Sync configured?
174
+ const configPath = join(homedir(), '.config', 'codeburn', 'sync.json');
175
+ if (existsSync(configPath)) {
176
+ try {
177
+ const cfg = JSON.parse(readFileSync(configPath, 'utf8'));
178
+ console.log(` ✓ sync configured: ${cfg.baseUrl || cfg.endpoint || cfg.url || '(configured)'}`);
179
+ }
180
+ catch {
181
+ console.log(` ✓ sync config exists: ${configPath}`);
182
+ }
183
+ }
184
+ else {
185
+ console.log(' ✗ sync not configured (run setup first)');
186
+ }
187
+ // Schedule exists?
188
+ if (scheduleExists()) {
189
+ console.log(` ✓ ${schedulerName()} schedule active`);
190
+ }
191
+ else {
192
+ console.log(` ✗ no ${schedulerName()} schedule found`);
193
+ }
194
+ // Last log?
195
+ const logFile = join(LOG_DIR, 'otel-sync.log');
196
+ if (existsSync(logFile)) {
197
+ const lastLines = run(`tail -5 "${logFile}"`);
198
+ if (lastLines.ok && lastLines.stdout) {
199
+ console.log(` Last sync output (${logFile}):`);
200
+ for (const line of lastLines.stdout.split('\n')) {
201
+ console.log(` ${line}`);
202
+ }
203
+ }
204
+ }
205
+ console.log('');
206
+ }
207
+ // ---- Main ----
208
+ export default {
209
+ description: 'Set up automated codeburn OTEL sync (pushes AI usage telemetry every 12h)',
210
+ options: [
211
+ { flags: '--url <url>', description: 'OTEL collector URL (from CDK deploy output OtelCollectorUrl)' },
212
+ { flags: '--interval <hours>', description: 'Sync interval in hours (default: 12)' },
213
+ { flags: '--status', description: 'Show current sync schedule status' },
214
+ { flags: '--remove', description: 'Remove the sync schedule' },
215
+ ],
216
+ async action(opts) {
217
+ const codeburnPath = getCodeburnPath();
218
+ // --status
219
+ if (opts.status) {
220
+ showStatus(codeburnPath);
221
+ return;
222
+ }
223
+ // --remove
224
+ if (opts.remove) {
225
+ if (scheduleExists()) {
226
+ removeSchedule();
227
+ console.log(`\n ✓ ${schedulerName()} schedule removed.\n`);
228
+ }
229
+ else {
230
+ console.log(`\n No ${schedulerName()} schedule found — nothing to remove.\n`);
231
+ }
232
+ return;
233
+ }
234
+ // ---- Setup flow ----
235
+ console.log('\n🔄 OTEL Sync Setup\n');
236
+ console.log('This will configure automated codeburn telemetry sync to your PRISM');
237
+ console.log('OTEL collector. Runs every 12 hours to keep dashboards fresh.\n');
238
+ // 1. Check codeburn version
239
+ if (!codeburnPath) {
240
+ console.error('Error: codeburn not found. Install with: npm install -g codeburn');
241
+ process.exit(1);
242
+ }
243
+ const verResult = run(`${codeburnPath} --version`);
244
+ if (verResult.ok) {
245
+ const ver = verResult.stdout.replace(/[^0-9.]/g, '');
246
+ const parts = ver.split('.').map(Number);
247
+ if (parts[0] === 0 && parts[1] < 9 || (parts[1] === 9 && parts[2] < 16)) {
248
+ console.error(`Error: codeburn >= 0.9.16 required (found ${ver}). Run: npm update -g codeburn`);
249
+ process.exit(1);
250
+ }
251
+ console.log(` ✓ codeburn ${ver}`);
252
+ }
253
+ // 2. Get the OTEL collector URL
254
+ const otelUrl = opts.url || await prompt('OTEL collector URL (from CDK deploy output OtelCollectorUrl)');
255
+ if (!otelUrl) {
256
+ console.error('Error: OTEL collector URL is required.');
257
+ process.exit(1);
258
+ }
259
+ // 3. Check if codeburn sync is already configured for this URL
260
+ const configPath = join(homedir(), '.config', 'codeburn', 'sync.json');
261
+ let needsSetup = true;
262
+ if (existsSync(configPath)) {
263
+ try {
264
+ const cfg = JSON.parse(readFileSync(configPath, 'utf8'));
265
+ const existingUrl = cfg.baseUrl || cfg.endpoint || cfg.url || '';
266
+ if (existingUrl === otelUrl || existingUrl.startsWith(otelUrl)) {
267
+ console.log(` ✓ codeburn sync already configured for this endpoint.`);
268
+ needsSetup = false;
269
+ }
270
+ else {
271
+ console.log(` ⚠ codeburn sync configured for different endpoint: ${existingUrl}`);
272
+ const override = await prompt('Reconfigure for the new URL? (y/N)', 'N');
273
+ if (override.toLowerCase() !== 'y') {
274
+ console.log(' Keeping existing configuration.');
275
+ needsSetup = false;
276
+ }
277
+ }
278
+ }
279
+ catch {
280
+ // Corrupt config — re-setup
281
+ }
282
+ }
283
+ // 4. Run codeburn sync setup (opens browser for OIDC)
284
+ if (needsSetup) {
285
+ console.log('\n Running: codeburn sync setup ' + otelUrl);
286
+ console.log(' (This will open a browser for OIDC authentication)\n');
287
+ try {
288
+ execSync(`${codeburnPath} sync setup ${otelUrl}`, { stdio: 'inherit' });
289
+ console.log('\n ✓ codeburn sync configured.');
290
+ }
291
+ catch (err) {
292
+ console.error(`\n ✗ codeburn sync setup failed. Ensure Cognito user exists and try again.`);
293
+ process.exit(1);
294
+ }
295
+ }
296
+ // 5. Initial backfill push (30d — server drops >14d from CloudWatch, keeps in DDB/S3)
297
+ console.log('\n Pushing initial backfill (last 30 days of telemetry)...');
298
+ const backfill = run(`${codeburnPath} sync push --since 30d`);
299
+ if (backfill.ok) {
300
+ console.log(' ✓ Backfill push complete.');
301
+ if (backfill.stdout)
302
+ console.log(` ${backfill.stdout.split('\n').slice(-1)[0]}`);
303
+ }
304
+ else {
305
+ console.log(` ⚠ Backfill push failed (non-fatal): ${backfill.stderr.split('\n')[0]}`);
306
+ console.log(' The schedule will retry. Check ~/.prism/logs/otel-sync.log');
307
+ }
308
+ // 6. Install OS schedule
309
+ const intervalHours = opts.interval ? parseInt(opts.interval, 10) : 12;
310
+ if (isNaN(intervalHours) || intervalHours < 1) {
311
+ console.error('Error: --interval must be a positive integer (hours).');
312
+ process.exit(1);
313
+ }
314
+ if (scheduleExists()) {
315
+ console.log(`\n ✓ ${schedulerName()} schedule already exists — updating.`);
316
+ removeSchedule();
317
+ }
318
+ installSchedule(codeburnPath, intervalHours);
319
+ console.log(` ✓ ${schedulerName()} schedule installed (every ${intervalHours}h).`);
320
+ // Summary
321
+ console.log('\n════════════════════════════════════════════════');
322
+ console.log(' ✅ OTEL sync setup complete!');
323
+ console.log('════════════════════════════════════════════════');
324
+ console.log(`\n Endpoint: ${otelUrl}`);
325
+ console.log(` Schedule: every ${intervalHours}h via ${schedulerName()}`);
326
+ console.log(` Backfill: last 30d pushed (dashboard shows last 14d)`);
327
+ console.log(` Logs: ${join(LOG_DIR, 'otel-sync.log')}`);
328
+ console.log(`\n Run \`prism-cli bootstrapper setup-otel-sync --status\` to check health.`);
329
+ console.log(` Run \`prism-cli bootstrapper setup-otel-sync --remove\` to uninstall.\n`);
330
+ },
331
+ };
332
+ //# sourceMappingURL=setup-otel-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-otel-sync.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-otel-sync.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prism-d1/cli",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "description": "PRISM D1 Velocity CLI — AI development lifecycle assessment tool",
5
5
  "author": "andklee",
6
6
  "engines": {