atris 3.52.0 → 3.55.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/FOR_AGENTS.md +7 -0
- package/README.md +1 -1
- package/atris/policies/ANTISLOP.md +2 -1
- package/atris/skills/copy-editor/SKILL.md +2 -1
- package/atris/skills/design/SKILL.md +1 -1
- package/atris/skills/engines/SKILL.md +8 -7
- package/atris/skills/x-search/SKILL.md +24 -22
- package/atris/skills/youtube/SKILL.md +89 -40
- package/atris/team/_template/SOUL.md +40 -0
- package/atris/team/validator/MEMBER.md +1 -2
- package/ax +592 -91
- package/bin/atris +21 -0
- package/bin/atris.js +580 -378
- package/commands/api-key.js +170 -0
- package/commands/auth.js +453 -43
- package/commands/autoland.js +3 -1
- package/commands/autopilot-front.js +58 -11
- package/commands/avail.js +5 -4
- package/commands/balance.js +55 -0
- package/commands/bench.js +29 -1
- package/commands/brain.js +63 -12
- package/commands/brainstorm.js +18 -0
- package/commands/business.js +149 -1
- package/commands/chat-scan.js +17 -4
- package/commands/close.js +5 -1
- package/commands/computer.js +2 -2
- package/commands/doctor.js +174 -0
- package/commands/dream.js +7 -0
- package/commands/drill.js +34 -2
- package/commands/engine.js +47 -35
- package/commands/errors.js +58 -8
- package/commands/experiments.js +14 -0
- package/commands/feedback.js +61 -2
- package/commands/fleet-report.js +27 -7
- package/commands/fleet.js +98 -11
- package/commands/founder.js +60 -21
- package/commands/human-missions.js +79 -4
- package/commands/improve.js +54 -4
- package/commands/init.js +68 -92
- package/commands/install.js +40 -0
- package/commands/integrations.js +399 -30
- package/commands/interview.js +8 -0
- package/commands/land.js +48 -17
- package/commands/learn.js +29 -4
- package/commands/log.js +51 -0
- package/commands/member.js +68 -10
- package/commands/mission.js +339 -74
- package/commands/now.js +25 -6
- package/commands/orb.js +97 -2
- package/commands/pack.js +83 -9
- package/commands/playbook.js +381 -0
- package/commands/plugin.js +16 -0
- package/commands/radar.js +96 -40
- package/commands/recap.js +128 -21
- package/commands/release.js +102 -2
- package/commands/review.js +5 -10
- package/commands/revisions.js +55 -0
- package/commands/run-front.js +12 -4
- package/commands/scout.js +12 -1
- package/commands/search.js +123 -28
- package/commands/sign.js +2 -2
- package/commands/signup.js +12 -3
- package/commands/site-deploy.js +17 -4
- package/commands/site.js +2 -2
- package/commands/skill-eval.js +289 -0
- package/commands/skill.js +56 -6
- package/commands/social.js +603 -0
- package/commands/spaceship.js +69 -6
- package/commands/stream.js +8 -1
- package/commands/study.js +32 -19
- package/commands/sync.js +28 -22
- package/commands/task.js +418 -60
- package/commands/teach.js +72 -63
- package/commands/terminal.js +25 -9
- package/commands/topup.js +88 -0
- package/commands/truth.js +52 -6
- package/commands/usage.js +91 -0
- package/commands/verify.js +3 -2
- package/commands/version.js +3 -1
- package/commands/voice.js +5 -1
- package/commands/who.js +69 -11
- package/commands/wiki.js +7 -4
- package/commands/wish.js +76 -5
- package/commands/workflow.js +327 -145
- package/commands/worktree.js +1 -0
- package/commands/write.js +5 -0
- package/commands/x-search.js +340 -0
- package/commands/xp.js +5 -1
- package/commands/youtube.js +551 -33
- package/lib/account-bound.js +47 -0
- package/lib/auto-accept-certified.js +10 -2
- package/lib/auto-lessons.js +186 -0
- package/lib/autoland.js +4 -2
- package/lib/chat-log-scan.js +7 -4
- package/lib/cli-json.js +77 -0
- package/lib/cli-scope.js +26 -0
- package/lib/codex-flight.js +1 -0
- package/lib/conductor-artifacts.js +1 -1
- package/lib/context-gatherer.js +11 -0
- package/lib/developer-api.js +116 -0
- package/lib/engine-ask.js +11 -3
- package/lib/engine-registry.js +32 -7
- package/lib/feedback-triage.js +274 -0
- package/lib/first-minute.js +398 -0
- package/lib/fleet.js +115 -34
- package/lib/functional-owner.js +22 -0
- package/lib/known-commands.js +31 -5
- package/lib/member-scaffold.js +197 -0
- package/lib/mission-ledger-compact.js +127 -0
- package/lib/mission-root.js +4 -2
- package/lib/mission-runtime-loop.js +30 -2
- package/lib/next-moves.js +34 -34
- package/lib/noninteractive.js +88 -0
- package/lib/permission-grants.js +10 -1
- package/lib/policy-lessons.js +4 -1
- package/lib/revision-metric.js +279 -0
- package/lib/runner-command.js +5 -4
- package/lib/scratch-root.js +48 -0
- package/lib/skill-eval-gate.js +249 -0
- package/lib/task-db.js +22 -2
- package/lib/task-proof.js +75 -9
- package/lib/task-receipt.js +5 -1
- package/lib/verifier-quality.js +69 -0
- package/lib/wish-audit.js +2 -2
- package/lib/wish-delegate.js +10 -1
- package/lib/workspace-scaffold.js +270 -0
- package/package.json +4 -2
- package/scripts/det/checklist-score.js +191 -0
- package/scripts/det/ytsearch +31 -0
- package/scripts/outbound-artifact-gate.js +227 -0
- package/utils/update-check.js +15 -0
package/commands/integrations.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
* Integration commands for Atris CLI
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* atris gmail inbox - List recent emails
|
|
6
|
-
* atris gmail read <id> - Read specific email
|
|
5
|
+
* atris gmail inbox [--account <id>] - List recent emails for a mailbox
|
|
6
|
+
* atris gmail read <id> [--account <id>] - Read specific email
|
|
7
|
+
* atris gmail archive <id> [...] [--account <id>] - Archive messages
|
|
8
|
+
* atris gmail connect [name] - Connect or reconnect a Gmail account
|
|
9
|
+
* atris gmail accounts - List connected Gmail accounts and the active account
|
|
10
|
+
* atris gmail use [<account_id|name>] - Choose or set the active Gmail account
|
|
7
11
|
* atris calendar today - Show today's events
|
|
8
12
|
* atris calendar yesterday - Show yesterday's events
|
|
9
13
|
* atris calendar week - Show this week's events
|
|
@@ -25,9 +29,114 @@ const {
|
|
|
25
29
|
const fs = require('fs');
|
|
26
30
|
const os = require('os');
|
|
27
31
|
const path = require('path');
|
|
32
|
+
const readline = require('readline');
|
|
28
33
|
const { spawnSync } = require('child_process');
|
|
29
34
|
|
|
30
35
|
const CALENDAR_CACHE_PATH = path.join(os.homedir(), '.atris', 'calendar-events-cache.json');
|
|
36
|
+
const GMAIL_CONNECT_POLL_MS = 3000;
|
|
37
|
+
const GMAIL_CONNECT_TIMEOUT_MS = 3 * 60 * 1000;
|
|
38
|
+
|
|
39
|
+
function gmailAccountStatePath() {
|
|
40
|
+
return process.env.ATRIS_GMAIL_ACCOUNT_FILE
|
|
41
|
+
|| path.join(os.homedir(), '.atris', 'gmail-account.json');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readGmailStickyAccount() {
|
|
45
|
+
try {
|
|
46
|
+
const data = JSON.parse(fs.readFileSync(gmailAccountStatePath(), 'utf8'));
|
|
47
|
+
const id = String(data.account_id || '').trim();
|
|
48
|
+
return id || null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function writeGmailStickyAccount(accountId) {
|
|
55
|
+
const filePath = gmailAccountStatePath();
|
|
56
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
57
|
+
fs.writeFileSync(filePath, `${JSON.stringify({ account_id: accountId }, null, 2)}\n`, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveGmailAccountId(explicitId) {
|
|
61
|
+
const trimmed = String(explicitId || '').trim();
|
|
62
|
+
if (trimmed) return trimmed;
|
|
63
|
+
const sticky = readGmailStickyAccount();
|
|
64
|
+
if (sticky) return sticky;
|
|
65
|
+
return 'default';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function gmailAccountIdentity(account = {}) {
|
|
69
|
+
const id = String(account.account_id || account.id || 'default').trim() || 'default';
|
|
70
|
+
const email = String(account.email_address || account.email || '').trim().toLowerCase();
|
|
71
|
+
const name = String(account.display_name || account.name || id).trim().toLowerCase() || id.toLowerCase();
|
|
72
|
+
return { id, email, name };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sortGmailAccounts(accounts) {
|
|
76
|
+
return [...accounts].sort((left, right) => {
|
|
77
|
+
const leftIdentity = gmailAccountIdentity(left);
|
|
78
|
+
const rightIdentity = gmailAccountIdentity(right);
|
|
79
|
+
const leftDefault = leftIdentity.id.toLowerCase() === 'default';
|
|
80
|
+
const rightDefault = rightIdentity.id.toLowerCase() === 'default';
|
|
81
|
+
if (leftDefault !== rightDefault) return leftDefault ? -1 : 1;
|
|
82
|
+
return leftIdentity.name.localeCompare(rightIdentity.name)
|
|
83
|
+
|| leftIdentity.id.localeCompare(rightIdentity.id);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatGmailAccountRows(accounts, activeId = null) {
|
|
88
|
+
const rows = sortGmailAccounts(accounts).map((account) => {
|
|
89
|
+
const identity = gmailAccountIdentity(account);
|
|
90
|
+
return { ...identity, emailLabel: identity.email || 'email pending' };
|
|
91
|
+
});
|
|
92
|
+
const nameWidth = Math.max(...rows.map((row) => row.name.length));
|
|
93
|
+
const emailWidth = Math.max(...rows.map((row) => row.emailLabel.length));
|
|
94
|
+
const active = String(activeId || '').toLowerCase();
|
|
95
|
+
return rows.map((row) => {
|
|
96
|
+
const marker = row.id.toLowerCase() === active ? ' (active)' : '';
|
|
97
|
+
return `${row.name.padEnd(nameWidth)} ${row.emailLabel.padEnd(emailWidth)}${marker}`.trimEnd();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function fetchGmailAccounts(token, options = {}) {
|
|
102
|
+
const result = await apiRequestJson('/integrations/gmail/accounts', {
|
|
103
|
+
method: 'GET',
|
|
104
|
+
token,
|
|
105
|
+
});
|
|
106
|
+
if (!result.ok) {
|
|
107
|
+
if (options.quiet) return [];
|
|
108
|
+
console.error(`Error: ${result.error || 'Failed to fetch accounts'}`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const accounts = result.data?.accounts || result.data || [];
|
|
112
|
+
return Array.isArray(accounts) ? accounts : [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function findGmailAccount(accounts, accountId) {
|
|
116
|
+
const wanted = String(accountId || '').trim().toLowerCase();
|
|
117
|
+
return accounts.find((account) => gmailAccountIdentity(account).id.toLowerCase() === wanted) || null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function findGmailAccountForUse(accounts, selector) {
|
|
121
|
+
const wanted = String(selector || '').trim().toLowerCase();
|
|
122
|
+
return findGmailAccount(accounts, wanted)
|
|
123
|
+
|| accounts.find((account) => gmailAccountIdentity(account).name === wanted)
|
|
124
|
+
|| null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function promptForGmailAccount(deps = {}) {
|
|
128
|
+
if (deps.prompt) return deps.prompt('choose account: ');
|
|
129
|
+
const rl = readline.createInterface({
|
|
130
|
+
input: deps.input || process.stdin,
|
|
131
|
+
output: deps.output || process.stdout,
|
|
132
|
+
});
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
rl.question('choose account: ', (answer) => {
|
|
135
|
+
rl.close();
|
|
136
|
+
resolve(answer);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
31
140
|
|
|
32
141
|
async function getAuth() {
|
|
33
142
|
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
@@ -51,13 +160,54 @@ async function getAuthToken() {
|
|
|
51
160
|
// GMAIL
|
|
52
161
|
// ============================================================================
|
|
53
162
|
|
|
163
|
+
function extractGmailMailboxAccount(args = []) {
|
|
164
|
+
const out = [];
|
|
165
|
+
let mailboxAccountId = null;
|
|
166
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
167
|
+
const arg = args[i];
|
|
168
|
+
if (arg === '--account' && args[i + 1] && !String(args[i + 1]).startsWith('-')) {
|
|
169
|
+
mailboxAccountId = args[i + 1];
|
|
170
|
+
i += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
out.push(arg);
|
|
174
|
+
}
|
|
175
|
+
return { args: out, mailboxAccountId };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseGmailArgs(args = []) {
|
|
179
|
+
const options = { accountId: null };
|
|
180
|
+
const positional = [];
|
|
181
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
182
|
+
const arg = args[i];
|
|
183
|
+
if (arg === '--account') {
|
|
184
|
+
options.accountId = String(args[i + 1] || '').trim();
|
|
185
|
+
if (!options.accountId) {
|
|
186
|
+
console.error('Usage: --account requires an account id');
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
i += 1;
|
|
190
|
+
} else {
|
|
191
|
+
positional.push(arg);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { ...options, positional };
|
|
195
|
+
}
|
|
196
|
+
|
|
54
197
|
async function gmailInbox(options = {}) {
|
|
55
198
|
const { token, email } = await getAuth();
|
|
56
199
|
const limit = options.limit || 10;
|
|
200
|
+
const accountId = resolveGmailAccountId(options.accountId);
|
|
201
|
+
const accounts = await fetchGmailAccounts(token, { quiet: true });
|
|
202
|
+
const account = findGmailAccount(accounts, accountId);
|
|
203
|
+
const mailboxEmail = account ? gmailAccountIdentity(account).email : '';
|
|
57
204
|
|
|
205
|
+
if (mailboxEmail) console.log(`inbox for ${mailboxEmail} (${accountId.toLowerCase()})`);
|
|
58
206
|
console.log('📬 Fetching inbox...\n');
|
|
59
207
|
|
|
60
|
-
const
|
|
208
|
+
const path = `/integrations/gmail/messages?max_results=${limit}&account_id=${encodeURIComponent(accountId)}`;
|
|
209
|
+
|
|
210
|
+
const result = await apiRequestJson(path, {
|
|
61
211
|
method: 'GET',
|
|
62
212
|
token,
|
|
63
213
|
});
|
|
@@ -97,17 +247,20 @@ async function gmailInbox(options = {}) {
|
|
|
97
247
|
}
|
|
98
248
|
}
|
|
99
249
|
|
|
100
|
-
async function gmailRead(messageId) {
|
|
250
|
+
async function gmailRead(messageId, options = {}) {
|
|
101
251
|
if (!messageId) {
|
|
102
|
-
console.error('Usage: atris gmail read <message_id>');
|
|
252
|
+
console.error('Usage: atris gmail read <message_id> [--account <id>]');
|
|
103
253
|
process.exit(1);
|
|
104
254
|
}
|
|
105
255
|
|
|
106
256
|
const token = await getAuthToken();
|
|
257
|
+
const accountId = resolveGmailAccountId(options.accountId);
|
|
107
258
|
|
|
108
259
|
console.log('📧 Fetching message...\n');
|
|
109
260
|
|
|
110
|
-
const
|
|
261
|
+
const path = `/integrations/gmail/messages/${messageId}?account_id=${encodeURIComponent(accountId)}`;
|
|
262
|
+
|
|
263
|
+
const result = await apiRequestJson(path, {
|
|
111
264
|
method: 'GET',
|
|
112
265
|
token,
|
|
113
266
|
});
|
|
@@ -129,21 +282,24 @@ async function gmailRead(messageId) {
|
|
|
129
282
|
console.log(msg.body || msg.snippet || '(no body)');
|
|
130
283
|
}
|
|
131
284
|
|
|
132
|
-
async function gmailArchive(messageIds) {
|
|
285
|
+
async function gmailArchive(messageIds, options = {}) {
|
|
133
286
|
const ids = (messageIds || []).map(id => String(id || '').trim()).filter(Boolean);
|
|
134
287
|
if (!ids.length) {
|
|
135
|
-
console.error('Usage: atris gmail archive <message_id> [<message_id> ...]');
|
|
288
|
+
console.error('Usage: atris gmail archive <message_id> [<message_id> ...] [--account <id>]');
|
|
136
289
|
process.exit(1);
|
|
137
290
|
}
|
|
138
291
|
|
|
139
292
|
const token = await getAuthToken();
|
|
293
|
+
const accountId = resolveGmailAccountId(options.accountId);
|
|
140
294
|
|
|
141
295
|
console.log(`Archiving ${ids.length} message${ids.length === 1 ? '' : 's'}...`);
|
|
142
296
|
|
|
297
|
+
const body = { message_ids: ids, account_id: accountId };
|
|
298
|
+
|
|
143
299
|
const result = await apiRequestJson('/integrations/gmail/messages/batch-archive', {
|
|
144
300
|
method: 'POST',
|
|
145
301
|
token,
|
|
146
|
-
body
|
|
302
|
+
body,
|
|
147
303
|
});
|
|
148
304
|
|
|
149
305
|
if (!result.ok) {
|
|
@@ -155,23 +311,169 @@ async function gmailArchive(messageIds) {
|
|
|
155
311
|
console.log(`Archived ${archived} message${archived === 1 ? '' : 's'}. They stay searchable in All Mail.`);
|
|
156
312
|
}
|
|
157
313
|
|
|
314
|
+
function openGmailAuthUrl(authUrl, deps = {}) {
|
|
315
|
+
const run = deps.spawnSync || spawnSync;
|
|
316
|
+
const platform = deps.platform || process.platform;
|
|
317
|
+
const command = platform === 'darwin' ? 'open' : 'xdg-open';
|
|
318
|
+
const opened = run(command, [authUrl], { stdio: 'ignore' });
|
|
319
|
+
return !opened.error && opened.status === 0;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function gmailConnect(accountName, deps = {}) {
|
|
323
|
+
const accountId = String(accountName || 'default').trim();
|
|
324
|
+
if (!/^[a-z0-9-]{1,32}$/.test(accountId)) {
|
|
325
|
+
console.error('invalid gmail account name. use 1 to 32 lowercase letters, numbers, or hyphens.');
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const token = await getAuthToken();
|
|
330
|
+
const result = await apiRequestJson('/integrations/gmail/start', {
|
|
331
|
+
method: 'POST',
|
|
332
|
+
token,
|
|
333
|
+
body: { account_id: accountId },
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (!result.ok) {
|
|
337
|
+
const detail = String(result.error || 'request failed').toLowerCase();
|
|
338
|
+
console.error(`could not start gmail connection: ${detail}`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const authUrl = String(result.data?.auth_url || result.data?.url || '').trim();
|
|
343
|
+
if (!authUrl) {
|
|
344
|
+
console.error('could not start gmail connection: the server did not return an auth url.');
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const openAuthUrl = deps.openAuthUrl || openGmailAuthUrl;
|
|
349
|
+
if (!openAuthUrl(authUrl, deps)) {
|
|
350
|
+
console.log(`open this url in your browser: ${authUrl}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
console.log(`waiting for gmail account ${accountId}...`);
|
|
354
|
+
|
|
355
|
+
const pollIntervalMs = deps.pollIntervalMs ?? GMAIL_CONNECT_POLL_MS;
|
|
356
|
+
const timeoutMs = deps.timeoutMs ?? GMAIL_CONNECT_TIMEOUT_MS;
|
|
357
|
+
const sleep = deps.sleep || ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
358
|
+
const maxPolls = Math.max(1, Math.ceil(timeoutMs / pollIntervalMs));
|
|
359
|
+
|
|
360
|
+
for (let poll = 0; poll < maxPolls; poll += 1) {
|
|
361
|
+
await sleep(pollIntervalMs);
|
|
362
|
+
const accountsResult = await apiRequestJson('/integrations/gmail/accounts', {
|
|
363
|
+
method: 'GET',
|
|
364
|
+
token,
|
|
365
|
+
});
|
|
366
|
+
const accounts = accountsResult.ok
|
|
367
|
+
? (accountsResult.data?.accounts || accountsResult.data || [])
|
|
368
|
+
: [];
|
|
369
|
+
const match = Array.isArray(accounts) ? findGmailAccount(accounts, accountId) : null;
|
|
370
|
+
const email = match ? gmailAccountIdentity(match).email.trim() : '';
|
|
371
|
+
if (email && email !== 'unknown') {
|
|
372
|
+
console.log(`connected ${email} as ${accountId}`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
console.error(`connection timed out. finish in your browser: ${authUrl}`);
|
|
378
|
+
console.error(`or connect from settings: https://atris.ai/dashboard/settings?connect=gmail&account=${accountId}`);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function gmailAccounts() {
|
|
383
|
+
const token = await getAuthToken();
|
|
384
|
+
const accounts = await fetchGmailAccounts(token);
|
|
385
|
+
const activeId = resolveGmailAccountId(null);
|
|
386
|
+
|
|
387
|
+
if (!accounts.length) {
|
|
388
|
+
console.log('no connected accounts.');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
for (const line of formatGmailAccountRows(accounts, activeId)) console.log(line);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function gmailUse(accountId, deps = {}) {
|
|
396
|
+
const token = await getAuthToken();
|
|
397
|
+
const accounts = await fetchGmailAccounts(token);
|
|
398
|
+
let selector = String(accountId || '').trim();
|
|
399
|
+
|
|
400
|
+
if (!selector) {
|
|
401
|
+
if (!accounts.length) {
|
|
402
|
+
console.log('no connected accounts.');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (accounts.length === 1) {
|
|
406
|
+
const { name, email } = gmailAccountIdentity(accounts[0]);
|
|
407
|
+
console.log(`${name} (${email || 'email pending'}) is already the only gmail account.`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const sortedAccounts = sortGmailAccounts(accounts);
|
|
412
|
+
const activeId = resolveGmailAccountId(null);
|
|
413
|
+
const rows = formatGmailAccountRows(sortedAccounts, activeId);
|
|
414
|
+
rows.forEach((line, index) => console.log(`${index + 1}. ${line}`));
|
|
415
|
+
const answer = String(await promptForGmailAccount(deps) || '').trim();
|
|
416
|
+
const number = /^\d+$/.test(answer) ? Number(answer) : 0;
|
|
417
|
+
selector = number >= 1 && number <= sortedAccounts.length
|
|
418
|
+
? gmailAccountIdentity(sortedAccounts[number - 1]).id
|
|
419
|
+
: answer;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const match = findGmailAccountForUse(accounts, selector);
|
|
423
|
+
if (!match) {
|
|
424
|
+
console.error(`unknown gmail account "${selector}".`);
|
|
425
|
+
if (accounts.length) {
|
|
426
|
+
console.error('connected accounts:');
|
|
427
|
+
for (const account of sortGmailAccounts(accounts)) {
|
|
428
|
+
const { id, email, name } = gmailAccountIdentity(account);
|
|
429
|
+
console.error(` ${id.toLowerCase()}: ${email || 'email pending'}, ${name}`);
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
console.error('no connected accounts.');
|
|
433
|
+
}
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const { id, name, email } = gmailAccountIdentity(match);
|
|
438
|
+
writeGmailStickyAccount(id);
|
|
439
|
+
console.log(`now using ${name} (${email || 'email pending'})`);
|
|
440
|
+
}
|
|
441
|
+
|
|
158
442
|
async function gmailCommand(subcommand, ...args) {
|
|
159
443
|
switch (subcommand) {
|
|
160
444
|
case 'inbox':
|
|
161
|
-
case 'list':
|
|
162
|
-
|
|
445
|
+
case 'list': {
|
|
446
|
+
const parsed = parseGmailArgs(args);
|
|
447
|
+
await gmailInbox({ accountId: parsed.accountId || undefined });
|
|
163
448
|
break;
|
|
164
|
-
|
|
165
|
-
|
|
449
|
+
}
|
|
450
|
+
case 'read': {
|
|
451
|
+
const parsed = parseGmailArgs(args);
|
|
452
|
+
await gmailRead(parsed.positional[0], { accountId: parsed.accountId || undefined });
|
|
166
453
|
break;
|
|
167
|
-
|
|
168
|
-
|
|
454
|
+
}
|
|
455
|
+
case 'archive': {
|
|
456
|
+
const parsed = parseGmailArgs(args);
|
|
457
|
+
await gmailArchive(parsed.positional, { accountId: parsed.accountId || undefined });
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
case 'connect':
|
|
461
|
+
await gmailConnect(args[0]);
|
|
462
|
+
break;
|
|
463
|
+
case 'accounts':
|
|
464
|
+
await gmailAccounts();
|
|
465
|
+
break;
|
|
466
|
+
case 'use':
|
|
467
|
+
await gmailUse(args.join(' '));
|
|
169
468
|
break;
|
|
170
469
|
default:
|
|
171
|
-
console.log('
|
|
172
|
-
console.log(' atris gmail inbox -
|
|
173
|
-
console.log(' atris gmail read <id> -
|
|
174
|
-
console.log(' atris gmail archive <id> [...] -
|
|
470
|
+
console.log('gmail commands:');
|
|
471
|
+
console.log(' atris gmail inbox [--account <id>] - list recent emails for a mailbox');
|
|
472
|
+
console.log(' atris gmail read <id> [--account <id>] - read specific email');
|
|
473
|
+
console.log(' atris gmail archive <id> [...] [--account <id>] - archive messages (reversible, all mail keeps them)');
|
|
474
|
+
console.log(' atris gmail connect [name] - connect or reconnect a gmail account');
|
|
475
|
+
console.log(' atris gmail accounts - list connected gmail accounts and the active account');
|
|
476
|
+
console.log(' atris gmail use [<account_id|name>] - choose or set the active gmail account');
|
|
175
477
|
}
|
|
176
478
|
}
|
|
177
479
|
|
|
@@ -228,7 +530,8 @@ function formatCalendarTimeRange(event) {
|
|
|
228
530
|
|
|
229
531
|
function formatCalendarEvents(label, events) {
|
|
230
532
|
if (events.length === 0) {
|
|
231
|
-
|
|
533
|
+
// label is already a phrase like "today's events" or "events on 2026-08-24"
|
|
534
|
+
console.log(`No ${label}.`);
|
|
232
535
|
return;
|
|
233
536
|
}
|
|
234
537
|
|
|
@@ -599,7 +902,15 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
599
902
|
// ============================================================================
|
|
600
903
|
|
|
601
904
|
async function twitterPost(args = []) {
|
|
602
|
-
const
|
|
905
|
+
const approved = args.includes('--approved') || args.includes('--confirm-approved');
|
|
906
|
+
const cleaned = args.filter((a) => a !== '--approved' && a !== '--confirm-approved');
|
|
907
|
+
if (!approved) {
|
|
908
|
+
console.error('Refusing to post without --approved after the exact text is confirmed.');
|
|
909
|
+
console.error('Usage: atris twitter post "<text>" --approved');
|
|
910
|
+
process.exit(2);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const outbound = prepareOutboundSendTextOrExit('twitter', cleaned);
|
|
603
914
|
let text = outbound.body;
|
|
604
915
|
if (!text) {
|
|
605
916
|
const readline = require('readline');
|
|
@@ -658,7 +969,7 @@ async function twitterCommand(subcommand, ...args) {
|
|
|
658
969
|
break;
|
|
659
970
|
default:
|
|
660
971
|
console.log('Twitter commands:');
|
|
661
|
-
console.log(' atris twitter post
|
|
972
|
+
console.log(' atris twitter post "<text>" --approved - Post a tweet');
|
|
662
973
|
}
|
|
663
974
|
}
|
|
664
975
|
|
|
@@ -834,9 +1145,16 @@ async function slackSearch(query, args = []) {
|
|
|
834
1145
|
|
|
835
1146
|
async function slackSend(channel, textParts) {
|
|
836
1147
|
const target = String(channel || '').trim();
|
|
837
|
-
const
|
|
1148
|
+
const approved = (textParts || []).includes('--approved') || (textParts || []).includes('--confirm-approved');
|
|
1149
|
+
const cleaned = (textParts || []).filter((a) => a !== '--approved' && a !== '--confirm-approved');
|
|
1150
|
+
if (!approved) {
|
|
1151
|
+
console.error('Refusing to send without --approved after the exact channel and exact text are confirmed.');
|
|
1152
|
+
console.error('Usage: atris slack send <channel> "<message>" --approved');
|
|
1153
|
+
process.exit(2);
|
|
1154
|
+
}
|
|
1155
|
+
const text = prepareOutboundSendTextOrExit('slack', cleaned).body;
|
|
838
1156
|
if (!target || !text) {
|
|
839
|
-
console.error('Usage: atris slack send <channel> "<message>"');
|
|
1157
|
+
console.error('Usage: atris slack send <channel> "<message>" --approved');
|
|
840
1158
|
process.exit(1);
|
|
841
1159
|
}
|
|
842
1160
|
|
|
@@ -860,9 +1178,16 @@ async function slackSend(channel, textParts) {
|
|
|
860
1178
|
|
|
861
1179
|
async function slackDm(userId, textParts) {
|
|
862
1180
|
const target = String(userId || '').trim();
|
|
863
|
-
const
|
|
1181
|
+
const approved = (textParts || []).includes('--approved') || (textParts || []).includes('--confirm-approved');
|
|
1182
|
+
const cleaned = (textParts || []).filter((a) => a !== '--approved' && a !== '--confirm-approved');
|
|
1183
|
+
if (!approved) {
|
|
1184
|
+
console.error('Refusing to send without --approved after the exact recipient and exact text are confirmed.');
|
|
1185
|
+
console.error('Usage: atris slack dm <slack_user_id> "<message>" --approved');
|
|
1186
|
+
process.exit(2);
|
|
1187
|
+
}
|
|
1188
|
+
const text = prepareOutboundSendTextOrExit('slack', cleaned).body;
|
|
864
1189
|
if (!target || !text) {
|
|
865
|
-
console.error('Usage: atris slack dm <slack_user_id> "<message>"');
|
|
1190
|
+
console.error('Usage: atris slack dm <slack_user_id> "<message>" --approved');
|
|
866
1191
|
process.exit(1);
|
|
867
1192
|
}
|
|
868
1193
|
|
|
@@ -912,8 +1237,8 @@ async function slackCommand(subcommand, ...args) {
|
|
|
912
1237
|
console.log(' atris slack dms - List Slack DMs');
|
|
913
1238
|
console.log(' atris slack messages <channel> - Read recent messages');
|
|
914
1239
|
console.log(' atris slack search <query> - Search Slack messages');
|
|
915
|
-
console.log(' atris slack send <channel> "<message>" - Send a message as you');
|
|
916
|
-
console.log(' atris slack dm <user_id> "<message>" - DM someone as you');
|
|
1240
|
+
console.log(' atris slack send <channel> "<message>" --approved - Send a message as you');
|
|
1241
|
+
console.log(' atris slack dm <user_id> "<message>" --approved - DM someone as you');
|
|
917
1242
|
}
|
|
918
1243
|
}
|
|
919
1244
|
|
|
@@ -1588,10 +1913,41 @@ async function imessageCommand(subcommand, ...args) {
|
|
|
1588
1913
|
// STATUS
|
|
1589
1914
|
// ============================================================================
|
|
1590
1915
|
|
|
1591
|
-
async function integrationsStatus() {
|
|
1916
|
+
async function integrationsStatus(args = []) {
|
|
1917
|
+
const { parseScopeFlag } = require('../lib/cli-scope');
|
|
1918
|
+
const scope = parseScopeFlag(args);
|
|
1919
|
+
const json = scope.args.includes('--json') || args.includes('--json');
|
|
1920
|
+
|
|
1921
|
+
if (!scope.global) {
|
|
1922
|
+
const localPath = path.join(process.cwd(), '.atris', 'integrations.json');
|
|
1923
|
+
let local = null;
|
|
1924
|
+
try {
|
|
1925
|
+
if (fs.existsSync(localPath)) local = JSON.parse(fs.readFileSync(localPath, 'utf8'));
|
|
1926
|
+
} catch {}
|
|
1927
|
+
const payload = {
|
|
1928
|
+
scope: 'workspace',
|
|
1929
|
+
workspace_root: process.cwd(),
|
|
1930
|
+
integrations: Array.isArray(local?.integrations) ? local.integrations : [],
|
|
1931
|
+
note: 'workspace scope only. pass --global for account-level mail/chat/code hosts.',
|
|
1932
|
+
};
|
|
1933
|
+
if (json) {
|
|
1934
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1935
|
+
return 0;
|
|
1936
|
+
}
|
|
1937
|
+
console.log('Integration Status (workspace)\n');
|
|
1938
|
+
if (!payload.integrations.length) {
|
|
1939
|
+
console.log(' (none configured in this workspace)');
|
|
1940
|
+
} else {
|
|
1941
|
+
for (const name of payload.integrations) console.log(` • ${name}`);
|
|
1942
|
+
}
|
|
1943
|
+
console.log('\nPass --global to check account-level gmail/slack/github status.');
|
|
1944
|
+
return 0;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1592
1947
|
const token = await getAuthToken();
|
|
1948
|
+
const rows = [];
|
|
1593
1949
|
|
|
1594
|
-
console.log('🔌 Integration Status:\n');
|
|
1950
|
+
console.log('🔌 Integration Status (global):\n');
|
|
1595
1951
|
|
|
1596
1952
|
const integrations = ['gmail', 'google-calendar', 'slack', 'twitter', 'github'];
|
|
1597
1953
|
|
|
@@ -1606,19 +1962,31 @@ async function integrationsStatus() {
|
|
|
1606
1962
|
const icon = connected ? '✅' : '❌';
|
|
1607
1963
|
const displayName = name.replace('google-', '').replace('-', ' ');
|
|
1608
1964
|
console.log(` ${icon} ${displayName}`);
|
|
1965
|
+
rows.push({ name, connected: Boolean(connected) });
|
|
1609
1966
|
} catch {
|
|
1610
1967
|
console.log(` ❓ ${name}`);
|
|
1968
|
+
rows.push({ name, connected: null });
|
|
1611
1969
|
}
|
|
1612
1970
|
}
|
|
1613
1971
|
|
|
1614
1972
|
const imessage = imessageDoctor();
|
|
1615
1973
|
console.log(` ${imessage.connected ? '✅' : '❌'} iMessage (local Mac)`);
|
|
1974
|
+
rows.push({ name: 'imessage', connected: Boolean(imessage.connected) });
|
|
1616
1975
|
|
|
1617
1976
|
console.log('\nConnect integrations at: https://atris.ai/dashboard/settings');
|
|
1977
|
+
if (json) {
|
|
1978
|
+
console.log(JSON.stringify({ scope: 'global', integrations: rows }, null, 2));
|
|
1979
|
+
}
|
|
1980
|
+
return 0;
|
|
1618
1981
|
}
|
|
1619
1982
|
|
|
1620
1983
|
module.exports = {
|
|
1621
1984
|
gmailCommand,
|
|
1985
|
+
gmailConnect,
|
|
1986
|
+
gmailUse,
|
|
1987
|
+
extractGmailMailboxAccount,
|
|
1988
|
+
parseGmailArgs,
|
|
1989
|
+
gmailAccountStatePath,
|
|
1622
1990
|
calendarCommand,
|
|
1623
1991
|
twitterCommand,
|
|
1624
1992
|
slackCommand,
|
|
@@ -1627,6 +1995,7 @@ module.exports = {
|
|
|
1627
1995
|
integrationsStatus,
|
|
1628
1996
|
parseCalendarSearchArgs,
|
|
1629
1997
|
filterCalendarEventsForSearch,
|
|
1998
|
+
formatCalendarEvents,
|
|
1630
1999
|
formatCalendarSearchResults,
|
|
1631
2000
|
formatCalendarSearchError,
|
|
1632
2001
|
};
|
package/commands/interview.js
CHANGED
|
@@ -153,6 +153,14 @@ async function interviewCommand(args = [], root = process.cwd()) {
|
|
|
153
153
|
return 0;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
const { refuseHeadlessUnless } = require('../lib/noninteractive');
|
|
157
|
+
if (refuseHeadlessUnless(args, {
|
|
158
|
+
allowYes: true,
|
|
159
|
+
usage: 'Usage: atris interview [subject] [--yes]\nHeadless interview needs --yes.',
|
|
160
|
+
})) {
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
|
|
156
164
|
const ws = findWorkspaceRoot(root);
|
|
157
165
|
if (!ws) {
|
|
158
166
|
console.error('✗ Not inside an Atris workspace (no atris/ directory found).');
|