atris 3.72.4 → 3.73.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/README.md +3 -1
- package/atris/skills/commerce/SKILL.md +24 -3
- package/bin/atris.js +10 -0
- package/commands/gm.js +17 -6
- package/commands/member.js +59 -9
- package/commands/pay.js +329 -0
- package/commands/task.js +55 -18
- package/commands/trust.js +117 -0
- package/commands/zen.js +17 -0
- package/lib/gm-bet.js +1443 -162
- package/lib/gm-phone-page.js +460 -357
- package/lib/gm-phone.js +74 -13
- package/lib/known-commands.js +1 -1
- package/lib/site.js +9 -1
- package/lib/try-repo.js +442 -0
- package/lib/zen-apply.js +28 -4
- package/lib/zen-fleet.js +100 -0
- package/lib/zen.js +41 -6
- package/package.json +1 -1
- package/scripts/det/xsearch-bench.js +1 -0
package/commands/task.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const http = require('http');
|
|
8
|
+
const crypto = require('crypto');
|
|
8
9
|
const { execSync } = require('node:child_process');
|
|
9
10
|
const path = require('path');
|
|
10
11
|
const os = require('os');
|
|
@@ -12482,7 +12483,7 @@ function taskBoardTemplate(model) {
|
|
|
12482
12483
|
async function api(path, options = {}) {
|
|
12483
12484
|
const res = await fetch(path, {
|
|
12484
12485
|
...options,
|
|
12485
|
-
headers: { 'content-type': 'application/json', ...(options.headers || {}) }
|
|
12486
|
+
headers: { 'content-type': 'application/json', 'x-atris-token': new URLSearchParams(location.search).get('t') || '', ...(options.headers || {}) }
|
|
12486
12487
|
});
|
|
12487
12488
|
const data = await res.json();
|
|
12488
12489
|
if (!res.ok || data.ok === false) throw new Error(data.detail || data.reason || 'request failed');
|
|
@@ -12798,18 +12799,30 @@ function taskBoardHtml() {
|
|
|
12798
12799
|
}
|
|
12799
12800
|
|
|
12800
12801
|
function readJsonBody(req) {
|
|
12802
|
+
if (String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase() !== 'application/json') {
|
|
12803
|
+
return Promise.reject(Object.assign(new Error('json required'), { statusCode: 415 }));
|
|
12804
|
+
}
|
|
12801
12805
|
return new Promise((resolve, reject) => {
|
|
12802
|
-
let
|
|
12806
|
+
let size = 0;
|
|
12807
|
+
const chunks = [];
|
|
12803
12808
|
req.on('data', chunk => {
|
|
12804
|
-
|
|
12805
|
-
|
|
12806
|
-
|
|
12807
|
-
|
|
12809
|
+
if (size > 1_000_000) return;
|
|
12810
|
+
size += chunk.length;
|
|
12811
|
+
if (size > 1_000_000) {
|
|
12812
|
+
reject(Object.assign(new Error('body too large'), { statusCode: 413 }));
|
|
12813
|
+
return;
|
|
12808
12814
|
}
|
|
12815
|
+
chunks.push(chunk);
|
|
12809
12816
|
});
|
|
12810
12817
|
req.on('end', () => {
|
|
12818
|
+
if (size > 1_000_000) return;
|
|
12819
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
12811
12820
|
if (!body.trim()) return resolve({});
|
|
12812
|
-
try {
|
|
12821
|
+
try {
|
|
12822
|
+
const value = JSON.parse(body);
|
|
12823
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('object required');
|
|
12824
|
+
resolve(value);
|
|
12825
|
+
} catch (e) { reject(Object.assign(e, { statusCode: 400 })); }
|
|
12813
12826
|
});
|
|
12814
12827
|
req.on('error', reject);
|
|
12815
12828
|
});
|
|
@@ -12818,14 +12831,16 @@ function readJsonBody(req) {
|
|
|
12818
12831
|
function sendJson(res, status, value, headers = {}) {
|
|
12819
12832
|
res.writeHead(status, {
|
|
12820
12833
|
'content-type': 'application/json; charset=utf-8',
|
|
12821
|
-
'
|
|
12834
|
+
'cache-control': 'no-store',
|
|
12835
|
+
'referrer-policy': 'no-referrer',
|
|
12836
|
+
'x-content-type-options': 'nosniff',
|
|
12822
12837
|
...headers,
|
|
12823
12838
|
});
|
|
12824
12839
|
res.end(JSON.stringify(value, null, 2));
|
|
12825
12840
|
}
|
|
12826
12841
|
|
|
12827
12842
|
function sendHtml(res, value) {
|
|
12828
|
-
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
12843
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'referrer-policy': 'no-referrer', 'x-content-type-options': 'nosniff' });
|
|
12829
12844
|
res.end(value);
|
|
12830
12845
|
}
|
|
12831
12846
|
|
|
@@ -13469,8 +13484,24 @@ function matchTaskApiRoute(req, url) {
|
|
|
13469
13484
|
return null;
|
|
13470
13485
|
}
|
|
13471
13486
|
|
|
13472
|
-
async function handleTaskApi(req, res, taskDb, db) {
|
|
13473
|
-
|
|
13487
|
+
async function handleTaskApi(req, res, taskDb, db, token) {
|
|
13488
|
+
let url;
|
|
13489
|
+
try {
|
|
13490
|
+
const target = req.url || '/';
|
|
13491
|
+
url = new URL(target, 'http://127.0.0.1');
|
|
13492
|
+
if (!target.startsWith('/') || target.startsWith('//') || /[\\#\0]/.test(target) || decodeURIComponent(url.pathname).includes('\0')) throw new Error('url');
|
|
13493
|
+
decodeURIComponent(target);
|
|
13494
|
+
} catch {
|
|
13495
|
+
return sendJson(res, 400, { ok: false, reason: 'bad_url' });
|
|
13496
|
+
}
|
|
13497
|
+
const host = String(req.headers.host || '').toLowerCase();
|
|
13498
|
+
const port = req.socket.localPort;
|
|
13499
|
+
if (![`127.0.0.1:${port}`, `localhost:${port}`].includes(host)) return sendJson(res, 403, { ok: false, reason: 'host' });
|
|
13500
|
+
const provided = Buffer.from(String(req.headers['x-atris-token'] || (req.method === 'GET' && url.searchParams.get('t')) || ''));
|
|
13501
|
+
const expected = Buffer.from(token);
|
|
13502
|
+
if (provided.length !== expected.length || !crypto.timingSafeEqual(provided, expected)) return sendJson(res, 403, { ok: false, reason: 'token' });
|
|
13503
|
+
if (req.method === 'GET' && (req.headers['transfer-encoding'] || Number(req.headers['content-length'] || 0) > 0)) return sendJson(res, 400, { ok: false, reason: 'body' });
|
|
13504
|
+
if (req.method !== 'GET' && req.headers.origin !== undefined && req.headers.origin !== `http://${host}`) return sendJson(res, 403, { ok: false, reason: 'origin' });
|
|
13474
13505
|
const matchedRoute = matchTaskApiRoute(req, url);
|
|
13475
13506
|
if (!matchedRoute) return sendJson(res, 404, { ok: false, reason: 'not_found' });
|
|
13476
13507
|
return matchedRoute.route.serve({
|
|
@@ -13486,26 +13517,32 @@ async function handleTaskApi(req, res, taskDb, db) {
|
|
|
13486
13517
|
function createTaskApiServer(taskApi = {}) {
|
|
13487
13518
|
const taskDb = taskApi.taskDb || getTaskDb();
|
|
13488
13519
|
const db = taskApi.db || taskDb.open();
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13520
|
+
const token = crypto.randomBytes(32).toString('base64url');
|
|
13521
|
+
const server = http.createServer((req, res) => {
|
|
13522
|
+
handleTaskApi(req, res, taskDb, db, token).catch((error) => {
|
|
13523
|
+
if (res.headersSent || res.destroyed) return res.destroy();
|
|
13524
|
+
const status = error.statusCode || 500;
|
|
13525
|
+
sendJson(res, status, { ok: false, reason: status < 500 ? 'bad_request' : 'server_error' });
|
|
13526
|
+
}).catch(() => res.destroy());
|
|
13493
13527
|
});
|
|
13528
|
+
server.taskToken = token;
|
|
13529
|
+
return server;
|
|
13494
13530
|
}
|
|
13495
13531
|
|
|
13496
13532
|
function cmdServe(args) {
|
|
13497
13533
|
const host = String(flag(args, '--host') || '127.0.0.1');
|
|
13534
|
+
if (!['127.0.0.1', 'localhost'].includes(host)) throw new Error('task board only listens on 127.0.0.1');
|
|
13498
13535
|
const port = Number(flag(args, '--port') || process.env.PORT || 8787);
|
|
13499
13536
|
const taskDb = getTaskDb();
|
|
13500
13537
|
const db = taskDb.open();
|
|
13501
13538
|
const server = createTaskApiServer({ taskDb, db });
|
|
13502
13539
|
return new Promise((resolve, reject) => {
|
|
13503
13540
|
server.on('error', reject);
|
|
13504
|
-
server.listen(port,
|
|
13541
|
+
server.listen(port, '127.0.0.1', () => {
|
|
13505
13542
|
const addr = server.address();
|
|
13506
13543
|
const actualPort = addr && addr.port || port;
|
|
13507
|
-
console.log(`
|
|
13508
|
-
console.log(`
|
|
13544
|
+
console.log(`task board: http://${host}:${actualPort}/?t=${server.taskToken}`);
|
|
13545
|
+
console.log(`workspace: ${taskDb.workspaceRoot()}`);
|
|
13509
13546
|
});
|
|
13510
13547
|
|
|
13511
13548
|
const shutdown = () => {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust commands for Atris CLI, see who trusts an account and vouch for people.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* atris trust Your own trust
|
|
6
|
+
* atris trust <handle> Someone else's trust, and what you share
|
|
7
|
+
* atris trust vouch <handle> Vouch for someone (the same as following them)
|
|
8
|
+
* atris trust unvouch <handle> Take a vouch back
|
|
9
|
+
*
|
|
10
|
+
* <handle> can be @handle, handle, a user id, or an email. A leading @ is
|
|
11
|
+
* stripped before it is sent.
|
|
12
|
+
*
|
|
13
|
+
* Backend: GET /api/trust/me, GET /api/trust/{handle_or_id},
|
|
14
|
+
* POST and DELETE /api/trust/vouch/{handle_or_id}.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { isHelpToken, argsWantHelp } = require('../lib/noninteractive');
|
|
18
|
+
const { cleanTarget, targetLabel, createIo, call, reportError } = require('./pay');
|
|
19
|
+
|
|
20
|
+
function splitFlags(args) {
|
|
21
|
+
const list = Array.isArray(args) ? args : [];
|
|
22
|
+
return {
|
|
23
|
+
json: list.includes('--json'),
|
|
24
|
+
rest: list.filter((a) => a !== '--json'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function yesNo(value) {
|
|
29
|
+
if (value === true) return 'yes';
|
|
30
|
+
if (value === false) return 'no';
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function trustLines(view, isMe) {
|
|
35
|
+
const v = view || {};
|
|
36
|
+
const name = v.handle ? `@${String(v.handle).replace(/^@+/, '')}` : (v.user_id || 'this account');
|
|
37
|
+
const friends = Number(v.friends) || 0;
|
|
38
|
+
const lines = [isMe ? `${name} (you)` : name];
|
|
39
|
+
lines.push(` Trust score ${v.trust_score ?? 0}, from ${friends} ${friends === 1 ? 'friend' : 'friends'}`);
|
|
40
|
+
const trusted = yesNo(v.trusted_with_me);
|
|
41
|
+
if (trusted) lines.push(` Trusted with you: ${trusted}`);
|
|
42
|
+
if (v.shared_friends !== null && v.shared_friends !== undefined) {
|
|
43
|
+
lines.push(` Friends you share: ${v.shared_friends}`);
|
|
44
|
+
}
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function view(io, who, json) {
|
|
49
|
+
const isMe = !who;
|
|
50
|
+
const path = isMe ? '/trust/me' : `/trust/${encodeURIComponent(cleanTarget(who))}`;
|
|
51
|
+
const res = await call(io, path);
|
|
52
|
+
if (!res.ok) return reportError(io, res, `Could not load trust (${res.status})`);
|
|
53
|
+
if (json) {
|
|
54
|
+
io.log(JSON.stringify(res.data || {}, null, 2));
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
for (const line of trustLines(res.data, isMe)) io.log(line);
|
|
58
|
+
if (isMe && !(Number(res.data && res.data.friends) > 0)) {
|
|
59
|
+
io.log('\nTrust grows when people vouch for you. Find people: atris people <name>');
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function vouch(io, who, remove, json) {
|
|
65
|
+
const verb = remove ? 'unvouch' : 'vouch';
|
|
66
|
+
const target = cleanTarget(who);
|
|
67
|
+
if (!target || isHelpToken(target)) {
|
|
68
|
+
io.error(`Usage: atris trust ${verb} <handle>`);
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
const res = await call(io, `/trust/vouch/${encodeURIComponent(target)}`, {
|
|
72
|
+
method: remove ? 'DELETE' : 'POST',
|
|
73
|
+
});
|
|
74
|
+
if (!res.ok) return reportError(io, res, `Could not ${verb} (${res.status})`);
|
|
75
|
+
if (json) {
|
|
76
|
+
io.log(JSON.stringify(res.data || {}, null, 2));
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const label = targetLabel(who);
|
|
80
|
+
if (remove) {
|
|
81
|
+
io.log(`You took back your vouch for ${label}.`);
|
|
82
|
+
} else if (res.data && res.data.is_mutual) {
|
|
83
|
+
io.log(`You vouched for ${label}. You vouch for each other now.`);
|
|
84
|
+
} else {
|
|
85
|
+
io.log(`You vouched for ${label}.`);
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function printHelp(log = console.log) {
|
|
91
|
+
log(`atris trust, who trusts an account and who you vouch for
|
|
92
|
+
|
|
93
|
+
atris trust Your trust
|
|
94
|
+
atris trust <handle> Their trust, and the friends you share
|
|
95
|
+
atris trust vouch <handle> Vouch for someone (same as following them)
|
|
96
|
+
atris trust unvouch <handle> Take your vouch back
|
|
97
|
+
|
|
98
|
+
<handle> is @handle, a user id, or an email.
|
|
99
|
+
Trust score is how many friends an account has (people who follow each other).
|
|
100
|
+
Add --json for the raw answer.
|
|
101
|
+
Pay someone: atris pay <handle> <amount>`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function trustCommand(argv, ioOverrides) {
|
|
105
|
+
const io = createIo(ioOverrides);
|
|
106
|
+
const { json, rest } = splitFlags(argv || process.argv.slice(3));
|
|
107
|
+
const sub = rest[0];
|
|
108
|
+
if (isHelpToken(sub) || argsWantHelp(rest)) {
|
|
109
|
+
printHelp(io.log);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
if (sub === 'vouch') return vouch(io, rest[1], false, json);
|
|
113
|
+
if (sub === 'unvouch') return vouch(io, rest[1], true, json);
|
|
114
|
+
return view(io, sub, json);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { trustCommand, trustLines };
|
package/commands/zen.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
const { abandonedDaysFromEnv, checkZen, sideCopyLines } = require('../lib/zen');
|
|
4
4
|
const { applyZen } = require('../lib/zen-apply');
|
|
5
5
|
const { workspaceZen } = require('../lib/zen-workspace');
|
|
6
|
+
const { fleetFile, fleetRepos, fleetZen, printFleet } = require('../lib/zen-fleet');
|
|
6
7
|
|
|
7
8
|
function showZenHelp() {
|
|
8
9
|
console.log('usage: atris zen [--check] [--no-fetch] [--json]');
|
|
10
|
+
console.log(' atris zen --all [repo ...]');
|
|
9
11
|
console.log('');
|
|
10
12
|
console.log('Brings this repo to zen: code matches origin, nothing left lying around.');
|
|
11
13
|
console.log('Old stashes are archived, idle side copies put away, finished branches');
|
|
@@ -21,6 +23,9 @@ function showZenHelp() {
|
|
|
21
23
|
console.log(' --check Only look. Report what is out of place and change nothing.');
|
|
22
24
|
console.log(' --no-fetch Use the last fetched origin instead of fetching.');
|
|
23
25
|
console.log(' --json Print machine-readable results.');
|
|
26
|
+
console.log(' --all Zen every repo you list, one line each. Repos come from the');
|
|
27
|
+
console.log(' command line or ~/.atris/zen.json: {"repos": ["~/code/app"]}.');
|
|
28
|
+
console.log(' A repo with unsaved live data or secrets is skipped for you.');
|
|
24
29
|
console.log('');
|
|
25
30
|
console.log('Add more team-state paths in .atris/zen.json: {"team_state": ["logs/"]}');
|
|
26
31
|
}
|
|
@@ -151,6 +156,18 @@ function zenCommand(args = [], cwd = process.cwd()) {
|
|
|
151
156
|
const fetch = !args.includes('--no-fetch');
|
|
152
157
|
const json = args.includes('--json');
|
|
153
158
|
|
|
159
|
+
if (args.includes('--all')) {
|
|
160
|
+
const repos = fleetRepos(args.filter((a) => !a.startsWith('--')));
|
|
161
|
+
if (!repos.length) {
|
|
162
|
+
console.log(`zen: no repos listed. Name them (atris zen --all ~/code/app ~/code/site) or list them in ${fleetFile()}.`);
|
|
163
|
+
return 2;
|
|
164
|
+
}
|
|
165
|
+
const results = fleetZen({ repos, fetch });
|
|
166
|
+
if (json) console.log(JSON.stringify(results, null, 2));
|
|
167
|
+
else printFleet(results);
|
|
168
|
+
return results.every((r) => r.state === 'zen' || r.state === 'skipped') ? 0 : 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
154
171
|
if (args.includes('--check')) {
|
|
155
172
|
const result = checkZen({ cwd, fetch });
|
|
156
173
|
if (json) {
|