atris 3.15.54 → 3.15.56
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/commands/integrations.js +252 -25
- package/commands/xp.js +111 -10
- package/package.json +1 -1
package/commands/integrations.js
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* atris gmail inbox - List recent emails
|
|
6
6
|
* atris gmail read <id> - Read specific email
|
|
7
7
|
* atris calendar today - Show today's events
|
|
8
|
+
* atris calendar yesterday - Show yesterday's events
|
|
8
9
|
* atris calendar week - Show this week's events
|
|
10
|
+
* atris calendar date YYYY-MM-DD - Show events on a date
|
|
9
11
|
* atris twitter post - Post a tweet (interactive)
|
|
10
12
|
* atris slack channels - List Slack channels
|
|
13
|
+
* atris slack messages <channel> [--limit 20] - Read recent messages
|
|
14
|
+
* atris slack search <query> [--limit 20] - Search Slack messages
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
const { loadCredentials, ensureValidCredentials } = require('../utils/auth');
|
|
@@ -137,12 +141,85 @@ async function gmailCommand(subcommand, ...args) {
|
|
|
137
141
|
// CALENDAR
|
|
138
142
|
// ============================================================================
|
|
139
143
|
|
|
140
|
-
|
|
144
|
+
function localDayBounds(offsetDays = 0) {
|
|
145
|
+
const start = new Date();
|
|
146
|
+
start.setHours(0, 0, 0, 0);
|
|
147
|
+
start.setDate(start.getDate() + offsetDays);
|
|
148
|
+
const end = new Date(start);
|
|
149
|
+
end.setDate(end.getDate() + 1);
|
|
150
|
+
return { start, end };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function localDateBounds(dateText) {
|
|
154
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(String(dateText || ''))) {
|
|
155
|
+
console.error('Usage: atris calendar date YYYY-MM-DD');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const start = new Date(`${dateText}T00:00:00`);
|
|
159
|
+
if (Number.isNaN(start.getTime())) {
|
|
160
|
+
console.error('Invalid date. Use YYYY-MM-DD.');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const end = new Date(start);
|
|
164
|
+
end.setDate(end.getDate() + 1);
|
|
165
|
+
return { start, end };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function calendarEventTimeValue(value) {
|
|
169
|
+
if (!value) return '';
|
|
170
|
+
if (typeof value === 'string') return value;
|
|
171
|
+
return value.dateTime || value.date || '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isDateOnly(value) {
|
|
175
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(String(value || ''));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function formatCalendarTimeRange(event) {
|
|
179
|
+
const start = calendarEventTimeValue(event.start || event.start_time || event.startTime);
|
|
180
|
+
const end = calendarEventTimeValue(event.end || event.end_time || event.endTime);
|
|
181
|
+
if (!start || isDateOnly(start)) return 'All day';
|
|
182
|
+
const startDate = new Date(start);
|
|
183
|
+
if (Number.isNaN(startDate.getTime())) return 'Time unavailable';
|
|
184
|
+
const startText = startDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
185
|
+
if (!end || isDateOnly(end)) return startText;
|
|
186
|
+
const endDate = new Date(end);
|
|
187
|
+
if (Number.isNaN(endDate.getTime())) return startText;
|
|
188
|
+
return `${startText}-${endDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function formatCalendarEvents(label, events) {
|
|
192
|
+
if (events.length === 0) {
|
|
193
|
+
console.log(`No events ${label}. 🎉`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log('─'.repeat(50));
|
|
198
|
+
|
|
199
|
+
for (const event of events) {
|
|
200
|
+
const time = formatCalendarTimeRange(event);
|
|
201
|
+
const title = event.summary || '(no title)';
|
|
202
|
+
|
|
203
|
+
console.log(`${time} ${title}`);
|
|
204
|
+
if (event.location) {
|
|
205
|
+
console.log(` 📍 ${event.location}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log('─'.repeat(50));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function calendarRange(label, { timeMin, timeMax, days } = {}) {
|
|
141
213
|
const { token, email } = await getAuth();
|
|
142
214
|
|
|
143
|
-
console.log(
|
|
215
|
+
console.log(`📅 ${label}:\n`);
|
|
144
216
|
|
|
145
|
-
const
|
|
217
|
+
const params = new URLSearchParams();
|
|
218
|
+
if (timeMin) params.set('time_min', timeMin);
|
|
219
|
+
if (timeMax) params.set('time_max', timeMax);
|
|
220
|
+
if (days) params.set('days', String(days));
|
|
221
|
+
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
222
|
+
const result = await apiRequestJson(`/integrations/google-calendar/events${suffix}`, {
|
|
146
223
|
method: 'GET',
|
|
147
224
|
token,
|
|
148
225
|
});
|
|
@@ -159,26 +236,25 @@ async function calendarToday() {
|
|
|
159
236
|
}
|
|
160
237
|
|
|
161
238
|
const events = result.data?.events || result.data || [];
|
|
239
|
+
formatCalendarEvents(label.toLowerCase(), events);
|
|
240
|
+
}
|
|
162
241
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.log('─'.repeat(50));
|
|
242
|
+
async function calendarToday() {
|
|
243
|
+
await calendarRange("Today's events", { days: 1 });
|
|
244
|
+
}
|
|
169
245
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
246
|
+
async function calendarYesterday() {
|
|
247
|
+
const { start, end } = localDayBounds(-1);
|
|
248
|
+
await calendarRange("Yesterday's events", { timeMin: start.toISOString(), timeMax: end.toISOString() });
|
|
249
|
+
}
|
|
174
250
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
}
|
|
251
|
+
async function calendarWeek() {
|
|
252
|
+
await calendarRange("This week's events", { days: 7 });
|
|
253
|
+
}
|
|
180
254
|
|
|
181
|
-
|
|
255
|
+
async function calendarDate(dateText) {
|
|
256
|
+
const { start, end } = localDateBounds(dateText);
|
|
257
|
+
await calendarRange(`Events on ${dateText}`, { timeMin: start.toISOString(), timeMax: end.toISOString() });
|
|
182
258
|
}
|
|
183
259
|
|
|
184
260
|
async function calendarCommand(subcommand, ...args) {
|
|
@@ -186,13 +262,22 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
186
262
|
case 'today':
|
|
187
263
|
await calendarToday();
|
|
188
264
|
break;
|
|
265
|
+
case 'yesterday':
|
|
266
|
+
case 'yday':
|
|
267
|
+
await calendarYesterday();
|
|
268
|
+
break;
|
|
189
269
|
case 'week':
|
|
190
|
-
|
|
270
|
+
await calendarWeek();
|
|
271
|
+
break;
|
|
272
|
+
case 'date':
|
|
273
|
+
await calendarDate(args[0]);
|
|
191
274
|
break;
|
|
192
275
|
default:
|
|
193
276
|
console.log('Calendar commands:');
|
|
194
|
-
console.log(' atris calendar today
|
|
195
|
-
console.log(' atris calendar
|
|
277
|
+
console.log(' atris calendar today - Show today\'s events');
|
|
278
|
+
console.log(' atris calendar yesterday - Show yesterday\'s events');
|
|
279
|
+
console.log(' atris calendar week - Show this week\'s events');
|
|
280
|
+
console.log(' atris calendar date YYYY-MM-DD - Show events on a date');
|
|
196
281
|
}
|
|
197
282
|
}
|
|
198
283
|
|
|
@@ -270,7 +355,7 @@ async function slackChannels() {
|
|
|
270
355
|
|
|
271
356
|
console.log('💬 Fetching Slack channels...\n');
|
|
272
357
|
|
|
273
|
-
const result = await apiRequestJson('/integrations/slack/channels', {
|
|
358
|
+
const result = await apiRequestJson('/integrations/slack/me/channels', {
|
|
274
359
|
method: 'GET',
|
|
275
360
|
token,
|
|
276
361
|
});
|
|
@@ -298,8 +383,137 @@ async function slackChannels() {
|
|
|
298
383
|
for (const ch of channels) {
|
|
299
384
|
const name = ch.name || ch.id;
|
|
300
385
|
const priv = ch.is_private ? '🔒' : '#';
|
|
301
|
-
console.log(` ${priv} ${name}
|
|
386
|
+
console.log(` ${priv} ${name} ${ch.id || ''}`.trimEnd());
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function slackDms() {
|
|
391
|
+
const token = await getAuthToken();
|
|
392
|
+
|
|
393
|
+
console.log('💬 Fetching Slack DMs...\n');
|
|
394
|
+
|
|
395
|
+
const result = await apiRequestJson('/integrations/slack/me/dms', {
|
|
396
|
+
method: 'GET',
|
|
397
|
+
token,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
if (!result.ok) {
|
|
401
|
+
console.error(`Error: ${result.error || 'Failed to fetch DMs'}`);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const dms = result.data?.dms || result.data?.channels || result.data || [];
|
|
406
|
+
if (!dms.length) {
|
|
407
|
+
console.log('No DMs found.');
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
for (const dm of dms) {
|
|
411
|
+
const name = dm.name || dm.user_name || dm.user || dm.id;
|
|
412
|
+
console.log(` ${name} ${dm.id || ''}`.trimEnd());
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function parseLimit(args, fallback = 20) {
|
|
417
|
+
const idx = args.findIndex((arg) => arg === '--limit' || arg === '-n');
|
|
418
|
+
const raw = idx >= 0 ? args[idx + 1] : '';
|
|
419
|
+
const parsed = Number.parseInt(raw, 10);
|
|
420
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 100) : fallback;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function argsWithoutLimit(args = []) {
|
|
424
|
+
const out = [];
|
|
425
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
426
|
+
if (args[i] === '--limit' || args[i] === '-n') {
|
|
427
|
+
i += 1;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
out.push(args[i]);
|
|
431
|
+
}
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function slackPersonalChannels(token) {
|
|
436
|
+
const result = await apiRequestJson('/integrations/slack/me/channels', {
|
|
437
|
+
method: 'GET',
|
|
438
|
+
token,
|
|
439
|
+
});
|
|
440
|
+
if (!result.ok) return [];
|
|
441
|
+
return result.data?.channels || result.data || [];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function resolveSlackChannel(token, channel) {
|
|
445
|
+
const text = String(channel || '').trim();
|
|
446
|
+
if (!text) return '';
|
|
447
|
+
if (/^[A-Z][A-Z0-9]{5,}$/.test(text)) return text;
|
|
448
|
+
const wanted = text.replace(/^#/, '').toLowerCase();
|
|
449
|
+
const channels = await slackPersonalChannels(token);
|
|
450
|
+
const match = channels.find((ch) => String(ch.name || '').toLowerCase() === wanted || String(ch.id || '').toLowerCase() === wanted);
|
|
451
|
+
return match?.id || text;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function formatSlackMessages(messages) {
|
|
455
|
+
if (!messages.length) {
|
|
456
|
+
console.log('No Slack messages found.');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
console.log('─'.repeat(60));
|
|
460
|
+
for (const message of messages) {
|
|
461
|
+
const author = message.user_name || message.username || message.user || message.sender || 'unknown';
|
|
462
|
+
const ts = message.datetime || message.time || message.ts || message.created_at || '';
|
|
463
|
+
const text = String(message.text || message.content || message.message || '').replace(/\s+/g, ' ').trim();
|
|
464
|
+
console.log(`${ts} ${author}: ${text || '(no text)'}`);
|
|
302
465
|
}
|
|
466
|
+
console.log('─'.repeat(60));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function slackMessages(channel, args = []) {
|
|
470
|
+
if (!channel) {
|
|
471
|
+
console.error('Usage: atris slack messages <channel-or-id> [--limit 20]');
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
const token = await getAuthToken();
|
|
475
|
+
const limit = parseLimit(args);
|
|
476
|
+
const channelId = await resolveSlackChannel(token, channel);
|
|
477
|
+
|
|
478
|
+
console.log(`💬 Reading Slack messages from ${channel}...\n`);
|
|
479
|
+
|
|
480
|
+
const result = await apiRequestJson(`/integrations/slack/me/messages/${encodeURIComponent(channelId)}?limit=${limit}`, {
|
|
481
|
+
method: 'GET',
|
|
482
|
+
token,
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
if (!result.ok) {
|
|
486
|
+
console.error(`Error: ${result.error || 'Failed to fetch Slack messages'}`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const messages = result.data?.messages || result.data || [];
|
|
491
|
+
formatSlackMessages(messages);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function slackSearch(query, args = []) {
|
|
495
|
+
if (!query) {
|
|
496
|
+
console.error('Usage: atris slack search <query> [--limit 20]');
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
const token = await getAuthToken();
|
|
500
|
+
const limit = parseLimit(args);
|
|
501
|
+
const q = encodeURIComponent(query);
|
|
502
|
+
|
|
503
|
+
console.log(`💬 Searching Slack for "${query}"...\n`);
|
|
504
|
+
|
|
505
|
+
const result = await apiRequestJson(`/integrations/slack/me/search?q=${q}&count=${limit}`, {
|
|
506
|
+
method: 'GET',
|
|
507
|
+
token,
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
if (!result.ok) {
|
|
511
|
+
console.error(`Error: ${result.error || 'Failed to search Slack messages'}`);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const messages = result.data?.messages || result.data?.results || result.data || [];
|
|
516
|
+
formatSlackMessages(messages);
|
|
303
517
|
}
|
|
304
518
|
|
|
305
519
|
async function slackCommand(subcommand, ...args) {
|
|
@@ -308,9 +522,22 @@ async function slackCommand(subcommand, ...args) {
|
|
|
308
522
|
case 'list':
|
|
309
523
|
await slackChannels();
|
|
310
524
|
break;
|
|
525
|
+
case 'dms':
|
|
526
|
+
await slackDms();
|
|
527
|
+
break;
|
|
528
|
+
case 'messages':
|
|
529
|
+
case 'read':
|
|
530
|
+
await slackMessages(args[0], args.slice(1));
|
|
531
|
+
break;
|
|
532
|
+
case 'search':
|
|
533
|
+
await slackSearch(argsWithoutLimit(args).join(' '), args);
|
|
534
|
+
break;
|
|
311
535
|
default:
|
|
312
536
|
console.log('Slack commands:');
|
|
313
|
-
console.log(' atris slack channels
|
|
537
|
+
console.log(' atris slack channels - List Slack channels');
|
|
538
|
+
console.log(' atris slack dms - List Slack DMs');
|
|
539
|
+
console.log(' atris slack messages <channel> - Read recent messages');
|
|
540
|
+
console.log(' atris slack search <query> - Search Slack messages');
|
|
314
541
|
}
|
|
315
542
|
}
|
|
316
543
|
|
package/commands/xp.js
CHANGED
|
@@ -1123,6 +1123,36 @@ function workspaceName(workspace) {
|
|
|
1123
1123
|
return path.basename(workspace) || workspace;
|
|
1124
1124
|
}
|
|
1125
1125
|
|
|
1126
|
+
function booleanSetting(value) {
|
|
1127
|
+
if (value === true || value === false) return value;
|
|
1128
|
+
const text = String(value || '').trim().toLowerCase();
|
|
1129
|
+
if (['1', 'true', 'yes', 'public', 'global'].includes(text)) return true;
|
|
1130
|
+
if (['0', 'false', 'no', 'private', 'internal', 'org'].includes(text)) return false;
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function agentXpPublicBinding(binding = {}) {
|
|
1135
|
+
const nested = binding.agentxp && typeof binding.agentxp === 'object' ? binding.agentxp : {};
|
|
1136
|
+
const explicit = booleanSetting(
|
|
1137
|
+
binding.agentxp_public
|
|
1138
|
+
?? binding.public_agentxp
|
|
1139
|
+
?? binding.agentxp_public_leaderboard
|
|
1140
|
+
?? nested.public
|
|
1141
|
+
?? nested.public_agentxp
|
|
1142
|
+
);
|
|
1143
|
+
if (explicit !== null) return explicit;
|
|
1144
|
+
|
|
1145
|
+
const visibility = slugify(binding.agentxp_visibility || nested.visibility);
|
|
1146
|
+
if (visibility === 'public') return true;
|
|
1147
|
+
if (['internal', 'private'].includes(visibility)) return false;
|
|
1148
|
+
|
|
1149
|
+
const scope = slugify(binding.agentxp_scope || nested.scope);
|
|
1150
|
+
if (scope === 'global') return true;
|
|
1151
|
+
if (['org', 'internal', 'private'].includes(scope)) return false;
|
|
1152
|
+
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1126
1156
|
function publicWorkspaceBinding(workspace) {
|
|
1127
1157
|
const binding = readJsonFile(path.join(workspace, BUSINESS_BINDING_FILE), null);
|
|
1128
1158
|
if (!binding || typeof binding !== 'object') return null;
|
|
@@ -1130,6 +1160,7 @@ function publicWorkspaceBinding(workspace) {
|
|
|
1130
1160
|
const workspaceId = String(binding.workspace_id || '').trim();
|
|
1131
1161
|
const businessSlug = slugify(binding.slug || binding.business_slug || binding.name);
|
|
1132
1162
|
const workspaceTemplate = slugify(binding.workspace_template || binding.organization_type || binding.computer_type);
|
|
1163
|
+
const agentxpPublic = agentXpPublicBinding(binding);
|
|
1133
1164
|
if (!businessId && !workspaceId && !businessSlug) return null;
|
|
1134
1165
|
return {
|
|
1135
1166
|
business_id: businessId || null,
|
|
@@ -1138,6 +1169,7 @@ function publicWorkspaceBinding(workspace) {
|
|
|
1138
1169
|
workspace_template: workspaceTemplate || null,
|
|
1139
1170
|
computer: businessSlug || workspaceName(workspace),
|
|
1140
1171
|
computer_slug: businessSlug || slugify(workspaceName(workspace)),
|
|
1172
|
+
...(agentxpPublic === null ? {} : { agentxp_public: agentxpPublic }),
|
|
1141
1173
|
};
|
|
1142
1174
|
}
|
|
1143
1175
|
|
|
@@ -1727,10 +1759,34 @@ function loadLocalPayload(args) {
|
|
|
1727
1759
|
return normalizeLocalScore(JSON.parse(result.stdout), workspace);
|
|
1728
1760
|
}
|
|
1729
1761
|
|
|
1730
|
-
function
|
|
1762
|
+
function earnedAgentXp(value) {
|
|
1763
|
+
return Math.max(0, asNumber(value));
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function currentFormScore(value) {
|
|
1731
1767
|
return Math.max(0, Math.min(99, asNumber(value)));
|
|
1732
1768
|
}
|
|
1733
1769
|
|
|
1770
|
+
function agentXpLevelProgress(totalXp) {
|
|
1771
|
+
const levelXp = Math.max(0, asNumber(totalXp));
|
|
1772
|
+
const level = levelFromXp(levelXp);
|
|
1773
|
+
const currentLevelFloor = (level - 1) * LEVEL_XP;
|
|
1774
|
+
const nextLevelXp = level * LEVEL_XP;
|
|
1775
|
+
const xpIntoLevel = levelXp - currentLevelFloor;
|
|
1776
|
+
return {
|
|
1777
|
+
schema: 'atris.agentxp_level_progress.v1',
|
|
1778
|
+
level,
|
|
1779
|
+
stage: `Level ${level}`,
|
|
1780
|
+
level_xp: levelXp,
|
|
1781
|
+
current_level_floor: currentLevelFloor,
|
|
1782
|
+
next_level_xp: nextLevelXp,
|
|
1783
|
+
xp_into_level: xpIntoLevel,
|
|
1784
|
+
xp_to_next: Math.max(0, nextLevelXp - levelXp),
|
|
1785
|
+
progress_pct: Math.max(0, Math.min(99, Math.round((xpIntoLevel / LEVEL_XP) * 100))),
|
|
1786
|
+
uncapped: true,
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1734
1790
|
function verifiedProjection(projection) {
|
|
1735
1791
|
return projection?.integrity_status === 'verified'
|
|
1736
1792
|
&& projection?.integrity?.status === 'verified';
|
|
@@ -1768,19 +1824,22 @@ function uniqueTruthy(values) {
|
|
|
1768
1824
|
return Array.from(new Set(values.map(value => String(value || '').trim()).filter(Boolean))).sort();
|
|
1769
1825
|
}
|
|
1770
1826
|
|
|
1771
|
-
function syncAttribution(workspaces) {
|
|
1827
|
+
function syncAttribution(workspaces, options = {}) {
|
|
1772
1828
|
const included = workspaces.filter(workspace => workspace.included !== false);
|
|
1773
1829
|
const scoped = included.length ? included : workspaces;
|
|
1774
1830
|
const businessIds = uniqueTruthy(scoped.map(workspace => workspace.business_id));
|
|
1775
1831
|
const workspaceIds = uniqueTruthy(scoped.map(workspace => workspace.workspace_id));
|
|
1776
1832
|
const businessSlugs = uniqueTruthy(scoped.map(workspace => workspace.business_slug || workspace.computer_slug));
|
|
1777
1833
|
const templates = uniqueTruthy(scoped.map(workspace => workspace.workspace_template));
|
|
1834
|
+
const explicitPublic = typeof options.publicAgentXp === 'boolean' ? options.publicAgentXp : null;
|
|
1835
|
+
const workspacePublic = scoped.length === 1 && scoped[0]?.agentxp_public === true;
|
|
1836
|
+
const agentxpPublic = explicitPublic === null ? (workspacePublic ? true : null) : explicitPublic;
|
|
1778
1837
|
|
|
1779
1838
|
if (businessIds.length > 1) {
|
|
1780
|
-
return { attribution_scope: 'multi_business', computer: 'multiple-workspaces' };
|
|
1839
|
+
return { attribution_scope: 'multi_business', computer: 'multiple-workspaces', agentxp_public: explicitPublic === true };
|
|
1781
1840
|
}
|
|
1782
1841
|
if (businessIds.length === 0 && scoped.length > 1 && businessSlugs.length > 1) {
|
|
1783
|
-
return { attribution_scope: 'multi_workspace', computer: 'multiple-workspaces' };
|
|
1842
|
+
return { attribution_scope: 'multi_workspace', computer: 'multiple-workspaces', agentxp_public: explicitPublic === true };
|
|
1784
1843
|
}
|
|
1785
1844
|
|
|
1786
1845
|
const businessId = businessIds[0] || null;
|
|
@@ -1794,9 +1853,32 @@ function syncAttribution(workspaces) {
|
|
|
1794
1853
|
business_slug: businessSlug,
|
|
1795
1854
|
workspace_template: workspaceTemplate,
|
|
1796
1855
|
computer: businessSlug || scoped[0]?.computer || scoped[0]?.name || 'local',
|
|
1856
|
+
agentxp_public: agentxpPublic,
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
function syncScopeFields(attribution = {}) {
|
|
1861
|
+
const attributionScope = slugify(attribution.attribution_scope);
|
|
1862
|
+
const orgId = String(attribution.business_id || attribution.business_slug || '').trim() || null;
|
|
1863
|
+
const businessBound = Boolean(orgId) || attributionScope === 'business-bound' || attributionScope === 'multi-business';
|
|
1864
|
+
const publicAgentXp = typeof attribution.agentxp_public === 'boolean'
|
|
1865
|
+
? attribution.agentxp_public
|
|
1866
|
+
: !businessBound;
|
|
1867
|
+
return {
|
|
1868
|
+
scope: publicAgentXp ? 'global' : (businessBound ? 'org' : 'global'),
|
|
1869
|
+
org_id: orgId,
|
|
1870
|
+
computer_id: String(attribution.workspace_id || attribution.computer || 'local').trim() || 'local',
|
|
1871
|
+
visibility: publicAgentXp ? 'public' : 'internal',
|
|
1872
|
+
public_agentxp: publicAgentXp,
|
|
1797
1873
|
};
|
|
1798
1874
|
}
|
|
1799
1875
|
|
|
1876
|
+
function publicAgentXpOverride(args = []) {
|
|
1877
|
+
if (hasFlag(args, '--public')) return true;
|
|
1878
|
+
if (hasFlag(args, '--private') || hasFlag(args, '--internal')) return false;
|
|
1879
|
+
return null;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1800
1882
|
function credentialHandle(credentials) {
|
|
1801
1883
|
return slugify(
|
|
1802
1884
|
credentials?.username
|
|
@@ -1849,26 +1931,30 @@ function syncPlayer(args, projection) {
|
|
|
1849
1931
|
|
|
1850
1932
|
function buildAgentXpSyncPacket(args = []) {
|
|
1851
1933
|
const localMode = hasFlag(args, '--local') || hasFlag(args, '--workspace') || hasFlag(args, '--operator');
|
|
1852
|
-
const projectionArgs = args.filter(arg => !['--dry-run', '--no-post', '--packet'].includes(arg));
|
|
1934
|
+
const projectionArgs = args.filter(arg => !['--dry-run', '--no-post', '--packet', '--public', '--private', '--internal'].includes(arg));
|
|
1853
1935
|
const projection = hasFlag(args, '--all') || !localMode
|
|
1854
1936
|
? collectAllLocalXpProjection(projectionArgs)
|
|
1855
1937
|
: collectLocalXpProjection(projectionArgs);
|
|
1856
1938
|
const player = syncPlayer(args, projection);
|
|
1857
1939
|
const workspaces = projectionWorkspaceSummaries(projection);
|
|
1858
|
-
const attribution = syncAttribution(workspaces);
|
|
1940
|
+
const attribution = syncAttribution(workspaces, { publicAgentXp: publicAgentXpOverride(args) });
|
|
1941
|
+
const scopeFields = syncScopeFields(attribution);
|
|
1859
1942
|
const totalXp = asNumber(projection.total_agent_xp ?? projection.agent_xp ?? projection.total_xp ?? projection.career_xp);
|
|
1860
1943
|
const receiptsCount = asNumber(projection.receipts_count);
|
|
1861
1944
|
const eligible = verifiedProjection(projection) && receiptsCount > 0 && totalXp > 0;
|
|
1862
|
-
const publicXp =
|
|
1945
|
+
const publicXp = earnedAgentXp(totalXp);
|
|
1946
|
+
const currentForm = currentFormScore(totalXp);
|
|
1947
|
+
const levelProgress = agentXpLevelProgress(publicXp);
|
|
1863
1948
|
const workspaceRootHash = sha256(workspaces.map(item => item.workspace_root_hash || item.name).sort().join(':'));
|
|
1864
1949
|
const entry = {
|
|
1865
1950
|
user_id: player,
|
|
1866
1951
|
username: player,
|
|
1867
1952
|
agent_xp: publicXp,
|
|
1868
1953
|
career_xp: publicXp,
|
|
1869
|
-
current_form:
|
|
1870
|
-
ovr:
|
|
1871
|
-
level:
|
|
1954
|
+
current_form: currentForm,
|
|
1955
|
+
ovr: currentForm,
|
|
1956
|
+
level: levelProgress.level,
|
|
1957
|
+
level_progress: levelProgress,
|
|
1872
1958
|
verified_receipts: receiptsCount,
|
|
1873
1959
|
reviewed_tasks: receiptsCount,
|
|
1874
1960
|
recent_verified_receipts: receiptsCount,
|
|
@@ -1883,12 +1969,17 @@ function buildAgentXpSyncPacket(args = []) {
|
|
|
1883
1969
|
schema: 'atris.agentxp_sync_packet.v1',
|
|
1884
1970
|
generated_at: new Date().toISOString(),
|
|
1885
1971
|
workspace_root_hash: workspaceRootHash,
|
|
1972
|
+
scope: scopeFields.scope,
|
|
1973
|
+
org_id: scopeFields.org_id,
|
|
1886
1974
|
attribution_scope: attribution.attribution_scope,
|
|
1887
1975
|
business_id: attribution.business_id || null,
|
|
1888
1976
|
workspace_id: attribution.workspace_id || null,
|
|
1889
1977
|
business_slug: attribution.business_slug || null,
|
|
1890
1978
|
workspace_template: attribution.workspace_template || null,
|
|
1979
|
+
computer_id: scopeFields.computer_id,
|
|
1891
1980
|
computer: attribution.computer || projection.workspace_name || workspaces[0]?.name || 'local',
|
|
1981
|
+
visibility: scopeFields.visibility,
|
|
1982
|
+
public_agentxp: scopeFields.public_agentxp,
|
|
1892
1983
|
operator: player,
|
|
1893
1984
|
privacy: {
|
|
1894
1985
|
raw_proofs_included: false,
|
|
@@ -1903,12 +1994,17 @@ function buildAgentXpSyncPacket(args = []) {
|
|
|
1903
1994
|
local_evidence: {
|
|
1904
1995
|
schema: 'atris.agentxp_local_evidence.v1',
|
|
1905
1996
|
workspace_root_hash: workspaceRootHash,
|
|
1997
|
+
scope: scopeFields.scope,
|
|
1998
|
+
org_id: scopeFields.org_id,
|
|
1906
1999
|
attribution_scope: attribution.attribution_scope,
|
|
1907
2000
|
business_id: attribution.business_id || null,
|
|
1908
2001
|
workspace_id: attribution.workspace_id || null,
|
|
1909
2002
|
business_slug: attribution.business_slug || null,
|
|
1910
2003
|
workspace_template: attribution.workspace_template || null,
|
|
2004
|
+
computer_id: scopeFields.computer_id,
|
|
1911
2005
|
computer: attribution.computer || null,
|
|
2006
|
+
visibility: scopeFields.visibility,
|
|
2007
|
+
public_agentxp: scopeFields.public_agentxp,
|
|
1912
2008
|
workspaces,
|
|
1913
2009
|
verified_workspace_count: asNumber(projection.verified_workspace_count, verifiedProjection(projection) ? 1 : 0),
|
|
1914
2010
|
receipts_count: receiptsCount,
|
|
@@ -1933,12 +2029,17 @@ function buildAgentXpSyncPacket(args = []) {
|
|
|
1933
2029
|
gm_projection: {
|
|
1934
2030
|
schema: 'atris.gm_xp_projection.v1',
|
|
1935
2031
|
workspace_root_hash: workspaceRootHash,
|
|
2032
|
+
scope: scopeFields.scope,
|
|
2033
|
+
org_id: scopeFields.org_id,
|
|
1936
2034
|
attribution_scope: attribution.attribution_scope,
|
|
1937
2035
|
business_id: attribution.business_id || null,
|
|
1938
2036
|
workspace_id: attribution.workspace_id || null,
|
|
1939
2037
|
business_slug: attribution.business_slug || null,
|
|
1940
2038
|
workspace_template: attribution.workspace_template || null,
|
|
2039
|
+
computer_id: scopeFields.computer_id,
|
|
1941
2040
|
computer: attribution.computer || null,
|
|
2041
|
+
visibility: scopeFields.visibility,
|
|
2042
|
+
public_agentxp: scopeFields.public_agentxp,
|
|
1942
2043
|
operator: player,
|
|
1943
2044
|
player_score: {
|
|
1944
2045
|
agent_xp: totalXp,
|