atris 3.58.5 → 3.58.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +8 -0
  2. package/atris/policies/engineering-principles.md +129 -0
  3. package/atris/policies/genesis.md +112 -0
  4. package/atris/policies/product-design-principles.md +100 -0
  5. package/atris/skills/design/SKILL.md +3 -1
  6. package/atris/skills/engines/SKILL.md +3 -3
  7. package/atris/skills/x-search/SKILL.md +2 -2
  8. package/atris/skills/youtube/SKILL.md +44 -28
  9. package/bin/atris.js +56 -3
  10. package/commands/auth.js +58 -24
  11. package/commands/brain.js +1 -0
  12. package/commands/design.js +362 -0
  13. package/commands/doc-health.js +329 -0
  14. package/commands/drive.js +32 -0
  15. package/commands/improve.js +67 -1
  16. package/commands/land.js +144 -4
  17. package/commands/learn.js +211 -40
  18. package/commands/member.js +65 -11
  19. package/commands/mission.js +37 -7
  20. package/commands/pulse.js +38 -0
  21. package/commands/rsi.js +156 -0
  22. package/commands/task.js +41 -1
  23. package/commands/workflow.js +15 -14
  24. package/commands/x-search.js +9 -10
  25. package/commands/youtube.js +518 -107
  26. package/lib/apply-gate.js +22 -4
  27. package/lib/daily-log.js +88 -0
  28. package/lib/design-api.js +130 -0
  29. package/lib/engine-ask.js +1 -1
  30. package/lib/first-minute.js +1 -6
  31. package/lib/known-commands.js +3 -3
  32. package/lib/member-context.js +42 -0
  33. package/lib/rsi-record.js +335 -0
  34. package/lib/state-detection.js +8 -8
  35. package/lib/task-db.js +71 -51
  36. package/lib/task-list-keeper.js +192 -0
  37. package/lib/todo-fallback.js +9 -3
  38. package/lib/todo.js +22 -10
  39. package/mcp/atris-mcp/index.mjs +174 -0
  40. package/package.json +8 -3
  41. package/scripts/det/ytnotes +122 -10
  42. package/utils/auth.js +109 -13
package/utils/auth.js CHANGED
@@ -202,6 +202,51 @@ function loadPlacedAgentToken() {
202
202
  }
203
203
  }
204
204
 
