mindvest-atlas 0.23.0 → 0.23.2
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 +1 -0
- package/src/commands/screenshot.js +8 -2
- package/src/commands/share.js +14 -0
- package/src/config.js +1 -1
- package/src/remote-control.js +4 -4
- package/src/sharing.js +87 -11
package/package.json
CHANGED
|
@@ -195,6 +195,7 @@ async function handleAsk(ask, root) {
|
|
|
195
195
|
out = await sharing.publish(ask.job || '', root, {
|
|
196
196
|
name: ask.name, description: ask.description || '', notes: ask.notes || '',
|
|
197
197
|
asNew: Boolean(ask.as_new), force: Boolean(ask.force),
|
|
198
|
+
exclude: ask.exclude,
|
|
198
199
|
});
|
|
199
200
|
} else if (ask.op === 'unshare') out = await sharing.unpublish(ask.job || '', root);
|
|
200
201
|
else if (ask.op === 'peek') out = await sharing.peek(ask.share || '', root);
|
|
@@ -28,8 +28,14 @@ export async function screenshotCommand(args) {
|
|
|
28
28
|
const url = process.env.ATLAS_ASK_URL;
|
|
29
29
|
const token = process.env.ATLAS_ASK_TOKEN;
|
|
30
30
|
if (!url || !token) {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// NOT a Local Mode check, whatever it used to say. These variables are
|
|
32
|
+
// handed to a process Atlas starts — a script it runs, or an agent it
|
|
33
|
+
// spawned. Their absence means this command is running somewhere Atlas did
|
|
34
|
+
// not start it, and saying "turn Local Mode on" sent people to a switch
|
|
35
|
+
// that was already on (owner, 2026-09-05).
|
|
36
|
+
eprintln(color.red('This has to run from inside Atlas. Atlas is what draws the page.'));
|
|
37
|
+
eprintln(color.dim('Ask for it in a chat, or call screenshot() from the project\'s script. '
|
|
38
|
+
+ 'Run by hand in a terminal there is nothing to photograph.'));
|
|
33
39
|
return 1;
|
|
34
40
|
}
|
|
35
41
|
|
package/src/commands/share.js
CHANGED
|
@@ -25,6 +25,18 @@
|
|
|
25
25
|
import { println } from '../util/ui.js';
|
|
26
26
|
import * as sharing from '../sharing.js';
|
|
27
27
|
|
|
28
|
+
/** `--hold-back a/b.py` or `--hold-back=a,b`, and `--hold-back=` meaning none.
|
|
29
|
+
*
|
|
30
|
+
* undefined leaves the project's own choice alone; an EMPTY list is a real
|
|
31
|
+
* instruction ("share everything") and has to survive the difference. */
|
|
32
|
+
function holdBack(args) {
|
|
33
|
+
const raw = flag(args, 'hold-back', 'hold_back', 'exclude');
|
|
34
|
+
if (raw === undefined) return undefined;
|
|
35
|
+
if (typeof raw === 'boolean') return [];
|
|
36
|
+
const items = Array.isArray(raw) ? raw : String(raw).split(',');
|
|
37
|
+
return items.map((x) => String(x).trim()).filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
function flag(args, ...names) {
|
|
29
41
|
for (const name of names) {
|
|
30
42
|
const value = args.flags[name];
|
|
@@ -38,6 +50,7 @@ const SUB_HELP = `atlas share <state|publish|off|peek|file|install|updates|fetch
|
|
|
38
50
|
state --job <name> is this project shared, and its link
|
|
39
51
|
publish --job <name> [--notes …] put the current files out as a new version
|
|
40
52
|
[--as-new] [--force] [--name …] [--description …]
|
|
53
|
+
[--hold-back runs,notes.md] files to leave out (kept in atlas.json)
|
|
41
54
|
off --job <name> stop sharing; the link stops working
|
|
42
55
|
peek --link <id:token> download and describe, without installing
|
|
43
56
|
file --package <id> --path <p> one file's text from a downloaded package
|
|
@@ -67,6 +80,7 @@ export async function shareCommand(args) {
|
|
|
67
80
|
notes: String(flag(args, 'notes') || ''),
|
|
68
81
|
asNew: Boolean(flag(args, 'as-new', 'as_new', 'new')),
|
|
69
82
|
force: Boolean(flag(args, 'force')),
|
|
83
|
+
exclude: holdBack(args),
|
|
70
84
|
});
|
|
71
85
|
break;
|
|
72
86
|
case 'off':
|
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.23.
|
|
5
|
+
export const VERSION = '0.23.2';
|
|
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
|
@@ -1190,8 +1190,8 @@ export async function askModel(prompt, { job = '', tools = true, fresh = false,
|
|
|
1190
1190
|
const token = process.env.ATLAS_ASK_TOKEN;
|
|
1191
1191
|
if (!url || !token) {
|
|
1192
1192
|
throw new Error(
|
|
1193
|
-
'
|
|
1194
|
-
+ '
|
|
1193
|
+
'This script is not running through Atlas, so it has no way to reach a model. '
|
|
1194
|
+
+ 'Press Run in Atlas, or let its schedule start it.'
|
|
1195
1195
|
);
|
|
1196
1196
|
}
|
|
1197
1197
|
const res = await fetch(url, {
|
|
@@ -1240,8 +1240,8 @@ export async function chatModel(prompt, onEvent, { job = '', model = '', timeout
|
|
|
1240
1240
|
const token = process.env.ATLAS_ASK_TOKEN;
|
|
1241
1241
|
if (!url || !token) {
|
|
1242
1242
|
onEvent({ kind: 'error', message:
|
|
1243
|
-
'
|
|
1244
|
-
+ '
|
|
1243
|
+
'This is not running through Atlas, so it has no way to reach a model. '
|
|
1244
|
+
+ 'Press Run in Atlas, or let its schedule start it.' });
|
|
1245
1245
|
return;
|
|
1246
1246
|
}
|
|
1247
1247
|
try {
|
package/src/sharing.js
CHANGED
|
@@ -77,8 +77,53 @@ export function writePin(dir, pin) {
|
|
|
77
77
|
|
|
78
78
|
// ── packing ────────────────────────────────────────────────────────────────
|
|
79
79
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
80
|
+
/** Where a project remembers what its author holds back. In `atlas.json`, with
|
|
81
|
+
* the schedule and the run line, because it is a setting OF the project and a
|
|
82
|
+
* person may want to read or change it without opening a screen. */
|
|
83
|
+
export const EXCLUDE_KEY = 'share_exclude';
|
|
84
|
+
|
|
85
|
+
/** Paths this project's author has chosen not to share. Never a secret list:
|
|
86
|
+
* the hard rules (.env, .venv, the dot folders) are not in here and cannot be,
|
|
87
|
+
* because a preference must not be the thing standing between a key and a
|
|
88
|
+
* stranger. */
|
|
89
|
+
export function readExclude(dir) {
|
|
90
|
+
try {
|
|
91
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, 'atlas.json'), 'utf8'));
|
|
92
|
+
const items = raw && typeof raw === 'object' ? raw[EXCLUDE_KEY] : null;
|
|
93
|
+
if (!Array.isArray(items)) return [];
|
|
94
|
+
return [...new Set(items.map((x) => String(x).trim().replace(/^\/+|\/+$/g, '')).filter(Boolean))].sort();
|
|
95
|
+
} catch { return []; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Remember the choice, so the next publish holds the same things back. */
|
|
99
|
+
export function writeExclude(dir, paths) {
|
|
100
|
+
const file = path.join(dir, 'atlas.json');
|
|
101
|
+
let raw = {};
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
104
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) raw = parsed;
|
|
105
|
+
} catch { raw = {}; }
|
|
106
|
+
const clean = [...new Set((paths || []).map((x) => String(x).trim().replace(/^\/+|\/+$/g, '')).filter(Boolean))].sort();
|
|
107
|
+
if (clean.length) raw[EXCLUDE_KEY] = clean; else delete raw[EXCLUDE_KEY];
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(file, JSON.stringify(raw, null, 2));
|
|
110
|
+
return true;
|
|
111
|
+
} catch { return false; }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Is this file one the author left out — itself, or a folder holding it? */
|
|
115
|
+
const heldBack = (rel, exclude) =>
|
|
116
|
+
(exclude || []).some((item) => rel === item || rel.startsWith(`${item}/`));
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Every file that may leave the machine, as POSIX relative paths.
|
|
120
|
+
*
|
|
121
|
+
* `exclude` is the author's own choice on top of the relay filter, and only
|
|
122
|
+
* ever SUBTRACTS. Nothing a person can tick makes a `.env` shareable: the two
|
|
123
|
+
* are different kinds of rule and are kept apart on purpose.
|
|
124
|
+
*/
|
|
125
|
+
export function packableFiles(dir, exclude) {
|
|
126
|
+
const held = exclude === undefined ? readExclude(dir) : (exclude || []);
|
|
82
127
|
const out = [];
|
|
83
128
|
const walk = (at, prefix) => {
|
|
84
129
|
let items = [];
|
|
@@ -90,6 +135,7 @@ export function packableFiles(dir) {
|
|
|
90
135
|
if (item.isDirectory()) { walk(path.join(at, item.name), rel); continue; }
|
|
91
136
|
if (!item.isFile() || isSecret(item.name)) continue;
|
|
92
137
|
if (parts.length === 1 && HELPERS.has(item.name)) continue;
|
|
138
|
+
if (heldBack(rel, held)) continue;
|
|
93
139
|
out.push(rel);
|
|
94
140
|
}
|
|
95
141
|
};
|
|
@@ -121,12 +167,16 @@ export function requiredKeys(dir, files) {
|
|
|
121
167
|
return [...names].filter((n) => !n.startsWith('ATLAS_')).sort();
|
|
122
168
|
}
|
|
123
169
|
|
|
124
|
-
export function packProject(job, root, { name, description = '' } = {}) {
|
|
170
|
+
export function packProject(job, root, { name, description = '', exclude } = {}) {
|
|
125
171
|
const dir = projectDir(job, root);
|
|
126
172
|
if (!fs.existsSync(dir)) return { ok: false, error: `There is no project called ${job} here.` };
|
|
127
|
-
const files = packableFiles(dir);
|
|
173
|
+
const files = packableFiles(dir, exclude);
|
|
128
174
|
if (!files.length) {
|
|
129
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
error: 'Nothing would be shared. Every file in this project is either held back '
|
|
178
|
+
+ 'or one Atlas never shares.',
|
|
179
|
+
};
|
|
130
180
|
}
|
|
131
181
|
const hashes = {};
|
|
132
182
|
const entries = [];
|
|
@@ -150,8 +200,14 @@ export function packProject(job, root, { name, description = '' } = {}) {
|
|
|
150
200
|
|
|
151
201
|
// ── publishing ─────────────────────────────────────────────────────────────
|
|
152
202
|
|
|
153
|
-
export async function publish(job, root, {
|
|
203
|
+
export async function publish(job, root, {
|
|
204
|
+
name, description = '', notes = '', asNew = false, force = false, exclude,
|
|
205
|
+
} = {}) {
|
|
154
206
|
const dir = projectDir(job, root);
|
|
207
|
+
// The choice is REMEMBERED before it is used, so the next publish holds the
|
|
208
|
+
// same things back without being told again — and so a person can read it in
|
|
209
|
+
// atlas.json rather than having to open a screen to find out.
|
|
210
|
+
if (exclude !== undefined && exclude !== null) writeExclude(dir, exclude);
|
|
155
211
|
const packed = packProject(job, root, { name, description });
|
|
156
212
|
if (!packed.ok) return packed;
|
|
157
213
|
|
|
@@ -219,11 +275,31 @@ export async function unpublish(job, root) {
|
|
|
219
275
|
return { ok: true };
|
|
220
276
|
}
|
|
221
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Is this project shared, from where, and exactly what would go.
|
|
280
|
+
*
|
|
281
|
+
* The file list comes back with it so the share screen can show what is IN a
|
|
282
|
+
* publish before it happens — and let the author take something out. It is the
|
|
283
|
+
* real packing list, not a guess at one: the same call that packs produces it.
|
|
284
|
+
*/
|
|
222
285
|
export function shareState(job, root) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
286
|
+
const dir = projectDir(job, root);
|
|
287
|
+
const exclude = readExclude(dir);
|
|
288
|
+
const shareable = packableFiles(dir, []);
|
|
289
|
+
const base = {
|
|
226
290
|
ok: true,
|
|
291
|
+
shared: false,
|
|
292
|
+
mine: true,
|
|
293
|
+
// Every file that COULD go, whether or not it is going. NOT `files`: that
|
|
294
|
+
// word already means "what is in a package" on the peek side, and one name
|
|
295
|
+
// with two meanings is a bug waiting for a careless reader.
|
|
296
|
+
shareable,
|
|
297
|
+
exclude: exclude.filter((x) => shareable.some((f) => f === x || f.startsWith(`${x}/`))),
|
|
298
|
+
};
|
|
299
|
+
const pin = readPin(dir);
|
|
300
|
+
if (!pin) return base;
|
|
301
|
+
return {
|
|
302
|
+
...base,
|
|
227
303
|
shared: Boolean(pin.share),
|
|
228
304
|
share: pin.share,
|
|
229
305
|
package_id: pin.package_id,
|
|
@@ -313,7 +389,7 @@ export async function peek(shareText, root) {
|
|
|
313
389
|
export function peekRead(packageId, filePath, root) {
|
|
314
390
|
const zipPath = path.join(stagingDir(root, packageId), 'package.zip');
|
|
315
391
|
if (!fs.existsSync(zipPath)) {
|
|
316
|
-
return { ok: false, error: 'Open the link again
|
|
392
|
+
return { ok: false, error: 'Open the link again: this download has gone.' };
|
|
317
393
|
}
|
|
318
394
|
try {
|
|
319
395
|
const found = unzip(fs.readFileSync(zipPath)).find((e) => e.name === filePath);
|
|
@@ -370,7 +446,7 @@ export function install(packageId, root, { job } = {}) {
|
|
|
370
446
|
const stage = stagingDir(root, packageId);
|
|
371
447
|
const zipPath = path.join(stage, 'package.zip');
|
|
372
448
|
if (!fs.existsSync(zipPath)) {
|
|
373
|
-
return { ok: false, error: 'Open the link again
|
|
449
|
+
return { ok: false, error: 'Open the link again: this download has gone.' };
|
|
374
450
|
}
|
|
375
451
|
let manifest = {};
|
|
376
452
|
try {
|