@neuralis/brain-core 0.2.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 +8 -3
- package/app/README.md +1 -1
- package/app/filesystem/README.md +3 -3
- package/app/filesystem/api/README.md +4 -4
- package/app/filesystem/api/index.ts +144 -139
- package/app/filesystem/domain/types.ts +12 -0
- package/app/filesystem/hooks/README.md +5 -5
- package/app/filesystem/hooks/useDiscoverableSources.ts +4 -2
- package/app/filesystem/hooks/useFilesystemSSE.ts +31 -23
- package/app/filesystem/hooks/useGitChangesInventory.ts +4 -2
- package/app/filesystem/host/README.md +3 -2
- package/app/filesystem/host/installFilesystemHostComponents.tsx +22 -9
- package/app/filesystem/store/README.md +12 -8
- package/app/filesystem/store/filesystemStore.ts +444 -163
- package/app/filesystem/store/gitSlice.ts +6 -6
- package/app/filesystem/store/listSlice.ts +9 -6
- package/app/filesystem/store/searchSlice.ts +3 -3
- package/app/filesystem/ui/FileEditor.tsx +90 -39
- package/app/filesystem/ui/FileTree.tsx +10 -9
- package/app/filesystem/ui/GitCommitChangesView.tsx +5 -3
- package/app/filesystem/ui/GitCommitGraph.tsx +6 -5
- package/app/filesystem/ui/GitControlPanel.tsx +14 -14
- package/app/filesystem/ui/GitDiffView.tsx +14 -6
- package/app/filesystem/ui/GitFileDiff.tsx +22 -15
- package/app/filesystem/ui/PendingChangesPanel.tsx +108 -44
- package/app/filesystem/ui/PendingReviewDialog.tsx +8 -5
- package/app/filesystem/ui/README.md +9 -9
- package/app/filesystem/ui/ReembedDialog.tsx +4 -2
- package/app/filesystem/ui/SourcesPanel.tsx +26 -28
- package/app/filesystem/ui/Toolbar.tsx +5 -4
- package/app/filesystem/ui/VectorHealthPanel.tsx +48 -17
- package/dist/app/host.js +46 -46
- package/dist/src/config/env.d.ts +4 -0
- package/dist/src/config/env.d.ts.map +1 -1
- package/dist/src/config/env.js +13 -0
- package/dist/src/config/env.js.map +1 -1
- package/dist/src/connectors/LocalDiskConnector.d.ts +9 -1
- package/dist/src/connectors/LocalDiskConnector.d.ts.map +1 -1
- package/dist/src/connectors/LocalDiskConnector.js +51 -2
- package/dist/src/connectors/LocalDiskConnector.js.map +1 -1
- package/dist/src/domain/healthTypes.d.ts +1 -7
- package/dist/src/domain/healthTypes.d.ts.map +1 -1
- package/dist/src/domain/pendingChange.d.ts +31 -0
- package/dist/src/domain/pendingChange.d.ts.map +1 -1
- package/dist/src/domain/pendingChange.js.map +1 -1
- package/dist/src/routes/health.js +2 -3
- package/dist/src/routes/health.js.map +1 -1
- package/dist/src/routes/pending.d.ts +7 -7
- package/dist/src/routes/pending.d.ts.map +1 -1
- package/dist/src/routes/pending.js +21 -21
- package/dist/src/routes/pending.js.map +1 -1
- package/dist/src/services/FilesystemService.d.ts +13 -61
- package/dist/src/services/FilesystemService.d.ts.map +1 -1
- package/dist/src/services/FilesystemService.js +47 -189
- package/dist/src/services/FilesystemService.js.map +1 -1
- package/dist/src/services/FilesystemUiService.d.ts +33 -1
- package/dist/src/services/FilesystemUiService.d.ts.map +1 -1
- package/dist/src/services/FilesystemUiService.js +50 -1
- package/dist/src/services/FilesystemUiService.js.map +1 -1
- package/dist/src/store/PendingChangeStore.d.ts +3 -0
- package/dist/src/store/PendingChangeStore.d.ts.map +1 -1
- package/dist/src/store/PendingChangeStore.js +84 -25
- package/dist/src/store/PendingChangeStore.js.map +1 -1
- package/dist/src/usecases/orphanedPending.d.ts +2 -2
- package/dist/src/usecases/orphanedPending.d.ts.map +1 -1
- package/dist/src/usecases/orphanedPending.js +5 -3
- package/dist/src/usecases/orphanedPending.js.map +1 -1
- package/dist/src/usecases/pendingListing.d.ts +34 -0
- package/dist/src/usecases/pendingListing.d.ts.map +1 -0
- package/dist/src/usecases/pendingListing.js +244 -0
- package/dist/src/usecases/pendingListing.js.map +1 -0
- package/dist/src/usecases/vectorMaintenance.d.ts +6 -9
- package/dist/src/usecases/vectorMaintenance.d.ts.map +1 -1
- package/dist/src/usecases/vectorMaintenance.js +4 -13
- package/dist/src/usecases/vectorMaintenance.js.map +1 -1
- package/dist/src/vector/InMemoryVectorIndex.d.ts.map +1 -1
- package/dist/src/vector/InMemoryVectorIndex.js +30 -9
- package/dist/src/vector/InMemoryVectorIndex.js.map +1 -1
- package/package.json +27 -5
- package/rules/brain-rules.md +1 -1
- package/skills/create-package/SKILL.md +3 -3
- package/skills/create-package/references/config-and-credentials.md +1 -1
- package/skills/create-package/references/manifest-and-discovery.md +1 -1
- package/skills/create-package/references/runtime-and-build.md +1 -1
- package/skills/create-package/references/skills-and-context.md +25 -12
- package/skills/create-package/references/surfaces-and-design.md +7 -5
- package/skills/create-package/references/test-and-ship.md +9 -16
- package/skills/create-package/scripts/check-package.sh +47 -1
- package/skills/create-package/scripts/verify-loaded.sh +18 -27
- package/skills/git/SKILL.md +27 -29
- package/skills/manage-files/SKILL.md +37 -32
- package/skills/manage-files/scripts/upload.sh +7 -9
- package/skills/manage-files/scripts/write.sh +2 -6
- package/skills/manage-sources/SKILL.md +93 -95
- package/skills/manage-sources/scripts/reembed.sh +2 -6
- package/skills/manage-uri-policy/SKILL.md +30 -58
- package/app/filesystem/hooks/useContainerWidth.ts +0 -39
- package/skills/git/scripts/git.sh +0 -48
- package/skills/manage-files/scripts/approve.sh +0 -9
- package/skills/manage-files/scripts/list.sh +0 -9
- package/skills/manage-files/scripts/pending.sh +0 -23
- package/skills/manage-files/scripts/raw.sh +0 -11
- package/skills/manage-files/scripts/read.sh +0 -9
- package/skills/manage-files/scripts/search.sh +0 -15
- package/skills/manage-sources/scripts/browse.sh +0 -12
- package/skills/manage-sources/scripts/cancel.sh +0 -14
- package/skills/manage-sources/scripts/create.sh +0 -13
- package/skills/manage-sources/scripts/delete.sh +0 -17
- package/skills/manage-sources/scripts/discoverable.sh +0 -6
- package/skills/manage-sources/scripts/get.sh +0 -7
- package/skills/manage-sources/scripts/kind-schema.sh +0 -7
- package/skills/manage-sources/scripts/kinds.sh +0 -6
- package/skills/manage-sources/scripts/list.sh +0 -6
- package/skills/manage-sources/scripts/purge-index.sh +0 -18
- package/skills/manage-sources/scripts/reindex.sh +0 -13
- package/skills/manage-sources/scripts/reset.sh +0 -11
- package/skills/manage-sources/scripts/set-enabled.sh +0 -17
- package/skills/manage-sources/scripts/sync-status.sh +0 -14
- package/skills/manage-sources/scripts/sync.sh +0 -13
- package/skills/manage-sources/scripts/update.sh +0 -18
- package/skills/manage-uri-policy/scripts/manifests.sh +0 -20
- package/skills/manage-uri-policy/scripts/set-uri.sh +0 -66
- package/skills/manage-uri-policy/scripts/show-uri.sh +0 -23
|
@@ -15,7 +15,7 @@ Every surface lives in `package.json` under `neuralis.app.surfaces[]` — a unio
|
|
|
15
15
|
| kind | required | useful optional |
|
|
16
16
|
|---|---|---|
|
|
17
17
|
| `widget` | `kind`, `id`, `type`, `title`, `component` | `icon`, `layout`, `chrome`, `open`, `defaultOpen`, `requires` |
|
|
18
|
-
| `dock` | `kind`, `id`, `label`, `action` | `icon`, `position`, `section`, `dock`, `tooltip`, `color` |
|
|
18
|
+
| `dock` | `kind`, `id`, `label`, `action` | `icon`, `image`, `position`, `section`, `dock`, `tooltip`, `color` |
|
|
19
19
|
| `card` | `kind`, `id`, `type` | `title`, `render`, `match`, `bridge` |
|
|
20
20
|
|
|
21
21
|
**Every icon comes from ONE platform library** — about 270 curated lucide names in categories, the `ICON_LIBRARY` export of `@neuralis/package-system/icons`. Write the PascalCase name (`ChartLine`) or its lucide-kebab form (`chart-line`); a few aliases (`layout`, `terminal`, `admin`) also resolve. The same rule covers a surface `icon`, a team member's `appearance.icon` and a workflow template's `icon`. Any other name renders the fallback glyph, warns at load, and is an ERROR in `check-package.sh`.
|
|
@@ -68,6 +68,8 @@ The smallest complete, **reachable** UI a project-dropped package can ship. Four
|
|
|
68
68
|
|
|
69
69
|
**Gate the widget, not the dock entry** — dock visibility inherits the target widget's `requires.features` automatically. Duplicating `requires` only creates drift: a user holding the widget's features but not your accidentally-different dock feature gets a widget they can never open.
|
|
70
70
|
|
|
71
|
+
**An app picture is optional: `"image": "app/shared/<name>.png"`.** A PNG, JPEG or WebP under `app/shared/`, at most 256 KiB, each path segment letters, digits, `.`, `_` or `-`. The dock, the app menu and the inbox show it in a fixed-size box to whoever can see the dock entry; keep the `icon` glyph — it renders until the picture arrives and whenever it cannot be shown. The platform judges the BYTES, never the extension: an SVG, a renamed document or an oversized file is not served and the glyph stays. An invalid path (absolute, remote, outside `app/shared/`, `..`, a non-raster extension) is a validation error that fails the whole package. `check-package.sh` checks the path, the file and its bytes.
|
|
72
|
+
|
|
71
73
|
## Task: pick a renderer
|
|
72
74
|
|
|
73
75
|
**Two renderers exist, and both render.** There is no third option and no placeholder tier: `inline-html` and `wasm-ui` were removed from the contract, and a manifest still declaring one is rejected outright — *renderer 'inline-html' was removed; ship the HTML asset and declare renderer 'iframe' with a relative url*. A leftover `fallback` key is inert and warned about. (WASM remains the **tool/route** sandbox runtime; there has never been a WASM UI engine, and one can no longer be declared.)
|
|
@@ -117,7 +119,7 @@ Five things to know before you use it:
|
|
|
117
119
|
- **Budgets: 64 distinct paths and 8 MiB per SCOPE**, and the lane is `no-store`, so every render refetches the whole set. The preflight errors past both numbers and warns past 1 MiB — but it counts only your SURFACE tree, while the runtime budget also charges every `app/shared/` file your entry pulls in, so a shared-heavy bundle can pass authoring and still be refused at request time. The per-file 2 MiB cap applies in both modes.
|
|
118
120
|
- **Still no remote origins.** The CSP names none, so a CDN script or a Google font is blocked before the request leaves. Ship the file.
|
|
119
121
|
|
|
120
|
-
`app/shared/` is a declared **package-public** namespace: any visible surface of the package may read it, so nothing feature-private belongs there.
|
|
122
|
+
`app/shared/` is a declared **package-public** namespace: any visible surface of the package may read it, and a declared dock picture is served from it, so nothing feature-private belongs there.
|
|
121
123
|
|
|
122
124
|
## The silent failures — symptom first
|
|
123
125
|
|
|
@@ -167,7 +169,7 @@ Asset serving is authenticated per request, scope-bound, traversal-safe, `Cache-
|
|
|
167
169
|
|
|
168
170
|
*Symptom of omitting `color-scheme` or an explicit height:* the frame collapses to the HTML default 150px, and the browser paints an opaque **white** backdrop behind a cross-origin frame whose colour scheme differs from the dark workspace. Declare `color-scheme: dark light` at **every** nesting level — outer element and embedded document both.
|
|
169
171
|
|
|
170
|
-
Host CSS custom properties (`--color-text`, `--w-bg-rgb`, `--icon-accent`, `--control-surface`, …) reach `direct`-rendered components only; there, never hardcode a hex —
|
|
172
|
+
Host CSS custom properties (`--color-text`, `--w-bg-rgb`, `--icon-accent`, `--control-surface`, …) reach `direct`-rendered components only; there, never hardcode a hex — four themes ship, independently selected for widgets, docks and background — and `--w-bg-rgb` is comma-separated channels: `rgba(var(--w-bg-rgb), 0.55)`. Every theme is dark polarity (white text), so design once for the dark design.
|
|
171
173
|
|
|
172
174
|
### Design floor
|
|
173
175
|
|
|
@@ -183,7 +185,7 @@ Host CSS custom properties (`--color-text`, `--w-bg-rgb`, `--icon-accent`, `--co
|
|
|
183
185
|
|
|
184
186
|
`chrome.mode` is `toolbar` (default), `frameless` or `floating`. All three render the **same** host control strip, differing only in placement and visibility. `toolbar`/`floating` wrap content with padding and automatic scroll; **`frameless` clips content with no padding and no scrollbar** — the widget root owns its scroll and padding and must reserve roughly 4rem at the top right. *Symptom of ignoring it:* your top-right controls sit under the hover-revealed host strip and become unclickable, and content is cut off with no way to scroll. Iframe widgets usually want `frameless`.
|
|
185
187
|
|
|
186
|
-
**The close (X) button minimises; it does not destroy.** The widget stays mounted off-screen and survives a refresh. *Symptom:* reopening shows it exactly as it was, "fresh start" initialisation never runs, and teardown in an unmount handler never fires. True destroy is the dock item's
|
|
188
|
+
**The close (X) button minimises; it does not destroy.** The widget stays mounted off-screen and survives a refresh. *Symptom:* reopening shows it exactly as it was, "fresh start" initialisation never runs, and teardown in an unmount handler never fires. True destroy is the dock item's clean (broom) control. **Exception for `renderer: "iframe"` widgets:** only the React subtree stays mounted — the frame itself is detached from the document while minimised (and on a stage-mode switch), so the browser discards its browsing context and the frame reloads from the same URL with everything inside it gone. *Symptom:* your in-frame bootstrap DOES re-run and any in-frame in-memory state is lost. Make an iframe widget's bootstrap re-runnable and persist state server-side or in the frame's own storage.
|
|
187
189
|
|
|
188
190
|
Transparency resolves per-instance override → manifest `chrome.transparent` → the user's Layout default → opaque; the cycle control is hidden when `chrome.userTransparency: false` or the package is untrusted. **The level is a user setting your surface cannot read, and it changes the panel's CSS containment**: the panel emits a `backdrop-filter` at `dim` and `opaque` (not at `transparent`), so at those two levels it is a containing block for `position: fixed` descendants. Design so the level never matters — portal every `fixed` overlay to `document.body` (see the Design floor above). The panel's stacking context does not vary: it is isolated at every level.
|
|
189
191
|
|
|
@@ -274,7 +276,7 @@ registerCardComponent('thing.result', ThingCard, {
|
|
|
274
276
|
|
|
275
277
|
`component.import` resolves through a client-side host registry the package populates from its runtime UI module: declare `app.module`, build `app/host.tsx` with `neuralis-build ui`, and the host imports the module and calls each `install…HostComponents({ registry, port })` export with a registry that accepts only your own package's registrations. Every `component.import` your manifest declares must be registered somewhere under `app/`, or the surface resolves to null and shows the placeholder — no error. A module built before a platform upgrade can be refused (its record names an older host API or no React major): rebuild it with `neuralis-build ui`.
|
|
276
278
|
|
|
277
|
-
The component receives exactly `{ agentId, widgetId, state }
|
|
279
|
+
The component receives exactly `{ agentId, widgetId, state }` — `agentId` is the window's BOUND agent (fixed for that window, never the workspace's active one) and `widgetId` names the window; everything else comes from the workspace host port captured at install time — project/user ids, agents, `openWidget`, `updateWidgetState`, `getGrantedFeatures`, `useVisibleSkills`, and `subscribeRealtime({ channel, projectId, agentId?, onEvent })` over `runtime | presence | workflow | filesystem | conversation | project` (plus the optional `isRealtimeConnected()` for that connection's current state — treat its absence as "may be disconnected"). A widget can be open in SEVERAL windows (unless its manifest sets `open.singleton: true`): keep one client store PER WINDOW, keyed on `widgetId` and the bound `agentId`, never one global store read through the active agent — two windows must never share a selection, a draft or a late response. `openWidget({ agentId, type, title, target? })` picks the window: absent or `'default'` is the most recently focused window of that type bound to that agent (opened only when none exists), `'new'` always opens another (a singleton answers as `'default'`), and `{ instanceId }` is exactly that window. Subscribe to the optional `onClientStateReset(handler)` at install time: the dock's clean (broom) control fires it at four levels — `instance` (clean ONE window, `instanceId`; the window stays open), `widget` (that window is closed; reset only on YOUR `widgetType`), `agent` and `project` — and your handler drops that scope's client state (persisted tabs included) without calling the host back or touching the server. A host without the method never resets you; assume nothing. HTTP goes through the generic package API client, which injects the project and agent headers — build it from the window's bound agent.
|
|
278
280
|
|
|
279
281
|
### Offer the skill next to the button that does the same thing
|
|
280
282
|
|
|
@@ -77,16 +77,14 @@ grouping reason and the per-category file counts in place of a status.
|
|
|
77
77
|
Where to read it without server-log access:
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
|
|
81
|
-
-H "Authorization: Bearer ${NEURALIS_SESSION_TOKEN}" \
|
|
82
|
-
"${NEURALIS_API}/api/packages/@neuralis/agent-core/packages/diagnostics"
|
|
80
|
+
neuralis request GET @neuralis/agent-core/packages/diagnostics
|
|
83
81
|
```
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
derives the project from the verified ticket, and a
|
|
87
|
-
ticket's is refused outright. Without that
|
|
88
|
-
`
|
|
89
|
-
this platform.
|
|
83
|
+
Inside a skill's shell it authenticates with the session ticket already in the environment.
|
|
84
|
+
There is **no project header**: the catch-all derives the project from the verified ticket, and a
|
|
85
|
+
project id you supply that disagrees with the ticket's is refused outright. Without that ticket (or
|
|
86
|
+
a `neuralis login` grant, or a browser cookie session) the call is a flat `401` before anything else
|
|
87
|
+
is read — caller-supplied identity is not an auth mechanism anywhere on this platform.
|
|
90
88
|
|
|
91
89
|
Entries carry `{ packageId, status, reason?, error?, trust, registered }`. The route is gated on
|
|
92
90
|
the `core.observe` feature: **a `403` here means you lack the diagnostics capability, not that the
|
|
@@ -364,19 +362,14 @@ invisible, with **no error anywhere** — indistinguishable from correctly hidde
|
|
|
364
362
|
|
|
365
363
|
- `data://` and `packages://` ship with `trigger: "auto"`, so a disk-level write is picked up by the
|
|
366
364
|
file watcher after roughly a two-second debounce. **Nothing to call.**
|
|
367
|
-
- On any other source, or one set to `manual`: **no tool syncs.** Call the route from the same shell
|
|
368
|
-
both variables are already in a skill's environment:
|
|
365
|
+
- On any other source, or one set to `manual`: **no tool syncs.** Call the route from the same shell:
|
|
369
366
|
|
|
370
367
|
```bash
|
|
371
|
-
|
|
372
|
-
-H "Authorization: Bearer ${NEURALIS_SESSION_TOKEN}" \
|
|
373
|
-
-H "Content-Type: application/json" \
|
|
374
|
-
-d '{"source":"data"}' \
|
|
375
|
-
"${NEURALIS_API}/api/packages/@neuralis/brain-core/sources/sync"
|
|
368
|
+
neuralis request POST @neuralis/brain-core/sources/sync -f source=data
|
|
376
369
|
```
|
|
377
370
|
|
|
378
371
|
It answers `202` with a job snapshot; the run is asynchronous, so poll
|
|
379
|
-
`GET
|
|
372
|
+
`neuralis request GET @neuralis/brain-core/sources/sync-status -f source=data`.
|
|
380
373
|
- **Without `drive.sync` that call returns `403 {"error":"Sync requires drive.sync."}`.** Do not retry
|
|
381
374
|
it and do not route around it — say which thing is off, in these words: *"I wrote the package files
|
|
382
375
|
to `<uri>`. Indexing them needs a sync, which requires the `drive.sync` feature my role does not
|
|
@@ -568,10 +568,56 @@ if (man) {
|
|
|
568
568
|
for (const [f, t] of toolCardTypes) {
|
|
569
569
|
if (!byType.has(t)) warn(f, `x-neuralis.ui.cardType "${t}" matches no card surface declared here — the result falls back to the generic default card. Deliberate when the type is a host/builtin card; otherwise add {"kind":"card","id":"…","type":"${t}"}`);
|
|
570
570
|
}
|
|
571
|
+
const imageDocks = docks.filter((d) => d.image !== undefined);
|
|
571
572
|
if (hasShared) {
|
|
572
573
|
warn(`${SHARED_DIR}/`, anyBundle
|
|
573
574
|
? `declared package-public: any visible surface of this package may read it, so no feature-private asset belongs there. It IS served — a bundle surface reaches it as '../shared/…' relative to the entry's own directory, so mind the depth (an entry at 'sub/index.html' needs '../../shared/…')`
|
|
574
|
-
: `declared package-public: any visible surface of this package may read it, so no feature-private asset belongs there. No surface here declares "assetMode":"bundle", so nothing serves it — a self-contained entry requests it with no session cookie. Declare bundle mode on the surface that needs it, or inline the asset`);
|
|
575
|
+
: `declared package-public: any visible surface of this package may read it, so no feature-private asset belongs there. No surface here declares "assetMode":"bundle", so ${imageDocks.length ? 'only the declared dock picture(s) are served from it' : 'nothing serves it'} — a self-contained entry requests it with no session cookie. Declare bundle mode on the surface that needs it, or inline the asset`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// dock pictures — an optional raster `image` on a dock entry. The PATH rule is
|
|
579
|
+
// the platform's own (`resolveDockImagePath`), the BYTE rule its own raster
|
|
580
|
+
// sniff and 256 KiB floor (`@neuralis/package-system/web-common`); where those
|
|
581
|
+
// do not resolve from here, the check is SKIPPED and says so.
|
|
582
|
+
if (imageDocks.length) {
|
|
583
|
+
let kernel = null, raster = null;
|
|
584
|
+
for (const from of [ROOT, process.cwd(), SCRIPT_DIR]) {
|
|
585
|
+
try {
|
|
586
|
+
kernel = require(require.resolve('@neuralis/package-system', { paths: [from] }));
|
|
587
|
+
raster = require(require.resolve('@neuralis/package-system/web-common', { paths: [from] }));
|
|
588
|
+
break;
|
|
589
|
+
} catch { kernel = null; raster = null; }
|
|
590
|
+
}
|
|
591
|
+
if (!kernel || typeof kernel.resolveDockImagePath !== 'function' || !raster || typeof raster.sniffRasterImage !== 'function') {
|
|
592
|
+
F.push({ lvl: 'SKIP', f: MF, m: `${imageDocks.length} dock image(s) NOT checked — @neuralis/package-system does not resolve from here; run the check where the platform is installed` });
|
|
593
|
+
} else {
|
|
594
|
+
let sharedReal = null;
|
|
595
|
+
try { sharedReal = fs.realpathSync(at('app', 'shared')); } catch { /* no app/shared */ }
|
|
596
|
+
for (const d of imageDocks) {
|
|
597
|
+
const L = `dock "${d.id || '(no id)'}" image`;
|
|
598
|
+
const resolved = typeof d.image === 'string' ? kernel.resolveDockImagePath(d.image) : { ok: false, error: 'not a string' };
|
|
599
|
+
if (!resolved.ok) {
|
|
600
|
+
err(MF, `${L} is not a valid picture path (${resolved.error}) — declare a relative path under '${SHARED_DIR}/' naming a .png, .jpg, .jpeg or .webp file (letters, digits, '.', '_', '-' per segment). The package does NOT load with this value`);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
let real = null;
|
|
604
|
+
try { real = fs.realpathSync(at(...resolved.path.split('/'))); } catch { /* missing */ }
|
|
605
|
+
if (!real) { err(resolved.path, `${L} is declared but not on disk — the dock shows only its glyph`); continue; }
|
|
606
|
+
if (!sharedReal || !real.startsWith(sharedReal + path.sep)) {
|
|
607
|
+
err(resolved.path, `${L} resolves outside '${SHARED_DIR}/' through a link — the platform refuses to serve it`);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const st = fs.statSync(real);
|
|
611
|
+
if (!st.isFile()) { err(resolved.path, `${L} is not a regular file`); continue; }
|
|
612
|
+
if (st.size > raster.APPEARANCE_IMAGE_MAX_BYTES) {
|
|
613
|
+
err(resolved.path, `${L} is ${st.size} bytes; a package picture is served only up to ${raster.APPEARANCE_IMAGE_MAX_BYTES} bytes (256 KiB) — downscale it, a dock tile is a few dozen pixels`);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (!raster.sniffRasterImage(new Uint8Array(fs.readFileSync(real)))) {
|
|
617
|
+
err(resolved.path, `${L} is not a PNG, JPEG or WebP picture by its BYTES — the platform sniffs the content, never the extension, and serves nothing else (an SVG or a renamed document is refused)`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
575
621
|
}
|
|
576
622
|
|
|
577
623
|
// icons — a name outside the platform icon library renders the fallback
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
# only what the platform said.
|
|
10
10
|
#
|
|
11
11
|
# WHAT IT CHECKS
|
|
12
|
-
# Reads the agent-core package diagnostics and overview surfaces as YOU
|
|
13
|
-
# the session ticket
|
|
12
|
+
# Reads the agent-core package diagnostics and overview surfaces as YOU through
|
|
13
|
+
# `neuralis request` (the session ticket inside an activated skill, your
|
|
14
|
+
# `neuralis login` on your own machine), and reports one of:
|
|
14
15
|
# loaded — registered and operational
|
|
15
16
|
# partial — declarative contributions registered, no code runtime; prints
|
|
16
17
|
# the reason (build-missing | runtime-unavailable |
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
# 1 = partial (reason printed)
|
|
53
54
|
# 2 = absent — not listed for this caller
|
|
54
55
|
# 3 = error / missing (recorded message printed)
|
|
55
|
-
# 4 = could not reach the diagnostics surface (no
|
|
56
|
+
# 4 = could not reach the diagnostics surface (no `neuralis` CLI or login, no
|
|
56
57
|
# network, HTTP failure, or the caller lacks the `core.observe`
|
|
57
58
|
# feature). NOTHING is claimed about the package in this state.
|
|
58
59
|
#
|
|
@@ -127,8 +128,8 @@ if [ -z "$PKG_ID" ]; then
|
|
|
127
128
|
echo "usage: bash verify-loaded.sh <package-id> [--raw]" >&2
|
|
128
129
|
exit 4
|
|
129
130
|
fi
|
|
130
|
-
if ! command -v
|
|
131
|
-
echo "ERROR
|
|
131
|
+
if ! command -v neuralis >/dev/null 2>&1; then
|
|
132
|
+
echo "ERROR neuralis (the CLI) not found on PATH" >&2
|
|
132
133
|
manual_fallback
|
|
133
134
|
exit 4
|
|
134
135
|
fi
|
|
@@ -137,30 +138,20 @@ if ! command -v node >/dev/null 2>&1; then
|
|
|
137
138
|
manual_fallback
|
|
138
139
|
exit 4
|
|
139
140
|
fi
|
|
140
|
-
|
|
141
|
-
echo "ERROR NEURALIS_API is not set — activate this skill first (execute action=\"skill\"); the API base and the session ticket are injected at activation" >&2
|
|
142
|
-
manual_fallback
|
|
143
|
-
exit 4
|
|
144
|
-
fi
|
|
145
|
-
if [ -z "${NEURALIS_SESSION_TOKEN:-}" ]; then
|
|
146
|
-
echo "ERROR NEURALIS_SESSION_TOKEN is not set — activate this skill first; every internal call authenticates with the per-stream session ticket" >&2
|
|
147
|
-
manual_fallback
|
|
148
|
-
exit 4
|
|
149
|
-
fi
|
|
150
|
-
|
|
151
|
-
API_BASE="${NEURALIS_API%/}"
|
|
152
|
-
BASE_ROUTE="${API_BASE}/api/packages/@neuralis/agent-core/packages"
|
|
141
|
+
BASE_ROUTE="@neuralis/agent-core/packages"
|
|
153
142
|
|
|
154
143
|
WORK="$(mktemp -d)"
|
|
155
144
|
trap 'rm -rf "$WORK"' EXIT
|
|
156
145
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
[
|
|
146
|
+
# `neuralis request` prints a 2xx body on stdout (exit 0); a non-2xx body goes to
|
|
147
|
+
# stderr with an `HTTP <code>` line (exit 1). No such line = no answer
|
|
148
|
+
# at all (no CLI login, no network) — "000". Its error text stays in request.err.
|
|
149
|
+
fetch() { # fetch <route> <outfile> -> echoes the HTTP status ("000" = no response)
|
|
150
|
+
local rc=0 code
|
|
151
|
+
neuralis request GET "$1" >"$2" 2>"$WORK/request.err" || rc=$?
|
|
152
|
+
if [ "$rc" -eq 0 ]; then echo 200; return; fi
|
|
153
|
+
code="$(sed -n '/^HTTP [0-9][0-9][0-9]$/{s/^HTTP //p;q;}' "$WORK/request.err")"
|
|
154
|
+
if [ -n "$code" ]; then awk '!d && /^HTTP [0-9][0-9][0-9]$/ {d=1; next} {print}' "$WORK/request.err" > "$2"; else code="000"; fi
|
|
164
155
|
echo "$code"
|
|
165
156
|
}
|
|
166
157
|
|
|
@@ -174,7 +165,7 @@ if [ "$DIAG_CODE" = "403" ]; then
|
|
|
174
165
|
fi
|
|
175
166
|
if [ "$DIAG_CODE" != "200" ]; then
|
|
176
167
|
echo "ERROR GET ${BASE_ROUTE}/diagnostics returned HTTP ${DIAG_CODE}" >&2
|
|
177
|
-
[ -s "$WORK/
|
|
168
|
+
[ -s "$WORK/request.err" ] && sed 's/^/ /' "$WORK/request.err" >&2
|
|
178
169
|
[ -s "$WORK/diag.json" ] && head -c 600 "$WORK/diag.json" >&2 && echo >&2
|
|
179
170
|
manual_fallback
|
|
180
171
|
exit 4
|
|
@@ -322,7 +313,7 @@ const counts = () => {
|
|
|
322
313
|
process.stdout.write(' drop — and into NO tenant at all when the package is neither\n');
|
|
323
314
|
process.stdout.write(' first-party nor owned by a resolvable project, which is silent\n');
|
|
324
315
|
process.stdout.write(' apart from one server log line. Prove the instance exists with\n');
|
|
325
|
-
process.stdout.write(' GET
|
|
316
|
+
process.stdout.write(' neuralis request GET @neuralis/agent-core/connectors; that list is\n');
|
|
326
317
|
process.stdout.write(' the platform lane plus your own project, and nothing else.\n');
|
|
327
318
|
}
|
|
328
319
|
if (overEntry.enabled === false) {
|
package/skills/git/SKILL.md
CHANGED
|
@@ -14,28 +14,32 @@ panel drives**, so an agent gets exactly the rights a human member has: gated by
|
|
|
14
14
|
before every destructive op** and **credential-safe remotes** (the push/pull token is
|
|
15
15
|
resolved server-side and never touches the agent shell).
|
|
16
16
|
|
|
17
|
-
Call them with
|
|
18
|
-
|
|
19
|
-
`
|
|
20
|
-
|
|
17
|
+
Call them with `neuralis request <GET|POST> @neuralis/brain-core/git/<route> …` — inside an
|
|
18
|
+
activated skill it authenticates with the per-stream session ticket, on your own machine with
|
|
19
|
+
`neuralis login`; never with a git credential (fetch/pull/push resolve the remote token
|
|
20
|
+
server-side). The method is part of every call line: `commit` and `branch` READ on GET and
|
|
21
|
+
WRITE on POST. This is *not* about committing to the Neuralis repo itself — it is about an
|
|
22
|
+
agent operating git on a user's source.
|
|
21
23
|
|
|
22
24
|
## Three ways to reach git — pick the plane
|
|
23
25
|
|
|
24
26
|
| Plane | Use it for | How | Guarantees |
|
|
25
27
|
|---|---|---|---|
|
|
26
|
-
| **A. Governed routes** (prefer for local ops + your own remote) | Review, stage, commit, branch/checkout, and fetch/pull/push on a git-backed source in the workspace | `
|
|
28
|
+
| **A. Governed routes** (prefer for local ops + your own remote) | Review, stage, commit, branch/checkout, and fetch/pull/push on a git-backed source in the workspace | `neuralis request <GET|POST> @neuralis/brain-core/git/<route>` (this skill) | uri-policy gated; auto-snapshot on destructive ops; remote token resolved server-side, never in the shell |
|
|
27
29
|
| **B. External GitHub MCP** | Collaboration on the hosting platform — pull requests, issues, reviews, releases | the connected GitHub MCP server's tools (if the user connected one) | authz delegated to the GitHub account + enterprise settings — Neuralis does not re-implement git ACLs |
|
|
28
30
|
| **C. Raw git CLI** | Quick local inspection, or a user's own imported git skill | `execute action="shell"` running `git …` | uri-policy + Landlock sandboxed; secrets are stripped from the env, so it **cannot push**, and there is **no server snapshot**. It also resolves the repository by walking UP from `cwd`, so it is **repository-scoped, never source-scoped** — see the `repo` parameter note below |
|
|
29
31
|
|
|
30
32
|
Default to **A** for governed local ops and for pushing/pulling your connected remote; **B**
|
|
31
33
|
for platform collaboration; **C** for a fast read or when a user's own skill already drives git.
|
|
32
|
-
Plane A is always "call the routes
|
|
34
|
+
Plane A is always "call the routes with `neuralis request`" — the first-party `git` connector shown in the
|
|
33
35
|
`<packages>` overview is metadata; there are no first-party git MCP *tools* to call.
|
|
34
36
|
|
|
35
37
|
## Routes covered
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
Path: `@neuralis/brain-core/git/<route>`. GET carries `source` (+ optional `repo`) as `-f`
|
|
40
|
+
query fields; POST carries them in the JSON body (`--input -`), and every POST body must include
|
|
41
|
+
`"source"` plus that route's own required fields (e.g. `paths[]` for `stage`, `message` for
|
|
42
|
+
`commit`) — a source-less body is a `400`. `repo` is the optional in-source
|
|
39
43
|
selector for a nested checkout when the source root is not itself the repo (omit for a
|
|
40
44
|
single-repo source).
|
|
41
45
|
|
|
@@ -58,8 +62,10 @@ with nothing to report**. The same empty answer covers a
|
|
|
58
62
|
source with no `.git` at all. (`commit`, `outgoing` and `show` are honest — they answer a typed
|
|
59
63
|
`{"ok":false,"code":"unavailable"}` — and `diff` returns an empty diff string, except when it
|
|
60
64
|
refuses a huge read with its own typed `{"ok":false,"code":"too_large"}`. Both of those typed
|
|
61
|
-
refusals arrive on a **200**, so `
|
|
62
|
-
a non-zero exit is an HTTP refusal
|
|
65
|
+
refusals arrive on a **200**, so `neuralis request` exits 0: a zero exit with `ok:false` is a
|
|
66
|
+
soft answer, a non-zero exit is an HTTP refusal whose body and `HTTP <code>` are on stderr —
|
|
67
|
+
EVERY refusal carries `error`; a write-op failure and the three remote 409s also carry
|
|
68
|
+
`ok:false` and `code`; a 403 carries `code` (`feature_denied` / `uri_policy_denied`).)
|
|
63
69
|
|
|
64
70
|
**So an empty read is never evidence of a clean tree.** Do not test it with `branch:null`: a real
|
|
65
71
|
repository on a detached HEAD, or freshly initialised with no commit yet, reports exactly that.
|
|
@@ -112,7 +118,7 @@ Notes:
|
|
|
112
118
|
`no_credential` / `no_https_remote` otherwise).
|
|
113
119
|
- Destructive routes (`discard`, `branch` delete) snapshot server-side **before** mutating and
|
|
114
120
|
return a typed `rescue` carrier. **Undo it through the governed `restore` route** — echo the
|
|
115
|
-
carrier straight back: `POST restore
|
|
121
|
+
carrier straight back: `POST restore` with `{"source":"…","rescue":{"kind":"stash","ref":"<sha>"}}`
|
|
116
122
|
for discarded content, `{"kind":"branch","ref":"<anchor>","name":"<branch>"}` for a deleted
|
|
117
123
|
branch. Same gate as any other write (`drive.write` + `write` on the root — no `exec`, no new
|
|
118
124
|
capability), and the restore itself snapshots first. Do **not** reach for raw-shell
|
|
@@ -153,21 +159,13 @@ connect the remote in that card first.
|
|
|
153
159
|
general "don't `reset --hard` / `--no-verify` / force-push unrequested" rule still applies to
|
|
154
160
|
everything not named here.
|
|
155
161
|
|
|
156
|
-
## Env vars
|
|
157
|
-
|
|
158
|
-
- `NEURALIS_API` + `NEURALIS_SESSION_TOKEN` — injected at activation; every `git.sh` call reads them.
|
|
159
|
-
- `git.sh` arguments: `<GET|POST> <route>` then, for **GET**, URL-encoded `key=value` pairs; for
|
|
160
|
-
**POST**, a single JSON body string. Every POST body must include `"source"` plus that route's
|
|
161
|
-
own required fields (e.g. `paths[]` for `stage`, `message` for `commit`) — a source-less body is
|
|
162
|
-
a `400`.
|
|
163
|
-
|
|
164
162
|
## Examples
|
|
165
163
|
|
|
166
164
|
```bash
|
|
167
165
|
# Review: what changed, then the diff of one file, then recent history
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
neuralis request GET @neuralis/brain-core/git/status -f source=my-repo
|
|
167
|
+
neuralis request GET @neuralis/brain-core/git/diff -f source=my-repo -f path=src/app.ts
|
|
168
|
+
neuralis request GET @neuralis/brain-core/git/log -f source=my-repo -f maxCount=10
|
|
171
169
|
|
|
172
170
|
# NOT DISPOSABLE — every POST below writes to a REAL repository. These routes only
|
|
173
171
|
# operate on an already-attached git-backed source, so there is no throwaway repo to
|
|
@@ -181,16 +179,16 @@ bash ${SKILL_DIR}/scripts/git.sh GET log source=my-repo maxCount=10
|
|
|
181
179
|
# authorship, and is unsafe once it has been pushed (the remote keeps the original
|
|
182
180
|
# and the two histories diverge). Stage NAMED paths only — `paths:[…]` is what keeps
|
|
183
181
|
# a stray secret from being swept in; there is no route that un-commits.
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
neuralis request POST @neuralis/brain-core/git/stage --input - <<<'{"source":"my-repo","paths":["src/app.ts","README.md"]}'
|
|
183
|
+
neuralis request POST @neuralis/brain-core/git/commit --input - <<<'{"source":"my-repo","message":"Fix null guard in loader"}'
|
|
186
184
|
|
|
187
185
|
# Create a branch, then switch to it.
|
|
188
186
|
# checkout MOVES HEAD and swaps the working tree to that branch's content — the files
|
|
189
187
|
# the user is looking at change under them. It is plain (never `-f`): if the switch
|
|
190
188
|
# would overwrite uncommitted local changes, git refuses and you get a 409. Report
|
|
191
189
|
# that refusal; there is no force option here and you must not build one in Plane C.
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
neuralis request POST @neuralis/brain-core/git/branch --input - <<<'{"source":"my-repo","action":"create","name":"fix/loader"}'
|
|
191
|
+
neuralis request POST @neuralis/brain-core/git/checkout -f source=my-repo -f ref=fix/loader
|
|
194
192
|
|
|
195
193
|
# Sync a connected remote (needs uri-policy exec + a connected remote).
|
|
196
194
|
# pull REWRITES the working tree to the remote tip — fast-forward only, so it refuses
|
|
@@ -200,9 +198,9 @@ bash ${SKILL_DIR}/scripts/git.sh POST checkout '{"source":"my-repo","ref":"fix/l
|
|
|
200
198
|
# OUTWARD-FACING: teammates, CI, and anything deploying from that branch see it the
|
|
201
199
|
# moment it lands, and nothing in Neuralis can take it back. ASK THE USER FIRST, every
|
|
202
200
|
# time — this is the one line here you never run on your own initiative.
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
neuralis request POST @neuralis/brain-core/git/pull -f source=my-repo
|
|
202
|
+
neuralis request POST @neuralis/brain-core/git/push -f source=my-repo
|
|
205
203
|
|
|
206
204
|
# Nested repo inside a multi-repo source: add the repo selector
|
|
207
|
-
|
|
205
|
+
neuralis request GET @neuralis/brain-core/git/status -f source=mono -f repo=packages/api
|
|
208
206
|
```
|
|
@@ -7,24 +7,25 @@ allowed-tools: execute
|
|
|
7
7
|
|
|
8
8
|
# Files Management Skill
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`
|
|
10
|
+
Covers `@neuralis/brain-core/{list,search,read,raw,write,upload,approve,pending}`.
|
|
11
|
+
The `fs_*` tools (`fs_read`, `fs_write`, etc.) remain the primary path for one-off
|
|
12
|
+
file operations — this skill is for *bulk*: iterating with a shell loop, posting a
|
|
13
|
+
batch, or scripting an `upload + approve` sequence end-to-end. Call the routes with
|
|
14
|
+
`neuralis request` (the session ticket inside an activated skill, `neuralis login`
|
|
15
|
+
on your own machine); `write` and `upload` go through their scripts.
|
|
15
16
|
|
|
16
17
|
## Routes covered
|
|
17
18
|
|
|
18
|
-
| Action | Method | Path
|
|
19
|
-
|
|
20
|
-
| List files / folders at a URI | POST |
|
|
21
|
-
| Search vector index | POST |
|
|
22
|
-
| Read multiple files | POST |
|
|
23
|
-
| Raw (un-summarized) read | GET |
|
|
24
|
-
| Write — every verb (create / overwrite / edit / insert / lines / fields / move / copy / mkdir / revert / delete) | POST |
|
|
25
|
-
| Upload a payload (multipart/form-data) | POST |
|
|
26
|
-
| Approve a pending change | POST |
|
|
27
|
-
| List pending changes (project-wide) | GET |
|
|
19
|
+
| Action | Method | Path (`@neuralis/brain-core/…`) | Feature gate |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| List files / folders at a URI | POST | `list` | `drive.read` |
|
|
22
|
+
| Search vector index | POST | `search` | `drive.search` |
|
|
23
|
+
| Read multiple files | POST | `read` | `drive.read` |
|
|
24
|
+
| Raw (un-summarized) read | GET | `raw` | `drive.read` |
|
|
25
|
+
| Write — every verb (create / overwrite / edit / insert / lines / fields / move / copy / mkdir / revert / delete) | POST | `write` — `scripts/write.sh` | `drive.write` |
|
|
26
|
+
| Upload a payload (multipart/form-data) | POST | `upload` — `scripts/upload.sh` | `drive.write` |
|
|
27
|
+
| Approve a pending change | POST | `approve` | `drive.write` |
|
|
28
|
+
| List pending changes (project-wide) | GET | `pending` | `drive.read` |
|
|
28
29
|
|
|
29
30
|
Per-route feature gates are enforced server-side by the RouteDispatcher against the
|
|
30
31
|
caller's real `grantedFeatures`, independently of this skill's `requiredFeatures`
|
|
@@ -32,41 +33,45 @@ frontmatter — `/list`, `/read`, `/raw` and `GET /pending` need `drive.read`; `
|
|
|
32
33
|
`drive.search`; `/write`, `/upload`, `/approve` and the mutating `/pending`
|
|
33
34
|
verbs (`revert`, `discard`, `dismiss`, `discard-orphans`) need `drive.write`. A caller who holds
|
|
34
35
|
`drive.write` (enough to see and activate this skill) but NOT `drive.search` gets a
|
|
35
|
-
server-side **403** from `search
|
|
36
|
+
server-side **403** from `search` even though the other routes work.
|
|
36
37
|
|
|
37
|
-
##
|
|
38
|
+
## Bodies
|
|
38
39
|
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
- `/write` body = `{"agentId":"…","uri":"data://notes/plan.md","<verb>":…}` — the VERB is a KEY and its presence is the intent. Content verbs COMPOSE in one call, in a fixed order, as ONE write with ONE baseline: `create` (`{content, client_request_id?}` — never overwrites; a taken name lands BESIDE as `name-2.ext` and the response `uri` is where it went, so read it back), `overwrite` (`{content}` — refuses on a missing file and names `create`), `edit` (`[{old_string, new_string?, replace_all?, expected_replacements?}]`, applied in order, one failure discards all of them), `insert` (`{insert_text, insert_line?}` — inserts AFTER that line, `0` = start of file, omitted = append), `lines` (`{line_start, line_end, content}`), `fields` (`{category, status, pinned, tags{add,remove,set}, metadata}`). Structural verbs stand ALONE: `move`/`copy` (a destination uri or, for a file move, a bare new filename; `copy` may cross sources), `mkdir:true` (a real empty directory; not on `brain://`, where folders are implicit), `revert:true` (one-level undo), `delete:true` (a file, or a whole folder recursively). **A 2xx does not mean success** — a refusal is 200 with `ok:false`, so branch on `ok`. A `move` (or a `revert` that relocates or re-creates) onto a uri that already holds something answers `ok:false` + `error:"DESTINATION_EXISTS"` — nothing was overwritten; delete or rename the occupant first. A delete has ONE spelling, the `delete` key: `fields.status:"deleted"` is refused. `review` is never read from the body — a content edit (`overwrite`/`edit`/`insert`/`lines`) lands `pending_review` on every lane (visible and revertible in Changes); `create`/`move`/`copy`/`mkdir` land committed for a human interactive caller and `pending_review` from this ticket lane; `/upload` also journals agent-origin uploads as pending review; interactive human uploads land committed. Use `/write` for JSON text and `/upload` for multipart file bytes. A missing `agentId`/`uri` is a **400** naming the field, a body the server could not parse a **400** `Request body must be a JSON object`. A uri-policy denial is a **403 with no `ok` field**, a disabled source a **409** with `code:"source_disabled"`. A source slug this project does not have answers one of two ways, and BOTH mean "stop guessing this name": with a scheme (`zzz://a.md`) the uri gate answers **403 `uri_policy_denied`** first; without one (`notes.md`) you get **404 `code:"source_not_configured"`** carrying `availableSources` — the sources you CAN address. `/list` gives the 404 in both forms. Switch to a source from that list (or from `<runtime_stack>`); retrying the same name never works. `/read` is different again: it reports PER FILE inside a 200, so a denied or disabled source arrives as `{uri, error, code:"forbidden"}` — never a 409 — and a missing file as `code:"not_found"`. No code means the read failed for some other reason; do not conclude the file is gone. A folder delete or copy that partly succeeded reports what really happened: `affected` (files touched) and `failed` (the NAMES that could not be), so "failed" does not mean "nothing happened". `warning:"moved_with_source_retained"` on a move means the copy landed but the original could not be removed — BOTH copies exist on purpose; do not re-run the move. `indexed:false` means the bytes DID land and are readable, but search indexing was skipped or failed; report it, do not re-write. Sync retries failed indexing only for eligible files; excluded files remain unindexed.
|
|
40
|
+
- `list` / `search` / `read` / `approve` take a JSON body (`--input - <<<'<json>'`);
|
|
41
|
+
`write.sh` takes it as `BODY_JSON`. `create.sh`, `modify.sh`, `copy.sh` and `mkdir.sh` are kept only so an imported skill's existing path still writes; they exec `write.sh`, so they take the same verb-keyed body.
|
|
42
|
+
- `/write` body = `{"agentId":"…","uri":"data://notes/plan.md","<verb>":…}` — the VERB is a KEY and its presence is the intent. Content verbs COMPOSE in one call, in a fixed order, as ONE write with ONE baseline: `create` (`{content, client_request_id?}` — never overwrites; a taken name lands BESIDE as `name-2.ext` and the response `uri` is where it went, so read it back), `overwrite` (`{content}` — refuses on a missing file and names `create`), `edit` (`[{old_string, new_string?, replace_all?, expected_replacements?}]`, applied in order, one failure discards all of them), `insert` (`{insert_text, insert_line?}` — inserts AFTER that line, `0` = start of file, omitted = append), `lines` (`{line_start, line_end, content}`), `fields` (`{category, status, pinned, tags{add,remove,set}, metadata}`). Structural verbs stand ALONE: `move`/`copy` (a destination uri or, for a file move, a bare new filename; `copy` may cross sources), `mkdir:true` (a real empty directory; not on `brain://`, where folders are implicit), `revert:true` (one-level undo), `delete:true` (a file, or a whole folder recursively). **A 2xx does not mean success** — a refusal is 200 with `ok:false`, so branch on `ok`. A `move` (or a `revert` that relocates or re-creates) onto a uri that already holds something answers `ok:false` + `error:"DESTINATION_EXISTS"` — nothing was overwritten; delete or rename the occupant first. A delete has ONE spelling, the `delete` key: `fields.status:"deleted"` is refused. `review` is never read from the body — a content edit (`overwrite`/`edit`/`insert`/`lines`) lands `pending_review` on every lane (visible and revertible in Changes); `create`/`move`/`copy`/`mkdir` land committed for a human interactive caller and `pending_review` from this ticket lane; `/upload` also journals agent-origin uploads as pending review; interactive human uploads land committed. Use `/write` for JSON text and `/upload` for multipart file bytes. A missing `agentId`/`uri` is a **400** naming the field, a body the server could not parse a **400** `Request body must be a JSON object`. A uri-policy denial is a **403 with no `ok` field**, a disabled source a **409** with `code:"source_disabled"`. A source slug this project does not have answers one of two ways, and BOTH mean "stop guessing this name": with a scheme (`zzz://a.md`) the uri gate answers **403 `uri_policy_denied`** first; without one (`notes.md`) you get **404 `code:"source_not_configured"`** carrying `availableSources` — the sources you CAN address. `/list` gives the 404 in both forms. Switch to a source from that list (or from `<runtime_stack>`); retrying the same name never works. `/read` is different again: it reports PER FILE inside a 200, so a denied or disabled source arrives as `{uri, error, code:"forbidden"}` — never a 409 — and a missing file as `code:"not_found"`. No code means the read failed for some other reason; do not conclude the file is gone. A folder delete or copy that partly succeeded reports what really happened: `affected` (files touched) and `failed` (the NAMES that could not be), so "failed" does not mean "nothing happened". `warning:"moved_with_source_retained"` on a move means the copy landed but the original could not be removed — BOTH copies exist on purpose; do not re-run the move. `indexed:false` means the bytes DID land and are readable, but search indexing was skipped or failed; report it, do not re-write. Sync retries failed indexing only for eligible files; excluded files remain unindexed. A non-2xx prints the route's body too — on stderr, followed by an `HTTP <code>` line, under a `--- stderr ---` separator, so read the whole output.
|
|
42
43
|
- `/search` body key is `search_mode` (semantic|filter|grep) — the SAME vocabulary as the `fs_search` tool; the legacy `mode` key is rejected with 400. The route forwards `search_mode`, `query`, `folder_uri`, `glob`, `category`, `tags`, `status`, `created_after`/`created_before`, `limit`, and the grep-only `path_glob`, `context` and `case_sensitive`. In `grep` mode the search is uri-policy `read`-gated (only paths the caller holds `read` policy on are grepped), the match is a FIXED STRING, and the answer carries `truncated` + `total_seen` — a cut set says so, so never read a full-looking list as proof that the rest is absent.
|
|
43
|
-
- `/raw` is a GET — pass the file URI
|
|
44
|
+
- `/raw` is a GET — pass the file URI as `-f uri=<uri>` and redirect stdout to a file: it always returns the full bytes, unaffected by the response headers; what those headers say is a BROWSER instruction. Safe types (images except SVG, video, audio, PDF, text, JSON) are marked for inline display; SVG, HTML and XML are marked `attachment` so a person clicking the link downloads them instead of executing them on the app origin. Do not describe a `.svg` or `.html` you wrote as viewable in the browser — offer it as a download, or convert it to a raster image if a human needs to look at it.
|
|
44
45
|
- `/read` is at parity with the `fs_read` tool: the body takes `files[]` — each entry a URI string or `{"uri":"…","line_start":N,"line_end":M}` with that file's own window — plus `read_version`, batch-level `line_start`/`line_end` and `include_ids`. An entry that is neither shape is a **400**. Text is a bounded preview; native media may accompany it, while unsupported/invalid binary data has a URI and marker. For a byte-exact binary copy use the authorized `/raw` byte lane and multipart `/upload`, never re-encode a text preview. If you only need the pre-edit content, a plain `/read` response already carries it: each file's `version.baseline.content`, with `available_versions` listing `baseline` exactly when one exists.
|
|
45
|
-
- `/upload` is **multipart/form-data**, NOT JSON —
|
|
46
|
-
- `GET /pending` lists durable pending records inside the Changes workspace. `
|
|
46
|
+
- `/upload` is **multipart/form-data**, NOT JSON — `upload.sh` takes `FILE_PATH=…` `AGENT_ID=…` (required) plus optional `URI=…` / `TAGS=a,b`; a JSON body is rejected with 400. The 200 carries `indexed`: `false` means the bytes DID land but search indexing was skipped by the source selection or failed (provider outage/quota) — report it, do not re-upload. An eligible sync retries failed indexing; excluded uploads remain unindexed until the selection changes. The 200 also carries `mimeType`, and it may differ from what the upload declared: the stored type is resolved from the filename extension first, with the declared value kept only for an extension the platform does not know, and a common-image claim the leading bytes contradict falls back to `application/octet-stream`. Give the file a truthful extension and the stored type follows.
|
|
47
|
+
- `GET /pending` lists durable pending records inside the Changes workspace. `status` (`pending_review`\|`approved`\|`reverted`), `source`, `op` (`create`\|`modify`\|`delete`\|`move`) and `limit` filter the list; owner/scope and URI READ gates apply per record. Review begins with `POST /pending` `{action:"preview",id}`: it returns the current connector content separately from the recorded snapshot and saved baseline, typed missing/unavailable/binary/too-large states, `indexState`, `requiresAcknowledgement`, and `expectedFingerprint`. The preview is bounded by `pendingPreviewMaxBytes`; it never fabricates missing content or vectors. Resolve through `POST /approve` `{id,expectedFingerprint}` or `POST /pending` `{action:"revert"|"dismiss",id,expectedFingerprint}`. If the preview requires acknowledgement, inspect those states and include `acknowledgeCurrentState:true`; never auto-acknowledge an unseen preview. The server rechecks freshness and WRITE authority at apply: a changed fingerprint requires a new preview, and acknowledgement grants no missing permission. Bare legacy `/write revert:true` refuses unsafe pending recovery; ordinary non-pending one-level undo remains available. Approval/dismissal preserve the stored snapshot/index state (including recovery-only or absent index), rather than certifying current connector bytes as indexed. For a file the preview shows `missing` with a saved `journal-latest-write` baseline, an acknowledged approve/dismiss closes the review and the file stays gone; revert is the restore. Terminal retries remain owner-gated and idempotent; `uri` is omitted without current READ and WRITE access. Revert restores the saved baseline and refuses missing baselines or occupied move destinations. Discard/Repair remain orphan-only, owner-gated operations; no transient outage is proof of orphanhood.
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
`/api/packages/@neuralis/brain-core/list,search,read,write,…` route source
|
|
51
|
-
for the exact schemas. For one-off calls, prefer the `fs_*` tools.
|
|
50
|
+
For one-off calls, prefer the `fs_*` tools.
|
|
52
51
|
|
|
53
52
|
## Examples
|
|
54
53
|
|
|
55
54
|
For a disposable pending record you created in your project, substitute its real id below. Inspect the preview before resolving; copy its exact fingerprint, and add acknowledgement only after reviewing the states the preview names. A 409 requires a new preview, never a fabricated fingerprint.
|
|
56
55
|
|
|
57
56
|
```bash
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
neuralis request POST @neuralis/brain-core/pending -f action=preview -f id=<scratch-record-id>
|
|
58
|
+
neuralis request POST @neuralis/brain-core/pending --input - <<<'{"action":"revert","id":"<scratch-record-id>","expectedFingerprint":"<64-hex-preview-fingerprint>","acknowledgeCurrentState":true}'
|
|
60
59
|
```
|
|
61
60
|
|
|
61
|
+
Pending calls return one page; scan-orphans preserves that page in `changes` and puts its orphan subset in `orphans`/`orphanIds`: pass `cursor` from `nextCursor` unchanged with the same filters, or `uri` for an exact URI. Check `complete`, `partial` and `hasMoreAvailable`; a partial exhausted branch remains incomplete. On `restartRequired`, restart the list instead of retrying the old cursor. Repair returns scannedVisible/orphaned/attempted/discarded/refused/failed/reasons for one batch; continue only via nextCursor; pause on partial and use an explicit Continue available/Retry.
|
|
62
|
+
|
|
62
63
|
```bash
|
|
63
64
|
# Recursive list of /data
|
|
64
|
-
|
|
65
|
-
bash ${SKILL_DIR}/scripts/list.sh
|
|
65
|
+
neuralis request POST @neuralis/brain-core/list --input - <<<'{"uri":"data://","recursive":true,"limit":100}'
|
|
66
66
|
|
|
67
67
|
# Vector search across all sources (body key is `search_mode`, same as the fs_search tool)
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
neuralis request POST @neuralis/brain-core/search -f search_mode=semantic -f query='telemetry pipeline' -F limit=10
|
|
69
|
+
|
|
70
|
+
# Pending review records still waiting, one page
|
|
71
|
+
neuralis request GET @neuralis/brain-core/pending -f status=pending_review -f limit=20
|
|
72
|
+
|
|
73
|
+
# The exact bytes of one file
|
|
74
|
+
neuralis request GET @neuralis/brain-core/raw -f uri=data://report.pdf > /tmp/report.pdf
|
|
70
75
|
|
|
71
76
|
# Create a text file — the journaled write lane. The verb is the KEY.
|
|
72
77
|
# `<agent-id>` is a placeholder; a real id makes this line write for real.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -euo pipefail
|
|
3
|
-
API_BASE="${NEURALIS_API:?NEURALIS_API not set; activate skill first}"
|
|
4
|
-
TOKEN="${NEURALIS_SESSION_TOKEN:?missing session ticket; activate the skill via execute action=\"skill\"}"
|
|
5
3
|
# POST /upload REQUIRES multipart/form-data (NOT a JSON body): the handler reads the
|
|
6
4
|
# form fields file (the binary/text payload), agentId, uri (optional), tags (optional,
|
|
7
5
|
# comma-split). projectId is taken from the session ticket — passing a different one
|
|
@@ -9,10 +7,10 @@ TOKEN="${NEURALIS_SESSION_TOKEN:?missing session ticket; activate the skill via
|
|
|
9
7
|
# from the form field.
|
|
10
8
|
FILE_PATH="${FILE_PATH:?FILE_PATH is required (local path of the payload to upload)}"
|
|
11
9
|
AGENT_ID="${AGENT_ID:?AGENT_ID is required (form field; the destination agent)}"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
${
|
|
18
|
-
|
|
10
|
+
# `--form` sends multipart/form-data (`file=@path` is the file part). A non-2xx
|
|
11
|
+
# answer prints the route's body and `HTTP <code>` on stderr and exits 1.
|
|
12
|
+
neuralis request POST @neuralis/brain-core/upload \
|
|
13
|
+
--form "file=@${FILE_PATH}" \
|
|
14
|
+
--form "agentId=${AGENT_ID}" \
|
|
15
|
+
${URI:+--form "uri=${URI}"} \
|
|
16
|
+
${TAGS:+--form "tags=${TAGS}"}
|
|
@@ -16,8 +16,6 @@ set -euo pipefail
|
|
|
16
16
|
# `edit`, `insert`, `lines`) lands `pending_review` on EVERY lane; `create`, `move`,
|
|
17
17
|
# `copy` and `mkdir` land committed for a human interactive caller and
|
|
18
18
|
# `pending_review` from this ticket lane; `delete` tombstones on both.
|
|
19
|
-
API_BASE="${NEURALIS_API:?NEURALIS_API not set; activate skill first}"
|
|
20
|
-
TOKEN="${NEURALIS_SESSION_TOKEN:?missing session ticket; activate the skill via execute action=\"skill\"}"
|
|
21
19
|
BODY_JSON="${BODY_JSON:?BODY_JSON is required — a JSON object with agentId, uri and ONE verb key; see the header comment for the shape}"
|
|
22
20
|
# The retired body shape names no verb; the route would only say "Missing uri".
|
|
23
21
|
# Refuse it HERE, naming what the caller has to send instead.
|
|
@@ -27,7 +25,5 @@ case "${BODY_JSON}" in
|
|
|
27
25
|
exit 2
|
|
28
26
|
;;
|
|
29
27
|
esac
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-H "Content-Type: application/json" -d "${BODY_JSON}" \
|
|
33
|
-
"${API_BASE}/api/packages/@neuralis/brain-core/write"
|
|
28
|
+
# A non-2xx answer prints the route's body and `HTTP <code>` on stderr and exits 1.
|
|
29
|
+
neuralis request POST @neuralis/brain-core/write --input - <<<"${BODY_JSON}"
|