atris 3.46.1 → 3.48.0
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/design/SKILL.md +2 -1
- package/atris/skills/youtube/SKILL.md +5 -3
- package/bin/atris.js +3 -1
- package/commands/ci.js +1 -1
- package/commands/close.js +167 -6
- package/commands/mission.js +1 -22
- package/commands/youtube.js +31 -1
- package/lib/ci-runner.js +215 -28
- package/lib/engine-ask.js +15 -1
- package/lib/engine-registry.js +22 -0
- package/lib/fleet.js +15 -0
- package/package.json +2 -1
- package/scripts/det/README.md +162 -0
- package/scripts/det/ax-lane-eval.js +116 -0
- package/scripts/det/changelog.js +148 -0
- package/scripts/det/codex-watchdog.js +217 -0
- package/scripts/det/commit-msg.js +153 -0
- package/scripts/det/data/ax-lane-gold.jsonl +81 -0
- package/scripts/det/data/ax-lane-holdout.jsonl +20 -0
- package/scripts/det/date.js +91 -0
- package/scripts/det/det.js +149 -0
- package/scripts/det/extract.js +93 -0
- package/scripts/det/hash.js +79 -0
- package/scripts/det/hunk-filter.js +73 -0
- package/scripts/det/json.js +120 -0
- package/scripts/det/pr-description.js +213 -0
- package/scripts/det/test.js +296 -0
- package/scripts/det/text.js +102 -0
- package/scripts/det/voice.js +76 -0
- package/scripts/det/ytnotes +196 -0
- package/scripts/det/ytquote-repair.js +181 -0
- package/scripts/det/ytrail-eval.js +124 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design
|
|
3
3
|
description: Frontend aesthetics policy. Use when building UI, components, landing pages, dashboards, or any frontend work. Prevents generic ai-generated look.
|
|
4
|
-
version: 3.1.
|
|
4
|
+
version: 3.1.1
|
|
5
5
|
allowed-tools: Read, Write, Edit, Bash, Glob
|
|
6
6
|
tags:
|
|
7
7
|
- design
|
|
@@ -112,6 +112,7 @@ Every entry: id, rule, detector, status. A detector is a regex/command a gate ca
|
|
|
112
112
|
| D11 | layout contracts, not dioramas: min-height over fixed height, fluid max-width over fixed px, every overflow reachable (scroll or +N more), stress-test with hostile content before shipping | judgment | active |
|
|
113
113
|
| D12 | one accent moment per card: brand accent for the primary action only, gold for confidence/progress fills, green only for completed; everything else tonal | judgment | active |
|
|
114
114
|
| D13 | motion is calm and eased: 120-300ms ease-out on opacity/transform only, loops match a measured source cadence, prefers-reduced-motion always freezes them | judgment | active |
|
|
115
|
+
| D14 | compact selectors lead with the chosen name and a discriminating icon; remove redundant field labels and visible type explanations when icon, title, and accessible label carry them | judgment | active |
|
|
115
116
|
|
|
116
117
|
Measured recipes live in the mimic studies: `~/arena/mimic-beautiful-ui/LESSONS.md` (18 AI-interface components with exact tokens) and its `remix.css` :root block (the portable Atris token sheet, coffee + paper themes). Start there before designing an agent surface.
|
|
117
118
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: youtube
|
|
3
|
-
description: "Process YouTube videos
|
|
3
|
+
description: "Process YouTube videos: extract insights, answer questions, store as knowledge. 5 credits per video. Triggers on: youtube, video, process video, watch this, learn from video."
|
|
4
4
|
version: 2.3.0
|
|
5
5
|
tags:
|
|
6
6
|
- youtube
|
|
@@ -15,9 +15,11 @@ Process any YouTube video through Atris transcript-first analysis. The CLI extra
|
|
|
15
15
|
|
|
16
16
|
## Route first: learning vs product
|
|
17
17
|
|
|
18
|
-
Two rails process YouTube videos
|
|
18
|
+
Two rails process YouTube videos: pick before running anything:
|
|
19
19
|
|
|
20
20
|
- **Learning / work rail** → use the `alpha-learn` skill (ytnotes). Local yt-dlp + grok, zero credits, podcastnotes-style notes, tweet-feed output, `[claimable]` entries in today's journal for other agents. Use this when the goal is to LEARN from a video or mine it for Atris work.
|
|
21
|
+
Canonical ytnotes source is `scripts/det/ytnotes` (install: `ln -sf "$PWD/scripts/det/ytnotes" ~/.local/bin/ytnotes`).
|
|
22
|
+
Runs are scored by `node scripts/det/ytrail-eval.js`.
|
|
21
23
|
- **Product rail** → this skill (`atris youtube process`). Credits-billed, stores knowledge in the Atris backend, customer-facing path. Use this when a customer/agent needs the video stored as Atris knowledge or answered via the API.
|
|
22
24
|
|
|
23
25
|
If the user says "learn from", "notes on", "alpha", or "rabbit hole" → alpha-learn. If they say "process", "store", "add to knowledge" → this skill.
|
|
@@ -189,7 +191,7 @@ Every important insight should carry a timestamp when the transcript provides on
|
|
|
189
191
|
| `401` | Token expired/invalid | `atris login --force` |
|
|
190
192
|
| `402` | Not enough credits | Check balance, purchase at atris.ai |
|
|
191
193
|
| `400` | Invalid YouTube URL | Check URL format |
|
|
192
|
-
| `502` | Transcript or cloud processing failed | Retry
|
|
194
|
+
| `502` | Transcript or cloud processing failed | Retry; credits auto-refunded when backend fails |
|
|
193
195
|
|
|
194
196
|
---
|
|
195
197
|
|
package/bin/atris.js
CHANGED
|
@@ -38,6 +38,7 @@ try {
|
|
|
38
38
|
const {
|
|
39
39
|
checkForUpdates,
|
|
40
40
|
showUpdateNotification,
|
|
41
|
+
autoUpdate,
|
|
41
42
|
inspectInstallGitState,
|
|
42
43
|
formatInstallGitWarning,
|
|
43
44
|
} = require('../utils/update-check');
|
|
@@ -106,6 +107,7 @@ if (!skipUpdateCheck && (!updateCommand || (updateCommand && !['version', 'updat
|
|
|
106
107
|
showUpdateNotification(updateInfo, {
|
|
107
108
|
packageRoot: path.join(__dirname, '..'),
|
|
108
109
|
});
|
|
110
|
+
autoUpdate(updateInfo, { packageRoot: path.join(__dirname, '..') });
|
|
109
111
|
}
|
|
110
112
|
return updateInfo;
|
|
111
113
|
})
|
|
@@ -569,7 +571,7 @@ function showHelp() {
|
|
|
569
571
|
console.log(' router - inspect ax lane outcomes and promote gated reflex overrides');
|
|
570
572
|
console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
|
|
571
573
|
console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
|
|
572
|
-
console.log(' youtube -
|
|
574
|
+
console.log(' youtube - Free local notes or 5-credit cloud process for YouTube videos');
|
|
573
575
|
console.log('');
|
|
574
576
|
console.log('Experiments:');
|
|
575
577
|
console.log(' experiments init [slug] - Prepare atris/experiments/ or scaffold a pack');
|
package/commands/ci.js
CHANGED
|
@@ -14,7 +14,7 @@ function showCiHelp() {
|
|
|
14
14
|
console.log('atris ci runs github actions jobs on this machine with a warm local work folder.');
|
|
15
15
|
console.log('change runs-on: ubuntu-latest to runs-on: atris.');
|
|
16
16
|
console.log('a GITHUB_TOKEN or an authenticated gh cli is required.');
|
|
17
|
-
console.log('usage: atris ci runner --repo <owner/name> [--label <name>] [--once]');
|
|
17
|
+
console.log('usage: atris ci runner --repo <owner/name> [--repo <owner/name> ...] [--label <name>] [--once]');
|
|
18
18
|
console.log(' atris ci usage [--repo <owner/name>]');
|
|
19
19
|
}
|
|
20
20
|
|
package/commands/close.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
4
5
|
const { knownCommands } = require('../lib/known-commands');
|
|
5
6
|
const { readUsage, usagePath } = require('../lib/usage');
|
|
6
7
|
const pulse = require('../lib/pulse');
|
|
@@ -16,6 +17,12 @@ const EXPERIMENTS_RELATIVE_PATH = path.join('.atris', 'state', 'experiments.json
|
|
|
16
17
|
const EXPERIMENTS_DIR_RELATIVE_PATH = path.join('atris', 'experiments');
|
|
17
18
|
const RESOLVED_IN_SOURCE_PROOF = 'resolved in source store';
|
|
18
19
|
const FAILED_TASK_BATCH_PREFIX = 'tasks:failed:';
|
|
20
|
+
// Live-probe incidents: a diagnosis may only be recorded alongside a command
|
|
21
|
+
// that fails right now, and may only close after that command passes twice
|
|
22
|
+
// with a real gap between runs (catches the bug that comes back hours later).
|
|
23
|
+
const PROBE_TIMEOUT_MS = 60 * 1000;
|
|
24
|
+
const PROBE_HOLD_GAP_MS = 60 * 60 * 1000;
|
|
25
|
+
const PROBE_STOP_AFTER_FAILED_CHECKS = 2;
|
|
19
26
|
|
|
20
27
|
function ledgerPath(cwd = process.cwd()) {
|
|
21
28
|
return path.join(cwd, LEDGER_RELATIVE_PATH);
|
|
@@ -111,6 +118,8 @@ function foldEvents(events, options = {}) {
|
|
|
111
118
|
ttl_days: Number(event.ttl_days) || 7,
|
|
112
119
|
close_condition: normalizeSpaces(event.close_condition || 'the loop is resolved'),
|
|
113
120
|
source: normalizeSpaces(event.source || 'manual'),
|
|
121
|
+
probe: normalizeSpaces(event.probe || '') || null,
|
|
122
|
+
probe_runs: [],
|
|
114
123
|
status: 'open',
|
|
115
124
|
closed_at: null,
|
|
116
125
|
dissolved_at: null,
|
|
@@ -138,6 +147,12 @@ function foldEvents(events, options = {}) {
|
|
|
138
147
|
} else if (event.kind === 'escalated') {
|
|
139
148
|
const day = event.day || (event.at ? String(event.at).slice(0, 10) : null);
|
|
140
149
|
if (day && !flag.escalated_days.includes(day)) flag.escalated_days.push(day);
|
|
150
|
+
} else if (event.kind === 'probed') {
|
|
151
|
+
flag.probe_runs.push({
|
|
152
|
+
at: eventTime(event),
|
|
153
|
+
pass: !!event.pass,
|
|
154
|
+
exit_code: Number.isFinite(event.exit_code) ? event.exit_code : null,
|
|
155
|
+
});
|
|
141
156
|
}
|
|
142
157
|
}
|
|
143
158
|
|
|
@@ -161,9 +176,49 @@ function withComputedState(flag, now = new Date()) {
|
|
|
161
176
|
days_old: daysOld,
|
|
162
177
|
days_past_ttl: daysPastTtl,
|
|
163
178
|
overdue,
|
|
179
|
+
...probeState(flag),
|
|
164
180
|
};
|
|
165
181
|
}
|
|
166
182
|
|
|
183
|
+
// Probe state from recorded runs. 'failing' until a check passes, 'passed
|
|
184
|
+
// once' until a second pass lands PROBE_HOLD_GAP_MS later, then 'holding'
|
|
185
|
+
// (eligible to close). failed_checks counts re-checks that still failed:
|
|
186
|
+
// each one is a fix attempt that did not move reality.
|
|
187
|
+
function probeState(flag) {
|
|
188
|
+
if (!flag.probe) {
|
|
189
|
+
return { probe_status: null, failed_checks: 0, probe_holding: false };
|
|
190
|
+
}
|
|
191
|
+
const runs = flag.probe_runs || [];
|
|
192
|
+
const failedChecks = runs.filter((run) => !run.pass).length;
|
|
193
|
+
|
|
194
|
+
const trailing = [];
|
|
195
|
+
for (let index = runs.length - 1; index >= 0 && runs[index].pass; index -= 1) {
|
|
196
|
+
trailing.unshift(runs[index]);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let status = 'failing';
|
|
200
|
+
let holding = false;
|
|
201
|
+
if (trailing.length >= 1) {
|
|
202
|
+
const firstMs = parseDate(trailing[0].at) || 0;
|
|
203
|
+
const lastMs = parseDate(trailing[trailing.length - 1].at) || 0;
|
|
204
|
+
holding = trailing.length >= 2 && lastMs - firstMs >= PROBE_HOLD_GAP_MS;
|
|
205
|
+
status = holding ? 'holding' : 'passed once';
|
|
206
|
+
}
|
|
207
|
+
return { probe_status: status, failed_checks: failedChecks, probe_holding: holding };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function runProbe(command, context = {}) {
|
|
211
|
+
const result = spawnSync(command, {
|
|
212
|
+
shell: true,
|
|
213
|
+
cwd: context.cwd || process.cwd(),
|
|
214
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
215
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
216
|
+
encoding: 'utf8',
|
|
217
|
+
});
|
|
218
|
+
const exitCode = typeof result.status === 'number' ? result.status : 1;
|
|
219
|
+
return { pass: exitCode === 0, exit_code: exitCode };
|
|
220
|
+
}
|
|
221
|
+
|
|
167
222
|
function openFlags(cwd = process.cwd(), options = {}) {
|
|
168
223
|
return foldEvents(readEvents(cwd), options).filter((flag) => flag.status === 'open');
|
|
169
224
|
}
|
|
@@ -202,6 +257,7 @@ function listLine(flag) {
|
|
|
202
257
|
flag.what.toLowerCase(),
|
|
203
258
|
`${formatDays(flag.days_old)} old`,
|
|
204
259
|
];
|
|
260
|
+
if (flag.probe) parts.push(`probe ${flag.probe_status}`);
|
|
205
261
|
if (flag.overdue) {
|
|
206
262
|
parts.push(`${formatDays(flag.days_past_ttl)} past ttl`);
|
|
207
263
|
}
|
|
@@ -269,10 +325,11 @@ function publicFlag(flag) {
|
|
|
269
325
|
}
|
|
270
326
|
|
|
271
327
|
function printHelp() {
|
|
272
|
-
console.log('usage: atris close <add|list|done|dissolve|snooze|sweep|scan>');
|
|
273
|
-
console.log('add "<what>" [--owner x] [--lane life|business|code] [--ttl 7] [--when "<condition>"] [--source y]');
|
|
328
|
+
console.log('usage: atris close <add|list|done|dissolve|snooze|sweep|scan|check>');
|
|
329
|
+
console.log('add "<what>" [--owner x] [--lane life|business|code] [--ttl 7] [--when "<condition>"] [--source y] [--probe "<cmd that fails now>"]');
|
|
274
330
|
console.log('list [--json] [--lane x]');
|
|
275
|
-
console.log('done <id> [--proof "..."]');
|
|
331
|
+
console.log('done <id> [--proof "..."] (a probed incident closes only after its probe holds)');
|
|
332
|
+
console.log('check [id] [--json] (run live probes; failing probe = old theory still live)');
|
|
276
333
|
console.log('dissolve <id> --why "..."');
|
|
277
334
|
console.log('snooze <id> --days n');
|
|
278
335
|
console.log('sweep [--json]');
|
|
@@ -295,6 +352,20 @@ function commandAdd(args, context = {}) {
|
|
|
295
352
|
return 0;
|
|
296
353
|
}
|
|
297
354
|
|
|
355
|
+
// A probe turns the flag into a live incident: the claim in `what` is only
|
|
356
|
+
// recordable while the probe command fails. A passing probe means there is
|
|
357
|
+
// nothing to diagnose, so refuse instead of storing a story.
|
|
358
|
+
if (options.probe === true) throw new Error('--probe needs a command');
|
|
359
|
+
const probe = normalizeSpaces(options.probe || '');
|
|
360
|
+
let probeExit = null;
|
|
361
|
+
if (probe) {
|
|
362
|
+
const result = (context.runProbe || runProbe)(probe, context);
|
|
363
|
+
if (result.pass) {
|
|
364
|
+
throw new Error('probe passes right now; an incident needs a command that currently fails. fix the probe or drop --probe');
|
|
365
|
+
}
|
|
366
|
+
probeExit = result.exit_code;
|
|
367
|
+
}
|
|
368
|
+
|
|
298
369
|
const openedAt = now.toISOString();
|
|
299
370
|
const event = {
|
|
300
371
|
kind: 'opened',
|
|
@@ -308,11 +379,88 @@ function commandAdd(args, context = {}) {
|
|
|
308
379
|
close_condition: normalizeSpaces(options.when || 'the loop is resolved'),
|
|
309
380
|
source: normalizeSpaces(options.source || 'manual'),
|
|
310
381
|
};
|
|
382
|
+
if (probe) event.probe = probe;
|
|
311
383
|
appendEvent(event, context.cwd);
|
|
312
|
-
|
|
384
|
+
if (probe) {
|
|
385
|
+
console.log(`opened ${event.id}, probe failing as required (exit ${probeExit})`);
|
|
386
|
+
} else {
|
|
387
|
+
console.log(`opened ${event.id}`);
|
|
388
|
+
}
|
|
313
389
|
return 0;
|
|
314
390
|
}
|
|
315
391
|
|
|
392
|
+
// atris close check [id] — run the live probes on open incidents. This is the
|
|
393
|
+
// first move for any session entering an area: if a probe still fails, the
|
|
394
|
+
// recorded theory is still live; do not diagnose fresh. After
|
|
395
|
+
// PROBE_STOP_AFTER_FAILED_CHECKS failing re-checks the verdict is automatic:
|
|
396
|
+
// stop and tell the human.
|
|
397
|
+
function commandCheck(args, context = {}) {
|
|
398
|
+
const { positional, options } = parseArgs(args);
|
|
399
|
+
const id = positional[0] || null;
|
|
400
|
+
const now = context.now ? new Date(context.now) : new Date();
|
|
401
|
+
|
|
402
|
+
let flags = openFlags(context.cwd, { now }).filter((flag) => flag.probe);
|
|
403
|
+
if (id) {
|
|
404
|
+
flags = flags.filter((flag) => flag.id === id);
|
|
405
|
+
if (flags.length === 0) throw new Error(`${id} is not an open incident with a probe`);
|
|
406
|
+
}
|
|
407
|
+
if (flags.length === 0) {
|
|
408
|
+
console.log('no open incidents with probes.');
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const results = [];
|
|
413
|
+
for (const flag of flags) {
|
|
414
|
+
const run = (context.runProbe || runProbe)(flag.probe, context);
|
|
415
|
+
appendEvent({
|
|
416
|
+
kind: 'probed',
|
|
417
|
+
at: now.toISOString(),
|
|
418
|
+
id: flag.id,
|
|
419
|
+
pass: run.pass,
|
|
420
|
+
exit_code: run.exit_code,
|
|
421
|
+
}, context.cwd);
|
|
422
|
+
results.push({ id: flag.id, what: flag.what, ...run });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const after = new Map(
|
|
426
|
+
openFlags(context.cwd, { now }).map((flag) => [flag.id, flag])
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
if (options.json) {
|
|
430
|
+
printJson({
|
|
431
|
+
checked: results.map((result) => {
|
|
432
|
+
const flag = after.get(result.id);
|
|
433
|
+
return {
|
|
434
|
+
...result,
|
|
435
|
+
probe_status: flag ? flag.probe_status : null,
|
|
436
|
+
failed_checks: flag ? flag.failed_checks : null,
|
|
437
|
+
};
|
|
438
|
+
}),
|
|
439
|
+
});
|
|
440
|
+
return 0;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let anyFailing = false;
|
|
444
|
+
for (const result of results) {
|
|
445
|
+
const flag = after.get(result.id);
|
|
446
|
+
if (result.pass) {
|
|
447
|
+
if (flag && flag.probe_holding) {
|
|
448
|
+
console.log(`${result.id}: probe held. close it: atris close done ${result.id}`);
|
|
449
|
+
} else {
|
|
450
|
+
console.log(`${result.id}: probe passed once. re-check in an hour to confirm it holds.`);
|
|
451
|
+
}
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
anyFailing = true;
|
|
455
|
+
const failedChecks = flag ? flag.failed_checks : 0;
|
|
456
|
+
console.log(`${result.id}: probe still failing (exit ${result.exit_code}). the recorded theory is still live.`);
|
|
457
|
+
if (failedChecks >= PROBE_STOP_AFTER_FAILED_CHECKS) {
|
|
458
|
+
console.log(`${result.id}: ${failedChecks} fix attempts have not moved the probe. stop and tell the human.`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return anyFailing ? 1 : 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
316
464
|
function commandList(args, context = {}) {
|
|
317
465
|
const { options } = parseArgs(args);
|
|
318
466
|
const lane = options.lane ? String(options.lane).toLowerCase() : null;
|
|
@@ -346,13 +494,25 @@ function commandDone(args, context = {}) {
|
|
|
346
494
|
const { positional, options } = parseArgs(args);
|
|
347
495
|
const id = positional[0];
|
|
348
496
|
if (!id) throw new Error('id is required');
|
|
349
|
-
requireOpenFlag(id, context);
|
|
497
|
+
const flag = requireOpenFlag(id, context);
|
|
498
|
+
|
|
499
|
+
// An incident with a live probe closes on held evidence, not on a claim:
|
|
500
|
+
// the probe must pass twice, an hour apart, so the storm that returns
|
|
501
|
+
// hours later cannot be marked solved in the same breath as the fix.
|
|
502
|
+
if (flag.probe && !flag.probe_holding) {
|
|
503
|
+
if (flag.probe_status === 'failing') {
|
|
504
|
+
throw new Error(`probe still fails; run the fix, then: atris close check ${id}`);
|
|
505
|
+
}
|
|
506
|
+
throw new Error(`probe passed once; confirm it holds with atris close check ${id} at least an hour after the first pass`);
|
|
507
|
+
}
|
|
508
|
+
|
|
350
509
|
const at = (context.now ? new Date(context.now) : new Date()).toISOString();
|
|
510
|
+
const defaultProof = flag.probe ? `probe held: ${flag.probe}` : '';
|
|
351
511
|
appendEvent({
|
|
352
512
|
kind: 'closed',
|
|
353
513
|
at,
|
|
354
514
|
id,
|
|
355
|
-
proof: normalizeSpaces(options.proof ||
|
|
515
|
+
proof: normalizeSpaces(options.proof || defaultProof),
|
|
356
516
|
}, context.cwd);
|
|
357
517
|
console.log(`closed ${id}`);
|
|
358
518
|
return 0;
|
|
@@ -1097,6 +1257,7 @@ function run(args = [], context = {}) {
|
|
|
1097
1257
|
if (subcommand === 'snooze') return commandSnooze(rest, context);
|
|
1098
1258
|
if (subcommand === 'sweep') return commandSweep(rest, context);
|
|
1099
1259
|
if (subcommand === 'scan') return commandScan(rest, context);
|
|
1260
|
+
if (subcommand === 'check') return commandCheck(rest, context);
|
|
1100
1261
|
console.error(`unknown close command: ${subcommand}`);
|
|
1101
1262
|
return 2;
|
|
1102
1263
|
} catch (error) {
|
package/commands/mission.js
CHANGED
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
} = require('../lib/runner-command');
|
|
24
24
|
const {
|
|
25
25
|
resolveEngineForRoleWithPreference,
|
|
26
|
+
engineFailureHealthStatus,
|
|
26
27
|
setEngineHealth,
|
|
27
28
|
} = require('../lib/engine-registry');
|
|
28
29
|
const {
|
|
@@ -378,28 +379,6 @@ function resolveMissionTickRunner(mission, root = process.cwd()) {
|
|
|
378
379
|
};
|
|
379
380
|
}
|
|
380
381
|
|
|
381
|
-
function engineFailureHealthStatus(result) {
|
|
382
|
-
if (!result || result.status !== 'errored') return null;
|
|
383
|
-
const signalText = [
|
|
384
|
-
result.reason,
|
|
385
|
-
result.model_unavailable,
|
|
386
|
-
result.claude && result.claude.summary,
|
|
387
|
-
result.claude && result.claude.receipt_text,
|
|
388
|
-
result.claude && result.claude.stderr,
|
|
389
|
-
result.rate_limit_info && JSON.stringify(result.rate_limit_info),
|
|
390
|
-
].filter(Boolean).join('\n').toLowerCase();
|
|
391
|
-
if (/usage[ _-]?limit|purchase more credits|insufficient credits|credit(?:s)?[ _-]?(?:out|limit)|rate[ _-]?limit/.test(signalText)) {
|
|
392
|
-
return 'credit_out';
|
|
393
|
-
}
|
|
394
|
-
if (/timeout|model-unavailable/.test(signalText)) return 'not_installed';
|
|
395
|
-
// Any other errored tick (claude-error, no-ready-engine's sibling failures,
|
|
396
|
-
// etc.) is still a real failure signal for the engine that ran it. Falling
|
|
397
|
-
// through to null here left the registry showing "ready" for an engine
|
|
398
|
-
// that had just hard-failed (e.g. a 401), so auto routing kept sending
|
|
399
|
-
// ticks back to it. Mark it "error" instead of silently doing nothing.
|
|
400
|
-
return 'error';
|
|
401
|
-
}
|
|
402
|
-
|
|
403
382
|
function recordMissionEngineTickOutcome(engineId, result, root = process.cwd()) {
|
|
404
383
|
if (!engineId) return null;
|
|
405
384
|
const status = result && result.status === 'ran' ? 'ready' : engineFailureHealthStatus(result);
|
package/commands/youtube.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
const { apiRequestJson } = require('../utils/api');
|
|
2
2
|
const { ensureValidCredentials } = require('../utils/auth');
|
|
3
3
|
const { spawnSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const https = require('https');
|
|
5
6
|
|
|
7
|
+
const YTNOTES_USAGE = 'usage: ytnotes <youtube-url> [haiku|atris-fast|gemini|grok|codex|cursor]';
|
|
8
|
+
const YTNOTES_HINT = 'zero credits, local captions + a fast engine';
|
|
9
|
+
|
|
6
10
|
const DEFAULT_QUERY = [
|
|
7
11
|
'Create a timestamped YouTube brief for Atris.',
|
|
8
12
|
'Include: metadata, timestamped outline, core claims with confidence, memorable examples, actionable takeaways, Atris/product implications, and next actions.',
|
|
@@ -19,9 +23,11 @@ const ALLOWED_CAPTION_HOST_SUFFIXES = [
|
|
|
19
23
|
|
|
20
24
|
function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
21
25
|
output('');
|
|
22
|
-
output(`Usage: ${commandName}
|
|
26
|
+
output(`Usage: ${commandName} notes <youtube-url> [engine]`);
|
|
27
|
+
output(` ${commandName} process <youtube-url> [options]`);
|
|
23
28
|
output(` ${commandName} <youtube-url> [options]`);
|
|
24
29
|
output('');
|
|
30
|
+
output('notes = free local notes, process = 5 credits cloud knowledge');
|
|
25
31
|
output('Process a YouTube video through Atris using timestamped transcript-first analysis.');
|
|
26
32
|
output('Falls back to cloud video processing when local captions are unavailable.');
|
|
27
33
|
output('');
|
|
@@ -37,6 +43,7 @@ function showYoutubeHelp(output = console.log, commandName = 'atris youtube') {
|
|
|
37
43
|
output(' metadata -> timestamped outline -> claims -> examples -> takeaways -> Atris implications -> next actions');
|
|
38
44
|
output('');
|
|
39
45
|
output('Examples:');
|
|
46
|
+
output(` ${commandName} notes https://www.youtube.com/watch?v=VIDEO_ID`);
|
|
40
47
|
output(` ${commandName} https://www.youtube.com/watch?v=VIDEO_ID`);
|
|
41
48
|
output(` ${commandName} process https://youtu.be/VIDEO_ID --query "Key takeaways"`);
|
|
42
49
|
output('');
|
|
@@ -437,8 +444,31 @@ function formatYoutubeResult(data) {
|
|
|
437
444
|
return lines.join('\n');
|
|
438
445
|
}
|
|
439
446
|
|
|
447
|
+
function runYoutubeNotes(args = [], deps = {}) {
|
|
448
|
+
const output = deps.output || ((line = '') => console.error(line));
|
|
449
|
+
const url = args[0];
|
|
450
|
+
const engine = args[1];
|
|
451
|
+
if (!url) {
|
|
452
|
+
output(YTNOTES_USAGE);
|
|
453
|
+
output(YTNOTES_HINT);
|
|
454
|
+
return 2;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const script = path.join(__dirname, '..', 'scripts', 'det', 'ytnotes');
|
|
458
|
+
const spawn = deps.spawnSync || spawnSync;
|
|
459
|
+
const childArgs = engine ? [url, engine] : [url];
|
|
460
|
+
const result = spawn(script, childArgs, { stdio: 'inherit' });
|
|
461
|
+
if (result.status == null) return 1;
|
|
462
|
+
return result.status;
|
|
463
|
+
}
|
|
464
|
+
|
|
440
465
|
async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
441
466
|
const output = deps.output || ((line = '') => console.log(line));
|
|
467
|
+
if (argv[0] === 'notes') {
|
|
468
|
+
const code = runYoutubeNotes(argv.slice(1), deps);
|
|
469
|
+
if (!deps.output && !deps.spawnSync) process.exit(code);
|
|
470
|
+
return code;
|
|
471
|
+
}
|
|
442
472
|
const options = parseYoutubeArgs(argv);
|
|
443
473
|
if (options.help) {
|
|
444
474
|
showYoutubeHelp(output, deps.commandName || 'atris youtube');
|