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.
Files changed (171) hide show
  1. package/README.md +133 -22
  2. package/dist/adopt/scan.d.ts +8 -2
  3. package/dist/adopt/scan.js +31 -13
  4. package/dist/adopt/scan.js.map +1 -1
  5. package/dist/api/agent.d.ts +85 -0
  6. package/dist/api/agent.js +92 -0
  7. package/dist/api/agent.js.map +1 -0
  8. package/dist/api/auth.d.ts +2 -0
  9. package/dist/api/auth.js +28 -6
  10. package/dist/api/auth.js.map +1 -1
  11. package/dist/api/client.d.ts +13 -1
  12. package/dist/api/client.js +26 -4
  13. package/dist/api/client.js.map +1 -1
  14. package/dist/api/commit.d.ts +9 -0
  15. package/dist/api/commit.js +36 -0
  16. package/dist/api/commit.js.map +1 -1
  17. package/dist/api/errors.js +12 -2
  18. package/dist/api/errors.js.map +1 -1
  19. package/dist/api/objects.d.ts +36 -5
  20. package/dist/api/objects.js +176 -10
  21. package/dist/api/objects.js.map +1 -1
  22. package/dist/api/poll.d.ts +5 -3
  23. package/dist/api/poll.js +17 -6
  24. package/dist/api/poll.js.map +1 -1
  25. package/dist/api/txPlan.d.ts +15 -0
  26. package/dist/api/txPlan.js +196 -0
  27. package/dist/api/txPlan.js.map +1 -0
  28. package/dist/assets/assets.d.ts +9 -0
  29. package/dist/assets/assets.js +33 -4
  30. package/dist/assets/assets.js.map +1 -1
  31. package/dist/codegen/emit.d.ts +4 -7
  32. package/dist/codegen/emit.js +10 -29
  33. package/dist/codegen/emit.js.map +1 -1
  34. package/dist/codegen/imports.d.ts +8 -15
  35. package/dist/codegen/imports.js +27 -72
  36. package/dist/codegen/imports.js.map +1 -1
  37. package/dist/codegen/instances.d.ts +5 -0
  38. package/dist/codegen/instances.js +30 -0
  39. package/dist/codegen/instances.js.map +1 -0
  40. package/dist/codegen/styles.d.ts +1 -0
  41. package/dist/codegen/styles.js +3 -0
  42. package/dist/codegen/styles.js.map +1 -0
  43. package/dist/commands/agent.d.ts +2 -0
  44. package/dist/commands/agent.js +87 -0
  45. package/dist/commands/agent.js.map +1 -0
  46. package/dist/commands/create.js +1 -0
  47. package/dist/commands/create.js.map +1 -1
  48. package/dist/commands/impl/adoptImpl.js +3 -0
  49. package/dist/commands/impl/adoptImpl.js.map +1 -1
  50. package/dist/commands/impl/agentImpl.d.ts +23 -0
  51. package/dist/commands/impl/agentImpl.js +426 -0
  52. package/dist/commands/impl/agentImpl.js.map +1 -0
  53. package/dist/commands/impl/createImpl.js +31 -4
  54. package/dist/commands/impl/createImpl.js.map +1 -1
  55. package/dist/commands/impl/initImpl.js +28 -4
  56. package/dist/commands/impl/initImpl.js.map +1 -1
  57. package/dist/commands/impl/syncImpl.js +30 -5
  58. package/dist/commands/impl/syncImpl.js.map +1 -1
  59. package/dist/commands/init.js +1 -0
  60. package/dist/commands/init.js.map +1 -1
  61. package/dist/index.js +12 -1
  62. package/dist/index.js.map +1 -1
  63. package/dist/merge/applyOps.d.ts +5 -2
  64. package/dist/merge/applyOps.js +75 -32
  65. package/dist/merge/applyOps.js.map +1 -1
  66. package/dist/merge/childrenOrder.d.ts +13 -0
  67. package/dist/merge/childrenOrder.js +51 -0
  68. package/dist/merge/childrenOrder.js.map +1 -0
  69. package/dist/merge/diff.d.ts +20 -1
  70. package/dist/merge/diff.js +77 -17
  71. package/dist/merge/diff.js.map +1 -1
  72. package/dist/merge/threeWay.d.ts +2 -2
  73. package/dist/merge/threeWay.js +74 -10
  74. package/dist/merge/threeWay.js.map +1 -1
  75. package/dist/merge/toEdits.d.ts +1 -3
  76. package/dist/merge/toEdits.js +3 -91
  77. package/dist/merge/toEdits.js.map +1 -1
  78. package/dist/nav/navigatorTree.d.ts +78 -0
  79. package/dist/nav/navigatorTree.js +410 -0
  80. package/dist/nav/navigatorTree.js.map +1 -0
  81. package/dist/nav/placement.d.ts +53 -0
  82. package/dist/nav/placement.js +260 -0
  83. package/dist/nav/placement.js.map +1 -0
  84. package/dist/nav/routeTree.d.ts +8 -2
  85. package/dist/nav/routeTree.js +79 -151
  86. package/dist/nav/routeTree.js.map +1 -1
  87. package/dist/nav/screenOptions.d.ts +15 -0
  88. package/dist/nav/screenOptions.js +213 -0
  89. package/dist/nav/screenOptions.js.map +1 -0
  90. package/dist/project/agentState.d.ts +30 -0
  91. package/dist/project/agentState.js +42 -0
  92. package/dist/project/agentState.js.map +1 -0
  93. package/dist/project/baseStore.d.ts +19 -1
  94. package/dist/project/baseStore.js +28 -0
  95. package/dist/project/baseStore.js.map +1 -1
  96. package/dist/project/componentsMap.d.ts +7 -0
  97. package/dist/project/componentsMap.js +21 -0
  98. package/dist/project/componentsMap.js.map +1 -0
  99. package/dist/project/config.d.ts +12 -3
  100. package/dist/project/config.js +11 -2
  101. package/dist/project/config.js.map +1 -1
  102. package/dist/project/env.d.ts +10 -3
  103. package/dist/project/env.js +23 -5
  104. package/dist/project/env.js.map +1 -1
  105. package/dist/project/pagesMap.d.ts +2 -0
  106. package/dist/project/pagesMap.js.map +1 -1
  107. package/dist/project/secret.d.ts +6 -2
  108. package/dist/project/secret.js +11 -5
  109. package/dist/project/secret.js.map +1 -1
  110. package/dist/services/adoptService.d.ts +3 -0
  111. package/dist/services/adoptService.js +357 -38
  112. package/dist/services/adoptService.js.map +1 -1
  113. package/dist/services/agentInstall.d.ts +80 -0
  114. package/dist/services/agentInstall.js +309 -0
  115. package/dist/services/agentInstall.js.map +1 -0
  116. package/dist/services/agentLease.d.ts +2 -0
  117. package/dist/services/agentLease.js +32 -0
  118. package/dist/services/agentLease.js.map +1 -0
  119. package/dist/services/clientFactory.d.ts +2 -0
  120. package/dist/services/clientFactory.js +20 -3
  121. package/dist/services/clientFactory.js.map +1 -1
  122. package/dist/services/doctorService.js +77 -66
  123. package/dist/services/doctorService.js.map +1 -1
  124. package/dist/services/scaffold.js +4 -0
  125. package/dist/services/scaffold.js.map +1 -1
  126. package/dist/services/syncEngine.d.ts +7 -0
  127. package/dist/services/syncEngine.js +719 -306
  128. package/dist/services/syncEngine.js.map +1 -1
  129. package/dist/style/theme.js +17 -1
  130. package/dist/style/theme.js.map +1 -1
  131. package/dist/types.d.ts +51 -2
  132. package/dist/types.js +25 -0
  133. package/dist/types.js.map +1 -1
  134. package/dist/ui/report.d.ts +15 -0
  135. package/dist/ui/report.js +30 -0
  136. package/dist/ui/report.js.map +1 -1
  137. package/package.json +3 -2
  138. package/templates/agent/agents-md/AGENTS.md +8 -0
  139. package/templates/agent/claude/CLAUDE.md +7 -0
  140. package/templates/agent/claude/mcp.json +11 -0
  141. package/templates/agent/claude/settings.json +26 -0
  142. package/templates/agent/codex/config.toml +6 -0
  143. package/templates/agent/codex/hooks.json +26 -0
  144. package/templates/agent/cursor/hooks.json +9 -0
  145. package/templates/agent/cursor/mcp.json +10 -0
  146. package/templates/agent/mapples.md +124 -0
  147. package/templates/agent/skills/mapples-conventions/SKILL.md +137 -0
  148. package/templates/agent/skills/mapples-design/SKILL.md +235 -0
  149. package/templates/agent/skills/mapples-design/reference/component-library.md +128 -0
  150. package/templates/agent/skills/mapples-design/reference/design-tokens.md +132 -0
  151. package/templates/agent/skills/mapples-design/reference/layout-contract.md +227 -0
  152. package/templates/agent/skills/mapples-design/reference/lint-checklist.md +51 -0
  153. package/templates/agent/skills/mapples-design/reference/plan.md +95 -0
  154. package/templates/agent/skills/mapples-design/reference/playbooks/empty-states.md +14 -0
  155. package/templates/agent/skills/mapples-design/reference/playbooks/forms-and-auth.md +22 -0
  156. package/templates/agent/skills/mapples-design/reference/playbooks/home-dashboard.md +14 -0
  157. package/templates/agent/skills/mapples-design/reference/playbooks/list-and-detail.md +18 -0
  158. package/templates/agent/skills/mapples-design/reference/playbooks/microcopy.md +20 -0
  159. package/templates/agent/skills/mapples-design/reference/playbooks/navigation-chrome.md +21 -0
  160. package/templates/agent/skills/mapples-design/reference/playbooks/onboarding-flow.md +15 -0
  161. package/templates/agent/skills/mapples-design/reference/playbooks/screen-flow-wiring.md +37 -0
  162. package/templates/agent/skills/mapples-design/reference/playbooks/settings-profile.md +16 -0
  163. package/templates/agent/skills/mapples-design/reference/playbooks/stats-and-progress.md +17 -0
  164. package/templates/agent/skills/mapples-design/reference/playbooks/visual-hierarchy.md +18 -0
  165. package/templates/agent/skills/mapples-design/reference/style-guides.md +92 -0
  166. package/templates/agent/skills/mapples-sync/SKILL.md +93 -0
  167. package/templates/app/_layout.tsx +3 -0
  168. package/templates/app.json +0 -1
  169. package/templates/gitignore +2 -0
  170. package/templates/mapples/sid.d.ts +6 -0
  171. 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 — `$sid`-tagged JSX
