mindvest-atlas 0.5.0 → 0.6.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/package.json +1 -1
- package/src/commands/remote-control.js +9 -23
- package/src/config.js +1 -1
- package/src/remote-control.js +44 -0
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ import { requestJson } from '../api/client.js';
|
|
|
15
15
|
import { runStream } from '../util/stream-loop.js';
|
|
16
16
|
import { color, eprintln } from '../util/ui.js';
|
|
17
17
|
import {
|
|
18
|
-
DEFAULT_TIMEOUT_MS, computerName, jobEnv, listJobFiles, machineId, osLabel,
|
|
18
|
+
DEFAULT_TIMEOUT_MS, computerName, jobEnv, listJobFiles, listRuns, machineId, osLabel,
|
|
19
19
|
readJobFile, staleness, summarize, tail, workspace, workspaceRoot,
|
|
20
20
|
} from '../remote-control.js';
|
|
21
21
|
import fs from 'node:fs';
|
|
@@ -53,30 +53,15 @@ function writeLog(dir, job, status, started, finished, text) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Deliberately does nothing.
|
|
57
57
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* A run is recorded in the log beside the script that produced it, on the
|
|
59
|
+
* computer that ran it, and nowhere else. This is kept as the single place every
|
|
60
|
+
* outcome funnels through, so "where does a run go" has one answer rather than
|
|
61
|
+
* five call sites each deciding.
|
|
60
62
|
*/
|
|
61
|
-
async function report(
|
|
62
|
-
|
|
63
|
-
await requestJson(API.localRuns, {
|
|
64
|
-
method: 'POST',
|
|
65
|
-
body: {
|
|
66
|
-
automation_id: job.automation_id || job.id,
|
|
67
|
-
name: job.name || '',
|
|
68
|
-
source: job.source || 'schedule',
|
|
69
|
-
status,
|
|
70
|
-
started_at: iso(started),
|
|
71
|
-
finished_at: iso(finished),
|
|
72
|
-
// One account can run jobs on several computers; the results view says
|
|
73
|
-
// which one produced each record.
|
|
74
|
-
machine: computerName(),
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
} catch (err) {
|
|
78
|
-
eprintln(color.dim(` ↳ could not report the run: ${err.message}`));
|
|
79
|
-
}
|
|
63
|
+
async function report() {
|
|
64
|
+
return undefined;
|
|
80
65
|
}
|
|
81
66
|
|
|
82
67
|
function runOnce(argv, dir, env, timeoutMs) {
|
|
@@ -112,6 +97,7 @@ async function answer(requestId, payload) {
|
|
|
112
97
|
async function handleAsk(ask, root) {
|
|
113
98
|
if (!ask?.request_id) return;
|
|
114
99
|
if (ask.op === 'list') return answer(ask.request_id, { ok: true, jobs: listJobFiles(root) });
|
|
100
|
+
if (ask.op === 'runs') return answer(ask.request_id, { ok: true, runs: listRuns(root) });
|
|
115
101
|
if (ask.op === 'read') return answer(ask.request_id, readJobFile(ask.job || '', ask.path || '', root));
|
|
116
102
|
return answer(ask.request_id, { ok: false, error: 'Unknown request.' });
|
|
117
103
|
}
|
package/src/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
|
|
5
|
-
export const VERSION = '0.
|
|
5
|
+
export const VERSION = '0.6.0';
|
|
6
6
|
export const DEFAULT_BASE_URL = 'https://atlasmcp.finmanagerai.com';
|
|
7
7
|
export const DEFAULT_SCOPE = 'atlas broker';
|
|
8
8
|
export const CLIENT_NAME = 'Atlas CLI';
|
package/src/remote-control.js
CHANGED
|
@@ -283,3 +283,47 @@ export function readJobFile(job, rel, root) {
|
|
|
283
283
|
return { ok: false, error: 'That file could not be read.' };
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Recent runs, read from the logs this computer wrote.
|
|
289
|
+
*
|
|
290
|
+
* The machine that ran them is the only place they exist — nothing is reported
|
|
291
|
+
* to a server — so this is the source of truth, not a cache of one. Parsed from
|
|
292
|
+
* the filename (when, and how it ended) and the log's own second line (how
|
|
293
|
+
* long), which is why the writer stamps both.
|
|
294
|
+
*/
|
|
295
|
+
export function listRuns(root, limit = 50) {
|
|
296
|
+
const base = path.join(workspaceRoot(root), SCRIPTS_DIR);
|
|
297
|
+
const runs = [];
|
|
298
|
+
let jobs = [];
|
|
299
|
+
try {
|
|
300
|
+
jobs = fs.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
301
|
+
} catch { return runs; }
|
|
302
|
+
for (const job of jobs) {
|
|
303
|
+
let files = [];
|
|
304
|
+
try {
|
|
305
|
+
files = fs.readdirSync(path.join(base, job, 'runs')).filter((f) => f.endsWith('.log')).sort().reverse();
|
|
306
|
+
} catch { continue; }
|
|
307
|
+
for (const f of files.slice(0, limit)) {
|
|
308
|
+
const stem = f.replace(/\.log$/, '');
|
|
309
|
+
const parts = stem.split('-');
|
|
310
|
+
if (parts.length < 3) continue;
|
|
311
|
+
const status = parts.slice(2).join('-');
|
|
312
|
+
const [d, t] = parts;
|
|
313
|
+
const started = Date.parse(
|
|
314
|
+
`${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}T${t.slice(0, 2)}:${t.slice(2, 4)}:${t.slice(4, 6)}`,
|
|
315
|
+
) / 1000;
|
|
316
|
+
let name = job;
|
|
317
|
+
let took = null;
|
|
318
|
+
try {
|
|
319
|
+
const [first, second] = fs.readFileSync(path.join(base, job, 'runs', f), 'utf8').split('\n');
|
|
320
|
+
const bits = (first || '').split(' ');
|
|
321
|
+
if (bits.length >= 3) name = bits[2];
|
|
322
|
+
if ((second || '').startsWith('took ')) took = parseFloat(second.slice(5));
|
|
323
|
+
} catch { /* unreadable: the filename still tells the story */ }
|
|
324
|
+
runs.push({ job, name, status, started_at: Number.isFinite(started) ? started : 0, duration_s: took, log: f });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
runs.sort((a, b) => b.started_at - a.started_at);
|
|
328
|
+
return runs.slice(0, limit);
|
|
329
|
+
}
|