atris 2.1.0 → 2.2.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.
Files changed (44) hide show
  1. package/AGENT.md +35 -0
  2. package/AGENTS.md +46 -0
  3. package/GETTING_STARTED.md +2 -2
  4. package/PERSONA.md +5 -1
  5. package/README.md +16 -8
  6. package/atris/AGENTS.md +25 -0
  7. package/atris/GEMINI.md +8 -0
  8. package/atris/GETTING_STARTED.md +2 -2
  9. package/atris/atris.md +4 -3
  10. package/atris/features/README.md +41 -15
  11. package/atris/policies/LESSONS.md +1 -0
  12. package/atris/skills/README.md +45 -14
  13. package/atris/skills/atris/SKILL.md +7 -0
  14. package/atris/skills/autopilot/SKILL.md +9 -4
  15. package/atris/skills/autopilot/atris-autopilot.md +71 -0
  16. package/atris/skills/autopilot/hooks/stop-hook.sh +79 -0
  17. package/atris/skills/backend/SKILL.md +6 -1
  18. package/atris/skills/calendar/SKILL.md +301 -0
  19. package/atris/skills/clawhub/atris/SKILL.md +121 -0
  20. package/atris/skills/copy-editor/SKILL.md +470 -0
  21. package/atris/skills/design/SKILL.md +5 -1
  22. package/atris/skills/drive/SKILL.md +333 -0
  23. package/atris/skills/email-agent/SKILL.md +376 -0
  24. package/atris/skills/memory/SKILL.md +8 -0
  25. package/atris/skills/meta/SKILL.md +4 -0
  26. package/atris/skills/skill-improver/SKILL.md +147 -0
  27. package/atris/skills/writing/SKILL.md +3 -0
  28. package/atris/team/brainstormer.md +1 -0
  29. package/atris/team/executor.md +27 -0
  30. package/atris/team/launcher.md +1 -0
  31. package/atris/team/navigator.md +44 -5
  32. package/atris/team/validator.md +44 -3
  33. package/atris.md +37 -1
  34. package/bin/atris.js +58 -5
  35. package/commands/auth.js +24 -4
  36. package/commands/init.js +140 -17
  37. package/commands/integrations.js +330 -0
  38. package/commands/skill.js +496 -0
  39. package/commands/status.js +9 -1
  40. package/commands/sync.js +64 -19
  41. package/commands/workflow.js +7 -0
  42. package/package.json +4 -2
  43. package/utils/auth.js +33 -0
  44. package/commands/stubs.txt +0 -10
