atris 3.15.55 → 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 +171 -5
- package/commands/xp.js +53 -8
- package/package.json +1 -1
package/commands/integrations.js
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* atris calendar date YYYY-MM-DD - Show events on a date
|
|
11
11
|
* atris twitter post - Post a tweet (interactive)
|
|
12
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
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
const { loadCredentials, ensureValidCredentials } = require('../utils/auth');
|
|
@@ -163,6 +165,29 @@ function localDateBounds(dateText) {
|
|
|
163
165
|
return { start, end };
|
|
164
166
|
}
|
|
165
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
|
+
|
|
166
191
|
function formatCalendarEvents(label, events) {
|
|
167
192
|
if (events.length === 0) {
|
|
168
193
|
console.log(`No events ${label}. 🎉`);
|
|
@@ -172,8 +197,7 @@ function formatCalendarEvents(label, events) {
|
|
|
172
197
|
console.log('─'.repeat(50));
|
|
173
198
|
|
|
174
199
|
for (const event of events) {
|
|
175
|
-
const
|
|
176
|
-
const time = start ? new Date(start).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) : 'All day';
|
|
200
|
+
const time = formatCalendarTimeRange(event);
|
|
177
201
|
const title = event.summary || '(no title)';
|
|
178
202
|
|
|
179
203
|
console.log(`${time} ${title}`);
|
|
@@ -331,7 +355,7 @@ async function slackChannels() {
|
|
|
331
355
|
|
|
332
356
|
console.log('💬 Fetching Slack channels...\n');
|
|
333
357
|
|
|
334
|
-
const result = await apiRequestJson('/integrations/slack/channels', {
|
|
358
|
+
const result = await apiRequestJson('/integrations/slack/me/channels', {
|
|
335
359
|
method: 'GET',
|
|
336
360
|
token,
|
|
337
361
|
});
|
|
@@ -359,8 +383,137 @@ async function slackChannels() {
|
|
|
359
383
|
for (const ch of channels) {
|
|
360
384
|
const name = ch.name || ch.id;
|
|
361
385
|
const priv = ch.is_private ? '🔒' : '#';
|
|
362
|
-
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]);
|
|
363
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)'}`);
|
|
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);
|
|
364
517
|
}
|
|
365
518
|
|
|
366
519
|
async function slackCommand(subcommand, ...args) {
|
|
@@ -369,9 +522,22 @@ async function slackCommand(subcommand, ...args) {
|
|
|
369
522
|
case 'list':
|
|
370
523
|
await slackChannels();
|
|
371
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;
|
|
372
535
|
default:
|
|
373
536
|
console.log('Slack commands:');
|
|
374
|
-
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');
|
|
375
541
|
}
|
|
376
542
|
}
|
|
377
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
|
|
|
@@ -1792,19 +1824,22 @@ function uniqueTruthy(values) {
|
|
|
1792
1824
|
return Array.from(new Set(values.map(value => String(value || '').trim()).filter(Boolean))).sort();
|
|
1793
1825
|
}
|
|
1794
1826
|
|
|
1795
|
-
function syncAttribution(workspaces) {
|
|
1827
|
+
function syncAttribution(workspaces, options = {}) {
|
|
1796
1828
|
const included = workspaces.filter(workspace => workspace.included !== false);
|
|
1797
1829
|
const scoped = included.length ? included : workspaces;
|
|
1798
1830
|
const businessIds = uniqueTruthy(scoped.map(workspace => workspace.business_id));
|
|
1799
1831
|
const workspaceIds = uniqueTruthy(scoped.map(workspace => workspace.workspace_id));
|
|
1800
1832
|
const businessSlugs = uniqueTruthy(scoped.map(workspace => workspace.business_slug || workspace.computer_slug));
|
|
1801
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;
|
|
1802
1837
|
|
|
1803
1838
|
if (businessIds.length > 1) {
|
|
1804
|
-
return { attribution_scope: 'multi_business', computer: 'multiple-workspaces' };
|
|
1839
|
+
return { attribution_scope: 'multi_business', computer: 'multiple-workspaces', agentxp_public: explicitPublic === true };
|
|
1805
1840
|
}
|
|
1806
1841
|
if (businessIds.length === 0 && scoped.length > 1 && businessSlugs.length > 1) {
|
|
1807
|
-
return { attribution_scope: 'multi_workspace', computer: 'multiple-workspaces' };
|
|
1842
|
+
return { attribution_scope: 'multi_workspace', computer: 'multiple-workspaces', agentxp_public: explicitPublic === true };
|
|
1808
1843
|
}
|
|
1809
1844
|
|
|
1810
1845
|
const businessId = businessIds[0] || null;
|
|
@@ -1818,6 +1853,7 @@ function syncAttribution(workspaces) {
|
|
|
1818
1853
|
business_slug: businessSlug,
|
|
1819
1854
|
workspace_template: workspaceTemplate,
|
|
1820
1855
|
computer: businessSlug || scoped[0]?.computer || scoped[0]?.name || 'local',
|
|
1856
|
+
agentxp_public: agentxpPublic,
|
|
1821
1857
|
};
|
|
1822
1858
|
}
|
|
1823
1859
|
|
|
@@ -1825,15 +1861,24 @@ function syncScopeFields(attribution = {}) {
|
|
|
1825
1861
|
const attributionScope = slugify(attribution.attribution_scope);
|
|
1826
1862
|
const orgId = String(attribution.business_id || attribution.business_slug || '').trim() || null;
|
|
1827
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;
|
|
1828
1867
|
return {
|
|
1829
|
-
scope: businessBound ? 'org' : 'global',
|
|
1868
|
+
scope: publicAgentXp ? 'global' : (businessBound ? 'org' : 'global'),
|
|
1830
1869
|
org_id: orgId,
|
|
1831
1870
|
computer_id: String(attribution.workspace_id || attribution.computer || 'local').trim() || 'local',
|
|
1832
|
-
visibility:
|
|
1833
|
-
public_agentxp:
|
|
1871
|
+
visibility: publicAgentXp ? 'public' : 'internal',
|
|
1872
|
+
public_agentxp: publicAgentXp,
|
|
1834
1873
|
};
|
|
1835
1874
|
}
|
|
1836
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
|
+
|
|
1837
1882
|
function credentialHandle(credentials) {
|
|
1838
1883
|
return slugify(
|
|
1839
1884
|
credentials?.username
|
|
@@ -1886,13 +1931,13 @@ function syncPlayer(args, projection) {
|
|
|
1886
1931
|
|
|
1887
1932
|
function buildAgentXpSyncPacket(args = []) {
|
|
1888
1933
|
const localMode = hasFlag(args, '--local') || hasFlag(args, '--workspace') || hasFlag(args, '--operator');
|
|
1889
|
-
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));
|
|
1890
1935
|
const projection = hasFlag(args, '--all') || !localMode
|
|
1891
1936
|
? collectAllLocalXpProjection(projectionArgs)
|
|
1892
1937
|
: collectLocalXpProjection(projectionArgs);
|
|
1893
1938
|
const player = syncPlayer(args, projection);
|
|
1894
1939
|
const workspaces = projectionWorkspaceSummaries(projection);
|
|
1895
|
-
const attribution = syncAttribution(workspaces);
|
|
1940
|
+
const attribution = syncAttribution(workspaces, { publicAgentXp: publicAgentXpOverride(args) });
|
|
1896
1941
|
const scopeFields = syncScopeFields(attribution);
|
|
1897
1942
|
const totalXp = asNumber(projection.total_agent_xp ?? projection.agent_xp ?? projection.total_xp ?? projection.career_xp);
|
|
1898
1943
|
const receiptsCount = asNumber(projection.receipts_count);
|