@josfox/jos 4.1.0 → 5.0.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/LICENSE +208 -21
- package/PROMISES.md +52 -0
- package/README.md +334 -138
- package/SECURITY-MCP.md +117 -0
- package/SECURITY-SECRETS.md +108 -0
- package/STYLE.md +23 -0
- package/bin/jos +170 -160
- package/examples/01-hello.jos +44 -0
- package/examples/02-criteria.jos +54 -0
- package/examples/03-flow-gate.jos +83 -0
- package/examples/04-use-template/README.md +22 -0
- package/examples/04-use-template/lib/greeting.jos +56 -0
- package/examples/04-use-template/use.jos +64 -0
- package/examples/05-sealed/.jos/manifest.jos +53 -0
- package/examples/05-sealed/README.md +25 -0
- package/examples/06-requires.jos +42 -0
- package/examples/07-sandbox.jos +39 -0
- package/examples/08-ids-ledger-meter.md +26 -0
- package/examples/10-ollama.jos +47 -0
- package/examples/11-openai.jos +47 -0
- package/examples/12-gemini.jos +47 -0
- package/examples/13-anthropic.jos +47 -0
- package/examples/14-serve.jos +88 -0
- package/examples/README.md +105 -0
- package/examples/hello.jos +47 -0
- package/package.json +57 -40
- package/promises.json +894 -0
- package/src/args.js +47 -0
- package/src/commands/_frozen/add.README.md +1 -0
- package/src/commands/_frozen/get.README.md +1 -0
- package/src/commands/_frozen/llm.README.md +1 -0
- package/src/commands/_frozen/prompts.README.md +1 -0
- package/src/commands/_frozen/repo.README.md +1 -0
- package/src/commands/_frozen/serve-explorer.README.md +1 -0
- package/src/commands/ids.js +101 -0
- package/src/commands/init.js +128 -162
- package/src/commands/ledger.js +94 -0
- package/src/commands/meter.js +96 -0
- package/src/commands/migrate.js +214 -0
- package/src/commands/overview.js +118 -0
- package/src/commands/run.js +194 -244
- package/src/commands/runs.js +102 -0
- package/src/commands/secrets.js +717 -137
- package/src/commands/start.js +210 -0
- package/src/commands/validate.js +178 -194
- package/src/commands/verify.js +56 -98
- package/src/commands/version.js +23 -0
- package/src/intention.js +114 -0
- package/src/kernel.js +69 -0
- package/src/ledgers.js +57 -0
- package/src/mcp-guide.js +138 -0
- package/src/mcp.js +734 -0
- package/src/serve.js +48 -781
- package/src/ui.js +98 -0
- package/src/adapters/index.js +0 -16
- package/src/adapters/native.js +0 -41
- package/src/adapters/ollama.js +0 -140
- package/src/commands/add.js +0 -145
- package/src/commands/get.js +0 -245
- package/src/commands/llm.js +0 -159
- package/src/commands/prompts.js +0 -229
- package/src/commands/repo.js +0 -139
- package/src/utils/crypto.js +0 -41
- package/src/utils/request.js +0 -41
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JOS MIGRATE - Reseal a 0.0.7 document sealed by security.integrity_ref as a 2.0 document sealed by integrity.hash
|
|
3
|
+
* (PLAN-INTEGRITY-REF-0.0.7 §5, decision D7: a host tool, not a conformance requirement).
|
|
4
|
+
*
|
|
5
|
+
* The file is read once. jos-core's verifyLegacyRef (the one check `run` uses) verifies the manifest against those
|
|
6
|
+
* octets; a document that does not verify is exit 3 and nothing is written, so a changed document cannot be laundered
|
|
7
|
+
* into a valid seal. The migrated document is jos-core's normalization with three changes: security.integrity_ref
|
|
8
|
+
* (x-legacy-integrity_ref after normalization) removed, security.permissions moved to security.x-legacy-permissions
|
|
9
|
+
* (4.1.0 never applied it; 2.0 would apply an empty list as a ceiling with no shell), and integrity.hash =
|
|
10
|
+
* "sha256:" + the payload of its art_ (SPEC-2.0 §8.1). Before writing, the exact octets it writes, read back through
|
|
11
|
+
* jos-core's own reader (parseText), must be that document and validate as 2.0 with that art_ (rc.8, F-05: an object
|
|
12
|
+
* can be valid in memory while its JSON text is not), and its dry run must give the code and plan of the original's. Exit 0 written (or already 2.0), 1 not written (exists,
|
|
13
|
+
* cannot write, self-check), 2 invalid, 3 does not verify, 5 no kernel, 64 usage or not a sealed 0.0.7 document.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const kernel = require('../kernel');
|
|
19
|
+
const ui = require('../ui');
|
|
20
|
+
const { RULE } = ui;
|
|
21
|
+
const { parse, problem } = require('../args');
|
|
22
|
+
|
|
23
|
+
const OPTIONS = { '--out': true, '--in-place': false, '--force': false };
|
|
24
|
+
const USAGE = 'usage: jos migrate <doc.jos> [--out <file> | --in-place] [--force]';
|
|
25
|
+
|
|
26
|
+
const HELP = (C) => `
|
|
27
|
+
${C.purple}${RULE}${C.reset}
|
|
28
|
+
${C.bold}JOS MIGRATE${C.reset} // Reseal a 0.0.7 document as 2.0
|
|
29
|
+
${C.purple}${RULE}${C.reset}
|
|
30
|
+
|
|
31
|
+
${C.bold}Usage:${C.reset}
|
|
32
|
+
jos migrate <doc.jos> [--out <file> | --in-place] [--force]
|
|
33
|
+
|
|
34
|
+
For a 0.0.7 document sealed by security.integrity_ref (what jos init of 4.1.0
|
|
35
|
+
wrote, with its .sig.json manifest). The manifest is verified first, exactly as
|
|
36
|
+
jos run verifies it: a document that does not match is refused (exit 3) and
|
|
37
|
+
nothing is written. Then it writes the 2.0 document jos-core normalizes, sealed
|
|
38
|
+
by integrity.hash instead of the manifest:
|
|
39
|
+
|
|
40
|
+
security.integrity_ref removed (the manifest is no longer read)
|
|
41
|
+
security.permissions moved to security.x-legacy-permissions: 4.1.0
|
|
42
|
+
never applied it, and 2.0 would apply an empty
|
|
43
|
+
list as "no shell"
|
|
44
|
+
integrity.hash sha256 of the migrated document (SPEC-2.0 8.1)
|
|
45
|
+
|
|
46
|
+
The result must validate and plan the same steps as the original (dry run)
|
|
47
|
+
before it is written. The input and its manifest are left as they are.
|
|
48
|
+
|
|
49
|
+
${C.bold}Options:${C.reset}
|
|
50
|
+
--out <file> Where to write (default: <name>.v2.jos beside the input)
|
|
51
|
+
--in-place Replace the input itself (the manifest file stays, unused)
|
|
52
|
+
--force Overwrite an existing --out or <name>.v2.jos (never the input)
|
|
53
|
+
|
|
54
|
+
${C.bold}Exit codes:${C.reset}
|
|
55
|
+
0 written, or already 2.0 (nothing to do); 1 not written (the output exists,
|
|
56
|
+
cannot write, self-check failed); 2 invalid document; 3 integrity_ref does not
|
|
57
|
+
verify; 5 no kernel; 64 usage, or not a 0.0.7 document with integrity_ref
|
|
58
|
+
|
|
59
|
+
1.1.0 and 1.0.0 documents are not migrated in this version: they run as they are.
|
|
60
|
+
|
|
61
|
+
${C.bold}Example:${C.reset}
|
|
62
|
+
jos migrate ref.jos # writes ref.v2.jos
|
|
63
|
+
jos run ref.v2.jos
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
const plain = (s) => (s.length > 1 && s.startsWith('-') ? './' + s : s);
|
|
67
|
+
const canonical = (core, v) => core.canonicalize(v === undefined ? null : v);
|
|
68
|
+
|
|
69
|
+
// <dir>/<name>.v2.jos for <dir>/<name>.jos or .jos.json; any other name gets .v2.jos appended
|
|
70
|
+
function defaultOut(file) {
|
|
71
|
+
return path.join(path.dirname(file), path.basename(file).replace(/\.jos(\.json)?$/, '') + '.v2.jos');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sameFile(a, b) {
|
|
75
|
+
try { return fs.realpathSync(a) === fs.realpathSync(b); } catch (e) { return path.resolve(a) === path.resolve(b); }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The 2.0 document migrate writes, from jos-core's normalization; returns { doc, artifact, moved }.
|
|
79
|
+
function migrated(core, normalized) {
|
|
80
|
+
const m = JSON.parse(JSON.stringify(normalized));
|
|
81
|
+
const sec = m.security;
|
|
82
|
+
delete sec['x-legacy-integrity_ref'];
|
|
83
|
+
const moved = Object.prototype.hasOwnProperty.call(sec, 'permissions');
|
|
84
|
+
if (moved) {
|
|
85
|
+
sec['x-legacy-permissions'] = sec.permissions;
|
|
86
|
+
delete sec.permissions;
|
|
87
|
+
}
|
|
88
|
+
m.integrity = { hash: '' };
|
|
89
|
+
const artifact = core.artifactId(m);
|
|
90
|
+
m.integrity.hash = 'sha256:' + core.josid.parse(artifact).payload;
|
|
91
|
+
return { doc: m, artifact, moved };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
exports.execute = async (args) => {
|
|
95
|
+
const err = ui.printer(process.stderr);
|
|
96
|
+
const { C } = err;
|
|
97
|
+
const { opts, files, help, error } = parse(args, OPTIONS);
|
|
98
|
+
if (help) {
|
|
99
|
+
const out = ui.printer(process.stdout);
|
|
100
|
+
out.text(HELP(out.C));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
if (error || files.length !== 1 || (opts['--in-place'] && opts['--out'] !== undefined)) {
|
|
104
|
+
err.fail('jos migrate: ' + (error ? problem(error) : files.length !== 1 ? 'takes exactly one file' : '--out and --in-place exclude each other'));
|
|
105
|
+
err.hint(USAGE);
|
|
106
|
+
return 64;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let k;
|
|
110
|
+
let core;
|
|
111
|
+
try { k = kernel.locate(); core = kernel.need(k, ['loadFileBytes', 'parseText', 'verifyLegacyRef', 'artifactId', 'canonicalize', 'josid.parse']); } catch (e) { return kernel.fail(e); }
|
|
112
|
+
const file = path.resolve(files[0]);
|
|
113
|
+
const shown = files[0];
|
|
114
|
+
const dir = path.dirname(file);
|
|
115
|
+
|
|
116
|
+
let doc;
|
|
117
|
+
let bytes;
|
|
118
|
+
let v;
|
|
119
|
+
try {
|
|
120
|
+
({ doc, bytes } = core.loadFileBytes(file));
|
|
121
|
+
v = core.validate(doc);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (!(e instanceof core.JosError)) throw e;
|
|
124
|
+
v = { valid: false, errors: e.errors };
|
|
125
|
+
}
|
|
126
|
+
if (!v.valid) {
|
|
127
|
+
err.fail('jos migrate: ' + shown + ' is not a valid document (code 2)');
|
|
128
|
+
for (const e of v.errors) err.hint(e);
|
|
129
|
+
return 2;
|
|
130
|
+
}
|
|
131
|
+
if (v.form === '2.0') {
|
|
132
|
+
err.ok(shown + ' is already 2.0', '(' + v.artifact + '): nothing to migrate');
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
const ref = v.form === '0.0.7' ? core.verifyLegacyRef(doc, bytes, dir) : null;
|
|
136
|
+
if (ref === null) {
|
|
137
|
+
const what = v.form === '0.0.7' ? 'a 0.0.7 document without security.integrity_ref' : 'a ' + v.input_version + ' document';
|
|
138
|
+
err.fail('jos migrate: ' + shown + ' is ' + what + ': nothing to reseal');
|
|
139
|
+
err.hint('jos migrate rewrites 0.0.7 documents sealed by security.integrity_ref (in this version);');
|
|
140
|
+
err.hint('this one runs as it is, normalized in memory: jos run ' + ui.shellArg(plain(shown)));
|
|
141
|
+
return 64;
|
|
142
|
+
}
|
|
143
|
+
if (!ref.ok) {
|
|
144
|
+
err.fail('jos migrate: ' + shown + ': ' + ref.reason, '(code ' + ref.code + ')');
|
|
145
|
+
err.hint('nothing written: a document that does not match its manifest is not resealed');
|
|
146
|
+
return ref.code;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const out = opts['--in-place'] ? file : opts['--out'] !== undefined ? path.resolve(opts['--out']) : defaultOut(file);
|
|
150
|
+
const outShown = opts['--in-place'] ? shown : opts['--out'] !== undefined ? opts['--out'] : path.join(path.dirname(shown), path.basename(out));
|
|
151
|
+
if (!opts['--in-place'] && sameFile(out, file)) {
|
|
152
|
+
err.fail('jos migrate: ' + outShown + ' is the input', '(use --in-place to replace it)');
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
if (!opts['--in-place'] && fs.existsSync(out) && !opts['--force']) {
|
|
156
|
+
err.fail('jos migrate: ' + outShown + ' exists', '(use --force to overwrite)');
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Self-check before anything is written (also with --in-place): the octets it writes, read back by the kernel's own
|
|
161
|
+
// reader, are the migrated document, a valid 2.0 document with the art_ it is sealed with (rc.8, F-05: the object in
|
|
162
|
+
// memory is not what a later run reads), and its dry run (seal verified) gives the code and the plan of the
|
|
163
|
+
// original's dry run (manifest verified).
|
|
164
|
+
const m = migrated(core, v.document);
|
|
165
|
+
const octets = Buffer.from(JSON.stringify(m.doc, null, 2) + '\n', 'utf8');
|
|
166
|
+
let read = null;
|
|
167
|
+
let v2;
|
|
168
|
+
try {
|
|
169
|
+
read = core.parseText(octets);
|
|
170
|
+
v2 = core.validate(read);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
if (!(e instanceof core.JosError)) throw e;
|
|
173
|
+
v2 = { valid: false, errors: e.errors };
|
|
174
|
+
}
|
|
175
|
+
const same = read !== null && canonical(core, read) === canonical(core, m.doc);
|
|
176
|
+
const env = process.env;
|
|
177
|
+
const before = await core.run(doc, { cwd: dir, dir, source: bytes, env, dryRun: true });
|
|
178
|
+
const after = await core.run(read === null ? m.doc : read, { cwd: dir, dir, env, dryRun: true });
|
|
179
|
+
if (!v2.valid || !same || v2.form !== '2.0' || v2.artifact !== m.artifact || after.code !== before.code || canonical(core, after.plan) !== canonical(core, before.plan)) {
|
|
180
|
+
const why = !v2.valid ? 'validate' : !same ? 'read back from the octets it would write as the document it computed' : 'plan what the original plans';
|
|
181
|
+
err.fail('jos migrate: self-check failed: the migrated document does not ' + why, '(nothing written)');
|
|
182
|
+
err.hint('original dry run: code ' + before.code + (before.reason ? ' ' + before.reason : '') + '; migrated: code ' + after.code + (after.reason ? ' ' + after.reason : '') + (v2.valid ? '' : '; ' + v2.errors.join('; ')));
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const tmp = out + '.tmp';
|
|
187
|
+
try {
|
|
188
|
+
fs.writeFileSync(tmp, octets);
|
|
189
|
+
fs.renameSync(tmp, out);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
try { fs.rmSync(tmp, { force: true }); } catch (e2) { /* nothing to clean */ }
|
|
192
|
+
err.fail('jos migrate: cannot write ' + outShown + ': ' + (e && e.code ? e.code : e.message));
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
err.header('JOS MIGRATE', 'jos-core ' + k.version);
|
|
197
|
+
err.field('Input', shown + ` ${C.gray}(0.0.7, ${v.artifact})${C.reset}`);
|
|
198
|
+
err.ok('integrity_ref verified', '(' + ref.manifest + ', sha256 ' + ref.sha256.slice(0, 12) + '…, ' + ref.eol + ')');
|
|
199
|
+
err.ok('Written: ' + outShown, '(2.0.0 ' + m.doc.jos.kind + ', ' + m.artifact + ')');
|
|
200
|
+
err.line();
|
|
201
|
+
err.line(`${C.bold}Changes:${C.reset}`);
|
|
202
|
+
err.item('normalized to 2.0.0 by jos-core; the 0.0.7 members it does not map are kept as x-legacy-*');
|
|
203
|
+
err.item('security.integrity_ref removed: ' + ref.manifest + ' is no longer read');
|
|
204
|
+
if (m.moved) err.item('security.permissions moved to security.x-legacy-permissions (4.1.0 never applied it; in 2.0 an empty list means no shell)');
|
|
205
|
+
err.item('integrity.hash = ' + m.doc.integrity.hash.slice(0, 19) + '… (SPEC-2.0 8.1): any change to the document is now code 3');
|
|
206
|
+
err.ok('Self-check', '(the written octets read back as valid 2.0, same art_ as its seal, the dry run plans what the original plans)');
|
|
207
|
+
if (opts['--in-place']) err.hint('The input was replaced; ' + ref.manifest + ' is no longer used by it.');
|
|
208
|
+
err.hint('Next: jos run ' + ui.shellArg(plain(outShown)));
|
|
209
|
+
err.line();
|
|
210
|
+
return 0;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
exports.defaultOut = defaultOut;
|
|
214
|
+
exports.migrated = migrated;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `jos` with no arguments: what a .jos is and what jos does, in six lines, then a read-only look at the current
|
|
3
|
+
* folder: the .jos / .jos.json documents in it and one folder down, and the folders with a .jos/manifest.jos, each
|
|
4
|
+
* with the commands to validate and run it. Nothing runs and nothing is written. The home folder (or a folder that
|
|
5
|
+
* contains it, such as /) is never scanned. Exit 0.
|
|
6
|
+
*/
|
|
7
|
+
'use strict';
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const pkg = require('../../package.json');
|
|
12
|
+
const kernel = require('../kernel');
|
|
13
|
+
const ui = require('../ui');
|
|
14
|
+
|
|
15
|
+
const DOC = /\.jos(\.json)?$/;
|
|
16
|
+
const MAX_SHOWN = 12;
|
|
17
|
+
const MAX_DIRS = 200;
|
|
18
|
+
const SKIP_DIRS = new Set(['node_modules']);
|
|
19
|
+
|
|
20
|
+
const isFile = (p) => { try { return fs.statSync(p).isFile(); } catch (e) { return false; } };
|
|
21
|
+
// The entries of a folder sorted by name (code unit order), or none when it cannot be read.
|
|
22
|
+
const list = (d) => { try { return fs.readdirSync(d, { withFileTypes: true }).sort((a, b) => (a.name > b.name) - (a.name < b.name)); } catch (e) { return []; } };
|
|
23
|
+
|
|
24
|
+
// What is in `cwd` and one level down: { docs: [relative paths], folders: [relative dirs with .jos/manifest.jos] }.
|
|
25
|
+
function discover(cwd) {
|
|
26
|
+
const docs = [];
|
|
27
|
+
const folders = [];
|
|
28
|
+
if (isFile(path.join(cwd, '.jos', 'manifest.jos'))) folders.push('.');
|
|
29
|
+
const top = list(cwd);
|
|
30
|
+
const subdirs = [];
|
|
31
|
+
for (const e of top) {
|
|
32
|
+
if (e.isFile() && DOC.test(e.name)) docs.push(e.name);
|
|
33
|
+
else if (e.isDirectory() && !e.name.startsWith('.') && !SKIP_DIRS.has(e.name)) subdirs.push(e.name);
|
|
34
|
+
}
|
|
35
|
+
for (const d of subdirs.slice(0, MAX_DIRS)) {
|
|
36
|
+
if (isFile(path.join(cwd, d, '.jos', 'manifest.jos'))) folders.push(d);
|
|
37
|
+
for (const e of list(path.join(cwd, d))) {
|
|
38
|
+
if (e.isFile() && DOC.test(e.name)) docs.push(d + '/' + e.name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { docs, folders };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// True when scanning `cwd` would mean scanning the home folder: cwd is home, or contains it (/, /Users).
|
|
45
|
+
function isHomeOrAbove(cwd, home) {
|
|
46
|
+
const rel = path.relative(cwd, home);
|
|
47
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// "atom hello" / "invalid: <first error>" for a document, read and validated (never run) by jos-core.
|
|
51
|
+
function label(core, file) {
|
|
52
|
+
try {
|
|
53
|
+
const v = core.validate(core.loadFile(file));
|
|
54
|
+
if (!v.valid) return 'invalid: ' + v.errors[0];
|
|
55
|
+
return v.document.jos.kind + (typeof v.document.id === 'string' ? ' ' + v.document.id : '') + ', valid';
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (!(e instanceof core.JosError)) throw e;
|
|
58
|
+
return 'invalid: ' + e.errors[0];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exports.execute = async () => {
|
|
63
|
+
const out = ui.printer(process.stdout);
|
|
64
|
+
const { C } = out;
|
|
65
|
+
let k = null;
|
|
66
|
+
try { k = kernel.locate(); } catch (e) { if (!(e instanceof kernel.KernelNotFound)) throw e; }
|
|
67
|
+
out.header(`${C.cyan}JOS${C.reset} ${C.bold}v${pkg.version}`, k ? 'host of jos-core ' + k.version : 'jos-core not found (run and validate exit 5)');
|
|
68
|
+
out.line(' A .jos is an intention you declare: what to do, when it is done, what to avoid.');
|
|
69
|
+
out.item('Validated without running anything ' + `${C.cyan}jos validate <file>${C.reset}`);
|
|
70
|
+
out.item('Run with guarantees: limits, criteria, receipts ' + `${C.cyan}jos run <file>${C.reset}`);
|
|
71
|
+
out.item('Hash-chained ledger of every run, verifiable ' + `${C.cyan}jos runs${C.reset}, ${C.cyan}jos ledger verify <run>${C.reset}`);
|
|
72
|
+
out.item('Content identity: art_ (content hash), JOSID ' + `${C.cyan}jos ids <file>${C.reset}`);
|
|
73
|
+
out.item('Fractal: atom, flow, a folder with .jos/ ' + `${C.cyan}jos run <folder>${C.reset}`);
|
|
74
|
+
out.item('Reusable: use, templates, digests ' + `${C.cyan}jos run <flow> --library <dir>${C.reset}`);
|
|
75
|
+
out.hint('Two reference kernels (JavaScript, Python) give identical results. jos itself opens no network connection.');
|
|
76
|
+
out.line();
|
|
77
|
+
|
|
78
|
+
const cwd = process.cwd();
|
|
79
|
+
if (isHomeOrAbove(cwd, os.homedir())) {
|
|
80
|
+
out.line(`${C.bold}This folder:${C.reset} ${cwd}`);
|
|
81
|
+
out.hint('Not scanned: it is your home folder or contains it. cd into a project folder, or:');
|
|
82
|
+
out.cmd('jos start');
|
|
83
|
+
out.line();
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
const found = discover(cwd);
|
|
87
|
+
out.line(`${C.bold}This folder:${C.reset} ${cwd} ${C.gray}(read only; nothing runs until you type a command)${C.reset}`);
|
|
88
|
+
if (!found.docs.length && !found.folders.length) {
|
|
89
|
+
out.hint('No .jos documents here or one folder down. Write four examples to start with:');
|
|
90
|
+
out.cmd('jos start');
|
|
91
|
+
out.line();
|
|
92
|
+
out.hint('All commands: jos --help');
|
|
93
|
+
out.line();
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
const entries = found.folders.map((d) => ({ kind: 'folder', rel: d })).concat(found.docs.map((f) => ({ kind: 'doc', rel: f })));
|
|
97
|
+
for (const e of entries.slice(0, MAX_SHOWN)) {
|
|
98
|
+
if (e.kind === 'folder') {
|
|
99
|
+
const manifest = (e.rel === '.' ? '' : e.rel + '/') + '.jos/manifest.jos';
|
|
100
|
+
out.item((e.rel === '.' ? 'this folder' : e.rel + '/') + ` ${C.gray}(a .jos/ folder` + (k ? ': ' + label(k.core, path.join(cwd, manifest)) : '') + `)${C.reset}`);
|
|
101
|
+
out.cmd('jos validate ' + ui.shellArg(manifest));
|
|
102
|
+
out.cmd('jos run ' + ui.shellArg(e.rel));
|
|
103
|
+
} else {
|
|
104
|
+
out.item(e.rel + (k ? ` ${C.gray}(${label(k.core, path.join(cwd, e.rel))})${C.reset}` : ''));
|
|
105
|
+
const arg = (e.rel.startsWith('-') ? '-- ' : '') + ui.shellArg(e.rel);
|
|
106
|
+
out.cmd('jos validate ' + arg);
|
|
107
|
+
out.cmd('jos run ' + arg);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (entries.length > MAX_SHOWN) out.hint('… and ' + (entries.length - MAX_SHOWN) + ' more');
|
|
111
|
+
out.line();
|
|
112
|
+
out.hint('All commands: jos --help');
|
|
113
|
+
out.line();
|
|
114
|
+
return 0;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports.discover = discover;
|
|
118
|
+
module.exports.isHomeOrAbove = isHomeOrAbove;
|