package/commands/init.js CHANGED
@@ -46,10 +46,13 @@ function detectProjectContext(projectRoot = process.cwd()) {
46
46
  try {
47
47
  const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
48
48
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
49
+ // Check meta-frameworks first (they include base frameworks as deps)
50
+ if (deps.next) return 'next';
51
+ if (deps.nuxt) return 'nuxt';
52
+ if (deps.angular || deps['@angular/core']) return 'angular';
53
+ // Then base frameworks
49
54
  if (deps.react || deps['react-dom']) return 'react';
50
55
  if (deps.vue) return 'vue';
51
- if (deps.angular || deps['@angular/core']) return 'angular';
52
- if (deps.next) return 'next';
53
56
  if (deps['express']) return 'express';
54
57
  if (deps['fastify']) return 'fastify';
55
58
  return 'nodejs';
@@ -229,7 +232,8 @@ ${profile.hasCode ? `**Validation:** Run \`${profile.testCommand}\` to verify ch
229
232
 
230
233
  function initAtris() {
231
234
  const targetDir = path.join(process.cwd(), 'atris');
232
- const agentTeamDir = path.join(targetDir, 'team');
235
+ const teamDir = path.join(targetDir, 'team');
236
+ const legacyAgentTeamDir = path.join(targetDir, 'agent_team');
233
237
  const sourceFile = path.join(__dirname, '..', 'atris.md');
234
238
  const targetFile = path.join(targetDir, 'atris.md');
235
239
 
@@ -240,8 +244,39 @@ function initAtris() {
240
244
  console.log('✓ atris/ folder already exists');
241
245
  }
242
246
 
243
- if (!fs.existsSync(agentTeamDir)) {
244
- fs.mkdirSync(agentTeamDir, { recursive: true });
247
+ // MIGRATION: agent_team/ → team/ (v2.0.x → v2.1.0)
248
+ if (fs.existsSync(legacyAgentTeamDir)) {
249
+ console.log('');
250
+ console.log('📦 Migrating agent_team/ → team/ (v2.1.0 update)');
251
+
252
+ // Create team/ if it doesn't exist
253
+ if (!fs.existsSync(teamDir)) {
254
+ fs.mkdirSync(teamDir, { recursive: true });
255
+ }
256
+
257
+ // Copy any custom files from agent_team/ to team/
258
+ const legacyFiles = fs.readdirSync(legacyAgentTeamDir);
259
+ for (const file of legacyFiles) {
260
+ const srcPath = path.join(legacyAgentTeamDir, file);
261
+ const destPath = path.join(teamDir, file);
262
+
263
+ // Only copy if destination doesn't exist (preserve any customizations)
264
+ if (!fs.existsSync(destPath)) {
265
+ if (fs.statSync(srcPath).isFile()) {
266
+ fs.copyFileSync(srcPath, destPath);
267
+ console.log(` ✓ Migrated ${file}`);
268
+ }
269
+ }
270
+ }
271
+
272
+ // Remove old agent_team/ folder
273
+ fs.rmSync(legacyAgentTeamDir, { recursive: true, force: true });
274
+ console.log(' ✓ Removed old agent_team/ folder');
275
+ console.log('');
276
+ }
277
+
278
+ if (!fs.existsSync(teamDir)) {
279
+ fs.mkdirSync(teamDir, { recursive: true });
245
280
  console.log('✓ Created atris/team/ folder');
246
281
  }
247
282
 
@@ -249,11 +284,11 @@ function initAtris() {
249
284
  const personaFile = path.join(targetDir, 'PERSONA.md');
250
285
  const mapFile = path.join(targetDir, 'MAP.md');
251
286
  const todoFile = path.join(targetDir, 'TODO.md');
252
- const navigatorFile = path.join(agentTeamDir, 'navigator.md');
253
- const executorFile = path.join(agentTeamDir, 'executor.md');
254
- const validatorFile = path.join(agentTeamDir, 'validator.md');
255
- const launcherFile = path.join(agentTeamDir, 'launcher.md');
256
- const brainstormerFile = path.join(agentTeamDir, 'brainstormer.md');
287
+ const navigatorFile = path.join(teamDir, 'navigator.md');
288
+ const executorFile = path.join(teamDir, 'executor.md');
289
+ const validatorFile = path.join(teamDir, 'validator.md');
290
+ const launcherFile = path.join(teamDir, 'launcher.md');
291
+ const brainstormerFile = path.join(teamDir, 'brainstormer.md');
257
292
 
258
293
  const gettingStartedSource = path.join(__dirname, '..', 'GETTING_STARTED.md');
259
294
  const personaSource = path.join(__dirname, '..', 'PERSONA.md');
@@ -338,6 +373,19 @@ function initAtris() {
338
373
  console.log('✓ Created TODO.md placeholder');
339
374
  }
340
375
 
376
+ // Create lessons.md (feedback loop for learning across features)
377
+ const lessonsFile = path.join(targetDir, 'lessons.md');
378
+ if (!fs.existsSync(lessonsFile)) {
379
+ fs.writeFileSync(lessonsFile, `# lessons.md — What We Learned
380
+
381
+ > Append-only. One line per lesson. Harvested by validator after every feature.
382
+
383
+ ---
384
+
385
+ `);
386
+ console.log('✓ Created lessons.md');
387
+ }
388
+
341
389
  // Create logs directory and today's journal with bootstrap tasks
342
390
  const logsDir = path.join(targetDir, 'logs');
343
391
  const yearDir = path.join(logsDir, new Date().getFullYear().toString());
@@ -465,7 +513,7 @@ function initAtris() {
465
513
  console.log(`✓ Generated .project-profile.json (detected: ${profile.type}${profile.framework !== 'none' ? '/' + profile.framework : ''})`);
466
514
 
467
515
  // Inject project patterns into agent specs
468
- injectProjectPatterns(agentTeamDir, profile);
516
+ injectProjectPatterns(teamDir, profile);
469
517
  console.log('✓ Injected project patterns into team specs');
470
518
 
471
519
  // Create agent instruction files for different tools
@@ -560,6 +608,62 @@ Rules: 3-4 sentences max, ASCII visuals, check MAP.md first.`;
560
608
  console.log('✓ Created .claude/commands/atris.md (for Claude Code)');
561
609
  }
562
610
 
611
+ // .claude/commands/atris-autopilot.md for autonomous loops
612
+ const autopilotCommandFile = path.join(claudeCommandsDir, 'atris-autopilot.md');
613
+ if (!fs.existsSync(autopilotCommandFile)) {
614
+ fs.mkdirSync(claudeCommandsDir, { recursive: true });
615
+ const autopilotCommand = `---
616
+ description: PRD-driven autonomous execution - give it a task, it loops until done
617
+ arguments:
618
+ - name: task
619
+ description: What to build (e.g., "Add dark mode toggle")
620
+ required: true
621
+ - name: max-iterations
622
+ description: Max loops before stopping (default 10)
623
+ required: false
624
+ ---
625
+
626
+ # Atris Autopilot
627
+
628
+ Autonomous mode. Loop until task complete or max iterations.
629
+
630
+ ## Setup State
631
+
632
+ \`\`\`bash
633
+ mkdir -p .claude
634
+ cat > .claude/atris-autopilot.state.md << 'STATEEOF'
635
+ ---
636
+ iteration: 1
637
+ max_iterations: \${2:-10}
638
+ completion_promise: <promise>COMPLETE</promise>
639
+ ---
640
+
641
+ $1
642
+ STATEEOF
643
+ \`\`\`
644
+
645
+ ## Task: $1
646
+
647
+ ## Process (each iteration)
648
+
649
+ 1. **PLAN** — Read MAP.md, identify ONE thing to do
650
+ 2. **DO** — Implement it, commit
651
+ 3. **REVIEW** — Check acceptance criteria
652
+
653
+ ## Rules
654
+
655
+ - ONE thing per iteration
656
+ - Check MAP.md before touching code
657
+ - Search before assuming not implemented
658
+ - When done: \`<promise>COMPLETE</promise>\`
659
+
660
+ ## Start
661
+
662
+ Read atris/MAP.md. Begin iteration 1.`;
663
+ fs.writeFileSync(autopilotCommandFile, autopilotCommand);
664
+ console.log('✓ Created .claude/commands/atris-autopilot.md (autonomous loops)');
665
+ }
666
+
563
667
  // Copy skills from package to atris/skills/ and symlink to .claude/skills/
564
668
  const skillsSourceDir = path.join(__dirname, '..', 'atris', 'skills');
565
669
  const skillsTargetDir = path.join(targetDir, 'skills');
@@ -582,12 +686,21 @@ Rules: 3-4 sentences max, ASCII visuals, check MAP.md first.`;
582
686
  const destSkillDir = path.join(skillsTargetDir, skill);
583
687
 
584
688
  if (!fs.existsSync(destSkillDir)) {
585
- fs.mkdirSync(destSkillDir, { recursive: true });
586
- // Copy all files in skill folder
587
- const files = fs.readdirSync(srcSkillDir);
588
- for (const file of files) {
589
- fs.copyFileSync(path.join(srcSkillDir, file), path.join(destSkillDir, file));
590
- }
689
+ // Recursive copy function for skills (handles subdirs like hooks/)
690
+ const copyRecursive = (src, dest) => {
691
+ fs.mkdirSync(dest, { recursive: true });
692
+ const entries = fs.readdirSync(src);
693
+ for (const entry of entries) {
694
+ const srcPath = path.join(src, entry);
695
+ const destPath = path.join(dest, entry);
696
+ if (fs.statSync(srcPath).isDirectory()) {
697
+ copyRecursive(srcPath, destPath);
698
+ } else {
699
+ fs.copyFileSync(srcPath, destPath);
700
+ }
701
+ }
702
+ };
703
+ copyRecursive(srcSkillDir, destSkillDir);
591
704
  console.log(`✓ Copied skill: ${skill}`);
592
705
  }
593
706
  }
@@ -659,6 +772,16 @@ Rules: 3-4 sentences max, ASCII visuals, check MAP.md first.`;
659
772
  }
660
773
  ]
661
774
  }
775
+ ],
776
+ Stop: [
777
+ {
778
+ hooks: [
779
+ {
780
+ type: "command",
781
+ command: "atris/skills/autopilot/hooks/stop-hook.sh"
782
+ }
783
+ ]
784
+ }
662
785
  ]
663
786
  }
664
787
  };
@@ -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
+ };