atris 2.0.21 → 2.2.0

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.
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Integration commands for Atris CLI
3
+ *
4
+ * Usage:
5
+ * atris gmail inbox - List recent emails
6
+ * atris gmail read <id> - Read specific email
7
+ * atris calendar today - Show today's events
8
+ * atris calendar week - Show this week's events
9
+ * atris twitter post - Post a tweet (interactive)
10
+ * atris slack channels - List Slack channels
11
+ */
12
+
13
+ const { loadCredentials } = require('../utils/auth');
14
+ const { apiRequestJson } = require('../utils/api');
15
+
16
+ async function getAuthToken() {
17
+ const creds = loadCredentials();
18
+ if (!creds || !creds.token) {
19
+ console.error('Not logged in. Run: atris login');
20
+ process.exit(1);
21
+ }
22
+ return creds.token;
23
+ }
24
+
25
+ // ============================================================================
26
+ // GMAIL
27
+ // ============================================================================
28
+
29
+ async function gmailInbox(options = {}) {
30
+ const token = await getAuthToken();
31
+ const limit = options.limit || 10;
32
+
33
+ console.log('📬 Fetching inbox...\n');
34
+
35
+ const result = await apiRequestJson(`/integrations/gmail/messages?max_results=${limit}`, {
36
+ method: 'GET',
37
+ token,
38
+ });
39
+
40
+ if (!result.ok) {
41
+ if (result.status === 401) {
42
+ console.error('Gmail not connected. Connect at: https://atris.ai/dashboard/settings');
43
+ } else {
44
+ console.error(`Error: ${result.error || 'Failed to fetch inbox'}`);
45
+ }
46
+ process.exit(1);
47
+ }
48
+
49
+ const messages = result.data?.messages || result.data || [];
50
+
51
+ if (messages.length === 0) {
52
+ console.log('No messages found.');
53
+ return;
54
+ }
55
+
56
+ console.log(`Found ${messages.length} messages:\n`);
57
+ console.log('─'.repeat(60));
58
+
59
+ for (const msg of messages) {
60
+ const from = msg.from || msg.sender || 'Unknown';
61
+ const subject = msg.subject || '(no subject)';
62
+ const date = msg.date || msg.received_at || '';
63
+ const id = msg.id || msg.message_id || '';
64
+
65
+ console.log(`From: ${from}`);
66
+ console.log(`Subj: ${subject}`);
67
+ console.log(`Date: ${date}`);
68
+ console.log(`ID: ${id}`);
69
+ console.log('─'.repeat(60));
70
+ }
71
+ }
72
+
73
+ async function gmailRead(messageId) {
74
+ if (!messageId) {
75
+ console.error('Usage: atris gmail read <message_id>');
76
+ process.exit(1);
77
+ }
78
+
79
+ const token = await getAuthToken();
80
+
81
+ console.log('📧 Fetching message...\n');
82
+
83
+ const result = await apiRequestJson(`/integrations/gmail/messages/${messageId}`, {
84
+ method: 'GET',
85
+ token,
86
+ });
87
+
88
+ if (!result.ok) {
89
+ console.error(`Error: ${result.error || 'Failed to fetch message'}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ const msg = result.data;
94
+
95
+ console.log('─'.repeat(60));
96
+ console.log(`From: ${msg.from || 'Unknown'}`);
97
+ console.log(`To: ${msg.to || 'Unknown'}`);
98
+ console.log(`Subject: ${msg.subject || '(no subject)'}`);
99
+ console.log(`Date: ${msg.date || ''}`);
100
+ console.log('─'.repeat(60));
101
+ console.log('');
102
+ console.log(msg.body || msg.snippet || '(no body)');
103
+ }
104
+
105
+ async function gmailCommand(subcommand, ...args) {
106
+ switch (subcommand) {
107
+ case 'inbox':
108
+ case 'list':
109
+ await gmailInbox();
110
+ break;
111
+ case 'read':
112
+ await gmailRead(args[0]);
113
+ break;
114
+ default:
115
+ console.log('Gmail commands:');
116
+ console.log(' atris gmail inbox - List recent emails');
117
+ console.log(' atris gmail read <id> - Read specific email');
118
+ }
119
+ }
120
+
121
+ // ============================================================================
122
+ // CALENDAR
123
+ // ============================================================================
124
+
125
+ async function calendarToday() {
126
+ const token = await getAuthToken();
127
+
128
+ console.log('📅 Today\'s events:\n');
129
+
130
+ const result = await apiRequestJson('/integrations/google-calendar/events/today', {
131
+ method: 'GET',
132
+ token,
133
+ });
134
+
135
+ if (!result.ok) {
136
+ if (result.status === 401) {
137
+ console.error('Calendar not connected. Connect at: https://atris.ai/dashboard/settings');
138
+ } else {
139
+ console.error(`Error: ${result.error || 'Failed to fetch events'}`);
140
+ }
141
+ process.exit(1);
142
+ }
143
+
144
+ const events = result.data?.events || result.data || [];
145
+
146
+ if (events.length === 0) {
147
+ console.log('No events today. 🎉');
148
+ return;
149
+ }
150
+
151
+ console.log('─'.repeat(50));
152
+
153
+ for (const event of events) {
154
+ const start = event.start?.dateTime || event.start?.date || '';
155
+ const time = start ? new Date(start).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }) : 'All day';
156
+ const title = event.summary || '(no title)';
157
+
158
+ console.log(`${time} ${title}`);
159
+ if (event.location) {
160
+ console.log(` 📍 ${event.location}`);
161
+ }
162
+ }
163
+
164
+ console.log('─'.repeat(50));
165
+ }
166
+
167
+ async function calendarCommand(subcommand, ...args) {
168
+ switch (subcommand) {
169
+ case 'today':
170
+ await calendarToday();
171
+ break;
172
+ case 'week':
173
+ console.log('Week view coming soon...');
174
+ break;
175
+ default:
176
+ console.log('Calendar commands:');
177
+ console.log(' atris calendar today - Show today\'s events');
178
+ console.log(' atris calendar week - Show this week\'s events');
179
+ }
180
+ }
181
+
182
+ // ============================================================================
183
+ // TWITTER
184
+ // ============================================================================
185
+
186
+ async function twitterPost(text) {
187
+ const token = await getAuthToken();
188
+
189
+ if (!text) {
190
+ const readline = require('readline');
191
+ const rl = readline.createInterface({
192
+ input: process.stdin,
193
+ output: process.stdout
194
+ });
195
+
196
+ text = await new Promise((resolve) => {
197
+ rl.question('Tweet: ', (answer) => {
198
+ rl.close();
199
+ resolve(answer.trim());
200
+ });
201
+ });
202
+ }
203
+
204
+ if (!text) {
205
+ console.error('Tweet text is required');
206
+ process.exit(1);
207
+ }
208
+
209
+ console.log('\n🐦 Posting tweet...');
210
+
211
+ const result = await apiRequestJson('/integrations/twitter/tweet', {
212
+ method: 'POST',
213
+ token,
214
+ body: { text },
215
+ });
216
+
217
+ if (!result.ok) {
218
+ if (result.status === 401) {
219
+ console.error('Twitter not connected. Connect at: https://atris.ai/dashboard/settings');
220
+ } else {
221
+ console.error(`Error: ${result.error || 'Failed to post tweet'}`);
222
+ }
223
+ process.exit(1);
224
+ }
225
+
226
+ console.log('✓ Tweet posted!');
227
+ if (result.data?.id) {
228
+ console.log(` https://twitter.com/i/status/${result.data.id}`);
229
+ }
230
+ }
231
+
232
+ async function twitterCommand(subcommand, ...args) {
233
+ switch (subcommand) {
234
+ case 'post':
235
+ case 'tweet':
236
+ await twitterPost(args.join(' '));
237
+ break;
238
+ default:
239
+ console.log('Twitter commands:');
240
+ console.log(' atris twitter post [text] - Post a tweet');
241
+ }
242
+ }
243
+
244
+ // ============================================================================
245
+ // SLACK
246
+ // ============================================================================
247
+
248
+ async function slackChannels() {
249
+ const token = await getAuthToken();
250
+
251
+ console.log('💬 Fetching Slack channels...\n');
252
+
253
+ const result = await apiRequestJson('/integrations/slack/channels', {
254
+ method: 'GET',
255
+ token,
256
+ });
257
+
258
+ if (!result.ok) {
259
+ if (result.status === 401) {
260
+ console.error('Slack not connected. Connect at: https://atris.ai/dashboard/settings');
261
+ } else {
262
+ console.error(`Error: ${result.error || 'Failed to fetch channels'}`);
263
+ }
264
+ process.exit(1);
265
+ }
266
+
267
+ const channels = result.data?.channels || result.data || [];
268
+
269
+ if (channels.length === 0) {
270
+ console.log('No channels found.');
271
+ return;
272
+ }
273
+
274
+ console.log('Channels:');
275
+ for (const ch of channels) {
276
+ const name = ch.name || ch.id;
277
+ const priv = ch.is_private ? '🔒' : '#';
278
+ console.log(` ${priv} ${name}`);
279
+ }
280
+ }
281
+
282
+ async function slackCommand(subcommand, ...args) {
283
+ switch (subcommand) {
284
+ case 'channels':
285
+ case 'list':
286
+ await slackChannels();
287
+ break;
288
+ default:
289
+ console.log('Slack commands:');
290
+ console.log(' atris slack channels - List Slack channels');
291
+ }
292
+ }
293
+
294
+ // ============================================================================
295
+ // STATUS
296
+ // ============================================================================
297
+
298
+ async function integrationsStatus() {
299
+ const token = await getAuthToken();
300
+
301
+ console.log('🔌 Integration Status:\n');
302
+
303
+ const integrations = ['gmail', 'google-calendar', 'slack', 'twitter', 'github'];
304
+
305
+ for (const name of integrations) {
306
+ try {
307
+ const result = await apiRequestJson(`/integrations/${name}/status`, {
308
+ method: 'GET',
309
+ token,
310
+ });
311
+
312
+ const connected = result.ok && result.data?.connected;
313
+ const icon = connected ? '✅' : '❌';
314
+ const displayName = name.replace('google-', '').replace('-', ' ');
315
+ console.log(` ${icon} ${displayName}`);
316
+ } catch {
317
+ console.log(` ❓ ${name}`);
318
+ }
319
+ }
320
+
321
+ console.log('\nConnect integrations at: https://atris.ai/dashboard/settings');
322
+ }
323
+
324
+ module.exports = {
325
+ gmailCommand,
326
+ calendarCommand,
327
+ twitterCommand,
328
+ slackCommand,
329
+ integrationsStatus,
330
+ };
package/commands/sync.js CHANGED
@@ -3,15 +3,47 @@ const path = require('path');
3
3
 
4
4
  function syncAtris() {
5
5
  const targetDir = path.join(process.cwd(), 'atris');
6
- const agentTeamDir = path.join(targetDir, 'agent_team');
6
+ const teamDir = path.join(targetDir, 'team');
7
+ const legacyAgentTeamDir = path.join(targetDir, 'agent_team');
7
8
 
8
9
  if (!fs.existsSync(targetDir)) {
9
10
  console.error('✗ Error: atris/ folder not found. Run "atris init" first.');
10
11
  process.exit(1);
11
12
  }
12
13
 
13
- if (!fs.existsSync(agentTeamDir)) {
14
- fs.mkdirSync(agentTeamDir, { recursive: true });
14
+ // MIGRATION: agent_team/ → team/ (v2.0.x → v2.1.0)
15
+ if (fs.existsSync(legacyAgentTeamDir)) {
16
+ console.log('');
17
+ console.log('📦 Migrating agent_team/ → team/ (v2.1.0 update)');
18
+
19
+ // Create team/ if it doesn't exist
20
+ if (!fs.existsSync(teamDir)) {
21
+ fs.mkdirSync(teamDir, { recursive: true });
22
+ }
23
+
24
+ // Copy any custom files from agent_team/ to team/
25
+ const legacyFiles = fs.readdirSync(legacyAgentTeamDir);
26
+ for (const file of legacyFiles) {
27
+ const srcPath = path.join(legacyAgentTeamDir, file);
28
+ const destPath = path.join(teamDir, file);
29
+
30
+ // Only copy if destination doesn't exist (preserve any customizations)
31
+ if (!fs.existsSync(destPath)) {
32
+ if (fs.statSync(srcPath).isFile()) {
33
+ fs.copyFileSync(srcPath, destPath);
34
+ console.log(` ✓ Migrated ${file}`);
35
+ }
36
+ }
37
+ }
38
+
39
+ // Remove old agent_team/ folder
40
+ fs.rmSync(legacyAgentTeamDir, { recursive: true, force: true });
41
+ console.log(' ✓ Removed old agent_team/ folder');
42
+ console.log('');
43
+ }
44
+
45
+ if (!fs.existsSync(teamDir)) {
46
+ fs.mkdirSync(teamDir, { recursive: true });
15
47
  }
16
48
 
17
49
  // Ensure policies folder exists
@@ -27,11 +59,11 @@ function syncAtris() {
27
59
  { source: 'PERSONA.md', target: 'PERSONA.md' },
28
60
  { source: 'GETTING_STARTED.md', target: 'GETTING_STARTED.md' },
29
61
  { source: 'atris/CLAUDE.md', target: 'CLAUDE.md' },
30
- { source: 'atris/agent_team/navigator.md', target: 'agent_team/navigator.md' },
31
- { source: 'atris/agent_team/executor.md', target: 'agent_team/executor.md' },
32
- { source: 'atris/agent_team/validator.md', target: 'agent_team/validator.md' },
33
- { source: 'atris/agent_team/launcher.md', target: 'agent_team/launcher.md' },
34
- { source: 'atris/agent_team/brainstormer.md', target: 'agent_team/brainstormer.md' },
62
+ { source: 'atris/team/navigator.md', target: 'team/navigator.md' },
63
+ { source: 'atris/team/executor.md', target: 'team/executor.md' },
64
+ { source: 'atris/team/validator.md', target: 'team/validator.md' },
65
+ { source: 'atris/team/launcher.md', target: 'team/launcher.md' },
66
+ { source: 'atris/team/brainstormer.md', target: 'team/brainstormer.md' },
35
67
  { source: 'atris/policies/ANTISLOP.md', target: 'policies/ANTISLOP.md' }
36
68
  ];
37
69
 
@@ -93,23 +125,36 @@ function syncAtris() {
93
125
  const destSkillDir = path.join(userSkillsDir, skill);
94
126
  const symlinkPath = path.join(claudeSkillsBaseDir, skill);
95
127
 
96
- // Copy skill folder if doesn't exist or update SKILL.md
97
- if (!fs.existsSync(destSkillDir)) {
98
- fs.mkdirSync(destSkillDir, { recursive: true });
99
- }
100
-
101
- // Copy/update SKILL.md
102
- const srcSkillFile = path.join(srcSkillDir, 'SKILL.md');
103
- const destSkillFile = path.join(destSkillDir, 'SKILL.md');
104
- if (fs.existsSync(srcSkillFile)) {
105
- const srcContent = fs.readFileSync(srcSkillFile, 'utf8');
106
- const destContent = fs.existsSync(destSkillFile) ? fs.readFileSync(destSkillFile, 'utf8') : '';
107
- if (srcContent !== destContent) {
108
- fs.writeFileSync(destSkillFile, srcContent);
109
- console.log(`✓ Updated atris/skills/${skill}/SKILL.md`);
110
- updated++;
128
+ // Recursive sync function for skills (handles subdirs like hooks/)
129
+ const syncRecursive = (src, dest, skillName, basePath = '') => {
130
+ if (!fs.existsSync(dest)) {
131
+ fs.mkdirSync(dest, { recursive: true });
111
132
  }
112
- }
133
+ const entries = fs.readdirSync(src);
134
+ for (const entry of entries) {
135
+ const srcPath = path.join(src, entry);
136
+ const destPath = path.join(dest, entry);
137
+ const relPath = basePath ? `${basePath}/${entry}` : entry;
138
+
139
+ if (fs.statSync(srcPath).isDirectory()) {
140
+ syncRecursive(srcPath, destPath, skillName, relPath);
141
+ } else {
142
+ const srcContent = fs.readFileSync(srcPath, 'utf8');
143
+ const destContent = fs.existsSync(destPath) ? fs.readFileSync(destPath, 'utf8') : '';
144
+ if (srcContent !== destContent) {
145
+ fs.writeFileSync(destPath, srcContent);
146
+ // Preserve executable permission for shell scripts
147
+ if (entry.endsWith('.sh')) {
148
+ fs.chmodSync(destPath, 0o755);
149
+ }
150
+ console.log(`✓ Updated atris/skills/${skillName}/${relPath}`);
151
+ updated++;
152
+ }
153
+ }
154
+ }
155
+ };
156
+
157
+ syncRecursive(srcSkillDir, destSkillDir, skill);
113
158
 
114
159
  // Create symlink if doesn't exist
115
160
  if (!fs.existsSync(symlinkPath)) {
@@ -14,7 +14,7 @@ async function planAtris(userInput = null) {
14
14
  const executionMode = executeFlag ? 'agent' : (config.execution_mode || 'prompt');
15
15
 
16
16
  const targetDir = path.join(process.cwd(), 'atris');
17
- const navigatorFile = path.join(targetDir, 'agent_team', 'navigator.md');
17
+ const navigatorFile = path.join(targetDir, 'team', 'navigator.md');
18
18
  const personaPath = path.join(targetDir, 'PERSONA.md');
19
19
  const mapFilePath = path.join(targetDir, 'MAP.md');
20
20
  const featuresReadmePath = path.join(targetDir, 'features', 'README.md');
@@ -307,7 +307,7 @@ async function doAtris() {
307
307
 
308
308
  const cwd = process.cwd();
309
309
  const targetDir = path.join(cwd, 'atris');
310
- const executorFile = path.join(targetDir, 'agent_team', 'executor.md');
310
+ const executorFile = path.join(targetDir, 'team', 'executor.md');
311
311
 
312
312
  if (!fs.existsSync(executorFile)) {
313
313
  console.log('✗ executor.md not found. Run "atris init" first.');
@@ -676,7 +676,7 @@ async function reviewAtris() {
676
676
  const executionMode = executeFlag ? 'agent' : (config.execution_mode || 'prompt');
677
677
 
678
678
  const targetDir = path.join(process.cwd(), 'atris');
679
- const validatorFile = path.join(targetDir, 'agent_team', 'validator.md');
679
+ const validatorFile = path.join(targetDir, 'team', 'validator.md');
680
680
 
681
681
  if (!fs.existsSync(validatorFile)) {
682
682
  console.log('✗ validator.md not found. Run "atris init" first.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "2.0.21",
3
+ "version": "2.2.0",
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": {
@@ -12,12 +12,14 @@
12
12
  "utils/",
13
13
  "lib/",
14
14
  "README.md",
15
+ "AGENT.md",
16
+ "AGENTS.md",
15
17
  "atris.md",
16
18
  "GETTING_STARTED.md",
17
19
  "PERSONA.md",
18
20
  "atris/CLAUDE.md",
19
21
  "atris/GEMINI.md",
20
- "atris/agent_team/",
22
+ "atris/team/",
21
23
  "atris/features/_templates/",
22
24
  "atris/policies/",
23
25
  "atris/skills/"
@@ -44,6 +46,6 @@
44
46
  "license": "MIT",
45
47
  "repository": {
46
48
  "type": "git",
47
- "url": "git+https://github.com/atrislabs/atris.md.git"
49
+ "url": "git+https://github.com/atrislabs/atris.git"
48
50
  }
49
51
  }
package/utils/auth.js CHANGED
@@ -23,7 +23,40 @@ function openBrowser(url) {
23
23
  });
24
24
  }
25
25
 
26
+ // Shared readline for piped input
27
+ let sharedRl = null;
28
+ let inputLines = [];
29
+ let inputIndex = 0;
30
+
26
31
  function promptUser(question) {
32
+ // If stdin is not a TTY (piped input), read all lines upfront
33
+ if (!process.stdin.isTTY && inputLines.length === 0 && !sharedRl) {
34
+ return new Promise((resolve) => {
35
+ let data = '';
36
+ process.stdin.setEncoding('utf8');
37
+ process.stdin.on('data', chunk => data += chunk);
38
+ process.stdin.on('end', () => {
39
+ inputLines = data.trim().split('\n');
40
+ process.stdout.write(question);
41
+ const answer = inputLines[inputIndex++] || '';
42
+ console.log(answer);
43
+ resolve(answer.trim());
44
+ });
45
+ process.stdin.resume();
46
+ });
47
+ }
48
+
49
+ // If we already have buffered lines from piped input
50
+ if (inputLines.length > 0 && inputIndex < inputLines.length) {
51
+ return new Promise((resolve) => {
52
+ process.stdout.write(question);
53
+ const answer = inputLines[inputIndex++] || '';
54
+ console.log(answer);
55
+ resolve(answer.trim());
56
+ });
57
+ }
58
+
59
+ // Interactive TTY mode - create readline per prompt
27
60
  const rl = readline.createInterface({
28
61
  input: process.stdin,
29
62
  output: process.stdout
@@ -1,10 +0,0 @@
1
- Still need to create:
2
- - visualize.js (read from bin/atris.js lines 1825-1889)
3
- - plan.js (lines 2639-2700)
4
- - do.js (lines 2702-2874)
5
- - console.js (lines 2876-3024)
6
- - review.js (lines 3026-3109)
7
- - launch.js (lines 3111-3184)
8
- - status.js (lines 3186-3307)
9
- - analytics.js (lines 3477-3611)
10
- - autopilot.js (lines 2183-2408)
File without changes
File without changes