atris 2.3.0 → 2.3.3

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.
@@ -144,12 +144,12 @@ curl -s -X POST "https://api.atris.ai/api/integrations/google-calendar/events" \
144
144
  -H "Authorization: Bearer $TOKEN" \
145
145
  -H "Content-Type: application/json" \
146
146
  -d '{
147
- "summary": "Meeting with Sushanth",
147
+ "summary": "Meeting with Hugo",
148
148
  "start": "2026-02-15T14:00:00-08:00",
149
149
  "end": "2026-02-15T15:00:00-08:00",
150
- "description": "Discuss AI transformation roadmap",
150
+ "description": "Discuss project roadmap",
151
151
  "location": "Zoom",
152
- "attendees": ["sushanth@pallet.com"],
152
+ "attendees": ["hugo@atrismail.com"],
153
153
  "timezone": "America/Los_Angeles"
154
154
  }'
155
155
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: atris
3
- description: Codebase intelligence — generates structured navigation maps with file:line references so agents stop re-scanning the same files every session. Use when exploring code, answering "where is X?", or onboarding to a new codebase.
3
+ description: "Codebase intelligence — generates structured navigation maps with file:line references so agents stop re-scanning the same files every session. Use when exploring code, answering 'where is X?', or onboarding to a new codebase."
4
4
  version: 1.1.0
5
5
  requires:
6
6
  bins:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: drive
3
3
  description: Google Drive integration via AtrisOS API. Browse, search, read, upload files and work with Google Sheets. Use when user asks about Drive, files, docs, sheets, or spreadsheets.
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  tags:
6
6
  - drive
7
7
  - backend
@@ -199,6 +199,29 @@ curl -s -X POST "https://api.atris.ai/api/integrations/google-drive/files" \
199
199
  }'
200
200
  ```
201
201
 
202
+ ### Update Existing File
203
+ ```bash
204
+ curl -s -X PUT "https://api.atris.ai/api/integrations/google-drive/files/{file_id}" \
205
+ -H "Authorization: Bearer $TOKEN" \
206
+ -H "Content-Type: application/json" \
207
+ -d '{
208
+ "content": "Updated file content here",
209
+ "mime_type": "text/plain"
210
+ }'
211
+ ```
212
+
213
+ **Update content and rename:**
214
+ ```bash
215
+ curl -s -X PUT "https://api.atris.ai/api/integrations/google-drive/files/{file_id}" \
216
+ -H "Authorization: Bearer $TOKEN" \
217
+ -H "Content-Type: application/json" \
218
+ -d '{
219
+ "content": "New content",
220
+ "mime_type": "text/plain",
221
+ "name": "renamed-file.txt"
222
+ }'
223
+ ```
224
+
202
225
  ---
203
226
 
204
227
  ## Google Sheets
@@ -299,6 +322,14 @@ curl -s -X POST "https://api.atris.ai/api/integrations/google-drive/sheets/{spre
299
322
  3. **Confirm with user**: "Upload {filename} to Drive?"
300
323
  4. Upload: `POST /google-drive/files` with `{name, content, mime_type}`
301
324
 
325
+ ### "Edit a file on Drive"
326
+ 1. Run bootstrap
327
+ 2. Find the file: `GET /google-drive/search?q=FILENAME`
328
+ 3. Read current content: `GET /google-drive/files/{id}/export?mime_type=text/plain`
329
+ 4. Make edits
330
+ 5. **Show user the changes for approval**
331
+ 6. Update: `PUT /google-drive/files/{id}` with `{content, mime_type}`
332
+
302
333
  ---
303
334
 
304
335
  ## Error Handling
@@ -356,8 +387,13 @@ curl -s -X POST "https://api.atris.ai/api/integrations/google-drive/sheets/{id}/
356
387
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
357
388
  -d '{"range":"Sheet1","values":[["Alice",95]]}'
358
389
 
359
- # Upload a file
390
+ # Upload a new file
360
391
  curl -s -X POST "https://api.atris.ai/api/integrations/google-drive/files" \
361
392
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
362
393
  -d '{"name":"notes.txt","content":"Hello world","mime_type":"text/plain"}'
394
+
395
+ # Update an existing file
396
+ curl -s -X PUT "https://api.atris.ai/api/integrations/google-drive/files/{file_id}" \
397
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
398
+ -d '{"content":"Updated content","mime_type":"text/plain"}'
363
399
  ```
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: email-agent
3
3
  description: Gmail integration via AtrisOS API. Read, send, archive emails. Use when user asks about email, inbox, or wants to send/check messages.
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  tags:
6
6
  - email-agent
