@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
package/src/ui.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The one style module of the host (STYLE.md). Every line written for a person goes through a printer of this file:
|
|
3
|
+
// the header with rules, the marks ✓ ✖ ⚠, the colors and the indentation live here and nowhere else. Colors are off
|
|
4
|
+
// when NO_COLOR is set or the stream is not a terminal, so piped output carries no escape codes. Machine output
|
|
5
|
+
// (`--json`) and data that is not a message (a child's output, a revealed value) also leave through this file, by
|
|
6
|
+
// json() and data(), which never add color or decoration.
|
|
7
|
+
|
|
8
|
+
const FULL = {
|
|
9
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
10
|
+
purple: '\x1b[38;5;135m', magenta: '\x1b[38;5;198m', cyan: '\x1b[38;5;51m',
|
|
11
|
+
blue: '\x1b[38;5;39m', white: '\x1b[38;5;255m', gray: '\x1b[38;5;245m',
|
|
12
|
+
pink: '\x1b[38;5;213m', teal: '\x1b[38;5;44m',
|
|
13
|
+
green: '\x1b[38;5;78m', red: '\x1b[38;5;196m', yellow: '\x1b[38;5;220m',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const BASIC = {
|
|
17
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
18
|
+
purple: '\x1b[35m', magenta: '\x1b[35m', cyan: '\x1b[36m',
|
|
19
|
+
blue: '\x1b[34m', white: '\x1b[37m', gray: '\x1b[90m',
|
|
20
|
+
pink: '\x1b[35m', teal: '\x1b[36m',
|
|
21
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function supports256Color() {
|
|
25
|
+
const term = process.env.TERM || '';
|
|
26
|
+
const colorterm = process.env.COLORTERM || '';
|
|
27
|
+
return term.includes('256color') || colorterm === 'truecolor' ||
|
|
28
|
+
colorterm === '24bit' || process.platform === 'darwin';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function supportsUnicode() {
|
|
32
|
+
const term = process.env.TERM || '';
|
|
33
|
+
const lang = process.env.LANG || '';
|
|
34
|
+
if (lang && !lang.includes('UTF-8') && !lang.includes('utf8')) return false;
|
|
35
|
+
return lang.includes('UTF-8') || lang.includes('utf8') ||
|
|
36
|
+
term.includes('xterm') || term.includes('256color') ||
|
|
37
|
+
(process.platform === 'darwin' && !lang);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function palette(stream) {
|
|
41
|
+
const on = !process.env.NO_COLOR && stream && stream.isTTY;
|
|
42
|
+
const src = supports256Color() ? FULL : BASIC;
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const k of Object.keys(FULL)) out[k] = on ? src[k] : '';
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const RULE = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
|
|
49
|
+
|
|
50
|
+
// A printer bound to one stream (process.stdout, process.stderr or a stream a test passes in). The stream's write
|
|
51
|
+
// is looked up on every call, so a test that replaces process.stdout.write sees every line.
|
|
52
|
+
// header(title, sub) blank line, rule, "TITLE // sub", rule, blank line
|
|
53
|
+
// ok / fail / warn "✓ text", "✖ text", "⚠ text" in green / red / yellow, then an optional gray detail
|
|
54
|
+
// step "▶ text" in cyan: something that starts now (a command the MCP client runs)
|
|
55
|
+
// hint(text) an indented, dimmed follow-up line (usage, next step, why)
|
|
56
|
+
// field(label, value) an indented "label: value" line
|
|
57
|
+
// item(text) an indented "• text" line
|
|
58
|
+
// cmd(text) an indented command to copy: " $ text"
|
|
59
|
+
// line(s) / text(s) a line, or a block or prompt written as given (composed with C, this printer's palette)
|
|
60
|
+
function printer(stream) {
|
|
61
|
+
const C = palette(stream);
|
|
62
|
+
const text = (s) => { stream.write(s); };
|
|
63
|
+
const line = (s) => text((s === undefined ? '' : s) + '\n');
|
|
64
|
+
const mark = (color, sign) => (s, detail) => line(`${C[color]}${sign} ${s}${C.reset}` + (detail ? ` ${C.gray}${detail}${C.reset}` : ''));
|
|
65
|
+
return {
|
|
66
|
+
C,
|
|
67
|
+
text,
|
|
68
|
+
line,
|
|
69
|
+
header(title, sub) {
|
|
70
|
+
line();
|
|
71
|
+
line(`${C.purple}${RULE}${C.reset}`);
|
|
72
|
+
line(`${C.bold}${title}${C.reset}` + (sub ? ` // ${C.gray}${sub}${C.reset}` : ''));
|
|
73
|
+
line(`${C.purple}${RULE}${C.reset}`);
|
|
74
|
+
line();
|
|
75
|
+
},
|
|
76
|
+
ok: mark('green', '✓'),
|
|
77
|
+
fail: mark('red', '✖'),
|
|
78
|
+
warn: mark('yellow', '⚠'),
|
|
79
|
+
step: mark('cyan', '▶'),
|
|
80
|
+
hint: (s) => line(` ${C.dim}${s}${C.reset}`),
|
|
81
|
+
field: (label, value) => line(` ${C.cyan}${label}:${C.reset} ${value}`),
|
|
82
|
+
item: (s) => line(` ${C.white}•${C.reset} ${s}`),
|
|
83
|
+
cmd: (s) => line(` ${C.gray}$${C.reset} ${C.cyan}${s}${C.reset}`),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Machine output (--json): plain JSON, two-space indent, one final newline; never colored, whatever the terminal.
|
|
88
|
+
function json(stream, value) { stream.write(JSON.stringify(value, null, 2) + '\n'); }
|
|
89
|
+
|
|
90
|
+
// Data that is not a message (a child's stdout or stderr, a value revealed on a terminal): written byte for byte.
|
|
91
|
+
function data(stream, chunk) { stream.write(chunk); }
|
|
92
|
+
|
|
93
|
+
// One argument as a POSIX shell word, for commands printed to be copied: left as is when it is plain, else quoted.
|
|
94
|
+
function shellArg(s) {
|
|
95
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(s) ? s : "'" + s.replace(/'/g, "'\\''") + "'";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { palette, supportsUnicode, RULE, printer, json, data, shellArg };
|
package/src/adapters/index.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const NativeAdapter = require('./native');
|
|
2
|
-
const OllamaAdapter = require('./ollama');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Adapter Factory
|
|
6
|
-
* Returns the appropriate adapter based on provider type
|
|
7
|
-
*/
|
|
8
|
-
exports.getAdapter = (type) => {
|
|
9
|
-
switch (type) {
|
|
10
|
-
case 'ollama':
|
|
11
|
-
return OllamaAdapter;
|
|
12
|
-
case 'native':
|
|
13
|
-
default:
|
|
14
|
-
return NativeAdapter;
|
|
15
|
-
}
|
|
16
|
-
};
|
package/src/adapters/native.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Native JOS Adapter
|
|
3
|
-
* Connects to the standard JOS Prompts API (e.g. josfox.cloud)
|
|
4
|
-
*/
|
|
5
|
-
const { apiRequest } = require('../utils/request');
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
type: 'native',
|
|
9
|
-
|
|
10
|
-
async optimize(artifact, options, providerUrl, model, apiKey) {
|
|
11
|
-
const res = await apiRequest(`${providerUrl}/optimize`, 'POST', {
|
|
12
|
-
artifact, options, format_version: '0.0.7'
|
|
13
|
-
}, apiKey);
|
|
14
|
-
if (res.status !== 200) throw new Error(res.data?.error?.message || `API ${res.status}`);
|
|
15
|
-
return res.data;
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
async validate(artifact, providerUrl, model, apiKey) {
|
|
19
|
-
const res = await apiRequest(`${providerUrl}/validate`, 'POST', {
|
|
20
|
-
artifact, format_version: '0.0.7'
|
|
21
|
-
}, apiKey);
|
|
22
|
-
if (res.status !== 200) throw new Error(res.data?.error?.message || `API ${res.status}`);
|
|
23
|
-
return res.data;
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
async generate(intention, options, providerUrl, model, apiKey) {
|
|
27
|
-
const res = await apiRequest(`${providerUrl}/generate`, 'POST', {
|
|
28
|
-
intention, options, format_version: '0.0.7'
|
|
29
|
-
}, apiKey);
|
|
30
|
-
if (res.status !== 200) throw new Error(res.data?.error?.message || `API ${res.status}`);
|
|
31
|
-
return res.data;
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
async execute(artifact, providerUrl, model, apiKey) {
|
|
35
|
-
const res = await apiRequest(`${providerUrl}/execute`, 'POST', {
|
|
36
|
-
artifact, format_version: '0.0.7'
|
|
37
|
-
}, apiKey);
|
|
38
|
-
if (res.status !== 200) throw new Error(res.data?.error?.message || `API ${res.status}`);
|
|
39
|
-
return res.data;
|
|
40
|
-
}
|
|
41
|
-
};
|
package/src/adapters/ollama.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [EXPERIMENTAL] Ollama Adapter
|
|
3
|
-
* Adapts JOS Intentions to Local LLM Prompts
|
|
4
|
-
*/
|
|
5
|
-
const { apiRequest } = require('../utils/request');
|
|
6
|
-
|
|
7
|
-
const C = {
|
|
8
|
-
dim: '\x1b[2m',
|
|
9
|
-
reset: '\x1b[0m'
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
async function callOllama(baseUrl, model, messages) {
|
|
13
|
-
// Ensure URL ends with /api/chat
|
|
14
|
-
const endpoint = baseUrl.endsWith('/api/chat') ? baseUrl : `${baseUrl.replace(/\/$/, '')}/api/chat`;
|
|
15
|
-
|
|
16
|
-
console.log(`${C.dim}Connecting to Ollama at ${endpoint}...${C.reset}`);
|
|
17
|
-
|
|
18
|
-
return apiRequest(endpoint, 'POST', {
|
|
19
|
-
model: model || 'llama3', // Default to llama3 if not specified
|
|
20
|
-
messages: messages,
|
|
21
|
-
stream: false,
|
|
22
|
-
format: 'json' // Force JSON mode for structure
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
module.exports = {
|
|
27
|
-
type: 'ollama',
|
|
28
|
-
experimental: true,
|
|
29
|
-
|
|
30
|
-
async optimize(artifact, options, providerUrl, model) {
|
|
31
|
-
const prompt = `You are an expert prompt engineer specializing in JOS (JSON Orchestration Spec).
|
|
32
|
-
Your goal is to optimize the PROMPTS inside the following JOS artifact.
|
|
33
|
-
|
|
34
|
-
GOALS: ${options.optimization_goals.join(', ')}
|
|
35
|
-
|
|
36
|
-
INPUT ARTIFACT:
|
|
37
|
-
${JSON.stringify(artifact, null, 2)}
|
|
38
|
-
|
|
39
|
-
INSTRUCTIONS:
|
|
40
|
-
1. Analyze the 'intention' and 'tasks'.
|
|
41
|
-
2. Rewrite task descriptions to be more precise, using Chain-of-Thought or clear directives.
|
|
42
|
-
3. Do NOT change the structure or keys. Only text content.
|
|
43
|
-
4. Return the COMPLETE logical JSON of the optimized artifact.
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
const res = await callOllama(providerUrl, model, [{ role: 'user', content: prompt }]);
|
|
47
|
-
|
|
48
|
-
if (res.status !== 200) throw new Error(`Ollama Error: ${res.status} ${JSON.stringify(res.data)}`);
|
|
49
|
-
|
|
50
|
-
const optimizedArtifact = typeof res.data.message.content === 'string'
|
|
51
|
-
? JSON.parse(res.data.message.content)
|
|
52
|
-
: res.data.message.content;
|
|
53
|
-
|
|
54
|
-
// Mock quality score for local execution
|
|
55
|
-
return {
|
|
56
|
-
optimized_artifact: optimizedArtifact,
|
|
57
|
-
quality_score: { before: 0.5, after: 0.9 },
|
|
58
|
-
changes: [{ field: 'tasks', original: '...', optimized: '...', reason: 'Ollama Optimization' }]
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
async validate(artifact, providerUrl, model) {
|
|
63
|
-
const prompt = `You are a QA Validator for JOS Artifacts.
|
|
64
|
-
Analyze the following artifact for logical consistency, missing context, or vague prompts.
|
|
65
|
-
|
|
66
|
-
ARTIFACT:
|
|
67
|
-
${JSON.stringify(artifact, null, 2)}
|
|
68
|
-
|
|
69
|
-
Return a JSON object with:
|
|
70
|
-
- valid (boolean)
|
|
71
|
-
- quality_score (0.0 to 1.0)
|
|
72
|
-
- issues (array of objects { field, message, severity })
|
|
73
|
-
- suggestions (array of objects { field, suggestion })
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
const res = await callOllama(providerUrl, model, [{ role: 'user', content: prompt }]);
|
|
77
|
-
if (res.status !== 200) throw new Error(`Ollama Error: ${res.status}`);
|
|
78
|
-
|
|
79
|
-
return typeof res.data.message.content === 'string'
|
|
80
|
-
? JSON.parse(res.data.message.content)
|
|
81
|
-
: res.data.message.content;
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
async generate(intention, options, providerUrl, model) {
|
|
85
|
-
const prompt = `You are a JOS Architect. Generate a valid .jos JSON artifact based on this user intention.
|
|
86
|
-
|
|
87
|
-
INTENTION: "${intention}"
|
|
88
|
-
|
|
89
|
-
REQUIREMENTS:
|
|
90
|
-
- Type: ${options.type || 'task'}
|
|
91
|
-
- Format: JOS v0.0.7 (JSON)
|
|
92
|
-
- Must include 'intention', 'tasks', and 'orchestration'.
|
|
93
|
-
|
|
94
|
-
Return ONLY the JSON.
|
|
95
|
-
`;
|
|
96
|
-
|
|
97
|
-
const res = await callOllama(providerUrl, model, [{ role: 'user', content: prompt }]);
|
|
98
|
-
if (res.status !== 200) throw new Error(`Ollama Error: ${res.status}`);
|
|
99
|
-
|
|
100
|
-
const artifact = typeof res.data.message.content === 'string'
|
|
101
|
-
? JSON.parse(res.data.message.content)
|
|
102
|
-
: res.data.message.content;
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
confidence: 0.85,
|
|
106
|
-
artifact: artifact,
|
|
107
|
-
assumptions: ['Generated by Local Ollama']
|
|
108
|
-
};
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
async execute(artifact, providerUrl, model) {
|
|
112
|
-
// MAGIC JOSFOXAI PROMPT WRAPPER
|
|
113
|
-
const systemPrompt = `[SYSTEM]
|
|
114
|
-
You are an Intention-Native Agent powered by JOSFOXAI.
|
|
115
|
-
MAGIC_KEY: JOSFOX_ALPHA_V1
|
|
116
|
-
|
|
117
|
-
You are executing the following JOS Artifact.
|
|
118
|
-
Strictly adhere to the 'orchestration' flow and 'requirements'.
|
|
119
|
-
|
|
120
|
-
ARTIFACT:
|
|
121
|
-
${JSON.stringify(artifact, null, 2)}
|
|
122
|
-
`;
|
|
123
|
-
|
|
124
|
-
const userPrompt = `Execute the intention defined in the artifact. Report your status.`;
|
|
125
|
-
|
|
126
|
-
const res = await callOllama(providerUrl, model, [
|
|
127
|
-
{ role: 'system', content: systemPrompt },
|
|
128
|
-
{ role: 'user', content: userPrompt }
|
|
129
|
-
]);
|
|
130
|
-
|
|
131
|
-
if (res.status !== 200) throw new Error(`Ollama Error: ${res.status}`);
|
|
132
|
-
|
|
133
|
-
// For non-native adapters, the result is text/chat, which we wrap in a JOS result structure
|
|
134
|
-
return {
|
|
135
|
-
status: 'completed',
|
|
136
|
-
output: typeof res.data.message.content === 'string' ? res.data.message.content : JSON.stringify(res.data.message.content),
|
|
137
|
-
provider: 'ollama'
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
};
|
package/src/commands/add.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JOS ADD Command - Add task definitions to .jos artifacts
|
|
3
|
-
* Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
|
|
10
|
-
const C = {
|
|
11
|
-
reset: '\x1b[0m', bold: '\x1b[1m',
|
|
12
|
-
purple: '\x1b[38;5;135m', cyan: '\x1b[38;5;51m',
|
|
13
|
-
green: '\x1b[38;5;78m', red: '\x1b[38;5;196m',
|
|
14
|
-
yellow: '\x1b[38;5;220m', gray: '\x1b[38;5;245m'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
exports.execute = async (args, home) => {
|
|
18
|
-
const help = args.includes('--help') || args.includes('-h');
|
|
19
|
-
|
|
20
|
-
if (help || args.length < 2) {
|
|
21
|
-
console.log(`
|
|
22
|
-
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
23
|
-
${C.bold}JOS ADD${C.reset} // Add task definitions to .jos artifacts
|
|
24
|
-
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
25
|
-
|
|
26
|
-
${C.bold}Usage:${C.reset}
|
|
27
|
-
jos add <task-name> <file.jos> [options]
|
|
28
|
-
|
|
29
|
-
${C.bold}Options:${C.reset}
|
|
30
|
-
--type <shell|jos> Task type (default: shell)
|
|
31
|
-
--command <cmd> Shell command to execute
|
|
32
|
-
--artifact <path> Path to sub-artifact (for type=jos)
|
|
33
|
-
--description <desc> Task description
|
|
34
|
-
--flow <name> Also add to this flow's steps
|
|
35
|
-
|
|
36
|
-
${C.bold}Examples:${C.reset}
|
|
37
|
-
jos add build my-artifact.jos --command "npm run build"
|
|
38
|
-
jos add deploy my-artifact.jos --type jos --artifact ./deploy.jos
|
|
39
|
-
jos add test my-artifact.jos --command "npm test" --flow main
|
|
40
|
-
`);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const taskName = args[0];
|
|
45
|
-
const targetFile = args[1];
|
|
46
|
-
|
|
47
|
-
// Parse options
|
|
48
|
-
const getArg = (flag) => {
|
|
49
|
-
const idx = args.indexOf(flag);
|
|
50
|
-
return idx !== -1 && args[idx + 1] ? args[idx + 1] : null;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const taskType = getArg('--type') || 'shell';
|
|
54
|
-
const command = getArg('--command');
|
|
55
|
-
const artifact = getArg('--artifact');
|
|
56
|
-
const description = getArg('--description') || `Task: ${taskName}`;
|
|
57
|
-
const flowName = getArg('--flow');
|
|
58
|
-
|
|
59
|
-
// Validate
|
|
60
|
-
if (taskType === 'shell' && !command) {
|
|
61
|
-
console.log(`${C.red}✖ Error: --command is required for shell tasks${C.reset}`);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
if (taskType === 'jos' && !artifact) {
|
|
65
|
-
console.log(`${C.red}✖ Error: --artifact is required for jos tasks${C.reset}`);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Load artifact
|
|
70
|
-
let artifactPath = path.resolve(targetFile);
|
|
71
|
-
if (!fs.existsSync(artifactPath)) {
|
|
72
|
-
console.log(`${C.red}✖ Artifact not found: ${targetFile}${C.reset}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let content;
|
|
77
|
-
try {
|
|
78
|
-
content = JSON.parse(fs.readFileSync(artifactPath, 'utf8'));
|
|
79
|
-
} catch (e) {
|
|
80
|
-
console.log(`${C.red}✖ Invalid JSON: ${e.message}${C.reset}`);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Ensure orchestration structure exists
|
|
85
|
-
if (!content.orchestration) content.orchestration = {};
|
|
86
|
-
if (!content.orchestration.definitions) content.orchestration.definitions = {};
|
|
87
|
-
if (!content.orchestration.flows) content.orchestration.flows = {};
|
|
88
|
-
if (!content.orchestration.flows.main) content.orchestration.flows.main = { steps: [] };
|
|
89
|
-
|
|
90
|
-
// Check for duplicate
|
|
91
|
-
if (content.orchestration.definitions[taskName]) {
|
|
92
|
-
console.log(`${C.yellow}⚠ Task '${taskName}' already exists. Overwriting...${C.reset}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Add task definition
|
|
96
|
-
const taskDef = {
|
|
97
|
-
type: taskType,
|
|
98
|
-
description: description
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (taskType === 'shell') {
|
|
102
|
-
taskDef.command = command;
|
|
103
|
-
} else if (taskType === 'jos') {
|
|
104
|
-
taskDef.artifact = artifact;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
content.orchestration.definitions[taskName] = taskDef;
|
|
108
|
-
console.log(`${C.green}✓ Added task definition: ${taskName}${C.reset}`);
|
|
109
|
-
|
|
110
|
-
// Add to flow if specified
|
|
111
|
-
if (flowName) {
|
|
112
|
-
if (!content.orchestration.flows[flowName]) {
|
|
113
|
-
content.orchestration.flows[flowName] = { steps: [] };
|
|
114
|
-
}
|
|
115
|
-
if (!content.orchestration.flows[flowName].steps.includes(taskName)) {
|
|
116
|
-
content.orchestration.flows[flowName].steps.push(taskName);
|
|
117
|
-
console.log(`${C.green}✓ Added to flow: ${flowName}${C.reset}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Write updated artifact
|
|
122
|
-
fs.writeFileSync(artifactPath, JSON.stringify(content, null, 2));
|
|
123
|
-
console.log(`${C.green}✓ Updated: ${targetFile}${C.reset}`);
|
|
124
|
-
|
|
125
|
-
// Regenerate manifest
|
|
126
|
-
const manifestPath = artifactPath + '.sig.json';
|
|
127
|
-
const newContent = fs.readFileSync(artifactPath, 'utf8');
|
|
128
|
-
const sha256 = crypto.createHash('sha256').update(newContent).digest('hex');
|
|
129
|
-
const stats = fs.statSync(artifactPath);
|
|
130
|
-
|
|
131
|
-
const manifest = {
|
|
132
|
-
"$schema": "https://josfox.ai/schemas/jos-manifest-0.0.7.json",
|
|
133
|
-
"manifest_version": "1.0.0",
|
|
134
|
-
"artifact_ref": path.basename(artifactPath),
|
|
135
|
-
"artifact_sha256": sha256,
|
|
136
|
-
"artifact_size_bytes": stats.size,
|
|
137
|
-
"timestamp": new Date().toISOString(),
|
|
138
|
-
"generated_by": "jos add command",
|
|
139
|
-
"canonical_version": "Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)"
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
143
|
-
console.log(`${C.green}✓ Updated manifest: ${path.basename(manifestPath)}${C.reset}`);
|
|
144
|
-
console.log(`${C.gray} SHA256: ${sha256}${C.reset}`);
|
|
145
|
-
};
|
package/src/commands/get.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JOS GET Command - Fetch .jos packages from repos
|
|
3
|
-
* Architecture similar to npm/brew/apt but offline-first
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const https = require('https');
|
|
9
|
-
const http = require('http');
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
|
|
12
|
-
// AURORA colors
|
|
13
|
-
const C = {
|
|
14
|
-
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
15
|
-
purple: '\x1b[38;5;135m', magenta: '\x1b[38;5;198m', cyan: '\x1b[38;5;51m',
|
|
16
|
-
green: '\x1b[38;5;78m', red: '\x1b[38;5;196m', gray: '\x1b[38;5;245m',
|
|
17
|
-
white: '\x1b[38;5;255m', yellow: '\x1b[38;5;220m'
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Default repos configuration
|
|
21
|
-
const DEFAULT_REPOS = {
|
|
22
|
-
default: 'https://registry.josfox.ai',
|
|
23
|
-
local: '~/.jos/artifacts'
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function getReposConfig(home) {
|
|
27
|
-
const reposPath = path.join(home, 'repos.json');
|
|
28
|
-
if (fs.existsSync(reposPath)) {
|
|
29
|
-
return { ...DEFAULT_REPOS, ...JSON.parse(fs.readFileSync(reposPath)) };
|
|
30
|
-
}
|
|
31
|
-
return DEFAULT_REPOS;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function computeIntegrity(content) {
|
|
35
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Lock file management
|
|
39
|
-
function getLockPath(home) {
|
|
40
|
-
return path.join(home, 'lock.json');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function loadLock(home) {
|
|
44
|
-
const lockPath = getLockPath(home);
|
|
45
|
-
if (fs.existsSync(lockPath)) {
|
|
46
|
-
return JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
47
|
-
}
|
|
48
|
-
return { locked: null, packages: {} };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function saveLock(home, lock) {
|
|
52
|
-
const lockPath = getLockPath(home);
|
|
53
|
-
lock.locked = new Date().toISOString();
|
|
54
|
-
fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function updateLock(home, pkgName, version, integrity) {
|
|
58
|
-
const lock = loadLock(home);
|
|
59
|
-
lock.packages[pkgName] = { version, integrity: `sha256-${integrity}` };
|
|
60
|
-
saveLock(home, lock);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Parse package reference: "pkg", "repo:pkg", "./path", "http://..."
|
|
64
|
-
function parsePackageRef(ref) {
|
|
65
|
-
// URL
|
|
66
|
-
if (ref.startsWith('http://') || ref.startsWith('https://')) {
|
|
67
|
-
return { type: 'url', url: ref };
|
|
68
|
-
}
|
|
69
|
-
// Local path
|
|
70
|
-
if (ref.startsWith('./') || ref.startsWith('/') || ref.startsWith('~')) {
|
|
71
|
-
return { type: 'local', path: ref };
|
|
72
|
-
}
|
|
73
|
-
// Named repo: "myrepo:package"
|
|
74
|
-
if (ref.includes(':')) {
|
|
75
|
-
const [repo, pkg] = ref.split(':');
|
|
76
|
-
return { type: 'repo', repo, package: pkg };
|
|
77
|
-
}
|
|
78
|
-
// Default: package name
|
|
79
|
-
return { type: 'default', package: ref };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Fetch from URL
|
|
83
|
-
function fetchUrl(url) {
|
|
84
|
-
return new Promise((resolve, reject) => {
|
|
85
|
-
const protocol = url.startsWith('https') ? https : http;
|
|
86
|
-
protocol.get(url, (res) => {
|
|
87
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
88
|
-
return fetchUrl(res.headers.location).then(resolve).catch(reject);
|
|
89
|
-
}
|
|
90
|
-
if (res.statusCode !== 200) {
|
|
91
|
-
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
92
|
-
}
|
|
93
|
-
let data = '';
|
|
94
|
-
res.on('data', chunk => data += chunk);
|
|
95
|
-
res.on('end', () => resolve(data));
|
|
96
|
-
}).on('error', reject);
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
exports.execute = async (args, home) => {
|
|
101
|
-
const target = args[0];
|
|
102
|
-
const fromRepo = args.find((a, i) => args[i - 1] === '--from');
|
|
103
|
-
const showHelp = args.includes('--help') || args.includes('-h');
|
|
104
|
-
|
|
105
|
-
if (showHelp || !target) {
|
|
106
|
-
console.log(`
|
|
107
|
-
${C.cyan}${C.bold}JOS GET${C.reset} - Fetch .jos packages
|
|
108
|
-
|
|
109
|
-
${C.white}Usage:${C.reset} jos get <package> [options]
|
|
110
|
-
|
|
111
|
-
${C.white}Package formats:${C.reset}
|
|
112
|
-
jos get hello ${C.dim}# From default registry${C.reset}
|
|
113
|
-
jos get ./my-package ${C.dim}# From local folder${C.reset}
|
|
114
|
-
jos get myrepo:package ${C.dim}# From named repo${C.reset}
|
|
115
|
-
jos get http://host/p.jos ${C.dim}# From URL${C.reset}
|
|
116
|
-
|
|
117
|
-
${C.white}Options:${C.reset}
|
|
118
|
-
--from <host> Override source (e.g., --from 192.168.1.10:1111)
|
|
119
|
-
--help, -h Show this help
|
|
120
|
-
|
|
121
|
-
${C.white}Configuration:${C.reset}
|
|
122
|
-
Repos defined in: ~/.jos/repos.json
|
|
123
|
-
|
|
124
|
-
${C.white}Example repos.json:${C.reset}
|
|
125
|
-
{
|
|
126
|
-
"default": "https://registry.josfox.ai",
|
|
127
|
-
"local": "~/.jos/artifacts",
|
|
128
|
-
"myrepo": "http://192.168.1.10:1111"
|
|
129
|
-
}
|
|
130
|
-
`);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log(`\n${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
|
|
135
|
-
console.log(`${C.cyan}${C.bold}JOS GET${C.reset} // ${C.gray}Package Manager${C.reset}`);
|
|
136
|
-
console.log(`${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`);
|
|
137
|
-
|
|
138
|
-
const repos = getReposConfig(home);
|
|
139
|
-
const artifactsDir = path.join(home, 'artifacts');
|
|
140
|
-
if (!fs.existsSync(artifactsDir)) fs.mkdirSync(artifactsDir, { recursive: true });
|
|
141
|
-
|
|
142
|
-
// Parse reference
|
|
143
|
-
let ref = parsePackageRef(target);
|
|
144
|
-
|
|
145
|
-
// Override with --from
|
|
146
|
-
if (fromRepo) {
|
|
147
|
-
const host = fromRepo.includes(':') ? fromRepo : `${fromRepo}:1111`;
|
|
148
|
-
ref = { type: 'url', url: `http://${host}/${target}.jos` };
|
|
149
|
-
console.log(`${C.white}📡 Source:${C.reset} ${host}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(`${C.white}📦 Package:${C.reset} ${target}`);
|
|
153
|
-
console.log(`${C.white}📁 Destination:${C.reset} ${artifactsDir}`);
|
|
154
|
-
|
|
155
|
-
let content, sourcePath;
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
switch (ref.type) {
|
|
159
|
-
case 'local':
|
|
160
|
-
// Local folder/file
|
|
161
|
-
sourcePath = ref.path.replace('~', process.env.HOME);
|
|
162
|
-
if (!sourcePath.endsWith('.jos')) sourcePath += '.jos';
|
|
163
|
-
if (!fs.existsSync(sourcePath)) throw new Error(`Not found: ${sourcePath}`);
|
|
164
|
-
content = fs.readFileSync(sourcePath, 'utf8');
|
|
165
|
-
console.log(`${C.green}✓ Found locally:${C.reset} ${sourcePath}`);
|
|
166
|
-
break;
|
|
167
|
-
|
|
168
|
-
case 'url':
|
|
169
|
-
// Direct URL
|
|
170
|
-
console.log(`${C.dim}⏳ Fetching from URL...${C.reset}`);
|
|
171
|
-
content = await fetchUrl(ref.url);
|
|
172
|
-
console.log(`${C.green}✓ Downloaded from:${C.reset} ${ref.url}`);
|
|
173
|
-
break;
|
|
174
|
-
|
|
175
|
-
case 'repo':
|
|
176
|
-
// Named repo
|
|
177
|
-
const repoUrl = repos[ref.repo];
|
|
178
|
-
if (!repoUrl) throw new Error(`Unknown repo: ${ref.repo}`);
|
|
179
|
-
const pkgUrl = repoUrl.startsWith('http')
|
|
180
|
-
? `${repoUrl}/${ref.package}.jos`
|
|
181
|
-
: path.join(repoUrl.replace('~', process.env.HOME), `${ref.package}.jos`);
|
|
182
|
-
|
|
183
|
-
if (pkgUrl.startsWith('http')) {
|
|
184
|
-
console.log(`${C.dim}⏳ Fetching from ${ref.repo}...${C.reset}`);
|
|
185
|
-
content = await fetchUrl(pkgUrl);
|
|
186
|
-
} else {
|
|
187
|
-
if (!fs.existsSync(pkgUrl)) throw new Error(`Not found: ${pkgUrl}`);
|
|
188
|
-
content = fs.readFileSync(pkgUrl, 'utf8');
|
|
189
|
-
}
|
|
190
|
-
console.log(`${C.green}✓ Found in ${ref.repo}:${C.reset} ${ref.package}`);
|
|
191
|
-
break;
|
|
192
|
-
|
|
193
|
-
case 'default':
|
|
194
|
-
// Try local first, then default registry
|
|
195
|
-
const localPath = path.join(artifactsDir, `${ref.package}.jos`);
|
|
196
|
-
if (fs.existsSync(localPath)) {
|
|
197
|
-
console.log(`${C.green}✓ Already cached:${C.reset} ${localPath}`);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Try default registry
|
|
202
|
-
if (repos.default && repos.default.startsWith('http')) {
|
|
203
|
-
console.log(`${C.dim}⏳ Fetching from registry...${C.reset}`);
|
|
204
|
-
try {
|
|
205
|
-
content = await fetchUrl(`${repos.default}/${ref.package}.jos`);
|
|
206
|
-
console.log(`${C.green}✓ Downloaded:${C.reset} ${ref.package}`);
|
|
207
|
-
} catch (e) {
|
|
208
|
-
throw new Error(`Package not found: ${ref.package}`);
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
throw new Error(`Package not found: ${ref.package}`);
|
|
212
|
-
}
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Validate JSON
|
|
217
|
-
let artifact;
|
|
218
|
-
try {
|
|
219
|
-
artifact = JSON.parse(content);
|
|
220
|
-
} catch (e) {
|
|
221
|
-
throw new Error('Invalid .jos file (not valid JSON)');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Compute integrity
|
|
225
|
-
const integrity = computeIntegrity(content);
|
|
226
|
-
console.log(`${C.white}🔐 Integrity:${C.reset} ${C.green}${integrity.substring(0, 16)}...${C.reset}`);
|
|
227
|
-
|
|
228
|
-
// Save artifact
|
|
229
|
-
const pkgName = artifact.meta?.name || artifact.name || target.replace(/[^a-z0-9]/gi, '_');
|
|
230
|
-
const pkgVersion = artifact.meta?.version || artifact.orchestration_contract?.version || '1.0.0';
|
|
231
|
-
const destPath = path.join(artifactsDir, `${pkgName}.jos`);
|
|
232
|
-
fs.writeFileSync(destPath, content);
|
|
233
|
-
|
|
234
|
-
// Update lock file
|
|
235
|
-
updateLock(home, pkgName, pkgVersion, integrity);
|
|
236
|
-
console.log(`${C.dim}📋 Lock file updated${C.reset}`);
|
|
237
|
-
|
|
238
|
-
console.log(`\n${C.green}✓ Saved to:${C.reset} ${destPath}`);
|
|
239
|
-
console.log(`${C.dim} Run with: jos run ${pkgName}.jos${C.reset}\n`);
|
|
240
|
-
|
|
241
|
-
} catch (e) {
|
|
242
|
-
console.log(`\n${C.red}✖ Error:${C.reset} ${e.message}\n`);
|
|
243
|
-
process.exit(1);
|
|
244
|
-
}
|
|
245
|
-
};
|