eidosmd 0.1.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 +133 -0
- package/NOTICE +15 -0
- package/README.md +146 -0
- package/browser/index.html +268 -0
- package/dist/src/cli.js +5 -0
- package/dist/src/commands/agents.js +47 -0
- package/dist/src/commands/browser.js +40 -0
- package/dist/src/commands/check.js +61 -0
- package/dist/src/commands/convert.js +30 -0
- package/dist/src/commands/framework.js +76 -0
- package/dist/src/commands/index.js +64 -0
- package/dist/src/commands/init.js +66 -0
- package/dist/src/commands/instructions.js +40 -0
- package/dist/src/commands/list.js +73 -0
- package/dist/src/commands/new.js +68 -0
- package/dist/src/commands/seeds.js +40 -0
- package/dist/src/commands/show.js +24 -0
- package/dist/src/commands/whoami.js +84 -0
- package/dist/src/context.js +35 -0
- package/dist/src/core/blueprint.js +129 -0
- package/dist/src/core/check.js +407 -0
- package/dist/src/core/convert.js +37 -0
- package/dist/src/core/framework-markdown.js +233 -0
- package/dist/src/core/framework-model.js +48 -0
- package/dist/src/core/framework-structured.js +334 -0
- package/dist/src/core/framework.js +56 -0
- package/dist/src/core/frontmatter.js +116 -0
- package/dist/src/core/index-leaf.js +83 -0
- package/dist/src/core/markdown.js +100 -0
- package/dist/src/core/me.js +89 -0
- package/dist/src/core/naming.js +46 -0
- package/dist/src/core/root.js +88 -0
- package/dist/src/core/scaffold.js +118 -0
- package/dist/src/core/seed.js +223 -0
- package/dist/src/core/server.js +275 -0
- package/dist/src/core/shape.js +26 -0
- package/dist/src/output.js +38 -0
- package/dist/src/paths.js +28 -0
- package/dist/src/program.js +242 -0
- package/instructions/authoring.md +50 -0
- package/instructions/configuring.md +65 -0
- package/instructions/init-required.md +32 -0
- package/instructions/overview.md +37 -0
- package/instructions/validating.md +33 -0
- package/package.json +53 -0
- package/standard/EIDOS.md +324 -0
- package/standard/seeds/README.md +25 -0
- package/standard/seeds/book/Framework.md +87 -0
- package/standard/seeds/book/README.md +22 -0
- package/standard/seeds/book/_gitignore +4 -0
- package/standard/seeds/book/me.md +18 -0
- package/standard/seeds/book/roles/README.md +12 -0
- package/standard/seeds/book/roles/collaborator.md +16 -0
- package/standard/seeds/book/roles/editor.md +16 -0
- package/standard/seeds/book/roles/framework-owner.md +16 -0
- package/standard/seeds/book/roles/reader.md +16 -0
- package/standard/seeds/book/shapes/chapter.full.md +45 -0
- package/standard/seeds/book/shapes/chapter.sketch.md +26 -0
- package/standard/seeds/book/shapes/frame.market.md +23 -0
- package/standard/seeds/book/shapes/frame.premise.md +24 -0
- package/standard/seeds/book/shapes/frame.reader.md +23 -0
- package/standard/seeds/book/shapes/frame.voice.md +24 -0
- package/standard/seeds/research/Framework.md +88 -0
- package/standard/seeds/research/README.md +23 -0
- package/standard/seeds/research/_gitignore +4 -0
- package/standard/seeds/research/me.md +18 -0
- package/standard/seeds/research/roles/README.md +12 -0
- package/standard/seeds/research/roles/framework-owner.md +16 -0
- package/standard/seeds/research/roles/researcher.md +16 -0
- package/standard/seeds/research/roles/reviewer.md +16 -0
- package/standard/seeds/research/roles/sponsor.md +16 -0
- package/standard/seeds/research/shapes/frame.ethics.md +23 -0
- package/standard/seeds/research/shapes/frame.method.md +24 -0
- package/standard/seeds/research/shapes/frame.prior-work.md +23 -0
- package/standard/seeds/research/shapes/frame.question.md +24 -0
- package/standard/seeds/research/shapes/investigation.full.md +49 -0
- package/standard/seeds/research/shapes/investigation.note.md +26 -0
- package/standard/seeds/software/Framework.md +88 -0
- package/standard/seeds/software/README.md +24 -0
- package/standard/seeds/software/_gitignore +4 -0
- package/standard/seeds/software/me.md +18 -0
- package/standard/seeds/software/roles/.gitkeep +0 -0
- package/standard/seeds/software/roles/README.md +13 -0
- package/standard/seeds/software/roles/designer.md +16 -0
- package/standard/seeds/software/roles/developer.md +16 -0
- package/standard/seeds/software/roles/framework-owner.md +16 -0
- package/standard/seeds/software/roles/project-manager.md +18 -0
- package/standard/seeds/software/roles/stakeholder.md +16 -0
- package/standard/seeds/software/shapes/.gitkeep +0 -0
- package/standard/seeds/software/shapes/frame.architecture.md +26 -0
- package/standard/seeds/software/shapes/frame.audience.md +26 -0
- package/standard/seeds/software/shapes/frame.criteria.md +27 -0
- package/standard/seeds/software/shapes/frame.market.md +31 -0
- package/standard/seeds/software/shapes/spec.full.md +67 -0
- package/standard/seeds/software/shapes/spec.micro.md +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Required Notice: Copyright 2026 The Virtual Panda (https://eidosmd.com)
|
|
2
|
+
|
|
3
|
+
# PolyForm Noncommercial License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree
|
|
10
|
+
to them as both strict obligations and conditions to all
|
|
11
|
+
your licenses.
|
|
12
|
+
|
|
13
|
+
## Copyright License
|
|
14
|
+
|
|
15
|
+
The licensor grants you a copyright license for the
|
|
16
|
+
software to do everything you might do with the software
|
|
17
|
+
that would otherwise infringe the licensor's copyright
|
|
18
|
+
in it for any permitted purpose. However, you may
|
|
19
|
+
only distribute the software according to [Distribution
|
|
20
|
+
License](#distribution-license) and make changes or new works
|
|
21
|
+
based on the software according to [Changes and New Works
|
|
22
|
+
License](#changes-and-new-works-license).
|
|
23
|
+
|
|
24
|
+
## Distribution License
|
|
25
|
+
|
|
26
|
+
The licensor grants you an additional copyright license
|
|
27
|
+
to distribute copies of the software. Your license
|
|
28
|
+
to distribute covers distributing the software with
|
|
29
|
+
changes and new works permitted by [Changes and New Works
|
|
30
|
+
License](#changes-and-new-works-license).
|
|
31
|
+
|
|
32
|
+
## Notices
|
|
33
|
+
|
|
34
|
+
You must ensure that anyone who gets a copy of any part of
|
|
35
|
+
the software from you also gets a copy of these terms or the
|
|
36
|
+
URL for them above, as well as copies of any plain-text lines
|
|
37
|
+
beginning with `Required Notice:` that the licensor provided
|
|
38
|
+
with the software. For example:
|
|
39
|
+
|
|
40
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
41
|
+
|
|
42
|
+
## Changes and New Works License
|
|
43
|
+
|
|
44
|
+
The licensor grants you an additional copyright license to
|
|
45
|
+
make changes and new works based on the software for any
|
|
46
|
+
permitted purpose.
|
|
47
|
+
|
|
48
|
+
## Patent License
|
|
49
|
+
|
|
50
|
+
The licensor grants you a patent license for the software that
|
|
51
|
+
covers patent claims the licensor can license, or becomes able
|
|
52
|
+
to license, that you would infringe by using the software.
|
|
53
|
+
|
|
54
|
+
## Noncommercial Purposes
|
|
55
|
+
|
|
56
|
+
Any noncommercial purpose is a permitted purpose.
|
|
57
|
+
|
|
58
|
+
## Personal Uses
|
|
59
|
+
|
|
60
|
+
Personal use for research, experiment, and testing for
|
|
61
|
+
the benefit of public knowledge, personal study, private
|
|
62
|
+
entertainment, hobby projects, amateur pursuits, or religious
|
|
63
|
+
observance, without any anticipated commercial application,
|
|
64
|
+
is use for a permitted purpose.
|
|
65
|
+
|
|
66
|
+
## Noncommercial Organizations
|
|
67
|
+
|
|
68
|
+
Use by any charitable organization, educational institution,
|
|
69
|
+
public research organization, public safety or health
|
|
70
|
+
organization, environmental protection organization,
|
|
71
|
+
or government institution is use for a permitted purpose
|
|
72
|
+
regardless of the source of funding or obligations resulting
|
|
73
|
+
from the funding.
|
|
74
|
+
|
|
75
|
+
## Fair Use
|
|
76
|
+
|
|
77
|
+
You may have "fair use" rights for the software under the
|
|
78
|
+
law. These terms do not limit them.
|
|
79
|
+
|
|
80
|
+
## No Other Rights
|
|
81
|
+
|
|
82
|
+
These terms do not allow you to sublicense or transfer any of
|
|
83
|
+
your licenses to anyone else, or prevent the licensor from
|
|
84
|
+
granting licenses to anyone else. These terms do not imply
|
|
85
|
+
any other licenses.
|
|
86
|
+
|
|
87
|
+
## Patent Defense
|
|
88
|
+
|
|
89
|
+
If you make any written claim that the software infringes or
|
|
90
|
+
contributes to infringement of any patent, your patent license
|
|
91
|
+
for the software granted under these terms ends immediately. If
|
|
92
|
+
your company makes such a claim, your patent license ends
|
|
93
|
+
immediately for work on behalf of your company.
|
|
94
|
+
|
|
95
|
+
## Violations
|
|
96
|
+
|
|
97
|
+
The first time you are notified in writing that you have
|
|
98
|
+
violated any of these terms, or done anything with the software
|
|
99
|
+
not covered by your licenses, your licenses can nonetheless
|
|
100
|
+
continue if you come into full compliance with these terms,
|
|
101
|
+
and take practical steps to correct past violations, within
|
|
102
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
103
|
+
end immediately.
|
|
104
|
+
|
|
105
|
+
## No Liability
|
|
106
|
+
|
|
107
|
+
***As far as the law allows, the software comes as is, without
|
|
108
|
+
any warranty or condition, and the licensor will not be liable
|
|
109
|
+
to you for any damages arising out of these terms or the use
|
|
110
|
+
or nature of the software, under any kind of legal claim.***
|
|
111
|
+
|
|
112
|
+
## Definitions
|
|
113
|
+
|
|
114
|
+
The **licensor** is the individual or entity offering these
|
|
115
|
+
terms, and the **software** is the software the licensor makes
|
|
116
|
+
available under these terms.
|
|
117
|
+
|
|
118
|
+
**You** refers to the individual or entity agreeing to these
|
|
119
|
+
terms.
|
|
120
|
+
|
|
121
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
122
|
+
or other kind of organization that you work for, plus all
|
|
123
|
+
organizations that have control over, are under the control of,
|
|
124
|
+
or are under common control with that organization. **Control**
|
|
125
|
+
means ownership of substantially all the assets of an entity,
|
|
126
|
+
or the power to direct its management and policies by vote,
|
|
127
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
128
|
+
|
|
129
|
+
**Your licenses** are all the licenses granted to you for the
|
|
130
|
+
software under these terms.
|
|
131
|
+
|
|
132
|
+
**Use** means anything you do with the software requiring one
|
|
133
|
+
of your licenses.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
eidosmd
|
|
2
|
+
Copyright 2026 The Virtual Panda
|
|
3
|
+
|
|
4
|
+
The eidos CLI is licensed under the PolyForm Noncommercial License 1.0.0
|
|
5
|
+
(see LICENSE): free to use, read, and change for any noncommercial purpose;
|
|
6
|
+
commercial use needs a license from The Virtual Panda.
|
|
7
|
+
|
|
8
|
+
This product includes material from the Eidos standard
|
|
9
|
+
(https://github.com/BuildableWorks/Eidos), Copyright 2026 Buildable,
|
|
10
|
+
licensed under the Apache License, Version 2.0
|
|
11
|
+
(https://www.apache.org/licenses/LICENSE-2.0). The files under standard/
|
|
12
|
+
(EIDOS.md and seeds/) are copied from the pinned checkout of that repository
|
|
13
|
+
at the monorepo's standard/ submodule by scripts/sync-standard.mjs,
|
|
14
|
+
unmodified except that each seed's .gitignore travels as _gitignore, and are
|
|
15
|
+
installed into a root by `eidos init`. That material keeps its Apache license.
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# eidosmd
|
|
2
|
+
|
|
3
|
+
The command line for [Eidos](https://github.com/BuildableWorks/Eidos/blob/main/README.md): a markdown standard in which one file is the complete source of truth for one unit of a thing, true whether or not it has been built.
|
|
4
|
+
|
|
5
|
+
`eidos` scaffolds a root, generates blueprints that are born conforming, validates them against the root's own framework, regenerates the indexes, and hands any coding agent the workflow it needs to work in one. Plain files, no service, no hidden state. It carries **Eidos 4.4.3**.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g eidosmd
|
|
9
|
+
eidos --help
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Node 20 or newer. The package installs one binary, `eidos`, and three small pure-JavaScript dependencies with no dependencies of their own, so the install takes seconds and every command starts in well under a tenth of one.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
eidos seeds # the bundled starting frameworks
|
|
18
|
+
eidos init --group Identity --product "Care Connect"
|
|
19
|
+
cd Blueprints
|
|
20
|
+
eidos framework # what this root declares
|
|
21
|
+
eidos new specs "Session Management" --group identity --summary "Keeps a signed-in user signed in."
|
|
22
|
+
eidos check # a review, not a gate
|
|
23
|
+
eidos index # each collection's index.md
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`init` copies a seed into `Blueprints/_eidos/` (the framework: shapes, roles, `Framework.yaml`), creates a folder per collection, and scaffolds one blank frame per framing flavor. It writes no prose. Everything a seed ships is reshapeable afterwards.
|
|
27
|
+
|
|
28
|
+
### The framework document
|
|
29
|
+
|
|
30
|
+
The CLI works on `_eidos/Framework.yaml`: the framework as data, the same fields the standard's markdown form carries, comments where you want them, and every collection's index inside it under `index` (which `eidos index` rewrites and nothing else). The standard also allows `Framework.md`, the form for people, with a per-collection `index.md`; a root that keeps it is converted first, and every other command says so:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
eidos convert --dry-run # the writes it would make
|
|
34
|
+
eidos convert # Framework.yaml with the index inside; Framework.md and each index.md removed
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The markdown form's prose has no field to land in and is left behind; each collection's description travels.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
| Command | What it does |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `eidos init [root]` | Create a root from a seed: `--seed`, `--naming`, `--group` (repeatable), `--product`, `--dry-run` |
|
|
44
|
+
| `eidos convert` | Move a markdown root to `Framework.yaml`, the form the CLI works in; `--dry-run` |
|
|
45
|
+
| `eidos seeds` | List the bundled seeds (software, book, research) with their collections and flavors |
|
|
46
|
+
| `eidos framework` | Show the root's framework: version, naming, top-level docs, collections, flavors, groups, Schema; `--as yaml` prints it as the document; the one command that also reads a `Framework.md` root |
|
|
47
|
+
| `eidos list [collection]` | List blueprints; filter with `--group`, `--flavor`, `--where key=value` |
|
|
48
|
+
| `eidos show <id\|file\|path>` | Print one blueprint, or its properties and sections with `--json` |
|
|
49
|
+
| `eidos new <collection> <title>` | Scaffold a blueprint: frontmatter from the Schema, body from the flavor's shape, filename in convention |
|
|
50
|
+
| `eidos check [paths...]` | Validate the root (or the named blueprints) against its own framework; `--strict`, `--json` |
|
|
51
|
+
| `eidos index` | Regenerate the indexes, the `index` key inside `Framework.yaml`; `--check` verifies without writing |
|
|
52
|
+
| `eidos browser` | Open the root in a local web page on 127.0.0.1: browse, read, create, edit, check, index |
|
|
53
|
+
| `eidos roles` | The roles the framework installed under `_eidos/roles/` |
|
|
54
|
+
| `eidos whoami` | Show or set the actor in `_eidos/me.md` (role and calibration) |
|
|
55
|
+
| `eidos instructions [guide]` | The workflow an agent follows: overview, authoring, validating, configuring, init-required |
|
|
56
|
+
| `eidos standard` | Print the text of the standard this CLI carries; `--version` for just the number |
|
|
57
|
+
| `eidos agents` | Print the nudge that points agents at `eidos instructions`; `--write` puts it in `AGENTS.md` |
|
|
58
|
+
|
|
59
|
+
Every command takes `--root <path>`; without it the root is found from the working directory by its `_eidos/` marker, looking up the tree and then two levels down, so the commands work from a repository root that keeps its blueprints in `Blueprints/`.
|
|
60
|
+
|
|
61
|
+
`framework`, `list`, `show`, `check`, `index`, `new`, and `whoami` take `--json` for stable output. Exit codes: `0` done, `1` the check found errors (or an index is stale under `--check`), `2` the command could not run.
|
|
62
|
+
|
|
63
|
+
## The browser
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
eidos browser # http://127.0.0.1:6180, this machine only; Ctrl+C to stop
|
|
67
|
+
eidos browser --no-open --port 7000
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The root in a local web page: browse collections and groups, read a blueprint rendered with its properties and its findings, follow links between blueprints, create one from the same scaffold `new` uses, edit and save one (the check runs on save), run the check, rebuild the index, and read the framework, the standard, and the agent instructions. There is nothing in the page that is not a command: every write runs the same core function as `new`, `index`, or `check`, and the page re-reads the root on every request, so it stays honest while files change on disk.
|
|
71
|
+
|
|
72
|
+
## What `check` reports
|
|
73
|
+
|
|
74
|
+
Validation is framework-defined: every rule reads `_eidos/Framework.yaml` and the shapes in `_eidos/shapes/`, never a contract hardcoded in the tool.
|
|
75
|
+
|
|
76
|
+
- **Errors** are wrong on any reading: frontmatter that does not parse, a missing `id` or `title`, an `id` that is not kebab-case or is used twice, a `flavor` the collection does not declare, a link to a file that does not exist, a shape the framework points at but lacks.
|
|
77
|
+
- **Warnings** are gaps the standard says to surface, never refuse: a property the Schema applies here but the blueprint lacks or leaves empty, a property the Schema does not declare, a work-tracking field, a section the flavor's shape declares but the body lacks, a section the shape does not know, sections out of order, a grouping value that does not match its folder, a declared frame nobody has written, a stale `index.md`, a version gap between the framework and the standard the CLI carries.
|
|
78
|
+
|
|
79
|
+
Frontmatter that is not valid YAML (an unquoted summary with a colon in it) is read leniently and reported as a warning, because Obsidian will refuse it even though a person can read it. `--strict` fails on warnings too, for CI.
|
|
80
|
+
|
|
81
|
+
## For agents
|
|
82
|
+
|
|
83
|
+
Eidos is human-first: the owner holds the intent, the scope, and the decisions, and an agent facilitates. The CLI owns structure (`new`, `check`, `index`) so an agent never hand-assembles frontmatter or edits a generated index; the words in a blueprint are written with the owner.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
eidos instructions overview # required first read
|
|
87
|
+
eidos agents --write # a short pointer in AGENTS.md (or --file CLAUDE.md)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The guides are the operating contract, the same one the standard's [Claude skills](https://github.com/BuildableWorks/Eidos/tree/main/skills) carry, restated around the commands. An agent with a shell needs nothing else.
|
|
91
|
+
|
|
92
|
+
## The standard and this tool
|
|
93
|
+
|
|
94
|
+
Two things version separately.
|
|
95
|
+
|
|
96
|
+
- **The standard** is [`EIDOS.md`](https://github.com/BuildableWorks/Eidos/blob/main/EIDOS.md), maintained by Buildable in its own repository. A root records the version it targets as `eidos_version` in its framework document.
|
|
97
|
+
- **This CLI** follows its own semver, in [`package.json`](package.json) and [`CHANGELOG.md`](CHANGELOG.md). It ships one release of the standard under [`standard/`](standard) (`EIDOS.md` and the seeds), which is what `eidos init` installs and what `eidos check` compares a framework's version against. `eidos --version` names both.
|
|
98
|
+
|
|
99
|
+
`standard/` is a committed copy of that repository's `EIDOS.md` and `seeds/`, refreshed from the monorepo's `standard/` submodule by `pnpm sync-standard` (`--check` exits 1 if it has drifted from the pin); `npm pack` ships only what sits inside this package, so the copy has to be here. Each seed's `.gitignore` travels as `_gitignore`, because npm never packs a file by that name, and `eidos init` renames it back. Never edit `standard/` by hand: sync it.
|
|
100
|
+
|
|
101
|
+
A root on a newer or older standard still works: the framework in front of the tool is the operative contract, and the gap is reported once by `check`. Moving a root between versions is a judgment call this CLI does not automate; the `migrate` skill and [`versions/MIGRATIONS.md`](https://github.com/BuildableWorks/Eidos/blob/main/versions/MIGRATIONS.md) cover it.
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
From the monorepo root or from `cli/`:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm install
|
|
109
|
+
pnpm build # tsc → dist/
|
|
110
|
+
pnpm test # builds, then runs the suite with node --test
|
|
111
|
+
pnpm check # type-check
|
|
112
|
+
node dist/src/cli.js --help
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```txt
|
|
116
|
+
src/
|
|
117
|
+
cli.ts the executable entry
|
|
118
|
+
program.ts every command, its options, and its help (commander)
|
|
119
|
+
context.ts find the root, load its framework and blueprints
|
|
120
|
+
commands/ one module per command: what each prints and returns
|
|
121
|
+
browser/ the one HTML page `eidos browser` serves
|
|
122
|
+
core/ the testable model, no printing:
|
|
123
|
+
root.ts finding a root by its _eidos/ marker
|
|
124
|
+
framework.ts finding and loading the framework document
|
|
125
|
+
framework-model.ts the model: collections, flavors, groups, Schema
|
|
126
|
+
framework-markdown.ts parsing Framework.md, for convert and framework --as yaml
|
|
127
|
+
framework-structured.ts parsing and writing Framework.yaml, and the index inside it
|
|
128
|
+
shape.ts reading a shape file's sections and template
|
|
129
|
+
blueprint.ts loading blueprints from a collection's folder
|
|
130
|
+
frontmatter.ts YAML in, house-style frontmatter out
|
|
131
|
+
check.ts every validation rule
|
|
132
|
+
index-leaf.ts the index inside Framework.yaml
|
|
133
|
+
convert.ts moving a markdown root to Framework.yaml
|
|
134
|
+
seed.ts installing a seed into a new root
|
|
135
|
+
server.ts the browser's http server and JSON API, over the same core
|
|
136
|
+
scaffold.ts generating one conforming blueprint
|
|
137
|
+
me.ts roles and the actor
|
|
138
|
+
instructions/ the guides `eidos instructions` prints
|
|
139
|
+
standard/ the committed copy of EIDOS.md and seeds/ (synced from Buildable's repo, never hand-edited)
|
|
140
|
+
scripts/ sync-standard.mjs
|
|
141
|
+
test/ node:test suites; temp roots are installed from the seeds
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
[PolyForm Noncommercial 1.0.0](LICENSE). Copyright The Virtual Panda. The source is here to read, run, and change for any noncommercial purpose; using it at work needs a commercial license, the way Obsidian does it. `standard/` is material from the [Eidos](https://github.com/BuildableWorks/Eidos) repository, copyright Buildable, under Apache-2.0; see [NOTICE](NOTICE).
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Eidos</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
--bg: #fbfaf7; --panel: #f3f1ec; --line: #ddd8cf; --text: #1f1d1a; --muted: #6b665e;
|
|
11
|
+
--accent: #7a4a1f; --accent-soft: #f1e4d6; --error: #b23a2a; --warn: #9a6a12; --ok: #2f6b3a;
|
|
12
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
13
|
+
--sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
14
|
+
}
|
|
15
|
+
@media (prefers-color-scheme: dark) {
|
|
16
|
+
:root { --bg: #1b1916; --panel: #232019; --line: #3a352c; --text: #ebe6dc; --muted: #a39d92; --accent: #d9a56b; --accent-soft: #3a2d1f; --error: #e2705f; --warn: #d8a437; --ok: #7bc48a; }
|
|
17
|
+
}
|
|
18
|
+
* { box-sizing: border-box; }
|
|
19
|
+
body { margin: 0; font: 15px/1.5 var(--sans); color: var(--text); background: var(--bg); }
|
|
20
|
+
a { color: var(--accent); }
|
|
21
|
+
button, select, input, textarea { font: inherit; color: inherit; }
|
|
22
|
+
button { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 4px 10px; cursor: pointer; }
|
|
23
|
+
button:hover { border-color: var(--accent); }
|
|
24
|
+
button.primary { background: var(--accent); color: var(--bg); border-color: var(--accent); }
|
|
25
|
+
input, select, textarea { background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 5px 8px; width: 100%; }
|
|
26
|
+
code, pre { font-family: var(--mono); font-size: 0.92em; }
|
|
27
|
+
pre { background: var(--panel); padding: 10px 12px; border-radius: 6px; overflow-x: auto; }
|
|
28
|
+
.layout { display: grid; grid-template-columns: 290px 1fr; min-height: 100vh; }
|
|
29
|
+
aside { background: var(--panel); border-right: 1px solid var(--line); padding: 14px; overflow-y: auto; position: sticky; top: 0; height: 100vh; }
|
|
30
|
+
main { padding: 20px 32px 60px; max-width: 980px; }
|
|
31
|
+
.brand { display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px; }
|
|
32
|
+
.brand strong { font-size: 18px; }
|
|
33
|
+
.brand small { color: var(--muted); }
|
|
34
|
+
.nav a { display: block; padding: 3px 6px; border-radius: 4px; color: inherit; text-decoration: none; }
|
|
35
|
+
.nav a.active, .nav a:hover { background: var(--accent-soft); }
|
|
36
|
+
.nav h3 { margin: 14px 0 4px; font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); display: flex; justify-content: space-between; align-items: center; }
|
|
37
|
+
.nav h3 button { font-size: 12px; padding: 1px 7px; }
|
|
38
|
+
.nav h4 { margin: 8px 0 2px 6px; font-size: 12px; color: var(--muted); font-weight: 600; }
|
|
39
|
+
.nav .bp { font-size: 14px; padding-left: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
40
|
+
.nav .bp.flagged::after { content: " •"; color: var(--warn); }
|
|
41
|
+
.search { margin: 6px 0 4px; }
|
|
42
|
+
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; align-items: center; }
|
|
43
|
+
.toolbar .spacer { flex: 1; }
|
|
44
|
+
.meta { color: var(--muted); font-size: 13px; margin: 0 0 6px; }
|
|
45
|
+
.meta code { color: var(--text); }
|
|
46
|
+
h1.title { margin: 0 0 6px; font-size: 26px; }
|
|
47
|
+
table.props { border-collapse: collapse; margin: 14px 0 22px; font-size: 14px; }
|
|
48
|
+
table.props td { padding: 3px 12px 3px 0; vertical-align: top; }
|
|
49
|
+
table.props td:first-child { color: var(--muted); font-family: var(--mono); font-size: 13px; white-space: nowrap; }
|
|
50
|
+
.body { border-top: 1px solid var(--line); padding-top: 12px; }
|
|
51
|
+
.body h1 { font-size: 24px; } .body h2 { font-size: 20px; margin-top: 28px; border-bottom: 1px solid var(--line); padding-bottom: 4px; } .body h3 { font-size: 16px; }
|
|
52
|
+
.body table { border-collapse: collapse; } .body th, .body td { border: 1px solid var(--line); padding: 4px 8px; }
|
|
53
|
+
.body em:only-child { color: var(--muted); }
|
|
54
|
+
.findings { margin: 10px 0 16px; padding: 0; list-style: none; font-size: 14px; }
|
|
55
|
+
.findings li { padding: 4px 8px; border-left: 3px solid var(--warn); background: var(--panel); margin-bottom: 4px; border-radius: 0 4px 4px 0; }
|
|
56
|
+
.findings li.error { border-left-color: var(--error); }
|
|
57
|
+
.findings code { color: var(--muted); }
|
|
58
|
+
.badge { display: inline-block; font-size: 12px; padding: 1px 8px; border-radius: 10px; background: var(--panel); border: 1px solid var(--line); color: var(--muted); margin-left: 6px; }
|
|
59
|
+
.badge.error { color: var(--error); border-color: var(--error); } .badge.warn { color: var(--warn); border-color: var(--warn); } .badge.ok { color: var(--ok); border-color: var(--ok); }
|
|
60
|
+
textarea.editor { min-height: 60vh; font-family: var(--mono); font-size: 13px; line-height: 1.45; white-space: pre; }
|
|
61
|
+
.form { display: grid; grid-template-columns: 140px 1fr; gap: 10px 14px; max-width: 640px; align-items: center; margin: 16px 0; }
|
|
62
|
+
.form label { color: var(--muted); font-size: 14px; }
|
|
63
|
+
.toast { position: fixed; right: 20px; bottom: 20px; background: var(--text); color: var(--bg); padding: 10px 16px; border-radius: 8px; max-width: 420px; font-size: 14px; white-space: pre-wrap; box-shadow: 0 6px 24px rgba(0,0,0,.25); }
|
|
64
|
+
.file-group { margin: 18px 0 6px; font-family: var(--mono); font-size: 14px; }
|
|
65
|
+
.file-group a { text-decoration: none; }
|
|
66
|
+
.hint { color: var(--muted); font-size: 13px; }
|
|
67
|
+
.empty { color: var(--muted); padding: 40px 0; }
|
|
68
|
+
.fw h2 { margin-top: 26px; }
|
|
69
|
+
.fw dl { display: grid; grid-template-columns: max-content 1fr; gap: 4px 14px; }
|
|
70
|
+
.fw dt { color: var(--muted); }
|
|
71
|
+
@media (max-width: 760px) { .layout { grid-template-columns: 1fr; } aside { position: static; height: auto; } main { padding: 16px; } }
|
|
72
|
+
</style>
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
<div class="layout">
|
|
76
|
+
<aside>
|
|
77
|
+
<div class="brand"><strong id="rootName">Eidos</strong><small id="rootVersion"></small></div>
|
|
78
|
+
<input class="search" id="search" type="search" placeholder="Find a blueprint…" autocomplete="off">
|
|
79
|
+
<nav class="nav" id="nav"></nav>
|
|
80
|
+
</aside>
|
|
81
|
+
<main id="main"><div class="empty">Loading…</div></main>
|
|
82
|
+
</div>
|
|
83
|
+
<script>
|
|
84
|
+
(() => {
|
|
85
|
+
const $ = (sel, el = document) => el.querySelector(sel);
|
|
86
|
+
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
87
|
+
const api = async (method, url, body) => {
|
|
88
|
+
const res = await fetch(url, { method, headers: body ? { 'content-type': 'application/json' } : {}, body: body ? JSON.stringify(body) : undefined });
|
|
89
|
+
const data = await res.json().catch(() => ({}));
|
|
90
|
+
if (!res.ok) throw new Error(data.error || res.statusText);
|
|
91
|
+
return data;
|
|
92
|
+
};
|
|
93
|
+
let state = { root: null, filter: '', flagged: new Set() };
|
|
94
|
+
let toastTimer;
|
|
95
|
+
const toast = (msg, ms = 4200) => {
|
|
96
|
+
let el = $('.toast'); if (!el) { el = document.createElement('div'); el.className = 'toast'; document.body.appendChild(el); }
|
|
97
|
+
el.textContent = msg; clearTimeout(toastTimer); toastTimer = setTimeout(() => el.remove(), ms);
|
|
98
|
+
};
|
|
99
|
+
const route = () => { const h = location.hash.replace(/^#\/?/, ''); const [view, ...rest] = h.split('/'); return { view: view || 'framework', arg: rest.join('/') }; };
|
|
100
|
+
const go = (path) => { location.hash = '#/' + path; };
|
|
101
|
+
const bpHref = (p) => '#/blueprint/' + p.split('/').map(encodeURIComponent).join('/');
|
|
102
|
+
|
|
103
|
+
async function loadRoot() {
|
|
104
|
+
state.root = await api('GET', '/api/root');
|
|
105
|
+
$('#rootName').textContent = state.root.name;
|
|
106
|
+
$('#rootVersion').textContent = `Eidos ${state.root.framework.eidos_version ?? '?'} · ${state.root.framework.file}`;
|
|
107
|
+
document.title = `${state.root.name} · Eidos`;
|
|
108
|
+
renderNav();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function renderNav() {
|
|
112
|
+
const { framework, blueprints } = state.root;
|
|
113
|
+
const q = state.filter.toLowerCase();
|
|
114
|
+
const cur = route();
|
|
115
|
+
const active = (h) => location.hash === h ? ' active' : '';
|
|
116
|
+
let html = `<a href="#/framework" class="${active('#/framework') || (cur.view === 'framework' ? 'active' : '')}">Framework</a>
|
|
117
|
+
<a href="#/check" class="${cur.view === 'check' ? 'active' : ''}">Check</a>
|
|
118
|
+
<a href="#/standard" class="${cur.view === 'standard' ? 'active' : ''}">The standard</a>
|
|
119
|
+
<a href="#/instructions/overview" class="${cur.view === 'instructions' ? 'active' : ''}">Agent instructions</a>`;
|
|
120
|
+
for (const c of framework.collections) {
|
|
121
|
+
const own = blueprints.filter((b) => b.collection === c.name && (!q || [b.title, b.id, b.summary, b.path].some((v) => (v || '').toLowerCase().includes(q))));
|
|
122
|
+
html += `<h3><span>${esc(c.name)}</span><button data-new="${esc(c.name)}" title="New blueprint in ${esc(c.name)}">+ new</button></h3>`;
|
|
123
|
+
const groups = [...new Set(own.map((b) => b.group).filter(Boolean))].sort();
|
|
124
|
+
const item = (b) => `<a class="bp${state.flagged.has(b.path) ? ' flagged' : ''}${cur.view === 'blueprint' && decodeURIComponent(cur.arg) === b.path ? ' active' : ''}" href="${bpHref(b.path)}" title="${esc(b.summary || b.path)}">${esc(b.title)}</a>`;
|
|
125
|
+
html += own.filter((b) => !b.group).map(item).join('');
|
|
126
|
+
for (const g of groups) html += `<h4>${esc(g)}</h4>` + own.filter((b) => b.group === g).map(item).join('');
|
|
127
|
+
if (own.length === 0) html += `<div class="hint" style="padding-left:12px">${q ? 'no match' : 'empty'}</div>`;
|
|
128
|
+
}
|
|
129
|
+
$('#nav').innerHTML = html;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const findingsHtml = (findings) => findings.length ? `<ul class="findings">${findings.map((f) => `<li class="${f.level}"><code>${esc(f.code)}</code> ${esc(f.message)}</li>`).join('')}</ul>` : '';
|
|
133
|
+
|
|
134
|
+
async function viewBlueprint(pathArg) {
|
|
135
|
+
const p = decodeURIComponent(pathArg);
|
|
136
|
+
const main = $('#main');
|
|
137
|
+
let b;
|
|
138
|
+
try { b = await api('GET', '/api/blueprints/' + p.split('/').map(encodeURIComponent).join('/')); }
|
|
139
|
+
catch (e) { main.innerHTML = `<div class="empty">${esc(e.message)}</div>`; return; }
|
|
140
|
+
const errors = b.findings.filter((f) => f.level === 'error').length, warns = b.findings.length - errors;
|
|
141
|
+
const badge = errors ? `<span class="badge error">${errors} error${errors > 1 ? 's' : ''}</span>` : '';
|
|
142
|
+
const wbadge = warns ? `<span class="badge warn">${warns} warning${warns > 1 ? 's' : ''}</span>` : '';
|
|
143
|
+
// a property value that is a markdown link opens the blueprint it points at
|
|
144
|
+
const val = (x) => { const m = typeof x === 'string' && /^\s*\[([^\]]*)\]\(([^)]+)\)\s*$/.exec(x); if (!m) return `<code>${esc(x)}</code>`; const hit = state.root.blueprints.find((y) => y.path === resolveRel(p, m[2].split('#')[0])); return hit ? `<a href="${bpHref(hit.path)}">${esc(m[1])}</a>` : `<span title="not a blueprint in this root: ${esc(m[2])}">${esc(m[1])}</span> <span class="badge error">broken</span>`; };
|
|
145
|
+
const props = Object.entries(b.properties).map(([k, v]) => `<tr><td>${esc(k)}</td><td>${Array.isArray(v) ? (v.length ? v.map(val).join(', ') : '<span class="hint">[]</span>') : (v === null || v === '' ? '<span class="hint">—</span>' : esc(v))}</td></tr>`).join('');
|
|
146
|
+
const missing = b.shape_sections.filter((s) => !b.sections.map((x) => x.toLowerCase()).includes(s.toLowerCase()));
|
|
147
|
+
main.innerHTML = `
|
|
148
|
+
<div class="toolbar"><span class="meta">${esc(b.collection)}${b.group ? ' / ' + esc(b.group) : ''} · <code>${esc(b.path)}</code> · ${esc(b.flavor || '')}</span><span class="spacer"></span>
|
|
149
|
+
<button id="edit">Edit</button></div>
|
|
150
|
+
<h1 class="title">${esc(b.title)}${badge}${wbadge}</h1>
|
|
151
|
+
<p class="meta">id <code>${esc(b.id || '(none)')}</code>${b.summary ? ' · ' + esc(b.summary) : ' · <span class="hint">no summary yet</span>'}</p>
|
|
152
|
+
${findingsHtml(b.findings)}
|
|
153
|
+
${missing.length ? `<p class="hint">Sections the ${esc(b.flavor)} shape declares and this blueprint lacks: ${missing.map((s) => `<code>${esc(s)}</code>`).join(', ')}</p>` : ''}
|
|
154
|
+
<table class="props">${props}</table>
|
|
155
|
+
<div class="body" id="body">${b.html}</div>
|
|
156
|
+
<div id="editor" hidden><textarea class="editor" id="text"></textarea>
|
|
157
|
+
<div class="toolbar" style="margin-top:10px"><button class="primary" id="save">Save</button><button id="cancel">Cancel</button><span class="hint">Saving runs the check on this file; the result shows above.</span></div></div>`;
|
|
158
|
+
if (b.findings.length) state.flagged.add(p); else state.flagged.delete(p);
|
|
159
|
+
renderNav();
|
|
160
|
+
// relative .md links open the blueprint they point at
|
|
161
|
+
for (const a of main.querySelectorAll('#body a[href]')) {
|
|
162
|
+
const href = a.getAttribute('href');
|
|
163
|
+
if (/^[a-z]+:/i.test(href) || href.startsWith('#')) continue;
|
|
164
|
+
const target = resolveRel(p, href.split('#')[0]);
|
|
165
|
+
const hit = state.root.blueprints.find((x) => x.path === target);
|
|
166
|
+
if (hit) { a.href = bpHref(hit.path); } else { a.title = 'not a blueprint in this root: ' + target; a.addEventListener('click', (e) => { e.preventDefault(); toast('Not a blueprint in this root: ' + target); }); }
|
|
167
|
+
}
|
|
168
|
+
$('#edit').onclick = () => { $('#body').hidden = true; $('#editor').hidden = false; $('#text').value = b.raw; $('#edit').hidden = true; $('#text').focus(); };
|
|
169
|
+
$('#cancel').onclick = () => viewBlueprint(pathArg);
|
|
170
|
+
$('#save').onclick = async () => {
|
|
171
|
+
try { const r = await api('PUT', '/api/blueprints/' + p.split('/').map(encodeURIComponent).join('/'), { text: $('#text').value }); toast(r.findings.length ? `Saved. ${r.findings.length} finding${r.findings.length > 1 ? 's' : ''} on this file.` : 'Saved. No findings.'); await loadRoot(); viewBlueprint(pathArg); }
|
|
172
|
+
catch (e) { toast('Save failed: ' + e.message); }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function resolveRel(from, href) {
|
|
176
|
+
const parts = from.split('/').slice(0, -1);
|
|
177
|
+
for (const seg of decodeURIComponent(href).split('/')) { if (seg === '..') parts.pop(); else if (seg !== '.' && seg !== '') parts.push(seg); }
|
|
178
|
+
return parts.join('/');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function viewFramework() {
|
|
182
|
+
const f = state.root.framework, a = state.root.actor;
|
|
183
|
+
const flav = (c) => c.flavors.map((x) => `<code>${esc(x.name)}</code>${x.default ? ' (default)' : ''}${x.description ? ' — ' + esc(x.description) : ''}`).join('<br>');
|
|
184
|
+
const canvas = (c) => !c.canvas ? '' : typeof c.canvas === 'string' ? c.canvas : `card from ## ${esc(c.canvas.section)}`;
|
|
185
|
+
$('#main').innerHTML = `<div class="fw">
|
|
186
|
+
<h1 class="title">${esc(state.root.name)}</h1>
|
|
187
|
+
<p class="meta">Eidos ${esc(f.eidos_version ?? 'unversioned')} · naming <code>${esc(f.naming)}</code> · <code>${esc(f.file)}</code> · CLI ${esc(state.root.cli_version)} (standard ${esc(state.root.standard_version)})</p>
|
|
188
|
+
${f.problems.length ? findingsHtml(f.problems.map((m) => ({ level: 'error', code: 'framework-invalid', message: m }))) : ''}
|
|
189
|
+
<div class="toolbar"><button id="reindex">Rebuild index</button><button id="runcheck">Run check</button><span class="hint">Both run exactly what <code>eidos index</code> and <code>eidos check</code> run.</span></div>
|
|
190
|
+
<h2>Actor</h2><p>${a.exists && a.role ? `<strong>${esc(a.role)}</strong> · ownership: ${esc(a.ownership || '—')} · experience: ${esc(a.experience || '—')} · capacity: ${esc(a.capacity || '—')}` : `<span class="hint">unset (full facilitation) — set it with <code>eidos whoami --role <name></code></span>`}</p>
|
|
191
|
+
<p class="hint">Roles installed: ${state.root.roles.map((r) => `<code>${esc(r.name)}</code>`).join(', ') || 'none'}</p>
|
|
192
|
+
<h2>Collections</h2>
|
|
193
|
+
${f.collections.map((c, i) => `<h3>${esc(c.name)}${i === 0 ? ' <span class="badge">framing</span>' : ''}${c.grouping ? ` <span class="badge">grouped by ${esc(c.grouping.label)}${c.grouping.property ? ' (' + esc(c.grouping.property) + ')' : ''}</span>` : ''}</h3>
|
|
194
|
+
${c.description ? `<p>${esc(c.description)}</p>` : ''}
|
|
195
|
+
<dl><dt>flavors</dt><dd>${flav(c)}</dd>${c.canvas ? `<dt>canvas</dt><dd>${canvas(c)}</dd>` : ''}${c.grouping && c.grouping.groups ? `<dt>groups</dt><dd>${c.grouping.groups.map((g) => `<code>${esc(g.name)}</code>${g.description ? ' — ' + esc(g.description) : ''}`).join('<br>')}</dd>` : ''}</dl>`).join('')}
|
|
196
|
+
<h2>Schema</h2>
|
|
197
|
+
<table class="props"><tr><td>core</td><td>${f.schema.core.map((p) => `<code>${esc(p.name)}</code>`).join(' ')}</td></tr>
|
|
198
|
+
${f.schema.custom.map((p) => `<tr><td>${esc(p.name)}</td><td><span class="badge">${esc(p.type)}</span> <span class="badge">${Array.isArray(p.applies_to) ? esc(p.applies_to.join(', ')) : 'all'}</span> ${esc(p.meaning)}</td></tr>`).join('')}</table>
|
|
199
|
+
${f.top_level && f.top_level.length ? `<h2>Top-level docs</h2><ul>${f.top_level.map((d) => `<li><strong>${esc(d.title)}</strong> <code>${esc(d.path)}</code>${d.description ? ' — ' + esc(d.description) : ''}</li>`).join('')}</ul>` : ''}
|
|
200
|
+
</div>`;
|
|
201
|
+
$('#reindex').onclick = async () => { try { const r = await api('POST', '/api/index'); toast(r.indexes.map((i) => `${i.collection}: ${i.status} (${i.blueprints})`).join('\n') + (r.missing_summary.length ? `\n${r.missing_summary.length} blueprint(s) still need a summary` : '')); } catch (e) { toast(e.message); } };
|
|
202
|
+
$('#runcheck').onclick = () => go('check');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function viewCheck() {
|
|
206
|
+
const main = $('#main'); main.innerHTML = '<div class="empty">Checking…</div>';
|
|
207
|
+
const r = await api('GET', '/api/check');
|
|
208
|
+
state.flagged = new Set(r.findings.map((f) => f.path).filter(Boolean));
|
|
209
|
+
renderNav();
|
|
210
|
+
const byFile = new Map();
|
|
211
|
+
for (const f of r.findings) { const k = f.path || '(root)'; if (!byFile.has(k)) byFile.set(k, []); byFile.get(k).push(f); }
|
|
212
|
+
const isBp = (k) => state.root.blueprints.some((b) => b.path === k);
|
|
213
|
+
main.innerHTML = `<h1 class="title">Check <span class="badge ${r.errors ? 'error' : r.warnings ? 'warn' : 'ok'}">${r.errors} error${r.errors === 1 ? '' : 's'}, ${r.warnings} warning${r.warnings === 1 ? '' : 's'}</span></h1>
|
|
214
|
+
<p class="meta">${r.blueprints} blueprints in ${r.collections} collections · Eidos ${esc(r.eidos_version ?? '?')} · ${esc(r.naming)} · the same report as <code>eidos check</code></p>
|
|
215
|
+
${r.findings.length === 0 ? '<p class="empty">No findings.</p>' : ''}
|
|
216
|
+
${[...byFile.entries()].map(([k, fs]) => `<div class="file-group">${isBp(k) ? `<a href="${bpHref(k)}">${esc(k)}</a>` : esc(k)}</div>${findingsHtml(fs)}`).join('')}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function viewDoc(url, title) {
|
|
220
|
+
const main = $('#main'); main.innerHTML = '<div class="empty">Loading…</div>';
|
|
221
|
+
const r = await api('GET', url);
|
|
222
|
+
main.innerHTML = `<div class="body" style="border:0">${r.html}</div>`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function viewNew(collectionName) {
|
|
226
|
+
const f = state.root.framework;
|
|
227
|
+
const c = f.collections.find((x) => x.name === collectionName) || f.collections[0];
|
|
228
|
+
const groups = (c.grouping && c.grouping.groups) ? c.grouping.groups.map((g) => g.name) : [];
|
|
229
|
+
$('#main').innerHTML = `<h1 class="title">New blueprint</h1>
|
|
230
|
+
<p class="hint">Runs <code>eidos new</code>: frontmatter from the Schema, body from the flavor's shape, filename in the naming convention. You fill the prose afterwards.</p>
|
|
231
|
+
<div class="form">
|
|
232
|
+
<label>Collection</label><select id="n-col">${f.collections.map((x) => `<option ${x.name === c.name ? 'selected' : ''}>${esc(x.name)}</option>`).join('')}</select>
|
|
233
|
+
<label>Title</label><input id="n-title" placeholder="Session Management" autofocus>
|
|
234
|
+
<label>Flavor</label><select id="n-flavor">${c.flavors.map((x) => `<option ${x.default ? 'selected' : ''}>${esc(x.name)}</option>`).join('')}</select>
|
|
235
|
+
<label>Group</label><input id="n-group" list="n-groups" placeholder="${groups.length ? 'one of the declared groups, or a new one' : c.grouping ? c.grouping.label : 'optional sub-folder'}"><datalist id="n-groups">${groups.map((g) => `<option value="${esc(g)}">`).join('')}</datalist>
|
|
236
|
+
<label>Summary</label><input id="n-summary" placeholder="One plain line: what this blueprint is.">
|
|
237
|
+
<span></span><div class="toolbar"><button class="primary" id="n-create">Create</button><button id="n-cancel">Cancel</button></div>
|
|
238
|
+
</div>`;
|
|
239
|
+
$('#n-col').onchange = () => viewNew($('#n-col').value);
|
|
240
|
+
$('#n-cancel').onclick = () => history.back();
|
|
241
|
+
$('#n-create').onclick = async () => {
|
|
242
|
+
try {
|
|
243
|
+
const r = await api('POST', '/api/blueprints', { collection: $('#n-col').value, title: $('#n-title').value, flavor: $('#n-flavor').value, group: $('#n-group').value, summary: $('#n-summary').value });
|
|
244
|
+
toast(`Created ${r.path}` + (r.notes.length ? '\n' + r.notes.join('\n') : ''), 6000);
|
|
245
|
+
await loadRoot(); go('blueprint/' + r.path.split('/').map(encodeURIComponent).join('/'));
|
|
246
|
+
} catch (e) { toast(e.message, 6000); }
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function render() {
|
|
251
|
+
const { view, arg } = route();
|
|
252
|
+
renderNav();
|
|
253
|
+
if (view === 'blueprint') return viewBlueprint(arg);
|
|
254
|
+
if (view === 'check') return viewCheck();
|
|
255
|
+
if (view === 'standard') return viewDoc('/api/standard');
|
|
256
|
+
if (view === 'instructions') return viewDoc('/api/instructions/' + (arg || 'overview'));
|
|
257
|
+
if (view === 'new') return viewNew(decodeURIComponent(arg));
|
|
258
|
+
return viewFramework();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
$('#search').addEventListener('input', (e) => { state.filter = e.target.value; renderNav(); });
|
|
262
|
+
$('#nav').addEventListener('click', (e) => { const b = e.target.closest('button[data-new]'); if (b) go('new/' + encodeURIComponent(b.dataset.new)); });
|
|
263
|
+
window.addEventListener('hashchange', render);
|
|
264
|
+
loadRoot().then(render).catch((e) => { $('#main').innerHTML = `<div class="empty">${esc(e.message)}</div>`; });
|
|
265
|
+
})();
|
|
266
|
+
</script>
|
|
267
|
+
</body>
|
|
268
|
+
</html>
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// `eidos agents`: the short nudge that points an agent at `eidos instructions`,
|
|
2
|
+
// printed, or written into an instruction file such as AGENTS.md or CLAUDE.md.
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { EXIT_OK, print } from '../output.js';
|
|
6
|
+
export const NUDGE_START = '<!-- eidos:instructions:start -->';
|
|
7
|
+
export const NUDGE_END = '<!-- eidos:instructions:end -->';
|
|
8
|
+
export function nudge() {
|
|
9
|
+
return [
|
|
10
|
+
NUDGE_START,
|
|
11
|
+
'## Eidos',
|
|
12
|
+
'',
|
|
13
|
+
'This repository keeps its blueprints in an Eidos root. Before creating, editing, reviewing, or discussing a blueprint or the framework, run `eidos instructions overview` and follow it; it names the guide to read for each kind of work. Use the `eidos` CLI for structure (`eidos new`, `eidos check`, `eidos index`); never hand-assemble frontmatter or edit an `index.md`.',
|
|
14
|
+
NUDGE_END,
|
|
15
|
+
].join('\n');
|
|
16
|
+
}
|
|
17
|
+
export function runAgents(options, cwd = process.cwd()) {
|
|
18
|
+
const block = nudge();
|
|
19
|
+
if (!options.write) {
|
|
20
|
+
print(block, '', `write it: eidos agents --write [--file ${options.file}]`);
|
|
21
|
+
return EXIT_OK;
|
|
22
|
+
}
|
|
23
|
+
const file = path.resolve(cwd, options.file);
|
|
24
|
+
const rel = path.relative(cwd, file) || options.file;
|
|
25
|
+
if (!existsSync(file)) {
|
|
26
|
+
writeFileSync(file, block + '\n', 'utf8');
|
|
27
|
+
print(`✓ created ${rel} with the Eidos nudge`);
|
|
28
|
+
return EXIT_OK;
|
|
29
|
+
}
|
|
30
|
+
const current = readFileSync(file, 'utf8');
|
|
31
|
+
const start = current.indexOf(NUDGE_START);
|
|
32
|
+
const end = current.indexOf(NUDGE_END);
|
|
33
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
34
|
+
const updated = current.slice(0, start) + block + current.slice(end + NUDGE_END.length);
|
|
35
|
+
if (updated === current) {
|
|
36
|
+
print(`✓ ${rel} already carries the current nudge`);
|
|
37
|
+
return EXIT_OK;
|
|
38
|
+
}
|
|
39
|
+
writeFileSync(file, updated, 'utf8');
|
|
40
|
+
print(`✓ refreshed the Eidos nudge in ${rel}`);
|
|
41
|
+
return EXIT_OK;
|
|
42
|
+
}
|
|
43
|
+
const separator = current.endsWith('\n') ? (current.endsWith('\n\n') ? '' : '\n') : '\n\n';
|
|
44
|
+
writeFileSync(file, current + separator + block + '\n', 'utf8');
|
|
45
|
+
print(`✓ appended the Eidos nudge to ${rel}`);
|
|
46
|
+
return EXIT_OK;
|
|
47
|
+
}
|