atris 2.3.1 → 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.
@@ -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
  ```
@@ -250,6 +250,17 @@ curl -s -X DELETE "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}
250
250
  -H "Authorization: Bearer $TOKEN"
251
251
  ```
252
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
+
253
264
  ### Archive Email
254
265
  ```bash
255
266
  # Single message
@@ -263,6 +274,19 @@ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/messages/batch-arch
263
274
  -d '{"message_ids": ["id1", "id2", "id3"]}'
264
275
  ```
265
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
+
266
290
  ### Check Status
267
291
  ```bash
268
292
  curl -s "https://api.atris.ai/api/integrations/gmail/status" \
@@ -410,6 +434,12 @@ curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts" \
410
434
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
411
435
  -d '{"to":"email@example.com","subject":"Hi","body":"Draft text"}'
412
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
+
413
443
  # Send a draft
414
444
  curl -s -X POST "https://api.atris.ai/api/integrations/gmail/drafts/{draft_id}/send" \
415
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/commands/sync.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const os = require('os');
3
4
 
4
5
  function syncAtris() {
5
6
  const targetDir = path.join(process.cwd(), 'atris');
@@ -293,83 +294,114 @@ After displaying the boot output, respond to the user naturally.
293
294
  }
294
295
 
295
296
  /**
296
- * Lightweight skill-only sync. Compares package skills against project skills
297
- * and updates any that differ. Called automatically from `atris activate`.
297
+ * Recursively sync files from src to dest. Returns count of files updated.
298
+ */
299
+ function syncRecursiveCount(src, dest, label, silent) {
300
+ let count = 0;
301
+ if (!fs.existsSync(dest)) {
302
+ fs.mkdirSync(dest, { recursive: true });
303
+ }
304
+ const entries = fs.readdirSync(src);
305
+ for (const entry of entries) {
306
+ const srcPath = path.join(src, entry);
307
+ const destPath = path.join(dest, entry);
308
+
309
+ if (fs.statSync(srcPath).isDirectory()) {
310
+ count += syncRecursiveCount(srcPath, destPath, `${label}/${entry}`, silent);
311
+ } else {
312
+ const srcContent = fs.readFileSync(srcPath, 'utf8');
313
+ const destContent = fs.existsSync(destPath) ? fs.readFileSync(destPath, 'utf8') : '';
314
+ if (srcContent !== destContent) {
315
+ fs.writeFileSync(destPath, srcContent);
316
+ if (entry.endsWith('.sh')) {
317
+ fs.chmodSync(destPath, 0o755);
318
+ }
319
+ if (!silent) {
320
+ console.log(`✓ Updated ${label}/${entry}`);
321
+ }
322
+ count++;
323
+ }
324
+ }
325
+ }
326
+ return count;
327
+ }
328
+
329
+ /**
330
+ * Lightweight skill-only sync. Syncs skills from the npm package to:
331
+ * 1. Global skill dirs (~/.claude/skills/, ~/.codex/skills/) — always, if they exist
332
+ * 2. Project-level (atris/skills/ + .claude/skills/ symlinks) — if in a project
333
+ *
334
+ * Global = baseline truth. Project = optional override.
298
335
  * Returns number of files updated (0 = already current).
299
336
  */
300
337
  function syncSkills({ silent = false } = {}) {
301
- const targetDir = path.join(process.cwd(), 'atris');
302
338
  const packageSkillsDir = path.join(__dirname, '..', 'atris', 'skills');
303
- const userSkillsDir = path.join(targetDir, 'skills');
304
- const claudeSkillsBaseDir = path.join(process.cwd(), '.claude', 'skills');
305
-
306
- if (!fs.existsSync(targetDir) || !fs.existsSync(packageSkillsDir)) {
339
+ if (!fs.existsSync(packageSkillsDir)) {
307
340
  return 0;
308
341
  }
309
342
 
310
- if (!fs.existsSync(userSkillsDir)) {
311
- fs.mkdirSync(userSkillsDir, { recursive: true });
312
- }
313
- if (!fs.existsSync(claudeSkillsBaseDir)) {
314
- fs.mkdirSync(claudeSkillsBaseDir, { recursive: true });
315
- }
316
-
317
343
  let updated = 0;
344
+ const homeDir = os.homedir();
318
345
 
319
346
  const skillFolders = fs.readdirSync(packageSkillsDir).filter(f =>
320
347
  fs.statSync(path.join(packageSkillsDir, f)).isDirectory()
321
348
  );
322
349
 
323
- for (const skill of skillFolders) {
324
- const srcSkillDir = path.join(packageSkillsDir, skill);
325
- const destSkillDir = path.join(userSkillsDir, skill);
326
- const symlinkPath = path.join(claudeSkillsBaseDir, skill);
350
+ // --- 1. Global skill directories (sync if they exist) ---
351
+ const globalSkillDirs = [
352
+ path.join(homeDir, '.claude', 'skills'),
353
+ path.join(homeDir, '.codex', 'skills'),
354
+ ];
327
355
 
328
- const syncRecursive = (src, dest, skillName, basePath = '') => {
329
- if (!fs.existsSync(dest)) {
330
- fs.mkdirSync(dest, { recursive: true });
331
- }
332
- const entries = fs.readdirSync(src);
333
- for (const entry of entries) {
334
- const srcPath = path.join(src, entry);
335
- const destPath = path.join(dest, entry);
336
- const relPath = basePath ? `${basePath}/${entry}` : entry;
337
-
338
- if (fs.statSync(srcPath).isDirectory()) {
339
- syncRecursive(srcPath, destPath, skillName, relPath);
340
- } else {
341
- const srcContent = fs.readFileSync(srcPath, 'utf8');
342
- const destContent = fs.existsSync(destPath) ? fs.readFileSync(destPath, 'utf8') : '';
343
- if (srcContent !== destContent) {
344
- fs.writeFileSync(destPath, srcContent);
345
- if (entry.endsWith('.sh')) {
346
- fs.chmodSync(destPath, 0o755);
347
- }
348
- if (!silent) {
349
- console.log(`✓ Updated atris/skills/${skillName}/${relPath}`);
350
- }
351
- updated++;
352
- }
353
- }
356
+ for (const globalDir of globalSkillDirs) {
357
+ if (!fs.existsSync(globalDir)) continue;
358
+ const dirName = path.basename(path.dirname(globalDir)); // .claude or .codex
359
+
360
+ for (const skill of skillFolders) {
361
+ const srcSkillDir = path.join(packageSkillsDir, skill);
362
+ const destSkillDir = path.join(globalDir, skill);
363
+
364
+ if (fs.existsSync(destSkillDir) || fs.existsSync(globalDir)) {
365
+ updated += syncRecursiveCount(srcSkillDir, destSkillDir, `~/${dirName}/skills/${skill}`, silent);
354
366
  }
355
- };
367
+ }
368
+ }
356
369
 
357
- syncRecursive(srcSkillDir, destSkillDir, skill);
370
+ // --- 2. Project-level (only if inside an atris project) ---
371
+ const targetDir = path.join(process.cwd(), 'atris');
372
+ if (fs.existsSync(targetDir)) {
373
+ const userSkillsDir = path.join(targetDir, 'skills');
374
+ const claudeSkillsBaseDir = path.join(process.cwd(), '.claude', 'skills');
358
375
 
359
- // Create symlink if doesn't exist
360
- if (!fs.existsSync(symlinkPath)) {
361
- const relativePath = path.join('..', '..', 'atris', 'skills', skill);
362
- try {
363
- fs.symlinkSync(relativePath, symlinkPath);
364
- if (!silent) {
365
- console.log(`✓ Linked .claude/skills/${skill}`);
366
- }
367
- } catch (e) {
368
- // Fallback: copy instead of symlink
369
- fs.mkdirSync(symlinkPath, { recursive: true });
370
- const skillFile = path.join(destSkillDir, 'SKILL.md');
371
- if (fs.existsSync(skillFile)) {
372
- fs.copyFileSync(skillFile, path.join(symlinkPath, 'SKILL.md'));
376
+ if (!fs.existsSync(userSkillsDir)) {
377
+ fs.mkdirSync(userSkillsDir, { recursive: true });
378
+ }
379
+ if (!fs.existsSync(claudeSkillsBaseDir)) {
380
+ fs.mkdirSync(claudeSkillsBaseDir, { recursive: true });
381
+ }
382
+
383
+ for (const skill of skillFolders) {
384
+ const srcSkillDir = path.join(packageSkillsDir, skill);
385
+ const destSkillDir = path.join(userSkillsDir, skill);
386
+ const symlinkPath = path.join(claudeSkillsBaseDir, skill);
387
+
388
+ updated += syncRecursiveCount(srcSkillDir, destSkillDir, `atris/skills/${skill}`, silent);
389
+
390
+ // Create symlink if doesn't exist
391
+ if (!fs.existsSync(symlinkPath)) {
392
+ const relativePath = path.join('..', '..', 'atris', 'skills', skill);
393
+ try {
394
+ fs.symlinkSync(relativePath, symlinkPath);
395
+ if (!silent) {
396
+ console.log(`✓ Linked .claude/skills/${skill}`);
397
+ }
398
+ } catch (e) {
399
+ // Fallback: copy instead of symlink
400
+ fs.mkdirSync(symlinkPath, { recursive: true });
401
+ const skillFile = path.join(destSkillDir, 'SKILL.md');
402
+ if (fs.existsSync(skillFile)) {
403
+ fs.copyFileSync(skillFile, path.join(symlinkPath, 'SKILL.md'));
404
+ }
373
405
  }
374
406
  }
375
407
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "atrisDev (atris dev) - CLI for AI coding agents. Works with Claude Code, Cursor, Windsurf. Make any codebase AI-navigable.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {