atris 3.30.0 → 3.30.2
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/AGENTS.md +6 -0
- package/atris/AGENTS.md +11 -0
- package/atris/CLAUDE.md +5 -0
- package/atris/atris.md +19 -4
- package/atris/policies/atris-design.md +71 -0
- package/atris/policies/design-seed.md +187 -0
- package/atris/skills/atris/SKILL.md +26 -3
- package/atris/skills/design/SKILL.md +3 -2
- package/atris/skills/loop/SKILL.md +5 -3
- package/atris/team/_template/MEMBER.md +19 -0
- package/atris.md +63 -23
- package/ax +1434 -1698
- package/bin/atris.js +5 -1
- package/commands/agent-spawn.js +2 -2
- package/commands/brain.js +92 -7
- package/commands/brainstorm.js +62 -22
- package/commands/business-sync.js +92 -8
- package/commands/business.js +13 -7
- package/commands/chat-scan.js +102 -0
- package/commands/computer.js +758 -15
- package/commands/deck.js +505 -105
- package/commands/init.js +6 -1
- package/commands/launchpad.js +638 -0
- package/commands/log-sync.js +44 -13
- package/commands/loop-front.js +290 -0
- package/commands/member.js +124 -3
- package/commands/mission.js +653 -30
- package/commands/pull.js +37 -28
- package/commands/pulse.js +11 -8
- package/commands/run.js +79 -39
- package/commands/sync.js +36 -17
- package/commands/task.js +1072 -89
- package/commands/workflow.js +6 -6
- package/commands/xp.js +13 -1
- package/commands/youtube.js +221 -7
- package/decks/README.md +89 -0
- package/decks/archetype-catalog.json +180 -0
- package/decks/atris-antislop-pitch.json +48 -0
- package/decks/atris-archetypes-v2.json +83 -0
- package/decks/atris-one-loop-pitch.json +80 -0
- package/decks/atris-seed-pitch-v3.json +118 -0
- package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
- package/decks/atris-seed-pitch-v5.json +109 -0
- package/decks/atris-seed-pitch-v6.json +137 -0
- package/decks/atris-seed-pitch-v7.json +133 -0
- package/decks/atris-single-shot-proof.json +74 -0
- package/decks/mark-pincus-narrative.json +102 -0
- package/decks/mark-pincus-sourcery.json +94 -0
- package/decks/yash-applied-compute-detailed.json +150 -0
- package/decks/yash-applied-compute-generalist.json +82 -0
- package/decks/yash-applied-compute-narrative.json +54 -0
- package/lib/ax-prefs.js +5 -12
- package/lib/chat-log-scan.js +377 -0
- package/lib/context-gatherer.js +35 -1
- package/lib/deck-compose.js +145 -0
- package/lib/deck-history.js +64 -0
- package/lib/deck-layout.js +169 -0
- package/lib/deck-review.js +431 -0
- package/lib/deck-schema.js +154 -0
- package/lib/file-ops.js +3 -3
- package/lib/functional-owner.js +189 -0
- package/lib/journal.js +7 -73
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +128 -11
- package/package.json +2 -1
- package/templates/business-starter/team/START_HERE.md +12 -8
- package/utils/auth.js +4 -0
- package/utils/config.js +4 -0
- package/atris/atrisDev.md +0 -717
package/commands/workflow.js
CHANGED
|
@@ -555,7 +555,7 @@ async function planAtris(userInput = null) {
|
|
|
555
555
|
printConfidenceGate(' ');
|
|
556
556
|
console.log('3) Write tasks to atris/TODO.md under ## Backlog');
|
|
557
557
|
console.log(' Format: - **T#:** Description [explore|execute]');
|
|
558
|
-
console.log('4) Log to atris/team/navigator/
|
|
558
|
+
console.log('4) Log to atris/team/navigator/logs/YYYY-MM-DD.md');
|
|
559
559
|
console.log(' (Task, Delivered, User reaction, Pattern)');
|
|
560
560
|
if (atris2Mode) {
|
|
561
561
|
console.log('5) EXECUTE MODE ENABLED: Will execute tasks directly.');
|
|
@@ -636,7 +636,7 @@ async function planAtris(userInput = null) {
|
|
|
636
636
|
userPrompt += ` - Each task: one job, clear exit condition\n`;
|
|
637
637
|
userPrompt += ` - Include file:line references from MAP.md\n\n`;
|
|
638
638
|
userPrompt += `STEP 5: Log to your journal\n`;
|
|
639
|
-
userPrompt += ` - Write to atris/team/navigator/
|
|
639
|
+
userPrompt += ` - Write to atris/team/navigator/logs/YYYY-MM-DD.md\n`;
|
|
640
640
|
userPrompt += ` - Include: Task, Delivered, User reaction, Pattern\n`;
|
|
641
641
|
userPrompt += ` - Your journal is how you learn — record what worked\n\n`;
|
|
642
642
|
userPrompt += `Start planning now. Read MAP.md for file references.`;
|
|
@@ -900,7 +900,7 @@ async function doAtris() {
|
|
|
900
900
|
console.log('3) Execute step-by-step. Run tests as you go.');
|
|
901
901
|
console.log('4) Before completion, rerun the gate against proof and residual risk');
|
|
902
902
|
console.log('5) When done, move task to ## Completed');
|
|
903
|
-
console.log('6) Log to atris/team/executor/
|
|
903
|
+
console.log('6) Log to atris/team/executor/logs/YYYY-MM-DD.md');
|
|
904
904
|
console.log(' (Task, Delivered, Errors hit, Learned)');
|
|
905
905
|
console.log('');
|
|
906
906
|
console.log('⛔ Do NOT plan — just execute what\'s written.');
|
|
@@ -989,7 +989,7 @@ async function doAtris() {
|
|
|
989
989
|
userPrompt += `4. Execute task: Use file edit tools, terminal commands, etc.\n`;
|
|
990
990
|
userPrompt += `5. Before completion, rerun the gate against proof and residual risk\n`;
|
|
991
991
|
userPrompt += `6. Move task to ## Completed in TODO.md\n`;
|
|
992
|
-
userPrompt += `7. Log to atris/team/executor/
|
|
992
|
+
userPrompt += `7. Log to atris/team/executor/logs/YYYY-MM-DD.md\n`;
|
|
993
993
|
userPrompt += ` (Task, Delivered, Errors hit, Learned)\n`;
|
|
994
994
|
userPrompt += `8. Use MAP.md to navigate codebase\n\n`;
|
|
995
995
|
userPrompt += `DO NOT just describe what you would do - actually edit files and execute commands!\n`;
|
|
@@ -1288,7 +1288,7 @@ async function reviewAtris() {
|
|
|
1288
1288
|
console.log('4) Confirm active task state is clean: no unresolved Backlog/In Progress/Blocked rows for the reviewed work.');
|
|
1289
1289
|
console.log(' If durable task state changed, regenerate the readable view with `atris task render --out atris/TODO.md`.');
|
|
1290
1290
|
console.log(' Do not hand-delete rendered completed history; use `atris task list --status done` for the ledger.');
|
|
1291
|
-
console.log('5) Log to atris/team/validator/
|
|
1291
|
+
console.log('5) Log to atris/team/validator/logs/YYYY-MM-DD.md');
|
|
1292
1292
|
console.log(' (Task, Result, Issues found, Learned)');
|
|
1293
1293
|
console.log('6) If anything surprised you, append to atris/lessons.md.');
|
|
1294
1294
|
console.log('');
|
|
@@ -1389,7 +1389,7 @@ async function reviewAtris() {
|
|
|
1389
1389
|
userPrompt += ` • Confirm active task state is clean — no unresolved Backlog/In Progress/Blocked rows for reviewed work.\n`;
|
|
1390
1390
|
userPrompt += ` If durable task state changed, regenerate the readable view with \`atris task render --out atris/TODO.md\`.\n`;
|
|
1391
1391
|
userPrompt += ` Do not hand-delete rendered completed history; if a task fails, move or mark it blocked with a note.\n`;
|
|
1392
|
-
userPrompt += ` • Log to atris/team/validator/
|
|
1392
|
+
userPrompt += ` • Log to atris/team/validator/logs/YYYY-MM-DD.md\n`;
|
|
1393
1393
|
userPrompt += ` (Task, Result, Issues found, Learned)\n`;
|
|
1394
1394
|
userPrompt += ` • If anything surprised you, append to atris/lessons.md\n`;
|
|
1395
1395
|
userPrompt += ` • EVOLUTION: If you see drift in the logs, propose a tool upgrade.\n\n`;
|
package/commands/xp.js
CHANGED
|
@@ -2048,6 +2048,7 @@ async function syncAgentXp(args = []) {
|
|
|
2048
2048
|
if (dryRun) return preview;
|
|
2049
2049
|
|
|
2050
2050
|
const token = readFlag(args, '--token', process.env.ATRIS_AGENTXP_SYNC_TOKEN || process.env.AGENTXP_SYNC_TOKEN || '');
|
|
2051
|
+
const envToken = process.env.ATRIS_TOKEN && process.env.ATRIS_TOKEN.trim() ? process.env.ATRIS_TOKEN.trim() : '';
|
|
2051
2052
|
const options = {
|
|
2052
2053
|
method: 'POST',
|
|
2053
2054
|
body: preview.packet,
|
|
@@ -2055,6 +2056,8 @@ async function syncAgentXp(args = []) {
|
|
|
2055
2056
|
};
|
|
2056
2057
|
if (token) {
|
|
2057
2058
|
options.headers = { 'X-AgentXP-Sync-Token': token };
|
|
2059
|
+
} else if (envToken) {
|
|
2060
|
+
options.token = envToken;
|
|
2058
2061
|
} else {
|
|
2059
2062
|
const ensured = await ensureValidCredentials(apiRequestJson);
|
|
2060
2063
|
if (ensured.error) {
|
|
@@ -2106,7 +2109,16 @@ function renderSync(payload) {
|
|
|
2106
2109
|
console.log('Login auth mapped this sync to your Atris account.');
|
|
2107
2110
|
}
|
|
2108
2111
|
console.log(`Packet ${payload.packet_hash}`);
|
|
2109
|
-
|
|
2112
|
+
const publicAccepted = asNumber(server.public_accepted_count);
|
|
2113
|
+
const internalAccepted = asNumber(server.internal_accepted_count);
|
|
2114
|
+
if (publicAccepted > 0) {
|
|
2115
|
+
console.log(`Published to AgentXP: ${AGENTXP_LEADERBOARD_URL}`);
|
|
2116
|
+
} else if (internalAccepted > 0 || server.private_agentxp) {
|
|
2117
|
+
console.log('Saved internally. Public AgentXP was not updated.');
|
|
2118
|
+
console.log('To publish publicly, run: atris xp sync --all --public');
|
|
2119
|
+
} else {
|
|
2120
|
+
console.log(`Leaderboard: ${AGENTXP_LEADERBOARD_URL}`);
|
|
2121
|
+
}
|
|
2110
2122
|
}
|
|
2111
2123
|
|
|
2112
2124
|
function render(payload) {
|
package/commands/youtube.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
const { apiRequestJson } = require('../utils/api');
|
|
2
2
|
const { ensureValidCredentials } = require('../utils/auth');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const https = require('https');
|
|
3
5
|
|
|
4
6
|
const DEFAULT_QUERY = 'Extract main topics, key insights, and actionable takeaways.';
|
|
5
7
|
const DEFAULT_TIMEOUT_MS = 300000;
|
|
8
|
+
const LOCAL_TRANSCRIPT_MAX_BYTES = 5 * 1024 * 1024;
|
|
9
|
+
const LOCAL_TRANSCRIPT_MAX_CHARS = 250000;
|
|
10
|
+
const ALLOWED_CAPTION_HOST_SUFFIXES = [
|
|
11
|
+
'youtube.com',
|
|
12
|
+
'googlevideo.com',
|
|
13
|
+
'youtubei.googleapis.com',
|
|
14
|
+
];
|
|
6
15
|
|
|
7
16
|
function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
8
17
|
output('');
|
|
9
18
|
output(`Usage: ${commandName} process <youtube-url> [options]`);
|
|
10
19
|
output(` ${commandName} <youtube-url> [options]`);
|
|
11
20
|
output('');
|
|
12
|
-
output('Process a YouTube video through Atris using
|
|
21
|
+
output('Process a YouTube video through Atris using transcript-first analysis.');
|
|
13
22
|
output('');
|
|
14
23
|
output('Options:');
|
|
15
24
|
output(' --query, -q <text> Focus question for the analysis');
|
|
@@ -108,9 +117,190 @@ function buildYoutubePayload(options) {
|
|
|
108
117
|
};
|
|
109
118
|
if (options.agentId) payload.agent_id = options.agentId;
|
|
110
119
|
if (options.storeAsKnowledge) payload.store_as_knowledge = true;
|
|
120
|
+
if (options.localTranscript?.transcriptText) {
|
|
121
|
+
payload.transcript_text = options.localTranscript.transcriptText;
|
|
122
|
+
if (options.localTranscript.language) payload.transcript_language = options.localTranscript.language;
|
|
123
|
+
if (options.localTranscript.durationSeconds) payload.duration_seconds = options.localTranscript.durationSeconds;
|
|
124
|
+
}
|
|
125
|
+
if (options.cacheTranscript !== undefined) {
|
|
126
|
+
payload.cache_transcript = Boolean(options.cacheTranscript);
|
|
127
|
+
}
|
|
111
128
|
return payload;
|
|
112
129
|
}
|
|
113
130
|
|
|
131
|
+
function resultErrorText(result) {
|
|
132
|
+
const raw = result?.error || result?.text || 'unknown error';
|
|
133
|
+
if (typeof raw === 'string') return raw;
|
|
134
|
+
try {
|
|
135
|
+
return JSON.stringify(raw);
|
|
136
|
+
} catch {
|
|
137
|
+
return String(raw);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function shouldRetryWithLocalTranscript(result) {
|
|
142
|
+
if (!result || result.ok) return false;
|
|
143
|
+
if (result.status === 502) return true;
|
|
144
|
+
if (result.status !== 400) return false;
|
|
145
|
+
return /YouTube video is not publicly accessible|oEmbed|metadata lookup failed/i.test(resultErrorText(result));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function youtubeFailureError(result) {
|
|
149
|
+
const hint = result.status === 401
|
|
150
|
+
? ' Run "atris login --force".'
|
|
151
|
+
: result.status === 402
|
|
152
|
+
? ' Check Atris credits.'
|
|
153
|
+
: '';
|
|
154
|
+
return new Error(`YouTube processing failed (${result.status}): ${resultErrorText(result)}.${hint}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function captionHostAllowed(urlString) {
|
|
158
|
+
try {
|
|
159
|
+
const parsed = new URL(urlString);
|
|
160
|
+
if (parsed.protocol !== 'https:') return false;
|
|
161
|
+
const hostname = parsed.hostname.toLowerCase().replace(/\.$/, '');
|
|
162
|
+
return ALLOWED_CAPTION_HOST_SUFFIXES.some((suffix) => (
|
|
163
|
+
hostname === suffix || hostname.endsWith(`.${suffix}`)
|
|
164
|
+
));
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function chooseCaptionTrack(info = {}) {
|
|
171
|
+
const preferred = ['en', 'en-orig', 'en-US', 'en-GB'];
|
|
172
|
+
const chooseFrom = (trackSets = {}) => {
|
|
173
|
+
for (const language of preferred) {
|
|
174
|
+
for (const track of trackSets[language] || []) {
|
|
175
|
+
if (track?.url && ['json3', 'vtt', 'srv3', 'ttml'].includes(track.ext)) return { language, track };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const [language, tracks] of Object.entries(trackSets)) {
|
|
179
|
+
for (const track of tracks || []) {
|
|
180
|
+
if (track?.url) return { language, track };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return chooseFrom(info.subtitles) || chooseFrom(info.automatic_captions);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function fetchCaptionText(urlString, redirects = 0) {
|
|
190
|
+
if (!captionHostAllowed(urlString)) {
|
|
191
|
+
return Promise.resolve(null);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
const req = https.get(urlString, { timeout: 30000 }, (res) => {
|
|
196
|
+
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location && redirects < 3) {
|
|
197
|
+
res.resume();
|
|
198
|
+
const nextUrl = new URL(res.headers.location, urlString).toString();
|
|
199
|
+
resolve(fetchCaptionText(nextUrl, redirects + 1));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (res.statusCode !== 200 || !captionHostAllowed(res.responseUrl || urlString)) {
|
|
204
|
+
res.resume();
|
|
205
|
+
resolve(null);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const contentLength = Number(res.headers['content-length'] || 0);
|
|
210
|
+
if (contentLength > LOCAL_TRANSCRIPT_MAX_BYTES) {
|
|
211
|
+
res.resume();
|
|
212
|
+
resolve(null);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const chunks = [];
|
|
217
|
+
let total = 0;
|
|
218
|
+
res.on('data', (chunk) => {
|
|
219
|
+
total += chunk.length;
|
|
220
|
+
if (total > LOCAL_TRANSCRIPT_MAX_BYTES) {
|
|
221
|
+
req.destroy();
|
|
222
|
+
resolve(null);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
chunks.push(chunk);
|
|
226
|
+
});
|
|
227
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
228
|
+
});
|
|
229
|
+
req.on('timeout', () => req.destroy());
|
|
230
|
+
req.on('error', () => resolve(null));
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseCaptionText(raw) {
|
|
235
|
+
const trimmed = String(raw || '').trimStart();
|
|
236
|
+
if (!trimmed) return '';
|
|
237
|
+
|
|
238
|
+
if (trimmed.startsWith('{')) {
|
|
239
|
+
try {
|
|
240
|
+
const payload = JSON.parse(trimmed);
|
|
241
|
+
const segments = [];
|
|
242
|
+
for (const event of payload.events || []) {
|
|
243
|
+
const text = (event.segs || [])
|
|
244
|
+
.map((piece) => piece.utf8 || '')
|
|
245
|
+
.join('')
|
|
246
|
+
.replace(/\s+/g, ' ')
|
|
247
|
+
.trim();
|
|
248
|
+
if (!text) continue;
|
|
249
|
+
if (segments[segments.length - 1] === text) continue;
|
|
250
|
+
segments.push(text);
|
|
251
|
+
}
|
|
252
|
+
return segments.join(' ');
|
|
253
|
+
} catch {
|
|
254
|
+
return '';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const segments = [];
|
|
259
|
+
for (const line of String(raw).split(/\r?\n/)) {
|
|
260
|
+
const stripped = line.trim();
|
|
261
|
+
if (!stripped) continue;
|
|
262
|
+
if (/^(WEBVTT|Kind:|Language:|NOTE|STYLE|REGION)/.test(stripped)) continue;
|
|
263
|
+
if (stripped.includes('-->')) continue;
|
|
264
|
+
if (/^\d+$/.test(stripped)) continue;
|
|
265
|
+
if (stripped.includes('<c>') || stripped.includes('</c>')) continue;
|
|
266
|
+
const text = stripped.replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
|
|
267
|
+
if (!text) continue;
|
|
268
|
+
if (segments[segments.length - 1] === text) continue;
|
|
269
|
+
segments.push(text);
|
|
270
|
+
}
|
|
271
|
+
return segments.join(' ');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function extractLocalTranscript(youtubeUrl, deps = {}) {
|
|
275
|
+
if (process.env.ATRIS_YOUTUBE_LOCAL_TRANSCRIPT === '0') return null;
|
|
276
|
+
const runner = deps.spawnSync || spawnSync;
|
|
277
|
+
const result = runner('yt-dlp', ['-J', '--skip-download', '--no-warnings', youtubeUrl], {
|
|
278
|
+
encoding: 'utf8',
|
|
279
|
+
timeout: 20000,
|
|
280
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
281
|
+
});
|
|
282
|
+
if (result.error || result.status !== 0 || !result.stdout) return null;
|
|
283
|
+
|
|
284
|
+
let info;
|
|
285
|
+
try {
|
|
286
|
+
info = JSON.parse(result.stdout);
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const selected = chooseCaptionTrack(info);
|
|
292
|
+
if (!selected?.track?.url) return null;
|
|
293
|
+
const rawCaption = await (deps.fetchCaptionText || fetchCaptionText)(selected.track.url);
|
|
294
|
+
const transcript = parseCaptionText(rawCaption);
|
|
295
|
+
if (!transcript) return null;
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
transcriptText: transcript.slice(0, LOCAL_TRANSCRIPT_MAX_CHARS),
|
|
299
|
+
language: selected.language || 'unknown',
|
|
300
|
+
durationSeconds: Number(info.duration || 0) || undefined,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
114
304
|
async function processYoutube(options, deps = {}) {
|
|
115
305
|
const apiFn = deps.apiRequestJson || apiRequestJson;
|
|
116
306
|
const ensureFn = deps.ensureValidCredentials || ensureValidCredentials;
|
|
@@ -121,20 +311,42 @@ async function processYoutube(options, deps = {}) {
|
|
|
121
311
|
throw new Error(detail ? `Authentication failed: ${detail}. Run "atris login".` : 'Not logged in. Run "atris login".');
|
|
122
312
|
}
|
|
123
313
|
|
|
314
|
+
const localExtractor = deps.extractLocalTranscript || extractLocalTranscript;
|
|
315
|
+
let localTranscript = null;
|
|
316
|
+
try {
|
|
317
|
+
localTranscript = await localExtractor(options.youtubeUrl, deps);
|
|
318
|
+
} catch {
|
|
319
|
+
localTranscript = null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (localTranscript?.transcriptText) {
|
|
323
|
+
const transcriptResult = await apiFn('/agent/process_youtube', {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
token: creds.token,
|
|
326
|
+
timeoutMs: options.timeoutMs,
|
|
327
|
+
retries: 0,
|
|
328
|
+
body: buildYoutubePayload({ ...options, localTranscript, cacheTranscript: false }),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (transcriptResult.ok) {
|
|
332
|
+
return transcriptResult.data;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (transcriptResult.status === 401 || transcriptResult.status === 402 || transcriptResult.status === 400) {
|
|
336
|
+
throw youtubeFailureError(transcriptResult);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
124
340
|
const result = await apiFn('/agent/process_youtube', {
|
|
125
341
|
method: 'POST',
|
|
126
342
|
token: creds.token,
|
|
127
343
|
timeoutMs: options.timeoutMs,
|
|
344
|
+
retries: 0,
|
|
128
345
|
body: buildYoutubePayload(options),
|
|
129
346
|
});
|
|
130
347
|
|
|
131
348
|
if (!result.ok) {
|
|
132
|
-
|
|
133
|
-
? ' Run "atris login --force".'
|
|
134
|
-
: result.status === 402
|
|
135
|
-
? ' Check Atris credits.'
|
|
136
|
-
: '';
|
|
137
|
-
throw new Error(`YouTube processing failed (${result.status}): ${result.error || result.text || 'unknown error'}.${hint}`);
|
|
349
|
+
throw youtubeFailureError(result);
|
|
138
350
|
}
|
|
139
351
|
|
|
140
352
|
return result.data;
|
|
@@ -177,7 +389,9 @@ module.exports = {
|
|
|
177
389
|
showYoutubeHelp,
|
|
178
390
|
parseYoutubeArgs,
|
|
179
391
|
buildYoutubePayload,
|
|
392
|
+
extractLocalTranscript,
|
|
180
393
|
processYoutube,
|
|
394
|
+
shouldRetryWithLocalTranscript,
|
|
181
395
|
formatYoutubeResult,
|
|
182
396
|
youtubeCommand,
|
|
183
397
|
};
|
package/decks/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Deck specs
|
|
2
|
+
|
|
3
|
+
Build anti-slop Google Slides decks from JSON specs.
|
|
4
|
+
|
|
5
|
+
## Loop
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
notes.md / video -> atris deck compose (analysis -> spec, picks layouts)
|
|
9
|
+
spec.json -> atris deck lint (cheap pre-check: shape + taste)
|
|
10
|
+
-> atris deck build --review (publish + thumbnail fetch)
|
|
11
|
+
-> agent reads review.json + PNG thumbnails (or --review-auto)
|
|
12
|
+
-> fix spec/engine if needed
|
|
13
|
+
-> atris deck review confirm <id> (mark ready + emit receipt)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
One-shot: `atris deck video <youtube-url> --review` runs compose -> lint -> build -> review.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Compose a spec from a markdown outline (offline) or a video (cloud)
|
|
22
|
+
atris deck compose --md notes.md --out decks/my.json --style narrative
|
|
23
|
+
atris deck compose --url https://youtu.be/VIDEO --out decks/my.json --theme ink
|
|
24
|
+
|
|
25
|
+
# Pre-build lint (schema shape + taste/clip)
|
|
26
|
+
atris deck lint decks/atris-seed-pitch-v3.json
|
|
27
|
+
|
|
28
|
+
# Build + open visual review packet (--review-auto also flags blank thumbnails)
|
|
29
|
+
atris deck build decks/atris-seed-pitch-v3.json --title "Atris Seed Pitch v3" --review
|
|
30
|
+
|
|
31
|
+
# Rebuild an existing deck in place (URL/id stay stable)
|
|
32
|
+
atris deck build decks/atris-seed-pitch-v3.json --update 139zTe9cPOGttzYbW04adRU26VJz0SqWRwaZmkoVibP8
|
|
33
|
+
|
|
34
|
+
# Batch-build several specs
|
|
35
|
+
atris deck build decks/atris-seed-pitch-v3.json decks/mark-pincus-narrative.json
|
|
36
|
+
|
|
37
|
+
# One command: video -> reviewed deck
|
|
38
|
+
atris deck video https://youtu.be/VIDEO --theme noir --review
|
|
39
|
+
|
|
40
|
+
# Re-fetch thumbnails for an existing deck
|
|
41
|
+
atris deck review 139zTe9cPOGttzYbW04adRU26VJz0SqWRwaZmkoVibP8 --spec decks/atris-seed-pitch-v3.json
|
|
42
|
+
|
|
43
|
+
# After thumbnails pass review (emits a receipt for the action queue)
|
|
44
|
+
atris deck review 139zTe9cPOGttzYbW04adRU26VJz0SqWRwaZmkoVibP8 --confirm "slides 5+8 spacing fixed"
|
|
45
|
+
|
|
46
|
+
# Builds recorded for a spec
|
|
47
|
+
atris deck history decks/atris-seed-pitch-v3.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Themes
|
|
51
|
+
|
|
52
|
+
| Theme | Vibe | When |
|
|
53
|
+
|-------|------|------|
|
|
54
|
+
| `paper` | warm light cream | default editorial |
|
|
55
|
+
| `terminal` | warm dark brown | wedge / antislop |
|
|
56
|
+
| `ink` | stark white + red accent | press / magazine |
|
|
57
|
+
| `noir` | cool dark + blue accent | podcast / interview |
|
|
58
|
+
|
|
59
|
+
Pick in spec: `"theme": "noir"`, or override at build: `atris deck build spec.json --theme ink`
|
|
60
|
+
|
|
61
|
+
List all: `atris deck themes`
|
|
62
|
+
|
|
63
|
+
## Quality bar
|
|
64
|
+
|
|
65
|
+
- `build` and `lint` **block on errors**: bad shape (unknown type, missing field), "it is not X, it is Y" copy, or `content-truncated` (more list items than the engine renders)
|
|
66
|
+
- Agent reads every thumbnail before `--confirm` (or `--review-auto` flags blank/failed renders)
|
|
67
|
+
- Stack slides: clean cards, no side accent bars
|
|
68
|
+
- State claims directly; avoid contrast framing ("not taste, it's proof")
|
|
69
|
+
- Prefer narrative types (`interstitial`, `lede`, `prose`, `split`, `bullets`) when content is a story, not a dashboard
|
|
70
|
+
- Lint warns on **template fatigue** (4+ boxed slides: panel, receipt, versus, metricgrid, stack, chips)
|
|
71
|
+
- Dense statement/columns/prose/lede/quote/split text auto-shrinks to stay on-slide
|
|
72
|
+
- `review.json` is the manifest for agents; `receipt.json` is emitted on confirm
|
|
73
|
+
- `slide-01.png` ... `slide-N.png` are the thumbnails to inspect
|
|
74
|
+
|
|
75
|
+
## Specs
|
|
76
|
+
|
|
77
|
+
| File | Purpose |
|
|
78
|
+
|------|---------|
|
|
79
|
+
| `atris-seed-pitch-v3.json` | Main investor pitch (10 slides, paper theme) |
|
|
80
|
+
| `atris-one-loop-pitch.json` | Outcome wedge deck (7 slides, terminal theme) |
|
|
81
|
+
| `yash-applied-compute-generalist.json` | Yash Patil / Own or Be Owned (ink, boxed template) |
|
|
82
|
+
| `yash-applied-compute-narrative.json` | Same story, typography-first (ink, no widgets) |
|
|
83
|
+
| `yash-applied-compute-detailed.json` | Full episode notes, 18 slides, bullets + cases |
|
|
84
|
+
| `mark-pincus-sourcery.json` | Mark Pincus / Life at the Speed of Play (noir, boxed) |
|
|
85
|
+
| `mark-pincus-narrative.json` | Same story, box-free narrative (12 slides, noir) |
|
|
86
|
+
| `atris-single-shot-proof.json` | Review loop proof deck (6 slides, single-pass quality bar) |
|
|
87
|
+
| `atris-archetypes-v2.json` | New archetypes showcase (8 slides, paper theme) |
|
|
88
|
+
| `atris-antislop-pitch.json` | 3-slide antislop product pitch |
|
|
89
|
+
| `archetype-catalog.json` | Every slide type once (19 slides, paper theme), design reference for writers |
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"theme": "paper",
|
|
3
|
+
"brand": { "name": "Atris", "accent": "." },
|
|
4
|
+
"slides": [
|
|
5
|
+
{
|
|
6
|
+
"type": "title",
|
|
7
|
+
"headline": "The deck **archetype** catalog",
|
|
8
|
+
"sub": "Every slide type Atris renders, shown once, so writers can pick the right one by eye.",
|
|
9
|
+
"panel": {
|
|
10
|
+
"header": { "title": "Reference deck", "meta": "19 types" },
|
|
11
|
+
"rows": [
|
|
12
|
+
{ "title": "Narrative", "sub": "story slides", "value": "7" },
|
|
13
|
+
{ "title": "Structured", "sub": "data slides", "value": "8" },
|
|
14
|
+
{ "title": "Framing", "sub": "open and close", "value": "4" }
|
|
15
|
+
],
|
|
16
|
+
"footer": { "left": "one of each", "right": "paper" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "statement",
|
|
21
|
+
"text": "Pick the **shape** before you write the words.",
|
|
22
|
+
"sub": "A statement is one sentence that earns the whole slide. Keep the sub short and it carries the claim."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "columns",
|
|
26
|
+
"heading": "When to reach for columns",
|
|
27
|
+
"columns": [
|
|
28
|
+
{ "h": "Compare", "b": "Set two or three peers side by side so the reader scans the difference fast." },
|
|
29
|
+
{ "h": "Group", "b": "Bucket related ideas under short headers instead of one long paragraph." },
|
|
30
|
+
{ "h": "Balance", "b": "Give each idea equal weight when no single one should win the slide." }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "panel",
|
|
35
|
+
"heading": "Panel reads like a status board",
|
|
36
|
+
"sub": "Use it for rows of named items with a value and a state dot. Header and footer are optional.",
|
|
37
|
+
"panel": {
|
|
38
|
+
"header": { "title": "Loop health", "meta": "live" },
|
|
39
|
+
"rows": [
|
|
40
|
+
{ "title": "Plan", "sub": "navigator", "value": "ok", "sev": 0 },
|
|
41
|
+
{ "title": "Build", "sub": "executor", "value": "ok", "active": true },
|
|
42
|
+
{ "title": "Review", "sub": "validator", "value": "queue", "sev": 1 },
|
|
43
|
+
{ "title": "Ship", "sub": "launcher", "value": "hold", "sev": 2 }
|
|
44
|
+
],
|
|
45
|
+
"footer": { "left": "four rows max", "right": "verified" }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "chips",
|
|
50
|
+
"heading": "Chips for a quick taxonomy",
|
|
51
|
+
"sub": "Drop a row of short labels when the point is breadth, not depth.",
|
|
52
|
+
"chips": ["title", "statement", "columns", "panel", "timeline", "versus", "receipt", "stack", "quote", "hero"],
|
|
53
|
+
"mono": "one accent, sentence case, no shouting"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "bignumber",
|
|
57
|
+
"number": "19",
|
|
58
|
+
"label": "slide types in one design system",
|
|
59
|
+
"sub": "Bignumber exists for the single figure that should land before anything else on the slide."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"type": "timeline",
|
|
63
|
+
"heading": "Timeline walks a process",
|
|
64
|
+
"steps": [
|
|
65
|
+
{ "label": "spec", "sub": "write json" },
|
|
66
|
+
{ "label": "lint", "sub": "cheap check" },
|
|
67
|
+
{ "label": "build", "sub": "publish", "active": true },
|
|
68
|
+
{ "label": "review", "sub": "thumbnails" },
|
|
69
|
+
{ "label": "confirm", "sub": "mark ready" }
|
|
70
|
+
],
|
|
71
|
+
"sub": "Five steps fit cleanly. More than that and the labels start to crowd the edges."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "versus",
|
|
75
|
+
"heading": "Versus splits two stances",
|
|
76
|
+
"left": {
|
|
77
|
+
"label": "Default slop",
|
|
78
|
+
"items": ["arial on white", "purple gradients", "em dash everywhere", "five accent colors"]
|
|
79
|
+
},
|
|
80
|
+
"right": {
|
|
81
|
+
"label": "Atris deck",
|
|
82
|
+
"items": ["committed type", "one warm accent", "sanitized copy", "real data panels"]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"type": "metricgrid",
|
|
87
|
+
"heading": "Metricgrid for four headline figures",
|
|
88
|
+
"metrics": [
|
|
89
|
+
{ "value": "4", "label": "cards is the cap before the grid gets cramped" },
|
|
90
|
+
{ "value": "2x2", "label": "fixed layout keeps the eye moving in a square" },
|
|
91
|
+
{ "value": "32pt", "label": "value type so the number reads from the back row" },
|
|
92
|
+
{ "value": "1", "label": "accent hue across every card on the slide" }
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "receipt",
|
|
97
|
+
"heading": "Receipt makes a claim auditable",
|
|
98
|
+
"sub": "Use it when the slide needs evidence fields, not prose. Up to six rows plus a stamp.",
|
|
99
|
+
"receiptTitle": "catalog build #001",
|
|
100
|
+
"fields": [
|
|
101
|
+
{ "k": "spec", "v": "archetype-catalog.json" },
|
|
102
|
+
{ "k": "theme", "v": "paper, warm editorial" },
|
|
103
|
+
{ "k": "types", "v": "all 19, one each" },
|
|
104
|
+
{ "k": "lint", "v": "zero errors, zero truncation" },
|
|
105
|
+
{ "k": "use", "v": "design reference for writers" }
|
|
106
|
+
],
|
|
107
|
+
"stamp": "lint clean"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"type": "stack",
|
|
111
|
+
"heading": "Stack shows layers in order",
|
|
112
|
+
"layers": [
|
|
113
|
+
{ "title": "Read the catalog", "sub": "find the type that fits the content" },
|
|
114
|
+
{ "title": "Copy the fields", "sub": "match the names the engine expects" },
|
|
115
|
+
{ "title": "Lint before build", "sub": "catch clips and caps locally" }
|
|
116
|
+
],
|
|
117
|
+
"sub": "Three layers leave room for a closing line beneath the cards."
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "quote",
|
|
121
|
+
"text": "Write the slide the reader **already wishes** they had.",
|
|
122
|
+
"author": "Atris deck engine",
|
|
123
|
+
"role": "design note"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"type": "hero",
|
|
127
|
+
"headline": "Hero centers one **big idea**",
|
|
128
|
+
"sub": "Reach for it when the slide is a moment, not a table. Everything centers under one rule.",
|
|
129
|
+
"mono": "atris deck build decks/archetype-catalog.json --review",
|
|
130
|
+
"footer": "atris.ai · archetype catalog · 2026"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"type": "interstitial",
|
|
134
|
+
"kicker": "section break",
|
|
135
|
+
"text": "Now the **narrative** types",
|
|
136
|
+
"sub": "Interstitial is a chapter card. Big type, lots of air, a kicker to mark the turn."
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"type": "lede",
|
|
140
|
+
"headline": "Lede opens like a feature story",
|
|
141
|
+
"dek": "A magazine intro slide: one strong headline, a deck of supporting text, and a byline that grounds it.",
|
|
142
|
+
"byline": "atris-cli · deck-compose · paper theme"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"type": "prose",
|
|
146
|
+
"heading": "Prose is for paragraphs",
|
|
147
|
+
"paragraphs": [
|
|
148
|
+
"When the content is genuinely a few sentences of argument, do not force it into cards. Prose gives the reader room to follow a line of thought without a widget in the way.",
|
|
149
|
+
"The engine fits one font size across every paragraph and floats them down to the slide floor, so two or three short paragraphs stay readable and balanced."
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"type": "split",
|
|
154
|
+
"heading": "Split pairs text with one figure",
|
|
155
|
+
"body": "Put the explanation on the left and let a single number or short phrase carry the right side. It reads as claim plus proof without a full panel.",
|
|
156
|
+
"accent": "60%",
|
|
157
|
+
"accentSub": "of the slide is text; the accent is the punchline the reader remembers."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"type": "bullets",
|
|
161
|
+
"heading": "Bullets, done with restraint",
|
|
162
|
+
"items": [
|
|
163
|
+
{ "text": "Keep it to seven or fewer", "sub": "the engine shrinks type as the list grows" },
|
|
164
|
+
{ "text": "Lead each item with a verb or a noun", "sub": "not a full sentence" },
|
|
165
|
+
{ "text": "Add a sub only when it earns its line", "sub": "optional second tier per item" },
|
|
166
|
+
{ "text": "Prefer a real list over a wall of cards", "sub": "typography, not boxes" }
|
|
167
|
+
],
|
|
168
|
+
"sub": "A true bullet list: accent dot, text, no card around each line."
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"type": "close",
|
|
172
|
+
"tagline": "One design system, nineteen ways to say it well.",
|
|
173
|
+
"buttons": [
|
|
174
|
+
{ "label": "Read the engine", "primary": true },
|
|
175
|
+
{ "label": "Lint your spec" }
|
|
176
|
+
],
|
|
177
|
+
"footer": "atris.ai · archetype catalog · 2026"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|