├── .gitignore
├── .editorconfig
├── .eslintrc.cjs
├── .github
└── workflows
│ └── main.yml
├── package.json
├── CHANGELOG.md
├── LICENSE
├── README.md
└── bin
└── jsonld.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw[op]
2 | *~
3 | .cproject
4 | .project
5 | *.sublime-project
6 | *.sublime-workspace
7 | .DS_Store
8 | .settings
9 | coverage
10 | node_modules
11 | v8.log
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.{js,json,jsonld,yaml,yml}]
12 | indent_style = space
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'digitalbazaar',
8 | 'digitalbazaar/module',
9 | 'digitalbazaar/jsdoc'
10 | ],
11 | rules: {
12 | 'unicorn/prefer-node-protocol': 'error'
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Main CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | timeout-minutes: 10
9 | strategy:
10 | matrix:
11 | node-version: [20.x]
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Use Node.js ${{ matrix.node-version }}
15 | uses: actions/setup-node@v4
16 | with:
17 | node-version: ${{ matrix.node-version }}
18 | - name: Install
19 | run: npm install
20 | - name: Lint
21 | run: npm run lint
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jsonld-cli",
3 | "version": "2.0.1-0",
4 | "description": "A JSON-LD command line interface tool.",
5 | "homepage": "https://github.com/digitalbazaar/jsonld-cli",
6 | "author": {
7 | "name": "Digital Bazaar, Inc.",
8 | "email": "support@digitalbazaar.com",
9 | "url": "https://digitalbazaar.com/"
10 | },
11 | "contributors": [
12 | {
13 | "name": "Dave Longley",
14 | "email": "dlongley@digitalbazaar.com"
15 | },
16 | {
17 | "name": "David I. Lehn",
18 | "email": "dlehn@digitalbazaar.com"
19 | }
20 | ],
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/digitalbazaar/jsonld-cli"
24 | },
25 | "bugs": {
26 | "url": "https://github.com/digitalbazaar/jsonld-cli/issues",
27 | "email": "support@digitalbazaar.com"
28 | },
29 | "license": "BSD-3-Clause",
30 | "bin": {
31 | "jsonld": "./bin/jsonld.js"
32 | },
33 | "type": "module",
34 | "dependencies": {
35 | "commander": "^11.1.0",
36 | "jsonld": "^8.3.2",
37 | "jsonld-request": "^2.0.1"
38 | },
39 | "devDependencies": {
40 | "eslint": "^8.56.0",
41 | "eslint-config-digitalbazaar": "^5.0.1",
42 | "eslint-plugin-jsdoc": "^48.0.1",
43 | "eslint-plugin-unicorn": "^50.0.1"
44 | },
45 | "files": [
46 | "bin/**/*.js"
47 | ],
48 | "engines": {
49 | "node": ">=16"
50 | },
51 | "keywords": [
52 | "JSON",
53 | "JSON-LD",
54 | "Linked Data",
55 | "RDF",
56 | "RDFa",
57 | "Semantic Web",
58 | "jsonld"
59 | ],
60 | "scripts": {
61 | "lint": "eslint ."
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # jsonld-cli ChangeLog
2 |
3 | ## 2.0.0 - 2024-01-04
4 |
5 | ## Changed
6 | - Set version from `package.json`.
7 | - **BREAKING**: Update dependencies.
8 | - `commander` requires Node.js >= 16.
9 |
10 | ## 1.0.0 - 2022-09-01
11 |
12 | ## Changed
13 | - **BREAKING**: Update dependencies. Likely behavior changes since last
14 | release.
15 | - **BREAKING**: Change `--nquads` option to `--n-quads`.
16 | - **BREAKING**: Requre Node.js >=14.
17 | - **BREAKING**: `base` now defaults to `null`. Use `-b ` or `--base
18 | ` to set `base` explicitly. Use `-B/--auto-base` to set `base`
19 | automatically based on the source file, URL, or stdin.
20 | - **BREAKING**: Primary input can be stdin, file, HTTP, or HTTPS resources.
21 | Secondary input can only be HTTP or HTTPS for security reasons unless the
22 | `-a/--allow` option is used.
23 | - **BREAKING**: Change `normalize` command to `canonize`.
24 |
25 | ## Added
26 | - Add `toRdf` command.
27 | - Add `lint` command. Note that this uses currently private unstable
28 | [jsonld.js][] APIs.
29 | - Add `-s/--safe` `safe` mode to commands.
30 | - Add `-l/--lint` `lint` mode to commands.
31 | - Add `-a/--allow` option to change allowed secondary resource loaders.
32 |
33 | ## 0.3.0 - 2018-07-06
34 |
35 | ## Changed
36 | - *BREAKING*: Update dependencies. Includes update to jsonld.js 1.x which fixes
37 | bugs but could also cause some behavior changes. Future updates will include
38 | more processing control flags.
39 |
40 | ## 0.2.0 - 2017-12-18
41 |
42 | ## Changed
43 | - Updated dependencies.
44 |
45 | ## 0.1.0 - 2015-09-12
46 |
47 | ### Added
48 | - Command line interface tool from [jsonld.js][].
49 |
50 | [jsonld.js]: https://github.com/digitalbazaar/jsonld.js
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | You may use the jsonld.js project under the terms of the BSD License.
2 |
3 | You are free to use this project in commercial projects as long as the
4 | copyright header is left intact.
5 |
6 | If you are a commercial entity and use this set of libraries in your
7 | commercial software then reasonable payment to Digital Bazaar, if you can
8 | afford it, is not required but is expected and would be appreciated. If this
9 | library saves you time, then it's saving you money. The cost of developing
10 | JSON-LD was on the order of several months of work and tens of
11 | thousands of dollars. We are attempting to strike a balance between helping
12 | the development community while not being taken advantage of by lucrative
13 | commercial entities for our efforts.
14 |
15 | -------------------------------------------------------------------------------
16 | New BSD License (3-clause)
17 | Copyright (c) 2010, Digital Bazaar, Inc.
18 | All rights reserved.
19 |
20 | Redistribution and use in source and binary forms, with or without
21 | modification, are permitted provided that the following conditions are met:
22 | * Redistributions of source code must retain the above copyright
23 | notice, this list of conditions and the following disclaimer.
24 | * Redistributions in binary form must reproduce the above copyright
25 | notice, this list of conditions and the following disclaimer in the
26 | documentation and/or other materials provided with the distribution.
27 | * Neither the name of Digital Bazaar, Inc. nor the
28 | names of its contributors may be used to endorse or promote products
29 | derived from this software without specific prior written permission.
30 |
31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
32 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
33 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
34 | DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
35 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
38 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | jsonld-cli
2 | ==========
3 |
4 | Introduction
5 | ------------
6 |
7 | This module provides a command line tool `jsonld` to manipulate [JSON-LD][]
8 | data. It is written in JavaScript for [Node.js][] and uses the [jsonld.js][]
9 | and [jsonld-request][] libraries. Inputs can be from stdin, URLs, or files.
10 |
11 | ## Requirements
12 |
13 | * [Node.js][]
14 | * [npm][]
15 |
16 | ## Installation
17 |
18 | ### Install from NPM
19 |
20 | ```
21 | npm install -g jsonld-cli
22 | ```
23 |
24 | ### Use directly with npx
25 |
26 | ```
27 | npx jsonld-cli ...
28 | ```
29 |
30 | ## Usage
31 |
32 | The `jsonld` command line tool can be used to:
33 |
34 | * Check JSON-LD for various problematic data.
35 | * Transform JSON-LD to compact, expanded, flattened, or canonized form.
36 | * Transform [RDFa][] to JSON-LD.
37 | * Canonize JSON-LD/RDFa Datasets to [N-Quads][].
38 |
39 | To show tool options, a list of commands, or command options:
40 |
41 | jsonld --help
42 | jsonld COMMAND --help
43 |
44 | To check JSON-LD for some common problems:
45 |
46 | jsonld lint "https://example.com/data.json"
47 |
48 | To compact a document on the Web using a JSON-LD context published on
49 | the Web:
50 |
51 | jsonld compact -c "https://w3id.org/payswarm/v1" "http://recipes.payswarm.com/?p=10554"
52 |
53 | The command above will read in a PaySwarm Asset and Listing in [RDFa][] 1.0
54 | format, convert it to JSON-LD expanded form, compact it using the
55 | 'https://w3id.org/payswarm/v1' context, and dump it out to the console in
56 | compacted form.
57 |
58 | jsonld canonize -q "http://recipes.payswarm.com/?p=10554"
59 |
60 | The command above will read in a PaySwarm Asset and Listing in [RDFa][] 1.0
61 | format, canonize the data using the RDF Dataset canonicalization algorithm, and
62 | then dump the output to canonized [N-Quads][] format. The [N-Quads][] can then
63 | be processed via SHA-256, or similar algorithm, to get a deterministic hash of
64 | the contents of the Dataset.
65 |
66 | Security Considerations
67 | -----------------------
68 |
69 | * This tool is able to read stdin, local files, and remote resources.
70 | * Loading of remote resources may reveal aspects of the data being processed.
71 | * Input data may recursively load remote resources.
72 | * Input data may load arbitrary local files if allowed.
73 | * Processing data that uses untrusted remote resources could result in
74 | unexpected output.
75 |
76 | Commercial Support
77 | ------------------
78 |
79 | Commercial support for this library is available upon request from
80 | [Digital Bazaar][]: support@digitalbazaar.com
81 |
82 | Source Code
83 | -----------
84 |
85 | https://github.com/digitalbazaar/jsonld-cli
86 |
87 | [Digital Bazaar]: https://digitalbazaar.com/
88 | [JSON-LD]: https://json-ld.org/
89 | [N-Quads]: https://www.w3.org/TR/n-quads/
90 | [Node.js]: https://nodejs.org/
91 | [RDFa]: http://www.w3.org/TR/rdfa-core/
92 | [json-ld.org]: https://github.com/json-ld/json-ld.org
93 | [jsonld-request]: https://github.com/digitalbazaar/jsonld-request
94 | [jsonld.js]: https://github.com/digitalbazaar/jsonld.js
95 | [npm]: https://npmjs.org/
96 |
--------------------------------------------------------------------------------
/bin/jsonld.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * A command line JSON-LD utility.
4 | *
5 | * @author David I. Lehn
6 | *
7 | * BSD 3-Clause License
8 | * Copyright (c) 2013-2022 Digital Bazaar, Inc.
9 | * All rights reserved.
10 | */
11 | import {fileURLToPath} from 'node:url';
12 | import https from 'node:https';
13 | import {inspect} from 'node:util';
14 | import jsonld from 'jsonld';
15 | import {jsonldRequest} from 'jsonld-request';
16 | import path from 'node:path';
17 | import {program} from 'commander';
18 | import {readFileSync} from 'node:fs';
19 |
20 | const version = {
21 | value: undefined,
22 | toString() {
23 | // load version when used
24 | if(this.value === undefined) {
25 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
26 | const pkg =
27 | JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json')));
28 | this.value = pkg.version;
29 | }
30 | return this.value;
31 | }
32 | };
33 |
34 | program.version(version);
35 |
36 | // all allowed modes for jsonld-request
37 | const ALLOW_ALL = ['stdin', 'file', 'http', 'https'];
38 | const ALLOW_DEFAULT = ['http', 'https'];
39 | const ALLOW_NONE = [];
40 |
41 | // Parse the string or value and return the boolean value encoded or raise an
42 | // exception.
43 | function boolify(value) {
44 | if(typeof value === 'boolean') {
45 | return value;
46 | }
47 | if(typeof value === 'string' && value) {
48 | switch(value.toLowerCase()) {
49 | case 'true':
50 | case 't':
51 | case '1':
52 | case 'yes':
53 | case 'y':
54 | return true;
55 | case 'false':
56 | case 'f':
57 | case '0':
58 | case 'no':
59 | case 'n':
60 | return false;
61 | }
62 | }
63 | // if here we couldn't parse it
64 | throw new Error('Invalid boolean:' + value);
65 | }
66 |
67 | // common output function
68 | async function _output(data, cmd) {
69 | if(typeof data === 'object') {
70 | const output = JSON.stringify(data, null, cmd.indent);
71 | process.stdout.write(output);
72 | } else if(typeof data === 'string') {
73 | process.stdout.write(data.trim());
74 | } else {
75 | process.stdout.write(data);
76 | }
77 | if(cmd.newline) {
78 | process.stdout.write('\n');
79 | }
80 | }
81 |
82 | // lint warning handler
83 | function _warningHandler({event, next}) {
84 | if(event.level === 'warning') {
85 | console.log(`WARNING: ${event.message}`);
86 | console.log(inspect(event, {colors: true, depth: 10}));
87 | }
88 | next();
89 | }
90 |
91 | // error handler
92 | function _error(err, msg = 'Error:') {
93 | if(err) {
94 | if(err.stack) {
95 | console.log(err.stack);
96 | } else {
97 | console.log(err.toString());
98 | }
99 | if(typeof err === 'object') {
100 | const {cause, ...options} = err;
101 | if(Object.keys(options).length !== 0) {
102 | console.log(msg, inspect(options, {colors: true, depth: 10}));
103 | }
104 | if(cause) {
105 | _error(cause, 'Error Cause:');
106 | }
107 | }
108 | process.exit(1);
109 | }
110 | }
111 |
112 | // request wrapper to handle primary/secondary loading access
113 | let _primary = true;
114 | async function _jsonldRequest(url, reqOptions, options) {
115 | const _options = {...reqOptions};
116 | if(_primary) {
117 | _options.allow = ALLOW_ALL;
118 | } else {
119 | _options.allow = options.allow;
120 | }
121 | _primary = false;
122 | return jsonldRequest(url, _options);
123 | }
124 |
125 | // check for HTTP/HTTPS URL
126 | function _isHTTP(url) {
127 | return (url.indexOf('http://') === 0 || url.indexOf('https://') === 0);
128 | }
129 |
130 | // init common command options
131 | function _jsonLdCommand(command) {
132 | command
133 | .option('-i, --indent ', 'spaces to indent [2]', Number, 2)
134 | .option('-N, --no-newline', 'do not output the trailing newline [newline]')
135 | .option('-k, --insecure', 'allow insecure connections [false]')
136 | .option('-a, --allow ',
137 | 'allowed secondary resource loaders (none,all,stdin,file,http,https) ' +
138 | '[http,https]')
139 | .option('-t, --type ', 'input data type [auto]')
140 | .option('-B, --auto-base', 'use base IRI from source [false]')
141 | .option('-b, --base ', 'base IRI [null]')
142 | .option('-l, --lint', 'show lint warnings [false]')
143 | .option('-s, --safe', 'enable safe mode [false]');
144 | return command;
145 | }
146 |
147 | // determine source base
148 | function _getSourceBase(command, input) {
149 | // stdin
150 | if(input === '-') {
151 | return 'stdin://';
152 | }
153 | // use input as base if it looks like a URL
154 | if(_isHTTP(input)) {
155 | return input;
156 | }
157 | // use a file URL otherwise
158 | return 'file://' + path.resolve(process.cwd(), input);
159 | }
160 |
161 | // determine base
162 | function _getBase(command, input) {
163 | // explicit base set
164 | if(command.base) {
165 | return command.base;
166 | }
167 | if(command.sourceBase) {
168 | return _getSourceBase(command, input);
169 | }
170 | return null;
171 | }
172 |
173 | // init common request options
174 | function _requestOptions(command, input) {
175 | const options = {};
176 | if(command.insecure) {
177 | options.agent = new https.Agent({rejectUnauthorized: false});
178 | }
179 | if(command.type) {
180 | options.dataType = command.type;
181 | }
182 | options.base = _getBase(command, input);
183 | return options;
184 | }
185 |
186 | // init common jsonld options
187 | function _jsonLdOptions(command, input) {
188 | const options = {};
189 |
190 | if(command.allow) {
191 | // split allow modes
192 | options.allow = command.allow.split(',');
193 | if(options.allow.includes('all')) {
194 | options.allow = ALLOW_ALL;
195 | } else if(options.allow.includes('none')) {
196 | options.allow = ALLOW_NONE;
197 | }
198 | } else {
199 | // default to only load secondary HTTP/HTTPS resources
200 | options.access = ALLOW_DEFAULT;
201 | }
202 |
203 | if(command.lint) {
204 | options.eventHandler = _warningHandler;
205 | }
206 |
207 | if(command.safe) {
208 | options.safe = true;
209 | }
210 |
211 | options.base = _getBase(command, input);
212 |
213 | // setup documentLoader
214 | // FIXME: should be elsewhere
215 | options.documentLoader = async function documentLoader(url) {
216 | const reqOpts = _requestOptions(command, url);
217 | const reqResult = await _jsonldRequest(url, reqOpts, options);
218 | return {
219 | contextUrl: null,
220 | documentUrl: url,
221 | document: reqResult.data || null
222 | };
223 | };
224 |
225 | return options;
226 | }
227 |
228 | program
229 | .on('--help', function() {
230 | console.log();
231 | console.log(
232 | ' The primary input for all commands can be a filename, a URL\n' +
233 | ' beginning with "http://" or "https://", or "-" for stdin (the\n' +
234 | ' default). Secondary loaded resources can only be HTTP or HTTPS\n' +
235 | ' by default for security reasons unless the "-a/--allow" option\n' +
236 | ' is used.');
237 | console.log();
238 | console.log(
239 | ' Input type can be specified as a standard content type or a\n' +
240 | ' simple string for common types. See the "request" extension code\n' +
241 | ' for available types. XML and HTML variations will be converted\n' +
242 | ' with an RDFa processor if available. If the input type is not\n' +
243 | ' specified it will be auto-detected based on file extension, URL\n' +
244 | ' content type, or by guessing with various parsers. Guessing may\n' +
245 | ' not always produce correct results.');
246 | console.log();
247 | console.log(
248 | ' Output type can be specified for the "format" command and a\n' +
249 | ' N-Quads shortcut for the "canonize" command. For other commands\n' +
250 | ' you can pipe JSON-LD output to the "format" command.');
251 | console.log();
252 | });
253 |
254 | _jsonLdCommand(program.command('format [filename|URL|-]'))
255 | .description('format and convert JSON-LD')
256 | .option('-f, --format ', 'output format [json]', String)
257 | .option('-q, --n-quads', 'output application/n-quads [false]')
258 | .option('-j, --json', 'output application/json [true]')
259 | .action(async function format(input, cmd) {
260 | input = input || '-';
261 | const options = _jsonLdOptions(cmd, input);
262 | options.format = cmd.format || 'json';
263 | if(cmd.nQuads) {
264 | options.format = 'application/n-quads';
265 | }
266 | if(cmd.json) {
267 | options.format = 'application/json';
268 | }
269 |
270 | let result;
271 | switch(options.format.toLowerCase()) {
272 | case 'nquads':
273 | case 'n-quads':
274 | case 'application/nquads':
275 | case 'application/n-quads':
276 | // normalize format for toRDF
277 | options.format = 'application/n-quads';
278 | result = await jsonld.toRDF(input, options);
279 | break;
280 | case 'json':
281 | case 'jsonld':
282 | case 'json-ld':
283 | case 'ld+json':
284 | case 'application/json':
285 | case 'application/ld+json':
286 | // just doing basic JSON formatting
287 | const reqOpts = _requestOptions(cmd, input);
288 | const reqResult = await _jsonldRequest(input, reqOpts, options);
289 | result = reqResult.data;
290 | break;
291 | default:
292 | throw new Error('ERROR: Unknown format: ' + options.format);
293 | }
294 |
295 | await _output(result, cmd);
296 | });
297 |
298 | _jsonLdCommand(program.command('lint [filename|URL|-]'))
299 | .description('lint JSON-LD')
300 | .action(async function lint(input, cmd) {
301 | input = input || '-';
302 | const options = _jsonLdOptions(cmd, input);
303 |
304 | await jsonld.expand(input, {
305 | ...options,
306 | eventHandler: _warningHandler
307 | });
308 | });
309 |
310 | _jsonLdCommand(program.command('compact [filename|URL]'))
311 | .description('compact JSON-LD')
312 | .option('-c, --context ', 'context filename or URL')
313 | .option('-A, --no-compact-arrays',
314 | 'disable compacting arrays to single values')
315 | .option('-g, --graph', 'always output top-level graph [false]')
316 | .action(async function compact(input, cmd) {
317 | input = input || '-';
318 | if(!cmd.context) {
319 | throw new Error('ERROR: Context not specified, use -c/--context');
320 | }
321 | const options = _jsonLdOptions(cmd, input);
322 | options.compactArrays = cmd.compactArrays;
323 | options.graph = !!cmd.graph;
324 |
325 | const result = await jsonld.compact(input, cmd.context, options);
326 |
327 | await _output(result, cmd);
328 | });
329 |
330 | _jsonLdCommand(program.command('expand [filename|URL|-]'))
331 | .description('expand JSON-LD')
332 | .option(' --keep-free-floating-nodes', 'keep free-floating nodes')
333 | .action(async function expand(input, cmd) {
334 | input = input || '-';
335 | const options = _jsonLdOptions(cmd, input);
336 | options.keepFreeFloatingNodes = cmd.keepFreeFloatingNodes;
337 |
338 | const result = await jsonld.expand(input, options);
339 |
340 | await _output(result, cmd);
341 | });
342 |
343 | _jsonLdCommand(program.command('flatten [filename|URL|-]'))
344 | .description('flatten JSON-LD')
345 | .option('-c, --context ',
346 | 'context filename or URL for compaction [none]')
347 | .action(async function flatten(input, cmd) {
348 | input = input || '-';
349 | const options = _jsonLdOptions(cmd, input);
350 |
351 | const result = await jsonld.flatten(input, cmd.context, options);
352 |
353 | await _output(result, cmd);
354 | });
355 |
356 | _jsonLdCommand(program.command('frame [filename|URL|-]'))
357 | .description('frame JSON-LD')
358 | .option('-f, --frame ', 'frame to use')
359 | .option(' --embed