atris 3.74.8 → 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/gm.js +41 -14
- package/commands/integrations.js +2 -1
- package/commands/member.js +44 -10
- 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 +113 -75
- package/lib/gm-card.js +46 -19
- package/lib/gm-phone-icon.js +80 -0
- package/lib/gm-phone-page.js +866 -116
- 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/text-table.js +138 -12
- package/lib/zen-apply.js +15 -2
- package/lib/zen.js +37 -3
- package/package.json +1 -1
- package/scripts/det/ytnotes +9 -3
- 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/gm.js
CHANGED
|
@@ -797,19 +797,20 @@ function tail(text, used, width) {
|
|
|
797
797
|
return clip(text, Math.max(10, width - used));
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
-
// Last guard on width: a long line breaks after a
|
|
801
|
-
// line; anything still too long ends in "...".
|
|
800
|
+
// Last guard on width, in terminal columns: a long line breaks after a
|
|
801
|
+
// comma onto an indented line; anything still too long ends in "...".
|
|
802
802
|
function fitLines(lines, width) {
|
|
803
|
+
const { displayWidth, sliceColumns } = require('../lib/text-table');
|
|
803
804
|
const out = [];
|
|
804
805
|
for (const line of lines) {
|
|
805
806
|
let rest = line;
|
|
806
|
-
while (rest
|
|
807
|
-
const cut = rest.lastIndexOf(', ', width - 1);
|
|
807
|
+
while (displayWidth(rest) > width) {
|
|
808
|
+
const cut = rest.lastIndexOf(', ', sliceColumns(rest, width).length - 1);
|
|
808
809
|
if (cut <= 0) break;
|
|
809
810
|
out.push(rest.slice(0, cut + 1));
|
|
810
811
|
rest = ` ${rest.slice(cut + 2)}`;
|
|
811
812
|
}
|
|
812
|
-
out.push(rest
|
|
813
|
+
out.push(displayWidth(rest) > width ? `${sliceColumns(rest, width - 3)}...` : rest);
|
|
813
814
|
}
|
|
814
815
|
return out;
|
|
815
816
|
}
|
|
@@ -830,13 +831,30 @@ function boardWorkspaceRoot(args = []) {
|
|
|
830
831
|
return taskDb.workspaceRoot(path.resolve(workspaceArg));
|
|
831
832
|
}
|
|
832
833
|
|
|
834
|
+
// The team page's cells (role, ovr, engine) for each roster member, read
|
|
835
|
+
// only when the board draws its box in a terminal; null otherwise.
|
|
836
|
+
function boardCells(board, workspaceRoot, ratings) {
|
|
837
|
+
if (!process.stdout.isTTY || !workspaceRoot) return null;
|
|
838
|
+
return require('../lib/gm-bet').teamCells(workspaceRoot, board.roster.map(r => r.name), { ratings });
|
|
839
|
+
}
|
|
840
|
+
|
|
833
841
|
function loadBoard(workspaceRoot, options = {}) {
|
|
842
|
+
return loadSeasonBoard(workspaceRoot, options).board;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// The board and every member's rating (gm-season memberRatings, the same
|
|
846
|
+
// numbers the orb's team page reads) from one read of the season.
|
|
847
|
+
function loadSeasonBoard(workspaceRoot, options = {}) {
|
|
834
848
|
const season = require('../lib/gm-season');
|
|
835
|
-
|
|
849
|
+
const played = season.loadSeason(workspaceRoot, { lastSeen: options.lastSeen });
|
|
850
|
+
return { board: season.seasonBoard(played, options), ratings: season.memberRatings(played.outcomes || []) };
|
|
836
851
|
}
|
|
837
852
|
|
|
838
853
|
// One screen, about 40 rows at most. Pure: board in, lines out.
|
|
839
|
-
|
|
854
|
+
// In a terminal, given the team page's cells, the roster is the orb's team
|
|
855
|
+
// table as a divided box (member, role, ovr, engine, status); piped, or when
|
|
856
|
+
// the box cannot fit the width, it keeps the plain columns.
|
|
857
|
+
function renderBoardLines(board, { interactive = false, width = termWidth(), bordered = Boolean(process.stdout.isTTY), cells = null } = {}) {
|
|
840
858
|
const { clockLabel } = require('../lib/gm-season');
|
|
841
859
|
const lines = [];
|
|
842
860
|
const nameW = nameWidth([
|
|
@@ -856,7 +874,12 @@ function renderBoardLines(board, { interactive = false, width = termWidth() } =
|
|
|
856
874
|
lines.push('');
|
|
857
875
|
|
|
858
876
|
lines.push('roster, last 30 days');
|
|
859
|
-
|
|
877
|
+
const boxed = bordered && cells && board.roster.length
|
|
878
|
+
? require('../lib/gm-bet').teamBox(board.roster.map(r => ({ member: r.name, status: r.status })), cells, { width: width - 2 })
|
|
879
|
+
: null;
|
|
880
|
+
if (boxed) {
|
|
881
|
+
lines.push(...boxed.split('\n').map(line => ` ${line}`));
|
|
882
|
+
} else if (board.roster.length) {
|
|
860
883
|
lines.push(` ${pad('name', nameW)}${pad('ovr', 8)}${pad('form', 7)}${pad('record', 18)}status`);
|
|
861
884
|
for (const row of board.roster) {
|
|
862
885
|
const record = `landed ${row.landed} of ${row.jobs}`;
|
|
@@ -913,9 +936,10 @@ function renderBoardLines(board, { interactive = false, width = termWidth() } =
|
|
|
913
936
|
}
|
|
914
937
|
lines.push('');
|
|
915
938
|
|
|
916
|
-
lines.push(
|
|
939
|
+
lines.push(`key: ovr 60 to 99 = share of finished jobs landed, ${boxed ? 'new' : 'rookie'} under 3 finished,`);
|
|
917
940
|
lines.push(' stricter than the website rating on purpose (drops and abandons count)');
|
|
918
|
-
lines.push('
|
|
941
|
+
if (boxed) lines.push(' ·· = nothing to show yet: no finished job, no role, or no pinned model');
|
|
942
|
+
else lines.push(' form: last 5, newest right: k landed, d dropped, x sent back, a abandoned');
|
|
919
943
|
lines.push(' hours: claim to result. on a job: moved in the last 6h. review: pending');
|
|
920
944
|
if (interactive) for (const line of PLAY_KEYS) lines.push(line);
|
|
921
945
|
else lines.push(' run atris gm in a terminal to play');
|
|
@@ -990,9 +1014,10 @@ function printLeague() {
|
|
|
990
1014
|
}
|
|
991
1015
|
|
|
992
1016
|
function printBoard(args = []) {
|
|
993
|
-
const
|
|
1017
|
+
const root = boardWorkspaceRoot(args);
|
|
1018
|
+
const { board, ratings } = loadSeasonBoard(root);
|
|
994
1019
|
if (board.error) return failRead(board.error);
|
|
995
|
-
console.log(renderBoardLines(board).join('\n'));
|
|
1020
|
+
console.log(renderBoardLines(board, { cells: boardCells(board, root, ratings) }).join('\n'));
|
|
996
1021
|
return undefined;
|
|
997
1022
|
}
|
|
998
1023
|
|
|
@@ -1107,11 +1132,11 @@ async function gmPlay(args = []) {
|
|
|
1107
1132
|
leagueRowsSeen = [...board.rows, ...(board.hidden_clubs || [])];
|
|
1108
1133
|
write(renderLeagueLines(board, { interactive: true }).join('\n'));
|
|
1109
1134
|
} else {
|
|
1110
|
-
const board =
|
|
1135
|
+
const { board, ratings } = loadSeasonBoard(workspaceRoot, { seen, lastSeen });
|
|
1111
1136
|
lastBoard = board;
|
|
1112
1137
|
seen = new Set(board.last_plays.map(play => play.id));
|
|
1113
1138
|
stuck = board.stuck || [];
|
|
1114
|
-
write(renderBoardLines(board, { interactive: true }).join('\n'));
|
|
1139
|
+
write(renderBoardLines(board, { interactive: true, cells: boardCells(board, workspaceRoot, ratings) }).join('\n'));
|
|
1115
1140
|
season.writeLastSeen(workspaceRoot, board.now);
|
|
1116
1141
|
}
|
|
1117
1142
|
} catch (err) {
|
|
@@ -1986,9 +2011,11 @@ async function gmCommand(...args) {
|
|
|
1986
2011
|
}
|
|
1987
2012
|
|
|
1988
2013
|
module.exports = {
|
|
2014
|
+
boardCells,
|
|
1989
2015
|
displayCommand,
|
|
1990
2016
|
ensureStarterMission,
|
|
1991
2017
|
gmCommand,
|
|
2018
|
+
loadSeasonBoard,
|
|
1992
2019
|
pickClub,
|
|
1993
2020
|
playAction,
|
|
1994
2021
|
renderBoardLines,
|
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);
|
|
@@ -11664,6 +11664,44 @@ async function memberStandupWalk(room, args = []) {
|
|
|
11664
11664
|
return payload;
|
|
11665
11665
|
}
|
|
11666
11666
|
|
|
11667
|
+
// The team page's cells (role, ovr, engine) per standup member, read only
|
|
11668
|
+
// when the standup can draw its table (a terminal); --json never carries
|
|
11669
|
+
// them. ovr comes from gm-season memberRatings, as on the orb's team page.
|
|
11670
|
+
function standupTableData(standup, cwd = process.cwd()) {
|
|
11671
|
+
if (!process.stdout.isTTY) return { bordered: false };
|
|
11672
|
+
const season = require('../lib/gm-season');
|
|
11673
|
+
const root = require('../lib/task-db').workspaceRoot(path.resolve(cwd));
|
|
11674
|
+
let ratings = new Map();
|
|
11675
|
+
try {
|
|
11676
|
+
const played = season.loadSeason(root, { lastSeen: null });
|
|
11677
|
+
if (played && !played.error) ratings = season.memberRatings(played.outcomes || []);
|
|
11678
|
+
} catch { ratings = new Map(); }
|
|
11679
|
+
const cells = require('../lib/gm-bet').teamCells(root, standup.map((s) => s.member), { ratings });
|
|
11680
|
+
return { bordered: true, cells };
|
|
11681
|
+
}
|
|
11682
|
+
|
|
11683
|
+
// The standup: in a terminal the orb's team table as a divided box
|
|
11684
|
+
// (member, role, ovr, engine, status) with each member's found and push
|
|
11685
|
+
// lines under it; piped, or when the box cannot fit, the boxes it always had.
|
|
11686
|
+
function memberStandupLines(standup, { bordered = false, width = process.stdout.columns, cells = null } = {}) {
|
|
11687
|
+
if (bordered && cells && standup.length) {
|
|
11688
|
+
const table = require('../lib/gm-bet').teamBox(standup.map((s) => ({ member: s.member, status: s.status })), cells, { width: Number(width) - 2 });
|
|
11689
|
+
if (table) {
|
|
11690
|
+
return [
|
|
11691
|
+
...table.split('\n').map((line) => ` ${line}`),
|
|
11692
|
+
...standup.flatMap((s) => [` ${s.member}`, ` found: ${s.latest_update}`, ` push: ${s.next_push}`]),
|
|
11693
|
+
];
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
return standup.flatMap((s, idx) => [
|
|
11697
|
+
`+-----------------------------------------------------------------------+`,
|
|
11698
|
+
`| ${idx + 1}. ${s.member.toUpperCase()} (${s.role}) · ${s.status} · engine:${s.engine}`,
|
|
11699
|
+
`| Found: ${s.latest_update}`,
|
|
11700
|
+
`| Push: ${s.next_push}`,
|
|
11701
|
+
`+-----------------------------------------------------------------------+`,
|
|
11702
|
+
]);
|
|
11703
|
+
}
|
|
11704
|
+
|
|
11667
11705
|
async function memberRoom(...args) {
|
|
11668
11706
|
if (hasFlag(args, '--help') || hasFlag(args, '-h')) {
|
|
11669
11707
|
console.log('Usage: atris member [day|standup|room] [--walk] [--limit N] [--decisions "y|idea|n"] [--json]');
|
|
@@ -11680,13 +11718,7 @@ async function memberRoom(...args) {
|
|
|
11680
11718
|
? `Your team runs on ${teamEngines.length > 1 ? `${teamEngines.slice(0, -1).join(', ')} and ${teamEngines[teamEngines.length - 1]}` : teamEngines[0]}.`
|
|
11681
11719
|
: 'Your team picks an engine for each job.';
|
|
11682
11720
|
|
|
11683
|
-
const standupRows = room.standup
|
|
11684
|
-
`+-----------------------------------------------------------------------+`,
|
|
11685
|
-
`| ${idx + 1}. ${s.member.toUpperCase()} (${s.role}) · ${s.status} · engine:${s.engine}`,
|
|
11686
|
-
`| Found: ${s.latest_update}`,
|
|
11687
|
-
`| Push: ${s.next_push}`,
|
|
11688
|
-
`+-----------------------------------------------------------------------+`,
|
|
11689
|
-
]);
|
|
11721
|
+
const standupRows = memberStandupLines(room.standup, standupTableData(room.standup));
|
|
11690
11722
|
|
|
11691
11723
|
const handoffRows = room.handoffs.length
|
|
11692
11724
|
? room.handoffs.map((h) => ` - ${h.from} -> ${h.to}: ${h.summary} (run: ${h.next_command})`)
|
|
@@ -12019,6 +12051,8 @@ async function memberCommand(subcommand, ...args) {
|
|
|
12019
12051
|
|
|
12020
12052
|
module.exports = {
|
|
12021
12053
|
logDateForScan,
|
|
12054
|
+
memberStandupLines,
|
|
12055
|
+
standupTableData,
|
|
12022
12056
|
memberRunDefaultWallSeconds,
|
|
12023
12057
|
memberRunWantsReferee,
|
|
12024
12058
|
proposalPromptForGoal,
|
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
|
}
|