7
7
  - backend
@@ -158,6 +158,33 @@ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
158
158
  }'
159
159
  ```
160
160
 
161
+ **Reply in thread (IMPORTANT — use this for all replies):**
162
+
163
+ To reply within an existing email thread, you MUST pass `thread_id` and `reply_to_message_id`. Without these, Gmail creates a new thread.
164
+
165
+ ```bash
166
+ # 1. First, get the message you're replying to (extract thread_id and id)
167
+ curl -s "https://api.atris.ai/api/integrations/gmail/messages/{message_id}" \
168
+ -H "Authorization: Bearer $TOKEN"
169
+ # Response includes: id, thread_id, subject, from, etc.
170
+
171
+ # 2. Send reply in the same thread
172
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
173
+ -H "Authorization: Bearer $TOKEN" \
174
+ -H "Content-Type: application/json" \
175
+ -d '{
176
+ "to": "original-sender@example.com",
177
+ "subject": "Re: Original Subject",
178
+ "body": "Your reply text here",
179
+ "thread_id": "THREAD_ID_FROM_STEP_1",
180
+ "reply_to_message_id": "MESSAGE_ID_FROM_STEP_1"
181
+ }'
182
+ ```
183
+
184
+ - `thread_id` — The thread ID from the original message. Tells Gmail which thread to add this to.
185
+ - `reply_to_message_id` — The message ID you're replying to. The backend uses this to set `In-Reply-To` and `References` headers so Gmail threads it correctly.
186
+ - `subject` — Must match the original subject with "Re: " prefix.
187
+
161
188
  **With attachments:**
162
189
  ```bash
163
190
  curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
@@ -223,6 +250,17 @@ curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}
223
250
  -H "Authorization: Bearer $TOKEN"
224
251
  ```
225
252
 
253
+ ### Mark as Read / Unread
254
+ ```bash
255
+ # Mark as read
256
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/read" \
257
+ -H "Authorization: Bearer $TOKEN"
258
+
259
+ # Mark as unread
260
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/unread" \
261
+ -H "Authorization: Bearer $TOKEN"
262
+ ```
263
+
226
264
  ### Archive Email
227
265
  ```bash
228
266
  # Single message
@@ -236,6 +274,19 @@ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/batch-arch
236
274
  -d '{"message_ids": ["id1", "id2", "id3"]}'
237
275
  ```
238
276
 
277
+ ### Trash Email
278
+ ```bash
279
+ # Single message
280
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/trash" \
281
+ -H "Authorization: Bearer $TOKEN"
282
+
283
+ # Batch trash
284
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/batch-trash" \
285
+ -H "Authorization: Bearer $TOKEN" \
286
+ -H "Content-Type: application/json" \
287
+ -d '{"message_ids": ["id1", "id2", "id3"]}'
288
+ ```
289
+
239
290
  ### Check Status
240
291
  ```bash
241
292
  curl -s "https://api.atris.ai/api/integrations/gmail/status" \
@@ -264,6 +315,14 @@ curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail" \
264
315
  4. On approval: `POST /send` with `{to, subject, body}`
265
316
  5. Confirm: "Email sent!"
266
317
 
