@arjunkhera/atlas 0.3.4 → 0.3.6
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/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +1 -0
- package/door/cli.mjs +54 -16
- package/door/lib/install.mjs +1 -1
- package/door/lib/privacy.mjs +7 -2
- package/package.json +1 -1
- package/work/mcp.mjs +6 -2
package/.mcp.json
CHANGED
package/door/cli.mjs
CHANGED
|
@@ -54,18 +54,53 @@ Atlas ${ATLAS_VERSION}. Check version ${CHECK_VERSION}; it reads spec ${Object.k
|
|
|
54
54
|
A person merges every file the tooling writer writes.
|
|
55
55
|
`;
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
57
|
+
// Each flag of each command, declared once. A test holds this table equal to
|
|
58
|
+
// the flags the handlers read. Three kinds:
|
|
59
|
+
// - switch: never takes a value, so it may come before a path
|
|
60
|
+
// (`atlas ste --share page.md`, a miss on 6 October);
|
|
61
|
+
// - value: takes the next word, and a flag with no value stops the command;
|
|
62
|
+
// - optional: takes the next word unless it is a flag; alone it means the
|
|
63
|
+
// default (`--root` alone is this folder, `upgrade --version` alone is the
|
|
64
|
+
// newest release).
|
|
65
|
+
// Any other flag stops the command with exit 2. A misspelt `--shared` once
|
|
66
|
+
// skipped the privacy filter and printed GREEN (review of PR #107).
|
|
67
|
+
export const FLAGS = Object.freeze({
|
|
68
|
+
tooling: { root: 'optional', branch: 'optional', 'replace-unknown': 'switch', workflow: 'switch', 'dry-run': 'switch' },
|
|
69
|
+
status: { root: 'optional' },
|
|
70
|
+
'kind-drift': { root: 'optional', 'record-kind': 'value', registry: 'value', repo: 'value' },
|
|
71
|
+
check: { root: 'optional', repo: 'value', 'fail-on': 'value' },
|
|
72
|
+
ste: { share: 'switch', terms: 'value' },
|
|
73
|
+
design: {},
|
|
74
|
+
install: { local: 'switch', from: 'value', 'skip-global': 'switch', yes: 'switch' },
|
|
75
|
+
upgrade: { version: 'optional', yes: 'switch' },
|
|
76
|
+
doctor: {},
|
|
77
|
+
});
|
|
60
78
|
|
|
61
|
-
|
|
79
|
+
// Every command takes --help.
|
|
80
|
+
export function options(argv, declared = {}) {
|
|
81
|
+
const kinds = { help: 'switch', ...declared };
|
|
62
82
|
const out = { _: [] };
|
|
63
83
|
for (let index = 0; index < argv.length; index += 1) {
|
|
64
|
-
const
|
|
65
|
-
if (!
|
|
66
|
-
const
|
|
84
|
+
const word = argv[index];
|
|
85
|
+
if (!word.startsWith('--')) { out._.push(word); continue; }
|
|
86
|
+
const equals = word.indexOf('=');
|
|
87
|
+
const key = word.slice(2, equals === -1 ? undefined : equals);
|
|
88
|
+
const kind = Object.hasOwn(kinds, key) ? kinds[key] : null;
|
|
89
|
+
if (!kind) throw new Error(`there is no flag "--${key}" for "atlas ${argv[0]}". Run "atlas help".`);
|
|
90
|
+
if (equals !== -1) {
|
|
91
|
+
if (kind === 'switch') throw new Error(`the flag "--${key}" takes no value.`);
|
|
92
|
+
if (equals === word.length - 1) throw new Error(`the flag "--${key}" needs a value.`);
|
|
93
|
+
out[key] = word.slice(equals + 1);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (kind === 'switch') { out[key] = true; continue; }
|
|
67
97
|
const value = argv[index + 1];
|
|
68
|
-
if (
|
|
98
|
+
if (value === undefined || value.startsWith('--')) {
|
|
99
|
+
if (kind === 'value') throw new Error(`the flag "--${key}" needs a value.`);
|
|
100
|
+
out[key] = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (value === '') throw new Error(`the flag "--${key}" needs a value.`);
|
|
69
104
|
out[key] = value;
|
|
70
105
|
index += 1;
|
|
71
106
|
}
|
|
@@ -123,10 +158,10 @@ function statusCommand(chosen) {
|
|
|
123
158
|
|
|
124
159
|
function kindDriftCommand(chosen) {
|
|
125
160
|
const root = rootOf(chosen);
|
|
126
|
-
let recordKind = chosen['record-kind']
|
|
127
|
-
if (!recordKind && chosen.registry
|
|
161
|
+
let recordKind = chosen['record-kind'] ?? null;
|
|
162
|
+
if (!recordKind && chosen.registry) {
|
|
128
163
|
const kinds = kindsFromRegistry(JSON.parse(readFileSync(resolve(chosen.registry), 'utf8')));
|
|
129
|
-
const repo = chosen.repo
|
|
164
|
+
const repo = chosen.repo ?? null;
|
|
130
165
|
recordKind = repo ? kinds[repo] ?? null : null;
|
|
131
166
|
if (!repo) {
|
|
132
167
|
// One checkout, so only the registry row that names THIS repo can be
|
|
@@ -159,7 +194,8 @@ function steCommand(chosen) {
|
|
|
159
194
|
const paths = chosen._.slice(1);
|
|
160
195
|
if (!paths.length) throw new Error('give a file or a folder: atlas ste <path> [--share]');
|
|
161
196
|
const share = Boolean(chosen.share);
|
|
162
|
-
|
|
197
|
+
if (chosen.terms !== undefined && !share) throw new Error('--terms works only with --share.');
|
|
198
|
+
const terms = share ? loadPrivateTerms({ path: chosen.terms !== undefined ? resolve(chosen.terms) : null }) : [];
|
|
163
199
|
const results = checkPaths(paths.map((one) => resolve(one)), { share, terms });
|
|
164
200
|
let count = 0;
|
|
165
201
|
for (const { file, findings } of results) {
|
|
@@ -186,8 +222,8 @@ function designCommand(chosen) {
|
|
|
186
222
|
|
|
187
223
|
function checkCommand(chosen) {
|
|
188
224
|
const root = rootOf(chosen);
|
|
189
|
-
const report = scan({ root, repoId: chosen.repo
|
|
190
|
-
const decision = verdict(report, chosen['fail-on']
|
|
225
|
+
const report = scan({ root, repoId: chosen.repo ?? null });
|
|
226
|
+
const decision = verdict(report, chosen['fail-on'] ?? 'error');
|
|
191
227
|
process.stdout.write(toText(report));
|
|
192
228
|
line(`${decision.green ? 'GREEN' : 'RED'}: ${decision.why}`);
|
|
193
229
|
return decision.green ? 0 : 1;
|
|
@@ -200,8 +236,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
200
236
|
const result = spawnSync(process.execPath, [join(PACKAGE_ROOT, 'work', 'code-read.mjs'), ...argv.slice(1)], { stdio: 'inherit' });
|
|
201
237
|
return result.status ?? 1;
|
|
202
238
|
}
|
|
203
|
-
|
|
204
|
-
if (!command
|
|
239
|
+
if (!command || command === 'help' || command === '--help') { process.stdout.write(HELP); return 0; }
|
|
240
|
+
if (!Object.hasOwn(FLAGS, command)) throw new Error(`there is no verb "${command}". Run "atlas help".`);
|
|
241
|
+
const chosen = options(argv, FLAGS[command]);
|
|
242
|
+
if (chosen.help) { process.stdout.write(HELP); return 0; }
|
|
205
243
|
switch (command) {
|
|
206
244
|
case 'install': return install({ packageRoot: PACKAGE_ROOT, options: chosen, line });
|
|
207
245
|
case 'upgrade': return upgrade({ packageRoot: PACKAGE_ROOT, options: chosen, line });
|
package/door/lib/install.mjs
CHANGED
|
@@ -67,7 +67,7 @@ const findPlugin = (listed, id) => pluginRows(listed).find((row) => row.id === i
|
|
|
67
67
|
export async function install({ packageRoot, options = {}, line = console.log, env = process.env, exec = defaultExec, ask = defaultAsk }) {
|
|
68
68
|
const { name, version } = packageJson(packageRoot);
|
|
69
69
|
const local = Boolean(options.local);
|
|
70
|
-
const from = options.from
|
|
70
|
+
const from = options.from ? String(options.from) : null;
|
|
71
71
|
const catalog = catalogFor({ name, version, local });
|
|
72
72
|
const file = catalogFile(env);
|
|
73
73
|
const before = existsSync(file) ? readFileSync(file, 'utf8') : null;
|
package/door/lib/privacy.mjs
CHANGED
|
@@ -27,10 +27,15 @@ export const PRIVACY_PATTERNS = Object.freeze([
|
|
|
27
27
|
|
|
28
28
|
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
29
29
|
|
|
30
|
-
// The owner's private terms
|
|
30
|
+
// The owner's private terms. The default file is optional: no file there
|
|
31
|
+
// means no terms. A file the caller names must exist, because a mistyped
|
|
32
|
+
// name once loaded no terms and passed the page (review of PR #107).
|
|
31
33
|
export function loadPrivateTerms({ path = null, home = homedir() } = {}) {
|
|
32
34
|
const file = path ?? join(home, PRIVATE_TERMS_PATH);
|
|
33
|
-
if (!existsSync(file))
|
|
35
|
+
if (!existsSync(file)) {
|
|
36
|
+
if (path) throw new Error(`the private terms file ${path} does not exist.`);
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
34
39
|
return readFileSync(file, 'utf8').split('\n').map((line) => line.trim()).filter((line) => line && !line.startsWith('#'));
|
|
35
40
|
}
|
|
36
41
|
|
package/package.json
CHANGED
package/work/mcp.mjs
CHANGED
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
// The Engram address and key arrive together, as one setting, in
|
|
9
9
|
// ATLAS_ENGRAM. When that setting arrives empty, the server reads the same
|
|
10
10
|
// value from one owner-only file, ~/.config/atlas/engram-connection (atlas#101:
|
|
11
|
-
// Claude Desktop starts the server with the sensitive setting empty).
|
|
12
|
-
//
|
|
11
|
+
// Claude Desktop starts the server with the sensitive setting empty). In a
|
|
12
|
+
// Claude cloud session only, it reads the cloud environment's own ATLAS_ENGRAM
|
|
13
|
+
// variable first, which .mcp.json passes on as ATLAS_ENGRAM_CLOUD, because the
|
|
14
|
+
// empty plugin setting hides it and a cloud setup script cannot see it. It
|
|
15
|
+
// reads them from nowhere else.
|
|
13
16
|
import { execFileSync } from 'node:child_process';
|
|
14
17
|
import { randomUUID } from 'node:crypto';
|
|
15
18
|
import { resolve, isAbsolute, dirname, join, delimiter } from 'node:path';
|
|
@@ -111,6 +114,7 @@ function readConnectionFile(env) {
|
|
|
111
114
|
}
|
|
112
115
|
export function readConnection(env, { allowLoopbackHttp = false, now = Date.now() } = {}) {
|
|
113
116
|
let raw = unset(env.ATLAS_ENGRAM) ? '' : String(env.ATLAS_ENGRAM).trim();
|
|
117
|
+
if (!raw && inClaudeCloud(env) && !unset(env.ATLAS_ENGRAM_CLOUD)) raw = String(env.ATLAS_ENGRAM_CLOUD).trim();
|
|
114
118
|
if (!raw) {
|
|
115
119
|
const file = readConnectionFile(env);
|
|
116
120
|
if (file.refused) return { state: 'bad', message: `The Engram connection file is refused. ${file.refused}` };
|