atris 3.74.9 → 3.74.10
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/commands/auth.js +4 -3
- package/commands/computer.js +9 -7
- package/commands/context-sync.js +2 -1
- package/commands/integrations.js +2 -1
- package/commands/member.js +3 -3
- package/commands/ninety-nine.js +90 -30
- package/commands/orb.js +1 -1
- package/commands/pull.js +3 -3
- package/commands/push.js +2 -2
- package/commands/skill.js +5 -0
- package/commands/soul.js +2 -1
- package/commands/stream.js +3 -2
- package/commands/strings.js +1 -1
- package/commands/unknowns.js +1 -1
- package/commands/workflow.js +1 -1
- package/commands/zen.js +4 -3
- package/lib/auto-accept-certified.js +2 -3
- package/lib/autoland.js +4 -3
- package/lib/campaign-loop.js +12 -4
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/first-minute.js +1 -0
- package/lib/gm-bet.js +51 -48
- package/lib/gm-card.js +46 -19
- package/lib/gm-phone-icon.js +80 -0
- package/lib/gm-phone-page.js +803 -96
- package/lib/gm-phone.js +15 -4
- package/lib/journal.js +1 -1
- package/lib/noninteractive.js +5 -1
- package/lib/roster-swap.js +8 -3
- package/lib/task-decision.js +1 -1
- package/lib/zen-apply.js +14 -1
- package/lib/zen.js +37 -3
- package/package.json +1 -1
- package/utils/auth.js +69 -7
package/commands/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { AGENT_TOKEN_EXPIRED_DETAIL, decodeJwtClaims, loadCredentials, saveCredentials, saveAgentClientCredentials, preserveAgentClientLogin, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, ensureValidCredentials, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, saveProfile, getTokenExpiryEpochSeconds, getTerminalSessionId, setSessionProfile, getSessionProfile, cleanStaleSessions, getSessionsDir } = require('../utils/auth');
|
|
1
|
+
const { AGENT_TOKEN_EXPIRED_DETAIL, assertSafeLoginWrite, decodeJwtClaims, loadCredentials, saveCredentials, saveAgentClientCredentials, preserveAgentClientLogin, deleteCredentials, getCredentialsPath, openBrowser, promptUser, displayAccountSummary, ensureValidCredentials, loadProfile, listProfiles, profileNameFromEmail, deleteProfile, saveProfile, getTokenExpiryEpochSeconds, getTerminalSessionId, setSessionProfile, getSessionProfile, cleanStaleSessions, getSessionsDir } = require('../utils/auth');
|
|
2
2
|
const { getAppBaseUrl, getApiBaseUrl, apiRequestJson } = require('../utils/api');
|
|
3
3
|
const { isNonInteractive, wantsJson } = require('../lib/noninteractive');
|
|
4
4
|
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
@@ -939,6 +939,7 @@ function activeProfileName(credentials) {
|
|
|
939
939
|
function writeGlobalCredentials(profile) {
|
|
940
940
|
const kept = preserveAgentClientLogin();
|
|
941
941
|
const credentialsPath = getCredentialsPath();
|
|
942
|
+
assertSafeLoginWrite(credentialsPath);
|
|
942
943
|
fs.writeFileSync(credentialsPath, JSON.stringify(profile, null, 2));
|
|
943
944
|
try { fs.chmodSync(credentialsPath, 0o600); } catch {}
|
|
944
945
|
return kept;
|
|
@@ -1167,7 +1168,7 @@ function resolveProfile() {
|
|
|
1167
1168
|
const profile = loadProfile(p);
|
|
1168
1169
|
return profile?.email?.toLowerCase().includes(q);
|
|
1169
1170
|
});
|
|
1170
|
-
if (match) {
|
|
1171
|
+
if (match && loadProfile(match)) {
|
|
1171
1172
|
process.stdout.write(match);
|
|
1172
1173
|
process.exit(0);
|
|
1173
1174
|
}
|
|
@@ -1251,7 +1252,7 @@ function shellInit() {
|
|
|
1251
1252
|
' if [[ $? -eq 0 && "$_tty" == /dev/* ]]; then echo "$_tty"; return; fi',
|
|
1252
1253
|
'}',
|
|
1253
1254
|
'atris() {',
|
|
1254
|
-
' if [[ "$1" == "switch" && -n "$2" && "$2" != "
|
|
1255
|
+
' if [[ "$1" == "switch" && -n "$2" && "$2" != "-"* ]]; then',
|
|
1255
1256
|
' local _profile',
|
|
1256
1257
|
' _profile=$(command atris _resolve "$2" 2>/dev/null)',
|
|
1257
1258
|
' if [[ $? -eq 0 && -n "$_profile" ]]; then',
|
package/commands/computer.js
CHANGED
|
@@ -3547,25 +3547,27 @@ async function computerChat(token, ctx, initialOptions = {}) {
|
|
|
3547
3547
|
}
|
|
3548
3548
|
|
|
3549
3549
|
if (scriptedInput !== null) {
|
|
3550
|
-
|
|
3550
|
+
const messages = oneShotMessage ? [scriptedInput] : scriptedInput.split(/\r?\n/);
|
|
3551
|
+
for (const rawLine of messages) {
|
|
3551
3552
|
const line = String(rawLine || '').trim();
|
|
3552
3553
|
if (!line) continue;
|
|
3553
|
-
|
|
3554
|
-
if (line
|
|
3554
|
+
const isCommand = line.startsWith('/') && !/[\r\n]/.test(line);
|
|
3555
|
+
if (isCommand && (line === '/exit' || line === '/quit')) break;
|
|
3556
|
+
if (isCommand && line.startsWith('/run ')) {
|
|
3555
3557
|
await computerRun(token, line.slice(5), ctx);
|
|
3556
3558
|
continue;
|
|
3557
3559
|
}
|
|
3558
|
-
if (line === '/pwd') {
|
|
3560
|
+
if (isCommand && line === '/pwd') {
|
|
3559
3561
|
await computerRun(token, 'pwd', ctx);
|
|
3560
3562
|
continue;
|
|
3561
3563
|
}
|
|
3562
|
-
if (line === '/audit' || line.startsWith('/audit ')) {
|
|
3564
|
+
if (isCommand && (line === '/audit' || line.startsWith('/audit '))) {
|
|
3563
3565
|
const rawLimit = line.split(/\s+/, 2)[1];
|
|
3564
3566
|
const limit = rawLimit ? Number.parseInt(rawLimit, 10) : 10;
|
|
3565
3567
|
await computerAudit(token, ctx, Number.isFinite(limit) ? limit : 10);
|
|
3566
3568
|
continue;
|
|
3567
3569
|
}
|
|
3568
|
-
if (
|
|
3570
|
+
if (isCommand) {
|
|
3569
3571
|
const command = line.split(/\s+/, 1)[0];
|
|
3570
3572
|
if (!KNOWN_CHAT_COMMANDS.has(command)) {
|
|
3571
3573
|
console.log(`Unknown command: ${command}`);
|
|
@@ -3573,7 +3575,7 @@ async function computerChat(token, ctx, initialOptions = {}) {
|
|
|
3573
3575
|
}
|
|
3574
3576
|
continue;
|
|
3575
3577
|
}
|
|
3576
|
-
sessionId = await sendBusinessChat(token, ctx, line, sessionId, false, null, {
|
|
3578
|
+
sessionId = await sendBusinessChat(token, ctx, oneShotMessage ? rawLine : line, sessionId, false, null, {
|
|
3577
3579
|
worker,
|
|
3578
3580
|
model,
|
|
3579
3581
|
systemPrompt: chatSystemPrompt,
|
package/commands/context-sync.js
CHANGED
|
@@ -204,7 +204,8 @@ async function businessDiff(slug) {
|
|
|
204
204
|
const localContent = fs.readFileSync(localPath, 'utf8');
|
|
205
205
|
|
|
206
206
|
if (f.isNew) {
|
|
207
|
-
|
|
207
|
+
const lineCount = localContent.split('\n').length - (localContent.endsWith('\n') ? 1 : 0);
|
|
208
|
+
console.log(` + ${f.path.replace(/^\//, '')} (new file, ${lineCount} lines)`);
|
|
208
209
|
} else {
|
|
209
210
|
// Show a simple line-count diff
|
|
210
211
|
console.log(` ~ ${f.path.replace(/^\//, '')} (modified)`);
|
package/commands/integrations.js
CHANGED
|
@@ -1226,7 +1226,8 @@ async function calendarSearch(args = []) {
|
|
|
1226
1226
|
}
|
|
1227
1227
|
|
|
1228
1228
|
async function calendarToday() {
|
|
1229
|
-
|
|
1229
|
+
const { start, end } = localDayBounds();
|
|
1230
|
+
await calendarRange("Today's events", { timeMin: start.toISOString(), timeMax: end.toISOString() });
|
|
1230
1231
|
}
|
|
1231
1232
|
|
|
1232
1233
|
async function calendarYesterday() {
|
package/commands/member.js
CHANGED
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { execFileSync, spawnSync } = require('child_process');
|
|
6
|
-
const { loadCredentials } = require('../utils/auth');
|
|
6
|
+
const { loadCredentials, ensureFreshCredentials } = require('../utils/auth');
|
|
7
7
|
const { resolveBackendRoot } = require('../utils/backend-root');
|
|
8
8
|
const { apiRequestJson } = require('../utils/api');
|
|
9
9
|
const { runAliveTick } = require('../lib/member-alive');
|
|
@@ -4942,7 +4942,7 @@ async function memberPush(name) {
|
|
|
4942
4942
|
process.exit(1);
|
|
4943
4943
|
}
|
|
4944
4944
|
|
|
4945
|
-
const creds =
|
|
4945
|
+
const creds = await ensureFreshCredentials(apiRequestJson);
|
|
4946
4946
|
if (!creds || !creds.token) {
|
|
4947
4947
|
console.error('Not logged in. Run: atris login');
|
|
4948
4948
|
process.exit(1);
|
|
@@ -5007,7 +5007,7 @@ async function memberPush(name) {
|
|
|
5007
5007
|
// --- PULL subcommand ---
|
|
5008
5008
|
|
|
5009
5009
|
async function memberPull(nameOrAgentId) {
|
|
5010
|
-
const creds =
|
|
5010
|
+
const creds = await ensureFreshCredentials(apiRequestJson);
|
|
5011
5011
|
if (!creds || !creds.token) {
|
|
5012
5012
|
console.error('Not logged in. Run: atris login');
|
|
5013
5013
|
process.exit(1);
|
package/commands/ninety-nine.js
CHANGED
|
@@ -20,19 +20,26 @@ const {
|
|
|
20
20
|
sessionStatus,
|
|
21
21
|
sessionsToday,
|
|
22
22
|
healthToday,
|
|
23
|
-
healthSummary,
|
|
24
23
|
} = require('../lib/ninety-nine');
|
|
25
24
|
|
|
26
25
|
function usage() {
|
|
27
26
|
return [
|
|
28
27
|
'usage: atris 99 focus <25|50|90>',
|
|
29
28
|
' atris 99 meditate <10|20|30>',
|
|
29
|
+
' atris 99 show the timer and today',
|
|
30
|
+
' atris 99 <25|50|90> start focus',
|
|
31
|
+
' atris 99 <10|20|30> start meditation',
|
|
32
|
+
' atris 99 f <25|50|90>',
|
|
33
|
+
' atris 99 m <10|20|30>',
|
|
34
|
+
' atris 99 start <focus|meditate> <minutes>',
|
|
35
|
+
' atris 99 again repeat the last finished session',
|
|
30
36
|
' atris 99 stop [note words]',
|
|
31
37
|
' atris 99 pause',
|
|
32
38
|
' atris 99 resume',
|
|
33
39
|
' atris 99 status [--json]',
|
|
34
40
|
' atris 99 today [--json]',
|
|
35
41
|
' atris 99 sync [--dry-run]',
|
|
42
|
+
' atris 99 help | --help',
|
|
36
43
|
'',
|
|
37
44
|
'sync is off without a sync block in ~/.atris/99/config.json (ATRIS_99_CONFIG overrides it).',
|
|
38
45
|
'personal: {"sync":{"target":"personal","app_slug":"project-99"}}',
|
|
@@ -152,6 +159,23 @@ function appendRecord(logPath, previousText, record) {
|
|
|
152
159
|
return previousText + line;
|
|
153
160
|
}
|
|
154
161
|
|
|
162
|
+
function todayLine(result) {
|
|
163
|
+
return `Today: ${result.counts.focus} focus, ${result.counts.meditate} meditate, ${result.finished_minutes} min`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function kindName(kind) {
|
|
167
|
+
return kind === 'focus' ? 'Focus' : 'Meditate';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function timeLeft(session, time) {
|
|
171
|
+
const seconds = sessionStatus(session, time).running.remaining_seconds;
|
|
172
|
+
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function localEndTime(end) {
|
|
176
|
+
return end.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
155
179
|
function ninetyNineAtris(args = process.argv.slice(3), {
|
|
156
180
|
log = console.log,
|
|
157
181
|
now = () => new Date(),
|
|
@@ -161,32 +185,48 @@ function ninetyNineAtris(args = process.argv.slice(3), {
|
|
|
161
185
|
request,
|
|
162
186
|
credentials,
|
|
163
187
|
} = {}) {
|
|
164
|
-
|
|
188
|
+
let [action, ...rest] = args;
|
|
189
|
+
const overview = args.length === 0;
|
|
165
190
|
// Words after stop belong to its note, including the word "help".
|
|
166
191
|
if ((action !== 'stop' && argsWantHelp(args)) || (action === 'stop' && ['--help', '-h'].includes(rest[0]))) {
|
|
167
192
|
log(usage());
|
|
168
193
|
return 0;
|
|
169
194
|
}
|
|
170
195
|
if (action === 'sync') return syncNinetyNine(rest, { log, logPath, configPath, statePath, request, credentials });
|
|
196
|
+
if (action === 'start' && ['focus', 'meditate', 'f', 'm'].includes(rest[0])) [action, ...rest] = rest;
|
|
197
|
+
if (action === 'f') action = 'focus';
|
|
198
|
+
if (action === 'm') action = 'meditate';
|
|
199
|
+
for (const kind of ['focus', 'meditate']) {
|
|
200
|
+
if (validLength(kind, Number(action)) && String(Number(action)) === action) {
|
|
201
|
+
rest.unshift(action);
|
|
202
|
+
action = kind;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
171
206
|
const json = rest.includes('--json');
|
|
172
207
|
if (action === 'focus' || action === 'meditate') {
|
|
208
|
+
const lengths = action === 'focus' ? '25 | 50 | 90' : '10 | 20 | 30';
|
|
209
|
+
if (rest.length === 0) {
|
|
210
|
+
log(`Pick a length: atris 99 ${action} ${lengths}`);
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
173
213
|
if (rest.length !== 1 || !validLength(action, Number(rest[0])) || String(Number(rest[0])) !== rest[0]) {
|
|
174
|
-
log(action
|
|
175
|
-
return
|
|
214
|
+
log(`Allowed ${action} lengths: ${lengths.replaceAll(' | ', ', ')} minutes.`);
|
|
215
|
+
return 1;
|
|
176
216
|
}
|
|
177
217
|
} else if (action === 'status' || action === 'today') {
|
|
178
218
|
if (rest.length > 1 || (rest.length === 1 && !json)) {
|
|
179
219
|
log(`usage: atris 99 ${action} [--json]`);
|
|
180
220
|
return 2;
|
|
181
221
|
}
|
|
182
|
-
} else if (action === 'pause' || action === 'resume') {
|
|
222
|
+
} else if (action === 'pause' || action === 'resume' || action === 'again') {
|
|
183
223
|
if (rest.length) {
|
|
184
224
|
log(`usage: atris 99 ${action}`);
|
|
185
225
|
return 2;
|
|
186
226
|
}
|
|
187
|
-
} else if (action !== 'stop') {
|
|
188
|
-
log(
|
|
189
|
-
return
|
|
227
|
+
} else if (action !== 'stop' && !overview) {
|
|
228
|
+
log(`Unknown: ${action}. Try: atris 99 help`);
|
|
229
|
+
return 1;
|
|
190
230
|
}
|
|
191
231
|
|
|
192
232
|
let lock;
|
|
@@ -222,55 +262,75 @@ function ninetyNineAtris(args = process.argv.slice(3), {
|
|
|
222
262
|
|
|
223
263
|
if (action === 'stop') {
|
|
224
264
|
if (!stopTarget) {
|
|
225
|
-
log('
|
|
265
|
+
log('Nothing running. Start one: atris 99 50');
|
|
226
266
|
return 1;
|
|
227
267
|
}
|
|
228
268
|
const end = endRecord(stopTarget, time, rest.join(' '));
|
|
229
269
|
appendRecord(file, text, end);
|
|
230
|
-
|
|
270
|
+
records.push(end);
|
|
271
|
+
log(end.status === 'done'
|
|
272
|
+
? `${kindName(stopTarget.kind)} ${stopTarget.planned_min} done.`
|
|
273
|
+
: "Stopped early, so it doesn't count.");
|
|
274
|
+
log(todayLine(sessionsToday(foldSessions(records), time)));
|
|
231
275
|
return 0;
|
|
232
276
|
}
|
|
233
277
|
|
|
234
278
|
sessions = foldSessions(records);
|
|
235
279
|
active = runningSession(sessions, time);
|
|
236
|
-
if (action === '
|
|
280
|
+
if (action === 'again') {
|
|
281
|
+
const previous = sessions.filter((session) => session.status === 'done')
|
|
282
|
+
.sort((a, b) => Date.parse(b.ended_at) - Date.parse(a.ended_at))[0];
|
|
283
|
+
if (!previous) {
|
|
284
|
+
log('No finished session yet. Try: atris 99 50');
|
|
285
|
+
return 1;
|
|
286
|
+
}
|
|
287
|
+
action = previous.kind;
|
|
288
|
+
rest = [String(previous.planned_min)];
|
|
289
|
+
}
|
|
290
|
+
if (overview) {
|
|
291
|
+
if (!active) log('Nothing running.');
|
|
292
|
+
else log(`${kindName(active.kind)} · ${timeLeft(active, time)} left${active.status === 'paused' ? ' · paused' : ''}`);
|
|
293
|
+
log(todayLine(sessionsToday(sessions, time)));
|
|
294
|
+
log('Start: atris 99 50 Again: atris 99 again Help: atris 99 help');
|
|
295
|
+
} else if (action === 'focus' || action === 'meditate') {
|
|
237
296
|
if (active) {
|
|
238
297
|
log('a session is already running; use atris 99 stop first');
|
|
239
298
|
return 1;
|
|
240
299
|
}
|
|
241
300
|
appendRecord(file, text, startRecord(action, Number(rest[0]), time, randomUUID()));
|
|
242
|
-
|
|
301
|
+
const end = new Date(time.getTime() + Number(rest[0]) * 60000);
|
|
302
|
+
log(`${kindName(action)} ${rest[0]} started. Ends at ${localEndTime(end)}.`);
|
|
243
303
|
} else if (action === 'pause' || action === 'resume') {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
304
|
+
if (!active) {
|
|
305
|
+
log('Nothing running. Start one: atris 99 50');
|
|
306
|
+
return 1;
|
|
307
|
+
}
|
|
308
|
+
if (action === 'pause' && active.status === 'paused') {
|
|
309
|
+
log('Already paused. Resume: atris 99 resume');
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
if (action === 'resume' && active.status !== 'paused') {
|
|
313
|
+
log('Not paused.');
|
|
249
314
|
return 1;
|
|
250
315
|
}
|
|
316
|
+
const record = action === 'pause' ? pauseRecord(active, time) : resumeRecord(active, time);
|
|
251
317
|
appendRecord(file, text, record);
|
|
252
|
-
|
|
318
|
+
if (action === 'pause') log(`Paused. ${timeLeft(active, time)} left.`);
|
|
319
|
+
else {
|
|
320
|
+
const end = new Date(Date.parse(active.planned_end_at) + time.getTime() - Date.parse(active.paused_at));
|
|
321
|
+
log(`Resumed. Ends at ${localEndTime(end)}.`);
|
|
322
|
+
}
|
|
253
323
|
} else if (action === 'status') {
|
|
254
324
|
const result = sessionStatus(active, time);
|
|
255
325
|
if (json) log(JSON.stringify(result));
|
|
256
326
|
else if (!active) log('nothing running');
|
|
257
|
-
else {
|
|
258
|
-
const seconds = result.running.remaining_seconds;
|
|
259
|
-
log(`${active.kind}: ${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')} left${active.status === 'paused' ? ' (paused)' : ''}`);
|
|
260
|
-
}
|
|
327
|
+
else log(`${kindName(active.kind)} · ${timeLeft(active, time)} left${active.status === 'paused' ? ' · paused' : ''}`);
|
|
261
328
|
} else {
|
|
262
329
|
const result = sessionsToday(sessions, time);
|
|
263
330
|
const health = healthToday(records, time);
|
|
264
331
|
if (health) result.health_day = health;
|
|
265
332
|
if (json) log(JSON.stringify(result));
|
|
266
|
-
else
|
|
267
|
-
for (const session of result.sessions) {
|
|
268
|
-
log(`${session.kind} ${session.planned_min} minutes: ${session.status}`);
|
|
269
|
-
}
|
|
270
|
-
log(`${result.finished_minutes} finished minutes today`);
|
|
271
|
-
log(`Today: ${result.counts.focus} focus, ${result.counts.meditate} meditate, ${result.finished_minutes} min`);
|
|
272
|
-
if (health) log(healthSummary(health));
|
|
273
|
-
}
|
|
333
|
+
else log(todayLine(result));
|
|
274
334
|
}
|
|
275
335
|
return 0;
|
|
276
336
|
} catch (error) {
|
package/commands/orb.js
CHANGED
|
@@ -153,7 +153,7 @@ function unknownOrbFlags(args = []) {
|
|
|
153
153
|
function tailLines(text, count = 60) {
|
|
154
154
|
const lines = String(text || '').split(/\r?\n/);
|
|
155
155
|
if (lines[lines.length - 1] === '') lines.pop();
|
|
156
|
-
return lines.slice(-count).join('\n');
|
|
156
|
+
return lines.slice(Math.max(0, lines.length - count)).join('\n');
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
async function runOrb(args = []) {
|
package/commands/pull.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
3
|
+
const { ensureFreshCredentials, abortOnAuthFailure } = require('../utils/auth');
|
|
4
4
|
const { apiRequestJson } = require('../utils/api');
|
|
5
5
|
const { findAllMembers } = require('./member');
|
|
6
6
|
const { loadConfig } = require('../utils/config');
|
|
@@ -158,7 +158,7 @@ async function pullAtris() {
|
|
|
158
158
|
process.exit(1);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
const creds =
|
|
161
|
+
const creds = await ensureFreshCredentials(apiRequestJson);
|
|
162
162
|
if (!creds || !creds.token) {
|
|
163
163
|
console.error('Not logged in. Run: atris login');
|
|
164
164
|
process.exit(1);
|
|
@@ -206,7 +206,7 @@ async function pullAtris() {
|
|
|
206
206
|
|
|
207
207
|
async function pullBusiness(slug) {
|
|
208
208
|
const elapsedMs = startTimer();
|
|
209
|
-
const creds =
|
|
209
|
+
const creds = await ensureFreshCredentials(apiRequestJson);
|
|
210
210
|
if (!creds || !creds.token) {
|
|
211
211
|
console.error('Not logged in. Run: atris login');
|
|
212
212
|
process.exit(1);
|
package/commands/push.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const readline = require('readline');
|
|
4
|
-
const {
|
|
4
|
+
const { ensureFreshCredentials, abortOnAuthFailure } = require('../utils/auth');
|
|
5
5
|
const { apiRequestJson } = require('../utils/api');
|
|
6
6
|
const { loadBusinesses, saveBusinesses, businessMatchesSlug } = require('./business');
|
|
7
7
|
const { loadManifest, saveManifest, buildManifest, computeLocalHashes, isIgnoredSyncPath, filterSyncFiles } = require('../lib/manifest');
|
|
@@ -457,7 +457,7 @@ async function pushAtris() {
|
|
|
457
457
|
const timeoutSec = parsePushTimeoutSec(process.argv);
|
|
458
458
|
let onlyPrefixes = null;
|
|
459
459
|
|
|
460
|
-
const creds =
|
|
460
|
+
const creds = await ensureFreshCredentials(apiRequestJson);
|
|
461
461
|
if (!creds || !creds.token) { console.error('Not logged in. Run: atris login'); process.exit(1); }
|
|
462
462
|
|
|
463
463
|
// Determine source directory
|
package/commands/skill.js
CHANGED
|
@@ -549,6 +549,11 @@ function skillFix(name) {
|
|
|
549
549
|
const skillsDir = path.join(process.cwd(), 'atris', 'skills');
|
|
550
550
|
const allSkills = findAllSkills(skillsDir);
|
|
551
551
|
|
|
552
|
+
if (allSkills.length === 0) {
|
|
553
|
+
console.error('No skills found in atris/skills/.');
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
|
|
552
557
|
const targets = name === '--all'
|
|
553
558
|
? allSkills
|
|
554
559
|
: allSkills.filter(s => s.folder === name || s.leafFolder === name);
|
package/commands/soul.js
CHANGED
|
@@ -28,7 +28,8 @@ function readFile(filePath) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function countFiles(dir) {
|
|
31
|
-
try { return fs.
|
|
31
|
+
const isFile = (f) => { try { return fs.statSync(path.join(dir, f)).isFile(); } catch { return false; } }; // a dead link skips itself, not the count
|
|
32
|
+
try { return fs.readdirSync(dir, { recursive: true }).filter(f => !f.startsWith('.') && isFile(f)).length; } catch { return 0; }
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// ── Soul snapshot ──────────────────────────────────────
|
package/commands/stream.js
CHANGED
|
@@ -268,7 +268,8 @@ function makeEvent({ ts, agent, event, summary, raw, source, root }) {
|
|
|
268
268
|
const ms = timestampMs(ts);
|
|
269
269
|
if (!ms) return null;
|
|
270
270
|
const cleanAgent = sanitizeAgent(agent);
|
|
271
|
-
|
|
271
|
+
// Snapshot headings are complete lines, not agent-prefixed activity.
|
|
272
|
+
const cleanSummary = sanitizeSummary(summary, event === 'snapshot' ? '' : cleanAgent);
|
|
272
273
|
return {
|
|
273
274
|
ts: new Date(ms).toISOString(),
|
|
274
275
|
ms,
|
|
@@ -755,7 +756,7 @@ function renderJsonLine(record) {
|
|
|
755
756
|
ts: record.ts,
|
|
756
757
|
agent: sanitizeAgent(record.agent),
|
|
757
758
|
event: record.event,
|
|
758
|
-
summary: sanitizeSummary(record.summary, record.agent),
|
|
759
|
+
summary: sanitizeSummary(record.summary, record.event === 'snapshot' ? '' : record.agent),
|
|
759
760
|
raw: record.raw || null,
|
|
760
761
|
});
|
|
761
762
|
}
|
package/commands/strings.js
CHANGED
|
@@ -292,7 +292,7 @@ function stringsCommand(argv) {
|
|
|
292
292
|
Pairs with 'atris slop' (how copy reads), strings governs what words you ship.
|
|
293
293
|
The registry lives in .atris/strings.json. Wire 'check --staged' into the pre-commit gate.
|
|
294
294
|
`);
|
|
295
|
-
return 0;
|
|
295
|
+
return !sub || ['help', '--help', '-h'].includes(sub) ? 0 : 2;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
module.exports = {
|
package/commands/unknowns.js
CHANGED
|
@@ -377,7 +377,7 @@ function normalizeQuestions(payload, problem) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
function fallbackQuestions(problem) {
|
|
380
|
-
const subject =
|
|
380
|
+
const subject = clipLine((String(problem || '').trim() || 'this problem').replace(/[?!.]+$/, ''), 90);
|
|
381
381
|
return [
|
|
382
382
|
{
|
|
383
383
|
question: `What existing code path already owns "${subject}"?`,
|
package/commands/workflow.js
CHANGED
|
@@ -1902,7 +1902,7 @@ async function executeAgentSDKFast(userInput) {
|
|
|
1902
1902
|
} catch (error) {
|
|
1903
1903
|
console.error(`✗ Error: ${error.message}`);
|
|
1904
1904
|
console.log('');
|
|
1905
|
-
console.log('💡
|
|
1905
|
+
console.log('💡 start your local backend at http://127.0.0.1:8000.');
|
|
1906
1906
|
process.exit(1);
|
|
1907
1907
|
}
|
|
1908
1908
|
}
|
package/commands/zen.js
CHANGED
|
@@ -58,14 +58,15 @@ function makeOut(calm) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Name the three biggest places so a large count reads as "mostly agent
|
|
61
|
-
// notes in atris/" rather than an unexplained total.
|
|
61
|
+
// notes in atris/" rather than an unexplained total. A lone fourth place is
|
|
62
|
+
// named too, since "and 1 more place" takes as much room and says less.
|
|
62
63
|
function teamLine(n, places = []) {
|
|
63
64
|
if (!n) return 'no unsaved team notes.';
|
|
64
65
|
const head = n === 1 ? '1 unsaved team note left in place' : `${n} unsaved team notes left in place`;
|
|
65
66
|
if (!places.length) return `${head}.`;
|
|
66
|
-
const named = places.slice(0, 3).map((p) => `${p.place} ${p.count}`);
|
|
67
|
+
const named = places.slice(0, places.length === 4 ? 4 : 3).map((p) => `${p.place} ${p.count}`);
|
|
67
68
|
const rest = places.length - named.length;
|
|
68
|
-
if (rest) named.push(`and ${rest} more
|
|
69
|
+
if (rest) named.push(`and ${rest} more places`);
|
|
69
70
|
return `${head}: ${named.join(', ')}.`;
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -1099,7 +1099,7 @@ function sniffedProtectedLane(task) {
|
|
|
1099
1099
|
function deniedTagOf(task) {
|
|
1100
1100
|
const tag = String((task && task.tag) || '').trim().toLowerCase();
|
|
1101
1101
|
return [...DENIED_TAGS].find((d) =>
|
|
1102
|
-
tag === d || tag.split(/[^a-z0-9]+/).some((w) => w === d || w.replace(/s$/, '') === d)) || null;
|
|
1102
|
+
tag === d || tag.split(/[^a-z0-9]+/).some((w) => w === d || w.replace(/s$/, '') === d || w === d.replace(/s$/, ''))) || null;
|
|
1103
1103
|
}
|
|
1104
1104
|
|
|
1105
1105
|
function protectedLaneOf(task) {
|
|
@@ -1197,8 +1197,7 @@ function evaluateAutoAccept(task, options = {}) {
|
|
|
1197
1197
|
// exact-match cousins: match denied lanes on whole words with a plural
|
|
1198
1198
|
// strip, so a tag variant never slips money/deploy work past the human.
|
|
1199
1199
|
const tag = String(task.tag || '').trim().toLowerCase();
|
|
1200
|
-
const deniedTag =
|
|
1201
|
-
tag === d || tag.split(/[^a-z0-9]+/).some((w) => w === d || w.replace(/s$/, '') === d));
|
|
1200
|
+
const deniedTag = deniedTagOf(task);
|
|
1202
1201
|
if (deniedTag) return { eligible: false, ref, reason: `denied_tag_${deniedTag}` };
|
|
1203
1202
|
|
|
1204
1203
|
// A protected lane declared in prose is not a protection, it is a note.
|
package/lib/autoland.js
CHANGED
|
@@ -187,13 +187,14 @@ function cronMarker(root) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function buildCronLine(root) {
|
|
190
|
+
const quote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
190
191
|
const node = process.execPath;
|
|
191
192
|
const bin = path.join(root, 'bin', 'atris.js');
|
|
192
193
|
const cliBin = fs.existsSync(bin) ? bin : 'atris';
|
|
193
194
|
const logDir = path.join(stateHome(), '.atris', 'overnight');
|
|
194
195
|
const logFile = path.join(logDir, `autoland-${path.basename(root)}.log`);
|
|
195
|
-
const invoke = cliBin.endsWith('.js') ? `${node} ${cliBin}` : cliBin;
|
|
196
|
-
return `14 * * * * cd ${root} && ${invoke} autoland tick >> ${logFile} 2>&1 # ${cronMarker(root)}`;
|
|
196
|
+
const invoke = cliBin.endsWith('.js') ? `${quote(node)} ${quote(cliBin)}` : cliBin;
|
|
197
|
+
return `14 * * * * cd ${quote(root)} && ${invoke} autoland tick >> ${quote(logFile)} 2>&1 # ${cronMarker(root)}`;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
function readCrontab() {
|
|
@@ -499,7 +500,7 @@ function dejargon(line, options = {}) {
|
|
|
499
500
|
.replace(/--([a-z][a-z-]*)/g, (_match, flag) => flag.replace(/-/g, ' '))
|
|
500
501
|
.replace(/\b[a-z0-9]+(?:_[a-z0-9]+)+\b/gi, match => match.replace(/_/g, ' '))
|
|
501
502
|
.replace(/\b[a-z]+(?:[A-Z][A-Za-z0-9]*)+\b/g, match => match.replace(/([a-z0-9])([A-Z])/g, '$1 $2'));
|
|
502
|
-
if (options.preserveTickets !== true) value = value.replace(/\bCLI-\d+\b/g, '');
|
|
503
|
+
if (options.preserveTickets !== true) value = value.replace(/\(\s*CLI-\d+\s*\)|\bCLI-\d+\b/g, '');
|
|
503
504
|
return value.replace(/\s+/g, ' ').trim();
|
|
504
505
|
}
|
|
505
506
|
|
package/lib/campaign-loop.js
CHANGED
|
@@ -50,6 +50,14 @@ function readBrief(root, campaignId) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function objectiveWithBrief(objective, brief) {
|
|
54
|
+
if (!brief) return objective;
|
|
55
|
+
// Mission objectives also become titles and log lines, so keep them on one line.
|
|
56
|
+
const line = objective.replace(/\s+/g, ' ').trim();
|
|
57
|
+
const briefLine = brief.replace(/\s+/g, ' ').trim();
|
|
58
|
+
return line.includes(briefLine) ? line : `${line} ${briefLine}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
function rankCampaignMembers(view, rsiSummary = null, events = null) {
|
|
54
62
|
const seats = Array.isArray(view && view.seats) ? view.seats.map((s) => s.member).filter(Boolean) : [];
|
|
55
63
|
const candidates = [...new Set([view && view.owner, ...seats].filter(Boolean))];
|
|
@@ -180,7 +188,7 @@ async function askOwner(root, view, prompt) {
|
|
|
180
188
|
|
|
181
189
|
// Run one atris command and resolve with its exit code and output.
|
|
182
190
|
// A live step an engine prints while a mission runs: "<member> <hh:mm> <what it is doing>".
|
|
183
|
-
const ACTIVITY_LINE = /^\s*[a-z0-9][a-z0-9-]*\s+\d{1,2}:\d{2}\s{2,}\S/i;
|
|
191
|
+
const ACTIVITY_LINE = /^\s*[a-z0-9][a-z0-9._-]*\s+\d{1,2}:\d{2}\s{2,}\S/i;
|
|
184
192
|
|
|
185
193
|
function runCli(args, { cwd, env = process.env, onLine = null } = {}) {
|
|
186
194
|
return new Promise((resolve) => {
|
|
@@ -258,7 +266,7 @@ function makeLoop(root, options = {}, deps = {}) {
|
|
|
258
266
|
proposals.forEach((proposal, index) => {
|
|
259
267
|
const missionId = proposalId(proposal.objective);
|
|
260
268
|
const key = claimKey(proposal.objective);
|
|
261
|
-
|
|
269
|
+
proposal.objective = objectiveWithBrief(proposal.objective, brief);
|
|
262
270
|
append(id, 'mission_proposed', { mission_id: missionId, member: proposal.member, objective: proposal.objective, verify: proposal.verify, estimate_usd: proposal.estimate_usd || undefined }, proposal.member);
|
|
263
271
|
append(id, 'mission_claimed', { mission_id: missionId, claim_key: key }, proposal.member);
|
|
264
272
|
if (index === 0) append(id, 'step', { mission_id: missionId, ...plan }, current.owner);
|
|
@@ -282,7 +290,7 @@ function makeLoop(root, options = {}, deps = {}) {
|
|
|
282
290
|
const current = view(id);
|
|
283
291
|
const reserve = work.estimate_usd || defaultReserve(current);
|
|
284
292
|
const brief = (deps.brief ? deps.brief(id) : readBrief(root, id)).trim();
|
|
285
|
-
const objective =
|
|
293
|
+
const objective = objectiveWithBrief(work.objective, brief);
|
|
286
294
|
const started = await cli([
|
|
287
295
|
'mission', 'start', objective, '--mission-id', work.mission_id, '--owner', work.member,
|
|
288
296
|
'--runner', runner, '--verify', work.verify || current.judge, '--campaign', id, '--reserve', String(reserve),
|
|
@@ -299,7 +307,7 @@ function makeLoop(root, options = {}, deps = {}) {
|
|
|
299
307
|
append(id, 'mission_vetoed', { mission_id: work.mission_id, reason: why.slice(0, 200) }, current.owner);
|
|
300
308
|
return { mission_id: work.mission_id, started: false };
|
|
301
309
|
}
|
|
302
|
-
log(` ${work.mission_id} started for ${work.member}: ${
|
|
310
|
+
log(` ${work.mission_id} started for ${work.member}: ${objective}`);
|
|
303
311
|
// The engine's live steps ("researcher 06:47 editing stable.mjs") pass through to
|
|
304
312
|
// the campaign's own output, so a screen watching the run can show the work.
|
|
305
313
|
const stepsBefore = (view(id).missions.find((m) => m.id === work.mission_id) || {}).steps || 0;
|
package/lib/card.js
CHANGED
|
@@ -114,7 +114,7 @@ function buildCard(spec = {}, opts = {}) {
|
|
|
114
114
|
</style></head>
|
|
115
115
|
<body><div class="card">${main}${foot}</div></body></html>`;
|
|
116
116
|
|
|
117
|
-
return { html, width: size.w, height: size.h, kind, theme: spec.theme
|
|
117
|
+
return { html, width: size.w, height: size.h, kind, theme: themes[spec.theme] ? spec.theme : 'atris', size: spec.size || 'og' };
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
module.exports = { buildCard, SIZES, KINDS };
|
package/lib/chat-log-scan.js
CHANGED
|
@@ -310,7 +310,7 @@ function scanChatLogs(options = {}) {
|
|
|
310
310
|
scope: options.global === true ? 'global' : 'workspace',
|
|
311
311
|
window_hours: maxAgeHours,
|
|
312
312
|
sources: {
|
|
313
|
-
ax_dirs: axLogDirs(cwd),
|
|
313
|
+
ax_dirs: axLogDirs(cwd, { global: options.global === true }),
|
|
314
314
|
cursor_roots: cursorTranscriptRoots(cwd),
|
|
315
315
|
},
|
|
316
316
|
summary: {
|
package/lib/first-minute.js
CHANGED
|
@@ -1058,6 +1058,7 @@ function speakFirstMinute({
|
|
|
1058
1058
|
log(JSON.stringify(freshMinuteJson(folderName(root), visible, { root }), null, 2));
|
|
1059
1059
|
return 2;
|
|
1060
1060
|
}
|
|
1061
|
+
if (asJson) return speakKeepWorkingMinute({ root, asJson, log });
|
|
1061
1062
|
const screen = buildFirstMinute({
|
|
1062
1063
|
root,
|
|
1063
1064
|
fresh: isFresh,
|