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
package/commands/integrations.js
CHANGED
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
const { loadCredentials } = require('../utils/auth');
|
|
14
14
|
const { apiRequestJson } = require('../utils/api');
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
function getAuth() {
|
|
17
17
|
const creds = loadCredentials();
|
|
18
18
|
if (!creds || !creds.token) {
|
|
19
19
|
console.error('Not logged in. Run: atris login');
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
|
-
return creds.token;
|
|
22
|
+
return { token: creds.token, email: creds.email || 'unknown' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function getAuthToken() {
|
|
26
|
+
return getAuth().token;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
// ============================================================================
|
|
@@ -27,7 +31,7 @@ async function getAuthToken() {
|
|
|
27
31
|
// ============================================================================
|
|
28
32
|
|
|
29
33
|
async function gmailInbox(options = {}) {
|
|
30
|
-
const token =
|
|
34
|
+
const { token, email } = getAuth();
|
|
31
35
|
const limit = options.limit || 10;
|
|
32
36
|
|
|
33
37
|
console.log('📬 Fetching inbox...\n');
|
|
@@ -38,8 +42,10 @@ async function gmailInbox(options = {}) {
|
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
if (!result.ok) {
|
|
41
|
-
if (result.status === 401) {
|
|
42
|
-
console.error(
|
|
45
|
+
if (result.status === 400 || result.status === 401) {
|
|
46
|
+
console.error(`Gmail not connected for ${email}.`);
|
|
47
|
+
console.error('Connect at: https://atris.ai/dashboard/settings');
|
|
48
|
+
console.error(`Make sure you're signed in as ${email} on the web.`);
|
|
43
49
|
} else {
|
|
44
50
|
console.error(`Error: ${result.error || 'Failed to fetch inbox'}`);
|
|
45
51
|
}
|
|
@@ -123,7 +129,7 @@ async function gmailCommand(subcommand, ...args) {
|
|
|
123
129
|
// ============================================================================
|
|
124
130
|
|
|
125
131
|
async function calendarToday() {
|
|
126
|
-
const token =
|
|
132
|
+
const { token, email } = getAuth();
|
|
127
133
|
|
|
128
134
|
console.log('📅 Today\'s events:\n');
|
|
129
135
|
|
|
@@ -133,8 +139,10 @@ async function calendarToday() {
|
|
|
133
139
|
});
|
|
134
140
|
|
|
135
141
|
if (!result.ok) {
|
|
136
|
-
if (result.status === 401) {
|
|
137
|
-
console.error(
|
|
142
|
+
if (result.status === 400 || result.status === 401) {
|
|
143
|
+
console.error(`Calendar not connected for ${email}.`);
|
|
144
|
+
console.error('Connect at: https://atris.ai/dashboard/settings');
|
|
145
|
+
console.error(`Make sure you're signed in as ${email} on the web.`);
|
|
138
146
|
} else {
|
|
139
147
|
console.error(`Error: ${result.error || 'Failed to fetch events'}`);
|
|
140
148
|
}
|
|
@@ -215,8 +223,11 @@ async function twitterPost(text) {
|
|
|
215
223
|
});
|
|
216
224
|
|
|
217
225
|
if (!result.ok) {
|
|
218
|
-
if (result.status === 401) {
|
|
219
|
-
|
|
226
|
+
if (result.status === 400 || result.status === 401) {
|
|
227
|
+
const { email } = getAuth();
|
|
228
|
+
console.error(`Twitter not connected for ${email}.`);
|
|
229
|
+
console.error('Connect at: https://atris.ai/dashboard/settings');
|
|
230
|
+
console.error(`Make sure you're signed in as ${email} on the web.`);
|
|
220
231
|
} else {
|
|
221
232
|
console.error(`Error: ${result.error || 'Failed to post tweet'}`);
|
|
222
233
|
}
|
|
@@ -256,8 +267,11 @@ async function slackChannels() {
|
|
|
256
267
|
});
|
|
257
268
|
|
|
258
269
|
if (!result.ok) {
|
|
259
|
-
if (result.status === 401) {
|
|
260
|
-
|
|
270
|
+
if (result.status === 400 || result.status === 401) {
|
|
271
|
+
const { email } = getAuth();
|
|
272
|
+
console.error(`Slack not connected for ${email}.`);
|
|
273
|
+
console.error('Connect at: https://atris.ai/dashboard/settings');
|
|
274
|
+
console.error(`Make sure you're signed in as ${email} on the web.`);
|
|
261
275
|
} else {
|
|
262
276
|
console.error(`Error: ${result.error || 'Failed to fetch channels'}`);
|
|
263
277
|
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const { findAllSkills, parseFrontmatter } = require('./skill');
|
|
6
|
+
|
|
7
|
+
// --- Recursive Copy Helper ---
|
|
8
|
+
|
|
9
|
+
function copyRecursive(src, dest) {
|
|
10
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
11
|
+
const entries = fs.readdirSync(src);
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (entry === '.DS_Store' || entry === '.git') continue;
|
|
14
|
+
const srcPath = path.join(src, entry);
|
|
15
|
+
const destPath = path.join(dest, entry);
|
|
16
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
17
|
+
copyRecursive(srcPath, destPath);
|
|
18
|
+
} else {
|
|
19
|
+
fs.copyFileSync(srcPath, destPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --- Generate plugin.json manifest ---
|
|
25
|
+
|
|
26
|
+
function generateManifest(skills, projectDir) {
|
|
27
|
+
let pkg = {};
|
|
28
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
29
|
+
if (fs.existsSync(pkgPath)) {
|
|
30
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
name: 'atris-workspace',
|
|
35
|
+
version: pkg.version || '1.0.0',
|
|
36
|
+
description: `Atris workspace skills for Cowork — ${skills.length} integrations (email, calendar, slack, drive, notion, and more)`,
|
|
37
|
+
author: {
|
|
38
|
+
name: typeof pkg.author === 'string' ? pkg.author : (pkg.author?.name || 'Atris Labs')
|
|
39
|
+
},
|
|
40
|
+
homepage: pkg.homepage || 'https://github.com/atrislabs/atris',
|
|
41
|
+
keywords: ['atris', 'workspace', 'integrations', 'email', 'calendar', 'slack', 'notion', 'drive']
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// --- Generate /atris-setup command ---
|
|
46
|
+
|
|
47
|
+
function generateSetupCommand() {
|
|
48
|
+
return `---
|
|
49
|
+
description: Set up Atris authentication and connect integrations (Gmail, Calendar, Slack, Notion, Drive)
|
|
50
|
+
allowed-tools: Bash, Read
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
# Atris Setup
|
|
54
|
+
|
|
55
|
+
Run the following steps to bootstrap Atris for this workspace.
|
|
56
|
+
|
|
57
|
+
## Step 1: Check if Atris CLI is installed
|
|
58
|
+
|
|
59
|
+
\`\`\`bash
|
|
60
|
+
command -v atris || npm install -g atris
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
If the install fails, tell the user to run \`npm install -g atris\` manually.
|
|
64
|
+
|
|
65
|
+
## Step 2: Authenticate with AtrisOS
|
|
66
|
+
|
|
67
|
+
\`\`\`bash
|
|
68
|
+
atris whoami
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
If not logged in, run:
|
|
72
|
+
|
|
73
|
+
\`\`\`bash
|
|
74
|
+
atris login
|
|
75
|
+
\`\`\`
|
|
76
|
+
|
|
77
|
+
This is interactive — the user will need to complete OAuth in their browser and paste the CLI code. Guide them through it.
|
|
78
|
+
|
|
79
|
+
## Step 3: Check integration status
|
|
80
|
+
|
|
81
|
+
After login, check which integrations are connected:
|
|
82
|
+
|
|
83
|
+
\`\`\`bash
|
|
84
|
+
TOKEN=$(node -e "console.log(require('$HOME/.atris/credentials.json').token)")
|
|
85
|
+
|
|
86
|
+
echo "=== Gmail ==="
|
|
87
|
+
curl -s "https://api.atris.ai/api/integrations/gmail/status" -H "Authorization: Bearer $TOKEN"
|
|
88
|
+
|
|
89
|
+
echo "\\n=== Google Calendar ==="
|
|
90
|
+
curl -s "https://api.atris.ai/api/integrations/google-calendar/status" -H "Authorization: Bearer $TOKEN"
|
|
91
|
+
|
|
92
|
+
echo "\\n=== Slack ==="
|
|
93
|
+
curl -s "https://api.atris.ai/api/integrations/slack/status" -H "Authorization: Bearer $TOKEN"
|
|
94
|
+
|
|
95
|
+
echo "\\n=== Notion ==="
|
|
96
|
+
curl -s "https://api.atris.ai/api/integrations/notion/status" -H "Authorization: Bearer $TOKEN"
|
|
97
|
+
|
|
98
|
+
echo "\\n=== Google Drive ==="
|
|
99
|
+
curl -s "https://api.atris.ai/api/integrations/google-drive/status" -H "Authorization: Bearer $TOKEN"
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
## Step 4: Connect missing integrations
|
|
103
|
+
|
|
104
|
+
For any integration that shows \`"connected": false\`, guide the user through the OAuth flow:
|
|
105
|
+
|
|
106
|
+
1. Call the integration's \`/start\` endpoint to get an auth URL
|
|
107
|
+
2. Have the user open the URL in their browser
|
|
108
|
+
3. After authorizing, confirm the connection by re-checking status
|
|
109
|
+
|
|
110
|
+
Tell the user which integrations are connected and which still need setup. They can connect more later by running \`/atris-setup\` again.
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- Generate README.md ---
|
|
115
|
+
|
|
116
|
+
function generateREADME(skills) {
|
|
117
|
+
const skillList = skills.map(s => {
|
|
118
|
+
const fm = s.frontmatter || {};
|
|
119
|
+
const name = fm.name || s.folder || 'unknown';
|
|
120
|
+
const desc = (fm.description || '').split('.')[0] || 'No description';
|
|
121
|
+
return `- **${name}**: ${desc}`;
|
|
122
|
+
}).join('\n');
|
|
123
|
+
|
|
124
|
+
return `# Atris Workspace Plugin
|
|
125
|
+
|
|
126
|
+
Brings Atris CLI skills into the Cowork desktop app.
|
|
127
|
+
|
|
128
|
+
## Setup
|
|
129
|
+
|
|
130
|
+
1. Install this plugin in Cowork
|
|
131
|
+
2. Run \`/atris-setup\` to authenticate and connect integrations
|
|
132
|
+
3. Start using skills — they trigger automatically based on your requests
|
|
133
|
+
|
|
134
|
+
## Included Skills (${skills.length})
|
|
135
|
+
|
|
136
|
+
${skillList}
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- [Atris CLI](https://www.npmjs.com/package/atris) (\`npm install -g atris\`)
|
|
141
|
+
- AtrisOS account (free at https://atris.ai)
|
|
142
|
+
|
|
143
|
+
## Learn More
|
|
144
|
+
|
|
145
|
+
- Docs: https://github.com/atrislabs/atris
|
|
146
|
+
- CLI help: \`atris help\`
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// --- BUILD subcommand ---
|
|
151
|
+
|
|
152
|
+
function buildPlugin(...args) {
|
|
153
|
+
const projectDir = process.cwd();
|
|
154
|
+
const atrisDir = path.join(projectDir, 'atris');
|
|
155
|
+
const skillsDir = path.join(atrisDir, 'skills');
|
|
156
|
+
|
|
157
|
+
// 1. Validate
|
|
158
|
+
if (!fs.existsSync(atrisDir)) {
|
|
159
|
+
console.error('✗ Error: atris/ folder not found. Run "atris init" first.');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
if (!fs.existsSync(skillsDir)) {
|
|
163
|
+
console.error('✗ Error: atris/skills/ folder not found.');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 2. Discover skills
|
|
168
|
+
const allSkills = findAllSkills(skillsDir);
|
|
169
|
+
if (allSkills.length === 0) {
|
|
170
|
+
console.warn('⚠ No skills found in atris/skills/. Plugin will be empty.');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(`\n📦 Building Atris plugin (${allSkills.length} skills)...\n`);
|
|
174
|
+
|
|
175
|
+
// 3. Create temp dir
|
|
176
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-plugin-'));
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// 4. Create .claude-plugin/plugin.json
|
|
180
|
+
const pluginMetaDir = path.join(tempDir, '.claude-plugin');
|
|
181
|
+
fs.mkdirSync(pluginMetaDir, { recursive: true });
|
|
182
|
+
const manifest = generateManifest(allSkills, projectDir);
|
|
183
|
+
fs.writeFileSync(
|
|
184
|
+
path.join(pluginMetaDir, 'plugin.json'),
|
|
185
|
+
JSON.stringify(manifest, null, 2)
|
|
186
|
+
);
|
|
187
|
+
console.log(' ✓ Created plugin.json manifest');
|
|
188
|
+
|
|
189
|
+
// 5. Copy all skills
|
|
190
|
+
const pluginSkillsDir = path.join(tempDir, 'skills');
|
|
191
|
+
fs.mkdirSync(pluginSkillsDir, { recursive: true });
|
|
192
|
+
|
|
193
|
+
for (const skill of allSkills) {
|
|
194
|
+
// skill object from findAllSkills has .dir (full path) and we need folder name
|
|
195
|
+
const skillFolder = skill.folder || path.basename(skill.dir || skill.path || '');
|
|
196
|
+
if (!skillFolder) continue;
|
|
197
|
+
|
|
198
|
+
const srcPath = path.join(skillsDir, skillFolder);
|
|
199
|
+
const destPath = path.join(pluginSkillsDir, skillFolder);
|
|
200
|
+
|
|
201
|
+
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
202
|
+
copyRecursive(srcPath, destPath);
|
|
203
|
+
const fm = skill.frontmatter || {};
|
|
204
|
+
console.log(` ✓ ${fm.name || skillFolder}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 6. Create commands/atris-setup.md
|
|
209
|
+
const commandsDir = path.join(tempDir, 'commands');
|
|
210
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
211
|
+
fs.writeFileSync(path.join(commandsDir, 'atris-setup.md'), generateSetupCommand());
|
|
212
|
+
console.log(' ✓ Created /atris-setup command');
|
|
213
|
+
|
|
214
|
+
// 7. Create README.md
|
|
215
|
+
fs.writeFileSync(path.join(tempDir, 'README.md'), generateREADME(allSkills));
|
|
216
|
+
console.log(' ✓ Created README.md');
|
|
217
|
+
|
|
218
|
+
// 8. Parse --output flag
|
|
219
|
+
let outputFile = path.join(projectDir, 'atris-workspace.plugin');
|
|
220
|
+
for (const arg of args) {
|
|
221
|
+
if (typeof arg === 'string' && arg.startsWith('--output=')) {
|
|
222
|
+
outputFile = path.resolve(arg.split('=')[1]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 9. ZIP it
|
|
227
|
+
try {
|
|
228
|
+
// Create zip in /tmp first (Cowork best practice)
|
|
229
|
+
const tmpZip = path.join(os.tmpdir(), 'atris-workspace.plugin');
|
|
230
|
+
if (fs.existsSync(tmpZip)) fs.unlinkSync(tmpZip);
|
|
231
|
+
|
|
232
|
+
execSync(`cd "${tempDir}" && zip -r "${tmpZip}" . -x "*.DS_Store" "*.git*"`, {
|
|
233
|
+
stdio: 'pipe'
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Copy to final destination
|
|
237
|
+
fs.copyFileSync(tmpZip, outputFile);
|
|
238
|
+
fs.unlinkSync(tmpZip);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.error('✗ ZIP failed. Ensure "zip" command is available.');
|
|
241
|
+
console.error(` Temp dir preserved at: ${tempDir}`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 10. Summary
|
|
246
|
+
const stats = fs.statSync(outputFile);
|
|
247
|
+
const sizeKB = (stats.size / 1024).toFixed(1);
|
|
248
|
+
console.log('');
|
|
249
|
+
console.log(`✓ Plugin built: ${path.basename(outputFile)} (${allSkills.length} skills, ${sizeKB}KB)`);
|
|
250
|
+
console.log(` Location: ${outputFile}`);
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log('Install in Cowork:');
|
|
253
|
+
console.log(' Open the .plugin file or drag it into the Cowork app.');
|
|
254
|
+
console.log(' Then run /atris-setup to authenticate.');
|
|
255
|
+
console.log('');
|
|
256
|
+
|
|
257
|
+
} finally {
|
|
258
|
+
// Clean up temp dir (only if ZIP succeeded — we skip cleanup on error above)
|
|
259
|
+
if (fs.existsSync(tempDir)) {
|
|
260
|
+
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (e) { /* ignore */ }
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// --- INFO subcommand ---
|
|
266
|
+
|
|
267
|
+
function showPluginInfo() {
|
|
268
|
+
const skillsDir = path.join(process.cwd(), 'atris', 'skills');
|
|
269
|
+
|
|
270
|
+
if (!fs.existsSync(skillsDir)) {
|
|
271
|
+
console.error('✗ atris/skills/ not found. Run "atris init" first.');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const allSkills = findAllSkills(skillsDir);
|
|
276
|
+
|
|
277
|
+
let pkg = {};
|
|
278
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
279
|
+
if (fs.existsSync(pkgPath)) {
|
|
280
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log('');
|
|
284
|
+
console.log('Atris Plugin Preview');
|
|
285
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
286
|
+
console.log(` Name: atris-workspace`);
|
|
287
|
+
console.log(` Version: ${pkg.version || '1.0.0'}`);
|
|
288
|
+
console.log(` Skills: ${allSkills.length}`);
|
|
289
|
+
console.log('');
|
|
290
|
+
|
|
291
|
+
if (allSkills.length > 0) {
|
|
292
|
+
console.log('Included skills:');
|
|
293
|
+
for (const skill of allSkills) {
|
|
294
|
+
const fm = skill.frontmatter || {};
|
|
295
|
+
const name = fm.name || skill.folder || 'unknown';
|
|
296
|
+
const desc = (fm.description || 'No description').substring(0, 65);
|
|
297
|
+
console.log(` • ${name.padEnd(16)} ${desc}`);
|
|
298
|
+
}
|
|
299
|
+
console.log('');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.log('Components:');
|
|
303
|
+
console.log(` Skills: ${allSkills.length}`);
|
|
304
|
+
console.log(' Commands: 1 (/atris-setup)');
|
|
305
|
+
console.log('');
|
|
306
|
+
console.log('Run "atris plugin build" to package.');
|
|
307
|
+
console.log('');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// --- PUBLISH subcommand ---
|
|
311
|
+
|
|
312
|
+
function publishPlugin(...args) {
|
|
313
|
+
const projectDir = process.cwd();
|
|
314
|
+
const skillsDir = path.join(projectDir, 'atris', 'skills');
|
|
315
|
+
|
|
316
|
+
if (!fs.existsSync(skillsDir)) {
|
|
317
|
+
console.error('✗ atris/skills/ not found. Run "atris init" first.');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Find the marketplace repo — check sibling dir or --repo flag
|
|
322
|
+
let marketplaceDir = null;
|
|
323
|
+
for (const arg of args) {
|
|
324
|
+
if (typeof arg === 'string' && arg.startsWith('--repo=')) {
|
|
325
|
+
marketplaceDir = path.resolve(arg.split('=')[1]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!marketplaceDir) {
|
|
329
|
+
// Default: look for atris-plugins as a sibling directory
|
|
330
|
+
const siblingDir = path.join(path.dirname(projectDir), 'atris-plugins');
|
|
331
|
+
if (fs.existsSync(siblingDir)) {
|
|
332
|
+
marketplaceDir = siblingDir;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!marketplaceDir || !fs.existsSync(marketplaceDir)) {
|
|
336
|
+
console.error('✗ Marketplace repo not found.');
|
|
337
|
+
console.error(' Expected at: ../atris-plugins/');
|
|
338
|
+
console.error(' Or specify: atris plugin publish --repo=/path/to/atris-plugins');
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const pluginDir = path.join(marketplaceDir, 'plugins', 'atris-workspace');
|
|
343
|
+
const destSkillsDir = path.join(pluginDir, 'skills');
|
|
344
|
+
const destCommandsDir = path.join(pluginDir, 'commands');
|
|
345
|
+
|
|
346
|
+
console.log(`\n📡 Publishing to ${path.basename(marketplaceDir)}...\n`);
|
|
347
|
+
|
|
348
|
+
// 1. Clear old skills and re-copy
|
|
349
|
+
if (fs.existsSync(destSkillsDir)) {
|
|
350
|
+
fs.rmSync(destSkillsDir, { recursive: true, force: true });
|
|
351
|
+
}
|
|
352
|
+
fs.mkdirSync(destSkillsDir, { recursive: true });
|
|
353
|
+
|
|
354
|
+
const allSkills = findAllSkills(skillsDir);
|
|
355
|
+
for (const skill of allSkills) {
|
|
356
|
+
const skillFolder = skill.folder || path.basename(skill.dir || skill.path || '');
|
|
357
|
+
if (!skillFolder) continue;
|
|
358
|
+
const srcPath = path.join(skillsDir, skillFolder);
|
|
359
|
+
const destPath = path.join(destSkillsDir, skillFolder);
|
|
360
|
+
if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
|
|
361
|
+
copyRecursive(srcPath, destPath);
|
|
362
|
+
const fm = skill.frontmatter || {};
|
|
363
|
+
console.log(` ✓ ${fm.name || skillFolder}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 2. Update commands
|
|
368
|
+
fs.mkdirSync(destCommandsDir, { recursive: true });
|
|
369
|
+
fs.writeFileSync(path.join(destCommandsDir, 'atris-setup.md'), generateSetupCommand());
|
|
370
|
+
console.log(' ✓ /atris-setup command');
|
|
371
|
+
|
|
372
|
+
// 3. Update plugin.json version
|
|
373
|
+
let pkg = {};
|
|
374
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
375
|
+
if (fs.existsSync(pkgPath)) {
|
|
376
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
377
|
+
}
|
|
378
|
+
const pluginJsonPath = path.join(pluginDir, '.claude-plugin', 'plugin.json');
|
|
379
|
+
if (fs.existsSync(pluginJsonPath)) {
|
|
380
|
+
const pluginJson = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
|
|
381
|
+
pluginJson.version = pkg.version || pluginJson.version;
|
|
382
|
+
fs.writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + '\n');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 4. Update marketplace.json version
|
|
386
|
+
const marketplaceJsonPath = path.join(marketplaceDir, '.claude-plugin', 'marketplace.json');
|
|
387
|
+
if (fs.existsSync(marketplaceJsonPath)) {
|
|
388
|
+
const mkt = JSON.parse(fs.readFileSync(marketplaceJsonPath, 'utf8'));
|
|
389
|
+
const entry = (mkt.plugins || []).find(p => p.name === 'atris-workspace');
|
|
390
|
+
if (entry) {
|
|
391
|
+
entry.version = pkg.version || entry.version;
|
|
392
|
+
}
|
|
393
|
+
fs.writeFileSync(marketplaceJsonPath, JSON.stringify(mkt, null, 2) + '\n');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 5. Update README
|
|
397
|
+
fs.writeFileSync(path.join(pluginDir, 'README.md'), generateREADME(allSkills));
|
|
398
|
+
console.log(' ✓ README.md');
|
|
399
|
+
|
|
400
|
+
// 6. Git commit + push
|
|
401
|
+
console.log('');
|
|
402
|
+
const version = pkg.version || 'latest';
|
|
403
|
+
try {
|
|
404
|
+
execSync('git add -A', { cwd: marketplaceDir, stdio: 'pipe' });
|
|
405
|
+
const status = execSync('git status --porcelain', { cwd: marketplaceDir, encoding: 'utf8' }).trim();
|
|
406
|
+
if (!status) {
|
|
407
|
+
console.log('✓ No changes — marketplace already up to date.');
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
execSync(`git commit -m "chore: Update atris-workspace plugin to v${version}"`, {
|
|
411
|
+
cwd: marketplaceDir, stdio: 'pipe'
|
|
412
|
+
});
|
|
413
|
+
execSync('git push', { cwd: marketplaceDir, stdio: 'pipe' });
|
|
414
|
+
console.log(`✓ Published v${version} (${allSkills.length} skills) → pushed to remote`);
|
|
415
|
+
} catch (e) {
|
|
416
|
+
console.error('⚠ Skills synced but git push failed. Commit manually:');
|
|
417
|
+
console.error(` cd ${marketplaceDir} && git add -A && git commit -m "update" && git push`);
|
|
418
|
+
}
|
|
419
|
+
console.log('');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// --- Main Dispatcher ---
|
|
423
|
+
|
|
424
|
+
function pluginCommand(subcommand, ...args) {
|
|
425
|
+
switch (subcommand) {
|
|
426
|
+
case 'build':
|
|
427
|
+
buildPlugin(...args);
|
|
428
|
+
break;
|
|
429
|
+
case 'info':
|
|
430
|
+
case 'preview':
|
|
431
|
+
showPluginInfo();
|
|
432
|
+
break;
|
|
433
|
+
case 'publish':
|
|
434
|
+
case 'sync':
|
|
435
|
+
publishPlugin(...args);
|
|
436
|
+
break;
|
|
437
|
+
default:
|
|
438
|
+
console.log('');
|
|
439
|
+
console.log('Usage: atris plugin <subcommand>');
|
|
440
|
+
console.log('');
|
|
441
|
+
console.log('Subcommands:');
|
|
442
|
+
console.log(' build [--output=path] - Package skills into .plugin for Cowork');
|
|
443
|
+
console.log(' publish [--repo=path] - Sync skills to marketplace repo and push');
|
|
444
|
+
console.log(' info - Preview what will be included');
|
|
445
|
+
console.log('');
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
module.exports = { pluginCommand };
|
package/commands/sync.js
CHANGED
|
@@ -292,4 +292,90 @@ After displaying the boot output, respond to the user naturally.
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Lightweight skill-only sync. Compares package skills against project skills
|
|
297
|
+
* and updates any that differ. Called automatically from `atris activate`.
|
|
298
|
+
* Returns number of files updated (0 = already current).
|
|
299
|
+
*/
|
|
300
|
+
function syncSkills({ silent = false } = {}) {
|
|
301
|
+
const targetDir = path.join(process.cwd(), 'atris');
|
|
302
|
+
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)) {
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
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
|
+
let updated = 0;
|
|
318
|
+
|
|
319
|
+
const skillFolders = fs.readdirSync(packageSkillsDir).filter(f =>
|
|
320
|
+
fs.statSync(path.join(packageSkillsDir, f)).isDirectory()
|
|
321
|
+
);
|
|
322
|
+
|
|
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);
|
|
327
|
+
|
|
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
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
syncRecursive(srcSkillDir, destSkillDir, skill);
|
|
358
|
+
|
|
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'));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return updated;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
module.exports = { syncAtris, syncSkills };
|
package/package.json
CHANGED