mindvest-atlas 0.55.0 → 0.56.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/config.js +1 -1
- package/src/remote-control.js +43 -0
- package/src/sharing.js +109 -2
package/package.json
CHANGED
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.56.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
|
@@ -1615,6 +1615,48 @@ const STARTER_TRADES = JSON.stringify({
|
|
|
1615
1615
|
* project. Nothing here is ever overwritten — only what is MISSING is written,
|
|
1616
1616
|
* because everything in this list is the agent's to change once it exists.
|
|
1617
1617
|
*/
|
|
1618
|
+
/**
|
|
1619
|
+
* WHAT A SHARED COPY DOES NOT CARRY. A starter like the rest: written when
|
|
1620
|
+
* absent, never touched afterwards, so a project that predates it gets one and
|
|
1621
|
+
* an author who has edited theirs keeps every word.
|
|
1622
|
+
*
|
|
1623
|
+
* The three defaults are the author's OWN output, not the project - the
|
|
1624
|
+
* runner's logs, the trades this job recorded, and the numbers on their
|
|
1625
|
+
* dashboard. Sharing those hands somebody else's record to whoever imports it,
|
|
1626
|
+
* and reads on their Performance page as if it were theirs. Nothing breaks by
|
|
1627
|
+
* holding them back: ensureProjectFiles writes a fresh trades.json and
|
|
1628
|
+
* dashboard.json into the imported copy before its first run.
|
|
1629
|
+
*
|
|
1630
|
+
* They are held back VISIBLY - the share dialog shows them unticked with the
|
|
1631
|
+
* reason - so an author who does mean to publish their record can say so.
|
|
1632
|
+
*/
|
|
1633
|
+
const STARTER_SHAREIGNORE = `# What this project keeps to itself
|
|
1634
|
+
|
|
1635
|
+
Anything listed here is held back when the project is shared. One rule per line.
|
|
1636
|
+
A \`#\` is a comment, a list marker is fine, and a plain name matches that file or
|
|
1637
|
+
anything under it. A trailing slash means a folder: \`runs/\` holds back
|
|
1638
|
+
everything inside it, however deep. A \`*\` is a glob, matched against the whole
|
|
1639
|
+
path and against the file's own name, so \`*.csv\` catches one three folders down.
|
|
1640
|
+
|
|
1641
|
+
These three are here from the start because they are YOURS, not the project's:
|
|
1642
|
+
your logs and your numbers. Delete a line to share it anyway.
|
|
1643
|
+
|
|
1644
|
+
- runs/ every past run, and what the runner wrote while working
|
|
1645
|
+
- trades.json the trades this job recorded. Your record, not theirs
|
|
1646
|
+
- dashboard.json what your last run put on the dashboard
|
|
1647
|
+
|
|
1648
|
+
Add to it as you build - a downloaded file, a scratch folder, an export somebody
|
|
1649
|
+
handed you:
|
|
1650
|
+
|
|
1651
|
+
# - add/
|
|
1652
|
+
# - data/*.csv
|
|
1653
|
+
# - scratch.py
|
|
1654
|
+
|
|
1655
|
+
It can only ever hold things BACK. Nothing written here makes a \`.env\`
|
|
1656
|
+
shareable: secrets never travel whatever this file says, so this is for what is
|
|
1657
|
+
merely private, not for what is dangerous.
|
|
1658
|
+
`;
|
|
1659
|
+
|
|
1618
1660
|
export function starterFiles(slug) {
|
|
1619
1661
|
return [
|
|
1620
1662
|
['README.md', STARTER_README(slug)],
|
|
@@ -1626,6 +1668,7 @@ export function starterFiles(slug) {
|
|
|
1626
1668
|
['dashboard.json', STARTER_DASHBOARD_JSON(slug)],
|
|
1627
1669
|
['atlas.json', STARTER_MANIFEST],
|
|
1628
1670
|
['trades.json', STARTER_TRADES],
|
|
1671
|
+
['shareignore.md', STARTER_SHAREIGNORE],
|
|
1629
1672
|
['.env', '# KEY=value - one per line. Values stay on this computer.\n'],
|
|
1630
1673
|
];
|
|
1631
1674
|
}
|
package/src/sharing.js
CHANGED
|
@@ -115,6 +115,93 @@ export function writeExclude(dir, paths) {
|
|
|
115
115
|
const heldBack = (rel, exclude) =>
|
|
116
116
|
(exclude || []).some((item) => rel === item || rel.startsWith(`${item}/`));
|
|
117
117
|
|
|
118
|
+
/** The file a project uses to say what it keeps to itself. */
|
|
119
|
+
export const SHAREIGNORE = 'shareignore.md';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Patterns this project holds back on its own account. Mirror of
|
|
123
|
+
* `sharing.read_shareignore` — the same file is read by whichever CLI happens
|
|
124
|
+
* to publish, and a project that held something back under one and shipped it
|
|
125
|
+
* under the other is the worst kind of difference.
|
|
126
|
+
*
|
|
127
|
+
* A MARKDOWN FILE WITH PROSE IN IT, because that is what it is called and what
|
|
128
|
+
* an agent writes. So the reader has to tell a rule from a sentence, and the
|
|
129
|
+
* test is that A RULE IS A PATH: no spaces in it. Everything else on the line
|
|
130
|
+
* is a description.
|
|
131
|
+
*
|
|
132
|
+
* - runs/ every past run -> runs
|
|
133
|
+
* trades.json -> trades.json
|
|
134
|
+
* `my report.csv` -> my report.csv
|
|
135
|
+
* Anything listed here is held back. -> prose, not a rule
|
|
136
|
+
*/
|
|
137
|
+
export function readShareignore(dir) {
|
|
138
|
+
let raw = '';
|
|
139
|
+
try { raw = fs.readFileSync(path.join(dir, SHAREIGNORE), 'utf8'); } catch { return []; }
|
|
140
|
+
const out = new Set();
|
|
141
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
142
|
+
let item = line.trim();
|
|
143
|
+
// A heading or a commented-out rule. Both are the author talking, not
|
|
144
|
+
// asking for something.
|
|
145
|
+
if (!item || item.startsWith('#')) continue;
|
|
146
|
+
if (/^[-*+] /.test(item)) item = item.slice(2).trim();
|
|
147
|
+
// A description follows the rule after a COLUMN of space (or a tab, or a
|
|
148
|
+
// `#`), never after the single space that holds a sentence together.
|
|
149
|
+
item = item.split(/\s{2,}|\t|\s+#/)[0].trim();
|
|
150
|
+
const quoted = item.startsWith('`') && item.replace(/[.,]+$/, '').endsWith('`');
|
|
151
|
+
item = item.replace(/^`+|`+$/g, '').trim().replace(/\/+$/, '').trim();
|
|
152
|
+
if (!item) continue;
|
|
153
|
+
// THE TEST THAT KEEPS PROSE OUT. A sentence has spaces in it and a path
|
|
154
|
+
// does not, so anything still carrying one is a line the author wrote to be
|
|
155
|
+
// read, unless they said otherwise with backticks.
|
|
156
|
+
if (item.includes(' ') && !quoted) continue;
|
|
157
|
+
out.add(item);
|
|
158
|
+
}
|
|
159
|
+
return [...out].sort();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Does this project's own shareignore hold this file back?
|
|
164
|
+
*
|
|
165
|
+
* A plain path matches itself and anything under it; anything with a `*` in it
|
|
166
|
+
* is matched as a glob, against the whole path AND against the file's own name,
|
|
167
|
+
* so `*.csv` catches one three folders down without anybody having to write
|
|
168
|
+
* `**\/*.csv`.
|
|
169
|
+
*/
|
|
170
|
+
export function ignoredHere(rel, patterns) {
|
|
171
|
+
const name = rel.split('/').pop();
|
|
172
|
+
for (const item of patterns || []) {
|
|
173
|
+
if (/[*?[]/.test(item)) {
|
|
174
|
+
// The three glob characters, and nothing else treated as special: a
|
|
175
|
+
// pattern is a file name, so a dot or a dash in it means itself.
|
|
176
|
+
const rx = new RegExp(`^${item.replace(/[.+^${}()|\\]/g, '\\$&')
|
|
177
|
+
.replace(/\*/g, '[^/]*').replace(/\?/g, '[^/]')
|
|
178
|
+
.replace(/\[([^\]]+)\]/g, '[$1]')}$`);
|
|
179
|
+
if (rx.test(rel) || rx.test(name)) return true;
|
|
180
|
+
} else if (rel === item || rel.startsWith(`${item}/`)) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* What this project's own shareignore is holding back, as real paths, so the
|
|
189
|
+
* share dialog can show them ALREADY UNTICKED with the reason. A file simply
|
|
190
|
+
* missing from the list looks like a file Atlas lost; one shown as auto-ignored
|
|
191
|
+
* is a decision somebody can see and disagree with.
|
|
192
|
+
*/
|
|
193
|
+
export function heldBackHere(dir) {
|
|
194
|
+
const patterns = readShareignore(dir);
|
|
195
|
+
if (!patterns.length) return [];
|
|
196
|
+
// packableFiles already applies the relay filter and the secret rule; asking
|
|
197
|
+
// it for everything the author has NOT hand-excluded and then keeping what
|
|
198
|
+
// this file catches means the two lists can never disagree about what a file
|
|
199
|
+
// even is.
|
|
200
|
+
return packableFiles(dir, [], { applyIgnore: false })
|
|
201
|
+
.filter((rel) => ignoredHere(rel, patterns))
|
|
202
|
+
.sort();
|
|
203
|
+
}
|
|
204
|
+
|
|
118
205
|
/**
|
|
119
206
|
* Every file that may leave the machine, as POSIX relative paths.
|
|
120
207
|
*
|
|
@@ -122,8 +209,17 @@ const heldBack = (rel, exclude) =>
|
|
|
122
209
|
* ever SUBTRACTS. Nothing a person can tick makes a `.env` shareable: the two
|
|
123
210
|
* are different kinds of rule and are kept apart on purpose.
|
|
124
211
|
*/
|
|
125
|
-
export function packableFiles(dir, exclude) {
|
|
212
|
+
export function packableFiles(dir, exclude, { applyIgnore = true } = {}) {
|
|
126
213
|
const held = exclude === undefined ? readExclude(dir) : (exclude || []);
|
|
214
|
+
// WHAT THE PROJECT ITSELF HOLDS BACK, on top of the author's own choice. Read
|
|
215
|
+
// here rather than passed in, because it is the project's rule and not a
|
|
216
|
+
// caller's: every path that packs a folder gets it without asking.
|
|
217
|
+
//
|
|
218
|
+
// `applyIgnore: false` is for the ONE caller that needs the list as it would
|
|
219
|
+
// be without this file, so it can say what the file caught. It walks the same
|
|
220
|
+
// folder the same way rather than a second walk that could disagree about
|
|
221
|
+
// what a file even is.
|
|
222
|
+
const ignored = applyIgnore ? readShareignore(dir) : [];
|
|
127
223
|
const out = [];
|
|
128
224
|
const walk = (at, prefix) => {
|
|
129
225
|
let items = [];
|
|
@@ -136,6 +232,7 @@ export function packableFiles(dir, exclude) {
|
|
|
136
232
|
if (!item.isFile() || isSecret(item.name)) continue;
|
|
137
233
|
if (parts.length === 1 && HELPERS.has(item.name)) continue;
|
|
138
234
|
if (heldBack(rel, held)) continue;
|
|
235
|
+
if (ignoredHere(rel, ignored)) continue;
|
|
139
236
|
out.push(rel);
|
|
140
237
|
}
|
|
141
238
|
};
|
|
@@ -327,11 +424,21 @@ export async function unpublish(job, root) {
|
|
|
327
424
|
export function shareState(job, root) {
|
|
328
425
|
const dir = projectDir(job, root);
|
|
329
426
|
const exclude = readExclude(dir);
|
|
330
|
-
const
|
|
427
|
+
const packing = packableFiles(dir, []);
|
|
428
|
+
// SHOWN, THOUGH THEY ARE NOT GOING. A file the project's own shareignore
|
|
429
|
+
// holds back is already out of `packing`, and a file that simply is not in
|
|
430
|
+
// the list looks like one Atlas lost. So they are put back into what the
|
|
431
|
+
// dialog SEES, and named separately as the reason — a decision somebody can
|
|
432
|
+
// read and disagree with, rather than an absence.
|
|
433
|
+
const auto = heldBackHere(dir);
|
|
434
|
+
const shareable = [...new Set([...packing, ...auto])].sort();
|
|
331
435
|
const base = {
|
|
332
436
|
ok: true,
|
|
333
437
|
shared: false,
|
|
334
438
|
mine: true,
|
|
439
|
+
// Held back by the project itself, not by a tick. The way to change one is
|
|
440
|
+
// to edit shareignore.md, which is the single place that says so.
|
|
441
|
+
auto_ignored: auto,
|
|
335
442
|
// Every file that COULD go, whether or not it is going. NOT `files`: that
|
|
336
443
|
// word already means "what is in a package" on the peek side, and one name
|
|
337
444
|
// with two meanings is a bug waiting for a careless reader.
|