atris 3.67.0 → 3.68.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/youtube/SKILL.md +12 -1
- package/commands/member.js +24 -1
- package/commands/signup.js +45 -2
- package/commands/youtube.js +12 -2
- package/package.json +1 -1
- package/scripts/det/ytnotes +11 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: youtube
|
|
3
3
|
description: "YouTube discovery and learning. Get watch permalinks with atris youtube search QUERY (free, local ytsearch/yt-dlp). On 429 use printed rows if any; else the CLI retries once, then cached rows if printed, else STOP. Never run --paid after a 429. --paid only when the user explicitly asked to buy permalinks (5 credits). After a URL is picked, atris youtube notes URL (free, ephemeral unless --save). atris youtube process only to store knowledge (5 credits). Never paste tokens. Never /auth/cli. Mint with atris login --agent from a stored login. Never summarize a video from model memory. Triggers on: youtube search, find videos, paid youtube search, any youtube.com or youtu.be link, youtube, video, watch this, notes on this."
|
|
4
|
-
version: 2.18.
|
|
4
|
+
version: 2.18.24
|
|
5
5
|
tags:
|
|
6
6
|
- youtube
|
|
7
7
|
- research
|
|
@@ -11,6 +11,17 @@ tags:
|
|
|
11
11
|
|
|
12
12
|
# YouTube Skill
|
|
13
13
|
|
|
14
|
+
## Route first: learning vs product
|
|
15
|
+
|
|
16
|
+
Two rails. Pick one before running anything.
|
|
17
|
+
|
|
18
|
+
- **Learning rail (free):** `atris youtube notes URL` or `atris youtube teach URL`. Local yt-dlp captions plus a local AI writer, zero credits, no login. Use it when the ask is "learn from", "notes on", "alpha", "rabbit hole", "what does this say". This replaces the old alpha-learn skill.
|
|
19
|
+
- **Product rail (5 credits):** `atris youtube process URL` (bare `atris youtube URL` is the same call). Stores the analysis in the Atris backend, needs a stored login, and is gated on a filled Apply. Use it only when the ask is "process", "store", "add to knowledge", or `--agent ID --store`.
|
|
20
|
+
|
|
21
|
+
Process is gated. It exits 2 with `write one apply (change + receipt) before process.` until `atris/wiki/briefs/youtube-<id>.apply.md` (relative to the current directory) has real `change:` and `receipt:` lines. `fill this` does not count, and a notes/search keep-sidecar (`change: apply atris/experiments/...`) does not count. The CLI prints the exact file on the next line. It writes a fill-this stub there only when `atris/wiki/` already exists; otherwise create the file yourself. The gate runs before any login or billing, so a refusal never spends credits. Write the Apply from what the person will actually change, not a placeholder.
|
|
22
|
+
|
|
23
|
+
Notes writer: Gemini Flash through the Antigravity CLI (`agy -p`) when installed, else Haiku (`claude`), then Codex, then Cursor. The Antigravity editor's own `agy` launcher has no `-p` mode and is skipped. `notes by <writer>` names who wrote them. `check: fill this` after notes, teach, or search means the video had no number or named mechanism to build a check from; it is not an error.
|
|
24
|
+
|
|
14
25
|
## Feature map
|
|
15
26
|
|
|
16
27
|
Hard path. Follow in order. Soft memory does not override this.
|
package/commands/member.js
CHANGED
|
@@ -5889,6 +5889,26 @@ function fallbackSupervisorAnalysis(receipts) {
|
|
|
5889
5889
|
async function runSupervisorWake(name, paths, { execute = false } = {}) {
|
|
5890
5890
|
const mode = execute ? 'execute' : 'dry_run';
|
|
5891
5891
|
const root = process.cwd();
|
|
5892
|
+
const llmConfigured = Boolean(process.env.ATRIS_SUPERVISOR_LLM_JSON)
|
|
5893
|
+
|| process.env.ATRIS_SUPERVISOR_LLM === '1';
|
|
5894
|
+
if (!execute && !llmConfigured) {
|
|
5895
|
+
return {
|
|
5896
|
+
ok: true,
|
|
5897
|
+
skipped: true,
|
|
5898
|
+
action: 'wake',
|
|
5899
|
+
member: name,
|
|
5900
|
+
mode,
|
|
5901
|
+
decision: 'wait',
|
|
5902
|
+
reason: 'skipped_llm_not_configured',
|
|
5903
|
+
executed: false,
|
|
5904
|
+
needs_user: false,
|
|
5905
|
+
ask: null,
|
|
5906
|
+
next_command: null,
|
|
5907
|
+
receipt_path: null,
|
|
5908
|
+
log_path: null,
|
|
5909
|
+
recommendations_path: supervisorRecommendationsPath(root),
|
|
5910
|
+
};
|
|
5911
|
+
}
|
|
5892
5912
|
const receipts = listRecentSupervisorReceipts(root);
|
|
5893
5913
|
const logs = readSupervisorMemberLogs(root);
|
|
5894
5914
|
let llm = null;
|
|
@@ -8139,6 +8159,7 @@ const WAKE_REASON_TEXT = {
|
|
|
8139
8159
|
heuristic_objective_proposal_dry_run: 'it previewed an objective proposal using its built-in heuristics, without writing it',
|
|
8140
8160
|
install_requires_clean_git: 'installing needs a clean git tree first',
|
|
8141
8161
|
insufficient_world_model_data: 'its world model is too thin to act on yet',
|
|
8162
|
+
skipped_llm_not_configured: 'no model is configured here, so it skipped quietly instead of writing another dry-run receipt',
|
|
8142
8163
|
llm_json_parse_failed: 'the model reply did not parse, so it stopped rather than act on garbage',
|
|
8143
8164
|
llm_json_parse_failed_heuristic_used: 'the model reply did not parse, so it fell back to built-in heuristics',
|
|
8144
8165
|
missing_domain_input: 'it needs a domain file or domain text from you to work on',
|
|
@@ -8231,7 +8252,9 @@ function wakeBootLines(name, result) {
|
|
|
8231
8252
|
|| /^open_experiment_/.test(result.reason || '');
|
|
8232
8253
|
lines.push('', ` ${s.bold(ballWithHuman ? 'Your move' : 'Next step')}`, ` ${s.cyan(result.next_command)}`);
|
|
8233
8254
|
}
|
|
8234
|
-
|
|
8255
|
+
if (result.receipt_path) {
|
|
8256
|
+
lines.push('', ` ${s.dim(`receipt \u00b7 ${path.relative(process.cwd(), result.receipt_path)}`)}`, '');
|
|
8257
|
+
}
|
|
8235
8258
|
return lines;
|
|
8236
8259
|
}
|
|
8237
8260
|
|
package/commands/signup.js
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
// Backend: backend/routers/agent_auth_router.py (POST /auth/agent/signup).
|
|
12
12
|
|
|
13
13
|
const crypto = require('crypto');
|
|
14
|
+
const fs = require('fs');
|
|
14
15
|
const { apiRequestJson, getApiBaseUrl } = require('../utils/api');
|
|
15
|
-
const { saveCredentials } = require('../utils/auth');
|
|
16
|
+
const { saveCredentials, saveProfile, saveAgentClientCredentials, getCredentialsPath } = require('../utils/auth');
|
|
17
|
+
const agentClient = require('../utils/agent-client');
|
|
16
18
|
const { argsWantHelp, isHelpToken } = require('../lib/noninteractive');
|
|
17
19
|
|
|
18
20
|
// Mirror the server rule exactly (^[a-z0-9]{3,30}$) so we fail fast and friendly
|
|
@@ -45,6 +47,36 @@ function parseHandle(args = []) {
|
|
|
45
47
|
return (positional || '').trim().toLowerCase();
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
// Newer servers also hand back an agent client (id + secret) so the agent can
|
|
51
|
+
// renew its own login forever. Store it the way `atris login` does with the
|
|
52
|
+
// secret in ATRIS_CLIENT_SECRET: a private 0600 copy under
|
|
53
|
+
// ~/.atris/agent-clients/, then make the client the active login. The 30-day
|
|
54
|
+
// token just saved stays reachable as a profile named after the handle.
|
|
55
|
+
// Returns { ok: true } or { ok: false, secretFile, clientId }. Never throws,
|
|
56
|
+
// never prints the secret or a token.
|
|
57
|
+
async function adoptSignupClient(client, handle, deps = {}) {
|
|
58
|
+
const clientId = client && typeof client.client_id === 'string' ? client.client_id.trim() : '';
|
|
59
|
+
const secret = client && typeof client.client_secret === 'string' ? client.client_secret.trim() : '';
|
|
60
|
+
if (!clientId.startsWith(agentClient.CLIENT_ID_PREFIX) || !secret) return { ok: false };
|
|
61
|
+
|
|
62
|
+
let secretFile = null;
|
|
63
|
+
try {
|
|
64
|
+
const target = agentClient.managedSecretPath(clientId);
|
|
65
|
+
agentClient.writeFileAtomic(target, `${secret}\n`);
|
|
66
|
+
secretFile = target;
|
|
67
|
+
const entry = await agentClient.requestClientToken({ clientId, secret, scope: null }, deps);
|
|
68
|
+
|
|
69
|
+
// Keep the 30-day login as a profile named after the handle, then switch.
|
|
70
|
+
const fallback = JSON.parse(fs.readFileSync(getCredentialsPath(), 'utf8'));
|
|
71
|
+
saveProfile(handle, fallback);
|
|
72
|
+
saveAgentClientCredentials({ client_id: clientId, client_secret_file: secretFile, scope: null });
|
|
73
|
+
agentClient.writeTokenCache(clientId, entry);
|
|
74
|
+
return { ok: true, clientId };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return { ok: false, clientId, secretFile, reason: agentClient.scrub(error && error.message, [secret]) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
function printSignupUsage() {
|
|
49
81
|
console.error('Usage: atris signup <handle>');
|
|
50
82
|
console.error(' handle: 3–30 characters, lowercase letters and digits only.');
|
|
@@ -79,9 +111,20 @@ async function signupCommand(args = []) {
|
|
|
79
111
|
const { token, email, user_id: userId } = res.data;
|
|
80
112
|
const identity = email || `${handle}@atrismail.com`;
|
|
81
113
|
saveCredentials(token, null, identity, userId || null, 'atrisos');
|
|
114
|
+
const hasClient = Boolean(res.data.client);
|
|
115
|
+
const adopted = hasClient ? await adoptSignupClient(res.data.client, handle) : { ok: false };
|
|
82
116
|
console.log(`\n✓ You're in: ${identity}`);
|
|
117
|
+
if (adopted.ok) console.log('This agent renews its own login; no human needed.');
|
|
83
118
|
console.log(' Inert starter account (0 credits): identity is free, capability is earned.');
|
|
84
119
|
console.log(' Saved to your active profile.');
|
|
120
|
+
if (adopted.ok) {
|
|
121
|
+
console.log(` The 30-day login is kept as a backup. switch to it: atris switch ${handle} --global`);
|
|
122
|
+
} else if (hasClient && adopted.secretFile) {
|
|
123
|
+
console.log(` Could not sign in with the agent client yet${adopted.reason ? ` (${adopted.reason})` : ''}. Using the 30-day login for now.`);
|
|
124
|
+
console.log(` Try again: atris login --client-id ${adopted.clientId} --client-secret-file ${adopted.secretFile}`);
|
|
125
|
+
} else if (hasClient || typeof res.data.client_error === 'string') {
|
|
126
|
+
console.log(' This login lasts 30 days; after that, renewing it will need a fresh signup login.');
|
|
127
|
+
}
|
|
85
128
|
console.log('\nNext:');
|
|
86
129
|
console.log(' atris play # claim a starter mission and earn your first proof-backed rep');
|
|
87
130
|
console.log(' atris xp # see where you stand on the board');
|
|
@@ -107,4 +150,4 @@ async function signupCommand(args = []) {
|
|
|
107
150
|
return 1;
|
|
108
151
|
}
|
|
109
152
|
|
|
110
|
-
module.exports = { signupCommand, parseHandle, HANDLE_RE };
|
|
153
|
+
module.exports = { signupCommand, parseHandle, HANDLE_RE, adoptSignupClient };
|
package/commands/youtube.js
CHANGED
|
@@ -1021,16 +1021,26 @@ function runYoutubeUnsave(args = [], deps = {}) {
|
|
|
1021
1021
|
|
|
1022
1022
|
function ensureProcessApply({ cwd, url, now, output } = {}) {
|
|
1023
1023
|
const id = videoIdFromUrl(url);
|
|
1024
|
-
|
|
1024
|
+
const rel = id ? applySidecarRel(id) : null;
|
|
1025
|
+
const code = applyGate.ensureApply({
|
|
1025
1026
|
cwd,
|
|
1026
1027
|
source: url,
|
|
1027
|
-
rel
|
|
1028
|
+
rel,
|
|
1028
1029
|
now,
|
|
1029
1030
|
output,
|
|
1030
1031
|
incompleteMessage: PROCESS_APPLY_MESSAGE,
|
|
1031
1032
|
required: true,
|
|
1032
1033
|
human: true,
|
|
1033
1034
|
});
|
|
1035
|
+
if (code !== 0 && rel) {
|
|
1036
|
+
// Name the file so the gate is passable, including in a workspace with no atris/wiki yet.
|
|
1037
|
+
const print = typeof output === 'function' ? output : (line = '') => console.error(line);
|
|
1038
|
+
const exists = Boolean(cwd) && fs.existsSync(path.join(cwd, rel));
|
|
1039
|
+
print(exists
|
|
1040
|
+
? `next: fill change: and receipt: in ${rel}, then rerun process`
|
|
1041
|
+
: `next: create ${rel} with change: and receipt: lines, then rerun process`);
|
|
1042
|
+
}
|
|
1043
|
+
return code;
|
|
1034
1044
|
}
|
|
1035
1045
|
|
|
1036
1046
|
function processExperimentSlug(id) {
|
package/package.json
CHANGED
package/scripts/det/ytnotes
CHANGED
|
@@ -379,8 +379,18 @@ run_haiku() {
|
|
|
379
379
|
# Gemini Flash through the Antigravity CLI (agy). On an 85-minute transcript it
|
|
380
380
|
# writes notes in about 8s; Haiku takes 30-40s. The old gemini CLI is retired.
|
|
381
381
|
# Low effort is the floor for notes: high effort is slower and not sharper here.
|
|
382
|
+
# The Antigravity editor ships its own `agy` (a VS Code-style launcher script).
|
|
383
|
+
# It has no -p mode: it hands -p and --model to Electron and prints nothing, so
|
|
384
|
+
# it is not a writer. Only the Antigravity CLI is.
|
|
385
|
+
agy_is_cli() {
|
|
386
|
+
local bin
|
|
387
|
+
bin=$(command -v agy 2>/dev/null) || return 1
|
|
388
|
+
! head -c 4096 "$bin" 2>/dev/null | grep -q 'VSCODE_IPC_HOOK_CLI'
|
|
389
|
+
}
|
|
390
|
+
|
|
382
391
|
run_gemini() {
|
|
383
392
|
command -v agy >/dev/null 2>&1 || { echo "agy is not installed" >&2; return 127; }
|
|
393
|
+
agy_is_cli || { echo "agy on PATH is the Antigravity editor launcher, which has no -p mode. Install the Antigravity CLI for Gemini notes." >&2; return 127; }
|
|
384
394
|
run_with_timeout 120 agy -p "$PROMPT" --model "${ATRIS_YTNOTES_GEMINI_MODEL:-${1:-gemini-3.8-flash}}" \
|
|
385
395
|
--effort "${2:-low}" --print-timeout 110s </dev/null
|
|
386
396
|
}
|
|
@@ -448,6 +458,7 @@ run_auto() {
|
|
|
448
458
|
for entry in $(writer_list); do
|
|
449
459
|
IFS='|' read -r cmd model effort label <<< "$entry"
|
|
450
460
|
command -v "$cmd" >/dev/null 2>&1 || continue
|
|
461
|
+
if [ "$cmd" = agy ] && ! agy_is_cli; then continue; fi
|
|
451
462
|
if [ -n "$tried" ]; then
|
|
452
463
|
echo "$tried did not answer; writing the notes with $label instead." >&2
|
|
453
464
|
fi
|