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.
@@ -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
- // Skip binary files or errors
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
 
@@ -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) => !a.startsWith('--'));
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
- * Require an explicit mission/task id: atris stop --mission <id>
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
- // Create INTUITION.md - captures learnings, preferences, dead ends
472
- const intuitionFile = path.join(targetDir, 'INTUITION.md');
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');
@@ -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
- const board = collectBoard(root, { ttlDays, light: true });
241
- return {
242
- branches: board.summary.active + board.summary.due,
243
- due: board.summary.due,
244
- stale: board.summary.stale,
245
- ttlDays,
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
- console.error('not a git repository. fix: git init -b main');
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
- console.error(String(err && err.message || err));
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
- console.error(String(err && err.message || err));
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
- console.error(String(err && err.message || err));
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 logAtris() {
16
- const targetDir = path.join(process.cwd(), 'atris');
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
- if (!fs.existsSync(targetDir)) {
19
- console.error('✗ Error: atris/ folder not found. Run "atris init" first.');
20
- process.exit(1);
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
- ensureLogDirectory();
24
- const { logFile, dateFormatted } = getLogPath();
40
+ function captureNotes(logFile, notes) {
41
+ return notes.map((note) => addInboxIdea(logFile, note));
42
+ }
25
43
 
26
- if (!fs.existsSync(logFile)) {
27
- createLogFile(logFile, dateFormatted);
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 rel = path.relative(process.cwd(), logFile) || logFile;
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
- // `atris log "sentence"` (or multi-word args) appends to today's Inbox.
38
- if (note && !wantsRepl) {
39
- const id = addInboxIdea(logFile, note);
40
- console.log(`captured I${id}: ${note}`);
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
- // Forced headless: never open a REPL.
47
- if (forced) {
48
- console.log(`Daily log REPL needs a terminal (${dateFormatted}).`);
49
- console.log(`journal: ${rel}`);
50
- console.log('Next: atris log --repl # in a terminal, or atris log "note" / atris wish --json --no-mission');
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 === 0) {
60
- console.log(`Daily log REPL needs a terminal (${dateFormatted}).`);
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
- for (const noteLine of notes) {
66
- addInboxIdea(logFile, noteLine);
67
- }
68
- console.log(`captured ${notes.length} note${notes.length === 1 ? '' : 's'} in ${rel}`);
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