eidosmd 0.1.0 → 0.3.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/README.md +50 -29
- package/browser/dist/assets/index-Cc3cNWHY.css +1 -0
- package/browser/dist/assets/index-DQgCQRa5.js +46 -0
- package/browser/dist/favicon.svg +5 -0
- package/browser/dist/index.html +15 -0
- package/browser/dist/mark.svg +4 -0
- package/dist/src/cli.js +7 -0
- package/dist/src/commands/agents.js +1 -1
- package/dist/src/commands/canvas.js +77 -0
- package/dist/src/commands/check.js +10 -4
- package/dist/src/commands/configure.js +201 -0
- package/dist/src/commands/framework.js +37 -7
- package/dist/src/commands/index.js +4 -4
- package/dist/src/commands/init.js +5 -0
- package/dist/src/commands/instructions.js +1 -1
- package/dist/src/commands/list.js +8 -8
- package/dist/src/commands/migrate.js +32 -0
- package/dist/src/commands/new.js +3 -3
- package/dist/src/commands/property.js +125 -0
- package/dist/src/commands/seeds.js +5 -5
- package/dist/src/commands/setup.js +131 -0
- package/dist/src/commands/version.js +44 -0
- package/dist/src/commands/whoami.js +4 -4
- package/dist/src/context.js +5 -5
- package/dist/src/core/blueprint.js +16 -11
- package/dist/src/core/canvas-schema.js +148 -0
- package/dist/src/core/canvas.js +732 -0
- package/dist/src/core/check.js +221 -70
- package/dist/src/core/convert.js +7 -6
- package/dist/src/core/edits.js +1381 -0
- package/dist/src/core/framework-markdown.js +119 -34
- package/dist/src/core/framework-model.js +62 -10
- package/dist/src/core/framework-structured.js +222 -47
- package/dist/src/core/framework.js +13 -13
- package/dist/src/core/frontmatter.js +61 -1
- package/dist/src/core/git.js +84 -0
- package/dist/src/core/index-leaf.js +2 -2
- package/dist/src/core/links.js +87 -0
- package/dist/src/core/markdown.js +16 -9
- package/dist/src/core/me.js +16 -8
- package/dist/src/core/migrate.js +319 -0
- package/dist/src/core/naming.js +1 -1
- package/dist/src/core/regions.js +117 -0
- package/dist/src/core/root.js +2 -2
- package/dist/src/core/scaffold.js +33 -27
- package/dist/src/core/seed.js +187 -71
- package/dist/src/core/server.js +1410 -53
- package/dist/src/core/settings.js +232 -0
- package/dist/src/core/store.js +315 -0
- package/dist/src/core/template.js +32 -0
- package/dist/src/core/versions.js +84 -0
- package/dist/src/output.js +4 -1
- package/dist/src/program.js +421 -41
- package/instructions/authoring.md +15 -12
- package/instructions/configuring.md +81 -34
- package/instructions/init-required.md +4 -4
- package/instructions/overview.md +21 -9
- package/instructions/validating.md +9 -6
- package/package.json +21 -12
- package/standard/EIDOS.md +142 -259
- package/standard/seeds/README.md +12 -16
- package/standard/seeds/book/Framework.yaml +61 -0
- package/standard/seeds/book/README.md +10 -5
- package/standard/seeds/book/_gitignore +9 -3
- package/standard/seeds/book/me.md +1 -1
- package/standard/seeds/book/roles/README.md +3 -3
- package/standard/seeds/book/roles/framework-owner.md +2 -2
- package/standard/seeds/book/{shapes → templates}/chapter.full.md +0 -8
- package/standard/seeds/book/{shapes → templates}/chapter.sketch.md +0 -7
- package/standard/seeds/book/{shapes → templates}/frame.market.md +0 -6
- package/standard/seeds/book/templates/frame.premise.md +17 -0
- package/standard/seeds/book/{shapes → templates}/frame.reader.md +0 -6
- package/standard/seeds/book/{shapes → templates}/frame.voice.md +0 -7
- package/standard/seeds/research/Framework.yaml +61 -0
- package/standard/seeds/research/README.md +10 -5
- package/standard/seeds/research/_gitignore +9 -3
- package/standard/seeds/research/me.md +1 -1
- package/standard/seeds/research/roles/README.md +3 -3
- package/standard/seeds/research/roles/framework-owner.md +2 -2
- package/standard/seeds/research/{shapes → templates}/frame.ethics.md +0 -6
- package/standard/seeds/research/{shapes → templates}/frame.method.md +0 -7
- package/standard/seeds/research/{shapes → templates}/frame.prior-work.md +0 -6
- package/standard/seeds/research/{shapes → templates}/frame.question.md +0 -7
- package/standard/seeds/research/{shapes → templates}/investigation.full.md +0 -8
- package/standard/seeds/research/{shapes → templates}/investigation.note.md +0 -7
- package/standard/seeds/software/Framework.yaml +62 -0
- package/standard/seeds/software/README.md +7 -6
- package/standard/seeds/software/_gitignore +9 -3
- package/standard/seeds/software/me.md +1 -1
- package/standard/seeds/software/roles/README.md +3 -3
- package/standard/seeds/software/roles/framework-owner.md +2 -2
- package/standard/seeds/software/roles/project-manager.md +2 -2
- package/standard/seeds/software/roles/stakeholder.md +1 -1
- package/standard/seeds/software/{shapes → templates}/frame.architecture.md +0 -7
- package/standard/seeds/software/{shapes → templates}/frame.audience.md +1 -8
- package/standard/seeds/software/{shapes → templates}/frame.criteria.md +0 -8
- package/standard/seeds/software/{shapes → templates}/frame.market.md +0 -8
- package/standard/seeds/software/{shapes → templates}/spec.full.md +0 -8
- package/standard/seeds/software/{shapes → templates}/spec.micro.md +0 -9
- package/browser/index.html +0 -268
- package/dist/src/commands/convert.js +0 -30
- package/dist/src/core/shape.js +0 -26
- package/standard/seeds/book/Framework.md +0 -87
- package/standard/seeds/book/shapes/frame.premise.md +0 -24
- package/standard/seeds/research/Framework.md +0 -88
- package/standard/seeds/software/Framework.md +0 -88
- /package/standard/seeds/software/{shapes → templates}/.gitkeep +0 -0
package/dist/src/program.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// The eidos command line, assembled: every command, its options, and its help.
|
|
2
2
|
// `cli.ts` is the executable entry; this module is what tests build.
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { Command } from 'commander';
|
|
4
5
|
import { loadRoot } from './context.js';
|
|
5
6
|
import { runAgents } from './commands/agents.js';
|
|
6
7
|
import { DEFAULT_PORT, runBrowser } from './commands/browser.js';
|
|
8
|
+
import { runCanvasList, runCanvasNew, runCanvasSchema, runCanvasShow } from './commands/canvas.js';
|
|
9
|
+
import { runMigrate } from './commands/migrate.js';
|
|
7
10
|
import { runCheck } from './commands/check.js';
|
|
8
|
-
import { runConvert } from './commands/convert.js';
|
|
9
11
|
import { runFramework } from './commands/framework.js';
|
|
10
12
|
import { runIndex } from './commands/index.js';
|
|
11
13
|
import { runInit } from './commands/init.js';
|
|
@@ -13,7 +15,12 @@ import { runInstructions, runStandard } from './commands/instructions.js';
|
|
|
13
15
|
import { runList } from './commands/list.js';
|
|
14
16
|
import { runNew } from './commands/new.js';
|
|
15
17
|
import { runSeeds } from './commands/seeds.js';
|
|
18
|
+
import { offerSetup, runSetup } from './commands/setup.js';
|
|
19
|
+
import { runPlan, showCollection, showFolder, showProperty, showRole } from './commands/configure.js';
|
|
20
|
+
import { runPropertyGet, runPropertySet, runPropertyUnset } from './commands/property.js';
|
|
21
|
+
import { planCollectionAdd, planDocAdd, planDocRemove, planDocRename, planDocSet, planCollectionRemove, planCollectionRename, planFolderAdd, planFolderRemove, planFolderRename, planFolderSet, planGroupAdd, planGroupRemove, planGroupRename, planPropertyAdd, planPropertyRemap, planPropertyRemove, planPropertyRename, planPropertySet, planRoleAdd, planRoleRename, planRoleRemove, planTermAdd, planTermRemove, planTermSet, planVariantAdd, planVariantRemove, planVariantRename, planVariantSetDefault } from './core/edits.js';
|
|
16
22
|
import { runShow } from './commands/show.js';
|
|
23
|
+
import { runVersionList, runVersionRecord } from './commands/version.js';
|
|
17
24
|
import { runRoles, runWhoami } from './commands/whoami.js';
|
|
18
25
|
import { CliError, EXIT_USAGE, printError, today } from './output.js';
|
|
19
26
|
import { packageVersion, standardVersion } from './paths.js';
|
|
@@ -33,6 +40,11 @@ function run(action) {
|
|
|
33
40
|
process.exitCode = EXIT_USAGE;
|
|
34
41
|
}
|
|
35
42
|
}
|
|
43
|
+
// `--options A,B,C` as the list it names, in the order typed.
|
|
44
|
+
const splitValues = (given) => given
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((entry) => entry.trim())
|
|
47
|
+
.filter((entry) => entry !== '');
|
|
36
48
|
export function buildProgram() {
|
|
37
49
|
const program = new Command();
|
|
38
50
|
// --root is accepted before or after the command name.
|
|
@@ -42,7 +54,7 @@ export function buildProgram() {
|
|
|
42
54
|
.name('eidos')
|
|
43
55
|
.description(`Eidos: one markdown file is the complete source of truth for one unit of a thing.\nScaffold, validate, and index a root of blueprints; hand an agent the workflow with \`eidos instructions\`.`)
|
|
44
56
|
.version(`eidosmd ${packageVersion()} (standard ${standardVersion()})`, '-v, --version', 'print the CLI version and the standard it carries')
|
|
45
|
-
.option('--root <path>', 'the root to work in (default: found from the working directory by its
|
|
57
|
+
.option('--root <path>', 'the root to work in (default: found from the working directory by its .eidos/ marker)')
|
|
46
58
|
.enablePositionalOptions()
|
|
47
59
|
.showHelpAfterError('(run with --help for usage)')
|
|
48
60
|
.addHelpText('after', `
|
|
@@ -52,63 +64,61 @@ Start with \`eidos instructions\` for the workflow, or \`eidos seeds\` and \`eid
|
|
|
52
64
|
program
|
|
53
65
|
.command('init')
|
|
54
66
|
.argument('[root]', 'the root folder to create', 'Blueprints')
|
|
55
|
-
.description('create a root from a bundled seed: its framework as
|
|
67
|
+
.description('create a root from a bundled seed: its framework as .eidos/Framework.yaml, a folder per collection, a blank blueprint per framing variant (the framing docs are recommended; --no-framing leaves them out)')
|
|
56
68
|
.option('--seed <name>', 'the seed to install (see `eidos seeds`)', 'software')
|
|
57
69
|
.option('--naming <convention>', 'kebab-case | TitleCase | "Title Case"; governs every human-facing name, settled once', 'kebab-case')
|
|
58
70
|
.option('--group <name>', 'a starting group under the grouped collection (repeatable)', collect, [])
|
|
59
71
|
.option('--product <name>', "fills the README's {{Product}} placeholder")
|
|
72
|
+
.option('--no-framing', 'leave out the seed\'s framing collection (the docs about the whole product, recommended for every product)')
|
|
60
73
|
.option('--date <YYYY-MM-DD>', 'the date written into the scaffolded frames', today())
|
|
74
|
+
.option('--strict', 'a warning fails `eidos check` in this root (kept in settings.yaml, with the root)')
|
|
75
|
+
.option('--no-strict', 'only an error fails `eidos check` in this root')
|
|
61
76
|
.option('--dry-run', 'print every write and touch nothing', false)
|
|
62
77
|
.option('--json', 'machine-readable result', false)
|
|
63
78
|
.addHelpText('after', `
|
|
64
79
|
Writes no prose: the README one-liner, each group's description, and every frame's summary and body stay the owner's.
|
|
80
|
+
In a terminal, asks the two settings a framework owner decides once and commits with the root: whether the browser reads git, and whether a warning fails the check (--strict / --no-strict answers it without the question).
|
|
65
81
|
|
|
66
82
|
Examples:
|
|
67
83
|
eidos init
|
|
68
84
|
eidos init Blueprints --seed software --group Identity --group Billing --product "Care Connect"
|
|
69
85
|
eidos init docs/product --seed research --naming TitleCase --dry-run
|
|
70
86
|
|
|
71
|
-
Writes Framework.yaml
|
|
72
|
-
.action((root, options) => {
|
|
87
|
+
Writes the seed's Framework.yaml into the root with its guidance as comments, the naming, the starting groups, and the collection names set in it.`)
|
|
88
|
+
.action(async (root, options) => {
|
|
73
89
|
run(() => runInit(root, options));
|
|
90
|
+
// The owner's two settings: a flag answers, a terminal asks, the rest is `eidos setup`.
|
|
91
|
+
if (process.exitCode === 0 && !options.dryRun)
|
|
92
|
+
await offerSetup(path.resolve(root ?? 'Blueprints'), { strict: options.strict });
|
|
74
93
|
});
|
|
75
94
|
program
|
|
76
95
|
.command('seeds')
|
|
77
|
-
.description('list the bundled seeds: each one a complete starting framework, with its collections and
|
|
96
|
+
.description('list the bundled seeds: each one a complete starting framework, with its collections and variants')
|
|
78
97
|
.option('--json', 'machine-readable result', false)
|
|
79
98
|
.action((options) => {
|
|
80
99
|
run(() => runSeeds(options));
|
|
81
100
|
});
|
|
82
101
|
withRoot(program.command('framework'))
|
|
83
|
-
.description("show the root's framework: version, naming, top-level docs,
|
|
102
|
+
.description("show the root's framework: version, naming, top-level docs, folders (a collection's variants and groups), the Properties table, and the Vocabulary")
|
|
84
103
|
.option('--json', 'the framework as JSON: the document form plus root, file, and format', false)
|
|
85
|
-
.option('--as <format>', 'print the framework as the yaml document
|
|
104
|
+
.option('--as <format>', 'print the framework as the yaml document, normalized, with this CLI\'s guidance as comments')
|
|
86
105
|
.addHelpText('after', `
|
|
87
|
-
Read this before assuming any collection,
|
|
88
|
-
The document is
|
|
106
|
+
Read this before assuming any collection, variant, section, or property name; the framework declares its own.
|
|
107
|
+
The document is .eidos/Framework.yaml. A root still on the 4.x Framework.md is moved by \`eidos migrate\` first.
|
|
89
108
|
|
|
90
|
-
Output (--json): { root, file, format, eidos_version, naming, top_level[],
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.action((options) => {
|
|
94
|
-
run(() => runFramework(loadRoot(globals(options), process.cwd(), { allowMarkdown: true }), options));
|
|
95
|
-
});
|
|
96
|
-
withRoot(program.command('convert'))
|
|
97
|
-
.description('move a markdown root to Framework.yaml, the form the CLI works in: the same framework with the index inside it; Framework.md and each index.md removed')
|
|
98
|
-
.option('--dry-run', 'print the writes and touch nothing', false)
|
|
99
|
-
.option('--json', 'machine-readable result', false)
|
|
100
|
-
.addHelpText('after', `
|
|
101
|
-
Every command except this one and \`framework\` needs Framework.yaml, so on a markdown root this is the first thing to run. The markdown form's prose (section intros, comments) has no field to land in and is left behind; each collection's description travels. Blueprints are untouched.`)
|
|
109
|
+
Output (--json): { root, file, format, eidos_version, naming, top_level[], folders[{ name, type: collection | assets | other, description, variants[{ name, template, description, default }], grouping{ label, property, groups[] } }], properties{ core[], custom[{ name, type, applies_to, meaning, <tool>… }], tools{ <tool>: [...] } }, vocabulary[{ term, means, not[], see? }] }
|
|
110
|
+
A folder's variants and grouping are a collection's; an assets or other folder carries only its name, type, and description.
|
|
111
|
+
A key past the standard's four on a property entry is a tool's, named for the tool (this CLI's is eidosmd, carrying a canvas hint); properties.tools.<tool> is a block of properties a tool declared and alone writes.`)
|
|
102
112
|
.action((options) => {
|
|
103
|
-
run(() =>
|
|
113
|
+
run(() => runFramework(loadRoot(globals(options), process.cwd()), options));
|
|
104
114
|
});
|
|
105
115
|
withRoot(program.command('list'))
|
|
106
116
|
.argument('[collection]', 'only this collection')
|
|
107
117
|
.description('list the blueprints in the root')
|
|
108
118
|
.option('--group <name>', 'only this group (sub-folder)')
|
|
109
|
-
.option('--
|
|
119
|
+
.option('--variant <name>', 'only blueprints on this variant (resolved, so the default counts)')
|
|
110
120
|
.option('--where <key=value>', 'only blueprints whose property equals the value, case-insensitive (repeatable; a list matches any item)', collect, [])
|
|
111
|
-
.option('--json', 'one object per blueprint: id, title, summary, collection, group,
|
|
121
|
+
.option('--json', 'one object per blueprint: id, title, summary, collection, group, variant, path, properties', false)
|
|
112
122
|
.addHelpText('after', `
|
|
113
123
|
Examples:
|
|
114
124
|
eidos list
|
|
@@ -120,39 +130,41 @@ Examples:
|
|
|
120
130
|
withRoot(program.command('show'))
|
|
121
131
|
.argument('<blueprint>', 'an id, a filename, or a path')
|
|
122
132
|
.description('print one blueprint as written; with --json, its properties and sections parsed out')
|
|
123
|
-
.option('--json', '{ id, title, summary, collection, group,
|
|
133
|
+
.option('--json', '{ id, title, summary, collection, group, variant, path, absolute_path, properties, sections[{ heading, body }], body }', false)
|
|
124
134
|
.action((ref, options) => {
|
|
125
135
|
run(() => runShow(loadRoot(globals(options)), ref, options));
|
|
126
136
|
});
|
|
127
137
|
withRoot(program.command('new'))
|
|
128
138
|
.argument('<collection>', 'the collection the blueprint belongs to')
|
|
129
139
|
.argument('<title...>', 'the human-readable title')
|
|
130
|
-
.description("scaffold a blueprint that is born conforming: frontmatter from the
|
|
131
|
-
.option('--
|
|
140
|
+
.description("scaffold a blueprint that is born conforming: frontmatter from the Properties table, body from the variant's template, filename in the naming convention, a permanent kebab-case id")
|
|
141
|
+
.option('--variant <name>', "one of the collection's declared variants (default: the collection's default)")
|
|
132
142
|
.option('--group <name>', 'the sub-folder to place it in')
|
|
133
143
|
.option('--id <kebab-case>', 'the permanent id (default: the title in kebab-case)')
|
|
134
144
|
.option('--summary <line>', 'the one-line summary, so the index lists it at once')
|
|
135
145
|
.option('--set <key=value>', 'a property value at creation (repeatable; lists are comma-separated)', collect, [])
|
|
136
|
-
.option('--date <YYYY-MM-DD>', 'the date written into date properties the
|
|
146
|
+
.option('--date <YYYY-MM-DD>', 'the date written into date properties the Properties table declares', today())
|
|
137
147
|
.option('--dry-run', 'print the file instead of writing it', false)
|
|
138
|
-
.option('--json', '{ path, id, collection, group,
|
|
148
|
+
.option('--json', '{ path, id, collection, group, variant, notes[] }', false)
|
|
139
149
|
.addHelpText('after', `
|
|
140
|
-
The body keeps the
|
|
150
|
+
The body keeps the template's guidance prompts; fill them with the owner and delete them as you go. The CLI writes no prose.
|
|
141
151
|
|
|
142
152
|
Examples:
|
|
143
153
|
eidos new Specs "Session Management" --group Identity --summary "Keeps a signed-in user signed in across tabs and devices."
|
|
144
|
-
eidos new Specs "Passkeys" --
|
|
154
|
+
eidos new Specs "Passkeys" --variant micro --set status=Intake --set tags=auth,security
|
|
145
155
|
eidos new Frames Market --dry-run`)
|
|
146
156
|
.action((collection, title, options) => {
|
|
147
157
|
run(() => runNew(loadRoot(globals(options)), collection, title, options));
|
|
148
158
|
});
|
|
149
159
|
withRoot(program.command('check'))
|
|
150
160
|
.argument('[blueprint...]', 'only these blueprints (paths); default: the whole root')
|
|
151
|
-
.description("validate the root against its own framework: the
|
|
152
|
-
.option('--strict', 'warnings fail too
|
|
153
|
-
.option('--
|
|
161
|
+
.description("validate the root against its own framework: the framework document and templates, every blueprint's frontmatter and body, the layout, and the indexes")
|
|
162
|
+
.option('--strict', 'warnings fail too, for this run (the root\'s own setting is settings.yaml check.strict)')
|
|
163
|
+
.option('--no-strict', 'warnings do not fail, for this run')
|
|
164
|
+
.option('--json', '{ ok, strict, root, eidos_version, naming, blueprints, errors, warnings, findings[{ level, code, path, message }] }', false)
|
|
154
165
|
.addHelpText('after', `
|
|
155
|
-
Errors are wrong on any reading (unparseable frontmatter, a missing or duplicate id, an undeclared
|
|
166
|
+
Errors are wrong on any reading (unparseable frontmatter, a missing or duplicate id, an undeclared variant, a broken link). Warnings are gaps the standard says to surface, never refuse (a missing property or section, a stale index, a version gap). Exit 1 on an error; on a warning too when the root is strict.
|
|
167
|
+
Strictness is the framework owner's decision for the root, asked by \`eidos init\` and set by \`eidos setup --strict on|off\`, kept in .eidos/plugins/eidosmd/settings.yaml so CI and every machine agree; --strict and --no-strict override one run.
|
|
156
168
|
|
|
157
169
|
Examples:
|
|
158
170
|
eidos check
|
|
@@ -162,28 +174,28 @@ Examples:
|
|
|
162
174
|
run(() => runCheck(loadRoot(globals(options)), blueprints, options));
|
|
163
175
|
});
|
|
164
176
|
withRoot(program.command('index'))
|
|
165
|
-
.description("regenerate each collection's index
|
|
177
|
+
.description("regenerate each collection's index: every blueprint as a link with its summary, grouped by sub-folder")
|
|
166
178
|
.option('--collection <name>', 'only this collection (repeatable)', collect, [])
|
|
167
179
|
.option('--check', 'write nothing; exit 1 if any index is stale', false)
|
|
168
180
|
.option('--json', '{ ok, indexes[{ collection, path, status, blueprints }], missing_summary[] }', false)
|
|
169
181
|
.addHelpText('after', `
|
|
170
|
-
|
|
182
|
+
Every collection's index is written into the framework document under \`index\`, and the rest of the document is left as written. A blueprint without a summary is listed with a null summary and named on stderr.`)
|
|
171
183
|
.action((options) => {
|
|
172
184
|
run(() => runIndex(loadRoot(globals(options)), options));
|
|
173
185
|
});
|
|
174
186
|
withRoot(program.command('roles'))
|
|
175
|
-
.description('list the roles the framework installed under
|
|
187
|
+
.description('list the roles the framework installed under .eidos/roles/')
|
|
176
188
|
.option('--json', 'machine-readable result', false)
|
|
177
189
|
.action((options) => {
|
|
178
190
|
run(() => runRoles(loadRoot(globals(options)), options));
|
|
179
191
|
});
|
|
180
192
|
withRoot(program.command('whoami'))
|
|
181
|
-
.description('show
|
|
193
|
+
.description('show who is in the seat (.eidos/me.md: role and calibration), or set it')
|
|
182
194
|
.option('--role <name>', 'a role from `eidos roles`')
|
|
183
195
|
.option('--ownership <text>', 'what you own on this root, in your own words')
|
|
184
196
|
.option('--experience <text>', 'your experience with the scope: new, familiar, deep')
|
|
185
197
|
.option('--capacity <text>', 'your technical capacity: non-technical, some, fluent')
|
|
186
|
-
.option('--clear', 'blank
|
|
198
|
+
.option('--clear', 'blank me.md (full facilitation)', false)
|
|
187
199
|
.option('--json', 'machine-readable result', false)
|
|
188
200
|
.addHelpText('after', `
|
|
189
201
|
me.md is personal and gitignored: one per person, never shared. An agent reads it before acting and responds as the role file says.
|
|
@@ -194,6 +206,374 @@ Examples:
|
|
|
194
206
|
.action((options) => {
|
|
195
207
|
run(() => runWhoami(loadRoot(globals(options)), options));
|
|
196
208
|
});
|
|
209
|
+
const canvas = program.command('canvas').description('the canvas maps under .eidos/plugins/eidosmd/maps/: the design tool the browser draws');
|
|
210
|
+
withRoot(canvas.command('list'))
|
|
211
|
+
.description('list the canvases and their pages')
|
|
212
|
+
.option('--json', 'machine-readable result', false)
|
|
213
|
+
.action((options) => {
|
|
214
|
+
run(() => runCanvasList(loadRoot(globals(options)), options));
|
|
215
|
+
});
|
|
216
|
+
withRoot(canvas.command('new'))
|
|
217
|
+
.argument('<title...>', 'the canvas title; its id is the title in kebab-case')
|
|
218
|
+
.description('create a canvas with one empty page; the browser is where it is drawn')
|
|
219
|
+
.option('--json', 'machine-readable result', false)
|
|
220
|
+
.action((title, options) => {
|
|
221
|
+
run(() => runCanvasNew(loadRoot(globals(options)), title.join(' '), options));
|
|
222
|
+
});
|
|
223
|
+
withRoot(canvas.command('show'))
|
|
224
|
+
.argument('<id>', 'the canvas id, its file name without .yaml')
|
|
225
|
+
.description('print a canvas file as it is, or one page of it, before editing it by hand')
|
|
226
|
+
.option('--page <id>', 'one page only')
|
|
227
|
+
.option('--json', 'machine-readable result', false)
|
|
228
|
+
.action((id, options) => {
|
|
229
|
+
run(() => runCanvasShow(loadRoot(globals(options)), id, options));
|
|
230
|
+
});
|
|
231
|
+
canvas
|
|
232
|
+
.command('schema')
|
|
233
|
+
.description('print the JSON Schema of a canvas file, for editing one without the browser')
|
|
234
|
+
.action(() => {
|
|
235
|
+
run(() => runCanvasSchema());
|
|
236
|
+
});
|
|
237
|
+
canvas.addHelpText('after', `
|
|
238
|
+
A canvas is one YAML file in this tool's folder, .eidos/plugins/eidosmd/maps/<id>.yaml: a title and pages (each with frames, nodes, edges, layout, and comment threads).
|
|
239
|
+
The file is the whole truth of a canvas: the browser draws it and writes it back, and a person or an agent edits it directly; \`eidos canvas schema\` prints what it may hold and \`eidos canvas show <id>\` prints one.
|
|
240
|
+
A node is { id, blueprint: <id> }, { id, sketch: { label, note? } }, a sticky, a text, or an annotation, optionally attached to a frame; edges join node ids (or frame:<id> for a frame's border).
|
|
241
|
+
Nodes are styled by the properties whose schema row carries this tool's hint, eidosmd.canvas (shape by value, color by value, show); a node's own style in the file wins over that.
|
|
242
|
+
The search index the browser keeps is SQLite in memory, rebuilt on every start and never written to disk; nothing but the YAML is checked in.
|
|
243
|
+
A snapshot of the root as a whole is a version: \`eidos version record\`.`);
|
|
244
|
+
const version = program.command('version').description("the root's own versions: snapshots taken on purpose, this CLI's own record under .eidos/plugins/eidosmd/ (they need git)");
|
|
245
|
+
withRoot(version.command('list', { isDefault: true }))
|
|
246
|
+
.description('list the recorded versions, newest first')
|
|
247
|
+
.option('--json', 'machine-readable result', false)
|
|
248
|
+
.action((options) => {
|
|
249
|
+
run(() => runVersionList(loadRoot(globals(options)), options));
|
|
250
|
+
});
|
|
251
|
+
withRoot(version.command('record'))
|
|
252
|
+
.argument('<version>', "the root's own number, e.g. 1.0.0 (not the product's release version)")
|
|
253
|
+
.description('record a snapshot: a row naming a commit that exists; --tag also tags it <prefix><version>')
|
|
254
|
+
.option('--commit <ref>', 'the commit that is the snapshot (a sha, HEAD, a tag); HEAD when absent')
|
|
255
|
+
.option('--tag', 'create the tag <prefix><version> on that commit (the prefix is settings.yaml versions.tag_prefix, blueprints/ by default)', false)
|
|
256
|
+
.option('--json', 'machine-readable result', false)
|
|
257
|
+
.action((given, options) => {
|
|
258
|
+
run(() => runVersionRecord(loadRoot(globals(options)), given, options));
|
|
259
|
+
});
|
|
260
|
+
version.addHelpText('after', `
|
|
261
|
+
A version is a fixed point a team holds the definition against later. Nothing proposes one; record one only when asked.
|
|
262
|
+
The commit is the snapshot (git holds every blueprint as it was), nothing is copied, and the commit that adds the row is not the one it names.`);
|
|
263
|
+
program
|
|
264
|
+
.command('migrate')
|
|
265
|
+
.description('move a root to the standard this CLI carries: .eidos/, templates/, Framework.yaml, Properties, variants, the version; mechanical, nothing dropped')
|
|
266
|
+
.option('--root <path>', 'the root to migrate; default: the one found from here (.eidos/ or the older _eidos/)')
|
|
267
|
+
.option('--dry-run', 'list what would move and change nothing', false)
|
|
268
|
+
.option('--json', 'machine-readable result', false)
|
|
269
|
+
.addHelpText('after', `
|
|
270
|
+
The 4.x → 5.0.0 hop: _eidos/ becomes .eidos/, shapes/ becomes templates/, the framework document takes the 5.0.0 keys (schema → properties, flavors → variants, shape → template), a root that kept Framework.md gets Framework.yaml in its place (the index inside it; Framework.md and each collection's index.md removed), every blueprint's flavor becomes variant, and eidos_version is set.
|
|
271
|
+
From 5.0.0 on nothing moves on disk: the version is bumped, the core block is rewritten, and .eidos/.gitignore gains the one line every tool's personal file needs (plugins/*/local.yaml).
|
|
272
|
+
A template that opens with frontmatter loses the block; one named off its unit (<unit>.<variant>.md) is reported for you to rename. Run eidos check afterwards.`)
|
|
273
|
+
.action((options) => {
|
|
274
|
+
run(() => runMigrate(options));
|
|
275
|
+
});
|
|
276
|
+
withRoot(program.command('setup'))
|
|
277
|
+
.description("the CLI's own settings for this root: whether git is read, the people who work here, and who this machine acts as")
|
|
278
|
+
.option('--git <on|off>', 'read git in the browser: history, authors, versions')
|
|
279
|
+
.option('--strict <on|off>', 'a warning fails `eidos check` in this root, the way an error does; the framework owner\'s decision, kept with the root')
|
|
280
|
+
.option('--add-user <name>', 'add (or update) a user by display name (repeatable)', collect, [])
|
|
281
|
+
.option('--alias <alias>', 'the @mention handle of the user being added, no spaces')
|
|
282
|
+
.option('--role <name>', 'the role of the user being added, from `eidos roles`')
|
|
283
|
+
.option('--email <email>', 'the email of the user being added; ties them to a git identity')
|
|
284
|
+
.option('--as <alias>', 'act as this user on this machine (blank for the git identity)')
|
|
285
|
+
.option('--json', 'machine-readable result', false)
|
|
286
|
+
.addHelpText('after', `
|
|
287
|
+
Without flags, asks in the terminal. Writes .eidos/plugins/eidosmd/settings.yaml (shared with the root) and local.yaml (yours, gitignored).
|
|
288
|
+
|
|
289
|
+
Examples:
|
|
290
|
+
eidos setup
|
|
291
|
+
eidos setup --git on --add-user "Ada Lovelace" --alias ada --role developer --email ada@example.com --as ada
|
|
292
|
+
eidos setup --as client`)
|
|
293
|
+
.action(async (options) => {
|
|
294
|
+
try {
|
|
295
|
+
process.exitCode = await runSetup(loadRoot(globals(options)), options);
|
|
296
|
+
}
|
|
297
|
+
catch (cause) {
|
|
298
|
+
if (cause instanceof CliError) {
|
|
299
|
+
printError(`error: ${cause.message}`);
|
|
300
|
+
process.exitCode = cause.exitCode;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
printError(`error: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
304
|
+
process.exitCode = EXIT_USAGE;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
// ---- configure:<noun>: the framework edited from the shell, each a plan shown before it runs
|
|
309
|
+
const runAsync = async (action) => {
|
|
310
|
+
try {
|
|
311
|
+
process.exitCode = await action();
|
|
312
|
+
}
|
|
313
|
+
catch (cause) {
|
|
314
|
+
if (cause instanceof CliError) {
|
|
315
|
+
printError(`error: ${cause.message}`);
|
|
316
|
+
process.exitCode = cause.exitCode;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
printError(`error: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
320
|
+
process.exitCode = EXIT_USAGE;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const planned = (command) => withRoot(command)
|
|
324
|
+
.option('--dry-run', 'print the plan and write nothing', false)
|
|
325
|
+
.option('-y, --yes', 'proceed without asking', false)
|
|
326
|
+
.option('--force', 'proceed through what needs forcing: values or files that will be gone, a conflict the plan names', false)
|
|
327
|
+
.option('--json', 'the plan, its conflicts, and once applied every result, as one object', false);
|
|
328
|
+
const PLAN_HELP = `
|
|
329
|
+
The plan is printed whole, then the command asks in a terminal; --dry-run prints it and stops, --yes proceeds, --force proceeds where values or files would be gone. Without a terminal and without a switch the command exits 2 and names the switch.
|
|
330
|
+
After any change the index is rewritten and the check runs; a command never leaves the root with a finding it caused, unless --preserve, the owner's choice, says so.`;
|
|
331
|
+
const configure = (noun, description) => program.command(`configure:${noun}`).description(description).addHelpText('after', PLAN_HELP);
|
|
332
|
+
const configureProperty = configure('property', "the Properties table's custom block: add, show, rename, set, remap, or remove a property, every blueprint it touches following");
|
|
333
|
+
planned(configureProperty.command('add'))
|
|
334
|
+
.argument('<name>', 'the frontmatter key: letters, digits, _ and -')
|
|
335
|
+
.requiredOption('--type <type>', 'Text | List | Number | Checkbox | Date | "Date & time"')
|
|
336
|
+
.option('--applies-to <all|collection,...>', 'all, or a comma-separated list of collections', 'all')
|
|
337
|
+
.option('--required', 'generated into every blueprint it applies to, blank, and noted when missing', false)
|
|
338
|
+
.option('--options <value,...>', 'the closed set a Text value is one of, or a List\'s elements are, comma-separated in the order they run; absent, any value is valid')
|
|
339
|
+
.option('--meaning <text>', 'one line: what it holds and why', '')
|
|
340
|
+
.description('declare a custom property; required, it is backfilled blank into the blueprints it applies to')
|
|
341
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planPropertyAdd(edit, { name, type: options.type, appliesTo: options.appliesTo === 'all' ? 'all' : options.appliesTo.split(',').map((entry) => entry.trim()).filter((entry) => entry !== ''), required: options.required, meaning: options.meaning, options: options.options === undefined ? null : splitValues(options.options) }), options)));
|
|
342
|
+
withRoot(configureProperty.command('show'))
|
|
343
|
+
.argument('[name]', 'one property; none lists every block')
|
|
344
|
+
.option('--json', 'machine-readable result', false)
|
|
345
|
+
.description('print a property as the table declares it')
|
|
346
|
+
.action((name, options) => run(() => showProperty(loadRoot(globals(options)), name, options.json)));
|
|
347
|
+
planned(configureProperty.command('rename'))
|
|
348
|
+
.argument('<name>', 'the property')
|
|
349
|
+
.argument('<new>', 'its new key')
|
|
350
|
+
.description('rename a custom property: the entry, the key in every blueprint, a grouping that names it, and this CLI\'s settings')
|
|
351
|
+
.action((name, to, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planPropertyRename(edit, name, to), options)));
|
|
352
|
+
planned(configureProperty.command('set'))
|
|
353
|
+
.argument('<name>', 'the property')
|
|
354
|
+
.option('--type <type>', 'a new type')
|
|
355
|
+
.option('--applies-to <all|collection,...>', 'a new scope; a blueprint now out of scope loses the key unless --preserve')
|
|
356
|
+
.option('--required', 'make it required: backfilled blank where missing')
|
|
357
|
+
.option('--optional', 'make it optional')
|
|
358
|
+
.option('--options <value,...>', 'the closed set of values, comma-separated in order; narrowing a list surfaces every blueprint value off it and needs --force')
|
|
359
|
+
.option('--open', 'drop the options: any value is valid again')
|
|
360
|
+
.option('--meaning <text>', 'a new meaning')
|
|
361
|
+
.option('--preserve', 'leave keys in blueprints now out of scope, for check to report', false)
|
|
362
|
+
.description("change a custom property's type, scope, required, options, or meaning")
|
|
363
|
+
.action((name, options) => {
|
|
364
|
+
if (options.required && options.optional)
|
|
365
|
+
return runAsync(async () => Promise.reject(new CliError('--required and --optional exclude each other')));
|
|
366
|
+
if (options.options !== undefined && options.open)
|
|
367
|
+
return runAsync(async () => Promise.reject(new CliError('--options and --open exclude each other')));
|
|
368
|
+
return runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planPropertySet(edit, name, {
|
|
369
|
+
...(options.type !== undefined ? { type: options.type } : {}),
|
|
370
|
+
...(options.appliesTo !== undefined ? { appliesTo: options.appliesTo === 'all' ? 'all' : options.appliesTo.split(',').map((entry) => entry.trim()).filter((entry) => entry !== '') } : {}),
|
|
371
|
+
...(options.required ? { required: true } : options.optional ? { required: false } : {}),
|
|
372
|
+
...(options.options !== undefined ? { options: splitValues(options.options) } : options.open ? { options: null } : {}),
|
|
373
|
+
...(options.meaning !== undefined ? { meaning: options.meaning } : {}),
|
|
374
|
+
}, { preserve: options.preserve }), options));
|
|
375
|
+
});
|
|
376
|
+
planned(configureProperty.command('remap'))
|
|
377
|
+
.argument('<name>', 'the property')
|
|
378
|
+
.argument('<old=new...>', 'each value to rewrite, and what to')
|
|
379
|
+
.description('rewrite a value in every blueprint where it equals old, and the canvas style keyed by it')
|
|
380
|
+
.action((name, pairs, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planPropertyRemap(edit, name, pairs.map((pair) => {
|
|
381
|
+
const eq = pair.indexOf('=');
|
|
382
|
+
if (eq <= 0)
|
|
383
|
+
throw new CliError(`expected old=new, got '${pair}'`);
|
|
384
|
+
return [pair.slice(0, eq), pair.slice(eq + 1)];
|
|
385
|
+
})), options)));
|
|
386
|
+
planned(configureProperty.command('remove'))
|
|
387
|
+
.argument('<name>', 'the property')
|
|
388
|
+
.option('--preserve', 'leave the key in every blueprint, for check to report as undeclared', false)
|
|
389
|
+
.description('retire a custom property: the entry and, unless --preserve, the key in every blueprint, its values shown first')
|
|
390
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planPropertyRemove(edit, name, { preserve: options.preserve }), options)));
|
|
391
|
+
const configureCollection = configure('collection', 'the collections: add, show, rename, or remove one, its folder, templates, properties, and links following');
|
|
392
|
+
planned(configureCollection.command('add'))
|
|
393
|
+
.argument('<name>', 'the collection, which is its folder')
|
|
394
|
+
.requiredOption('--unit <unit>', 'the word for one of its blueprints (spec, chapter, decision); its templates are named for it')
|
|
395
|
+
.option('--description <text>', 'one line', '')
|
|
396
|
+
.option('--grouping <label>', 'group its blueprints one level deep under this label (Domains, Products)')
|
|
397
|
+
.description("declare a collection with one default variant, its folder, and that variant's template (a title and an Intent section)")
|
|
398
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planCollectionAdd(edit, { name, unit: options.unit, description: options.description, grouping: options.grouping ?? null }), options)));
|
|
399
|
+
withRoot(configureCollection.command('show'))
|
|
400
|
+
.argument('[name]', 'one collection; none lists them all')
|
|
401
|
+
.option('--json', 'machine-readable result', false)
|
|
402
|
+
.description('print a collection as the framework declares it')
|
|
403
|
+
.action((name, options) => run(() => showCollection(loadRoot(globals(options)), name, options.json)));
|
|
404
|
+
planned(configureCollection.command('rename'))
|
|
405
|
+
.argument('<name>', 'the collection')
|
|
406
|
+
.argument('[new]', 'its new name; omit to rename only the unit')
|
|
407
|
+
.option('--unit <unit>', 'a new unit: the template files are renamed for it')
|
|
408
|
+
.description('rename a collection: the folder, the entry, every applies_to that names it, and every link in the root that crosses the folder')
|
|
409
|
+
.action((name, to, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planCollectionRename(edit, name, to ?? name, options.unit ?? null), options)));
|
|
410
|
+
planned(configureCollection.command('remove'))
|
|
411
|
+
.argument('<name>', 'the collection')
|
|
412
|
+
.option('--preserve', 'leave the folder and its blueprints on disk, for check to report as undeclared', false)
|
|
413
|
+
.description('remove a collection: the entry, its templates, the properties scoped only to it, and, unless --preserve, its folder and blueprints')
|
|
414
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planCollectionRemove(edit, name, { preserve: options.preserve }), options)));
|
|
415
|
+
const folderType = (given) => {
|
|
416
|
+
if (given === 'assets' || given === 'other')
|
|
417
|
+
return given;
|
|
418
|
+
throw new CliError('--type takes assets or other; a collection is declared with configure:collection add');
|
|
419
|
+
};
|
|
420
|
+
const configureFolder = configure('folder', 'the folders at the root that are not collections (assets, other): add, show, rename, set, or remove one, its folder and the links into it following');
|
|
421
|
+
planned(configureFolder.command('add'))
|
|
422
|
+
.argument('<name>', 'the folder at the root, in the naming convention')
|
|
423
|
+
.requiredOption('--type <type>', 'assets (files that are not markdown) or other (whatever the description says)')
|
|
424
|
+
.option('--description <text>', 'one line: what the folder holds', '')
|
|
425
|
+
.description('declare a folder and create it')
|
|
426
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planFolderAdd(edit, { name, type: folderType(options.type), description: options.description }), options)));
|
|
427
|
+
withRoot(configureFolder.command('show'))
|
|
428
|
+
.argument('[name]', 'one folder; none lists every folder at the root with its type')
|
|
429
|
+
.option('--json', 'machine-readable result', false)
|
|
430
|
+
.description('print a folder as the framework declares it')
|
|
431
|
+
.action((name, options) => run(() => showFolder(loadRoot(globals(options)), name, options.json)));
|
|
432
|
+
planned(configureFolder.command('rename'))
|
|
433
|
+
.argument('<name>', 'the folder')
|
|
434
|
+
.argument('<new>', 'its new name')
|
|
435
|
+
.description('rename a folder: the entry, the folder on disk, and every link in the root into it')
|
|
436
|
+
.action((name, to, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planFolderRename(edit, name, to), options)));
|
|
437
|
+
planned(configureFolder.command('set'))
|
|
438
|
+
.argument('<name>', 'the folder')
|
|
439
|
+
.option('--type <type>', 'assets or other')
|
|
440
|
+
.option('--description <text>', 'a new description')
|
|
441
|
+
.description("change a folder's type or description")
|
|
442
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planFolderSet(edit, name, { ...(options.type !== undefined ? { type: folderType(options.type) } : {}), ...(options.description !== undefined ? { description: options.description } : {}) }), options)));
|
|
443
|
+
planned(configureFolder.command('remove'))
|
|
444
|
+
.argument('<name>', 'the folder')
|
|
445
|
+
.option('--preserve', 'leave the folder and its files on disk, for check to report as undeclared', false)
|
|
446
|
+
.description('remove a folder: the entry and, unless --preserve, the folder and every file in it, each listed first')
|
|
447
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planFolderRemove(edit, name, { preserve: options.preserve }), options)));
|
|
448
|
+
const configureGroup = configure('group', "a collection's groups: add, rename, or remove one, its sub-folder, the grouping values, and the links following");
|
|
449
|
+
planned(configureGroup.command('add'))
|
|
450
|
+
.argument('<collection>', 'the collection, which must declare a grouping')
|
|
451
|
+
.argument('<name>', 'the group, which is its sub-folder')
|
|
452
|
+
.option('--description <text>', 'one line', '')
|
|
453
|
+
.description('declare a group and create its sub-folder')
|
|
454
|
+
.action((collection, name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planGroupAdd(edit, collection, name, options.description), options)));
|
|
455
|
+
planned(configureGroup.command('rename'))
|
|
456
|
+
.argument('<collection>', 'the collection')
|
|
457
|
+
.argument('<name>', 'the group')
|
|
458
|
+
.argument('<new>', 'its new name')
|
|
459
|
+
.description('rename a group: the sub-folder, the entry, the grouping value in every blueprint inside, and every link in the root into or out of it')
|
|
460
|
+
.action((collection, name, to, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planGroupRename(edit, collection, name, to), options)));
|
|
461
|
+
planned(configureGroup.command('remove'))
|
|
462
|
+
.argument('<collection>', 'the collection')
|
|
463
|
+
.argument('<name>', 'the group')
|
|
464
|
+
.option('--preserve', 'leave the sub-folder and its blueprints on disk, for check to report as undeclared', false)
|
|
465
|
+
.description('remove a group: the entry and, unless --preserve, its sub-folder and blueprints')
|
|
466
|
+
.action((collection, name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planGroupRemove(edit, collection, name, { preserve: options.preserve }), options)));
|
|
467
|
+
const configureVariant = configure('variant', "a collection's variants: add, rename, set the default, or remove one, its template and the blueprints on it following");
|
|
468
|
+
planned(configureVariant.command('add'))
|
|
469
|
+
.argument('<collection>', 'the collection')
|
|
470
|
+
.argument('<name>', 'the variant (full, micro, api)')
|
|
471
|
+
.option('--description <text>', 'one line', '')
|
|
472
|
+
.option('--from <variant>', "copy this variant's template; default: the collection's default variant")
|
|
473
|
+
.description('declare a variant and write its template')
|
|
474
|
+
.action((collection, name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planVariantAdd(edit, collection, name, options.description, options.from ?? null), options)));
|
|
475
|
+
planned(configureVariant.command('rename'))
|
|
476
|
+
.argument('<collection>', 'the collection')
|
|
477
|
+
.argument('<name>', 'the variant')
|
|
478
|
+
.argument('<new>', 'its new name')
|
|
479
|
+
.description('rename a variant: the entry, its template file, and the variant value in every blueprint on it')
|
|
480
|
+
.action((collection, name, to, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planVariantRename(edit, collection, name, to), options)));
|
|
481
|
+
planned(configureVariant.command('set-default'))
|
|
482
|
+
.argument('<collection>', 'the collection')
|
|
483
|
+
.argument('<name>', 'the variant that becomes the default')
|
|
484
|
+
.description("make a variant the collection's default")
|
|
485
|
+
.action((collection, name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planVariantSetDefault(edit, collection, name), options)));
|
|
486
|
+
planned(configureVariant.command('remove'))
|
|
487
|
+
.argument('<collection>', 'the collection')
|
|
488
|
+
.argument('<name>', 'the variant')
|
|
489
|
+
.description('remove a variant and its template; refused while a blueprint names it unless --force, which clears their variant')
|
|
490
|
+
.action((collection, name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planVariantRemove(edit, collection, name, { force: options.force }), options)));
|
|
491
|
+
const configureTerm = configure('term', "the Vocabulary: add, set, or remove a term");
|
|
492
|
+
planned(configureTerm.command('add'))
|
|
493
|
+
.argument('<term>', 'the word, as prose uses it')
|
|
494
|
+
.requiredOption('--means <text>', 'one line: what it means')
|
|
495
|
+
.option('--not <clause>', 'a near-miss, opening with the word and saying why it differs (repeatable)', collect, [])
|
|
496
|
+
.option('--see <path>', 'the blueprint that defines it in full, relative to .eidos/')
|
|
497
|
+
.description('declare a term')
|
|
498
|
+
.action((term, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planTermAdd(edit, { term, means: options.means, not: options.not, see: options.see ?? null }), options)));
|
|
499
|
+
planned(configureTerm.command('set'))
|
|
500
|
+
.argument('<term>', 'the term')
|
|
501
|
+
.option('--term <word>', 'a new spelling')
|
|
502
|
+
.option('--means <text>', 'a new meaning')
|
|
503
|
+
.option('--not <clause>', 'the near-misses, replacing the list (repeatable)', collect, [])
|
|
504
|
+
.option('--see <path>', "where it is defined in full; '' clears it")
|
|
505
|
+
.description("change a term's word, meaning, near-misses, or see")
|
|
506
|
+
.action((term, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planTermSet(edit, term, { ...(options.term !== undefined ? { term: options.term } : {}), ...(options.means !== undefined ? { means: options.means } : {}), ...(options.not.length > 0 ? { not: options.not } : {}), ...(options.see !== undefined ? { see: options.see } : {}) }), options)));
|
|
507
|
+
planned(configureTerm.command('remove'))
|
|
508
|
+
.argument('<term>', 'the term')
|
|
509
|
+
.description('remove a term')
|
|
510
|
+
.action((term, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planTermRemove(edit, term), options)));
|
|
511
|
+
const configureDoc = configure('doc', "the top-level docs under top_level: add, rename, set, or remove one, its file at the root following its title");
|
|
512
|
+
planned(configureDoc.command('add'))
|
|
513
|
+
.argument('<title>', 'the title; the file is named for it in the naming convention')
|
|
514
|
+
.option('--description <text>', 'one line: what the document is', '')
|
|
515
|
+
.description('declare a top-level doc and write its file when there is none')
|
|
516
|
+
.action((title, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planDocAdd(edit, title, options.description), options)));
|
|
517
|
+
planned(configureDoc.command('rename'))
|
|
518
|
+
.argument('<doc>', 'the title or the file')
|
|
519
|
+
.argument('<title>', 'its new title; the file is renamed to match and every link follows (the same title brings a drifted file in line)')
|
|
520
|
+
.description('retitle a top-level doc and rename its file')
|
|
521
|
+
.action((ref, title, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planDocRename(edit, ref, title), options)));
|
|
522
|
+
planned(configureDoc.command('set'))
|
|
523
|
+
.argument('<doc>', 'the title or the file')
|
|
524
|
+
.option('--description <text>', 'a new description')
|
|
525
|
+
.description("change a top-level doc's description")
|
|
526
|
+
.action((ref, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planDocSet(edit, ref, { ...(options.description !== undefined ? { description: options.description } : {}) }), options)));
|
|
527
|
+
planned(configureDoc.command('remove'))
|
|
528
|
+
.argument('<doc>', 'the title or the file')
|
|
529
|
+
.option('--preserve', 'leave the file at the root, undeclared', false)
|
|
530
|
+
.description('remove a top-level doc: the entry, and the file unless --preserve')
|
|
531
|
+
.action((ref, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planDocRemove(edit, ref, { preserve: options.preserve }), options)));
|
|
532
|
+
const configureRole = configure('role', 'the roles under .eidos/roles/: add, show, rename, or remove one');
|
|
533
|
+
planned(configureRole.command('add'))
|
|
534
|
+
.argument('<name>', 'the role, lowercase words joined by hyphens')
|
|
535
|
+
.option('--from <role>', 'copy this role\'s file; default: a bare skeleton')
|
|
536
|
+
.description('write a role file')
|
|
537
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planRoleAdd(edit, name, options.from ?? null), options)));
|
|
538
|
+
withRoot(configureRole.command('show'))
|
|
539
|
+
.argument('[name]', 'one role, printed whole; none lists them')
|
|
540
|
+
.option('--json', 'machine-readable result', false)
|
|
541
|
+
.description('print a role')
|
|
542
|
+
.action((name, options) => run(() => showRole(loadRoot(globals(options)), name, options.json)));
|
|
543
|
+
planned(configureRole.command('rename'))
|
|
544
|
+
.argument('<name>', 'the role')
|
|
545
|
+
.argument('<new>', 'its new name, lowercase words joined by hyphens; the file, me.md, and the users who hold it follow')
|
|
546
|
+
.description('rename a role: the file moves, and every reference to it follows')
|
|
547
|
+
.action((name, to, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planRoleRename(edit, name, to), options)));
|
|
548
|
+
planned(configureRole.command('remove'))
|
|
549
|
+
.argument('<name>', 'the role')
|
|
550
|
+
.description('remove a role file; refused while me.md names it unless --force')
|
|
551
|
+
.action((name, options) => runAsync(() => runPlan(loadRoot(globals(options)), (edit) => planRoleRemove(edit, name, { force: options.force }), options)));
|
|
552
|
+
// ---- property: one blueprint's frontmatter
|
|
553
|
+
const property = program.command('property').description("one blueprint's frontmatter: get, set, or unset a property, typed by the Properties table, so nobody opens a file to flip a value");
|
|
554
|
+
withRoot(property.command('get'))
|
|
555
|
+
.argument('<blueprint>', '@<id>, a path, or a filename')
|
|
556
|
+
.argument('[property]', 'one property; none prints them all')
|
|
557
|
+
.option('--json', 'machine-readable result', false)
|
|
558
|
+
.description("print a blueprint's property, or all of them")
|
|
559
|
+
.action((ref, key, options) => run(() => runPropertyGet(loadRoot(globals(options)), ref, key, options)));
|
|
560
|
+
withRoot(property.command('set'))
|
|
561
|
+
.argument('<blueprint>', '@<id>, a path, or a filename')
|
|
562
|
+
.argument('<property>', 'the property, as the Properties table declares it for the collection')
|
|
563
|
+
.argument('<value>', 'the value; a List is comma-separated, a Checkbox true or false, a Date YYYY-MM-DD, one of the options where the property declares them')
|
|
564
|
+
.option('--force', 'write a property the table does not declare for this collection, a tool\'s own, or a value off its options', false)
|
|
565
|
+
.option('--dry-run', 'print the frontmatter as it would be, writing nothing', false)
|
|
566
|
+
.option('--json', 'machine-readable result', false)
|
|
567
|
+
.description('set one property, coerced to its declared type, with the root\'s on-save rules applied')
|
|
568
|
+
.action((ref, key, value, options) => run(() => runPropertySet(loadRoot(globals(options)), ref, key, value, options)));
|
|
569
|
+
withRoot(property.command('unset'))
|
|
570
|
+
.argument('<blueprint>', '@<id>, a path, or a filename')
|
|
571
|
+
.argument('<property>', 'the property to remove from the file')
|
|
572
|
+
.option('--dry-run', 'print the frontmatter as it would be, writing nothing', false)
|
|
573
|
+
.option('--json', 'machine-readable result', false)
|
|
574
|
+
.description('remove one property from a blueprint')
|
|
575
|
+
.action((ref, key, options) => run(() => runPropertyUnset(loadRoot(globals(options)), ref, key, options)));
|
|
576
|
+
property.addHelpText('after', '\nExamples:\n eidos property get @login\n eidos property set @login status Done\n eidos property set specs/identity/login.md tags auth,security\n eidos property unset @login depends_on');
|
|
197
577
|
withRoot(program.command('browser'))
|
|
198
578
|
.description('open the root in a local web page: browse and read blueprints, create one, edit one, run the check, rebuild the index')
|
|
199
579
|
.option('-p, --port <port>', 'the port to serve on; the next free one is used if it is busy', (value) => Number.parseInt(value, 10), DEFAULT_PORT)
|