205
+ function leftoverStoredAgentToken(parsed, envToken) {
206
+ if (!parsed || !envToken) return null;
207
+ const leftover = typeof parsed.agent_token === 'string' ? parsed.agent_token.trim() : '';
208
+ if (!leftover || leftover !== envToken) return null;
209
+ const expiresAt = parsed.agent_token_expires_at;
210
+ if (expiryTimeMs(expiresAt) === null) return null;
211
+ const scopes = Array.isArray(parsed.agent_token_scopes) ? parsed.agent_token_scopes : [];
212
+ return {
213
+ token: leftover,
214
+ expires_at: expiresAt,
215
+ scopes,
216
+ provider: null,
217
+ source: 'env',
218
+ agent_token: leftover,
219
+ agent_token_scopes: scopes,
220
+ agent_token_expires_at: expiresAt,
221
+ };
222
+ }
223
+
224
+ function loadLeftoverCredentialsAgentToken(envToken) {
225
+ if (!envToken) return null;
226
+ try {
227
+ return leftoverStoredAgentToken(JSON.parse(fs.readFileSync(getCredentialsPath(), 'utf8')), envToken);
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+
233
+ function resolveProfileOverride(profileOverride) {
234
+ if (!profileOverride) return null;
235
+ if (loadProfile(profileOverride)) return profileOverride;
236
+ const profiles = listProfiles();
237
+ const q = String(profileOverride).toLowerCase();
238
+ return profiles.find((name) => name.toLowerCase() === q)
239
+ || profiles.find((name) => name.toLowerCase().startsWith(q))
240
+ || profiles.find((name) => name.toLowerCase().includes(q))
241
+ || null;
242
+ }
243
+
244
+ function loadLeftoverProfileAgentToken(envToken) {
245
+ const name = resolveProfileOverride(process.env.ATRIS_PROFILE) || getSessionProfile();
246
+ if (!envToken || !name) return null;
247
+ return leftoverStoredAgentToken(loadProfile(name), envToken);
248
+ }
249
+
205
250
  function isUnexpiredCredential(credentials) {
206
251
  const expiresAt = expiryTimeMs(credentials?.expires_at);
207
252
  return expiresAt !== null && expiresAt > Date.now();
@@ -352,9 +397,13 @@ function autoSaveProfile(credentials) {
352
397
  }
353
398
  }
354
399
 
355
- function saveCredentials(token, refreshToken, email, userId, provider) {
400
+ function saveCredentials(token, refreshToken, email, userId, provider, extras = {}) {
401
+ if (decodeJwtClaims(token)?.type === 'agent_access') {
402
+ throw new Error('Refusing to save a scoped agent token as the login token; keep it under agent_token');
403
+ }
356
404
  const credentialsPath = getCredentialsPath();
357
405
  const credentials = {
406
+ ...extras,
358
407
  token,
359
408
  refresh_token: refreshToken || null,
360
409
  email: email || null,
@@ -374,10 +423,47 @@ function saveCredentials(token, refreshToken, email, userId, provider) {
374
423
  autoSaveProfile(credentials);
375
424
  }
376
425
 
377
- function loadCredentials() {
426
+ // Supplying the auth client enables asynchronous repair. Local-only readers stay synchronous.
427
+ function loadCredentials(apiRequestJson) {
428
+ const credentials = readCredentials();
429
+ if (!credentials || credentials.source || decodeJwtClaims(credentials.token)?.type !== 'agent_access') {
430
+ return credentials;
431
+ }
432
+ if (!credentials.refresh_token) {
433
+ console.error('atris login');
434
+ return credentials;
435
+ }
436
+ if (!apiRequestJson) return credentials;
437
+ return repairLoginCredentials(credentials, apiRequestJson);
438
+ }
439
+
440
+ async function repairLoginCredentials(credentials, apiRequestJson) {
441
+ const refreshed = await refreshAccessToken(credentials.refresh_token, null, apiRequestJson);
442
+ const token = refreshed.data?.access_token;
443
+ if (!refreshed.ok || !token || decodeJwtClaims(token)?.type === 'agent_access') {
444
+ throw new Error('Could not repair login file. Run atris login');
445
+ }
446
+ const claims = decodeJwtClaims(credentials.token);
447
+ const { source_profile, ...rest } = credentials;
448
+ const next = {
449
+ ...rest,
450
+ token,
451
+ refresh_token: refreshed.data.refresh_token || credentials.refresh_token,
452
+ agent_token: credentials.token,
453
+ agent_token_scopes: claims.scopes || [],
454
+ agent_token_expires_at: typeof claims.exp === 'number' ? new Date(claims.exp * 1000).toISOString() : null,
455
+ };
456
+ if (source_profile) saveProfile(source_profile, next);
457
+ else saveCredentials(token, next.refresh_token, next.email, next.user_id, next.provider, next);
458
+ console.error('Repaired login file: moved a scoped agent token aside.');
459
+ return { ...next, ...(source_profile ? { source_profile } : {}) };
460
+ }
461
+
462
+ function readCredentials() {
378
463
  // Priority: ATRIS_TOKEN env var → placed agent token → ATRIS_PROFILE env var
379
464
  // → per-terminal session file → global credentials.json. A fresh placed token
380
- // overrides a different env token because cloud images can carry a stale one.
465
+ // overrides env when the leftover is newer or when env only repeats that
466
+ // leftover, so billed auth still sees leftover scopes.
381
467
 
382
468
  // 0. Raw token injection. Headless boxes (cloud business computers) have no
383
469
  // browser for `atris login`; the runner injects a scoped token as env
@@ -385,15 +471,24 @@ function loadCredentials() {
385
471
  const envToken = process.env.ATRIS_TOKEN;
386
472
  const normalizedEnvToken = envToken && envToken.trim();
387
473
  const placedAgentToken = loadPlacedAgentToken();
388
- if (
389
- normalizedEnvToken &&
390
- placedAgentToken &&
391
- placedAgentToken.token !== normalizedEnvToken &&
392
- isUnexpiredCredential(placedAgentToken)
393
- ) {
474
+ if (normalizedEnvToken && placedAgentToken && isUnexpiredCredential(placedAgentToken)) {
475
+ // Fresh placed leftover wins over env, including when env repeats the same
476
+ // token, so billed auth still sees leftover scopes and does not remint.
394
477
  return placedAgentToken;
395
478
  }
396
479
  if (normalizedEnvToken) {
480
+ const leftoverCredentials = loadLeftoverCredentialsAgentToken(normalizedEnvToken);
481
+ if (leftoverCredentials && isUnexpiredCredential(leftoverCredentials)) {
482
+ // Env repeating leftover credentials.agent_token keeps leftover scopes
483
+ // and expiry so billed auth does not remint.
484
+ return leftoverCredentials;
485
+ }
486
+ const leftoverProfile = loadLeftoverProfileAgentToken(normalizedEnvToken);
487
+ if (leftoverProfile && isUnexpiredCredential(leftoverProfile)) {
488
+ // Env repeating leftover profile or session-profile agent_token keeps
489
+ // leftover scopes and expiry so billed auth does not remint.
490
+ return leftoverProfile;
491
+ }
397
492
  return { token: normalizedEnvToken, provider: null, source: 'env' };
398
493
  }
399
494
 
@@ -552,7 +647,7 @@ async function performTokenRefresh(credentials, apiRequestJson) {
552
647
  saved_at: new Date().toISOString(),
553
648
  });
554
649
  } else {
555
- saveCredentials(accessToken, newRefreshToken, email, userId, provider);
650
+ saveCredentials(accessToken, newRefreshToken, email, userId, provider, credentials);
556
651
  }
557
652
  };
558
653
 
@@ -586,7 +681,7 @@ async function performTokenRefresh(credentials, apiRequestJson) {
586
681
  saved_at: new Date().toISOString(),
587
682
  });
588
683
  } else {
589
- saveCredentials(accessToken, newRefreshToken, updatedEmail, updatedUserId, updatedProvider);
684
+ saveCredentials(accessToken, newRefreshToken, updatedEmail, updatedUserId, updatedProvider, credentials);
590
685
  }
591
686
  latestCreds = loadCredentials();
592
687
  }
@@ -603,7 +698,7 @@ async function performTokenRefresh(credentials, apiRequestJson) {
603
698
  }
604
699
 
605
700
  async function ensureValidCredentials(apiRequestJson, options = {}) {
606
- let credentials = loadCredentials();
701
+ let credentials = await loadCredentials(apiRequestJson);
607
702
  if (!credentials || !credentials.token) {
608
703
  return { error: 'not_logged_in' };
609
704
  }
@@ -659,7 +754,8 @@ async function ensureValidCredentials(apiRequestJson, options = {}) {
659
754
  credentials.refresh_token,
660
755
  updatedEmail,
661
756
  updatedUserId,
662
- updatedProvider
757
+ updatedProvider,
758
+ credentials
663
759
  );
664
760
  }
665
761
  }