atris 3.32.0 → 3.34.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 +43 -1
- package/atris.md +17 -0
- package/ax +364 -11
- package/bin/atris.js +62 -4
- package/commands/autoland.js +8 -2
- package/commands/engine.js +299 -0
- package/commands/integrations.js +147 -0
- package/commands/loops.js +212 -0
- package/commands/member.js +63 -10
- package/commands/mission.js +163 -18
- package/commands/task.js +173 -14
- package/commands/truth.js +70 -10
- package/lib/fleet.js +356 -0
- package/lib/inspect-fields.js +174 -0
- package/lib/runner-command.js +24 -0
- package/lib/task-db.js +38 -0
- package/package.json +2 -1
- package/scripts/agent_worktree.py +72 -0
package/bin/atris.js
CHANGED
|
@@ -127,6 +127,19 @@ function isOptionValue(args, index, optionNames) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
function applyRunnerFlags(args) {
|
|
130
|
+
// --engine <name> is the operator-facing spelling of --runner-profile:
|
|
131
|
+
// one flag rents a specific intelligence for this run.
|
|
132
|
+
const engineFlag = readOptionArg(args, '--engine');
|
|
133
|
+
if (engineFlag) {
|
|
134
|
+
const { canonicalEngineName } = require('../commands/engine');
|
|
135
|
+
const { RUNNER_PROFILE_NAMES } = require('../lib/runner-command');
|
|
136
|
+
const canonical = canonicalEngineName(engineFlag);
|
|
137
|
+
if (!canonical) {
|
|
138
|
+
console.error(`Unknown --engine "${engineFlag}". Known engines: ${RUNNER_PROFILE_NAMES.join(', ')}.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
process.env.ATRIS_RUNNER_PROFILE = canonical;
|
|
142
|
+
}
|
|
130
143
|
const runnerProfile = readOptionArg(args, '--runner-profile');
|
|
131
144
|
if (runnerProfile) {
|
|
132
145
|
// Fail fast at the CLI boundary: an unknown profile otherwise stays silent
|
|
@@ -153,8 +166,30 @@ function applyRunnerFlags(args) {
|
|
|
153
166
|
process.env.ATRIS_RUNNER_MODEL = runnerModel;
|
|
154
167
|
process.env.ATRIS_CLAUDE_MODEL = runnerModel;
|
|
155
168
|
}
|
|
169
|
+
// No per-run choice and no env: the workspace's saved engine
|
|
170
|
+
// (.atris/engine.json, written by `atris engine <name>`) becomes the
|
|
171
|
+
// profile for every loop spawn. For the loop commands themselves, fall all
|
|
172
|
+
// the way to the house engine (atris-fast when ax is installed) so the
|
|
173
|
+
// default intelligence is our own. Heartbeats stay engine-agnostic without
|
|
174
|
+
// any loop code knowing about engines.
|
|
175
|
+
if (!process.env.ATRIS_RUNNER_PROFILE) {
|
|
176
|
+
try {
|
|
177
|
+
const engine = require('../commands/engine');
|
|
178
|
+
const saved = engine.readSavedEngine();
|
|
179
|
+
if (saved) {
|
|
180
|
+
process.env.ATRIS_RUNNER_PROFILE = saved;
|
|
181
|
+
} else if (RUNNER_SPAWNING_COMMANDS.includes(command)) {
|
|
182
|
+
const resolved = engine.resolveDefaultEngine();
|
|
183
|
+
if (resolved.source === 'house') process.env.ATRIS_RUNNER_PROFILE = resolved.name;
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
156
187
|
}
|
|
157
188
|
|
|
189
|
+
// Commands whose ticks spawn a worker engine; only these pay the engine
|
|
190
|
+
// detection probe when nothing is configured.
|
|
191
|
+
const RUNNER_SPAWNING_COMMANDS = ['run', 'autopilot', 'mission', 'pulse', 'gm', 'spaceship'];
|
|
192
|
+
|
|
158
193
|
const isBusinessSyncSafetyCommand = command === 'sync'
|
|
159
194
|
&& (
|
|
160
195
|
commandArgs.includes('--status')
|
|
@@ -429,11 +464,13 @@ function showHelp() {
|
|
|
429
464
|
console.log('Quick Start:');
|
|
430
465
|
console.log('');
|
|
431
466
|
console.log(' 1. atris Open a persistent AI computer for this workspace');
|
|
467
|
+
console.log(' npx atris Same command after a local project install');
|
|
432
468
|
console.log(' 2. Describe what you want run, built, researched, or validated');
|
|
433
469
|
console.log(' 3. Atris acts with context, memory, tools, and a review loop');
|
|
434
470
|
console.log('');
|
|
435
471
|
console.log('Common invocations:');
|
|
436
|
-
console.log(' atris init');
|
|
472
|
+
console.log(' atris init Global install: initialize this project');
|
|
473
|
+
console.log(' npx atris init Local install: initialize this project');
|
|
437
474
|
console.log(' atris computer');
|
|
438
475
|
console.log(' atris business init "My Company"');
|
|
439
476
|
console.log(' atris run');
|
|
@@ -497,6 +534,7 @@ function showHelp() {
|
|
|
497
534
|
console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
|
|
498
535
|
console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
|
|
499
536
|
console.log(' autoland - Approve the policy once; certified work lands itself, you keep irreversible calls');
|
|
537
|
+
console.log(' engine - Bring any intelligence: roster of installed coding CLIs, default engine, --engine per run, `engine test` preflight');
|
|
500
538
|
console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
|
|
501
539
|
console.log(' visualize - Generate a Slack/deck-ready visual from a prompt');
|
|
502
540
|
console.log(' youtube - Process YouTube videos with timestamped transcript-first analysis');
|
|
@@ -556,6 +594,7 @@ function showHelp() {
|
|
|
556
594
|
console.log(' console - Start/attach always-on coding console (tmux daemon)');
|
|
557
595
|
console.log(' soul - Show, snapshot, or fork workspace identity');
|
|
558
596
|
console.log(' fleet - Inspect local fleet status');
|
|
597
|
+
console.log(' loops - Background loops board: what runs, what died, start/stop');
|
|
559
598
|
console.log(' agent - Select cloud agent, spawn worker requests, or run `agent doctor`');
|
|
560
599
|
console.log(' chat - Chat with Atris 2 Fast in this workspace (--agent for cloud agent; or: atris chat scan)');
|
|
561
600
|
console.log(' fast - Chat with Atris2 Fast');
|
|
@@ -567,7 +606,7 @@ function showHelp() {
|
|
|
567
606
|
console.log(' accounts - Manage accounts (list, add, remove)');
|
|
568
607
|
console.log('');
|
|
569
608
|
console.log('Integrations:');
|
|
570
|
-
console.log(' gmail - Email commands (inbox, read)');
|
|
609
|
+
console.log(' gmail - Email commands (inbox, read, archive)');
|
|
571
610
|
console.log(' calendar - Calendar commands (today, week)');
|
|
572
611
|
console.log(' twitter - Twitter commands (post)');
|
|
573
612
|
console.log(' slack - Slack commands (channels)');
|
|
@@ -937,7 +976,7 @@ const knownCommands = ['init', 'log', 'now', 'radar', 'ctop', 'launchpad', 'stat
|
|
|
937
976
|
'clean', 'harvest', 'verify', 'search', 'skill', 'member', 'codex-goal', 'app', 'apps', 'learn', 'lesson', 'plugin', 'experiments', 'receipt', 'proof', 'openclaw', 'pull', 'push', 'live', 'align', 'terminal', 'computer', 'diff', 'business', 'sync', 'youtube',
|
|
938
977
|
'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'probe', 'worktree', 'land', 'autoland', 'aeo', 'slop', 'strings', 'security-review', 'secure', 'deck', 'site', 'theme', 'card', 'reel', 'improve', 'xp', 'play', 'gm', 'x', 'recap', 'signup', 'clarity', 'moves',
|
|
939
978
|
'gmail', 'calendar', 'twitter', 'slack', 'imessage', 'integrations', 'setup', 'clean-workspace', 'cw',
|
|
940
|
-
'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'compile', 'spaceship', 'truth', 'sign'];
|
|
979
|
+
'fork', 'browse', 'publish', 'sleep', 'wake', 'feedback', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'loops', 'compile', 'spaceship', 'truth', 'sign', 'engine', 'engines'];
|
|
941
980
|
|
|
942
981
|
// Check if command is an atris.md spec file - triggers welcome visualization
|
|
943
982
|
function isSpecFile(cmd) {
|
|
@@ -1059,7 +1098,11 @@ async function interactiveEntry(userInput) {
|
|
|
1059
1098
|
|
|
1060
1099
|
// Fresh install - offer init
|
|
1061
1100
|
if (state.state === 'fresh') {
|
|
1062
|
-
console.log('\nNo atris/ folder found.
|
|
1101
|
+
console.log('\nNo atris/ folder found.');
|
|
1102
|
+
console.log('');
|
|
1103
|
+
console.log('Start here:');
|
|
1104
|
+
console.log(' atris init if Atris is installed globally');
|
|
1105
|
+
console.log(' npx atris init if Atris was installed in this project');
|
|
1063
1106
|
return;
|
|
1064
1107
|
}
|
|
1065
1108
|
|
|
@@ -2225,6 +2268,14 @@ if (command === 'init') {
|
|
|
2225
2268
|
require('../commands/fleet').fleet(args)
|
|
2226
2269
|
.then(() => process.exit(0))
|
|
2227
2270
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2271
|
+
} else if (command === 'loops') {
|
|
2272
|
+
try {
|
|
2273
|
+
require('../commands/loops').loopsCommand(process.argv[3], ...process.argv.slice(4));
|
|
2274
|
+
process.exit(0);
|
|
2275
|
+
} catch (error) {
|
|
2276
|
+
console.error(`\n✗ Error: ${error.message || error}`);
|
|
2277
|
+
process.exit(1);
|
|
2278
|
+
}
|
|
2228
2279
|
} else if (command === 'spaceship') {
|
|
2229
2280
|
const args = process.argv.slice(3);
|
|
2230
2281
|
require('../commands/spaceship').spaceship(args)
|
|
@@ -2272,6 +2323,13 @@ if (command === 'init') {
|
|
|
2272
2323
|
Promise.resolve(require('../commands/compile').compileCommand(subcommand, ...args))
|
|
2273
2324
|
.then(() => process.exit(process.exitCode || 0))
|
|
2274
2325
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2326
|
+
} else if (command === 'engine' || command === 'engines') {
|
|
2327
|
+
// Engine: bring any intelligence — roster of installed coding CLIs, default
|
|
2328
|
+
// engine per workspace, --engine <name> rides any loop for one run.
|
|
2329
|
+
try {
|
|
2330
|
+
const engineArgs = command === 'engines' && process.argv.length <= 3 ? [] : process.argv.slice(3);
|
|
2331
|
+
process.exit(require('../commands/engine').engineCommand(engineArgs) || 0);
|
|
2332
|
+
} catch (err) { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); }
|
|
2275
2333
|
} else if (command === 'sign') {
|
|
2276
2334
|
// Sign: prepare-commit-msg hook that credits Atris as co-author on commits in atris workspaces.
|
|
2277
2335
|
try { process.exit(require('../commands/sign').signCommand(process.argv[3]) || 0); }
|
package/commands/autoland.js
CHANGED
|
@@ -357,15 +357,21 @@ function showHelp() {
|
|
|
357
357
|
console.log(' on you past 24h');
|
|
358
358
|
console.log(' atris autoland off back to approving every item');
|
|
359
359
|
console.log(' atris autoland digest [--send] the daily message, now');
|
|
360
|
-
console.log(' atris autoland tick
|
|
360
|
+
console.log(' atris autoland tick [--json] one heartbeat (what the hourly cron runs)');
|
|
361
|
+
console.log('');
|
|
362
|
+
console.log('help is always read-only: atris autoland tick --help never lands work.');
|
|
361
363
|
console.log('');
|
|
362
364
|
return 0;
|
|
363
365
|
}
|
|
364
366
|
|
|
367
|
+
function wantsHelp(args = []) {
|
|
368
|
+
return args.some((arg) => arg === 'help' || arg === '--help' || arg === '-h');
|
|
369
|
+
}
|
|
370
|
+
|
|
365
371
|
function autolandCommand(args = []) {
|
|
366
372
|
const [sub, ...rest] = args;
|
|
373
|
+
if (wantsHelp(args)) return showHelp();
|
|
367
374
|
if (!sub || sub.startsWith('--')) return showStatus(repoRoot(), args);
|
|
368
|
-
if (sub === 'help' || sub === '--help' || sub === '-h') return showHelp();
|
|
369
375
|
const root = repoRoot();
|
|
370
376
|
if (sub === 'on') return turnOn(root, rest);
|
|
371
377
|
if (sub === 'off') return turnOff(root);
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// atris engine: bring any intelligence.
|
|
4
|
+
// Every installed headless coding CLI is a swappable worker behind one
|
|
5
|
+
// contract (bounded prompt in, verified proof out, engines never
|
|
6
|
+
// self-certify). This command shows the roster, flips the default, and the
|
|
7
|
+
// same names ride --engine on mission run / autopilot / run.
|
|
8
|
+
//
|
|
9
|
+
// atris engine roster + current default
|
|
10
|
+
// atris engine cursor make cursor the default engine here
|
|
11
|
+
// atris engine reset back to the house default (atris-fast)
|
|
12
|
+
//
|
|
13
|
+
// The default persists to .atris/engine.json. Per-run flags and
|
|
14
|
+
// ATRIS_RUNNER_PROFILE always beat the file.
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const { spawnSync } = require('child_process');
|
|
20
|
+
const {
|
|
21
|
+
RUNNER_PROFILE_DEFS,
|
|
22
|
+
RUNNER_PROFILE_ALIASES,
|
|
23
|
+
RUNNER_PROFILE_NAMES,
|
|
24
|
+
buildRunnerCommand,
|
|
25
|
+
} = require('../lib/runner-command');
|
|
26
|
+
|
|
27
|
+
const HOUSE_ENGINE = 'atris-fast';
|
|
28
|
+
|
|
29
|
+
function engineFile(root = process.cwd()) {
|
|
30
|
+
return path.join(root, '.atris', 'engine.json');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function binInstalled(bin) {
|
|
34
|
+
const safe = String(bin || '').replace(/[^A-Za-z0-9_.-]/g, '');
|
|
35
|
+
if (!safe) return false;
|
|
36
|
+
const probe = spawnSync('sh', ['-c', `command -v ${safe}`], { encoding: 'utf8' });
|
|
37
|
+
return probe.status === 0 && Boolean(String(probe.stdout || '').trim());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function canonicalEngineName(name) {
|
|
41
|
+
const trimmed = String(name || '').trim();
|
|
42
|
+
if (!trimmed) return '';
|
|
43
|
+
if (RUNNER_PROFILE_DEFS[trimmed]) return trimmed;
|
|
44
|
+
if (RUNNER_PROFILE_ALIASES[trimmed]) return RUNNER_PROFILE_ALIASES[trimmed];
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readSavedEngine(root = process.cwd()) {
|
|
49
|
+
try {
|
|
50
|
+
const saved = JSON.parse(fs.readFileSync(engineFile(root), 'utf8'));
|
|
51
|
+
return canonicalEngineName(saved.default);
|
|
52
|
+
} catch {
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The default engine for this workspace, in precedence order:
|
|
58
|
+
// env (per-run flags land here) -> .atris/engine.json -> house default.
|
|
59
|
+
// The house default is our own intelligence when it is installed.
|
|
60
|
+
function resolveDefaultEngine(root = process.cwd()) {
|
|
61
|
+
const env = canonicalEngineName(process.env.ATRIS_RUNNER_PROFILE);
|
|
62
|
+
if (env) return { name: env, source: 'env' };
|
|
63
|
+
const saved = readSavedEngine(root);
|
|
64
|
+
if (saved) return { name: saved, source: 'saved' };
|
|
65
|
+
if (binInstalled(RUNNER_PROFILE_DEFS[HOUSE_ENGINE].bin)) return { name: HOUSE_ENGINE, source: 'house' };
|
|
66
|
+
const fallback = RUNNER_PROFILE_NAMES.find((name) => binInstalled(RUNNER_PROFILE_DEFS[name].bin));
|
|
67
|
+
return fallback ? { name: fallback, source: 'detected' } : { name: HOUSE_ENGINE, source: 'none' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function roster(root = process.cwd()) {
|
|
71
|
+
const current = resolveDefaultEngine(root);
|
|
72
|
+
return RUNNER_PROFILE_NAMES.map((name) => ({
|
|
73
|
+
name,
|
|
74
|
+
bin: RUNNER_PROFILE_DEFS[name].bin,
|
|
75
|
+
installed: binInstalled(RUNNER_PROFILE_DEFS[name].bin),
|
|
76
|
+
default: name === current.name,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function setEngine(name, root = process.cwd()) {
|
|
81
|
+
const canonical = canonicalEngineName(name);
|
|
82
|
+
if (!canonical) {
|
|
83
|
+
throw new Error(`Unknown engine "${name}". Known engines: ${RUNNER_PROFILE_NAMES.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
const file = engineFile(root);
|
|
86
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
87
|
+
fs.writeFileSync(file, `${JSON.stringify({ default: canonical, set_at: new Date().toISOString() }, null, 2)}\n`);
|
|
88
|
+
return canonical;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resetEngine(root = process.cwd()) {
|
|
92
|
+
try { fs.unlinkSync(engineFile(root)); return true; } catch { return false; }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function printRoster(root) {
|
|
96
|
+
const list = roster(root);
|
|
97
|
+
const found = list.filter((e) => e.installed).length;
|
|
98
|
+
const current = resolveDefaultEngine(root);
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(` engines — ${found} intelligence${found === 1 ? '' : 's'} found`);
|
|
101
|
+
console.log('');
|
|
102
|
+
for (const engine of list) {
|
|
103
|
+
const mark = engine.default ? '→' : ' ';
|
|
104
|
+
const state = engine.installed ? 'ready' : 'not installed';
|
|
105
|
+
console.log(` ${mark} ${engine.name.padEnd(12)} ${state}`);
|
|
106
|
+
}
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(` default: ${current.name}${current.source === 'saved' ? ' (set here)' : current.source === 'env' ? ' (this session)' : ''}`);
|
|
109
|
+
console.log(` switch: atris engine <name> · one run: --engine <name> on mission run / autopilot / run`);
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Preflight: run one engine CLI headless with a reply-OK prompt and report
|
|
114
|
+
// pass/fail. A dead login, missing binary, or hung spawn is a one-command
|
|
115
|
+
// diagnosis instead of a failed overnight flight. `name` is canonical.
|
|
116
|
+
const PROBE_PROMPT = 'Reply with exactly the two characters: OK';
|
|
117
|
+
// Real engines think before replying: cursor measured at ~68s for a one-word
|
|
118
|
+
// answer on 2026-07-02. 30s produced a false FAIL on a healthy engine.
|
|
119
|
+
const PROBE_DEFAULT_TIMEOUT_MS = 120000;
|
|
120
|
+
|
|
121
|
+
function probeEngine(name, { timeout = PROBE_DEFAULT_TIMEOUT_MS } = {}) {
|
|
122
|
+
const def = RUNNER_PROFILE_DEFS[name];
|
|
123
|
+
if (!binInstalled(def.bin)) {
|
|
124
|
+
return {
|
|
125
|
+
engine: name,
|
|
126
|
+
bin: def.bin,
|
|
127
|
+
pass: false,
|
|
128
|
+
reason: 'not-installed',
|
|
129
|
+
message: `${def.bin} CLI not installed`,
|
|
130
|
+
stdout: '',
|
|
131
|
+
stderr: '',
|
|
132
|
+
durationMs: 0,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'atris-engine-probe-'));
|
|
136
|
+
const promptFile = path.join(tmpDir, 'prompt.txt');
|
|
137
|
+
fs.writeFileSync(promptFile, `${PROBE_PROMPT}\n`);
|
|
138
|
+
let cmd;
|
|
139
|
+
const prevProfile = process.env.ATRIS_RUNNER_PROFILE;
|
|
140
|
+
process.env.ATRIS_RUNNER_PROFILE = name;
|
|
141
|
+
try {
|
|
142
|
+
cmd = buildRunnerCommand({ promptFile });
|
|
143
|
+
} finally {
|
|
144
|
+
if (prevProfile === undefined) delete process.env.ATRIS_RUNNER_PROFILE;
|
|
145
|
+
else process.env.ATRIS_RUNNER_PROFILE = prevProfile;
|
|
146
|
+
}
|
|
147
|
+
const start = Date.now();
|
|
148
|
+
let res;
|
|
149
|
+
try {
|
|
150
|
+
res = spawnSync('sh', ['-c', cmd], { encoding: 'utf8', timeout });
|
|
151
|
+
} catch (err) {
|
|
152
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
153
|
+
return {
|
|
154
|
+
engine: name,
|
|
155
|
+
bin: def.bin,
|
|
156
|
+
pass: false,
|
|
157
|
+
reason: 'spawn-error',
|
|
158
|
+
message: String(err && err.message ? err.message : err),
|
|
159
|
+
stdout: '',
|
|
160
|
+
stderr: '',
|
|
161
|
+
durationMs: Date.now() - start,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const durationMs = Date.now() - start;
|
|
165
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
166
|
+
const stdout = res.stdout || '';
|
|
167
|
+
const stderr = res.stderr || '';
|
|
168
|
+
const timedOut = res.status === null && Boolean(res.signal) && String(res.signal).toLowerCase().includes('term');
|
|
169
|
+
const combined = `${stdout}\n${stderr}`.trim();
|
|
170
|
+
const ok = res.status === 0 && /OK/i.test(combined);
|
|
171
|
+
let reason;
|
|
172
|
+
if (ok) reason = 'ok';
|
|
173
|
+
else if (timedOut) reason = 'timeout';
|
|
174
|
+
else if (res.status !== 0 && res.status !== null) reason = 'bad-exit';
|
|
175
|
+
else if (!/OK/i.test(combined)) reason = 'no-ok';
|
|
176
|
+
else reason = 'unknown';
|
|
177
|
+
return {
|
|
178
|
+
engine: name,
|
|
179
|
+
bin: def.bin,
|
|
180
|
+
pass: ok,
|
|
181
|
+
reason,
|
|
182
|
+
message: ok
|
|
183
|
+
? 'responded OK'
|
|
184
|
+
: (timedOut ? `no reply within ${timeout}ms` : `did not reply OK (exit ${res.status}, signal ${res.signal})`),
|
|
185
|
+
stdout,
|
|
186
|
+
stderr,
|
|
187
|
+
durationMs,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function runEngineTest(targets, { json, root } = {}) {
|
|
192
|
+
let enginesToTest;
|
|
193
|
+
if (targets && targets.length) {
|
|
194
|
+
enginesToTest = targets.map((n) => {
|
|
195
|
+
const c = canonicalEngineName(n);
|
|
196
|
+
if (!c) {
|
|
197
|
+
throw new Error(`Unknown engine "${n}". Known engines: ${RUNNER_PROFILE_NAMES.join(', ')}`);
|
|
198
|
+
}
|
|
199
|
+
return c;
|
|
200
|
+
});
|
|
201
|
+
} else {
|
|
202
|
+
enginesToTest = RUNNER_PROFILE_NAMES.filter((n) => binInstalled(RUNNER_PROFILE_DEFS[n].bin));
|
|
203
|
+
if (!enginesToTest.length) {
|
|
204
|
+
if (json) {
|
|
205
|
+
console.log(JSON.stringify({ ok: false, results: [], summary: { pass: 0, fail: 0 } }, null, 2));
|
|
206
|
+
} else {
|
|
207
|
+
console.error('\n no installed engines to test\n');
|
|
208
|
+
}
|
|
209
|
+
return 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const results = enginesToTest.map((name) => probeEngine(name));
|
|
213
|
+
const failures = results.filter((r) => !r.pass);
|
|
214
|
+
const passed = results.length - failures.length;
|
|
215
|
+
if (json) {
|
|
216
|
+
console.log(JSON.stringify({
|
|
217
|
+
ok: failures.length === 0,
|
|
218
|
+
results,
|
|
219
|
+
summary: { pass: passed, fail: failures.length },
|
|
220
|
+
}, null, 2));
|
|
221
|
+
} else {
|
|
222
|
+
console.log('');
|
|
223
|
+
for (const r of results) {
|
|
224
|
+
const mark = r.pass ? '✓' : '✗';
|
|
225
|
+
const line = r.pass
|
|
226
|
+
? ` ${mark} ${r.engine.padEnd(12)} pass — ${r.message} (${r.durationMs}ms)`
|
|
227
|
+
: ` ${mark} ${r.engine.padEnd(12)} FAIL — ${r.message}`;
|
|
228
|
+
if (r.pass) console.log(line);
|
|
229
|
+
else console.error(line);
|
|
230
|
+
}
|
|
231
|
+
console.log('');
|
|
232
|
+
if (failures.length) {
|
|
233
|
+
console.error(` ${failures.length} engine${failures.length === 1 ? '' : 's'} failed: ${failures.map((f) => f.engine).join(', ')}`);
|
|
234
|
+
console.error(` fix the login/binary, then re-run: atris engine test${targets && targets.length ? ' ' + targets.join(' ') : ''}`);
|
|
235
|
+
} else {
|
|
236
|
+
console.log(` all engines responded — clear for flight`);
|
|
237
|
+
}
|
|
238
|
+
console.log('');
|
|
239
|
+
}
|
|
240
|
+
return failures.length ? 1 : 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function engineCommand(args = []) {
|
|
244
|
+
const json = args.includes('--json');
|
|
245
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
246
|
+
const sub = (positional[0] || '').trim();
|
|
247
|
+
const root = process.cwd();
|
|
248
|
+
|
|
249
|
+
if (sub === 'test') {
|
|
250
|
+
return runEngineTest(positional.slice(1), { json, root });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!sub || sub === 'list' || sub === 'status') {
|
|
254
|
+
if (json) {
|
|
255
|
+
const current = resolveDefaultEngine(root);
|
|
256
|
+
console.log(JSON.stringify({ engines: roster(root), default: current.name, source: current.source }, null, 2));
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
printRoster(root);
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (sub === 'reset' || sub === 'off') {
|
|
264
|
+
const removed = resetEngine(root);
|
|
265
|
+
console.log(removed
|
|
266
|
+
? `\n default engine reset — back to ${resolveDefaultEngine(root).name}\n`
|
|
267
|
+
: `\n nothing to reset — no engine was set here\n`);
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (sub === 'help') {
|
|
272
|
+
console.log('\n atris engine roster + current default\n atris engine <name> make that engine the default here\n atris engine test [name] preflight: run the engine CLI headless, report pass/fail\n atris engine reset back to the house default\n --engine <name> one run on that engine (mission run / autopilot / run)\n');
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// atris engine <name> — flip the default.
|
|
277
|
+
const canonical = setEngine(sub, root);
|
|
278
|
+
const def = RUNNER_PROFILE_DEFS[canonical];
|
|
279
|
+
const installed = binInstalled(def.bin);
|
|
280
|
+
console.log('');
|
|
281
|
+
console.log(` default engine: ${canonical}`);
|
|
282
|
+
if (!installed) console.log(` heads up: its CLI (${def.bin}) is not installed here yet — runs will fail until it is.`);
|
|
283
|
+
console.log(` every mission run / autopilot / run tick now rides it. one-off: --engine <name>. undo: atris engine reset`);
|
|
284
|
+
console.log('');
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = {
|
|
289
|
+
engineCommand,
|
|
290
|
+
resolveDefaultEngine,
|
|
291
|
+
canonicalEngineName,
|
|
292
|
+
readSavedEngine,
|
|
293
|
+
setEngine,
|
|
294
|
+
resetEngine,
|
|
295
|
+
roster,
|
|
296
|
+
probeEngine,
|
|
297
|
+
runEngineTest,
|
|
298
|
+
HOUSE_ENGINE,
|
|
299
|
+
};
|
package/commands/integrations.js
CHANGED
|
@@ -124,6 +124,32 @@ async function gmailRead(messageId) {
|
|
|
124
124
|
console.log(msg.body || msg.snippet || '(no body)');
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
async function gmailArchive(messageIds) {
|
|
128
|
+
const ids = (messageIds || []).map(id => String(id || '').trim()).filter(Boolean);
|
|
129
|
+
if (!ids.length) {
|
|
130
|
+
console.error('Usage: atris gmail archive <message_id> [<message_id> ...]');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const token = await getAuthToken();
|
|
135
|
+
|
|
136
|
+
console.log(`Archiving ${ids.length} message${ids.length === 1 ? '' : 's'}...`);
|
|
137
|
+
|
|
138
|
+
const result = await apiRequestJson('/integrations/gmail/messages/batch-archive', {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
token,
|
|
141
|
+
body: { message_ids: ids },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!result.ok) {
|
|
145
|
+
console.error(`Error: ${result.error || 'Failed to archive'}`);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const archived = result.data?.archived ?? result.data?.count ?? ids.length;
|
|
150
|
+
console.log(`Archived ${archived} message${archived === 1 ? '' : 's'}. They stay searchable in All Mail.`);
|
|
151
|
+
}
|
|
152
|
+
|
|
127
153
|
async function gmailCommand(subcommand, ...args) {
|
|
128
154
|
switch (subcommand) {
|
|
129
155
|
case 'inbox':
|
|
@@ -133,10 +159,14 @@ async function gmailCommand(subcommand, ...args) {
|
|
|
133
159
|
case 'read':
|
|
134
160
|
await gmailRead(args[0]);
|
|
135
161
|
break;
|
|
162
|
+
case 'archive':
|
|
163
|
+
await gmailArchive(args);
|
|
164
|
+
break;
|
|
136
165
|
default:
|
|
137
166
|
console.log('Gmail commands:');
|
|
138
167
|
console.log(' atris gmail inbox - List recent emails');
|
|
139
168
|
console.log(' atris gmail read <id> - Read specific email');
|
|
169
|
+
console.log(' atris gmail archive <id> [...] - Archive messages (reversible, All Mail keeps them)');
|
|
140
170
|
}
|
|
141
171
|
}
|
|
142
172
|
|
|
@@ -474,6 +504,58 @@ async function calendarDate(dateText) {
|
|
|
474
504
|
await calendarRange(`Events on ${dateText}`, { timeMin: start.toISOString(), timeMax: end.toISOString() });
|
|
475
505
|
}
|
|
476
506
|
|
|
507
|
+
async function calendarAdd(args) {
|
|
508
|
+
// atris calendar add "Title" --date YYYY-MM-DD --from HH:MM --to HH:MM
|
|
509
|
+
// or: --start <ISO> --end <ISO>; optional --location, --description
|
|
510
|
+
const rest = [...(args || [])];
|
|
511
|
+
const flags = {};
|
|
512
|
+
const positional = [];
|
|
513
|
+
for (let i = 0; i < rest.length; i++) {
|
|
514
|
+
const arg = String(rest[i] || '');
|
|
515
|
+
if (arg.startsWith('--')) {
|
|
516
|
+
flags[arg.slice(2)] = String(rest[i + 1] || '');
|
|
517
|
+
i += 1;
|
|
518
|
+
} else {
|
|
519
|
+
positional.push(arg);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const summary = positional.join(' ').trim();
|
|
523
|
+
let start = String(flags.start || '').trim();
|
|
524
|
+
let end = String(flags.end || '').trim();
|
|
525
|
+
if (!start && flags.date && flags.from) start = `${flags.date}T${flags.from}:00`;
|
|
526
|
+
if (!end && flags.date && flags.to) end = `${flags.date}T${flags.to}:00`;
|
|
527
|
+
if (!summary || !start || !end) {
|
|
528
|
+
console.error('Usage: atris calendar add "Title" --date YYYY-MM-DD --from HH:MM --to HH:MM');
|
|
529
|
+
console.error(' or: atris calendar add "Title" --start <ISO datetime> --end <ISO datetime>');
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const token = await getAuthToken();
|
|
534
|
+
|
|
535
|
+
console.log(`Creating event: ${summary} (${start} to ${end})...`);
|
|
536
|
+
|
|
537
|
+
const body = { summary, start, end };
|
|
538
|
+
if (flags.location) body.location = flags.location;
|
|
539
|
+
if (flags.description) body.description = flags.description;
|
|
540
|
+
if (flags.timezone) body.timezone = flags.timezone;
|
|
541
|
+
|
|
542
|
+
const result = await apiRequestJson('/integrations/google-calendar/events', {
|
|
543
|
+
method: 'POST',
|
|
544
|
+
token,
|
|
545
|
+
body,
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
if (!result.ok) {
|
|
549
|
+
console.error(`Error: ${result.error || 'Failed to create event'}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const created = result.data || {};
|
|
554
|
+
console.log(`Created: ${created.summary || summary}`);
|
|
555
|
+
if (created.htmlLink) console.log(`Link: ${created.htmlLink}`);
|
|
556
|
+
console.log(`Verified on calendar: ${created.verified ? 'yes' : 'no'}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
477
559
|
async function calendarCommand(subcommand, ...args) {
|
|
478
560
|
switch (subcommand) {
|
|
479
561
|
case 'today':
|
|
@@ -492,6 +574,10 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
492
574
|
case 'search':
|
|
493
575
|
await calendarSearch(args);
|
|
494
576
|
break;
|
|
577
|
+
case 'add':
|
|
578
|
+
case 'create':
|
|
579
|
+
await calendarAdd(args);
|
|
580
|
+
break;
|
|
495
581
|
default:
|
|
496
582
|
console.log('Calendar commands:');
|
|
497
583
|
console.log(' atris calendar today - Show today\'s events');
|
|
@@ -499,6 +585,7 @@ async function calendarCommand(subcommand, ...args) {
|
|
|
499
585
|
console.log(' atris calendar week - Show this week\'s events');
|
|
500
586
|
console.log(' atris calendar date YYYY-MM-DD - Show events on a date');
|
|
501
587
|
console.log(' atris calendar search <query> [--days 30] [--limit 20] [--timeout-ms 10000] [--json] - Search upcoming events');
|
|
588
|
+
console.log(' atris calendar add \"Title\" --date YYYY-MM-DD --from HH:MM --to HH:MM - Create an event');
|
|
502
589
|
}
|
|
503
590
|
}
|
|
504
591
|
|
|
@@ -737,6 +824,58 @@ async function slackSearch(query, args = []) {
|
|
|
737
824
|
formatSlackMessages(messages);
|
|
738
825
|
}
|
|
739
826
|
|
|
827
|
+
async function slackSend(channel, textParts) {
|
|
828
|
+
const target = String(channel || '').trim();
|
|
829
|
+
const text = (textParts || []).join(' ').trim();
|
|
830
|
+
if (!target || !text) {
|
|
831
|
+
console.error('Usage: atris slack send <channel> "<message>"');
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const token = await getAuthToken();
|
|
836
|
+
|
|
837
|
+
console.log(`Sending to ${target}...`);
|
|
838
|
+
|
|
839
|
+
const result = await apiRequestJson('/integrations/slack/me/send', {
|
|
840
|
+
method: 'POST',
|
|
841
|
+
token,
|
|
842
|
+
body: { channel: target, text },
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
if (!result.ok) {
|
|
846
|
+
console.error(`Error: ${result.error || 'Failed to send'}`);
|
|
847
|
+
process.exit(1);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
console.log(`Sent to ${target} as you.`);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function slackDm(userId, textParts) {
|
|
854
|
+
const target = String(userId || '').trim();
|
|
855
|
+
const text = (textParts || []).join(' ').trim();
|
|
856
|
+
if (!target || !text) {
|
|
857
|
+
console.error('Usage: atris slack dm <slack_user_id> "<message>"');
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const token = await getAuthToken();
|
|
862
|
+
|
|
863
|
+
console.log(`Sending DM to ${target}...`);
|
|
864
|
+
|
|
865
|
+
const result = await apiRequestJson('/integrations/slack/me/dm', {
|
|
866
|
+
method: 'POST',
|
|
867
|
+
token,
|
|
868
|
+
body: { slack_user_id: target, text },
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
if (!result.ok) {
|
|
872
|
+
console.error(`Error: ${result.error || 'Failed to send DM'}`);
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
console.log(`DM sent to ${target} as you.`);
|
|
877
|
+
}
|
|
878
|
+
|
|
740
879
|
async function slackCommand(subcommand, ...args) {
|
|
741
880
|
switch (subcommand) {
|
|
742
881
|
case 'channels':
|
|
@@ -753,12 +892,20 @@ async function slackCommand(subcommand, ...args) {
|
|
|
753
892
|
case 'search':
|
|
754
893
|
await slackSearch(argsWithoutLimit(args).join(' '), args);
|
|
755
894
|
break;
|
|
895
|
+
case 'send':
|
|
896
|
+
await slackSend(args[0], args.slice(1));
|
|
897
|
+
break;
|
|
898
|
+
case 'dm':
|
|
899
|
+
await slackDm(args[0], args.slice(1));
|
|
900
|
+
break;
|
|
756
901
|
default:
|
|
757
902
|
console.log('Slack commands:');
|
|
758
903
|
console.log(' atris slack channels - List Slack channels');
|
|
759
904
|
console.log(' atris slack dms - List Slack DMs');
|
|
760
905
|
console.log(' atris slack messages <channel> - Read recent messages');
|
|
761
906
|
console.log(' atris slack search <query> - Search Slack messages');
|
|
907
|
+
console.log(' atris slack send <channel> "<message>" - Send a message as you');
|
|
908
|
+
console.log(' atris slack dm <user_id> "<message>" - DM someone as you');
|
|
762
909
|
}
|
|
763
910
|
}
|
|
764
911
|
|