atris 2.2.2 → 2.3.1
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/atris/skills/calendar/SKILL.md +3 -3
- package/atris/skills/email-agent/SKILL.md +42 -2
- package/atris/skills/notion/SKILL.md +478 -0
- package/atris/skills/slides/SKILL.md +355 -0
- package/bin/atris.js +35 -109
- package/commands/auth.js +97 -2
- package/commands/integrations.js +26 -12
- package/commands/plugin.js +450 -0
- package/commands/sync.js +87 -1
- package/package.json +1 -1
- package/utils/auth.js +72 -3
- package/utils/update-check.js +33 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: slides
|
|
3
|
+
description: Google Slides integration via AtrisOS API. List, create, read, and update presentations. Add slides, text, shapes, images. Export to PDF. Use when user asks about slides, presentations, decks, or pitch decks.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
tags:
|
|
6
|
+
- slides
|
|
7
|
+
- google
|
|
8
|
+
- productivity
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Google Slides Agent
|
|
12
|
+
|
|
13
|
+
> Drop this in `~/.claude/skills/slides/SKILL.md` and Claude Code becomes your presentation assistant.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
Google Slides shares OAuth with Google Drive. If Drive is connected, Slides works automatically. If not:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
|
|
21
|
+
|
|
22
|
+
# Check if Drive is connected (Slides piggybacks on Drive)
|
|
23
|
+
curl -s "https://api.atris.ai/api/integrations/google-drive/status" -H "Authorization: Bearer $TOKEN"
|
|
24
|
+
|
|
25
|
+
# If not connected, start Drive OAuth (includes Slides scope)
|
|
26
|
+
curl -s -X POST "https://api.atris.ai/api/integrations/google-drive/start" \
|
|
27
|
+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
28
|
+
-d '{"next_url":"https://atris.ai/dashboard/settings"}'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## API Reference
|
|
34
|
+
|
|
35
|
+
Base: `https://api.atris.ai/api/integrations/google-slides`
|
|
36
|
+
|
|
37
|
+
All requests require: `-H "Authorization: Bearer $TOKEN"`
|
|
38
|
+
|
|
39
|
+
### Get Token
|
|
40
|
+
```bash
|
|
41
|
+
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Presentations
|
|
47
|
+
|
|
48
|
+
### List Presentations
|
|
49
|
+
```bash
|
|
50
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations?page_size=20" \
|
|
51
|
+
-H "Authorization: Bearer $TOKEN"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Get a Presentation (with all slides)
|
|
55
|
+
```bash
|
|
56
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations/{presentation_id}" \
|
|
57
|
+
-H "Authorization: Bearer $TOKEN"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Create a Presentation
|
|
61
|
+
```bash
|
|
62
|
+
curl -s -X POST "https://api.atris.ai/api/integrations/google-slides/presentations" \
|
|
63
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
64
|
+
-H "Content-Type: application/json" \
|
|
65
|
+
-d '{"title": "Q1 2026 Review"}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Updating Slides (batch-update)
|
|
71
|
+
|
|
72
|
+
All slide mutations use the batch-update endpoint. This is the most powerful endpoint — it can add slides, insert text, add shapes, images, change formatting, and more.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
curl -s -X POST "https://api.atris.ai/api/integrations/google-slides/presentations/{id}/batch-update" \
|
|
76
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
77
|
+
-H "Content-Type: application/json" \
|
|
78
|
+
-d '{"requests": [...]}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Add a Blank Slide
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"requests": [
|
|
85
|
+
{
|
|
86
|
+
"createSlide": {
|
|
87
|
+
"insertionIndex": 1,
|
|
88
|
+
"slideLayoutReference": {
|
|
89
|
+
"predefinedLayout": "BLANK"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Available layouts:** `BLANK`, `TITLE`, `TITLE_AND_BODY`, `TITLE_AND_TWO_COLUMNS`, `TITLE_ONLY`, `SECTION_HEADER`, `SECTION_TITLE_AND_DESCRIPTION`, `ONE_COLUMN_TEXT`, `MAIN_POINT`, `BIG_NUMBER`
|
|
98
|
+
|
|
99
|
+
### Add a Title Slide
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"requests": [
|
|
103
|
+
{
|
|
104
|
+
"createSlide": {
|
|
105
|
+
"insertionIndex": 0,
|
|
106
|
+
"slideLayoutReference": {
|
|
107
|
+
"predefinedLayout": "TITLE"
|
|
108
|
+
},
|
|
109
|
+
"placeholderIdMappings": [
|
|
110
|
+
{"layoutPlaceholder": {"type": "TITLE"}, "objectId": "titleId"},
|
|
111
|
+
{"layoutPlaceholder": {"type": "SUBTITLE"}, "objectId": "subtitleId"}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"insertText": {
|
|
117
|
+
"objectId": "titleId",
|
|
118
|
+
"text": "Q1 2026 Review"
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"insertText": {
|
|
123
|
+
"objectId": "subtitleId",
|
|
124
|
+
"text": "Atris Labs - Confidential"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Insert Text into an Element
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"requests": [
|
|
135
|
+
{
|
|
136
|
+
"insertText": {
|
|
137
|
+
"objectId": "ELEMENT_ID",
|
|
138
|
+
"text": "Hello, world!"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Add a Text Box
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"requests": [
|
|
149
|
+
{
|
|
150
|
+
"createShape": {
|
|
151
|
+
"objectId": "myTextBox1",
|
|
152
|
+
"shapeType": "TEXT_BOX",
|
|
153
|
+
"elementProperties": {
|
|
154
|
+
"pageObjectId": "SLIDE_ID",
|
|
155
|
+
"size": {
|
|
156
|
+
"width": {"magnitude": 400, "unit": "PT"},
|
|
157
|
+
"height": {"magnitude": 50, "unit": "PT"}
|
|
158
|
+
},
|
|
159
|
+
"transform": {
|
|
160
|
+
"scaleX": 1, "scaleY": 1,
|
|
161
|
+
"translateX": 100, "translateY": 200,
|
|
162
|
+
"unit": "PT"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"insertText": {
|
|
169
|
+
"objectId": "myTextBox1",
|
|
170
|
+
"text": "Custom text here"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Add an Image
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"requests": [
|
|
181
|
+
{
|
|
182
|
+
"createImage": {
|
|
183
|
+
"objectId": "myImage1",
|
|
184
|
+
"url": "https://example.com/image.png",
|
|
185
|
+
"elementProperties": {
|
|
186
|
+
"pageObjectId": "SLIDE_ID",
|
|
187
|
+
"size": {
|
|
188
|
+
"width": {"magnitude": 300, "unit": "PT"},
|
|
189
|
+
"height": {"magnitude": 200, "unit": "PT"}
|
|
190
|
+
},
|
|
191
|
+
"transform": {
|
|
192
|
+
"scaleX": 1, "scaleY": 1,
|
|
193
|
+
"translateX": 150, "translateY": 100,
|
|
194
|
+
"unit": "PT"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Replace All Text (find & replace)
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"requests": [
|
|
207
|
+
{
|
|
208
|
+
"replaceAllText": {
|
|
209
|
+
"containsText": {"text": "{{company_name}}"},
|
|
210
|
+
"replaceText": "Atris Labs"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Delete a Slide or Element
|
|
218
|
+
```json
|
|
219
|
+
{
|
|
220
|
+
"requests": [
|
|
221
|
+
{
|
|
222
|
+
"deleteObject": {
|
|
223
|
+
"objectId": "SLIDE_OR_ELEMENT_ID"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Style Text
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"requests": [
|
|
234
|
+
{
|
|
235
|
+
"updateTextStyle": {
|
|
236
|
+
"objectId": "ELEMENT_ID",
|
|
237
|
+
"style": {
|
|
238
|
+
"bold": true,
|
|
239
|
+
"fontSize": {"magnitude": 24, "unit": "PT"},
|
|
240
|
+
"foregroundColor": {
|
|
241
|
+
"opaqueColor": {"rgbColor": {"red": 0.2, "green": 0.2, "blue": 0.8}}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
"fields": "bold,fontSize,foregroundColor"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Pages (Individual Slides)
|
|
254
|
+
|
|
255
|
+
### Get a Single Slide
|
|
256
|
+
```bash
|
|
257
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations/{id}/pages/{page_id}" \
|
|
258
|
+
-H "Authorization: Bearer $TOKEN"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Get Slide Thumbnail
|
|
262
|
+
```bash
|
|
263
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations/{id}/pages/{page_id}/thumbnail" \
|
|
264
|
+
-H "Authorization: Bearer $TOKEN"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Export
|
|
270
|
+
|
|
271
|
+
### Export as PDF
|
|
272
|
+
```bash
|
|
273
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations/{id}/export" \
|
|
274
|
+
-H "Authorization: Bearer $TOKEN"
|
|
275
|
+
```
|
|
276
|
+
Returns `{"pdf_base64": "...", "content_type": "application/pdf"}`.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Workflows
|
|
281
|
+
|
|
282
|
+
### "Create a pitch deck"
|
|
283
|
+
1. Create presentation: `POST /presentations` with title
|
|
284
|
+
2. Get the presentation to find the first slide ID
|
|
285
|
+
3. Batch update: add title slide, content slides, closing slide
|
|
286
|
+
4. Each slide: createSlide + insertText for content
|
|
287
|
+
5. Return the presentation URL: `https://docs.google.com/presentation/d/{id}`
|
|
288
|
+
|
|
289
|
+
### "List my presentations"
|
|
290
|
+
1. `GET /presentations`
|
|
291
|
+
2. Display: name, last modified, link
|
|
292
|
+
|
|
293
|
+
### "Add a slide to an existing deck"
|
|
294
|
+
1. `GET /presentations/{id}` to see current slides
|
|
295
|
+
2. Batch update: createSlide at the desired index
|
|
296
|
+
3. Add content with insertText, createShape, createImage
|
|
297
|
+
|
|
298
|
+
### "Export deck to PDF"
|
|
299
|
+
1. `GET /presentations/{id}/export`
|
|
300
|
+
2. Decode base64 and save to file
|
|
301
|
+
|
|
302
|
+
### "Update text in a deck"
|
|
303
|
+
1. `GET /presentations/{id}` to find element IDs
|
|
304
|
+
2. Batch update with replaceAllText for template variables
|
|
305
|
+
3. Or insertText/deleteText for specific elements
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Important Notes
|
|
310
|
+
|
|
311
|
+
- **Shares Drive OAuth** — no separate connection needed. If Drive is connected, Slides works
|
|
312
|
+
- **Batch update is everything** — all slide mutations (add, edit, delete, style) go through batch-update
|
|
313
|
+
- **Object IDs** — every slide, shape, text box, image has an ID. Get them from `GET /presentations/{id}`
|
|
314
|
+
- **Slide size** — default is 10x5.63 inches (720x406.5 PT). Position elements accordingly
|
|
315
|
+
- **Images** — must be publicly accessible URLs. For private images, upload to Drive first
|
|
316
|
+
- **Templates** — use replaceAllText with `{{placeholder}}` patterns for template-based decks
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Error Handling
|
|
321
|
+
|
|
322
|
+
| Error | Meaning | Solution |
|
|
323
|
+
|-------|---------|----------|
|
|
324
|
+
| `Drive not connected` | No Drive OAuth token | Connect Google Drive first |
|
|
325
|
+
| `403 insufficient_scope` | Token missing presentations scope | Reconnect Drive to get updated scopes |
|
|
326
|
+
| `404 not_found` | Presentation doesn't exist | Check presentation ID |
|
|
327
|
+
| `400 invalid_request` | Bad batch update request | Check request format against API docs |
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Quick Reference
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Get token
|
|
335
|
+
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
|
|
336
|
+
|
|
337
|
+
# List presentations
|
|
338
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations" -H "Authorization: Bearer $TOKEN"
|
|
339
|
+
|
|
340
|
+
# Create presentation
|
|
341
|
+
curl -s -X POST "https://api.atris.ai/api/integrations/google-slides/presentations" \
|
|
342
|
+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
343
|
+
-d '{"title":"My Deck"}'
|
|
344
|
+
|
|
345
|
+
# Get presentation (with all slides)
|
|
346
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations/PRES_ID" -H "Authorization: Bearer $TOKEN"
|
|
347
|
+
|
|
348
|
+
# Add a slide with text
|
|
349
|
+
curl -s -X POST "https://api.atris.ai/api/integrations/google-slides/presentations/PRES_ID/batch-update" \
|
|
350
|
+
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
351
|
+
-d '{"requests":[{"createSlide":{"slideLayoutReference":{"predefinedLayout":"TITLE_AND_BODY"}}}]}'
|
|
352
|
+
|
|
353
|
+
# Export as PDF
|
|
354
|
+
curl -s "https://api.atris.ai/api/integrations/google-slides/presentations/PRES_ID/export" -H "Authorization: Bearer $TOKEN"
|
|
355
|
+
```
|
package/bin/atris.js
CHANGED
|
@@ -25,7 +25,7 @@ const DEFAULT_CLIENT_ID = `AtrisCLI/${CLI_VERSION}`;
|
|
|
25
25
|
const DEFAULT_USER_AGENT = `${DEFAULT_CLIENT_ID} (node ${process.version}; ${os.platform()} ${os.release()} ${os.arch()})`;
|
|
26
26
|
|
|
27
27
|
// Update check utility
|
|
28
|
-
const { checkForUpdates, showUpdateNotification } = require('../utils/update-check');
|
|
28
|
+
const { checkForUpdates, showUpdateNotification, autoUpdate } = require('../utils/update-check');
|
|
29
29
|
|
|
30
30
|
// State detection for smart default
|
|
31
31
|
const { detectWorkspaceState, loadContext } = require('../lib/state-detection');
|
|
@@ -39,9 +39,11 @@ if (!skipUpdateCheck && (!process.argv[2] || (process.argv[2] && !['version', 'u
|
|
|
39
39
|
.then((updateInfo) => {
|
|
40
40
|
// Show notification if update available (after command completes)
|
|
41
41
|
if (updateInfo) {
|
|
42
|
-
//
|
|
42
|
+
// Auto-update in background, fall back to notification if it fails
|
|
43
43
|
setTimeout(() => {
|
|
44
|
-
|
|
44
|
+
if (!autoUpdate(updateInfo)) {
|
|
45
|
+
showUpdateNotification(updateInfo);
|
|
46
|
+
}
|
|
45
47
|
}, 100);
|
|
46
48
|
}
|
|
47
49
|
return updateInfo;
|
|
@@ -54,6 +56,17 @@ if (!skipUpdateCheck && (!process.argv[2] || (process.argv[2] && !['version', 'u
|
|
|
54
56
|
|
|
55
57
|
const command = process.argv[2];
|
|
56
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
|
+
|
|
57
70
|
const TOKEN_REFRESH_BUFFER_SECONDS = 300; // Refresh ~5 minutes before expiry
|
|
58
71
|
|
|
59
72
|
function decodeJwtClaims(token) {
|
|
@@ -195,6 +208,8 @@ function showHelp() {
|
|
|
195
208
|
console.log(' login - Authenticate (use --token <t> for non-interactive)');
|
|
196
209
|
console.log(' logout - Remove credentials');
|
|
197
210
|
console.log(' whoami - Show auth status');
|
|
211
|
+
console.log(' switch - Switch account (atris switch <name>)');
|
|
212
|
+
console.log(' accounts - List saved accounts');
|
|
198
213
|
console.log('');
|
|
199
214
|
console.log('Integrations:');
|
|
200
215
|
console.log(' gmail - Email commands (inbox, read)');
|
|
@@ -208,6 +223,11 @@ function showHelp() {
|
|
|
208
223
|
console.log(' skill audit [name] - Validate skill against Anthropic guide');
|
|
209
224
|
console.log(' skill fix [name] - Auto-fix common compliance issues');
|
|
210
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('');
|
|
211
231
|
console.log('Other:');
|
|
212
232
|
console.log(' version - Show Atris version');
|
|
213
233
|
console.log(' help - Show this help');
|
|
@@ -302,7 +322,7 @@ const { initAtris: initCmd } = require('../commands/init');
|
|
|
302
322
|
const { syncAtris: syncCmd } = require('../commands/sync');
|
|
303
323
|
const { logAtris: logCmd } = require('../commands/log');
|
|
304
324
|
const { logSyncAtris: logSyncCmd } = require('../commands/log-sync');
|
|
305
|
-
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');
|
|
306
326
|
const { showVersion: versionCmd } = require('../commands/version');
|
|
307
327
|
const { planAtris: planCmd, doAtris: doCmd, reviewAtris: reviewCmd } = require('../commands/workflow');
|
|
308
328
|
const { visualizeAtris: visualizeCmd } = require('../commands/visualize');
|
|
@@ -314,11 +334,12 @@ const { analyticsAtris: analyticsCmd } = require('../commands/analytics');
|
|
|
314
334
|
const { cleanAtris: cleanCmd } = require('../commands/clean');
|
|
315
335
|
const { verifyAtris: verifyCmd } = require('../commands/verify');
|
|
316
336
|
const { skillCommand: skillCmd } = require('../commands/skill');
|
|
337
|
+
const { pluginCommand: pluginCmd } = require('../commands/plugin');
|
|
317
338
|
|
|
318
339
|
// Check if this is a known command or natural language input
|
|
319
340
|
const knownCommands = ['init', 'log', 'status', 'analytics', 'visualize', 'brainstorm', 'autopilot', 'plan', 'do', 'review',
|
|
320
|
-
'activate', 'agent', 'chat', 'login', 'logout', 'whoami', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
321
|
-
'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',
|
|
322
343
|
'gmail', 'calendar', 'twitter', 'slack', 'integrations'];
|
|
323
344
|
|
|
324
345
|
// Check if command is an atris.md spec file - triggers welcome visualization
|
|
@@ -648,6 +669,10 @@ if (command === 'init') {
|
|
|
648
669
|
logoutCmd();
|
|
649
670
|
} else if (command === 'whoami') {
|
|
650
671
|
whoamiCmd();
|
|
672
|
+
} else if (command === 'switch') {
|
|
673
|
+
switchCmd();
|
|
674
|
+
} else if (command === 'accounts') {
|
|
675
|
+
accountsCmd();
|
|
651
676
|
} else if (command === 'visualize') {
|
|
652
677
|
console.log('ℹ️ "atris visualize" is a legacy helper. Visualization is now built into "atris plan".');
|
|
653
678
|
console.log(' Prefer: atris plan');
|
|
@@ -800,6 +825,10 @@ if (command === 'init') {
|
|
|
800
825
|
const subcommand = process.argv[3];
|
|
801
826
|
const args = process.argv.slice(4);
|
|
802
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);
|
|
803
832
|
} else {
|
|
804
833
|
console.log(`Unknown command: ${command}`);
|
|
805
834
|
console.log('Run "atris help" to see available commands');
|
|
@@ -2631,109 +2660,6 @@ async function atrisDevEntry(userInput = null) {
|
|
|
2631
2660
|
console.log('');
|
|
2632
2661
|
}
|
|
2633
2662
|
|
|
2634
|
-
function launchAtris() {
|
|
2635
|
-
const targetDir = path.join(process.cwd(), 'atris');
|
|
2636
|
-
const launcherFile = path.join(targetDir, 'team', 'launcher.md');
|
|
2637
|
-
|
|
2638
|
-
if (!fs.existsSync(launcherFile)) {
|
|
2639
|
-
console.log('✗ launcher.md not found. Run "atris init" first.');
|
|
2640
|
-
process.exit(1);
|
|
2641
|
-
}
|
|
2642
|
-
|
|
2643
|
-
// Read launcher.md
|
|
2644
|
-
const launcherSpec = fs.readFileSync(launcherFile, 'utf8');
|
|
2645
|
-
|
|
2646
|
-
// Reference TODO.md (agents read on-demand, legacy TASK_CONTEXTS.md supported)
|
|
2647
|
-
const todoFile = path.join(targetDir, 'TODO.md');
|
|
2648
|
-
const legacyTaskContextsFile = path.join(targetDir, 'TASK_CONTEXTS.md');
|
|
2649
|
-
|
|
2650
|
-
// Reference MAP.md (agents read on-demand)
|
|
2651
|
-
const mapFile = path.join(targetDir, 'MAP.md');
|
|
2652
|
-
const mapPath = fs.existsSync(mapFile) ? path.relative(process.cwd(), mapFile) : null;
|
|
2653
|
-
|
|
2654
|
-
// Reference journal (agents read on-demand)
|
|
2655
|
-
const { logFile, dateFormatted } = getLogPath();
|
|
2656
|
-
let journalPath = '';
|
|
2657
|
-
if (fs.existsSync(logFile)) {
|
|
2658
|
-
journalPath = path.relative(process.cwd(), logFile);
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
console.log('');
|
|
2662
|
-
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
2663
|
-
console.log('│ Atris Launch — Launcher Agent Activated │');
|
|
2664
|
-
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
2665
|
-
console.log('');
|
|
2666
|
-
console.log('📋 AGENT SPEC:');
|
|
2667
|
-
console.log('─────────────────────────────────────────────────────────────');
|
|
2668
|
-
console.log(launcherSpec);
|
|
2669
|
-
console.log('');
|
|
2670
|
-
console.log('─────────────────────────────────────────────────────────────');
|
|
2671
|
-
console.log('');
|
|
2672
|
-
const taskFilePath = fs.existsSync(todoFile)
|
|
2673
|
-
? todoFile
|
|
2674
|
-
: (fs.existsSync(legacyTaskContextsFile) ? legacyTaskContextsFile : null);
|
|
2675
|
-
const taskContextsPath = taskFilePath ? path.relative(process.cwd(), taskFilePath) : null;
|
|
2676
|
-
console.log('📝 TODO.md: ' + (taskContextsPath || 'Not found'));
|
|
2677
|
-
console.log(' Read for completed tasks context (usually small, or reference path if large).');
|
|
2678
|
-
console.log('');
|
|
2679
|
-
console.log('─────────────────────────────────────────────────────────────');
|
|
2680
|
-
console.log('');
|
|
2681
|
-
console.log('🗺️ MAP.md: ' + (mapPath || 'Not found'));
|
|
2682
|
-
console.log(' Read this file for file:line references when navigating the codebase.');
|
|
2683
|
-
console.log('');
|
|
2684
|
-
console.log('─────────────────────────────────────────────────────────────');
|
|
2685
|
-
console.log('');
|
|
2686
|
-
console.log('📅 JOURNAL: ' + (journalPath || 'Not found'));
|
|
2687
|
-
console.log(' Read for recent completions and context.');
|
|
2688
|
-
console.log('');
|
|
2689
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2690
|
-
console.log('📋 INSTRUCTION PROMPT FOR YOUR CODING AGENT:');
|
|
2691
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2692
|
-
console.log('');
|
|
2693
|
-
console.log('You are the Launcher. Ship it clean.');
|
|
2694
|
-
console.log('');
|
|
2695
|
-
console.log('⚠️ CRITICAL: Execute these steps NOW using terminal and file tools:');
|
|
2696
|
-
console.log('');
|
|
2697
|
-
// Detect if this is the atris package project or a user project
|
|
2698
|
-
const isAtrisPackage = fs.existsSync(path.join(process.cwd(), 'package.json')) &&
|
|
2699
|
-
fs.existsSync(path.join(process.cwd(), 'bin', 'atris.js')) &&
|
|
2700
|
-
fs.existsSync(path.join(process.cwd(), 'atris.md'));
|
|
2701
|
-
|
|
2702
|
-
console.log('Launch Workflow:');
|
|
2703
|
-
console.log(' 1. Document what was shipped (add Launch entry to journal Notes section)');
|
|
2704
|
-
console.log(' 2. Extract learnings (what worked? what would you do differently?)');
|
|
2705
|
-
console.log(' 3. Update MAP.md with new patterns/file locations');
|
|
2706
|
-
console.log(' 4. Update relevant docs (README, API docs, etc.)');
|
|
2707
|
-
console.log(' 5. Clean up (remove temp files, unused code, etc.)');
|
|
2708
|
-
if (isAtrisPackage) {
|
|
2709
|
-
console.log(' 6. [EXECUTE] Test locally (package development):');
|
|
2710
|
-
console.log(' - Run: npm link (link package for local testing)');
|
|
2711
|
-
console.log(' - Test: Create test project, run atris init to verify changes');
|
|
2712
|
-
console.log(' 7. [EXECUTE] Git commit + push:');
|
|
2713
|
-
console.log(' - Run: git add -A');
|
|
2714
|
-
console.log(' - Run: git commit -m "Descriptive message about what was shipped"');
|
|
2715
|
-
console.log(' - Run: git push origin master');
|
|
2716
|
-
console.log(' 8. [EXECUTE] Publish to npm (if ready for release):');
|
|
2717
|
-
console.log(' - Run: npm publish');
|
|
2718
|
-
console.log(' 9. Optional: Update changelog/blog (7 sentences max essay on what shipped)');
|
|
2719
|
-
console.log(' 10. Run: atris log sync (to sync journal to backend)');
|
|
2720
|
-
console.log(' 11. Celebrate! 🎉');
|
|
2721
|
-
} else {
|
|
2722
|
-
console.log(' 6. [EXECUTE] Git commit + push:');
|
|
2723
|
-
console.log(' - Run: git add -A');
|
|
2724
|
-
console.log(' - Run: git commit -m "Descriptive message about what was shipped"');
|
|
2725
|
-
console.log(' - Run: git push origin <your-branch>');
|
|
2726
|
-
console.log(' 7. Optional: Update changelog/blog (7 sentences max essay on what shipped)');
|
|
2727
|
-
console.log(' 8. Run: atris log sync (to sync journal to backend)');
|
|
2728
|
-
console.log(' 9. Celebrate! 🎉');
|
|
2729
|
-
}
|
|
2730
|
-
console.log('');
|
|
2731
|
-
console.log('DO NOT just describe these steps - actually execute the git commands!');
|
|
2732
|
-
console.log('');
|
|
2733
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
2734
|
-
console.log('');
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
2663
|
function spawnClaudeCodeSession(url, token, body) {
|
|
2738
2664
|
return new Promise((resolve, reject) => {
|
|
2739
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
|
-
|
|
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 };
|