atris 3.74.9 → 3.74.11
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/atris.md +78 -17
- package/atris.md +1 -10
- package/commands/auth.js +4 -3
- package/commands/computer.js +9 -7
- package/commands/context-sync.js +2 -1
- package/commands/integrations.js +2 -1
- package/commands/member.js +3 -3
- package/commands/ninety-nine.js +90 -30
- package/commands/orb.js +1 -1
- package/commands/pull.js +3 -3
- package/commands/push.js +2 -2
- package/commands/skill.js +5 -0
- package/commands/soul.js +2 -1
- package/commands/stream.js +3 -2
- package/commands/strings.js +1 -1
- package/commands/unknowns.js +1 -1
- package/commands/workflow.js +1 -1
- package/commands/zen.js +4 -3
- package/lib/auto-accept-certified.js +2 -3
- package/lib/autoland.js +4 -3
- package/lib/campaign-loop.js +12 -4
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/first-minute.js +1 -0
- package/lib/gm-bet.js +51 -48
- package/lib/gm-card.js +46 -19
- package/lib/gm-phone-icon.js +80 -0
- package/lib/gm-phone-page.js +803 -96
- package/lib/gm-phone.js +15 -4
- package/lib/journal.js +1 -1
- package/lib/noninteractive.js +5 -1
- package/lib/roster-swap.js +8 -3
- package/lib/task-decision.js +1 -1
- package/lib/values.js +25 -10
- package/lib/zen-apply.js +23 -2
- package/lib/zen.js +99 -4
- package/package.json +1 -1
- package/utils/auth.js +69 -7
package/lib/gm-card.js
CHANGED
|
@@ -97,7 +97,7 @@ function idea({ id, title, body, line = null, unstarted = null, concern = null }
|
|
|
97
97
|
line: line || unstarted || null,
|
|
98
98
|
options: [
|
|
99
99
|
answer(id, { id: 'do', label: 'do it', action: 'do', style: 'primary', confirm: 'tap again to start' }),
|
|
100
|
-
answer(id, { id: 'sharper', label: '
|
|
100
|
+
answer(id, { id: 'sharper', label: 'sharpen it', action: 'sharper' }),
|
|
101
101
|
answer(id, { id: 'skip', label: 'not now', action: 'skip' }),
|
|
102
102
|
],
|
|
103
103
|
input: sayInput(),
|
|
@@ -175,6 +175,11 @@ function missed({ id }) {
|
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// the way back from a reply that asks its own question names the card it returns to
|
|
179
|
+
const IDEA_KINDS = new Set(['idea', 'started']);
|
|
180
|
+
const ASK_KINDS = new Set(['question', 'yes', 'report', 'choice']);
|
|
181
|
+
const backToBase = base => (IDEA_KINDS.has(base.kind) ? 'back to the idea' : ASK_KINDS.has(base.kind) ? 'back to the question' : 'back');
|
|
182
|
+
|
|
178
183
|
// A reply to words said on a card. When it asks its own question (it ends
|
|
179
184
|
// in a question mark) only its answers show, with the box and a quiet way
|
|
180
185
|
// back to the first question. When it only explains, the first card's own
|
|
@@ -200,7 +205,7 @@ function reply({ id, base, text, thread = [], suggestions = [], confirm = null }
|
|
|
200
205
|
...card, asks: id,
|
|
201
206
|
options: [
|
|
202
207
|
...suggestions.map((s, i) => answer(id, { id: `r${i + 1}`, label: s, action: 'say', style: 'choice', binds: { text: s } })),
|
|
203
|
-
nav({ id: 'back', label: base.kind === 'deck' ? 'back to the job' : base.kind === 'member' ? `back to ${base.title}` :
|
|
208
|
+
nav({ id: 'back', label: base.kind === 'deck' ? 'back to the job' : base.kind === 'member' ? `back to ${base.title}` : backToBase(base), action: 'back' }),
|
|
204
209
|
],
|
|
205
210
|
input: base.input || sayInput(),
|
|
206
211
|
};
|
|
@@ -235,10 +240,10 @@ function team({ id, rows, line = null, deck = false }) {
|
|
|
235
240
|
options.push(nav({ id: `talk:${r.member}`, label: r.member, action: 'member', binds: { member: r.member }, cell: { row: i, column: 'member' } }));
|
|
236
241
|
if (r.engine !== 'person') options.push(nav({ id: `swap:${r.member}`, label: r.engine, action: 'swap', binds: { member: r.member }, cell: { row: i, column: 'engine' } }));
|
|
237
242
|
});
|
|
238
|
-
if (deck) options.push(nav({ id: 'deck', label: 'the jobs
|
|
243
|
+
if (deck) options.push(nav({ id: 'deck', label: 'go through the jobs', action: 'deck' }));
|
|
239
244
|
options.push(nav({ id: 'back', label: 'close', action: 'back' }));
|
|
240
245
|
return {
|
|
241
|
-
id, kind: 'team', asks: id, title: 'the team', body: rows.length ? 'tap a name to talk to them, or
|
|
246
|
+
id, kind: 'team', asks: id, title: 'the team', body: rows.length ? 'tap a name to talk to them, or their engine to change it.' : 'nobody is on the team yet.', line,
|
|
242
247
|
table: rows.length ? { columns: TEAM_COLUMNS, rows: rows.map(r => [r.member, r.role, r.ovr, r.engine, r.sentence]), status: rows.map(r => r.status) } : null,
|
|
243
248
|
options, input: null,
|
|
244
249
|
};
|
|
@@ -253,37 +258,39 @@ function member({ id, name, sentence: said, job = null, line = null }) {
|
|
|
253
258
|
};
|
|
254
259
|
}
|
|
255
260
|
|
|
256
|
-
// what a job in the deck is doing, when there is no yes on it to answer
|
|
261
|
+
// what a job in the deck is doing, when there is no yes on it to answer;
|
|
262
|
+
// how long it has been is the card's own time line, so it is said once
|
|
257
263
|
function deckLine(card) {
|
|
258
|
-
|
|
259
|
-
if (card.label === 'working') return `${card.member} is working on it${when}. nothing needs you here.`;
|
|
264
|
+
if (card.label === 'working') return `${card.member} is working on it. nothing needs you here.`;
|
|
260
265
|
if (card.label === 'you answered') return `you answered this, so it is back with ${card.member}.`;
|
|
261
|
-
return `${card.member} has not started it yet
|
|
266
|
+
return `${card.member} has not started it yet.`;
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
// one running job: its place in the deck, the yes it waits on, its table
|
|
265
270
|
// row, and a box to talk to its member
|
|
266
271
|
function deck({ id, at, total, card, columns, job = null }) {
|
|
267
272
|
const options = [];
|
|
273
|
+
// previous and next first: the page draws them at the top, beside the card's place in the deck
|
|
274
|
+
if (total > 1) options.push(nav({ id: 'prev', label: 'previous', action: 'flip', binds: { step: -1 } }), nav({ id: 'next', label: 'next', action: 'flip', binds: { step: 1 } }));
|
|
268
275
|
if (card.yes) {
|
|
269
276
|
const binds = { job: card.mission_id, key: card.yes.key, hash: card.yes.hash || null };
|
|
270
277
|
options.push(answer(id, { id: 'yes', label: 'yes', action: 'yes', style: 'primary', confirm: 'tap again to say yes', binds }));
|
|
271
278
|
options.push(answer(id, { id: 'no', label: 'no', action: 'no', binds }));
|
|
272
279
|
options.push(answer(id, { id: 'later', label: 'not now', action: 'later', binds }));
|
|
273
280
|
}
|
|
274
|
-
|
|
275
|
-
options.push(nav({ id: 'back', label: 'the whole team', action: 'back' }));
|
|
276
|
-
if (total > 1) options.push(nav({ id: 'next', label: 'next', action: 'flip', binds: { step: 1 } }));
|
|
281
|
+
options.push(nav({ id: 'back', label: 'back to the team', action: 'back' }));
|
|
277
282
|
return {
|
|
278
283
|
id, kind: 'deck', asks: id,
|
|
279
284
|
title: card.yes ? card.yes.what : card.rows[0].work,
|
|
280
285
|
body: card.yes ? card.yes.why : deckLine(card),
|
|
281
286
|
line: card.said || null,
|
|
282
|
-
|
|
287
|
+
// a place with its unit, so it never reads as "1 of 4 needs you"
|
|
288
|
+
count: `job ${at + 1} of ${total}`,
|
|
283
289
|
label: card.label,
|
|
284
290
|
time: card.time || null,
|
|
285
291
|
job,
|
|
286
|
-
|
|
292
|
+
// each row's status too, so its cell reads in the same color as on the team page
|
|
293
|
+
table: { columns, rows: card.rows.map(r => columns.map(k => r[k] || '')), status: card.rows.map(r => r.status || '') },
|
|
287
294
|
options,
|
|
288
295
|
input: { ...SAY, label: `talk to ${card.member}`, placeholder: `talk to ${card.member}`, action: 'talk' },
|
|
289
296
|
member: card.member,
|
|
@@ -294,7 +301,7 @@ function deck({ id, at, total, card, columns, job = null }) {
|
|
|
294
301
|
|
|
295
302
|
// The swap card renders the swap list as it comes: Atris models, then the
|
|
296
303
|
// owner's picks, then "more models", which turns this card's options into
|
|
297
|
-
// the full list. A list with no picks opens short too: the model in use and
|
|
304
|
+
// the full list, with the models already shown first, where they were. A list with no picks opens short too: the model in use and
|
|
298
305
|
// the next two, then more models, when that hides at least two. Without the
|
|
299
306
|
// list, every model by tool.
|
|
300
307
|
const SHORT_LIST = 3;
|
|
@@ -308,7 +315,13 @@ function swapChoices(choices, all = false) {
|
|
|
308
315
|
more = { more: true, label: 'more models', options: list };
|
|
309
316
|
list = [...list.filter(o => o.current), ...list.filter(o => !o.current)].slice(0, SHORT_LIST);
|
|
310
317
|
}
|
|
311
|
-
if (all && more)
|
|
318
|
+
if (all && more) {
|
|
319
|
+
// the models already on the card keep their places, and the rest come in under them
|
|
320
|
+
const same = (a, b) => a.engine === b.engine && a.model === b.model;
|
|
321
|
+
const full = more.options || [];
|
|
322
|
+
const kept = list.map(o => full.find(f => same(f, o))).filter(Boolean);
|
|
323
|
+
return { list: [...kept, ...full.filter(f => !kept.includes(f))], more: null };
|
|
324
|
+
}
|
|
312
325
|
return { list, more };
|
|
313
326
|
}
|
|
314
327
|
|
|
@@ -317,7 +330,9 @@ function swapChoices(choices, all = false) {
|
|
|
317
330
|
function swap({ id, name, choices, scope = 'member', all = false, line = null }) {
|
|
318
331
|
const { list, more } = swapChoices(choices, all);
|
|
319
332
|
const options = list.map((m) => {
|
|
320
|
-
|
|
333
|
+
// the tool under the model, unless it is the same word (codex on codex)
|
|
334
|
+
const tool = m.tool && String(m.tool).trim().toLowerCase() !== String(m.label || '').trim().toLowerCase() ? m.tool : null;
|
|
335
|
+
const detail = m.current ? 'now' : [tool, m.record].filter(Boolean).join(', ') || null;
|
|
321
336
|
return answer(id, { id: `m-${hash8([m.engine, m.model, m.label])}`, label: m.label, detail, action: m.current ? 'none' : 'swap_to', style: 'choice', chosen: Boolean(m.current), binds: { engine: m.engine, model: m.model, scope } });
|
|
322
337
|
});
|
|
323
338
|
if (more) options.push(nav({ id: 'more', label: more.label || 'more models', action: 'more' }));
|
|
@@ -381,10 +396,22 @@ function sequenceProblems(cards) {
|
|
|
381
396
|
// Words the owner reads say what a thing is, not where a number comes from:
|
|
382
397
|
// "300 more npm installs" reads "300 more installs".
|
|
383
398
|
const ownerText = t => (typeof t === 'string' ? t.replace(/\bnpm (installs?|downloads?)\b/gi, '$1') : t);
|
|
399
|
+
// Every card speaks lowercase, so an idea or a job written "Write the page"
|
|
400
|
+
// reads "write the page" like the cards around it. Only a plain capitalized
|
|
401
|
+
// first word moves: a capital, then lowercase letters, and no other capital
|
|
402
|
+
// before the next space. OpenAI, GitHub, YouTube, McKinsey, API, and CRM stay
|
|
403
|
+
// as written, and iPhone starts lowercase already.
|
|
404
|
+
const lowerFirst = t => (typeof t === 'string' && /^[A-Z][a-z]+[^A-Z\s]*(?=\s|$)/.test(t) ? t[0].toLowerCase() + t.slice(1) : t);
|
|
405
|
+
// Only the words that are an idea or a job move. A talk card, a swap card,
|
|
406
|
+
// and the team page are titled with a name, and member and model names in
|
|
407
|
+
// cells and buttons are never changed: only the deck's work cell and the
|
|
408
|
+
// ideas on a choice card are sentences of that kind.
|
|
409
|
+
const NAMED = new Set(['member', 'swap', 'team']);
|
|
384
410
|
function ownerWords(card) {
|
|
385
|
-
const
|
|
386
|
-
out
|
|
387
|
-
|
|
411
|
+
const title = ownerText(card.title);
|
|
412
|
+
const out = { ...card, title: NAMED.has(card.kind) ? title : lowerFirst(title), body: ownerText(card.body), line: ownerText(card.line) };
|
|
413
|
+
out.options = (card.options || []).map(o => ({ ...o, label: card.kind === 'choice' && o.style === 'option' ? lowerFirst(ownerText(o.label)) : ownerText(o.label), ...(o.detail ? { detail: ownerText(o.detail) } : {}) }));
|
|
414
|
+
if (card.table) out.table = { ...card.table, rows: card.table.rows.map(row => row.map((cell, j) => (card.table.columns[j] === 'work' ? lowerFirst(ownerText(cell)) : ownerText(cell)))) };
|
|
388
415
|
return out;
|
|
389
416
|
}
|
|
390
417
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The phone page's icon: the lava orb's amber on the page's dark, small. An
|
|
4
|
+
// svg for the browser tab, and a 180px png for the home screen (iPhones take
|
|
5
|
+
// only a png there), drawn here with node's own zlib, no packages. Both go
|
|
6
|
+
// into the page inline as data, so no request for them ever needs the link's key.
|
|
7
|
+
|
|
8
|
+
const zlib = require('node:zlib');
|
|
9
|
+
|
|
10
|
+
const BG = [0x14, 0x11, 0x10];
|
|
11
|
+
// the lava's colors, lit from the upper left: cream where lit, amber, then deep
|
|
12
|
+
const LIT = [255, 232, 170];
|
|
13
|
+
const AMBER = [251, 150, 44];
|
|
14
|
+
const DEEP = [150, 52, 14];
|
|
15
|
+
|
|
16
|
+
const SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">'
|
|
17
|
+
+ '<defs><radialGradient id="o" cx="40%" cy="38%" r="66%">'
|
|
18
|
+
+ '<stop offset="0" stop-color="#ffe8aa"/><stop offset=".38" stop-color="#fb962c"/><stop offset="1" stop-color="#96340e"/>'
|
|
19
|
+
+ '</radialGradient></defs>'
|
|
20
|
+
+ '<rect width="64" height="64" rx="14" fill="#141110"/><circle cx="32" cy="32" r="20" fill="url(#o)"/></svg>';
|
|
21
|
+
|
|
22
|
+
const iconSvg = () => `data:image/svg+xml,${encodeURIComponent(SVG)}`;
|
|
23
|
+
|
|
24
|
+
const mix = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);
|
|
25
|
+
const clamp01 = v => Math.max(0, Math.min(1, v));
|
|
26
|
+
|
|
27
|
+
// one pixel of the orb at (x, y) on a size-wide square, as [r, g, b]
|
|
28
|
+
function orbPixel(x, y, size) {
|
|
29
|
+
const c = size / 2, r = size * 0.33;
|
|
30
|
+
const d = Math.hypot(x - c, y - c);
|
|
31
|
+
// the lit point sits up and to the left; the color runs from it outward
|
|
32
|
+
const lit = Math.hypot(x - size * 0.43, y - size * 0.41) / (r * 1.32);
|
|
33
|
+
const face = lit < 0.38 ? mix(LIT, AMBER, lit / 0.38) : mix(AMBER, DEEP, clamp01((lit - 0.38) / 0.62));
|
|
34
|
+
// a soft glow just past the edge, then the page
|
|
35
|
+
const glow = mix(BG, AMBER, 0.16 * Math.pow(clamp01(1 - (d - r) / (r * 0.5)), 2));
|
|
36
|
+
const edge = clamp01(r + 0.5 - d);
|
|
37
|
+
return mix(glow, face, edge).map(Math.round);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function chunk(type, data) {
|
|
41
|
+
const len = Buffer.alloc(4);
|
|
42
|
+
len.writeUInt32BE(data.length);
|
|
43
|
+
const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);
|
|
44
|
+
const crc = Buffer.alloc(4);
|
|
45
|
+
crc.writeUInt32BE(zlib.crc32(body));
|
|
46
|
+
return Buffer.concat([len, body, crc]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// a size-wide square png of the orb, rgb with no alpha (the home screen fills
|
|
50
|
+
// see-through corners with black), each row filtered by its left neighbour so
|
|
51
|
+
// the smooth color compresses small
|
|
52
|
+
function orbPng(size = 180) {
|
|
53
|
+
const raw = Buffer.alloc(size * (1 + size * 3));
|
|
54
|
+
for (let y = 0; y < size; y++) {
|
|
55
|
+
const row = y * (1 + size * 3);
|
|
56
|
+
raw[row] = 1;
|
|
57
|
+
let prev = [0, 0, 0];
|
|
58
|
+
for (let x = 0; x < size; x++) {
|
|
59
|
+
const px = orbPixel(x + 0.5, y + 0.5, size);
|
|
60
|
+
for (let k = 0; k < 3; k++) raw[row + 1 + x * 3 + k] = (px[k] - prev[k]) & 0xff;
|
|
61
|
+
prev = px;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const head = Buffer.alloc(13);
|
|
65
|
+
head.writeUInt32BE(size, 0);
|
|
66
|
+
head.writeUInt32BE(size, 4);
|
|
67
|
+
head[8] = 8;
|
|
68
|
+
head[9] = 2;
|
|
69
|
+
return Buffer.concat([
|
|
70
|
+
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
71
|
+
chunk('IHDR', head),
|
|
72
|
+
chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),
|
|
73
|
+
chunk('IEND', Buffer.alloc(0)),
|
|
74
|
+
]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let touch = null;
|
|
78
|
+
const touchIconPng = () => (touch || (touch = `data:image/png;base64,${orbPng(180).toString('base64')}`));
|
|
79
|
+
|
|
80
|
+
module.exports = { iconSvg, touchIconPng };
|