atris 2.5.5 β 2.6.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/GETTING_STARTED.md +2 -2
- package/atris/GETTING_STARTED.md +2 -2
- package/atris/policies/atris-design.md +200 -15
- package/atris/skills/design/SKILL.md +32 -7
- package/bin/atris.js +34 -10
- package/commands/auth.js +317 -67
- package/commands/context-sync.js +226 -0
- package/commands/pull.js +118 -40
- package/commands/push.js +150 -61
- package/lib/manifest.js +222 -0
- package/package.json +9 -4
- package/utils/auth.js +127 -0
- package/AGENT.md +0 -35
- package/atris/experiments/README.md +0 -118
- package/atris/experiments/_examples/smoke-keep-revert/README.md +0 -45
- package/atris/experiments/_examples/smoke-keep-revert/candidate.py +0 -8
- package/atris/experiments/_examples/smoke-keep-revert/loop.py +0 -129
- package/atris/experiments/_examples/smoke-keep-revert/measure.py +0 -47
- package/atris/experiments/_examples/smoke-keep-revert/program.md +0 -3
- package/atris/experiments/_examples/smoke-keep-revert/proposals/bad_patch.py +0 -19
- package/atris/experiments/_examples/smoke-keep-revert/proposals/fix_patch.py +0 -22
- package/atris/experiments/_examples/smoke-keep-revert/reset.py +0 -21
- package/atris/experiments/_examples/smoke-keep-revert/results.tsv +0 -5
- package/atris/experiments/_examples/smoke-keep-revert/visual.svg +0 -52
- package/atris/experiments/_fixtures/invalid/BadName/loop.py +0 -1
- package/atris/experiments/_fixtures/invalid/BadName/program.md +0 -3
- package/atris/experiments/_fixtures/invalid/BadName/results.tsv +0 -1
- package/atris/experiments/_fixtures/invalid/bloated-context/loop.py +0 -1
- package/atris/experiments/_fixtures/invalid/bloated-context/measure.py +0 -1
- package/atris/experiments/_fixtures/invalid/bloated-context/program.md +0 -6
- package/atris/experiments/_fixtures/invalid/bloated-context/results.tsv +0 -1
- package/atris/experiments/_fixtures/valid/good-experiment/loop.py +0 -1
- package/atris/experiments/_fixtures/valid/good-experiment/measure.py +0 -1
- package/atris/experiments/_fixtures/valid/good-experiment/program.md +0 -3
- package/atris/experiments/_fixtures/valid/good-experiment/results.tsv +0 -1
- package/atris/experiments/_template/pack/loop.py +0 -3
- package/atris/experiments/_template/pack/measure.py +0 -13
- package/atris/experiments/_template/pack/program.md +0 -3
- package/atris/experiments/_template/pack/reset.py +0 -3
- package/atris/experiments/_template/pack/results.tsv +0 -1
- package/atris/experiments/benchmark_runtime.py +0 -81
- package/atris/experiments/benchmark_validate.py +0 -70
- package/atris/experiments/validate.py +0 -92
- package/atris/team/navigator/journal/2026-02-23.md +0 -6
package/commands/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, loadProfile, listProfiles, profileNameFromEmail } = require('../utils/auth');
|
|
1
|
+
const { loadCredentials, saveCredentials, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, getTerminalSessionId, setSessionProfile, getSessionProfile, clearSessionProfile, cleanStaleSessions } = require('../utils/auth');
|
|
2
2
|
const { getAppBaseUrl, apiRequestJson } = require('../utils/api');
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
@@ -11,8 +11,6 @@ async function loginAtris(options = {}) {
|
|
|
11
11
|
const directToken = tokenIndex !== -1 ? args[tokenIndex + 1] : options.token;
|
|
12
12
|
|
|
13
13
|
try {
|
|
14
|
-
console.log('π Login to AtrisOS\n');
|
|
15
|
-
|
|
16
14
|
const existing = loadCredentials();
|
|
17
15
|
|
|
18
16
|
// Direct token mode (non-interactive)
|
|
@@ -30,13 +28,24 @@ async function loginAtris(options = {}) {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
if (existing && !forceFlag) {
|
|
33
|
-
const label = existing.email || existing.user_id || 'unknown
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
console.log(
|
|
31
|
+
const label = existing.email || existing.user_id || 'unknown';
|
|
32
|
+
const profiles = listProfiles();
|
|
33
|
+
console.log(`Currently signed in as: ${label}`);
|
|
34
|
+
if (profiles.length > 1) {
|
|
35
|
+
console.log(`${profiles.length} accounts saved. Use "atris switch" to change.\n`);
|
|
36
|
+
}
|
|
37
|
+
console.log(' 1. Add another account');
|
|
38
|
+
console.log(' 2. Re-login to current account');
|
|
39
|
+
console.log(' 3. Cancel\n');
|
|
40
|
+
|
|
41
|
+
const choice = await promptUser('Choice (1-3): ');
|
|
42
|
+
if (choice === '3' || (!choice)) {
|
|
43
|
+
console.log('Cancelled.');
|
|
38
44
|
process.exit(0);
|
|
39
45
|
}
|
|
46
|
+
// Both 1 and 2 proceed to OAuth β the difference is just the prompt
|
|
47
|
+
} else if (!existing) {
|
|
48
|
+
console.log('Welcome to Atris! Let\'s get you signed in.\n');
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
console.log('Choose login method:');
|
|
@@ -44,21 +53,20 @@ async function loginAtris(options = {}) {
|
|
|
44
53
|
console.log(' 2. Paste existing API token');
|
|
45
54
|
console.log(' 3. Cancel');
|
|
46
55
|
|
|
47
|
-
const
|
|
56
|
+
const methodChoice = await promptUser('\nEnter choice (1-3): ');
|
|
48
57
|
|
|
49
|
-
if (
|
|
58
|
+
if (methodChoice === '1') {
|
|
50
59
|
const loginUrl = `${getAppBaseUrl()}/auth/cli`;
|
|
51
|
-
console.log('\
|
|
52
|
-
console.log('If it
|
|
53
|
-
console.log(loginUrl);
|
|
54
|
-
console.log('
|
|
55
|
-
console.log('Codes expire after five minutes.\n');
|
|
60
|
+
console.log('\nOpening browserβ¦');
|
|
61
|
+
console.log('If it doesn\'t open, visit:');
|
|
62
|
+
console.log(` ${loginUrl}\n`);
|
|
63
|
+
console.log('After signing in, paste the CLI code shown in the browser.\n');
|
|
56
64
|
|
|
57
65
|
openBrowser(loginUrl);
|
|
58
66
|
|
|
59
|
-
const code = await promptUser('
|
|
67
|
+
const code = await promptUser('CLI code: ');
|
|
60
68
|
if (!code) {
|
|
61
|
-
console.error('
|
|
69
|
+
console.error('Error: Code is required.');
|
|
62
70
|
process.exit(1);
|
|
63
71
|
}
|
|
64
72
|
|
|
@@ -68,7 +76,7 @@ async function loginAtris(options = {}) {
|
|
|
68
76
|
});
|
|
69
77
|
|
|
70
78
|
if (!exchange.ok || !exchange.data) {
|
|
71
|
-
console.error(
|
|
79
|
+
console.error(`Error: ${exchange.error || 'Invalid or expired code'}`);
|
|
72
80
|
process.exit(1);
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -77,49 +85,47 @@ async function loginAtris(options = {}) {
|
|
|
77
85
|
const refreshToken = payload.refresh_token;
|
|
78
86
|
|
|
79
87
|
if (!token || !refreshToken) {
|
|
80
|
-
console.error('
|
|
88
|
+
console.error('Error: Backend did not return tokens. Try again.');
|
|
81
89
|
process.exit(1);
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
const email = payload.email ||
|
|
85
|
-
const userId = payload.user_id ||
|
|
92
|
+
const email = payload.email || null;
|
|
93
|
+
const userId = payload.user_id || null;
|
|
86
94
|
const provider = payload.provider || 'atris';
|
|
87
95
|
|
|
88
96
|
saveCredentials(token, refreshToken, email, userId, provider);
|
|
89
|
-
|
|
97
|
+
const name = profileNameFromEmail(email);
|
|
98
|
+
console.log(`\nβ Signed in as ${email || 'unknown'}${name ? ` (profile: ${name})` : ''}`);
|
|
90
99
|
await displayAccountSummary(apiRequestJson);
|
|
91
|
-
console.log('\nYou can now use cloud features with atris commands.');
|
|
92
100
|
process.exit(0);
|
|
93
|
-
} else if (
|
|
94
|
-
console.log('\
|
|
95
|
-
console.log('Get your token from: https://atris.ai/auth/cli\n');
|
|
101
|
+
} else if (methodChoice === '2') {
|
|
102
|
+
console.log('\nGet your token from: https://atris.ai/auth/cli\n');
|
|
96
103
|
|
|
97
|
-
const tokenInput = await promptUser('
|
|
104
|
+
const tokenInput = await promptUser('API token: ');
|
|
98
105
|
|
|
99
106
|
if (!tokenInput) {
|
|
100
|
-
console.error('
|
|
107
|
+
console.error('Error: Token is required.');
|
|
101
108
|
process.exit(1);
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
const trimmed = tokenInput.trim();
|
|
105
112
|
saveCredentials(trimmed, null, existing?.email || null, existing?.user_id || null, existing?.provider || 'manual');
|
|
106
|
-
console.log('\
|
|
113
|
+
console.log('\nValidatingβ¦\n');
|
|
107
114
|
|
|
108
115
|
const summary = await displayAccountSummary(apiRequestJson);
|
|
109
116
|
if (summary.error) {
|
|
110
|
-
console.log('\nβ οΈ Token saved, but validation failed.
|
|
117
|
+
console.log('\nβ οΈ Token saved, but validation failed.');
|
|
111
118
|
} else {
|
|
112
|
-
console.log('\nβ Token validated
|
|
119
|
+
console.log('\nβ Token validated.');
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
console.log('\nYou can now use cloud features with atris commands.');
|
|
116
122
|
process.exit(0);
|
|
117
123
|
} else {
|
|
118
|
-
console.log('
|
|
124
|
+
console.log('Cancelled.');
|
|
119
125
|
process.exit(0);
|
|
120
126
|
}
|
|
121
127
|
} catch (error) {
|
|
122
|
-
console.error(`\
|
|
128
|
+
console.error(`\nLogin failed: ${error.message || error}`);
|
|
123
129
|
process.exit(1);
|
|
124
130
|
}
|
|
125
131
|
}
|
|
@@ -128,39 +134,52 @@ function logoutAtris() {
|
|
|
128
134
|
const credentials = loadCredentials();
|
|
129
135
|
|
|
130
136
|
if (!credentials) {
|
|
131
|
-
console.log('Not
|
|
137
|
+
console.log('Not signed in.');
|
|
132
138
|
process.exit(0);
|
|
133
139
|
}
|
|
134
140
|
|
|
141
|
+
const profiles = listProfiles();
|
|
142
|
+
const currentName = profileNameFromEmail(credentials?.email);
|
|
143
|
+
|
|
135
144
|
deleteCredentials();
|
|
136
|
-
console.log(
|
|
137
|
-
|
|
145
|
+
console.log(`β Signed out from ${credentials.email || 'current account'}`);
|
|
146
|
+
|
|
147
|
+
// Remind about other profiles
|
|
148
|
+
const remaining = profiles.filter(p => p !== currentName);
|
|
149
|
+
if (remaining.length > 0) {
|
|
150
|
+
console.log(`\n${remaining.length} other account${remaining.length > 1 ? 's' : ''} saved.`);
|
|
151
|
+
console.log(`Switch to one: atris switch ${remaining[0]}`);
|
|
152
|
+
console.log('Or remove all: atris accounts remove --all');
|
|
153
|
+
}
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
async function whoamiAtris() {
|
|
141
157
|
const { apiRequestJson } = require('../utils/api');
|
|
142
|
-
|
|
158
|
+
|
|
143
159
|
try {
|
|
144
160
|
const summary = await displayAccountSummary(apiRequestJson);
|
|
145
161
|
if (summary.error) {
|
|
146
|
-
console.log('\nRun "atris login" to
|
|
162
|
+
console.log('\nRun "atris login" to sign in.');
|
|
147
163
|
process.exit(1);
|
|
148
164
|
}
|
|
149
165
|
process.exit(0);
|
|
150
166
|
} catch (error) {
|
|
151
|
-
console.error(
|
|
167
|
+
console.error(`Failed to fetch account: ${error.message || error}`);
|
|
152
168
|
process.exit(1);
|
|
153
169
|
}
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
async function switchAccount() {
|
|
157
173
|
const args = process.argv.slice(3);
|
|
174
|
+
const globalFlag = args.includes('--global') || args.includes('-g');
|
|
158
175
|
const targetName = args.filter(a => !a.startsWith('-'))[0];
|
|
159
176
|
|
|
177
|
+
// Clean up stale session files in the background
|
|
178
|
+
cleanStaleSessions();
|
|
179
|
+
|
|
160
180
|
const profiles = listProfiles();
|
|
161
181
|
if (profiles.length === 0) {
|
|
162
|
-
console.log('No saved
|
|
163
|
-
console.log('Profiles are auto-saved on login.');
|
|
182
|
+
console.log('No saved accounts. Run "atris login" to add one.');
|
|
164
183
|
process.exit(1);
|
|
165
184
|
}
|
|
166
185
|
|
|
@@ -173,39 +192,50 @@ async function switchAccount() {
|
|
|
173
192
|
profiles.forEach((name, i) => {
|
|
174
193
|
const profile = loadProfile(name);
|
|
175
194
|
const email = profile?.email || 'unknown';
|
|
176
|
-
const marker = name === currentName ? '
|
|
195
|
+
const marker = name === currentName ? ' β active' : '';
|
|
177
196
|
console.log(` ${i + 1}. ${name} β ${email}${marker}`);
|
|
178
197
|
});
|
|
179
|
-
console.log(` ${profiles.length + 1}.
|
|
198
|
+
console.log(` ${profiles.length + 1}. Add new account`);
|
|
199
|
+
console.log(` ${profiles.length + 2}. Cancel`);
|
|
180
200
|
|
|
181
|
-
const choice = await promptUser(`\
|
|
201
|
+
const choice = await promptUser(`\nChoice (1-${profiles.length + 2}): `);
|
|
182
202
|
const idx = parseInt(choice, 10) - 1;
|
|
183
203
|
|
|
204
|
+
if (idx === profiles.length) {
|
|
205
|
+
// Add new account
|
|
206
|
+
return loginAtris({ force: true });
|
|
207
|
+
}
|
|
208
|
+
|
|
184
209
|
if (isNaN(idx) || idx < 0 || idx >= profiles.length) {
|
|
185
210
|
console.log('Cancelled.');
|
|
186
211
|
process.exit(0);
|
|
187
212
|
}
|
|
188
213
|
|
|
189
214
|
const chosen = profiles[idx];
|
|
190
|
-
return activateProfile(chosen, currentName);
|
|
215
|
+
return activateProfile(chosen, currentName, { global: globalFlag });
|
|
191
216
|
}
|
|
192
217
|
|
|
193
218
|
// Direct: atris switch <name>
|
|
194
|
-
// Fuzzy match:
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
219
|
+
// Fuzzy match: exact β startsWith β substring β email substring
|
|
220
|
+
const q = targetName.toLowerCase();
|
|
221
|
+
const match = profiles.find(p => p.toLowerCase() === q)
|
|
222
|
+
|| profiles.find(p => p.toLowerCase().startsWith(q))
|
|
223
|
+
|| profiles.find(p => p.toLowerCase().includes(q))
|
|
224
|
+
|| profiles.find(p => {
|
|
225
|
+
const profile = loadProfile(p);
|
|
226
|
+
return profile?.email?.toLowerCase().includes(q);
|
|
227
|
+
});
|
|
198
228
|
|
|
199
229
|
if (!match) {
|
|
200
|
-
console.error(`
|
|
230
|
+
console.error(`No account matching "${targetName}".`);
|
|
201
231
|
console.log(`Available: ${profiles.join(', ')}`);
|
|
202
232
|
process.exit(1);
|
|
203
233
|
}
|
|
204
234
|
|
|
205
|
-
return activateProfile(match, currentName);
|
|
235
|
+
return activateProfile(match, currentName, { global: globalFlag });
|
|
206
236
|
}
|
|
207
237
|
|
|
208
|
-
function activateProfile(name, currentName) {
|
|
238
|
+
function activateProfile(name, currentName, { global = false } = {}) {
|
|
209
239
|
if (name === currentName) {
|
|
210
240
|
console.log(`Already on "${name}".`);
|
|
211
241
|
process.exit(0);
|
|
@@ -213,37 +243,257 @@ function activateProfile(name, currentName) {
|
|
|
213
243
|
|
|
214
244
|
const profile = loadProfile(name);
|
|
215
245
|
if (!profile || !profile.token) {
|
|
216
|
-
console.error(`Profile "${name}" is corrupted.
|
|
246
|
+
console.error(`Profile "${name}" is corrupted. Run "atris login" to fix.`);
|
|
217
247
|
process.exit(1);
|
|
218
248
|
}
|
|
219
249
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
250
|
+
if (global || !getTerminalSessionId()) {
|
|
251
|
+
// Global switch β write to credentials.json (affects all terminals)
|
|
252
|
+
const credentialsPath = getCredentialsPath();
|
|
253
|
+
fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
|
|
254
|
+
try { fs.chmodSync(credentialsPath, 0o600); } catch {}
|
|
255
|
+
console.log(`Switched to ${profile.email || name} (global β all terminals)`);
|
|
256
|
+
} else {
|
|
257
|
+
// Per-terminal switch β write session file (only this terminal)
|
|
258
|
+
setSessionProfile(name);
|
|
259
|
+
console.log(`Switched to ${profile.email || name}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function useAccount() {
|
|
264
|
+
const args = process.argv.slice(3);
|
|
265
|
+
const targetName = args.filter(a => !a.startsWith('-'))[0];
|
|
266
|
+
|
|
267
|
+
if (!targetName) {
|
|
268
|
+
// Show current per-terminal override or global
|
|
269
|
+
const envProfile = process.env.ATRIS_PROFILE;
|
|
270
|
+
if (envProfile) {
|
|
271
|
+
const profile = loadProfile(envProfile);
|
|
272
|
+
const email = profile?.email || envProfile;
|
|
273
|
+
console.log(`This terminal: ${email} (ATRIS_PROFILE=${envProfile})`);
|
|
274
|
+
} else {
|
|
275
|
+
const current = loadCredentials();
|
|
276
|
+
if (current) {
|
|
277
|
+
console.log(`Global: ${current.email || 'unknown'} (no per-terminal override)`);
|
|
278
|
+
} else {
|
|
279
|
+
console.log('Not signed in.');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
console.log('\nSet per-terminal account:');
|
|
283
|
+
console.log(' eval "$(atris use <name>)"');
|
|
284
|
+
console.log('\nOr manually:');
|
|
285
|
+
console.log(' export ATRIS_PROFILE=<name>');
|
|
286
|
+
process.exit(0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Fuzzy match the profile
|
|
290
|
+
const profiles = listProfiles();
|
|
291
|
+
const q = targetName.toLowerCase();
|
|
292
|
+
const match = profiles.find(p => p.toLowerCase() === q)
|
|
293
|
+
|| profiles.find(p => p.toLowerCase().startsWith(q))
|
|
294
|
+
|| profiles.find(p => p.toLowerCase().includes(q))
|
|
295
|
+
|| profiles.find(p => {
|
|
296
|
+
const profile = loadProfile(p);
|
|
297
|
+
return profile?.email?.toLowerCase().includes(q);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
if (!match) {
|
|
301
|
+
console.error(`No account matching "${targetName}".`);
|
|
302
|
+
console.error(`Available: ${profiles.join(', ')}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
224
305
|
|
|
225
|
-
|
|
306
|
+
const profile = loadProfile(match);
|
|
307
|
+
const email = profile?.email || match;
|
|
308
|
+
|
|
309
|
+
// If stdout is piped (eval mode), output just the export
|
|
310
|
+
if (!process.stdout.isTTY) {
|
|
311
|
+
process.stdout.write(`export ATRIS_PROFILE=${match}\n`);
|
|
312
|
+
} else {
|
|
313
|
+
// Interactive β print instructions
|
|
314
|
+
console.log(`export ATRIS_PROFILE=${match}`);
|
|
315
|
+
console.log(`\n# Run this to activate ${email} in this terminal:`);
|
|
316
|
+
console.log(`# eval "$(atris use ${targetName})"`);
|
|
317
|
+
console.log(`# Or just copy the export line above.`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function accountsCmd() {
|
|
322
|
+
const args = process.argv.slice(3);
|
|
323
|
+
const subCmd = args[0];
|
|
324
|
+
|
|
325
|
+
if (subCmd === 'add' || subCmd === 'login') {
|
|
326
|
+
return loginAtris({ force: true });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (subCmd === 'remove' || subCmd === 'rm') {
|
|
330
|
+
const target = args[1];
|
|
331
|
+
if (target === '--all') {
|
|
332
|
+
const profiles = listProfiles();
|
|
333
|
+
if (profiles.length === 0) {
|
|
334
|
+
console.log('No accounts to remove.');
|
|
335
|
+
process.exit(0);
|
|
336
|
+
}
|
|
337
|
+
const confirm = await promptUser(`Remove all ${profiles.length} accounts? (y/N): `);
|
|
338
|
+
if (confirm.toLowerCase() !== 'y') {
|
|
339
|
+
console.log('Cancelled.');
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
profiles.forEach(p => deleteProfile(p));
|
|
343
|
+
deleteCredentials();
|
|
344
|
+
console.log(`β Removed ${profiles.length} account(s).`);
|
|
345
|
+
process.exit(0);
|
|
346
|
+
}
|
|
347
|
+
if (!target) {
|
|
348
|
+
console.log('Usage: atris accounts remove <name>');
|
|
349
|
+
console.log(' atris accounts remove --all');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
// Fuzzy match
|
|
353
|
+
const profiles = listProfiles();
|
|
354
|
+
const q = target.toLowerCase();
|
|
355
|
+
const match = profiles.find(p => p.toLowerCase() === q)
|
|
356
|
+
|| profiles.find(p => p.toLowerCase().startsWith(q))
|
|
357
|
+
|| profiles.find(p => p.toLowerCase().includes(q));
|
|
358
|
+
if (!match) {
|
|
359
|
+
console.error(`No account matching "${target}".`);
|
|
360
|
+
console.log(`Available: ${profiles.join(', ')}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
const profile = loadProfile(match);
|
|
364
|
+
const email = profile?.email || 'unknown';
|
|
365
|
+
const confirm = await promptUser(`Remove ${match} (${email})? (y/N): `);
|
|
366
|
+
if (confirm.toLowerCase() !== 'y') {
|
|
367
|
+
console.log('Cancelled.');
|
|
368
|
+
process.exit(0);
|
|
369
|
+
}
|
|
370
|
+
deleteProfile(match);
|
|
371
|
+
// If this was the active account, clear credentials
|
|
372
|
+
const current = loadCredentials();
|
|
373
|
+
if (current && profileNameFromEmail(current.email) === match) {
|
|
374
|
+
deleteCredentials();
|
|
375
|
+
const remaining = listProfiles();
|
|
376
|
+
if (remaining.length > 0) {
|
|
377
|
+
console.log(`β Removed ${email}. No active account.`);
|
|
378
|
+
console.log(`Switch to another: atris switch ${remaining[0]}`);
|
|
379
|
+
} else {
|
|
380
|
+
console.log(`β Removed ${email}. No accounts remaining.`);
|
|
381
|
+
console.log('Run "atris login" to add one.');
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
console.log(`β Removed ${email}.`);
|
|
385
|
+
}
|
|
386
|
+
process.exit(0);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Default: list accounts
|
|
390
|
+
return listAccountsCmd();
|
|
226
391
|
}
|
|
227
392
|
|
|
228
393
|
function listAccountsCmd() {
|
|
229
394
|
const profiles = listProfiles();
|
|
230
395
|
if (profiles.length === 0) {
|
|
231
|
-
console.log('No saved
|
|
396
|
+
console.log('No accounts saved. Run "atris login" to add one.');
|
|
232
397
|
process.exit(0);
|
|
233
398
|
}
|
|
234
399
|
|
|
235
400
|
const current = loadCredentials();
|
|
236
|
-
const
|
|
401
|
+
const currentUid = current?.user_id;
|
|
402
|
+
// Also check which profile name is active (env var or session)
|
|
403
|
+
const envProfile = process.env.ATRIS_PROFILE;
|
|
404
|
+
const sessionProfile = getSessionProfile();
|
|
237
405
|
|
|
238
|
-
console.log('Accounts
|
|
406
|
+
console.log('\n Accounts\n');
|
|
239
407
|
profiles.forEach(name => {
|
|
240
408
|
const profile = loadProfile(name);
|
|
241
409
|
const email = profile?.email || 'unknown';
|
|
242
|
-
const
|
|
243
|
-
|
|
410
|
+
const isActive = profile?.user_id === currentUid;
|
|
411
|
+
if (isActive) {
|
|
412
|
+
console.log(` β ${name} ${email}`);
|
|
413
|
+
} else {
|
|
414
|
+
console.log(` ${name} ${email}`);
|
|
415
|
+
}
|
|
244
416
|
});
|
|
245
|
-
console.log(
|
|
246
|
-
console.log(
|
|
417
|
+
console.log(`\n Switch: atris switch <name>`);
|
|
418
|
+
console.log(` Add: atris accounts add`);
|
|
419
|
+
console.log(` Remove: atris accounts remove <name>\n`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function resolveProfile() {
|
|
423
|
+
// Hidden command: atris _resolve <query> β prints resolved profile name
|
|
424
|
+
const query = process.argv[3];
|
|
425
|
+
if (!query) { process.exit(1); }
|
|
426
|
+
const profiles = listProfiles();
|
|
427
|
+
const q = query.toLowerCase();
|
|
428
|
+
const match = profiles.find(p => p.toLowerCase() === q)
|
|
429
|
+
|| profiles.find(p => p.toLowerCase().startsWith(q))
|
|
430
|
+
|| profiles.find(p => p.toLowerCase().includes(q))
|
|
431
|
+
|| profiles.find(p => {
|
|
432
|
+
const profile = loadProfile(p);
|
|
433
|
+
return profile?.email?.toLowerCase().includes(q);
|
|
434
|
+
});
|
|
435
|
+
if (match) {
|
|
436
|
+
process.stdout.write(match);
|
|
437
|
+
process.exit(0);
|
|
438
|
+
}
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function profileEmail() {
|
|
443
|
+
// Hidden command: atris _profile-email <name> β prints email
|
|
444
|
+
const name = process.argv[3];
|
|
445
|
+
if (!name) { process.exit(1); }
|
|
446
|
+
const profile = loadProfile(name);
|
|
447
|
+
if (profile?.email) {
|
|
448
|
+
process.stdout.write(profile.email);
|
|
449
|
+
process.exit(0);
|
|
450
|
+
}
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function shellInit() {
|
|
455
|
+
// Output shell function for per-terminal account switching
|
|
456
|
+
// Usage: eval "$(atris shell-init)" (add to ~/.zshrc)
|
|
457
|
+
// Use array join to avoid JS template literal parsing issues with ${}
|
|
458
|
+
const lines = [
|
|
459
|
+
'# Atris per-terminal account switching',
|
|
460
|
+
'# Added by: eval "$(atris shell-init)"',
|
|
461
|
+
'atris() {',
|
|
462
|
+
' if [[ "$1" == "switch" && -n "$2" && "$2" != "--"* ]]; then',
|
|
463
|
+
' local _profile',
|
|
464
|
+
' _profile=$(command atris _resolve "$2" 2>/dev/null)',
|
|
465
|
+
' if [[ $? -eq 0 && -n "$_profile" ]]; then',
|
|
466
|
+
' export ATRIS_PROFILE="$_profile"',
|
|
467
|
+
' local _email',
|
|
468
|
+
' _email=$(command atris _profile-email "$_profile" 2>/dev/null)',
|
|
469
|
+
' echo "Switched to ${_email:-$_profile}"',
|
|
470
|
+
' else',
|
|
471
|
+
' echo "No account matching \'$2\'."',
|
|
472
|
+
' command atris accounts',
|
|
473
|
+
' fi',
|
|
474
|
+
' elif [[ "$1" == "switch" && $# -eq 1 ]]; then',
|
|
475
|
+
' command atris accounts',
|
|
476
|
+
' echo ""',
|
|
477
|
+
' printf "Switch to: "',
|
|
478
|
+
' read _pick',
|
|
479
|
+
' if [[ -n "$_pick" ]]; then',
|
|
480
|
+
' local _profile',
|
|
481
|
+
' _profile=$(command atris _resolve "$_pick" 2>/dev/null)',
|
|
482
|
+
' if [[ $? -eq 0 && -n "$_profile" ]]; then',
|
|
483
|
+
' export ATRIS_PROFILE="$_profile"',
|
|
484
|
+
' local _email',
|
|
485
|
+
' _email=$(command atris _profile-email "$_profile" 2>/dev/null)',
|
|
486
|
+
' echo "Switched to ${_email:-$_profile}"',
|
|
487
|
+
' else',
|
|
488
|
+
' echo "No account matching \'$_pick\'."',
|
|
489
|
+
' fi',
|
|
490
|
+
' fi',
|
|
491
|
+
' else',
|
|
492
|
+
' command atris "$@"',
|
|
493
|
+
' fi',
|
|
494
|
+
'}',
|
|
495
|
+
];
|
|
496
|
+
console.log(lines.join('\n'));
|
|
247
497
|
}
|
|
248
498
|
|
|
249
|
-
module.exports = { loginAtris, logoutAtris, whoamiAtris, switchAccount, listAccountsCmd };
|
|
499
|
+
module.exports = { loginAtris, logoutAtris, whoamiAtris, switchAccount, useAccount, accountsCmd, listAccountsCmd, resolveProfile, profileEmail, shellInit };
|