atris 3.82.18 → 3.82.20
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/engines/SKILL.md +17 -2
- package/ax +6 -2
- package/bin/atris.js +5 -0
- package/commands/ask.js +202 -13
- package/commands/engine.js +49 -7
- package/commands/film.js +191 -0
- package/commands/mission.js +11 -1
- package/commands/probe.js +108 -11
- package/lib/engine-ask.js +10 -1
- package/lib/engine-processes.js +7 -2
- package/lib/engine-registry.js +58 -2
- package/lib/fleet.js +1 -1
- package/lib/known-commands.js +1 -1
- package/lib/member-engine.js +66 -9
- package/lib/mission-core.js +7 -0
- package/lib/model-names.js +13 -0
- package/lib/runner-command.js +10 -0
- package/lib/runs-on.js +134 -0
- package/package.json +1 -1
package/lib/runs-on.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Where a team member does its work: "runs on" on a team line.
|
|
4
|
+
//
|
|
5
|
+
// - cosmo: claude, on: atris computer
|
|
6
|
+
// - amara: on: grok bot
|
|
7
|
+
//
|
|
8
|
+
// Three kinds of place:
|
|
9
|
+
// local this machine, shown by its own name ("Studio Mac mini")
|
|
10
|
+
// atris computer the computer Atris hosts for the person or business
|
|
11
|
+
// anything else a named bot on another machine ("grok bot", "claude duet")
|
|
12
|
+
//
|
|
13
|
+
// Naming an engine installed here ("on: muse") means that engine, locally.
|
|
14
|
+
// Only local members run here; the rest wait for their own machine.
|
|
15
|
+
//
|
|
16
|
+
// The other machine knows it is that place through ATRIS_RUNS_AS, a comma
|
|
17
|
+
// list of the names it answers to: ATRIS_RUNS_AS="grok bot" on the Grok bot's
|
|
18
|
+
// computer, ATRIS_RUNS_AS="atris computer" on the hosted one. Scheduled jobs
|
|
19
|
+
// rarely see shell settings, so the same list can live in ~/.atris/RUNS_AS
|
|
20
|
+
// (one line). A member seated there runs there and nowhere else.
|
|
21
|
+
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const { execFileSync } = require('child_process');
|
|
24
|
+
|
|
25
|
+
const LOCAL_WORDS = new Set(['', 'local', 'here', 'this mac', 'this computer', 'this machine', 'my mac', 'my laptop', 'mac', 'laptop']);
|
|
26
|
+
const ATRIS_COMPUTER_WORDS = new Set(['atris computer', 'atris cloud', 'cloud', 'computer', 'atris', 'your atris computer', 'business computer', 'personal computer', 'cloud computer']);
|
|
27
|
+
const ATRIS_COMPUTER = 'atris computer';
|
|
28
|
+
const KNOWN_LABELS = Object.freeze({
|
|
29
|
+
'atris computer': 'Atris computer',
|
|
30
|
+
'grok bot': 'Grok bot',
|
|
31
|
+
'claude duet': 'Claude Duet',
|
|
32
|
+
dots: 'Dots',
|
|
33
|
+
hermes: 'Hermes',
|
|
34
|
+
muse: 'Muse',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let cachedMachineName = null;
|
|
38
|
+
|
|
39
|
+
// The name a person gave this machine, the one AirDrop shows. Falls back to
|
|
40
|
+
// the host name without its .local tail.
|
|
41
|
+
function machineName() {
|
|
42
|
+
if (cachedMachineName !== null) return cachedMachineName;
|
|
43
|
+
let name = '';
|
|
44
|
+
if (process.env.ATRIS_MACHINE_NAME) name = process.env.ATRIS_MACHINE_NAME;
|
|
45
|
+
if (!name && process.platform === 'darwin') {
|
|
46
|
+
try {
|
|
47
|
+
// scheduled jobs often run without /usr/sbin on PATH, so call it by its
|
|
48
|
+
// full path; otherwise a job would see the network name instead
|
|
49
|
+
const scutil = require('fs').existsSync('/usr/sbin/scutil') ? '/usr/sbin/scutil' : 'scutil';
|
|
50
|
+
name = execFileSync(scutil, ['--get', 'ComputerName'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 2000 }).trim();
|
|
51
|
+
} catch { name = ''; }
|
|
52
|
+
}
|
|
53
|
+
if (!name) name = String(os.hostname() || '').replace(/\.local$/i, '');
|
|
54
|
+
cachedMachineName = name || 'this computer';
|
|
55
|
+
return cachedMachineName;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The place names this machine answers to, besides its own name.
|
|
59
|
+
function runsAsNames() {
|
|
60
|
+
let list = process.env.ATRIS_RUNS_AS;
|
|
61
|
+
if (list === undefined) {
|
|
62
|
+
try {
|
|
63
|
+
list = require('fs').readFileSync(require('path').join(os.homedir(), '.atris', 'RUNS_AS'), 'utf8').split(/\r?\n/)[0];
|
|
64
|
+
} catch { list = ''; }
|
|
65
|
+
}
|
|
66
|
+
return String(list || '')
|
|
67
|
+
.split(',')
|
|
68
|
+
.map((name) => cleanWords(name))
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.map((name) => (ATRIS_COMPUTER_WORDS.has(name) ? ATRIS_COMPUTER : name));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cleanWords(text) {
|
|
74
|
+
return String(text || '')
|
|
75
|
+
.replace(/[‘’]/g, "'")
|
|
76
|
+
.replace(/\s+/g, ' ')
|
|
77
|
+
.trim()
|
|
78
|
+
.toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function placeLabel(id) {
|
|
82
|
+
if (KNOWN_LABELS[id]) return KNOWN_LABELS[id];
|
|
83
|
+
return id.charAt(0).toUpperCase() + id.slice(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// What "on: <words>" means. isEngine(name) answers whether the words name an
|
|
87
|
+
// engine atris knows; it is passed in so this file stays free of the
|
|
88
|
+
// registry. Throws a plain sentence on words that cannot be a place.
|
|
89
|
+
function readRunsOn(words, options = {}) {
|
|
90
|
+
const raw = String(words || '').trim();
|
|
91
|
+
const clean = cleanWords(raw);
|
|
92
|
+
if (/[,:]/.test(clean)) throw new Error(`"${raw}" is not a place; use on: atris computer, on: local, or a bot name like on: grok bot`);
|
|
93
|
+
if (clean.length > 40) throw new Error(`"${raw}" is too long for a place name`);
|
|
94
|
+
if (LOCAL_WORDS.has(clean) || clean === cleanWords(machineName())) {
|
|
95
|
+
return { id: 'local', label: machineName(), here: true };
|
|
96
|
+
}
|
|
97
|
+
const answersTo = runsAsNames();
|
|
98
|
+
if (ATRIS_COMPUTER_WORDS.has(clean)) return { id: ATRIS_COMPUTER, label: placeLabel(ATRIS_COMPUTER), here: answersTo.includes(ATRIS_COMPUTER) };
|
|
99
|
+
const engine = typeof options.isEngine === 'function' ? options.isEngine(clean) : '';
|
|
100
|
+
if (engine) return { id: 'local', label: machineName(), here: true, engine };
|
|
101
|
+
return { id: clean, label: placeLabel(clean), here: answersTo.includes(clean) };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The "on:" part of a team line value, split off so the rest reads exactly
|
|
105
|
+
// as it always did. Returns { rest, on } where on is the words after "on:"
|
|
106
|
+
// or '' when the line names no place.
|
|
107
|
+
function splitRunsOn(value) {
|
|
108
|
+
const parts = String(value || '').split(',').map((part) => part.trim()).filter(Boolean);
|
|
109
|
+
let on = '';
|
|
110
|
+
const rest = [];
|
|
111
|
+
for (const part of parts) {
|
|
112
|
+
const match = /^on\s*:\s*(.*)$/i.exec(part);
|
|
113
|
+
if (match && !on) on = match[1].trim() || 'local';
|
|
114
|
+
else if (match) throw new Error('names two places; keep one on: part');
|
|
115
|
+
else rest.push(part);
|
|
116
|
+
}
|
|
117
|
+
return { rest: rest.join(', '), on };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The "runs on" text a table shows for one member.
|
|
121
|
+
function runsOnText(place, engineId) {
|
|
122
|
+
if (!place || place.here) {
|
|
123
|
+
const where = place && place.label ? place.label : machineName();
|
|
124
|
+
return engineId ? `${engineId} on ${where}` : where;
|
|
125
|
+
}
|
|
126
|
+
return place.label;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
machineName,
|
|
131
|
+
readRunsOn,
|
|
132
|
+
splitRunsOn,
|
|
133
|
+
runsOnText,
|
|
134
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.82.
|
|
3
|
+
"version": "3.82.20",
|
|
4
4
|
"description": "you say what you want in plain words. atris builds it, checks it, and shows you proof. project memory your coding agents share across sessions.",
|
|
5
5
|
"mcpName": "io.github.atrislabs/atris",
|
|
6
6
|
"main": "bin/atris.js",
|