atris 3.12.0 → 3.13.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.
- package/README.md +13 -12
- package/atris/skills/atris-feedback/SKILL.md +108 -0
- package/bin/atris.js +22 -11
- package/commands/business.js +124 -21
- package/commands/computer.js +145 -6
- package/commands/errors.js +155 -0
- package/commands/feedback.js +223 -71
- package/commands/proof.js +115 -0
- package/commands/pull.js +4 -2
- package/commands/push.js +6 -0
- package/commands/visualize.js +324 -8
- package/package.json +1 -1
package/commands/feedback.js
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
* Feedback command for Atris CLI
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* atris feedback "message here"
|
|
6
|
-
* atris feedback
|
|
7
|
-
* atris feedback list
|
|
5
|
+
* atris feedback "message here" Submit feedback
|
|
6
|
+
* atris feedback List feedback
|
|
7
|
+
* atris feedback list List feedback
|
|
8
|
+
* atris feedback resolve <id> "<resolution>" Mark resolved (admin)
|
|
9
|
+
* atris feedback close <id> Close as wontfix (admin)
|
|
10
|
+
* atris feedback delete <id> Delete feedback (admin)
|
|
11
|
+
*
|
|
12
|
+
* IDs may be the first 8 chars of the UUID — the CLI resolves the prefix
|
|
13
|
+
* against the live list before making the write request.
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
16
|
const fs = require('fs');
|
|
@@ -21,40 +27,17 @@ function getAuth() {
|
|
|
21
27
|
return { token: creds.token, email: creds.email || 'unknown' };
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
function
|
|
25
|
-
// 1. Check .atris/business.json in current directory
|
|
26
|
-
const bizFile = path.join(process.cwd(), '.atris', 'business.json');
|
|
27
|
-
if (fs.existsSync(bizFile)) {
|
|
28
|
-
try {
|
|
29
|
-
const biz = JSON.parse(fs.readFileSync(bizFile, 'utf8'));
|
|
30
|
-
if (biz.business_id) return biz.business_id;
|
|
31
|
-
} catch {}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// 2. Check ~/.atris/businesses.json (first connected business)
|
|
35
|
-
const home = require('os').homedir();
|
|
36
|
-
const globalBizFile = path.join(home, '.atris', 'businesses.json');
|
|
37
|
-
if (fs.existsSync(globalBizFile)) {
|
|
38
|
-
try {
|
|
39
|
-
const businesses = JSON.parse(fs.readFileSync(globalBizFile, 'utf8'));
|
|
40
|
-
const slugs = Object.keys(businesses);
|
|
41
|
-
if (slugs.length > 0 && businesses[slugs[0]].business_id) {
|
|
42
|
-
return businesses[slugs[0]].business_id;
|
|
43
|
-
}
|
|
44
|
-
} catch {}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function submitFeedback(message) {
|
|
30
|
+
async function submitFeedback(message, opts = {}) {
|
|
51
31
|
if (!message) {
|
|
52
|
-
console.error('Usage: atris feedback "your message here"');
|
|
32
|
+
console.error('Usage: atris feedback "your message here" [--business <slug|id>]');
|
|
53
33
|
process.exit(1);
|
|
54
34
|
}
|
|
55
35
|
|
|
56
36
|
const { token } = getAuth();
|
|
57
|
-
|
|
37
|
+
// Only attach business_id when the user explicitly asked for it via --business.
|
|
38
|
+
// Auto-scoping to "first business in businesses.json" silently hid feedback
|
|
39
|
+
// from every other workspace the user belongs to.
|
|
40
|
+
const businessId = opts.businessId || null;
|
|
58
41
|
|
|
59
42
|
const body = {
|
|
60
43
|
message,
|
|
@@ -81,70 +64,239 @@ async function submitFeedback(message) {
|
|
|
81
64
|
}
|
|
82
65
|
}
|
|
83
66
|
|
|
67
|
+
async function fetchFeedbackItems({ token, businessId, limit = 100, status = null } = {}) {
|
|
68
|
+
let url = `/feedback?limit=${limit}`;
|
|
69
|
+
if (businessId) url += `&business_id=${businessId}`;
|
|
70
|
+
if (status) url += `&status=${encodeURIComponent(status)}`;
|
|
71
|
+
|
|
72
|
+
const result = await apiRequestJson(url, { method: 'GET', token });
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
console.error(`Error: ${result.error || 'Failed to fetch feedback'}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
return result.data?.feedback || [];
|
|
78
|
+
}
|
|
79
|
+
|
|
84
80
|
async function listFeedback() {
|
|
85
81
|
const { token } = getAuth();
|
|
86
|
-
|
|
82
|
+
// Do NOT auto-scope by business: admins expect to see the full queue.
|
|
83
|
+
// The API already scopes non-admins to their own businesses server-side.
|
|
84
|
+
const items = await fetchFeedbackItems({ token, limit: 20 });
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
if (items.length === 0) {
|
|
87
|
+
console.log('No feedback found.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`Feedback Queue (${items.length} item${items.length !== 1 ? 's' : ''})\n`);
|
|
92
|
+
|
|
93
|
+
items.forEach((item, idx) => {
|
|
94
|
+
const status = (item.status || 'open').toUpperCase();
|
|
95
|
+
const shortId = (item.id || '').substring(0, 8);
|
|
96
|
+
const msg = item.message || '';
|
|
97
|
+
const preview = msg.length > 120 ? msg.substring(0, 120) + '...' : msg;
|
|
98
|
+
const date = item.created_at
|
|
99
|
+
? new Date(item.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
100
|
+
: '';
|
|
101
|
+
const fromEmail = item.context?.user_email || item.user_id || '';
|
|
102
|
+
|
|
103
|
+
console.log(`${idx + 1}. [${status}] id:${shortId}${date ? ' ' + date : ''}`);
|
|
104
|
+
console.log(` "${preview}"`);
|
|
105
|
+
if (fromEmail) console.log(` from: ${fromEmail}`);
|
|
106
|
+
if (item.resolution) console.log(` resolution: ${item.resolution}`);
|
|
107
|
+
console.log('');
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Resolve a short ID prefix (or full UUID) to the full UUID by scanning
|
|
113
|
+
* the list endpoint. Returns null if no unique match found.
|
|
114
|
+
*/
|
|
115
|
+
async function resolveIdPrefix(prefix, { token, businessId }) {
|
|
116
|
+
if (!prefix) return { error: 'ID required' };
|
|
117
|
+
// If it looks like a full UUID, trust it
|
|
118
|
+
if (prefix.length >= 32) return { id: prefix };
|
|
119
|
+
|
|
120
|
+
const items = await fetchFeedbackItems({ token, businessId, limit: 200 });
|
|
121
|
+
const matches = items.filter(it => (it.id || '').startsWith(prefix));
|
|
122
|
+
|
|
123
|
+
if (matches.length === 0) return { error: `No feedback matches id prefix "${prefix}"` };
|
|
124
|
+
if (matches.length > 1) {
|
|
125
|
+
return {
|
|
126
|
+
error: `Ambiguous id "${prefix}" — matches ${matches.length} items. Use a longer prefix.`,
|
|
127
|
+
};
|
|
91
128
|
}
|
|
129
|
+
return { id: matches[0].id, item: matches[0] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function resolveFeedback(idPrefix, resolution) {
|
|
133
|
+
if (!idPrefix || !resolution) {
|
|
134
|
+
console.error('Usage: atris feedback resolve <id> "<resolution>"');
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const { token } = getAuth();
|
|
92
138
|
|
|
93
|
-
const
|
|
94
|
-
|
|
139
|
+
const lookup = await resolveIdPrefix(idPrefix, { token });
|
|
140
|
+
if (lookup.error) {
|
|
141
|
+
console.error(`Error: ${lookup.error}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const result = await apiRequestJson(`/feedback/${lookup.id}`, {
|
|
146
|
+
method: 'PATCH',
|
|
95
147
|
token,
|
|
148
|
+
body: { status: 'resolved', resolution },
|
|
96
149
|
});
|
|
97
150
|
|
|
98
151
|
if (!result.ok) {
|
|
99
|
-
console.error(`Error: ${result.error || 'Failed to
|
|
152
|
+
console.error(`Error: ${result.error || 'Failed to resolve feedback'}`);
|
|
100
153
|
process.exit(1);
|
|
101
154
|
}
|
|
155
|
+
console.log(`Resolved ${lookup.id.substring(0, 8)}: ${resolution}`);
|
|
156
|
+
}
|
|
102
157
|
|
|
103
|
-
|
|
158
|
+
async function closeFeedback(idPrefix) {
|
|
159
|
+
if (!idPrefix) {
|
|
160
|
+
console.error('Usage: atris feedback close <id>');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const { token } = getAuth();
|
|
104
164
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
165
|
+
const lookup = await resolveIdPrefix(idPrefix, { token });
|
|
166
|
+
if (lookup.error) {
|
|
167
|
+
console.error(`Error: ${lookup.error}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const result = await apiRequestJson(`/feedback/${lookup.id}`, {
|
|
172
|
+
method: 'PATCH',
|
|
173
|
+
token,
|
|
174
|
+
body: { status: 'closed' },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!result.ok) {
|
|
178
|
+
console.error(`Error: ${result.error || 'Failed to close feedback'}`);
|
|
179
|
+
process.exit(1);
|
|
108
180
|
}
|
|
181
|
+
console.log(`Closed ${lookup.id.substring(0, 8)}`);
|
|
182
|
+
}
|
|
109
183
|
|
|
110
|
-
|
|
184
|
+
async function deleteFeedback(idPrefix) {
|
|
185
|
+
if (!idPrefix) {
|
|
186
|
+
console.error('Usage: atris feedback delete <id>');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const { token } = getAuth();
|
|
111
190
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
191
|
+
const lookup = await resolveIdPrefix(idPrefix, { token });
|
|
192
|
+
if (lookup.error) {
|
|
193
|
+
console.error(`Error: ${lookup.error}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const result = await apiRequestJson(`/feedback/${lookup.id}`, {
|
|
198
|
+
method: 'DELETE',
|
|
199
|
+
token,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (!result.ok) {
|
|
203
|
+
console.error(`Error: ${result.error || 'Failed to delete feedback'}`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
console.log(`Deleted ${lookup.id.substring(0, 8)}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function printHelp() {
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log('Usage:');
|
|
212
|
+
console.log(' atris feedback "message" Submit feedback (global)');
|
|
213
|
+
console.log(' atris feedback "msg" --business <slug> Submit feedback tagged to a business');
|
|
214
|
+
console.log(' atris feedback List feedback');
|
|
215
|
+
console.log(' atris feedback list List feedback');
|
|
216
|
+
console.log(' atris feedback resolve <id> "<note>" Mark resolved (admin)');
|
|
217
|
+
console.log(' atris feedback close <id> Close as wontfix (admin)');
|
|
218
|
+
console.log(' atris feedback delete <id> Delete feedback (admin)');
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log('IDs may be the first 8 chars of the UUID.');
|
|
221
|
+
console.log('Business slugs come from ~/.atris/businesses.json (e.g. pallet, atris-labs-1).');
|
|
222
|
+
console.log('');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function resolveBusinessArg(value) {
|
|
226
|
+
if (!value) return null;
|
|
227
|
+
// Full UUID — trust it
|
|
228
|
+
if (/^[0-9a-f-]{32,}$/i.test(value)) return value;
|
|
229
|
+
// Otherwise treat as slug and look up in ~/.atris/businesses.json
|
|
230
|
+
const home = require('os').homedir();
|
|
231
|
+
const file = path.join(home, '.atris', 'businesses.json');
|
|
232
|
+
if (!fs.existsSync(file)) return null;
|
|
233
|
+
try {
|
|
234
|
+
const map = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
235
|
+
const hit = map[value] || Object.values(map).find(b => b.slug === value);
|
|
236
|
+
return hit ? hit.business_id : null;
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function extractFlag(args, ...names) {
|
|
243
|
+
// Returns [value, remainingArgs]. Supports "--flag val" and "--flag=val".
|
|
244
|
+
const remaining = [];
|
|
245
|
+
let value = null;
|
|
246
|
+
for (let i = 0; i < args.length; i++) {
|
|
247
|
+
const a = args[i];
|
|
248
|
+
const eq = a.indexOf('=');
|
|
249
|
+
const key = eq >= 0 ? a.slice(0, eq) : a;
|
|
250
|
+
if (names.includes(key)) {
|
|
251
|
+
value = eq >= 0 ? a.slice(eq + 1) : args[++i];
|
|
252
|
+
} else {
|
|
253
|
+
remaining.push(a);
|
|
126
254
|
}
|
|
127
|
-
console.log('');
|
|
128
255
|
}
|
|
256
|
+
return [value, remaining];
|
|
129
257
|
}
|
|
130
258
|
|
|
131
259
|
async function feedbackCommand() {
|
|
132
|
-
const
|
|
260
|
+
const rawArgs = process.argv.slice(3);
|
|
261
|
+
const [businessArg, args] = extractFlag(rawArgs, '--business', '-b');
|
|
262
|
+
const businessId = resolveBusinessArg(businessArg);
|
|
263
|
+
if (businessArg && !businessId) {
|
|
264
|
+
console.error(`Error: unknown business "${businessArg}". Check ~/.atris/businesses.json`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const subcommand = args[0];
|
|
133
269
|
|
|
134
270
|
if (!subcommand || subcommand === 'list') {
|
|
135
271
|
await listFeedback();
|
|
136
|
-
|
|
137
|
-
console.log('');
|
|
138
|
-
console.log('Usage:');
|
|
139
|
-
console.log(' atris feedback "message" Submit feedback');
|
|
140
|
-
console.log(' atris feedback List your feedback');
|
|
141
|
-
console.log(' atris feedback list List your feedback');
|
|
142
|
-
console.log('');
|
|
143
|
-
} else {
|
|
144
|
-
// Everything else is a feedback message
|
|
145
|
-
const message = process.argv.slice(3).join(' ');
|
|
146
|
-
await submitFeedback(message);
|
|
272
|
+
return;
|
|
147
273
|
}
|
|
274
|
+
|
|
275
|
+
if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
276
|
+
printHelp();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (subcommand === 'resolve') {
|
|
281
|
+
const id = args[1];
|
|
282
|
+
const resolution = args.slice(2).join(' ');
|
|
283
|
+
await resolveFeedback(id, resolution);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (subcommand === 'close') {
|
|
288
|
+
await closeFeedback(args[1]);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (subcommand === 'delete') {
|
|
293
|
+
await deleteFeedback(args[1]);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Everything else is a feedback message
|
|
298
|
+
const message = args.join(' ');
|
|
299
|
+
await submitFeedback(message, { businessId });
|
|
148
300
|
}
|
|
149
301
|
|
|
150
302
|
module.exports = { feedbackCommand };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function readBusinessBinding(cwd = process.cwd()) {
|
|
5
|
+
const bindingPath = path.join(cwd, '.atris', 'business.json');
|
|
6
|
+
if (!fs.existsSync(bindingPath)) return null;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(fs.readFileSync(bindingPath, 'utf8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function slugify(value) {
|
|
15
|
+
return String(value || 'business-workflow')
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
18
|
+
.replace(/^-+|-+$/g, '') || 'business-workflow';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ensureDir(dir) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeJson(filePath, value) {
|
|
26
|
+
ensureDir(path.dirname(filePath));
|
|
27
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function doctor(cwd = process.cwd()) {
|
|
31
|
+
const binding = readBusinessBinding(cwd);
|
|
32
|
+
console.log('Receipt check');
|
|
33
|
+
console.log(`business binding: ${binding ? `${binding.name || binding.slug || binding.business_id} ready` : 'missing'}`);
|
|
34
|
+
console.log(`receipt folder: ${fs.existsSync(path.join(cwd, '.atris', 'receipts')) ? 'ready' : 'missing'}`);
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log('Next: atris receipt init business-workflow');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function init(taskSlug = 'business-workflow', cwd = process.cwd()) {
|
|
40
|
+
const binding = readBusinessBinding(cwd);
|
|
41
|
+
if (!binding) {
|
|
42
|
+
console.error('No business binding found. Run: atris business init <name> --here');
|
|
43
|
+
process.exitCode = 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const slug = slugify(taskSlug);
|
|
48
|
+
const root = path.join(cwd, '.atris');
|
|
49
|
+
const taskPath = path.join(root, 'tasks', `${slug}.json`);
|
|
50
|
+
const receiptsDir = path.join(root, 'receipts');
|
|
51
|
+
|
|
52
|
+
ensureDir(receiptsDir);
|
|
53
|
+
fs.writeFileSync(path.join(receiptsDir, '.gitkeep'), '');
|
|
54
|
+
writeJson(taskPath, {
|
|
55
|
+
schema: 'atris.receipt_task.v1',
|
|
56
|
+
slug,
|
|
57
|
+
goal: 'Run one business-computer task and save what happened.',
|
|
58
|
+
workspace: {
|
|
59
|
+
business_id: binding.business_id || binding.id || null,
|
|
60
|
+
workspace_id: binding.workspace_id || null,
|
|
61
|
+
name: binding.name || null,
|
|
62
|
+
slug: binding.slug || null,
|
|
63
|
+
},
|
|
64
|
+
runtime: {
|
|
65
|
+
proof_command: 'atris computer proof',
|
|
66
|
+
replay_command: 'atris experiments replay endstate',
|
|
67
|
+
},
|
|
68
|
+
verify: [
|
|
69
|
+
'atris computer proof',
|
|
70
|
+
'atris experiments replay endstate',
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log(`Receipt task ready: ${slug}`);
|
|
75
|
+
console.log(`Task: ${path.relative(cwd, taskPath)}`);
|
|
76
|
+
console.log(`Receipts: ${path.relative(cwd, receiptsDir)}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function run(args = []) {
|
|
80
|
+
const dryRun = args.includes('--dry-run');
|
|
81
|
+
console.log('Receipt run');
|
|
82
|
+
console.log('1. atris computer proof');
|
|
83
|
+
console.log('2. atris experiments replay endstate');
|
|
84
|
+
if (dryRun) {
|
|
85
|
+
console.log('Dry run only; no receipts written.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log('Run those commands, then save the receipt under .atris/receipts/.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function proofCommand(subcommand = 'doctor', ...args) {
|
|
92
|
+
switch (subcommand || 'doctor') {
|
|
93
|
+
case 'doctor':
|
|
94
|
+
return doctor();
|
|
95
|
+
case 'init':
|
|
96
|
+
return init(args[0] || 'business-workflow');
|
|
97
|
+
case 'proof':
|
|
98
|
+
return run(args);
|
|
99
|
+
case 'help':
|
|
100
|
+
case '--help':
|
|
101
|
+
case '-h':
|
|
102
|
+
console.log('Usage: atris receipt [doctor|init <slug>|run --dry-run]');
|
|
103
|
+
return;
|
|
104
|
+
case 'run':
|
|
105
|
+
return run(args);
|
|
106
|
+
default:
|
|
107
|
+
console.error(`Unknown receipt command: ${subcommand}`);
|
|
108
|
+
console.log('Usage: atris receipt [doctor|init <slug>|run --dry-run]');
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
proofCommand,
|
|
115
|
+
};
|
package/commands/pull.js
CHANGED
|
@@ -703,8 +703,10 @@ async function pullBusiness(slug) {
|
|
|
703
703
|
name: businessName,
|
|
704
704
|
}, null, 2));
|
|
705
705
|
|
|
706
|
-
// Wire skills → .claude/skills/ so they work as slash commands
|
|
707
|
-
|
|
706
|
+
// Wire skills → .claude/skills/ so they work as slash commands.
|
|
707
|
+
// Source of truth is atris/skills/ (vendor-neutral, syncs to cloud).
|
|
708
|
+
// .claude/skills/ is a locally-generated adapter Claude Code reads from.
|
|
709
|
+
const skillsDir = path.join(outputDir, 'atris', 'skills');
|
|
708
710
|
const claudeSkillsDir = path.join(outputDir, '.claude', 'skills');
|
|
709
711
|
|
|
710
712
|
if (fs.existsSync(skillsDir)) {
|
package/commands/push.js
CHANGED
|
@@ -367,6 +367,12 @@ async function pushAtris() {
|
|
|
367
367
|
|
|
368
368
|
if (!result.ok) {
|
|
369
369
|
if (result.status === 403) {
|
|
370
|
+
const detail = result.errorMessage || result.error || (result.data && result.data.detail) || '';
|
|
371
|
+
if (detail && /plan required|business, max, or enterprise/i.test(detail)) {
|
|
372
|
+
console.error(`\n Access denied: ${detail}`);
|
|
373
|
+
await emit('access_denied', { error_detail: detail });
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
370
376
|
// Permission denied — retry with only team/ and journal/ files
|
|
371
377
|
const allowed = filesToPush.filter(f => f.path.startsWith('/team/') || f.path.startsWith('/journal/'));
|
|
372
378
|
skipped = filesToPush.filter(f => !f.path.startsWith('/team/') && !f.path.startsWith('/journal/'));
|