fraim-framework 2.0.124 → 2.0.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.124",
3
+ "version": "2.0.126",
4
4
  "description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "manage-teams": "tsx scripts/fraim/manage-teams.ts",
33
33
  "partner-discounts": "tsx scripts/fraim/manage-partner-discounts.ts",
34
34
  "fix-key": "tsx scripts/fraim/fix-expired-key.ts",
35
+ "backfill:persona-entitlements": "tsx scripts/backfill-persona-entitlements.ts",
35
36
  "setup-stripe-webhook": "tsx scripts/fraim/setup-stripe-webhook.ts",
36
37
  "view-signups": "tsx scripts/view-signups.ts",
37
38
  "fraim:init": "npm run build && node index.js init",
@@ -126,6 +127,9 @@
126
127
  "publishConfig": {
127
128
  "access": "public"
128
129
  },
130
+ "overrides": {
131
+ "uuid": "^14.0.0"
132
+ },
129
133
  "dependencies": {
130
134
  "@octokit/rest": "^22.0.1",
131
135
  "adm-zip": "^0.5.16",
@@ -1,83 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DeviceFlowService = void 0;
7
- const axios_1 = __importDefault(require("axios"));
8
- const chalk_1 = __importDefault(require("chalk"));
9
- class DeviceFlowService {
10
- constructor(config) {
11
- this.config = config;
12
- }
13
- /**
14
- * Start the Device Flow Login
15
- */
16
- async login() {
17
- console.log(chalk_1.default.blue('\n🔗 Starting Authentication...'));
18
- try {
19
- // 1. Request device and user codes
20
- const deviceCode = await this.requestDeviceCode();
21
- console.log(chalk_1.default.yellow('\nACTION REQUIRED:'));
22
- console.log(`1. Go to: ${chalk_1.default.cyan.underline(deviceCode.verification_uri)}`);
23
- console.log(`2. Enter the code: ${chalk_1.default.bold.green(deviceCode.user_code)}`);
24
- console.log(chalk_1.default.gray(`\nWaiting for authorization (expires in ${Math.floor(deviceCode.expires_in / 60)} minutes)...`));
25
- // 2. Poll for the access token
26
- const token = await this.pollForToken(deviceCode.device_code, deviceCode.interval);
27
- console.log(chalk_1.default.green('\n✅ Authentication Successful!'));
28
- return token;
29
- }
30
- catch (error) {
31
- console.error(chalk_1.default.red(`\n❌ Authentication failed: ${error.message}`));
32
- throw error;
33
- }
34
- }
35
- async requestDeviceCode() {
36
- const response = await axios_1.default.post(this.config.authUrl, {
37
- client_id: this.config.clientId,
38
- scope: this.config.scope
39
- }, {
40
- headers: { Accept: 'application/json' }
41
- });
42
- return response.data;
43
- }
44
- async pollForToken(deviceCode, interval) {
45
- let currentInterval = interval * 1000;
46
- return new Promise((resolve, reject) => {
47
- const poll = async () => {
48
- try {
49
- const response = await axios_1.default.post(this.config.tokenUrl, {
50
- client_id: this.config.clientId,
51
- device_code: deviceCode,
52
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
53
- }, {
54
- headers: { Accept: 'application/json' }
55
- });
56
- if (response.data.access_token) {
57
- resolve(response.data.access_token);
58
- return;
59
- }
60
- if (response.data.error) {
61
- const error = response.data.error;
62
- if (error === 'authorization_pending') {
63
- // Keep polling
64
- setTimeout(poll, currentInterval);
65
- }
66
- else if (error === 'slow_down') {
67
- currentInterval += 5000;
68
- setTimeout(poll, currentInterval);
69
- }
70
- else {
71
- reject(new Error(response.data.error_description || error));
72
- }
73
- }
74
- }
75
- catch (error) {
76
- reject(error);
77
- }
78
- };
79
- setTimeout(poll, currentInterval);
80
- });
81
- }
82
- }
83
- exports.DeviceFlowService = DeviceFlowService;
@@ -1,152 +0,0 @@
1
- "use strict";
2
- /**
3
- * Prometheus Scraper for Claude Code OTel Token Usage Metrics
4
- *
5
- * Scrapes Claude Code's local Prometheus endpoint to capture cumulative
6
- * token usage counters. Returns raw snapshot values — delta computation
7
- * happens server-side per architecture constraint 15.2.
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.parsePrometheusText = parsePrometheusText;
11
- exports.extractTokenSnapshot = extractTokenSnapshot;
12
- exports.scrapeTokenSnapshot = scrapeTokenSnapshot;
13
- /** Candidate Prometheus endpoint ports (OTel SDK defaults) */
14
- const CANDIDATE_PORTS = [9464, 8888];
15
- /** Candidate metric names for token usage (OTel dots→underscores, optional suffixes) */
16
- const TOKEN_METRIC_CANDIDATES = [
17
- 'claude_code_token_usage_total',
18
- 'claude_code_token_usage',
19
- 'claude_code_token_usage_tokens_total',
20
- 'claude_code_token_usage_tokens',
21
- ];
22
- /** Candidate metric names for cost usage */
23
- const COST_METRIC_CANDIDATES = [
24
- 'claude_code_cost_usage_total',
25
- 'claude_code_cost_usage',
26
- 'claude_code_cost_usage_usd_total',
27
- 'claude_code_cost_usage_usd',
28
- ];
29
- /**
30
- * Parse Prometheus text exposition format into structured metrics.
31
- *
32
- * Format per line: metric_name{label1="val1",label2="val2"} value [timestamp]
33
- * Lines starting with # are comments (HELP, TYPE). Empty lines are skipped.
34
- */
35
- function parsePrometheusText(text) {
36
- const results = [];
37
- for (const line of text.split('\n')) {
38
- if (line.startsWith('#') || line.trim() === '')
39
- continue;
40
- // Match: metric_name{labels} value
41
- const labeledMatch = line.match(/^([a-zA-Z_:][a-zA-Z0-9_:]*)\{([^}]*)\}\s+([\d.eE+-]+)/);
42
- if (labeledMatch) {
43
- const [, name, labelsStr, valueStr] = labeledMatch;
44
- const labels = {};
45
- for (const pair of labelsStr.matchAll(/(\w+)="([^"]*)"/g)) {
46
- labels[pair[1]] = pair[2];
47
- }
48
- results.push({ name, labels, value: parseFloat(valueStr) });
49
- continue;
50
- }
51
- // Match: metric_name value (no labels)
52
- const noLabelMatch = line.match(/^([a-zA-Z_:][a-zA-Z0-9_:]*)\s+([\d.eE+-]+)/);
53
- if (noLabelMatch) {
54
- const [, name, valueStr] = noLabelMatch;
55
- results.push({ name, labels: {}, value: parseFloat(valueStr) });
56
- }
57
- }
58
- return results;
59
- }
60
- /**
61
- * Extract a TokenSnapshot from parsed Prometheus metrics.
62
- * Tries multiple candidate metric names for resilience to SDK version changes.
63
- */
64
- function extractTokenSnapshot(metrics) {
65
- const snapshot = {
66
- inputTokens: 0,
67
- outputTokens: 0,
68
- cacheReadTokens: 0,
69
- cacheCreationTokens: 0,
70
- costUsd: 0,
71
- claudeSessionId: null,
72
- model: null,
73
- capturedAt: new Date(),
74
- };
75
- for (const m of metrics) {
76
- if (TOKEN_METRIC_CANDIDATES.includes(m.name)) {
77
- const type = m.labels['type'];
78
- switch (type) {
79
- case 'input':
80
- snapshot.inputTokens = m.value;
81
- break;
82
- case 'output':
83
- snapshot.outputTokens = m.value;
84
- break;
85
- case 'cacheRead':
86
- snapshot.cacheReadTokens = m.value;
87
- break;
88
- case 'cacheCreation':
89
- snapshot.cacheCreationTokens = m.value;
90
- break;
91
- }
92
- snapshot.claudeSessionId = m.labels['session_id'] || snapshot.claudeSessionId;
93
- snapshot.model = m.labels['model'] || snapshot.model;
94
- }
95
- if (COST_METRIC_CANDIDATES.includes(m.name)) {
96
- snapshot.costUsd += m.value;
97
- snapshot.claudeSessionId = m.labels['session_id'] || snapshot.claudeSessionId;
98
- }
99
- }
100
- return snapshot;
101
- }
102
- /**
103
- * Attempt to fetch Prometheus metrics from a candidate endpoint.
104
- * Returns response text or null on failure.
105
- */
106
- async function tryFetch(port, timeoutMs) {
107
- try {
108
- const controller = new AbortController();
109
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
110
- const resp = await fetch(`http://localhost:${port}/metrics`, {
111
- signal: controller.signal,
112
- });
113
- clearTimeout(timeout);
114
- if (resp.ok) {
115
- return await resp.text();
116
- }
117
- return null;
118
- }
119
- catch {
120
- return null;
121
- }
122
- }
123
- /**
124
- * Scrape Claude Code's Prometheus endpoint and return a TokenSnapshot.
125
- *
126
- * Returns null if:
127
- * - Prometheus endpoint is not available (OTel not enabled)
128
- * - Scrape times out (>2s by default)
129
- * - No matching token metrics found
130
- *
131
- * @param log Optional logging function for debug output
132
- */
133
- async function scrapeTokenSnapshot(log) {
134
- const configuredPort = process.env.FRAIM_PROMETHEUS_PORT;
135
- const ports = configuredPort ? [parseInt(configuredPort, 10)] : CANDIDATE_PORTS;
136
- const timeoutMs = 2000;
137
- for (const port of ports) {
138
- const text = await tryFetch(port, timeoutMs);
139
- if (text && text.includes('#')) {
140
- const metrics = parsePrometheusText(text);
141
- const snapshot = extractTokenSnapshot(metrics);
142
- // Only return if we actually found token metrics
143
- if (snapshot.inputTokens > 0 || snapshot.outputTokens > 0) {
144
- log?.(`Captured token snapshot from localhost:${port} (input=${snapshot.inputTokens}, output=${snapshot.outputTokens})`);
145
- return snapshot;
146
- }
147
- log?.(`Prometheus endpoint found at localhost:${port} but no token metrics matched`);
148
- }
149
- }
150
- log?.('No Prometheus endpoint available — token snapshot skipped');
151
- return null;
152
- }