mapples 0.2.0-beta.0 → 0.2.0-beta.10
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 +133 -22
- package/dist/adopt/scan.d.ts +8 -2
- package/dist/adopt/scan.js +31 -13
- package/dist/adopt/scan.js.map +1 -1
- package/dist/api/agent.d.ts +85 -0
- package/dist/api/agent.js +92 -0
- package/dist/api/agent.js.map +1 -0
- package/dist/api/auth.d.ts +2 -0
- package/dist/api/auth.js +28 -6
- package/dist/api/auth.js.map +1 -1
- package/dist/api/client.d.ts +13 -1
- package/dist/api/client.js +26 -4
- package/dist/api/client.js.map +1 -1
- package/dist/api/commit.d.ts +9 -0
- package/dist/api/commit.js +36 -0
- package/dist/api/commit.js.map +1 -1
- package/dist/api/errors.js +12 -2
- package/dist/api/errors.js.map +1 -1
- package/dist/api/objects.d.ts +36 -5
- package/dist/api/objects.js +176 -10
- package/dist/api/objects.js.map +1 -1
- package/dist/api/poll.d.ts +5 -3
- package/dist/api/poll.js +17 -6
- package/dist/api/poll.js.map +1 -1
- package/dist/api/txPlan.d.ts +15 -0
- package/dist/api/txPlan.js +196 -0
- package/dist/api/txPlan.js.map +1 -0
- package/dist/assets/assets.d.ts +9 -0
- package/dist/assets/assets.js +33 -4
- package/dist/assets/assets.js.map +1 -1
- package/dist/codegen/emit.d.ts +4 -7
- package/dist/codegen/emit.js +10 -29
- package/dist/codegen/emit.js.map +1 -1
- package/dist/codegen/imports.d.ts +8 -15
- package/dist/codegen/imports.js +27 -72
- package/dist/codegen/imports.js.map +1 -1
- package/dist/codegen/instances.d.ts +5 -0
- package/dist/codegen/instances.js +30 -0
- package/dist/codegen/instances.js.map +1 -0
- package/dist/codegen/styles.d.ts +1 -0
- package/dist/codegen/styles.js +3 -0
- package/dist/codegen/styles.js.map +1 -0
- package/dist/commands/agent.d.ts +2 -0
- package/dist/commands/agent.js +87 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/create.js +1 -0
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/impl/adoptImpl.js +3 -0
- package/dist/commands/impl/adoptImpl.js.map +1 -1
- package/dist/commands/impl/agentImpl.d.ts +23 -0
- package/dist/commands/impl/agentImpl.js +426 -0
- package/dist/commands/impl/agentImpl.js.map +1 -0
- package/dist/commands/impl/createImpl.js +31 -4
- package/dist/commands/impl/createImpl.js.map +1 -1
- package/dist/commands/impl/initImpl.js +28 -4
- package/dist/commands/impl/initImpl.js.map +1 -1
- package/dist/commands/impl/syncImpl.js +30 -5
- package/dist/commands/impl/syncImpl.js.map +1 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/merge/applyOps.d.ts +5 -2
- package/dist/merge/applyOps.js +75 -32
- package/dist/merge/applyOps.js.map +1 -1
- package/dist/merge/childrenOrder.d.ts +13 -0
- package/dist/merge/childrenOrder.js +51 -0
- package/dist/merge/childrenOrder.js.map +1 -0
- package/dist/merge/diff.d.ts +20 -1
- package/dist/merge/diff.js +77 -17
- package/dist/merge/diff.js.map +1 -1
- package/dist/merge/threeWay.d.ts +2 -2
- package/dist/merge/threeWay.js +74 -10
- package/dist/merge/threeWay.js.map +1 -1
- package/dist/merge/toEdits.d.ts +1 -3
- package/dist/merge/toEdits.js +3 -91
- package/dist/merge/toEdits.js.map +1 -1
- package/dist/nav/navigatorTree.d.ts +78 -0
- package/dist/nav/navigatorTree.js +410 -0
- package/dist/nav/navigatorTree.js.map +1 -0
- package/dist/nav/placement.d.ts +53 -0
- package/dist/nav/placement.js +260 -0
- package/dist/nav/placement.js.map +1 -0
- package/dist/nav/routeTree.d.ts +8 -2
- package/dist/nav/routeTree.js +79 -151
- package/dist/nav/routeTree.js.map +1 -1
- package/dist/nav/screenOptions.d.ts +15 -0
- package/dist/nav/screenOptions.js +213 -0
- package/dist/nav/screenOptions.js.map +1 -0
- package/dist/project/agentState.d.ts +30 -0
- package/dist/project/agentState.js +42 -0
- package/dist/project/agentState.js.map +1 -0
- package/dist/project/baseStore.d.ts +19 -1
- package/dist/project/baseStore.js +28 -0
- package/dist/project/baseStore.js.map +1 -1
- package/dist/project/componentsMap.d.ts +7 -0
- package/dist/project/componentsMap.js +21 -0
- package/dist/project/componentsMap.js.map +1 -0
- package/dist/project/config.d.ts +12 -3
- package/dist/project/config.js +11 -2
- package/dist/project/config.js.map +1 -1
- package/dist/project/env.d.ts +10 -3
- package/dist/project/env.js +23 -5
- package/dist/project/env.js.map +1 -1
- package/dist/project/pagesMap.d.ts +2 -0
- package/dist/project/pagesMap.js.map +1 -1
- package/dist/project/secret.d.ts +6 -2
- package/dist/project/secret.js +11 -5
- package/dist/project/secret.js.map +1 -1
- package/dist/services/adoptService.d.ts +3 -0
- package/dist/services/adoptService.js +357 -38
- package/dist/services/adoptService.js.map +1 -1
- package/dist/services/agentInstall.d.ts +80 -0
- package/dist/services/agentInstall.js +309 -0
- package/dist/services/agentInstall.js.map +1 -0
- package/dist/services/agentLease.d.ts +2 -0
- package/dist/services/agentLease.js +32 -0
- package/dist/services/agentLease.js.map +1 -0
- package/dist/services/clientFactory.d.ts +2 -0
- package/dist/services/clientFactory.js +20 -3
- package/dist/services/clientFactory.js.map +1 -1
- package/dist/services/doctorService.js +77 -66
- package/dist/services/doctorService.js.map +1 -1
- package/dist/services/scaffold.js +4 -0
- package/dist/services/scaffold.js.map +1 -1
- package/dist/services/syncEngine.d.ts +7 -0
- package/dist/services/syncEngine.js +719 -306
- package/dist/services/syncEngine.js.map +1 -1
- package/dist/style/theme.js +17 -1
- package/dist/style/theme.js.map +1 -1
- package/dist/types.d.ts +51 -2
- package/dist/types.js +25 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/report.d.ts +15 -0
- package/dist/ui/report.js +30 -0
- package/dist/ui/report.js.map +1 -1
- package/package.json +3 -2
- package/templates/agent/agents-md/AGENTS.md +8 -0
- package/templates/agent/claude/CLAUDE.md +7 -0
- package/templates/agent/claude/mcp.json +11 -0
- package/templates/agent/claude/settings.json +26 -0
- package/templates/agent/codex/config.toml +6 -0
- package/templates/agent/codex/hooks.json +26 -0
- package/templates/agent/cursor/hooks.json +9 -0
- package/templates/agent/cursor/mcp.json +10 -0
- package/templates/agent/mapples.md +124 -0
- package/templates/agent/skills/mapples-conventions/SKILL.md +137 -0
- package/templates/agent/skills/mapples-design/SKILL.md +235 -0
- package/templates/agent/skills/mapples-design/reference/component-library.md +128 -0
- package/templates/agent/skills/mapples-design/reference/design-tokens.md +132 -0
- package/templates/agent/skills/mapples-design/reference/layout-contract.md +227 -0
- package/templates/agent/skills/mapples-design/reference/lint-checklist.md +51 -0
- package/templates/agent/skills/mapples-design/reference/plan.md +95 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/empty-states.md +14 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/forms-and-auth.md +22 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/home-dashboard.md +14 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/list-and-detail.md +18 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/microcopy.md +20 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/navigation-chrome.md +21 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/onboarding-flow.md +15 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/screen-flow-wiring.md +37 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/settings-profile.md +16 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/stats-and-progress.md +17 -0
- package/templates/agent/skills/mapples-design/reference/playbooks/visual-hierarchy.md +18 -0
- package/templates/agent/skills/mapples-design/reference/style-guides.md +92 -0
- package/templates/agent/skills/mapples-sync/SKILL.md +93 -0
- package/templates/app/_layout.tsx +3 -0
- package/templates/app.json +0 -1
- package/templates/gitignore +2 -0
- package/templates/mapples/sid.d.ts +6 -0
- package/templates/package.json +12 -10
package/README.md
CHANGED
|
@@ -1,42 +1,153 @@
|
|
|
1
1
|
# mapples
|
|
2
2
|
|
|
3
3
|
Two-way code sync between **Mapples Creator** and your Expo project (RFC-0002, over the JPATCH/1
|
|
4
|
-
protocol from RFC-0001). Screens live as generated, editable TSX in your repo —
|
|
5
|
-
is the code-side truth, extracted back into RenderDom and merged 3-way against
|
|
4
|
+
protocol from RFC-0001). Screens and components live as generated, editable TSX in your repo —
|
|
5
|
+
`$sid`-tagged JSX is the code-side truth, extracted back into RenderDom and merged 3-way against
|
|
6
|
+
the canvas.
|
|
6
7
|
|
|
7
8
|
```bash
|
|
8
|
-
npx mapples create my-app --project
|
|
9
|
-
npx mapples init --project
|
|
10
|
-
npx mapples sync
|
|
11
|
-
npx mapples
|
|
12
|
-
npx mapples
|
|
9
|
+
npx mapples create my-app --secret <project API secret> # scaffold a connected Expo app
|
|
10
|
+
npx mapples init --secret <project API secret> # link an existing Expo app
|
|
11
|
+
npx mapples sync # pull + push in one run
|
|
12
|
+
npx mapples sync --yes # …accepting every adoption proposal
|
|
13
|
+
npx mapples adopt app/settings.tsx # adopt a plain RN screen, no prompt
|
|
14
|
+
npx mapples doctor # $sid lint + drift check
|
|
15
|
+
npx mapples create my-app --secret <secret> --agent claude # …plus an agent bundle (claude | cursor | codex)
|
|
16
|
+
npx mapples agent install # add the bundle to a linked app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The project API secret comes from Creator (Project → Settings). It identifies the project and
|
|
20
|
+
authenticates the CLI; it is stored in `.mapples/secret` (git-ignored) or read from
|
|
21
|
+
`MAPPLES_SECRET`. A read-scoped secret pulls only; pushes need a write-scoped one.
|
|
22
|
+
|
|
23
|
+
## Where code lives
|
|
24
|
+
|
|
25
|
+
| In Creator | In your repo |
|
|
26
|
+
| ------------------------------ | ------------------------------------------------------------------- |
|
|
27
|
+
| A screen inside a navigator | Its expo-router route file: `app/<segment>.tsx` |
|
|
28
|
+
| A screen outside any section | `mapples/pages/<Name>.tsx` — moves into `app/` once it is placed |
|
|
29
|
+
| Navigators (stack/tabs/drawer) | CLI-owned `app/**/_layout.tsx`, regenerated every sync |
|
|
30
|
+
| A saved component | `mapples/components/<Name>.tsx` |
|
|
31
|
+
| A component placed on a screen | `import Card from '../mapples/components/Card'` + `<Card $sid … />` |
|
|
32
|
+
| Theme | `mapples/theme.ts` |
|
|
33
|
+
| Assets | `mapples/assets/` + generated `assetMap.ts` |
|
|
34
|
+
|
|
35
|
+
A routed file opens with a `// mapples-route: <id>` marker: renaming the file renames the route
|
|
36
|
+
in Creator, and a Creator rename moves the file. Apps born from `mapples create` own `app/`
|
|
37
|
+
from the root `_layout.tsx`; apps linked with `mapples init` keep the `app/(mapples)` group.
|
|
38
|
+
|
|
39
|
+
Generated files keep their styles in a dictionary below the component, the way hand-written
|
|
40
|
+
Mapples code does — never inline:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
export default function Login() {
|
|
44
|
+
return <Text $sid="nd_…" text="Sign In" styled={{ style: styles.signInText }} />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const styles = Styler.create({
|
|
48
|
+
signInText: { fontSize: 24, fontWeight: 'bold' },
|
|
49
|
+
});
|
|
13
50
|
```
|
|
14
51
|
|
|
15
52
|
## What sync does
|
|
16
53
|
|
|
17
|
-
1.
|
|
18
|
-
|
|
19
|
-
|
|
54
|
+
1. Adopts new files you wrote (`app/*.tsx`, `mapples/pages/*.tsx`, `mapples/components/*.tsx`)
|
|
55
|
+
after a prompt (`--yes` to accept, `--no-adopt` to skip): react-native primitives become the
|
|
56
|
+
matching `@mapples/ui` components with `styled` props, anything else is wrapped as an opaque
|
|
57
|
+
`Custom` node; a component file becomes a Creator component, and a page that uses it links
|
|
58
|
+
to it in the same run.
|
|
59
|
+
2. Extracts every managed file (`$sid` JSX → RenderDom; hooks, handlers and wrappers are
|
|
60
|
+
invisible). An element you added without a `$sid` is tagged the same way adoption would tag
|
|
61
|
+
it — it never stays invisible. A `styled={{ style: styles.card }}` reference is read through
|
|
62
|
+
your `StyleSheet.create` / `Styler.create` / plain-object dictionary.
|
|
63
|
+
3. Pulls the delta since the last synced HEAD (cold bootstrap when too far behind).
|
|
64
|
+
4. 3-way merges per `$sid` node, per prop key: disjoint edits auto-merge; same-prop or
|
|
20
65
|
delete-vs-edit conflicts **deprecate** the file (`_depr_<ts>_<name>.tsx.old` — your version is
|
|
21
66
|
never overwritten or deleted) and regenerate it from Creator.
|
|
22
|
-
|
|
23
|
-
|
|
67
|
+
5. Writes remote changes into your files **surgically** — user code is preserved byte-for-byte.
|
|
68
|
+
A Creator style edit lands in the dictionary entry the tag references when that element is
|
|
69
|
+
its only reader, and inline (with a warning) when the entry is shared.
|
|
70
|
+
6. Pushes your edits as JPATCH transactions of at most 200 ops; 409s rebase and retry; going
|
|
24
71
|
offline parks ops in `.mapples/outbox/` and drains them next run.
|
|
25
|
-
|
|
72
|
+
7. Rewrites client-minted `c_` ids to server ids from the response `idMap`.
|
|
73
|
+
|
|
74
|
+
Every lossy step is reported: expression props keep their last-known canvas value
|
|
75
|
+
(`NON_LITERAL_PROP`), props a component instance cannot carry stay in code only, and so on.
|
|
76
|
+
Nothing is dropped silently.
|
|
77
|
+
|
|
78
|
+
## Run in agent (Claude Code, Cursor, Codex)
|
|
79
|
+
|
|
80
|
+
`--agent <name>` on `create`/`init` (or `mapples agent install --agent <name>` in a linked app)
|
|
81
|
+
sets the app up for your local coding agent. Three skills — `mapples-design` builds the app
|
|
82
|
+
from a product brief the way the Creator's AI does (style guides → design tokens in
|
|
83
|
+
`mapples/theme.ts` → implementation plan → one screen per phase from the design playbooks →
|
|
84
|
+
wiring → review), `mapples-conventions` teaches how Mapples files are written and
|
|
85
|
+
`mapples-sync` how to run and read a sync — plus a conventions file, a pointer in the agent's
|
|
86
|
+
instructions file and a read-only `mapples` MCP entry, each where that agent reads it:
|
|
87
|
+
|
|
88
|
+
| `--agent` | Skills | Conventions | Instructions | MCP entry | Run the skill |
|
|
89
|
+
| ------------------- | ----------------- | -------------------- | ------------ | -------------------- | ----------------- |
|
|
90
|
+
| `claude` | `.claude/skills/` | `.claude/mapples.md` | `CLAUDE.md` | `.mcp.json` | `/mapples-design` |
|
|
91
|
+
| `cursor` | `.cursor/skills/` | `.cursor/mapples.md` | `AGENTS.md` | `.cursor/mcp.json` | `/mapples-design` |
|
|
92
|
+
| `codex` (`chatgpt`) | `.agents/skills/` | `.agents/mapples.md` | `AGENTS.md` | `.codex/config.toml` | `$mapples-design` |
|
|
93
|
+
|
|
94
|
+
Start the agent with the key in its environment (the MCP entry reads `MAPPLES_SECRET`; the CLI
|
|
95
|
+
itself reads `.mapples/secret`), then run the skill and describe your app:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cd my-app && MAPPLES_SECRET=$(cat .mapples/secret) claude # or: cursor . · codex
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Codex loads `.codex/config.toml` only once you trust the project. An existing instructions
|
|
102
|
+
file or MCP config is never rewritten: the pointer line is appended once and the MCP entry is
|
|
103
|
+
printed for you to add.
|
|
104
|
+
|
|
105
|
+
Every write still goes through `mapples sync`; the MCP server only reads. Generated bundle
|
|
106
|
+
files carry a `<!-- GENERATED by Mapples … -->` header and are refreshed by
|
|
107
|
+
`mapples agent install`; a file you edited is left alone (`--force` keeps it as `_depr_…old`
|
|
108
|
+
and regenerates).
|
|
109
|
+
|
|
110
|
+
While a run is in progress the CLI holds the project's **agent-session lease** so Creator shows
|
|
111
|
+
"<agent> is working on this project", a step log, and locks its own chat:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
mapples agent start --agent cursor --steps "Brief,Style guides,…" # take the lease (exit 4 if busy; --takeover)
|
|
115
|
+
mapples agent step --index 5 --title "Build the Home screen" --status running # report progress (exit 3 if the lease is gone)
|
|
116
|
+
mapples agent status --json # live session + local state
|
|
117
|
+
mapples agent stop --status done # release (or --status failed --error "…")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The bundle also installs **lease hooks** (`.claude/settings.json`, `.cursor/hooks.json` or
|
|
121
|
+
`.codex/hooks.json`): every prompt runs `mapples agent hook prompt`, which takes the lease, and
|
|
122
|
+
the end of the agent's reply runs `mapples agent hook stop`, which releases it. So Creator shows
|
|
123
|
+
the agent working for every request, even one that changes nothing. A lease a skill starts
|
|
124
|
+
explicitly (`/mapples-design`) is pinned: the stop hook leaves it held between turns, so the
|
|
125
|
+
chat stays locked while the skill asks questions, until the skill's own `agent stop`. When
|
|
126
|
+
another agent holds the project the hook tells the agent instead of failing the prompt. Codex
|
|
127
|
+
runs project hooks only after you trust the project and approve them with `/hooks`.
|
|
128
|
+
|
|
129
|
+
`start` writes `.mapples/agent.json` (git-ignored) and spawns a detached heartbeat daemon
|
|
130
|
+
(`mapples agent heartbeat --loop`, every `heartbeatSeconds` from the session; `--no-daemon`
|
|
131
|
+
to skip it) because the lease expires 90 s after the last heartbeat. Every `mapples sync` and
|
|
132
|
+
`mapples adopt` inside a session heartbeats too, and their commits are labelled
|
|
133
|
+
`<agent label>: <current step>` in Creator's history (`Claude Code`, `Cursor`, `Codex`). Disconnecting the agent in Creator makes
|
|
134
|
+
the next heartbeat/step exit 3. macOS/Linux daemon semantics in this version.
|
|
26
135
|
|
|
27
|
-
|
|
28
|
-
rename a wrapper file to rename the route), style ↔ `mapples/theme.ts`, and assets ↔
|
|
29
|
-
`mapples/assets/` + generated `assetMap.ts`.
|
|
136
|
+
Exit codes: `0` ok · `1` error · `2` sync conflicts · `3` agent lease gone · `4` project busy.
|
|
30
137
|
|
|
31
138
|
## Project state
|
|
32
139
|
|
|
33
140
|
```
|
|
34
141
|
.mapples/
|
|
35
|
-
config.json
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
142
|
+
config.json # projectId, apiUrl, pagesDir, routesDir, componentsDir, schema — committable
|
|
143
|
+
secret # project API secret — git-ignored (or MAPPLES_SECRET)
|
|
144
|
+
head # last synced HEAD + seq
|
|
145
|
+
pages.json # pageId <-> file mapping (+ route)
|
|
146
|
+
components.json # componentId <-> file mapping
|
|
147
|
+
base/ # canonical JSON per entity at the last synced HEAD
|
|
148
|
+
outbox/ # parked transactions (offline)
|
|
149
|
+
agent.json # agent-session lease held by this checkout — git-ignored, present during a run
|
|
40
150
|
```
|
|
41
151
|
|
|
42
|
-
|
|
152
|
+
`mapples/sid.d.ts` declares the transient JSX attributes (`$sid`, `$refs`, `$data`, `$actions`,
|
|
153
|
+
`$fallback`); `babel-plugin-mapples-sid` strips them from production bundles.
|
package/dist/adopt/scan.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type { PagesMap } from '../project/pagesMap.js';
|
|
2
1
|
import type { AdoptCandidate } from '../types.js';
|
|
3
|
-
export
|
|
2
|
+
export interface ScanDirs {
|
|
3
|
+
routesDir: string;
|
|
4
|
+
pagesDir: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function isUnder(file: string, dir: string): boolean;
|
|
7
|
+
/** Files pages.json and components.json already own. */
|
|
8
|
+
export declare function managedFiles(root: string): Set<string>;
|
|
9
|
+
export declare function scanForCandidates(root: string, managed: Set<string>, dirs: ScanDirs): AdoptCandidate[];
|
|
4
10
|
export declare function toRelative(root: string, file: string): string;
|
package/dist/adopt/scan.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
// Adoption scan: exported React components under
|
|
2
|
-
// and
|
|
1
|
+
// Adoption scan: exported React components under the route tree, the pages
|
|
2
|
+
// directory and app/ + mapples/ with no $sid and absent from the pages and
|
|
3
|
+
// components manifests. Unadopted files are never touched.
|
|
4
|
+
//
|
|
5
|
+
// Classification follows the file layout (RFC-0002 §5): a file under
|
|
6
|
+
// routesDir is a ROUTED page (it joins the navigation at its position), one
|
|
7
|
+
// under pagesDir — or elsewhere in app/ outside the managed tree — is a page
|
|
8
|
+
// outside the navigation, anything else is a component.
|
|
3
9
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
4
|
-
import { join, relative, sep } from 'node:path';
|
|
10
|
+
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
5
11
|
import { scanComponents } from '@mapples/sid-ast';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
import { readComponentsMap } from '../project/componentsMap.js';
|
|
13
|
+
import { readPagesMap } from '../project/pagesMap.js';
|
|
14
|
+
export function isUnder(file, dir) {
|
|
15
|
+
const rel = relative(dir, file);
|
|
16
|
+
return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
|
|
17
|
+
}
|
|
18
|
+
/** Files pages.json and components.json already own. */
|
|
19
|
+
export function managedFiles(root) {
|
|
20
|
+
return new Set([
|
|
21
|
+
...Object.values(readPagesMap(root)).map((m) => m.file),
|
|
22
|
+
...Object.values(readComponentsMap(root)).map((m) => m.file),
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
export function scanForCandidates(root, managed, dirs) {
|
|
9
26
|
const candidates = [];
|
|
27
|
+
const scanRoots = [
|
|
28
|
+
...new Set(['app', 'mapples', dirs.routesDir.split(sep)[0], dirs.pagesDir.split(sep)[0]]),
|
|
29
|
+
];
|
|
10
30
|
const walk = (dir) => {
|
|
11
31
|
const abs = join(root, dir);
|
|
12
32
|
if (!existsSync(abs))
|
|
@@ -30,18 +50,16 @@ export function scanForCandidates(root, pagesMap) {
|
|
|
30
50
|
if (info.hasSid || !info.hasJsx)
|
|
31
51
|
continue;
|
|
32
52
|
if (info.markers.length > 0)
|
|
33
|
-
continue; //
|
|
53
|
+
continue; // generated files / files already carrying a route
|
|
34
54
|
const componentName = info.exportedComponents[0];
|
|
35
55
|
if (!componentName)
|
|
36
56
|
continue;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
kind: rel.split(sep)[0] === 'app' ? 'page' : 'component',
|
|
41
|
-
});
|
|
57
|
+
const routed = isUnder(rel, dirs.routesDir);
|
|
58
|
+
const page = routed || isUnder(rel, dirs.pagesDir) || rel.split(sep)[0] === 'app';
|
|
59
|
+
candidates.push({ file: rel, componentName, kind: page ? 'page' : 'component', routed });
|
|
42
60
|
}
|
|
43
61
|
};
|
|
44
|
-
for (const dir of
|
|
62
|
+
for (const dir of scanRoots)
|
|
45
63
|
walk(dir);
|
|
46
64
|
return candidates.sort((a, b) => (a.file < b.file ? -1 : 1));
|
|
47
65
|
}
|
package/dist/adopt/scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/adopt/scan.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/adopt/scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type AxiosInstance } from 'axios';
|
|
2
|
+
export type AgentSessionStatus = 'active' | 'done' | 'failed' | 'expired' | 'disconnected';
|
|
3
|
+
export type AgentStepStatus = 'pending' | 'running' | 'done' | 'failed' | 'skipped';
|
|
4
|
+
export interface AgentSessionStep {
|
|
5
|
+
index: number;
|
|
6
|
+
title: string;
|
|
7
|
+
status: AgentStepStatus;
|
|
8
|
+
startedAt?: string | null;
|
|
9
|
+
finishedAt?: string | null;
|
|
10
|
+
detail?: string | null;
|
|
11
|
+
}
|
|
12
|
+
/** The session object every route returns (camelCase, as the backend serializes it). */
|
|
13
|
+
export interface AgentSession {
|
|
14
|
+
uuid: string;
|
|
15
|
+
project: string;
|
|
16
|
+
agent: string;
|
|
17
|
+
label: string | null;
|
|
18
|
+
status: AgentSessionStatus;
|
|
19
|
+
apiKey: {
|
|
20
|
+
uuid: string;
|
|
21
|
+
name: string | null;
|
|
22
|
+
} | null;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
lastHeartbeatAt: string;
|
|
25
|
+
expiresAt: string;
|
|
26
|
+
endedAt: string | null;
|
|
27
|
+
currentStep: string | null;
|
|
28
|
+
steps: AgentSessionStep[];
|
|
29
|
+
error: string | null;
|
|
30
|
+
rev: number;
|
|
31
|
+
heartbeatSeconds: number;
|
|
32
|
+
}
|
|
33
|
+
export interface AgentStartBody {
|
|
34
|
+
/** `claude-code` | `cursor` | `codex` — Creator names the session after it. */
|
|
35
|
+
agent: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
steps?: {
|
|
38
|
+
title: string;
|
|
39
|
+
}[];
|
|
40
|
+
force?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface AgentHeartbeatBody {
|
|
43
|
+
step?: {
|
|
44
|
+
index: number;
|
|
45
|
+
title?: string;
|
|
46
|
+
status: AgentStepStatus;
|
|
47
|
+
detail?: string;
|
|
48
|
+
};
|
|
49
|
+
currentStep?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface AgentReleaseBody {
|
|
52
|
+
status: 'done' | 'failed';
|
|
53
|
+
error?: string;
|
|
54
|
+
}
|
|
55
|
+
export type AgentSessionErrorCode = 'SESSION_ACTIVE' | 'SESSION_GONE' | 'NOT_OWNER' | 'FORBIDDEN' | 'UNEXPECTED';
|
|
56
|
+
export declare class AgentSessionError extends Error {
|
|
57
|
+
readonly code: AgentSessionErrorCode;
|
|
58
|
+
readonly http: number;
|
|
59
|
+
readonly body: unknown;
|
|
60
|
+
/** The live session that refused a start (409 AGENT_SESSION_ACTIVE). */
|
|
61
|
+
readonly session: AgentSession | null;
|
|
62
|
+
constructor(code: AgentSessionErrorCode, http: number, message: string, body: unknown);
|
|
63
|
+
}
|
|
64
|
+
/** Maps a non-success response to the typed error (never returns for 2xx). */
|
|
65
|
+
export declare function agentErrorFromResponse(status: number, body: unknown): AgentSessionError;
|
|
66
|
+
export interface AgentSessionClientOptions {
|
|
67
|
+
baseUrl: string;
|
|
68
|
+
projectId: string;
|
|
69
|
+
/** Project API key (write-scoped for start/heartbeat/release). */
|
|
70
|
+
token?: string;
|
|
71
|
+
}
|
|
72
|
+
export declare class AgentSessionClient {
|
|
73
|
+
readonly http: AxiosInstance;
|
|
74
|
+
readonly projectId: string;
|
|
75
|
+
constructor(opts: AgentSessionClientOptions);
|
|
76
|
+
private path;
|
|
77
|
+
/** POST …/agent-session → 201 session, 409 AGENT_SESSION_ACTIVE (SESSION_ACTIVE carries the live one). */
|
|
78
|
+
start(body: AgentStartBody): Promise<AgentSession>;
|
|
79
|
+
/** POST …/agent-session/{uuid}/heartbeat → 200 session, 409 SESSION_NOT_ACTIVE, 403, 404. */
|
|
80
|
+
heartbeat(uuid: string, body?: AgentHeartbeatBody): Promise<AgentSession>;
|
|
81
|
+
/** POST …/agent-session/{uuid}/release → 200 session (idempotent on terminal sessions). */
|
|
82
|
+
release(uuid: string, body: AgentReleaseBody): Promise<AgentSession>;
|
|
83
|
+
/** GET …/agent-session → {session: live | recently ended | null}. */
|
|
84
|
+
current(): Promise<AgentSession | null>;
|
|
85
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Agent-session lease client (service--mapplesorg `api/agent`): one live
|
|
2
|
+
// lease per project, kept alive by heartbeats, with a step log the Creator
|
|
3
|
+
// renders while the agent works. Same transport discipline as JpatchClient:
|
|
4
|
+
// X-API-Key auth, statuses mapped here, nothing retried at this layer.
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
export class AgentSessionError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
http;
|
|
9
|
+
body;
|
|
10
|
+
/** The live session that refused a start (409 AGENT_SESSION_ACTIVE). */
|
|
11
|
+
session;
|
|
12
|
+
constructor(code, http, message, body) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'AgentSessionError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.http = http;
|
|
17
|
+
this.body = body;
|
|
18
|
+
const session = typeof body === 'object' && body !== null
|
|
19
|
+
? body.session
|
|
20
|
+
: undefined;
|
|
21
|
+
this.session = code === 'SESSION_ACTIVE' && session ? session : null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function bodyField(body, key) {
|
|
25
|
+
return typeof body === 'object' && body !== null
|
|
26
|
+
? body[key]
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Maps a non-success response to the typed error (never returns for 2xx). */
|
|
30
|
+
export function agentErrorFromResponse(status, body) {
|
|
31
|
+
const code = bodyField(body, 'code');
|
|
32
|
+
const detail = bodyField(body, 'detail');
|
|
33
|
+
const message = typeof detail === 'string' ? detail : typeof code === 'string' ? code : `HTTP ${status}`;
|
|
34
|
+
if (status === 409 && code === 'AGENT_SESSION_ACTIVE') {
|
|
35
|
+
return new AgentSessionError('SESSION_ACTIVE', status, message, body);
|
|
36
|
+
}
|
|
37
|
+
if ((status === 409 && code === 'SESSION_NOT_ACTIVE') || status === 404) {
|
|
38
|
+
return new AgentSessionError('SESSION_GONE', status, message, body);
|
|
39
|
+
}
|
|
40
|
+
if (status === 403 && code === 'NOT_SESSION_OWNER') {
|
|
41
|
+
return new AgentSessionError('NOT_OWNER', status, message, body);
|
|
42
|
+
}
|
|
43
|
+
if (status === 403 || status === 401) {
|
|
44
|
+
return new AgentSessionError('FORBIDDEN', status, message, body);
|
|
45
|
+
}
|
|
46
|
+
return new AgentSessionError('UNEXPECTED', status, `unexpected response (HTTP ${status}): ${JSON.stringify(body)}`, body);
|
|
47
|
+
}
|
|
48
|
+
export class AgentSessionClient {
|
|
49
|
+
http;
|
|
50
|
+
projectId;
|
|
51
|
+
constructor(opts) {
|
|
52
|
+
this.projectId = opts.projectId;
|
|
53
|
+
this.http = axios.create({
|
|
54
|
+
baseURL: opts.baseUrl.replace(/\/+$/, ''),
|
|
55
|
+
headers: opts.token ? { 'X-API-Key': opts.token } : {},
|
|
56
|
+
validateStatus: () => true,
|
|
57
|
+
timeout: 15_000,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
path(suffix = '') {
|
|
61
|
+
return `/projects/${this.projectId}/agent-session${suffix}`;
|
|
62
|
+
}
|
|
63
|
+
/** POST …/agent-session → 201 session, 409 AGENT_SESSION_ACTIVE (SESSION_ACTIVE carries the live one). */
|
|
64
|
+
async start(body) {
|
|
65
|
+
const res = await this.http.post(this.path(), body);
|
|
66
|
+
if (res.status === 201 || res.status === 200)
|
|
67
|
+
return res.data;
|
|
68
|
+
throw agentErrorFromResponse(res.status, res.data);
|
|
69
|
+
}
|
|
70
|
+
/** POST …/agent-session/{uuid}/heartbeat → 200 session, 409 SESSION_NOT_ACTIVE, 403, 404. */
|
|
71
|
+
async heartbeat(uuid, body = {}) {
|
|
72
|
+
const res = await this.http.post(this.path(`/${encodeURIComponent(uuid)}/heartbeat`), body);
|
|
73
|
+
if (res.status === 200)
|
|
74
|
+
return res.data;
|
|
75
|
+
throw agentErrorFromResponse(res.status, res.data);
|
|
76
|
+
}
|
|
77
|
+
/** POST …/agent-session/{uuid}/release → 200 session (idempotent on terminal sessions). */
|
|
78
|
+
async release(uuid, body) {
|
|
79
|
+
const res = await this.http.post(this.path(`/${encodeURIComponent(uuid)}/release`), body);
|
|
80
|
+
if (res.status === 200)
|
|
81
|
+
return res.data;
|
|
82
|
+
throw agentErrorFromResponse(res.status, res.data);
|
|
83
|
+
}
|
|
84
|
+
/** GET …/agent-session → {session: live | recently ended | null}. */
|
|
85
|
+
async current() {
|
|
86
|
+
const res = await this.http.get(this.path());
|
|
87
|
+
if (res.status === 200)
|
|
88
|
+
return res.data.session;
|
|
89
|
+
throw agentErrorFromResponse(res.status, res.data);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/api/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/api/auth.d.ts
CHANGED
|
@@ -2,4 +2,6 @@ export interface ResolvedProject {
|
|
|
2
2
|
projectId: string;
|
|
3
3
|
name?: string;
|
|
4
4
|
}
|
|
5
|
+
/** Decode the `project_uuid` claim from an API-key JWT. Null when the secret is not a JWT. */
|
|
6
|
+
export declare function decodeKeyProject(secret: string): string | null;
|
|
5
7
|
export declare function resolveProject(apiUrl: string, secret: string): Promise<ResolvedProject>;
|
package/dist/api/auth.js
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
|
-
// Secret -> project resolution.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Secret -> project resolution (RFC-0003). A Mapples API key is an HS256 JWT
|
|
2
|
+
// whose payload carries `project_uuid` — the key both authenticates and
|
|
3
|
+
// identifies the project, so resolution is a LOCAL decode of the payload (no
|
|
4
|
+
// signature check client-side; the server verifies on every request). The
|
|
5
|
+
// HTTP fallback exists only for opaque dev secrets (the mock server's sk_…).
|
|
4
6
|
import axios from 'axios';
|
|
7
|
+
/** Decode the `project_uuid` claim from an API-key JWT. Null when the secret is not a JWT. */
|
|
8
|
+
export function decodeKeyProject(secret) {
|
|
9
|
+
const parts = secret.split('.');
|
|
10
|
+
if (parts.length !== 3)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
14
|
+
return typeof payload.project_uuid === 'string' && payload.project_uuid.length > 0
|
|
15
|
+
? payload.project_uuid
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Not a JWT payload we can read — fall through to the HTTP fallback.
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
5
23
|
export async function resolveProject(apiUrl, secret) {
|
|
24
|
+
const local = decodeKeyProject(secret);
|
|
25
|
+
if (local)
|
|
26
|
+
return { projectId: local };
|
|
27
|
+
// Opaque secret (mock server): resolve over HTTP with the real auth header.
|
|
6
28
|
const res = await axios.get(`${apiUrl.replace(/\/+$/, '')}/auth/project`, {
|
|
7
|
-
headers: {
|
|
29
|
+
headers: { 'X-API-Key': secret },
|
|
8
30
|
validateStatus: () => true,
|
|
9
31
|
timeout: 30_000,
|
|
10
32
|
});
|
|
11
33
|
if (res.status === 200 && typeof res.data.projectId === 'string') {
|
|
12
34
|
return res.data;
|
|
13
35
|
}
|
|
14
|
-
if (res.status === 401 || res.status === 403) {
|
|
15
|
-
throw new Error('
|
|
36
|
+
if (res.status === 401 || res.status === 403 || res.status === 404) {
|
|
37
|
+
throw new Error('this does not look like a Mapples API key — generate one in Creator (Project → Settings) and re-run with --secret');
|
|
16
38
|
}
|
|
17
39
|
throw new Error(`could not resolve the project from the API secret (HTTP ${res.status})`);
|
|
18
40
|
}
|
package/dist/api/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wEAAwE;AACxE,6EAA6E;AAC7E,0EAA0E;AAC1E,6EAA6E;AAE7E,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,8FAA8F;AAC9F,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAE9E,CAAC;QACF,OAAO,OAAO,OAAO,CAAC,YAAY,KAAK,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;YAChF,CAAC,CAAC,OAAO,CAAC,YAAY;YACtB,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,MAAc;IACjE,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,KAAK;QAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAEvC,4EAA4E;IAC5E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,EAAE;QACxE,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;QAChC,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;QAC1B,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,OAAQ,GAAG,CAAC,IAAgC,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC9F,OAAO,GAAG,CAAC,IAAuB,CAAC;IACrC,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACnE,MAAM,IAAI,KAAK,CACb,mHAAmH,CACpH,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,2DAA2D,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5F,CAAC"}
|
package/dist/api/client.d.ts
CHANGED
|
@@ -3,9 +3,14 @@ import type { Bootstrap, CommitOk, Delta, Envelope } from '../types.js';
|
|
|
3
3
|
export interface JpatchClientOptions {
|
|
4
4
|
baseUrl: string;
|
|
5
5
|
projectId: string;
|
|
6
|
+
/** Project API key (Creator → Project → Settings); sent as X-API-Key (RFC-0003). */
|
|
6
7
|
token?: string;
|
|
7
8
|
actor?: string;
|
|
8
9
|
origin?: string;
|
|
10
|
+
/** Host serving asset bodies (per-stage assets host); defaults to baseUrl. */
|
|
11
|
+
assetsBaseUrl?: string;
|
|
12
|
+
/** Commit summary for every envelope this client sends (`meta.summary`), e.g. the agent step. */
|
|
13
|
+
summary?: string;
|
|
9
14
|
}
|
|
10
15
|
/**
|
|
11
16
|
* Thin, faithful JPATCH/1 transport (RFC-0001 §5–§8). No retry logic here —
|
|
@@ -16,14 +21,21 @@ export declare class JpatchClient {
|
|
|
16
21
|
readonly projectId: string;
|
|
17
22
|
readonly actor: string | undefined;
|
|
18
23
|
readonly origin: string | undefined;
|
|
24
|
+
readonly summary: string | undefined;
|
|
25
|
+
readonly assetsBaseUrl: string;
|
|
19
26
|
constructor(opts: JpatchClientOptions);
|
|
20
27
|
private path;
|
|
21
28
|
commitRaw(envelope: Envelope): Promise<CommitOk>;
|
|
22
|
-
|
|
29
|
+
/** One delta page. The cursor is a SEQUENCE number (`since_seq`), not a head. */
|
|
30
|
+
pullOnce(sinceSeq: number): Promise<Delta>;
|
|
23
31
|
bootstrap(): Promise<Bootstrap>;
|
|
24
32
|
getObject(hash: string): Promise<unknown>;
|
|
25
33
|
/** Binary blob fetch (asset bodies, addressed as `sha256:<hex>`). */
|
|
26
34
|
getObjectBytes(hash: string): Promise<Uint8Array>;
|
|
35
|
+
/** Batched blob fetch: POST objects/get {hashes} -> {objects} (RFC-0001 §6, key-readable). */
|
|
36
|
+
objectsGet(hashes: string[]): Promise<Record<string, unknown>>;
|
|
37
|
+
/** Fetch raw bytes for an asset `link` (served from the assets host). */
|
|
38
|
+
getBytesAtPath(path: string): Promise<Uint8Array>;
|
|
27
39
|
/** Which of these content hashes is the store missing? Returns hash -> signed PUT URL. */
|
|
28
40
|
have(hashes: string[]): Promise<Record<string, string>>;
|
|
29
41
|
history(limit?: number): Promise<{
|
package/dist/api/client.js
CHANGED
|
@@ -9,20 +9,25 @@ export class JpatchClient {
|
|
|
9
9
|
projectId;
|
|
10
10
|
actor;
|
|
11
11
|
origin;
|
|
12
|
+
summary;
|
|
13
|
+
assetsBaseUrl;
|
|
12
14
|
constructor(opts) {
|
|
13
15
|
this.projectId = opts.projectId;
|
|
14
16
|
this.actor = opts.actor;
|
|
15
17
|
this.origin = opts.origin;
|
|
18
|
+
this.summary = opts.summary;
|
|
19
|
+
this.assetsBaseUrl = (opts.assetsBaseUrl ?? opts.baseUrl).replace(/\/+$/, '');
|
|
16
20
|
this.http = axios.create({
|
|
17
21
|
baseURL: opts.baseUrl.replace(/\/+$/, ''),
|
|
18
|
-
headers: opts.token ? {
|
|
22
|
+
headers: opts.token ? { 'X-API-Key': opts.token } : {},
|
|
19
23
|
// We map error bodies ourselves; axios must hand them to us.
|
|
20
24
|
validateStatus: () => true,
|
|
21
25
|
timeout: 30_000,
|
|
22
26
|
});
|
|
23
27
|
}
|
|
24
28
|
path(suffix = '') {
|
|
25
|
-
|
|
29
|
+
// Real backend shape (service--mapplesorg): plural `projects` prefix.
|
|
30
|
+
return `/projects/${this.projectId}/canvas${suffix}`;
|
|
26
31
|
}
|
|
27
32
|
async commitRaw(envelope) {
|
|
28
33
|
const res = await this.http.post(this.path(), envelope);
|
|
@@ -30,8 +35,9 @@ export class JpatchClient {
|
|
|
30
35
|
return res.data;
|
|
31
36
|
throw errorFromBody(res.status, res.data);
|
|
32
37
|
}
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
/** One delta page. The cursor is a SEQUENCE number (`since_seq`), not a head. */
|
|
39
|
+
async pullOnce(sinceSeq) {
|
|
40
|
+
const res = await this.http.get(this.path(), { params: { since_seq: sinceSeq } });
|
|
35
41
|
if (res.status === 200)
|
|
36
42
|
return res.data;
|
|
37
43
|
throw errorFromBody(res.status, res.data);
|
|
@@ -57,6 +63,22 @@ export class JpatchClient {
|
|
|
57
63
|
return new Uint8Array(res.data);
|
|
58
64
|
throw errorFromBody(res.status, JSON.parse(Buffer.from(res.data).toString('utf8')));
|
|
59
65
|
}
|
|
66
|
+
/** Batched blob fetch: POST objects/get {hashes} -> {objects} (RFC-0001 §6, key-readable). */
|
|
67
|
+
async objectsGet(hashes) {
|
|
68
|
+
const res = await this.http.post(this.path('/objects/get'), { hashes });
|
|
69
|
+
if (res.status === 200) {
|
|
70
|
+
return (res.data.objects ?? {});
|
|
71
|
+
}
|
|
72
|
+
throw errorFromBody(res.status, res.data);
|
|
73
|
+
}
|
|
74
|
+
/** Fetch raw bytes for an asset `link` (served from the assets host). */
|
|
75
|
+
async getBytesAtPath(path) {
|
|
76
|
+
const url = path.startsWith('http') ? path : `${this.assetsBaseUrl}${path}`;
|
|
77
|
+
const res = await this.http.get(url, { responseType: 'arraybuffer' });
|
|
78
|
+
if (res.status === 200)
|
|
79
|
+
return new Uint8Array(res.data);
|
|
80
|
+
throw errorFromBody(res.status, JSON.parse(Buffer.from(res.data).toString('utf8')));
|
|
81
|
+
}
|
|
60
82
|
/** Which of these content hashes is the store missing? Returns hash -> signed PUT URL. */
|
|
61
83
|
async have(hashes) {
|
|
62
84
|
if (hashes.length > 500)
|