5
- is the code-side truth, extracted back into RenderDom and merged 3-way against the canvas.
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 <projectId> # scaffold a connected Expo app
9
- npx mapples init --project <projectId> # link an existing Expo app
10
- npx mapples sync # pull + push in one run
11
- npx mapples adopt app/settings.tsx # force-adopt a plain RN screen
12
- npx mapples doctor # $sid lint + drift check
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. Extracts every `*Mapplet.tsx` (`$sid` JSX → RenderDom; hooks/handlers/wrappers are invisible).
18
- 2. Pulls the delta since the last synced HEAD (`GET ?since`, cold bootstrap when too far behind).
19
- 3. 3-way merges per `$sid` node, per prop key: disjoint edits auto-merge; same-prop or
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
- 4. Writes remote changes into your files **surgically** — user code is preserved byte-for-byte.
23
- 5. Pushes your edits as one JPATCH transaction; 409s rebase and retry with the same txId; going
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
- 6. Rewrites client-minted `c_` ids to server ids from the response `idMap`.
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
- Also synced: navigation ↔ `app/(mapples)/` route tree (layouts CLI-owned, wrappers create-once —
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 # projectId, apiUrl, appDir, routesDir
36
- head # last synced HEAD + seq
37
- pages.json # pageId <-> file mapping
38
- base/ # canonical JSON per entity at the last synced HEAD
39
- outbox/ # parked transactions (offline)
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
- Auth: set `MAPPLES_TOKEN` in the environment.
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.
@@ -1,4 +1,10 @@
1
- import type { PagesMap } from '../project/pagesMap.js';
2
1
  import type { AdoptCandidate } from '../types.js';
3
- export declare function scanForCandidates(root: string, pagesMap: PagesMap): AdoptCandidate[];
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;
@@ -1,12 +1,32 @@
1
- // Adoption scan: exported React components under app/ + mapples/ with no $sid
2
- // and absent from the pages manifest. Unadopted files are never touched.
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
- const SCAN_DIRS = ['app', 'mapples'];
7
- export function scanForCandidates(root, pagesMap) {
8
- const managed = new Set(Object.values(pagesMap).map((m) => m.file));
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; // route wrappers / generated files
53
+ continue; // generated files / files already carrying a route
34
54
  const componentName = info.exportedComponents[0];
35
55
  if (!componentName)
36
56
  continue;
37
- candidates.push({
38
- file: rel,
39
- componentName,
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 SCAN_DIRS)
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/adopt/scan.ts"],"names":[],"mappings":"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"}
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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"}
@@ -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. The API secret (Creator: Project -> Settings)
2
- // both authenticates the CLI and identifies the project; the JPATCH paths
3
- // still need the pid, so we resolve it once and cache it in config.json.
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: { Authorization: `Bearer ${secret}` },
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('the API secret was rejected — generate a fresh one in Creator (Project → Settings) and re-run with --secret');
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,0EAA0E;AAC1E,yEAAyE;AAEzE,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,MAAc;IACjE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,EAAE;QACxE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;QAC9C,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,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CACb,6GAA6G,CAC9G,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,2DAA2D,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5F,CAAC"}
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"}
@@ -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
- pullOnce(since: string): Promise<Delta>;
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<{
@@ -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 ? { Authorization: `Bearer ${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
- return `/${this.projectId}/canvas${suffix}`;
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
- async pullOnce(since) {
34
- const res = await this.http.get(this.path(), { params: { since } });
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)