318
+ ### "Reply to this email"
319
+ 1. Run bootstrap
320
+ 2. Read the message: `GET /messages/{message_id}` — extract `id`, `thread_id`, `from`, `subject`
321
+ 3. Draft reply content
322
+ 4. **Show user the reply for approval**
323
+ 5. On approval: `POST /send` with `{to, subject: "Re: ...", body, thread_id, reply_to_message_id}`
324
+ 6. Verify: response `thread_id` matches original thread_id (if it doesn't, something went wrong)
325
+
267
326
  ### "Clean up my inbox"
268
327
  1. Run bootstrap
269
328
  2. List: `GET /messages?query=in:inbox&max_results=50`
@@ -357,11 +416,16 @@ curl -s "https://api.atris.ai/api/integrations/gmail/status" -H "Authorization:
357
416
  # List inbox
358
417
  curl -s "https://api.atris.ai/api/integrations/gmail/messages?query=in:inbox&max_results=10" -H "Authorization: Bearer $TOKEN"
359
418
 
360
- # Send email
419
+ # Send new email
361
420
  curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
362
421
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
363
422
  -d '{"to":"email@example.com","subject":"Hi","body":"Hello!"}'
364
423
 
424
+ # Reply in thread (pass thread_id + reply_to_message_id)
425
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/send" \
426
+ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
427
+ -d '{"to":"sender@example.com","subject":"Re: Original","body":"Reply text","thread_id":"THREAD_ID","reply_to_message_id":"MSG_ID"}'
428
+
365
429
  # List drafts
366
430
  curl -s "https://api.atris.ai/api/integrations/gmail/drafts" -H "Authorization: Bearer $TOKEN"
367
431
 
@@ -370,6 +434,12 @@ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts" \
370
434
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
371
435
  -d '{"to":"email@example.com","subject":"Hi","body":"Draft text"}'
372
436
 
437
+ # Mark as read
438
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/read" -H "Authorization: Bearer $TOKEN"
439
+
440
+ # Trash an email
441
+ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/{message_id}/trash" -H "Authorization: Bearer $TOKEN"
442
+
373
443
  # Send a draft
374
444
  curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}/send" \
375
445
  -H "Authorization: Bearer $TOKEN"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: slack
3
3
  description: Slack integration via AtrisOS API. Read messages, send as yourself, search conversations, manage DMs. Use when user asks about Slack, messages, channels, or team communication.
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  tags:
6
6
  - slack
7
7
  - backend
@@ -187,6 +187,14 @@ curl -s "https://api.atris.ai/api/integrations/slack/users" \
187
187
  -H "Authorization: Bearer $TOKEN"
188
188
  ```
189
189
 
190
+ ### Search Users
191
+ ```bash
192
+ curl -s "https://api.atris.ai/api/integrations/slack/users/search?q=justin" \
193
+ -H "Authorization: Bearer $TOKEN"
194
+ ```
195
+
196
+ Searches by name, display name, or email. Much faster than pulling the full user list.
197
+
190
198
  ### Send as Bot
191
199
  ```bash
192
200
  curl -s -X POST "https://api.atris.ai/api/integrations/slack/test-send" \
@@ -223,9 +231,9 @@ curl -s -X DELETE "https://api.atris.ai/api/integrations/slack" \
223
231
 
224
232
  ### "Send a message to someone"
225
233
  1. Run bootstrap
226
- 2. Find the user: `GET /slack/users` (search by name/email)
234
+ 2. Find the user: `GET /slack/users/search?q=NAME`
227
235
  3. **Show user the draft for approval**
228
- 4. Send DM: `POST /slack/me/dm` with `{user_id, text}`
236
+ 4. Send DM: `POST /slack/me/dm` with `{slack_user_id, text}`
229
237
  5. Confirm: "Message sent!"
230
238
 
231
239
  ### "Reply in a channel"
@@ -243,7 +251,7 @@ curl -s -X DELETE "https://api.atris.ai/api/integrations/slack" \
243
251
 
244
252
  ### "What did [person] say to me?"
245
253
  1. Run bootstrap
246
- 2. List users: `GET /slack/users` (find user ID)
254
+ 2. Find user: `GET /slack/users/search?q=NAME` (get user ID)
247
255
  3. List DMs: `GET /slack/me/dms` (find DM channel with that user)
248
256
  4. Read messages: `GET /slack/me/messages/{channel_id}`
249
257
  5. Display conversation
@@ -317,4 +325,7 @@ curl -s "https://api.atris.ai/api/integrations/slack/me/search?q=project+update"
317
325
 
318
326
  # List workspace users
