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
|
@@ -8,7 +8,10 @@ const fs = require('fs');
|
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { spawn } = require('child_process');
|
|
10
10
|
const { pickRunnableMission, runBudgetSeconds } = require('./run-front');
|
|
11
|
-
const {
|
|
11
|
+
const { wantsJson, hasYesFlag } = require('../lib/noninteractive');
|
|
12
|
+
const { isUnboundScratchFolder, refuseUnboundScratch } = require('../lib/scratch-root');
|
|
13
|
+
const { resolveWorkspaceRoot } = require('../lib/mission-root');
|
|
14
|
+
const { personName } = require('../lib/first-minute');
|
|
12
15
|
|
|
13
16
|
const CLI_PATH = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
14
17
|
const DEFAULT_LEG_WALL_SECONDS = 3600;
|
|
@@ -190,13 +193,23 @@ function autopilotStatus(root = process.cwd()) {
|
|
|
190
193
|
return 0;
|
|
191
194
|
}
|
|
192
195
|
|
|
196
|
+
function greet(person) {
|
|
197
|
+
return person ? `hey ${person}, ` : '';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function inviteLines() {
|
|
201
|
+
return [
|
|
202
|
+
`${greet(personName())}I can keep working until you stop.`,
|
|
203
|
+
'',
|
|
204
|
+
'next: atris autopilot --yes',
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
193
208
|
function showFrontHelp() {
|
|
194
209
|
console.log('');
|
|
195
210
|
console.log('Usage: atris autopilot [options]');
|
|
196
211
|
console.log('');
|
|
197
|
-
console.log('
|
|
198
|
-
console.log('drives it through the mission runtime, then picks the next one.');
|
|
199
|
-
console.log('Runs until you stop it.');
|
|
212
|
+
console.log('Keep working until you stop. Pass --yes to start.');
|
|
200
213
|
console.log('');
|
|
201
214
|
console.log('Options:');
|
|
202
215
|
console.log(' --minutes N | --hours N Total budget (default: unlimited)');
|
|
@@ -216,7 +229,7 @@ async function autopilotFront(args = []) {
|
|
|
216
229
|
// (e.g. backend/) wrote its loop state into a nested .atris, so a later
|
|
217
230
|
// `autopilot stop`/`status` from the root couldn't see the running loop.
|
|
218
231
|
// Same resolver the mission/task/usage stores use; falls back to cwd.
|
|
219
|
-
const root =
|
|
232
|
+
const root = resolveWorkspaceRoot(process.cwd());
|
|
220
233
|
if (args[0] === 'stop') return autopilotStop(root);
|
|
221
234
|
if (args[0] === 'status') return autopilotStatus(root);
|
|
222
235
|
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') { showFrontHelp(); return 0; }
|
|
@@ -232,14 +245,16 @@ async function autopilotFront(args = []) {
|
|
|
232
245
|
}, null, 2));
|
|
233
246
|
return 2;
|
|
234
247
|
}
|
|
235
|
-
// Bare
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
// Bare invoke used to start a loop. --auto is the proceed flag pulse
|
|
249
|
+
// and spaceship already pass. --once is a duration, not consent.
|
|
250
|
+
if (!args.includes('--auto') && !hasYesFlag(args)) {
|
|
251
|
+
for (const line of inviteLines()) console.log(line);
|
|
252
|
+
return 2;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// --yes / --auto start the loop. They are not a workspace unlock.
|
|
256
|
+
// An unbound scratch folder is not a room (same class as slack/gmail).
|
|
257
|
+
if (isUnboundScratchFolder(root)) return refuseUnboundScratch();
|
|
243
258
|
|
|
244
259
|
const existing = readState(root);
|
|
245
260
|
if (existing && pidAlive(existing.pid) && existing.pid !== process.pid) {
|
|
@@ -324,6 +339,7 @@ async function autopilotFront(args = []) {
|
|
|
324
339
|
|
|
325
340
|
module.exports = {
|
|
326
341
|
autopilotFront,
|
|
342
|
+
inviteLines,
|
|
327
343
|
maxLegsFlag,
|
|
328
344
|
stopRequested,
|
|
329
345
|
requestStop,
|
package/commands/brainstorm.js
CHANGED
|
@@ -1,504 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
const fs = require('fs');
|
|
2
4
|
const path = require('path');
|
|
3
|
-
const readline = require('readline');
|
|
4
5
|
const { getLogPath, ensureLogDirectory, createLogFile } = require('../lib/journal');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
replaceInboxSection,
|
|
10
|
-
addInboxItemToContent,
|
|
11
|
-
getNextInboxId,
|
|
12
|
-
addInboxIdea,
|
|
13
|
-
} = require('../lib/file-ops');
|
|
14
|
-
const { loadConfig } = require('../utils/config');
|
|
15
|
-
const { loadCredentials, ensureValidCredentials } = require('../utils/auth');
|
|
16
|
-
const { apiRequestJson } = require('../utils/api');
|
|
17
|
-
const { isNonInteractive } = require('../lib/noninteractive');
|
|
18
|
-
const { planAtris, doAtris, reviewAtris } = require('./workflow');
|
|
19
|
-
|
|
20
|
-
const pkg = require('../package.json');
|
|
21
|
-
|
|
22
|
-
async function brainstormAtris() {
|
|
23
|
-
const args = process.argv.slice(3);
|
|
24
|
-
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
25
|
-
console.log('');
|
|
26
|
-
console.log('Usage: atris brainstorm [idea] [--cloud]');
|
|
27
|
-
console.log('');
|
|
28
|
-
console.log('Description:');
|
|
29
|
-
console.log(' Guided prompt generator for exploration before planning.');
|
|
30
|
-
console.log(' Default is local-first; pass --cloud to include AtrisOS journal context.');
|
|
31
|
-
console.log(' Headless agents: pass the idea on the command line (never prompts).');
|
|
32
|
-
console.log('');
|
|
33
|
-
console.log('Options:');
|
|
34
|
-
console.log(' --cloud Include AtrisOS journal context (optional).');
|
|
35
|
-
console.log(' --no-cloud Force local-only mode (skip AtrisOS).');
|
|
36
|
-
console.log(' --yes Non-interactive: capture idea args and exit.');
|
|
37
|
-
console.log('');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const targetDir = path.join(process.cwd(), 'atris');
|
|
42
|
-
if (!fs.existsSync(targetDir)) {
|
|
43
|
-
throw new Error('atris/ folder not found. Run "atris init" first.');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
ensureLogDirectory();
|
|
47
|
-
const { logFile, dateFormatted } = getLogPath();
|
|
48
|
-
if (!fs.existsSync(logFile)) {
|
|
49
|
-
createLogFile(logFile, dateFormatted);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const useCloudJournal = args.includes('--cloud') && !args.includes('--no-cloud');
|
|
53
|
-
const topicFromArgs = args.filter((arg) => !arg.startsWith('-')).join(' ').trim() || null;
|
|
54
|
-
|
|
55
|
-
// Headless agents must never hang on "Describe the desired outcome".
|
|
56
|
-
if (isNonInteractive(args)) {
|
|
57
|
-
if (topicFromArgs) {
|
|
58
|
-
const newId = addInboxIdea(logFile, topicFromArgs);
|
|
59
|
-
console.log(`captured I${newId}: ${topicFromArgs}`);
|
|
60
|
-
console.log(`journal: ${path.relative(process.cwd(), logFile) || logFile}`);
|
|
61
|
-
console.log('Next: atris plan');
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
console.log('brainstorm is interactive without an idea on the command line.');
|
|
65
|
-
console.log(`journal: ${path.relative(process.cwd(), logFile) || logFile}`);
|
|
66
|
-
console.log('Next: atris brainstorm "<idea>"');
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
6
|
+
const { addInboxIdea } = require('../lib/file-ops');
|
|
7
|
+
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
8
|
+
const { wantsJson } = require('../lib/noninteractive');
|
|
9
|
+
const { compactErrorPayload, compactSuccessPayload, printCliJson } = require('../lib/cli-json');
|
|
69
10
|
|
|
11
|
+
function printHelp() {
|
|
70
12
|
console.log('');
|
|
71
|
-
console.log('
|
|
72
|
-
console.log('│ Atris Brainstorm — structured prompt generator │');
|
|
73
|
-
console.log('└─────────────────────────────────────────────────────────────┘');
|
|
13
|
+
console.log('Usage: atris brainstorm "<idea>" [--json]');
|
|
74
14
|
console.log('');
|
|
75
|
-
console.log(
|
|
76
|
-
console.log('
|
|
15
|
+
console.log('Description:');
|
|
16
|
+
console.log(' Capture an idea to today\'s inbox and exit.');
|
|
17
|
+
console.log(' Never waits on a prompt. Headless.');
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log('Options:');
|
|
20
|
+
console.log(' --json Print a JSON receipt.');
|
|
21
|
+
console.log(' --cloud Accepted. Capture stays local.');
|
|
22
|
+
console.log(' --no-cloud Force local-only mode.');
|
|
23
|
+
console.log(' --yes Accepted. Always non-interactive.');
|
|
77
24
|
console.log('');
|
|
78
|
-
|
|
79
|
-
// Local journal context (source of truth for Inbox)
|
|
80
|
-
let localJournalContext = '';
|
|
81
|
-
if (fs.existsSync(logFile)) {
|
|
82
|
-
localJournalContext = fs.readFileSync(logFile, 'utf8');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Optional: fetch journal context from backend (for hints only)
|
|
86
|
-
let remoteJournalContext = '';
|
|
87
|
-
const config = loadConfig();
|
|
88
|
-
const ensured1 = await ensureValidCredentials(apiRequestJson);
|
|
89
|
-
const credentials = ensured1.error ? null : ensured1.credentials;
|
|
90
|
-
|
|
91
|
-
if (useCloudJournal && config.agent_id && credentials && credentials.token) {
|
|
92
|
-
try {
|
|
93
|
-
console.log('📖 Fetching latest journal entry from AtrisOS...');
|
|
94
|
-
const journalResult = await apiRequestJson(`/agents/${config.agent_id}/journal/today`, {
|
|
95
|
-
method: 'GET',
|
|
96
|
-
token: credentials.token,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (journalResult.ok && journalResult.data?.content) {
|
|
100
|
-
remoteJournalContext = journalResult.data.content;
|
|
101
|
-
console.log('✓ Loaded journal entry from backend');
|
|
102
|
-
} else {
|
|
103
|
-
// Try fetching latest entry if today doesn't exist
|
|
104
|
-
const listResult = await apiRequestJson(`/agents/${config.agent_id}/journal/?limit=1`, {
|
|
105
|
-
method: 'GET',
|
|
106
|
-
token: credentials.token,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
if (listResult.ok && listResult.data?.entries?.length > 0) {
|
|
110
|
-
remoteJournalContext = listResult.data.entries[0].content || '';
|
|
111
|
-
console.log('✓ Loaded latest journal entry from backend');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// Silently fail - we'll use local log file instead
|
|
116
|
-
console.log('ℹ️ Using local journal file (backend unavailable)');
|
|
117
|
-
}
|
|
118
|
-
console.log('');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Keep prompts high-signal: only include "recent context" when explicitly pulled from cloud.
|
|
122
|
-
const journalHintSource = remoteJournalContext;
|
|
123
|
-
|
|
124
|
-
const rl = readline.createInterface({
|
|
125
|
-
input: process.stdin,
|
|
126
|
-
output: process.stdout,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const ask = async (promptText, options = {}) => {
|
|
130
|
-
const { allowEmpty = false } = options;
|
|
131
|
-
while (true) {
|
|
132
|
-
const answer = await new Promise((resolve) => rl.question(promptText, resolve));
|
|
133
|
-
const trimmed = answer.trim();
|
|
134
|
-
if (trimmed.toLowerCase() === 'exit') {
|
|
135
|
-
throw brainstormAbortError();
|
|
136
|
-
}
|
|
137
|
-
if (!allowEmpty && trimmed === '') {
|
|
138
|
-
console.log('Please enter a value (or type "exit" to abort).');
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
return trimmed;
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const askYesNo = async (promptText) => {
|
|
146
|
-
while (true) {
|
|
147
|
-
const response = (await ask(promptText)).toLowerCase();
|
|
148
|
-
if (response === 'y' || response === 'yes') return true;
|
|
149
|
-
if (response === 'n' || response === 'no') return false;
|
|
150
|
-
console.log('Please answer with "y" or "n" (or type "exit" to abort).');
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const collectList = async (label, options = {}) => {
|
|
155
|
-
const { minimum = 0 } = options;
|
|
156
|
-
const items = [];
|
|
157
|
-
while (true) {
|
|
158
|
-
const promptSuffix = items.length === 0 ? '' : ' (blank to finish)';
|
|
159
|
-
const value = await ask(`${label} ${items.length + 1}${promptSuffix}: `, {
|
|
160
|
-
allowEmpty: items.length >= minimum,
|
|
161
|
-
});
|
|
162
|
-
if (!value) {
|
|
163
|
-
if (items.length < minimum) {
|
|
164
|
-
console.log(`Please provide at least ${minimum} ${minimum === 1 ? 'item' : 'items'}.`);
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
items.push(value);
|
|
170
|
-
}
|
|
171
|
-
return items;
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
let selectedInboxItem = null;
|
|
175
|
-
let topicSummary = '';
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
let inboxItems = parseInboxItems(localJournalContext || '');
|
|
179
|
-
|
|
180
|
-
if (topicFromArgs) {
|
|
181
|
-
topicSummary = topicFromArgs;
|
|
182
|
-
const newId = addInboxIdea(logFile, topicSummary);
|
|
183
|
-
console.log(`✓ Added I${newId} to today\'s Inbox.`);
|
|
184
|
-
selectedInboxItem = { id: newId, text: topicSummary };
|
|
185
|
-
inboxItems = parseInboxItems(fs.readFileSync(logFile, 'utf8'));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (topicFromArgs) {
|
|
189
|
-
// Topic provided via CLI args — treat as a new brainstorm and skip source selection.
|
|
190
|
-
} else if (inboxItems.length > 0) {
|
|
191
|
-
console.log('Choose a brainstorm source:');
|
|
192
|
-
console.log(' 1. Select an item from today\'s Inbox');
|
|
193
|
-
console.log(' 2. Enter a new idea');
|
|
194
|
-
console.log('');
|
|
195
|
-
|
|
196
|
-
let choice;
|
|
197
|
-
while (true) {
|
|
198
|
-
choice = await ask('Choice (1-2): ');
|
|
199
|
-
if (choice === '1' || choice === '2') {
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
console.log('Please enter 1 or 2.');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (choice === '1') {
|
|
206
|
-
console.log('');
|
|
207
|
-
console.log('Today\'s Inbox:');
|
|
208
|
-
inboxItems.forEach((item, index) => {
|
|
209
|
-
console.log(` ${index + 1}. I${item.id} — ${item.text}`);
|
|
210
|
-
});
|
|
211
|
-
console.log('');
|
|
212
|
-
|
|
213
|
-
while (true) {
|
|
214
|
-
const selection = await ask(`Pick an item (1-${inboxItems.length}): `);
|
|
215
|
-
const index = parseInt(selection, 10);
|
|
216
|
-
if (!Number.isNaN(index) && index >= 1 && index <= inboxItems.length) {
|
|
217
|
-
selectedInboxItem = inboxItems[index - 1];
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
console.log(`Enter a number between 1 and ${inboxItems.length}.`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const editedSummary = await ask('Brainstorm topic (press Enter to keep original): ', { allowEmpty: true });
|
|
224
|
-
topicSummary = editedSummary ? editedSummary : selectedInboxItem.text;
|
|
225
|
-
} else {
|
|
226
|
-
console.log('');
|
|
227
|
-
topicSummary = await ask('Describe the brainstorm topic: ');
|
|
228
|
-
const newId = addInboxIdea(logFile, topicSummary);
|
|
229
|
-
console.log(`✓ Added I${newId} to today\'s Inbox.`);
|
|
230
|
-
selectedInboxItem = { id: newId, text: topicSummary };
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
console.log('No items in today\'s Inbox. Capture a new idea to begin.');
|
|
234
|
-
topicSummary = await ask('Describe the brainstorm topic: ');
|
|
235
|
-
const newId = addInboxIdea(logFile, topicSummary);
|
|
236
|
-
console.log(`✓ Added I${newId} to today\'s Inbox.`);
|
|
237
|
-
selectedInboxItem = { id: newId, text: topicSummary };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const sourceLabel = selectedInboxItem ? `I${selectedInboxItem.id}` : 'Ad-hoc';
|
|
241
|
-
|
|
242
|
-
console.log('');
|
|
243
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
244
|
-
console.log('📖 Step 1: Craft the Story');
|
|
245
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
246
|
-
console.log('What should the output be? How should it feel?');
|
|
247
|
-
console.log('This helps us capture the vision before diving into details.');
|
|
248
|
-
console.log('');
|
|
249
|
-
|
|
250
|
-
const userStory = await ask('Describe the desired outcome (what should users experience?): ');
|
|
251
|
-
const feelingsVibe = await ask('Feelings/vibes we\'re aiming for? (optional): ', { allowEmpty: true });
|
|
252
|
-
|
|
253
|
-
console.log('');
|
|
254
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
255
|
-
console.log('🧠 Step 2: Brainstorm Session');
|
|
256
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
257
|
-
console.log('Now let\'s uncover what we need to build.');
|
|
258
|
-
console.log('');
|
|
259
|
-
|
|
260
|
-
const constraints = await ask('Constraints or guardrails? (optional): ', { allowEmpty: true });
|
|
261
|
-
|
|
262
|
-
// Build concise, spaced-out prompt (4-5 sentences max, lots of spacing)
|
|
263
|
-
const promptLines = [];
|
|
264
|
-
|
|
265
|
-
// Extract key snippets from journal if available (very brief)
|
|
266
|
-
let journalHint = '';
|
|
267
|
-
if (journalHintSource && journalHintSource.trim()) {
|
|
268
|
-
const maxHint = 200;
|
|
269
|
-
const lines = journalHintSource.split('\n').slice(0, 5).join(' ').trim();
|
|
270
|
-
if (lines.length > maxHint) {
|
|
271
|
-
journalHint = lines.substring(0, maxHint) + '...';
|
|
272
|
-
} else {
|
|
273
|
-
journalHint = lines;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
promptLines.push('You:');
|
|
278
|
-
promptLines.push('');
|
|
279
|
-
promptLines.push(`I want to brainstorm: ${topicSummary}`);
|
|
280
|
-
promptLines.push('');
|
|
281
|
-
|
|
282
|
-
if (userStory) {
|
|
283
|
-
promptLines.push(`The outcome should be: ${userStory}`);
|
|
284
|
-
promptLines.push('');
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (feelingsVibe) {
|
|
288
|
-
promptLines.push(`Vibe we\'re going for: ${feelingsVibe}`);
|
|
289
|
-
promptLines.push('');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (journalHint) {
|
|
293
|
-
promptLines.push(`Recent context: ${journalHint}`);
|
|
294
|
-
promptLines.push('');
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (constraints) {
|
|
298
|
-
promptLines.push(`Constraints: ${constraints}`);
|
|
299
|
-
promptLines.push('');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
promptLines.push('Help me uncover what we need to build. Keep responses short (4-5 sentences), pause for alignment, sketch ASCII when structure helps.');
|
|
303
|
-
promptLines.push('');
|
|
304
|
-
promptLines.push('Claude:');
|
|
305
|
-
|
|
306
|
-
const promptText = promptLines.join('\n');
|
|
307
|
-
|
|
308
|
-
console.log('');
|
|
309
|
-
console.log('Copy this prompt into Claude Code (or your agent of choice):');
|
|
310
|
-
console.log('');
|
|
311
|
-
console.log('```');
|
|
312
|
-
console.log(promptText);
|
|
313
|
-
console.log('```');
|
|
314
|
-
console.log('');
|
|
315
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
316
|
-
console.log('💬 Brainstorm Mode — Thinking Together');
|
|
317
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
318
|
-
console.log('');
|
|
319
|
-
console.log('For the agent: Be conversational and supportive:');
|
|
320
|
-
console.log(' • 3-4 sentences max per response');
|
|
321
|
-
console.log(' • Ask ONE question at a time (never multiple)');
|
|
322
|
-
console.log(' • Supportive tone: "That makes sense. What about X?"');
|
|
323
|
-
console.log(' • No files created (exploration only)');
|
|
324
|
-
console.log(' • User says "ready" or "plan" to exit brainstorm');
|
|
325
|
-
console.log('');
|
|
326
|
-
console.log('Example:');
|
|
327
|
-
console.log(' User: "notifications but not sure"');
|
|
328
|
-
console.log(' You: "What bothers you about current notifications?"');
|
|
329
|
-
console.log(' User: "Easy to miss"');
|
|
330
|
-
console.log(' You: "Makes sense. What if they stayed visible until dismissed?"');
|
|
331
|
-
console.log('');
|
|
332
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
333
|
-
console.log('');
|
|
334
|
-
|
|
335
|
-
const logChoice = await askYesNo('Log this brainstorm session to today\'s journal? (y/n): ');
|
|
336
|
-
if (logChoice) {
|
|
337
|
-
const sessionSummary = await ask('Session summary (1-2 sentences): ');
|
|
338
|
-
const nextStepsRaw = await ask('Next steps (optional, separate with ";"): ', { allowEmpty: true });
|
|
339
|
-
const nextSteps = nextStepsRaw
|
|
340
|
-
? nextStepsRaw.split(';').map((item) => item.trim()).filter(Boolean)
|
|
341
|
-
: [];
|
|
342
|
-
try {
|
|
343
|
-
recordBrainstormSession(
|
|
344
|
-
logFile,
|
|
345
|
-
sourceLabel,
|
|
346
|
-
topicSummary,
|
|
347
|
-
userStory,
|
|
348
|
-
[],
|
|
349
|
-
[],
|
|
350
|
-
constraints,
|
|
351
|
-
'',
|
|
352
|
-
feelingsVibe || '',
|
|
353
|
-
nextSteps,
|
|
354
|
-
sessionSummary
|
|
355
|
-
);
|
|
356
|
-
if (selectedInboxItem) {
|
|
357
|
-
const archive = await askYesNo('Archive this Inbox idea now? (y/n): ');
|
|
358
|
-
if (archive) {
|
|
359
|
-
try {
|
|
360
|
-
let latestContent = fs.readFileSync(logFile, 'utf8');
|
|
361
|
-
latestContent = removeInboxItemFromContent(latestContent, selectedInboxItem.id);
|
|
362
|
-
if (typeof latestContent !== 'string') {
|
|
363
|
-
throw new Error('Archive operation produced invalid journal content.');
|
|
364
|
-
}
|
|
365
|
-
writeJournalFile(logFile, latestContent);
|
|
366
|
-
console.log(`✓ Archived I${selectedInboxItem.id} from Inbox.`);
|
|
367
|
-
} catch (error) {
|
|
368
|
-
console.log(`Could not archive I${selectedInboxItem.id}: ${error.message}`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
console.log('✓ Brainstorm session logged.');
|
|
373
|
-
} catch (error) {
|
|
374
|
-
if (error && error.__brainstormAbort) {
|
|
375
|
-
throw error;
|
|
376
|
-
}
|
|
377
|
-
console.log(`Could not log brainstorm session: ${error.message}`);
|
|
378
|
-
}
|
|
379
|
-
} else {
|
|
380
|
-
console.log('Skipped journaling. Prompt is ready for your agent.');
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
console.log('\nBrainstorm complete.');
|
|
384
|
-
} finally {
|
|
385
|
-
rl.close();
|
|
386
|
-
}
|
|
387
25
|
}
|
|
388
26
|
|
|
389
|
-
function
|
|
390
|
-
|
|
391
|
-
error.__brainstormAbort = true;
|
|
392
|
-
return error;
|
|
27
|
+
function topicFromArgs(args) {
|
|
28
|
+
return args.filter((arg) => !String(arg).startsWith('-')).join(' ').trim() || null;
|
|
393
29
|
}
|
|
394
30
|
|
|
395
|
-
function
|
|
396
|
-
|
|
397
|
-
return replaceInboxSection(content, items);
|
|
31
|
+
function journalRel(logFile) {
|
|
32
|
+
return path.relative(process.cwd(), logFile) || logFile;
|
|
398
33
|
}
|
|
399
34
|
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
} catch (error) {
|
|
416
|
-
if (tempFileWritten || !error || error.code !== 'EEXIST') {
|
|
417
|
-
try {
|
|
418
|
-
fs.unlinkSync(tempFile);
|
|
419
|
-
} catch {}
|
|
420
|
-
}
|
|
421
|
-
const message = error && error.message ? error.message : String(error);
|
|
422
|
-
throw new Error(`Could not write journal file: ${message}`);
|
|
35
|
+
function printCaptured(id, text, rel, args) {
|
|
36
|
+
const inboxId = `I${id}`;
|
|
37
|
+
if (wantsJson(args)) {
|
|
38
|
+
const payload = compactSuccessPayload({
|
|
39
|
+
action: 'captured',
|
|
40
|
+
ids: {
|
|
41
|
+
id,
|
|
42
|
+
inbox_id: inboxId,
|
|
43
|
+
text,
|
|
44
|
+
journal: rel,
|
|
45
|
+
},
|
|
46
|
+
next_command: 'atris plan',
|
|
47
|
+
});
|
|
48
|
+
printCliJson(payload, payload, args);
|
|
49
|
+
return;
|
|
423
50
|
}
|
|
51
|
+
console.log(`captured ${inboxId}: ${text}`);
|
|
52
|
+
console.log(`journal: ${rel}`);
|
|
53
|
+
console.log('Next: atris plan');
|
|
424
54
|
}
|
|
425
55
|
|
|
426
|
-
function
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
56
|
+
function printIdeaRequired(rel, args) {
|
|
57
|
+
if (wantsJson(args)) {
|
|
58
|
+
const payload = compactErrorPayload({
|
|
59
|
+
reason: 'idea_required',
|
|
60
|
+
detail: 'brainstorm needs an idea on the command line',
|
|
61
|
+
next_command: 'atris brainstorm "<idea>"',
|
|
62
|
+
});
|
|
63
|
+
printCliJson(payload, payload, args);
|
|
64
|
+
return;
|
|
431
65
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const trimmedBody = body.replace(/\s*$/, '');
|
|
436
|
-
const newBody = trimmedBody
|
|
437
|
-
? `${trimmedBody}\n\n${block}\n`
|
|
438
|
-
: `\n${block}\n`;
|
|
439
|
-
return content.replace(regex, `${header}${newBody}${suffix}`);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function getTimeLabel() {
|
|
443
|
-
const now = new Date();
|
|
444
|
-
const hours = String(now.getHours()).padStart(2, '0');
|
|
445
|
-
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
446
|
-
return `${hours}:${minutes}`;
|
|
66
|
+
console.log('brainstorm captures an idea on the command line and exits.');
|
|
67
|
+
console.log(`journal: ${rel}`);
|
|
68
|
+
console.log('Next: atris brainstorm "<idea>"');
|
|
447
69
|
}
|
|
448
70
|
|
|
449
|
-
function
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
keyQuestions,
|
|
455
|
-
focusAreas,
|
|
456
|
-
constraints,
|
|
457
|
-
references,
|
|
458
|
-
tonePreference,
|
|
459
|
-
nextSteps,
|
|
460
|
-
sessionSummary
|
|
461
|
-
) {
|
|
462
|
-
let content = fs.readFileSync(logFile, 'utf8');
|
|
463
|
-
const lines = [
|
|
464
|
-
`### Brainstorm Session — ${getTimeLabel()}`,
|
|
465
|
-
`**Source:** ${sourceLabel}`,
|
|
466
|
-
`**Topic:** ${topic}`,
|
|
467
|
-
];
|
|
468
|
-
if (desiredOutcome) {
|
|
469
|
-
lines.push(`**User Story / Desired Outcome:** ${desiredOutcome}`);
|
|
470
|
-
}
|
|
471
|
-
if (tonePreference) {
|
|
472
|
-
lines.push(`**Vibe / Feelings:** ${tonePreference}`);
|
|
473
|
-
}
|
|
474
|
-
if (keyQuestions && keyQuestions.length > 0) {
|
|
475
|
-
lines.push('**Key Questions:**');
|
|
476
|
-
keyQuestions.forEach((item) => lines.push(`- ${item}`));
|
|
477
|
-
}
|
|
478
|
-
if (focusAreas && focusAreas.length > 0) {
|
|
479
|
-
lines.push('**Focus Areas:**');
|
|
480
|
-
focusAreas.forEach((item) => lines.push(`- ${item}`));
|
|
481
|
-
}
|
|
482
|
-
if (constraints) {
|
|
483
|
-
lines.push(`**Constraints:** ${constraints}`);
|
|
71
|
+
async function brainstormAtris() {
|
|
72
|
+
const args = process.argv.slice(3);
|
|
73
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
74
|
+
printHelp();
|
|
75
|
+
return;
|
|
484
76
|
}
|
|
485
|
-
|
|
486
|
-
|
|
77
|
+
|
|
78
|
+
const root = process.cwd();
|
|
79
|
+
if (isFreshWorkspace(root)) {
|
|
80
|
+
process.exit(speakFirstMinute({ root, fresh: true, asJson: wantsJson(args) }));
|
|
487
81
|
}
|
|
488
|
-
|
|
489
|
-
|
|
82
|
+
|
|
83
|
+
ensureLogDirectory();
|
|
84
|
+
const { logFile, dateFormatted } = getLogPath();
|
|
85
|
+
if (!fs.existsSync(logFile)) {
|
|
86
|
+
createLogFile(logFile, dateFormatted);
|
|
490
87
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
88
|
+
|
|
89
|
+
const idea = topicFromArgs(args);
|
|
90
|
+
const rel = journalRel(logFile);
|
|
91
|
+
|
|
92
|
+
// Named explore path: capture and exit. A TTY must not open a wizard.
|
|
93
|
+
// Scar 2026-08-24: wrote I1, then hung on "Describe the desired outcome".
|
|
94
|
+
if (idea) {
|
|
95
|
+
const newId = addInboxIdea(logFile, idea);
|
|
96
|
+
printCaptured(newId, idea, rel, args);
|
|
97
|
+
return;
|
|
494
98
|
}
|
|
495
99
|
|
|
496
|
-
|
|
497
|
-
content = insertIntoNotesSection(content, block);
|
|
498
|
-
writeJournalFile(logFile, content);
|
|
100
|
+
printIdeaRequired(rel, args);
|
|
499
101
|
}
|
|
500
102
|
|
|
501
|
-
|
|
502
103
|
module.exports = {
|
|
503
|
-
brainstormAtris
|
|
104
|
+
brainstormAtris,
|
|
504
105
|
};
|