atris 3.56.0 → 3.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atris/skills/engines/SKILL.md +11 -3
- package/atris/skills/x-search/SKILL.md +20 -1
- package/atris/skills/youtube/SKILL.md +51 -43
- package/bin/atris.js +119 -87
- package/commands/autopilot-front.js +29 -13
- package/commands/brainstorm.js +77 -476
- package/commands/business.js +13 -1
- package/commands/fleet-report.js +2 -2
- package/commands/human-missions.js +26 -2
- package/commands/init.js +2 -35
- package/commands/integrations.js +100 -0
- package/commands/land.js +53 -23
- package/commands/later.js +52 -0
- package/commands/log.js +79 -30
- package/commands/mission.js +117 -22
- package/commands/next.js +153 -63
- package/commands/now.js +80 -38
- package/commands/recap.js +66 -4
- package/commands/run-front.js +20 -5
- package/commands/spaceship.js +52 -12
- package/commands/status.js +30 -7
- package/commands/task.js +56 -19
- package/commands/terminal.js +5 -5
- package/commands/workflow.js +18 -5
- package/commands/x-search.js +123 -13
- package/commands/youtube.js +639 -36
- package/lib/account-bound.js +7 -0
- package/lib/apply-gate.js +94 -0
- package/lib/context-gatherer.js +55 -8
- package/lib/engine-ask.js +16 -6
- package/lib/first-minute.js +552 -26
- package/lib/known-commands.js +1 -1
- package/lib/runner-command.js +5 -3
- package/lib/scratch-root.js +44 -0
- package/package.json +1 -1
- package/utils/auth.js +9 -0
package/commands/business.js
CHANGED
|
@@ -3277,11 +3277,14 @@ async function deployBusiness(slug) {
|
|
|
3277
3277
|
// Upload workspace files
|
|
3278
3278
|
const workspaceDir = path.join(bizDir, 'workspace');
|
|
3279
3279
|
let uploadCount = 0;
|
|
3280
|
+
let uploadFailureCount = 0;
|
|
3281
|
+
let uploadTotal = 0;
|
|
3280
3282
|
if (fs.existsSync(workspaceDir)) {
|
|
3281
3283
|
const files = walkDir(workspaceDir);
|
|
3282
3284
|
for (const filePath of files) {
|
|
3283
3285
|
const relativePath = path.relative(workspaceDir, filePath);
|
|
3284
3286
|
if (relativePath.startsWith('.')) continue;
|
|
3287
|
+
uploadTotal++;
|
|
3285
3288
|
try {
|
|
3286
3289
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
3287
3290
|
const uploadResult = await apiRequestJson(
|
|
@@ -3291,9 +3294,14 @@ async function deployBusiness(slug) {
|
|
|
3291
3294
|
if (uploadResult.ok) {
|
|
3292
3295
|
uploadCount++;
|
|
3293
3296
|
process.stdout.write(` Uploaded: ${relativePath}\n`);
|
|
3297
|
+
} else {
|
|
3298
|
+
uploadFailureCount++;
|
|
3299
|
+
const detail = uploadResult.errorMessage || uploadResult.error || uploadResult.status || 'unknown error';
|
|
3300
|
+
console.log(` Failed: ${relativePath} (${detail})`);
|
|
3294
3301
|
}
|
|
3295
3302
|
} catch (e) {
|
|
3296
|
-
|
|
3303
|
+
uploadFailureCount++;
|
|
3304
|
+
console.log(` Failed: ${relativePath} (${e.message || String(e)})`);
|
|
3297
3305
|
}
|
|
3298
3306
|
}
|
|
3299
3307
|
}
|
|
@@ -3314,6 +3322,10 @@ async function deployBusiness(slug) {
|
|
|
3314
3322
|
|
|
3315
3323
|
console.log(`\n Deployed ${uploadCount} files to ${bizConfig.name}`);
|
|
3316
3324
|
console.log(` Dashboard: https://atris.ai/dashboard/gm/${bizConfig.business_id}`);
|
|
3325
|
+
if (uploadFailureCount > 0) {
|
|
3326
|
+
console.log(` ${uploadFailureCount} of ${uploadTotal} files failed to upload`);
|
|
3327
|
+
process.exitCode = 1;
|
|
3328
|
+
}
|
|
3317
3329
|
console.log('');
|
|
3318
3330
|
}
|
|
3319
3331
|
|
package/commands/fleet-report.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
const { loadCredentials } = require('../utils/auth');
|
|
17
17
|
const { apiRequestJson } = require('../utils/api');
|
|
18
|
-
const { requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
18
|
+
const { looksLikeBusinessSlug, requireAccountBound, refuseAccountGlobal } = require('../lib/account-bound');
|
|
19
19
|
const { argsWantHelp, wantsJson } = require('../lib/noninteractive');
|
|
20
20
|
|
|
21
21
|
function sleep(ms) {
|
|
@@ -181,7 +181,7 @@ async function fleetReport() {
|
|
|
181
181
|
const wake = gate.args.includes('--wake');
|
|
182
182
|
const dryRun = gate.args.includes('--dry-run');
|
|
183
183
|
const board = gate.args.includes('--leaderboard');
|
|
184
|
-
const slug = gate.args.find((a) =>
|
|
184
|
+
const slug = gate.args.find((a) => looksLikeBusinessSlug(a));
|
|
185
185
|
|
|
186
186
|
if (!allAlive && !slug && !board) {
|
|
187
187
|
console.log(usage);
|
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
const { apiRequestJson } = require('../utils/api');
|
|
15
15
|
const { decodeJwtClaims, loadCredentials } = require('../utils/auth');
|
|
16
16
|
const { isHelpToken } = require('../lib/noninteractive');
|
|
17
|
-
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
17
|
+
const { isFreshWorkspace, speakFirstMinute, speakNothingRunning } = require('../lib/first-minute');
|
|
18
18
|
|
|
19
19
|
const PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
|
|
20
20
|
const HUMAN_STATES = Object.freeze({
|
|
@@ -168,7 +168,8 @@ function parseMissionId(args = []) {
|
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
170
|
* Scratch / unbound folders must not drive account-current cloud work.
|
|
171
|
-
*
|
|
171
|
+
* Empty-folder stop talks first-talk instead. After a room exists,
|
|
172
|
+
* require an explicit mission/task id: atris stop --mission <id>
|
|
172
173
|
*/
|
|
173
174
|
function refuseUnboundCloudComputer(args = [], options = {}, commandName = 'stop') {
|
|
174
175
|
const root = options.root || process.cwd();
|
|
@@ -634,6 +635,15 @@ async function currentMissionCommand(args = [], options = {}) {
|
|
|
634
635
|
outputCard(card, asJson, options.log || console.log);
|
|
635
636
|
return 0;
|
|
636
637
|
} catch (caught) {
|
|
638
|
+
if (options.fallbackOnMissing) {
|
|
639
|
+
const code = speakFirstMinute({
|
|
640
|
+
root: options.root || process.cwd(),
|
|
641
|
+
asJson,
|
|
642
|
+
log: options.log || console.log,
|
|
643
|
+
});
|
|
644
|
+
if (options.setProcessExitCode !== false) process.exitCode = code;
|
|
645
|
+
return code;
|
|
646
|
+
}
|
|
637
647
|
return reportError(caught, 'show the current mission', asJson, options);
|
|
638
648
|
}
|
|
639
649
|
}
|
|
@@ -734,6 +744,20 @@ async function stopCommand(args, options = {}) {
|
|
|
734
744
|
(options.log || console.log)('Usage: atris stop [--mission <id>] [--json]');
|
|
735
745
|
return 0;
|
|
736
746
|
}
|
|
747
|
+
const root = options.root || process.cwd();
|
|
748
|
+
const asJson = args.includes('--json');
|
|
749
|
+
// Fresh folder: empty talks first-talk. A file already here
|
|
750
|
+
// names that file, same as bare atris. Do not mint a room.
|
|
751
|
+
if (isFreshWorkspace(root) && !parseMissionId(args)) {
|
|
752
|
+
const unbound = refuseUnboundCloudComputer(args, options, 'stop');
|
|
753
|
+
if (unbound) {
|
|
754
|
+
return speakNothingRunning({
|
|
755
|
+
root,
|
|
756
|
+
asJson,
|
|
757
|
+
log: options.log || console.log,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
737
761
|
return changeCurrentMission('stop', {}, args, options);
|
|
738
762
|
}
|
|
739
763
|
|
package/commands/init.js
CHANGED
|
@@ -468,41 +468,8 @@ function initAtris() {
|
|
|
468
468
|
}
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
if (!fs.existsSync(intuitionFile)) {
|
|
474
|
-
fs.writeFileSync(intuitionFile, `# INTUITION.md
|
|
475
|
-
|
|
476
|
-
> Accumulated learnings. Read before major decisions. Update after discoveries.
|
|
477
|
-
|
|
478
|
-
---
|
|
479
|
-
|
|
480
|
-
## Tripwires
|
|
481
|
-
|
|
482
|
-
*Things that seem obvious but break unexpectedly. Check these first when debugging.*
|
|
483
|
-
|
|
484
|
-
- (none yet — add when you hit surprising failures)
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## Preferences
|
|
489
|
-
|
|
490
|
-
*Patterns this codebase prefers. Follow these over generic best practices.*
|
|
491
|
-
|
|
492
|
-
- (none yet — add as you discover the codebase style)
|
|
493
|
-
|
|
494
|
-
---
|
|
495
|
-
|
|
496
|
-
## Dead Ends
|
|
497
|
-
|
|
498
|
-
*Approaches tried and abandoned. Don't retry without new information.*
|
|
499
|
-
|
|
500
|
-
- (none yet — log failed approaches so future agents skip them)
|
|
501
|
-
|
|
502
|
-
---
|
|
503
|
-
`);
|
|
504
|
-
markReady('workspace', 'INTUITION.md', '✓ Created INTUITION.md');
|
|
505
|
-
}
|
|
471
|
+
// INTUITION.md was retired into lessons.md. Do not write a parallel lesson file.
|
|
472
|
+
// Leave a user's existing INTUITION.md alone if one is already on disk.
|
|
506
473
|
|
|
507
474
|
// Create lessons.md (feedback loop for learning across features)
|
|
508
475
|
const lessonsFile = path.join(targetDir, 'lessons.md');
|
package/commands/integrations.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* atris gmail inbox [--account <id>] - List recent emails for a mailbox
|
|
6
6
|
* atris gmail read <id> [--account <id>] - Read specific email
|
|
7
7
|
* atris gmail archive <id> [...] [--account <id>] - Archive messages
|
|
8
|
+
* atris gmail verdicts [--account <id>] [--limit N] - List recent archive verdicts
|
|
8
9
|
* atris gmail send <to> <subject> <body...> [--body-file <path>] [--account <id>] - Send an email
|
|
9
10
|
* atris gmail voice [account] [--clear] - Edit an account's writing voice
|
|
10
11
|
* atris gmail connect [name] - Connect or reconnect a Gmail account
|
|
@@ -39,6 +40,77 @@ const GMAIL_CONNECT_POLL_MS = 3000;
|
|
|
39
40
|
const GMAIL_CONNECT_TIMEOUT_MS = 3 * 60 * 1000;
|
|
40
41
|
const GMAIL_SEND_USAGE = 'usage: atris gmail send <to> <subject> <body...> [--body-file <path>] [--account <id>]';
|
|
41
42
|
const GMAIL_VOICE_USAGE = 'usage: atris gmail voice [account] [--clear]';
|
|
43
|
+
const GMAIL_VERDICTS_USAGE = 'usage: atris gmail verdicts [--account <id>] [--limit N]';
|
|
44
|
+
|
|
45
|
+
function gmailVerdictsPath(root = process.cwd()) {
|
|
46
|
+
return path.join(root, '.atris', 'state', 'gmail-verdicts.jsonl');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function appendGmailVerdicts(verdicts, options = {}) {
|
|
50
|
+
const rows = Array.isArray(verdicts) ? verdicts : [];
|
|
51
|
+
if (!rows.length) return;
|
|
52
|
+
const filePath = options.filePath || gmailVerdictsPath(options.root);
|
|
53
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
54
|
+
const lines = rows.map((row) => JSON.stringify({
|
|
55
|
+
ts: row.ts || new Date().toISOString(),
|
|
56
|
+
account: String(row.account || ''),
|
|
57
|
+
verdict: 'archive',
|
|
58
|
+
message_id: String(row.message_id || ''),
|
|
59
|
+
...(row.from ? { from: row.from } : {}),
|
|
60
|
+
...(row.subject ? { subject: row.subject } : {}),
|
|
61
|
+
}));
|
|
62
|
+
fs.appendFileSync(filePath, `${lines.join('\n')}\n`, 'utf8');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readGmailVerdicts(options = {}) {
|
|
66
|
+
const filePath = options.filePath || gmailVerdictsPath(options.root);
|
|
67
|
+
let lines;
|
|
68
|
+
try {
|
|
69
|
+
lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.code === 'ENOENT') return [];
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
const account = String(options.account || '').trim();
|
|
75
|
+
const limit = Number.isInteger(options.limit) ? options.limit : 20;
|
|
76
|
+
return lines.reduce((rows, line) => {
|
|
77
|
+
try {
|
|
78
|
+
const row = JSON.parse(line);
|
|
79
|
+
if (!account || row.account === account) rows.push(row);
|
|
80
|
+
} catch {}
|
|
81
|
+
return rows;
|
|
82
|
+
}, []).reverse().slice(0, limit);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function printGmailVerdicts(options = {}) {
|
|
86
|
+
const rows = readGmailVerdicts(options);
|
|
87
|
+
if (!rows.length) {
|
|
88
|
+
console.log('no gmail verdicts found.');
|
|
89
|
+
return rows;
|
|
90
|
+
}
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
const details = [row.from && `from ${row.from}`, row.subject && `subject ${row.subject}`].filter(Boolean);
|
|
93
|
+
console.log(`${row.ts} archive ${row.message_id} account ${row.account}${details.length ? `, ${details.join(', ')}` : ''}`);
|
|
94
|
+
}
|
|
95
|
+
return rows;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseGmailVerdictsArgs(args = []) {
|
|
99
|
+
let account = null;
|
|
100
|
+
let limit = 20;
|
|
101
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
102
|
+
const arg = args[i];
|
|
103
|
+
const value = String(args[i + 1] || '').trim();
|
|
104
|
+
if (arg === '--account' && value && !value.startsWith('--')) account = value;
|
|
105
|
+
else if (arg === '--limit' && /^\d+$/.test(value) && Number(value) > 0) limit = Number(value);
|
|
106
|
+
else {
|
|
107
|
+
console.error(GMAIL_VERDICTS_USAGE);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
i += 1;
|
|
111
|
+
}
|
|
112
|
+
return { account, limit };
|
|
113
|
+
}
|
|
42
114
|
|
|
43
115
|
function gmailAccountStatePath() {
|
|
44
116
|
return process.env.ATRIS_GMAIL_ACCOUNT_FILE
|
|
@@ -312,6 +384,24 @@ async function gmailArchive(messageIds, options = {}) {
|
|
|
312
384
|
}
|
|
313
385
|
|
|
314
386
|
const archived = result.data?.archived ?? result.data?.count ?? ids.length;
|
|
387
|
+
const responseMessages = [
|
|
388
|
+
result.data?.messages,
|
|
389
|
+
result.data?.archived_messages,
|
|
390
|
+
result.data?.results,
|
|
391
|
+
].find(Array.isArray) || [];
|
|
392
|
+
const metadata = new Map(responseMessages.map((message) => [
|
|
393
|
+
String(message.id || message.message_id || ''),
|
|
394
|
+
message,
|
|
395
|
+
]));
|
|
396
|
+
appendGmailVerdicts(ids.map((messageId) => {
|
|
397
|
+
const message = metadata.get(messageId) || {};
|
|
398
|
+
return {
|
|
399
|
+
account: accountId,
|
|
400
|
+
message_id: messageId,
|
|
401
|
+
from: message.from || message.sender || undefined,
|
|
402
|
+
subject: message.subject || undefined,
|
|
403
|
+
};
|
|
404
|
+
}));
|
|
315
405
|
console.log(`Archived ${archived} message${archived === 1 ? '' : 's'}. They stay searchable in All Mail.`);
|
|
316
406
|
}
|
|
317
407
|
|
|
@@ -610,6 +700,11 @@ async function gmailCommand(subcommand, ...args) {
|
|
|
610
700
|
await gmailArchive(parsed.positional, { accountId: parsed.accountId || undefined });
|
|
611
701
|
break;
|
|
612
702
|
}
|
|
703
|
+
case 'verdicts': {
|
|
704
|
+
const parsed = parseGmailVerdictsArgs(args);
|
|
705
|
+
printGmailVerdicts(parsed);
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
613
708
|
case 'send': {
|
|
614
709
|
const parsed = parseGmailSendArgs(args);
|
|
615
710
|
await gmailSend(parsed.to, parsed.subject, parsed.body, { accountId: parsed.accountId || undefined });
|
|
@@ -634,6 +729,7 @@ async function gmailCommand(subcommand, ...args) {
|
|
|
634
729
|
console.log(' atris gmail inbox [--account <id>] - list recent emails for a mailbox');
|
|
635
730
|
console.log(' atris gmail read <id> [--account <id>] - read specific email');
|
|
636
731
|
console.log(' atris gmail archive <id> [...] [--account <id>] - archive messages (reversible, all mail keeps them)');
|
|
732
|
+
console.log(' atris gmail verdicts [--account <id>] [--limit N] - list recent archive verdicts');
|
|
637
733
|
console.log(' atris gmail send <to> <subject> <body...> [--body-file <path>] [--account <id>] - send an email');
|
|
638
734
|
console.log(' atris gmail voice [account] [--clear] - edit or clear an account writing voice');
|
|
639
735
|
console.log(' atris gmail connect [name] - connect or reconnect a gmail account');
|
|
@@ -2153,6 +2249,10 @@ module.exports = {
|
|
|
2153
2249
|
extractGmailMailboxAccount,
|
|
2154
2250
|
parseGmailArgs,
|
|
2155
2251
|
gmailAccountStatePath,
|
|
2252
|
+
gmailVerdictsPath,
|
|
2253
|
+
appendGmailVerdicts,
|
|
2254
|
+
readGmailVerdicts,
|
|
2255
|
+
printGmailVerdicts,
|
|
2156
2256
|
calendarCommand,
|
|
2157
2257
|
twitterCommand,
|
|
2158
2258
|
slackCommand,
|
package/commands/land.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { runGit } = require('../lib/git-spawn');
|
|
4
|
+
const { compactErrorPayload, printCliJson } = require('../lib/cli-json');
|
|
4
5
|
const { listWorktrees, statusCounts } = require('./worktree');
|
|
5
6
|
|
|
6
7
|
const DEFAULT_TTL_DAYS = 7;
|
|
@@ -236,14 +237,18 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
|
|
|
236
237
|
// merged branch residue.
|
|
237
238
|
function landSummary(cwd = process.cwd(), ttlDays = DEFAULT_TTL_DAYS) {
|
|
238
239
|
const root = repoRoot(cwd);
|
|
239
|
-
if (!root) return null;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
240
|
+
if (!root || !hasCommits(root)) return null;
|
|
241
|
+
try {
|
|
242
|
+
const board = collectBoard(root, { ttlDays, light: true });
|
|
243
|
+
return {
|
|
244
|
+
branches: board.summary.active + board.summary.due,
|
|
245
|
+
due: board.summary.due,
|
|
246
|
+
stale: board.summary.stale,
|
|
247
|
+
ttlDays,
|
|
248
|
+
};
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
247
252
|
}
|
|
248
253
|
|
|
249
254
|
function salvageDir(root) {
|
|
@@ -636,6 +641,40 @@ function readFollowingFlag(args, name, fallback = '') {
|
|
|
636
641
|
return args[idx + 1] || fallback;
|
|
637
642
|
}
|
|
638
643
|
|
|
644
|
+
const EMPTY_LAND = {
|
|
645
|
+
not_a_git_repo: 'this folder is not a git repo yet, so there is nothing to land.',
|
|
646
|
+
no_commits: 'nothing is in the air yet because this folder has no commits.',
|
|
647
|
+
no_base: 'no master or main branch yet, so there is nothing to land.',
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
function emptyLandKindFromError(err) {
|
|
651
|
+
if (err && err.code === 'LAND_NO_BASE') return 'no_base';
|
|
652
|
+
const msg = String(err && err.message || err || '');
|
|
653
|
+
if (/not a git/i.test(msg)) return 'not_a_git_repo';
|
|
654
|
+
if (/no commits/i.test(msg)) return 'no_commits';
|
|
655
|
+
if (/no master\/main/i.test(msg)) return 'no_base';
|
|
656
|
+
return '';
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function printEmptyLand(kind, json) {
|
|
660
|
+
const reason = EMPTY_LAND[kind] ? kind : 'no_base';
|
|
661
|
+
const detail = EMPTY_LAND[reason];
|
|
662
|
+
if (json) {
|
|
663
|
+
const payload = compactErrorPayload({ reason, detail });
|
|
664
|
+
printCliJson(payload, payload, ['--json']);
|
|
665
|
+
} else {
|
|
666
|
+
console.log(detail);
|
|
667
|
+
}
|
|
668
|
+
return 2;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function finishLandError(err, json) {
|
|
672
|
+
const kind = emptyLandKindFromError(err);
|
|
673
|
+
if (kind) return printEmptyLand(kind, json);
|
|
674
|
+
console.error(String(err && err.message || err).split('\n')[0]);
|
|
675
|
+
return 2;
|
|
676
|
+
}
|
|
677
|
+
|
|
639
678
|
function showHelp() {
|
|
640
679
|
console.log('');
|
|
641
680
|
console.log('atris land: the landing, what is actually done vs still in the air');
|
|
@@ -666,20 +705,14 @@ function landCommand(args = []) {
|
|
|
666
705
|
showHelp();
|
|
667
706
|
return 0;
|
|
668
707
|
}
|
|
708
|
+
const json = args.includes('--json');
|
|
669
709
|
const root = repoRoot();
|
|
670
|
-
if (!root)
|
|
671
|
-
|
|
672
|
-
return 1;
|
|
673
|
-
}
|
|
674
|
-
if (!hasCommits(root)) {
|
|
675
|
-
console.error('no commits yet. fix: git commit --allow-empty -m init');
|
|
676
|
-
return 1;
|
|
677
|
-
}
|
|
710
|
+
if (!root) return printEmptyLand('not_a_git_repo', json);
|
|
711
|
+
if (!hasCommits(root)) return printEmptyLand('no_commits', json);
|
|
678
712
|
const ttlRaw = readFollowingFlag(args, '--ttl', '');
|
|
679
713
|
const ttlParsed = Number(ttlRaw);
|
|
680
714
|
const ttlDays = ttlRaw !== '' && Number.isFinite(ttlParsed) && ttlParsed >= 0 ? ttlParsed : DEFAULT_TTL_DAYS;
|
|
681
715
|
const base = readFollowingFlag(args, '--base', '');
|
|
682
|
-
const json = args.includes('--json');
|
|
683
716
|
|
|
684
717
|
if (args.includes('--reap')) {
|
|
685
718
|
try {
|
|
@@ -688,8 +721,7 @@ function landCommand(args = []) {
|
|
|
688
721
|
else printReceipt(receipt);
|
|
689
722
|
return 0;
|
|
690
723
|
} catch (err) {
|
|
691
|
-
|
|
692
|
-
return 1;
|
|
724
|
+
return finishLandError(err, json);
|
|
693
725
|
}
|
|
694
726
|
}
|
|
695
727
|
|
|
@@ -706,8 +738,7 @@ function landCommand(args = []) {
|
|
|
706
738
|
else printStory(story);
|
|
707
739
|
return 0;
|
|
708
740
|
} catch (err) {
|
|
709
|
-
|
|
710
|
-
return 1;
|
|
741
|
+
return finishLandError(err, json);
|
|
711
742
|
}
|
|
712
743
|
}
|
|
713
744
|
|
|
@@ -717,8 +748,7 @@ function landCommand(args = []) {
|
|
|
717
748
|
else printBoard(board);
|
|
718
749
|
return 0;
|
|
719
750
|
} catch (err) {
|
|
720
|
-
|
|
721
|
-
return 1;
|
|
751
|
+
return finishLandError(err, json);
|
|
722
752
|
}
|
|
723
753
|
}
|
|
724
754
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { argsWantHelp } = require('../lib/noninteractive');
|
|
4
|
+
const {
|
|
5
|
+
laterNotePath,
|
|
6
|
+
listUserVisibleWork,
|
|
7
|
+
personName,
|
|
8
|
+
renderLaterRemember,
|
|
9
|
+
spokenLaterNote,
|
|
10
|
+
writeLaterNote,
|
|
11
|
+
} = require('../lib/first-minute');
|
|
12
|
+
|
|
13
|
+
function laterUsage() {
|
|
14
|
+
return 'Usage: atris later "<sentence>"';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function laterSentence(args = []) {
|
|
18
|
+
return spokenLaterNote((Array.isArray(args) ? args : [])
|
|
19
|
+
.filter((arg) => !String(arg).startsWith('-'))
|
|
20
|
+
.join(' '));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function laterAtris(args = process.argv.slice(3), {
|
|
24
|
+
root = process.cwd(),
|
|
25
|
+
log = console.log,
|
|
26
|
+
} = {}) {
|
|
27
|
+
const list = Array.isArray(args) ? args : [];
|
|
28
|
+
if (argsWantHelp(list)) {
|
|
29
|
+
log(laterUsage());
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const sentence = laterSentence(list);
|
|
33
|
+
if (!sentence) {
|
|
34
|
+
log(laterUsage());
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
writeLaterNote(root, sentence);
|
|
38
|
+
const files = listUserVisibleWork(root);
|
|
39
|
+
log('');
|
|
40
|
+
log(renderLaterRemember({
|
|
41
|
+
person: personName(),
|
|
42
|
+
sentence,
|
|
43
|
+
files,
|
|
44
|
+
root,
|
|
45
|
+
}));
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
laterAtris,
|
|
51
|
+
laterNotePath,
|
|
52
|
+
};
|
package/commands/log.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
const { getLogPath, ensureLogDirectory, createLogFile, addInboxIdea } = require('../lib/file-ops');
|
|
5
|
+
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
5
6
|
const { isForcedNonInteractive } = require('../lib/noninteractive');
|
|
6
7
|
|
|
7
8
|
function readPipedStdin() {
|
|
@@ -12,42 +13,89 @@ function readPipedStdin() {
|
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function
|
|
16
|
-
|
|
16
|
+
function journalRel(logFile) {
|
|
17
|
+
return (path.relative(process.cwd(), logFile) || logFile).split(path.sep).join('/');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printLogJson(payload) {
|
|
21
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
22
|
+
}
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
function printReplNeeded(asJson, dateFormatted, rel) {
|
|
25
|
+
const error = `Daily log REPL needs a terminal (${dateFormatted}).`;
|
|
26
|
+
if (asJson) {
|
|
27
|
+
printLogJson({
|
|
28
|
+
ok: false,
|
|
29
|
+
error,
|
|
30
|
+
journal: rel,
|
|
31
|
+
next_command: 'atris log "note"',
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
21
34
|
}
|
|
35
|
+
console.log(error);
|
|
36
|
+
console.log(`journal: ${rel}`);
|
|
37
|
+
console.log('Next: atris log --repl # in a terminal, or atris log "note" / atris wish --json --no-mission');
|
|
38
|
+
}
|
|
22
39
|
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
function captureNotes(logFile, notes) {
|
|
41
|
+
return notes.map((note) => addInboxIdea(logFile, note));
|
|
42
|
+
}
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
function printCapture(asJson, ids, notes, rel) {
|
|
45
|
+
if (asJson) {
|
|
46
|
+
const payload = {
|
|
47
|
+
ok: true,
|
|
48
|
+
action: 'inbox_capture',
|
|
49
|
+
journal: rel,
|
|
50
|
+
next_command: 'atris logs',
|
|
51
|
+
};
|
|
52
|
+
if (notes.length === 1) {
|
|
53
|
+
payload.id = `I${ids[0]}`;
|
|
54
|
+
payload.note = notes[0];
|
|
55
|
+
} else {
|
|
56
|
+
payload.count = notes.length;
|
|
57
|
+
payload.ids = ids.map((id) => `I${id}`);
|
|
58
|
+
payload.notes = notes;
|
|
59
|
+
}
|
|
60
|
+
printLogJson(payload);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (notes.length === 1) {
|
|
64
|
+
console.log(`captured I${ids[0]}: ${notes[0]}`);
|
|
65
|
+
console.log(`journal: ${rel}`);
|
|
66
|
+
console.log('Next: atris logs');
|
|
67
|
+
return;
|
|
28
68
|
}
|
|
69
|
+
console.log(`captured ${notes.length} note${notes.length === 1 ? '' : 's'} in ${rel}`);
|
|
70
|
+
}
|
|
29
71
|
|
|
72
|
+
function logAtris() {
|
|
73
|
+
const root = process.cwd();
|
|
30
74
|
const args = process.argv.slice(3);
|
|
31
|
-
const
|
|
75
|
+
const asJson = args.includes('--json');
|
|
32
76
|
const forced = isForcedNonInteractive(args);
|
|
33
77
|
const wantsRepl = args.includes('--repl');
|
|
34
78
|
const positional = args.filter((arg) => !String(arg).startsWith('-'));
|
|
35
79
|
const note = positional.join(' ').trim();
|
|
36
80
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
console.log(`journal: ${rel}`);
|
|
42
|
-
console.log('Next: atris logs');
|
|
43
|
-
return;
|
|
81
|
+
// Empty folder talks like bare atris. Do not create atris/ or write
|
|
82
|
+
// a journal. After init, a note still captures.
|
|
83
|
+
if (isFreshWorkspace(root)) {
|
|
84
|
+
process.exit(speakFirstMinute({ root, fresh: true, asJson }));
|
|
44
85
|
}
|
|
45
86
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
87
|
+
ensureLogDirectory();
|
|
88
|
+
const { logFile, dateFormatted } = getLogPath();
|
|
89
|
+
|
|
90
|
+
if (!fs.existsSync(logFile)) {
|
|
91
|
+
createLogFile(logFile, dateFormatted);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const rel = journalRel(logFile);
|
|
95
|
+
|
|
96
|
+
// `atris log "sentence"` (or one slug-like word) appends to today's Inbox.
|
|
97
|
+
if (note && !wantsRepl) {
|
|
98
|
+
printCapture(asJson, captureNotes(logFile, [note]), [note], rel);
|
|
51
99
|
return;
|
|
52
100
|
}
|
|
53
101
|
|
|
@@ -56,16 +104,17 @@ function logAtris() {
|
|
|
56
104
|
const piped = readPipedStdin();
|
|
57
105
|
const lines = piped.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
58
106
|
const notes = lines.filter((line) => line.toLowerCase() !== 'exit');
|
|
59
|
-
if (notes.length
|
|
60
|
-
|
|
61
|
-
console.log(`journal: ${rel}`);
|
|
62
|
-
console.log('Next: atris log --repl # in a terminal, or atris log "note" / atris logs --json');
|
|
107
|
+
if (notes.length > 0 && !wantsRepl) {
|
|
108
|
+
printCapture(asJson, captureNotes(logFile, notes), notes, rel);
|
|
63
109
|
return;
|
|
64
110
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
111
|
+
printReplNeeded(asJson, dateFormatted, rel);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Forced headless: never open a REPL.
|
|
116
|
+
if (forced) {
|
|
117
|
+
printReplNeeded(asJson, dateFormatted, rel);
|
|
69
118
|
return;
|
|
70
119
|
}
|
|
71
120
|
|