319
327
  curl -s "https://api.atris.ai/api/integrations/slack/users" -H "Authorization: Bearer $TOKEN"
328
+
329
+ # Search users by name
330
+ curl -s "https://api.atris.ai/api/integrations/slack/users/search?q=justin" -H "Authorization: Bearer $TOKEN"
320
331
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: writing
3
- description: Essay writing skill. Triggers on: essay, draft, write, outline
3
+ description: "Essay writing skill. Triggers on: essay, draft, write, outline"
4
4
  version: 1.0.0
5
5
  allowed-tools: Read, Write, Edit, Glob, Grep
6
6
  tags:
package/bin/atris.js CHANGED
@@ -56,6 +56,17 @@ if (!skipUpdateCheck && (!process.argv[2] || (process.argv[2] && !['version', 'u
56
56
 
57
57
  const command = process.argv[2];
58
58
 
59
+ // Auto-sync skills on every command (fast — just file diffs, no network)
60
+ try {
61
+ const { syncSkills } = require('../commands/sync');
62
+ const skillsUpdated = syncSkills({ silent: true });
63
+ if (skillsUpdated > 0) {
64
+ console.log(`⬆️ ${skillsUpdated} skill${skillsUpdated > 1 ? 's' : ''} updated`);
65
+ }
66
+ } catch (e) {
67
+ // Non-critical
68
+ }
69
+
59
70
  const TOKEN_REFRESH_BUFFER_SECONDS = 300; // Refresh ~5 minutes before expiry
60
71
 
61
72
  function decodeJwtClaims(token) {
@@ -197,6 +208,8 @@ function showHelp() {
197
208
  console.log(' login - Authenticate (use --token <t> for non-interactive)');
198
209
  console.log(' logout - Remove credentials');
199
210
  console.log(' whoami - Show auth status');
211
+ console.log(' switch - Switch account (atris switch <name>)');
212
+ console.log(' accounts - List saved accounts');
200
213
  console.log('');
201
214
  console.log('Integrations:');
202
215
  console.log(' gmail - Email commands (inbox, read)');
@@ -210,6 +223,11 @@ function showHelp() {
210
223
  console.log(' skill audit [name] - Validate skill against Anthropic guide');
211
224
  console.log(' skill fix [name] - Auto-fix common compliance issues');
212
225
  console.log('');
226
+ console.log('Plugin:');
227
+ console.log(' plugin build - Package skills as .plugin for Cowork');
228
+ console.log(' plugin publish - Sync skills to marketplace repo and push');
229
+ console.log(' plugin info - Preview what will be included');
230
+ console.log('');
213
231
  console.log('Other:');
214
232
  console.log(' version - Show Atris version');
215
233
  console.log(' help - Show this help');
@@ -304,7 +322,7 @@ const { initAtris: initCmd } = require('../commands/init');
304
322
  const { syncAtris: syncCmd } = require('../commands/sync');
305
323
  const { logAtris: logCmd } = require('../commands/log');
306
324
  const { logSyncAtris: logSyncCmd } = require('../commands/log-sync');
307
- const { loginAtris: loginCmd, logoutAtris: logoutCmd, whoamiAtris: whoamiCmd } = require('../commands/auth');
325
+ const { loginAtris: loginCmd, logoutAtris: logoutCmd, whoamiAtris: whoamiCmd, switchAccount: switchCmd, listAccountsCmd: accountsCmd } = require('../commands/auth');
308
326
  const { showVersion: versionCmd } = require('../commands/version');
309
327
  const { planAtris: planCmd, doAtris: doCmd, reviewAtris: reviewCmd } = require('../commands/workflow');
310
328
  const { visualizeAtris: visualizeCmd } = require('../commands/visualize');
@@ -316,11 +334,12 @@ const { analyticsAtris: analyticsCmd } = require('../commands/analytics');
316
334
  const { cleanAtris: cleanCmd } = require('../commands/clean');
317
335
  const { verifyAtris: verifyCmd } = require('../commands/verify');
318
336
  const { skillCommand: skillCmd } = require('../commands/skill');
337
+ const { pluginCommand: pluginCmd } = require('../commands/plugin');
319
338
 
320
339
  // Check if this is a known command or natural language input
321
340
  const knownCommands = ['init', 'log', 'status', 'analytics', 'visualize', 'brainstorm', 'autopilot', 'plan', 'do', 'review',
322
- 'activate', 'agent', 'chat', 'login', 'logout', 'whoami', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
323
- 'clean', 'verify', 'search', 'skill',
341
+ 'activate', 'agent', 'chat', 'login', 'logout', 'whoami', 'switch', 'accounts', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
342
+ 'clean', 'verify', 'search', 'skill', 'plugin',
324
343
  'gmail', 'calendar', 'twitter', 'slack', 'integrations'];
325
344
 
326
345
  // Check if command is an atris.md spec file - triggers welcome visualization
@@ -650,6 +669,10 @@ if (command === 'init') {
650
669
  logoutCmd();
651
670
  } else if (command === 'whoami') {
652
671
  whoamiCmd();
672
+ } else if (command === 'switch') {
673
+ switchCmd();
674
+ } else if (command === 'accounts') {
675
+ accountsCmd();
653
676
  } else if (command === 'visualize') {
654
677
  console.log('ℹ️ "atris visualize" is a legacy helper. Visualization is now built into "atris plan".');
655
678
  console.log(' Prefer: atris plan');
@@ -802,6 +825,10 @@ if (command === 'init') {
802
825
  const subcommand = process.argv[3];
803
826
  const args = process.argv.slice(4);
804
827
  skillCmd(subcommand, ...args);
828
+ } else if (command === 'plugin') {
829
+ const subcommand = process.argv[3] || 'build';
830
+ const args = process.argv.slice(4);
831
+ pluginCmd(subcommand, ...args);
805
832
  } else {
806
833
  console.log(`Unknown command: ${command}`);
807
834
  console.log('Run "atris help" to see available commands');
@@ -2633,109 +2660,6 @@ async function atrisDevEntry(userInput = null) {
2633
2660
  console.log('');
2634
2661
  }
2635
2662
 
2636
- function launchAtris() {
2637
- const targetDir = path.join(process.cwd(), 'atris');
2638
- const launcherFile = path.join(targetDir, 'team', 'launcher.md');
2639
-
2640
- if (!fs.existsSync(launcherFile)) {
2641
- console.log('✗ launcher.md not found. Run "atris init" first.');
2642
- process.exit(1);
2643
- }
2644
-
2645
- // Read launcher.md
2646
- const launcherSpec = fs.readFileSync(launcherFile, 'utf8');
2647
-
2648
- // Reference TODO.md (agents read on-demand, legacy TASK_CONTEXTS.md supported)
2649
- const todoFile = path.join(targetDir, 'TODO.md');
2650
- const legacyTaskContextsFile = path.join(targetDir, 'TASK_CONTEXTS.md');
2651
-
2652
- // Reference MAP.md (agents read on-demand)
2653
- const mapFile = path.join(targetDir, 'MAP.md');
2654
- const mapPath = fs.existsSync(mapFile) ? path.relative(process.cwd(), mapFile) : null;
2655
-
2656
- // Reference journal (agents read on-demand)
2657
- const { logFile, dateFormatted } = getLogPath();
2658
- let journalPath = '';
2659
- if (fs.existsSync(logFile)) {
2660
- journalPath = path.relative(process.cwd(), logFile);
2661
- }
2662
-
2663
- console.log('');
2664
- console.log('┌─────────────────────────────────────────────────────────────┐');
2665
- console.log('│ Atris Launch — Launcher Agent Activated │');
2666
- console.log('└─────────────────────────────────────────────────────────────┘');
2667
- console.log('');
2668
- console.log('📋 AGENT SPEC:');
2669
- console.log('─────────────────────────────────────────────────────────────');
2670
- console.log(launcherSpec);
2671
- console.log('');
2672
- console.log('─────────────────────────────────────────────────────────────');
2673
- console.log('');
2674
- const taskFilePath = fs.existsSync(todoFile)
2675
- ? todoFile
2676
- : (fs.existsSync(legacyTaskContextsFile) ? legacyTaskContextsFile : null);
2677
- const taskContextsPath = taskFilePath ? path.relative(process.cwd(), taskFilePath) : null;
2678
- console.log('📝 TODO.md: ' + (taskContextsPath || 'Not found'));
2679
- console.log(' Read for completed tasks context (usually small, or reference path if large).');
2680
- console.log('');
2681
- console.log('─────────────────────────────────────────────────────────────');
2682
- console.log('');
2683
- console.log('🗺️ MAP.md: ' + (mapPath || 'Not found'));
2684
- console.log(' Read this file for file:line references when navigating the codebase.');
2685
- console.log('');
2686
- console.log('─────────────────────────────────────────────────────────────');
2687
- console.log('');
2688
- console.log('📅 JOURNAL: ' + (journalPath || 'Not found'));
2689
- console.log(' Read for recent completions and context.');
2690
- console.log('');
2691
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2692
- console.log('📋 INSTRUCTION PROMPT FOR YOUR CODING AGENT:');
2693
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2694
- console.log('');
2695
- console.log('You are the Launcher. Ship it clean.');
2696
- console.log('');
2697
- console.log('⚠️ CRITICAL: Execute these steps NOW using terminal and file tools:');
2698
- console.log('');
2699
- // Detect if this is the atris package project or a user project
2700
- const isAtrisPackage = fs.existsSync(path.join(process.cwd(), 'package.json')) &&
2701
- fs.existsSync(path.join(process.cwd(), 'bin', 'atris.js')) &&
2702
- fs.existsSync(path.join(process.cwd(), 'atris.md'));
2703
-
2704
- console.log('Launch Workflow:');
2705
- console.log(' 1. Document what was shipped (add Launch entry to journal Notes section)');
2706
- console.log(' 2. Extract learnings (what worked? what would you do differently?)');
2707
- console.log(' 3. Update MAP.md with new patterns/file locations');
2708
- console.log(' 4. Update relevant docs (README, API docs, etc.)');
2709
- console.log(' 5. Clean up (remove temp files, unused code, etc.)');
2710
- if (isAtrisPackage) {
2711
- console.log(' 6. [EXECUTE] Test locally (package development):');
2712
- console.log(' - Run: npm link (link package for local testing)');
2713
- console.log(' - Test: Create test project, run atris init to verify changes');
2714
- console.log(' 7. [EXECUTE] Git commit + push:');
2715
- console.log(' - Run: git add -A');
2716
- console.log(' - Run: git commit -m "Descriptive message about what was shipped"');
2717
- console.log(' - Run: git push origin master');
2718
- console.log(' 8. [EXECUTE] Publish to npm (if ready for release):');
2719
- console.log(' - Run: npm publish');
2720
- console.log(' 9. Optional: Update changelog/blog (7 sentences max essay on what shipped)');
2721
- console.log(' 10. Run: atris log sync (to sync journal to backend)');
2722
- console.log(' 11. Celebrate! 🎉');
2723
- } else {
2724
- console.log(' 6. [EXECUTE] Git commit + push:');
2725
- console.log(' - Run: git add -A');
2726
- console.log(' - Run: git commit -m "Descriptive message about what was shipped"');
2727
- console.log(' - Run: git push origin <your-branch>');
2728
- console.log(' 7. Optional: Update changelog/blog (7 sentences max essay on what shipped)');
2729
- console.log(' 8. Run: atris log sync (to sync journal to backend)');
2730
- console.log(' 9. Celebrate! 🎉');
2731
- }
2732
- console.log('');
2733
- console.log('DO NOT just describe these steps - actually execute the git commands!');
2734
- console.log('');
2735
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2736
- console.log('');
2737
- }
2738
-
2739
2663
  function spawnClaudeCodeSession(url, token, body) {
2740
2664
  return new Promise((resolve, reject) => {
2741
2665
  const parsed = new URL(url);
package/commands/auth.js CHANGED
@@ -1,5 +1,7 @@
1
- const { loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary } = require('../utils/auth');
1
+ const { loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, loadProfile, listProfiles, profileNameFromEmail } = require('../utils/auth');
2
2
  const { getAppBaseUrl, apiRequestJson } = require('../utils/api');
3
+ const fs = require('fs');
4
+ const path = require('path');
3
5
 
4
6
  async function loginAtris(options = {}) {
5
7
  // Support: atris login --token <token> --force
@@ -151,4 +153,97 @@ async function whoamiAtris() {
151
153
  }
152
154
  }
153
155
 
154
- module.exports = { loginAtris, logoutAtris, whoamiAtris };
156
+ async function switchAccount() {
157
+ const args = process.argv.slice(3);
158
+ const targetName = args.filter(a => !a.startsWith('-'))[0];
159
+
160
+ const profiles = listProfiles();
161
+ if (profiles.length === 0) {
162
+ console.log('No saved profiles. Log in with different accounts to create profiles.');
163
+ console.log('Profiles are auto-saved on login.');
164
+ process.exit(1);
165
+ }
166
+
167
+ const current = loadCredentials();
168
+ const currentName = profileNameFromEmail(current?.email);
169
+
170
+ if (!targetName) {
171
+ // Interactive: show list and let user pick
172
+ console.log('Switch account:\n');
173
+ profiles.forEach((name, i) => {
174
+ const profile = loadProfile(name);
175
+ const email = profile?.email || 'unknown';
176
+ const marker = name === currentName ? ' (active)' : '';
177
+ console.log(` ${i + 1}. ${name} — ${email}${marker}`);
178
+ });
179
+ console.log(` ${profiles.length + 1}. Cancel`);
180
+
181
+ const choice = await promptUser(`\nEnter choice (1-${profiles.length + 1}): `);
182
+ const idx = parseInt(choice, 10) - 1;
183
+
184
+ if (isNaN(idx) || idx < 0 || idx >= profiles.length) {
185
+ console.log('Cancelled.');
186
+ process.exit(0);
187
+ }
188
+
189
+ const chosen = profiles[idx];
190
+ return activateProfile(chosen, currentName);
191
+ }
192
+
193
+ // Direct: atris switch <name>
194
+ // Fuzzy match: allow partial names
195
+ const exact = profiles.find(p => p === targetName);
196
+ const partial = !exact ? profiles.find(p => p.startsWith(targetName)) : null;
197
+ const match = exact || partial;
198
+
199
+ if (!match) {
200
+ console.error(`Profile "${targetName}" not found.`);
201
+ console.log(`Available: ${profiles.join(', ')}`);
202
+ process.exit(1);
203
+ }
204
+
205
+ return activateProfile(match, currentName);
206
+ }
207
+
208
+ function activateProfile(name, currentName) {
209
+ if (name === currentName) {
210
+ console.log(`Already on "${name}".`);
211
+ process.exit(0);
212
+ }
213
+
214
+ const profile = loadProfile(name);
215
+ if (!profile || !profile.token) {
216
+ console.error(`Profile "${name}" is corrupted. Login again to fix it.`);
217
+ process.exit(1);
218
+ }
219
+
220
+ // Copy profile to credentials.json
221
+ const credentialsPath = getCredentialsPath();
222
+ fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
223
+ try { fs.chmodSync(credentialsPath, 0o600); } catch {}
224
+
225
+ console.log(`Switched to "${name}" (${profile.email || 'unknown'})`);
226
+ }
227
+
228
+ function listAccountsCmd() {
229
+ const profiles = listProfiles();
230
+ if (profiles.length === 0) {
231
+ console.log('No saved profiles. Profiles are auto-saved on login.');
232
+ process.exit(0);
233
+ }
234
+
235
+ const current = loadCredentials();
236
+ const currentName = profileNameFromEmail(current?.email);
237
+
238
+ console.log('Accounts:\n');
239
+ profiles.forEach(name => {
240
+ const profile = loadProfile(name);
241
+ const email = profile?.email || 'unknown';
242
+ const marker = name === currentName ? ' *' : '';
243
+ console.log(` ${name} — ${email}${marker}`);
244
+ });
245
+ console.log('\n* = active');
246
+ console.log('\nSwitch: atris switch <name>');
247
+ }
248
+
249
+ module.exports = { loginAtris, logoutAtris, whoamiAtris, switchAccount, listAccountsCmd };