@neocompose/cli 0.1.3 → 0.1.5
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 +7 -2
- package/dist/neo.mjs +10814 -3929
- package/package.json +2 -2
- package/skills/neocompose-cli/SKILL.md +214 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neocompose/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Neo Compose schema-as-code CLI: a C#-flavored working copy of your project schema, bidirectionally synced with Neo Compose.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"README.md",
|
|
13
|
-
"
|
|
13
|
+
"skills"
|
|
14
14
|
],
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: neocompose-cli
|
|
3
|
+
description: >-
|
|
4
|
+
Edit a Neo Compose project's schema as C# files and sync bidirectionally
|
|
5
|
+
with the server using the `neo` CLI. Use when asked to add/change/remove
|
|
6
|
+
custom types, attributes, or enums in a Neo Compose project, to validate or
|
|
7
|
+
evaluate NeoScript, or to batch-edit values and localized strings. The
|
|
8
|
+
working copy lives in a `neo/` directory (neo.json + Attributes/ + Enums/);
|
|
9
|
+
commands are run via `node cli/bin/neo.mjs` (repo) or `npm run neo`.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Neo Compose schema-as-code CLI (`neo`)
|
|
13
|
+
|
|
14
|
+
The `neo/` working copy is a **git-like checkout** of a Neo Compose project
|
|
15
|
+
version's schema: custom types and their attributes as one C# class per file
|
|
16
|
+
under `Attributes/`, enums under `Enums/`. The server is the source of truth;
|
|
17
|
+
files are a peer editor alongside the web UI. Spec:
|
|
18
|
+
`specs/schema-as-code-cli.md`.
|
|
19
|
+
|
|
20
|
+
In Unity projects, `neo.json` may set `unityConfigPath` (relative path to the
|
|
21
|
+
game's `NeoComposeConfig.asset`) instead of `projectId`/`versionId` — the
|
|
22
|
+
asset is then the single source of truth for both ids, and version-changing
|
|
23
|
+
commands (`neo branch switch`, …) update the asset so the game always targets
|
|
24
|
+
the same version as the working copy. Never re-add the ids to `neo.json` in
|
|
25
|
+
that mode; the CLI rejects the duplication.
|
|
26
|
+
|
|
27
|
+
## Core discipline
|
|
28
|
+
|
|
29
|
+
1. `neo pull` before editing (and before any push after time has passed).
|
|
30
|
+
2. Edit the C# files **within the constrained subset** (see below).
|
|
31
|
+
3. `neo status` / `neo diff` to review, `neo push --dry-run` to preview.
|
|
32
|
+
4. `neo push` to commit — atomically, with compare-and-swap base hashes.
|
|
33
|
+
5. **Pull → push with no edits is always a no-op.** If `neo status` reports
|
|
34
|
+
changes right after a pull, that is a bug — report it, don't push.
|
|
35
|
+
|
|
36
|
+
The CLI never guesses: anything outside the subset is a file:line:column
|
|
37
|
+
error. Conflict markers in a file make it unparseable until resolved.
|
|
38
|
+
|
|
39
|
+
## The C# subset
|
|
40
|
+
|
|
41
|
+
- One top-level `class` (custom type) or `enum` per file.
|
|
42
|
+
- Properties carry exactly one `Neo*` attribute: `NeoBool`, `NeoInt`
|
|
43
|
+
(`Min`/`Max`), `NeoFloat` (`+DecimalPoints`), `NeoString`
|
|
44
|
+
(`Localizable`/`SearchKey`),
|
|
45
|
+
`NeoDictionary`/`NeoList` (entry chains), `NeoObject` (custom type),
|
|
46
|
+
`NeoEnum`, `NeoLookup` (`CollectionId`), `NeoProperty`
|
|
47
|
+
(`Code`, optional `SetterCode`, `RetJson`),
|
|
48
|
+
`NeoSprite`/`NeoAudio` (`TemplateId`), `NeoFunction`, and
|
|
49
|
+
`NeoNSFunction` (`Code`, `RetJson`, `ArgsJson`, `Deferred`).
|
|
50
|
+
- The positional string argument is the stable record id. **Omit it to
|
|
51
|
+
create**: `[NeoFloat(Min = 0)] public float? Weight { get; init; }` — push
|
|
52
|
+
assigns the id and rewrites the file canonically.
|
|
53
|
+
- Property nullability IS the `required` flag: `int?` optional, `int`
|
|
54
|
+
required. Property name is the schema key (`Key`/`Name` args override).
|
|
55
|
+
- Enum members: the member name is the option key (codegen symbol);
|
|
56
|
+
`[NeoEnumEntry(Text = "...")]` carries the display/localized-text id.
|
|
57
|
+
- `ExtraJson` holds fields the projection doesn't express — edit it as JSON,
|
|
58
|
+
never delete it casually.
|
|
59
|
+
- No method bodies, no initializers, no extra `using`s.
|
|
60
|
+
|
|
61
|
+
## Conflicts
|
|
62
|
+
|
|
63
|
+
Concurrent edits produce git-style `<<<<<<< local / ======= / >>>>>>> server`
|
|
64
|
+
markers at member granularity. **Edit the file to the desired final state and
|
|
65
|
+
push — the push IS the resolution** (it supersedes both sides). `neo resolve
|
|
66
|
+
--mine|--theirs` keeps one side wholesale. Markers break C# compilation on
|
|
67
|
+
purpose.
|
|
68
|
+
|
|
69
|
+
If push reports `base-hash-conflict`: run `neo pull` (merges or writes
|
|
70
|
+
markers), resolve, push again. Never bypass with `--force`-style flags.
|
|
71
|
+
|
|
72
|
+
If push reports `version-bump-required`: the server classified the change
|
|
73
|
+
(e.g. `requiredBump: major` for schema changes). Re-run with
|
|
74
|
+
`neo push --accept-bump` if the bump is intended.
|
|
75
|
+
|
|
76
|
+
## NeoScript
|
|
77
|
+
|
|
78
|
+
NeoScript is C#-flavored: typed declarations (`string x = ...;`, NOT `var`),
|
|
79
|
+
`this` = the containing type instance, `root.Assets` / `root.Save` /
|
|
80
|
+
`root.Session` roots. Validate before storing in a `NeoProperty` `Code` or
|
|
81
|
+
`SetterCode` arg, or invoke a stored typed NeoScript Function:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
neo script check --this Outpost --returns string 'return $"{this.Name}!";'
|
|
85
|
+
neo script check --mode setter --attribute ComputedName 'root.Session.Name = value;'
|
|
86
|
+
neo script check --mode nsfunction --attribute Outpost.RefreshUnlock 'return this.Level > 0;'
|
|
87
|
+
neo script compile --mode nsfunction --attribute Outpost.RefreshUnlock 'return this.Level > 0;'
|
|
88
|
+
neo script check --all # recompile every stored NeoScript body
|
|
89
|
+
neo script eval --returns string 'return root.Assets.Outposts[0].FullDisplayText;'
|
|
90
|
+
neo script apply --mode action '...' # prints write intents; commits nothing
|
|
91
|
+
neo script eval --function Outpost.RefreshUnlock --this-value <id> --args '[3]'
|
|
92
|
+
neo script apply --function Outpost.RefreshUnlock --this-value <id> --args '[3]'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`eval` runs against authored values with the same evaluator the web UI uses.
|
|
96
|
+
NSFunction `apply` is also preview-only: it reports the return value, write
|
|
97
|
+
intents, and created Session rows but commits nothing. Purely NeoScript
|
|
98
|
+
deferred paths may complete inline; native suspension reports unsupported.
|
|
99
|
+
`--json` everywhere for machine-readable output.
|
|
100
|
+
|
|
101
|
+
## Content (not in files)
|
|
102
|
+
|
|
103
|
+
Values, dialogue, and localized text are CLI verbs, not files:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
neo records query [--kind <recordKind>] # every record head in the version
|
|
107
|
+
neo records get <kind> <id>
|
|
108
|
+
neo values list [attributeId] / get <valueId> / set <valueId> '<json>'
|
|
109
|
+
neo loc locales / list / set <textId> <locale> "text"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Every `set` verb accepts a JSON-array batch on stdin or `--file` (e.g.
|
|
113
|
+
`[{"textId":"...","locale":"de-DE","value":"..."}]`) — use batches when
|
|
114
|
+
editing many records.
|
|
115
|
+
|
|
116
|
+
## Branches, merges, releases, migrations
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
neo branch list / create <name> [--from <ref>] / switch <nameOrId>
|
|
120
|
+
neo merge <branch> [--dry-run] # field-level 3-way; conflicts reject with per-record+field payload
|
|
121
|
+
neo release cut [--bump ...] [--dry-run] # bump floor DERIVED from transaction history; raise-only
|
|
122
|
+
neo migrate new <name> --target <Type> # creates Migrations/NNNN-name.neo
|
|
123
|
+
neo migrate list / check / run [--dry-run] [--skip-invalid]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Branches are copy-on-write forks (cheap; edits isolated until merged).
|
|
127
|
+
Landing a branch into its fork parent auto-archives it (refreshes don't);
|
|
128
|
+
archived versions hide under the picker's "Archived" submenu and behind
|
|
129
|
+
`neo branch restore <name>`.
|
|
130
|
+
Migrations are NeoScript actions in `.neo` files: `/// @migration <id>`,
|
|
131
|
+
`/// @target <Type|project>` headers, then the action body; `this` = each
|
|
132
|
+
instance of the target type. The v1 runner applies `this.<Field> = ...`
|
|
133
|
+
assignments as one atomic CAS transaction and marks `appliedAt` per version
|
|
134
|
+
(idempotent, per-branch). Sparse instances abort unless `--skip-invalid`.
|
|
135
|
+
|
|
136
|
+
## Setup (once per machine/repo)
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
npm i -g @neocompose/cli # or run from the web repo: node cli/bin/neo.mjs
|
|
140
|
+
neo login [--api <url>] [--profile editor|release] [--save-project <id>]
|
|
141
|
+
neo init --project <id> [--version <id>] [--dir neo]
|
|
142
|
+
neo dev [--push] # live: auto-pull on server change; 'p' push, 's' status
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Agents run non-interactively: always pass explicit flags/ids. (In a human
|
|
146
|
+
terminal the same commands prompt with pickers and confirms; with no TTY the
|
|
147
|
+
CLI never blocks on a prompt — missing arguments raise an error that names
|
|
148
|
+
the flag to pass, and confirms fall back to the pre-prompt behavior.)
|
|
149
|
+
|
|
150
|
+
Tokens land in the macOS Keychain when available (0600 file elsewhere;
|
|
151
|
+
`NEO_COMPOSE_TOKEN` / `--token-stdin` for CI). `--save-project` adds a
|
|
152
|
+
narrow per-project save-read scope for `script eval --save`.
|
|
153
|
+
|
|
154
|
+
## Dialogue validation & batch authoring
|
|
155
|
+
|
|
156
|
+
- **ALWAYS run `neo dialogue dryrun [ref]` after authoring or editing
|
|
157
|
+
dialogues.** It walks every option path from a fresh save, executes every
|
|
158
|
+
action/condition through the real evaluator, and applies the Unity
|
|
159
|
+
runtime's mutation-ownership rules (save/session clone-on-write,
|
|
160
|
+
lookup-resolution). Exit 1 = the runtime would throw on device. Known
|
|
161
|
+
trap it catches: `root.Save.Inventory.Add(...)` — collection mutations
|
|
162
|
+
THROUGH Lookup attributes resolve into the referenced asset collection
|
|
163
|
+
and always fail at runtime.
|
|
164
|
+
- `neo dialogue apply <spec.json> [--dry-run]` authors a whole dialogue
|
|
165
|
+
from one declarative file: nodes with `say`/`options`/`do` (NeoScript
|
|
166
|
+
`code` compiled server-side, `bind` overrides the action's `this`),
|
|
167
|
+
`group`/`start`/`linkedValueId`; strings minted in one transaction.
|
|
168
|
+
`neo dialogue export <ref>` round-trips an existing dialogue into the
|
|
169
|
+
same shape (actions surface compiled `ir`). Workflow: export → edit →
|
|
170
|
+
apply → dryrun.
|
|
171
|
+
- Cross-outpost writes need authored OutpostSaveMap entries for every
|
|
172
|
+
outpost (fresh saves seed from authored values) — `values add-entry` on
|
|
173
|
+
the save-map dict with `key=<outpost value id>`.
|
|
174
|
+
|
|
175
|
+
## Content authoring (dialogues, items, strings)
|
|
176
|
+
|
|
177
|
+
- `neo dialogue create '<IDialogueBase json>'` writes a whole dialogue (node
|
|
178
|
+
graph + trigger) in one call; `edit`/`delete` manage the record. Node text
|
|
179
|
+
fields hold localized-text IDS — mint the strings afterwards with
|
|
180
|
+
`neo loc create '{"id":<textId>,"value":"...","links":[...]}'` using
|
|
181
|
+
`dialogue-node-text` / `dialogue-choice-text` links (recordKind
|
|
182
|
+
`dialogue-node`). `localizedTextEdits` on node create/edit only EDITS
|
|
183
|
+
existing texts.
|
|
184
|
+
- `neo dialogue compile '{"code":...,"target":0|1|2,"dialogue":{},"node":{"primaryLinkedValueId":...}}'`
|
|
185
|
+
returns wire-ready compiled logic (0=condition, 1=action, 2=text variable).
|
|
186
|
+
Actions bind `this` to the NODE's primaryLinkedValueId — to unlock another
|
|
187
|
+
outpost, bind the action node to THAT outpost's value id. Lambdas don't
|
|
188
|
+
compile inside actions; lookup adds accept entry ids:
|
|
189
|
+
`root.Save.Inventory.Add("<entryValueId>")`.
|
|
190
|
+
- Collection rows: `neo values add-entry <containerValueId>
|
|
191
|
+
'{"entryAttributeId":...}'` (server mints the entry tree; find the LIVE
|
|
192
|
+
container by querying which list value contains existing entries — an
|
|
193
|
+
attribute's `valueId` may be its DEFAULT container, not the instance).
|
|
194
|
+
`neo values set <leafId> '<raw json value>'` — raw value, NOT wrapped in
|
|
195
|
+
`{"value":...}`.
|
|
196
|
+
- `neo script eval --returns "string[]"` etc. accept `T[]`/`T?` suffixes;
|
|
197
|
+
`neo script compile` prints compiled getter/action IR.
|
|
198
|
+
|
|
199
|
+
## Implementation notes agents should know
|
|
200
|
+
|
|
201
|
+
- The CLI talks to **Convex directly** with typed `api.*` calls; pushes go
|
|
202
|
+
through the session-gated `commitFromSession` (same scope gates + CAS as
|
|
203
|
+
the web). `--json` is available on status/diff/branch list/migrate
|
|
204
|
+
list/script/records/values/loc.
|
|
205
|
+
- The working copy also projects `Templates/*.cs`, `Localization.cs`, and
|
|
206
|
+
`Migrations/*.neo`. Migration pushes **pin compiled IR** on the record;
|
|
207
|
+
`migrate run` replays the pinned IR (rename-proof). `migrate run --server`
|
|
208
|
+
executes in the server runner; `neo merge --migrate` chains migrations at
|
|
209
|
+
merge, and `release cut` refuses while any migration is pending.
|
|
210
|
+
- Pull short-circuits on the sync-signal head ("Already up to date") when
|
|
211
|
+
nothing changed server-side and the copy is clean.
|
|
212
|
+
|
|
213
|
+
The editor profile cannot publish releases; that requires `--profile release`
|
|
214
|
+
at login (server-enforced scopes — ZERO TRUST).
|