create-objectstack 17.1.0 → 17.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,361 @@
1
1
  # create-objectstack
2
2
 
3
+ ## 17.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5a616d5: `create-objectstack` now closes with a "Created files" summary derived from a
8
+ walk of the finished project directory, so it names everything the run wrote —
9
+ including the files written after the template copy (#10323).
10
+
11
+ The old summary was the template copy's own list, printed before
12
+ `<pm> install` and before `npx skills add`. Measured against published
13
+ `create-objectstack@17.1.0` (`create-objectstack demo-app`, then a full walk of
14
+ the result): 12 entries printed, 18,045 paths on disk, **18,033 of them
15
+ unreachable from the summary** — `AGENTS.md`, `.github/copilot-instructions.md`,
16
+ `pnpm-lock.yaml`, `skills-lock.json`, `node_modules/`, and two ~968 KB trees of
17
+ agent instructions at `.agents/skills/` and `agent/skills/`.
18
+
19
+ That mattered because the same run ends with the `skills` CLI printing *"Review
20
+ skills before use; they run with full agent permissions."* Advice to review
21
+ files the run never named, at paths it never showed, is advice a newcomer
22
+ cannot act on — the wrong failure direction for a security-flavoured warning.
23
+
24
+ The list could not have been correct where it stood: two of the three write
25
+ phases belong to other processes, and the `skills` installer's destination set
26
+ moves with **its** releases, not ours. Reading the directory afterwards makes
27
+ the summary self-correcting instead. Large directories collapse to one line
28
+ carrying their path, entry count and size, so the bulk stays reviewable without
29
+ 18,000 lines of output, and the paths the skills installer created are marked
30
+ `⚠ skills` with the permissions warning tied to them.
31
+
32
+ Same run, after the change: 20 entries printed, **0 written paths unreachable**.
33
+
34
+ ### Patch Changes
35
+
36
+ - cec9d23: Fix `create-objectstack`'s startup banner hardcoding `◆ Create ObjectStack v6.x`
37
+ regardless of the package's real, released version — eleven majors stale, on
38
+ the first line of output a newcomer ever sees (#10325). The banner now calls
39
+ `readCliVersion()`, the same reader `.version()` already used, instead of a
40
+ literal string.
41
+
42
+ Dropping the real version in without recomputing the box's padding would have
43
+ reintroduced the same defect one line later — the border is a fixed run of
44
+ `═` computed for the 4-character `v6.x`, and a longer real version (`v17.1.0`
45
+ is 7 characters) would push the right border out of alignment (the sibling
46
+ bug fixed in #10322, one function away in the same file). The box now derives
47
+ its width from the version string's plain length and widens the frame — never
48
+ truncates — for a version long enough to need more room; ordinary versions
49
+ still render at the historical box size.
50
+
51
+ No behaviour change beyond the printed banner.
52
+ - 3a3f209: Tell a newcomer that the `blank` starter ships no app, so an empty Console
53
+ reads as the intended starting point rather than a broken install (#10317).
54
+
55
+ Measured on a real scaffold-and-boot (`create-objectstack my-app -t blank`,
56
+ published 17.1.0 packages, `objectstack dev --ui`): `GET /api/v1/meta/app`
57
+ returns the two platform apps (Setup, Account) and nothing of the project's
58
+ own, while `GET /api/v1/data/my_app_note` serves the scaffolded object the
59
+ whole time. The template ships `src/objects/` only — deliberately, as every
60
+ scaffolder template in this repo does — but nothing the newcomer could reach
61
+ said so, and `pnpm dev` advertises the Console URL on every boot.
62
+
63
+ Documentation only: a new "The Console" section in the generated `README.md`
64
+ naming the Console path, the consequence, and `src/apps/*.app.ts` as the
65
+ remedy. No change to what the scaffolder writes into `src/`.
66
+ - 7bf3fb7: Point every documentation link in these packages' published READMEs — and in
67
+ the project `create-objectstack` scaffolds — at the canonical docs origin
68
+ `https://objectstack.ai`, replacing the `docs.objectstack.ai` spelling.
69
+
70
+ Both spellings reach the same pages (the alias redirects to the apex,
71
+ path-preserving), so no link was broken. The reason it needs a release rather
72
+ than an in-repo fix alone: a README ships inside the npm tarball, so the
73
+ version already on npm keeps showing the old host to every reader of the
74
+ package page until a new one is published.
75
+ - 675ab57: **First-run polish:** a brand-new scaffold's very first `pnpm install` no longer reports two unmet peer dependencies (#10326).
76
+
77
+ Reproduced on a clean scaffold from published `create-objectstack@17.1.0` — no lockfile, `node_modules` removed, nothing configured by the user — and again on the second scaffold path, `objectstack init`. Both printed the same two:
78
+
79
+ ```
80
+ ✕ unmet peer better-call
81
+ Installed: 1.4.0
82
+ Wanted:
83
+ 1.3.7:
84
+ @better-auth/scim@1.7.0-rc.1
85
+
86
+ ✕ unmet peer better-sqlite3
87
+ Installed: 13.0.3
88
+ Wanted:
89
+ ^12.0.0:
90
+ better-auth@1.7.1
91
+ ```
92
+
93
+ Nothing was broken — but it is the first screen a newcomer sees, and there is nothing they did to cause it or can do about it.
94
+
95
+ **`better-sqlite3`: the pin is right and the upstream range is stale — so it is widened, not corrected.** better-auth 1.7.1 declares `better-sqlite3` as an **optional** peer at `^12.0.0`, and it governs exactly one configuration: a raw better-sqlite3 `Database` handed to better-auth's `database` option, which its Kysely dialect then drives. ObjectStack never takes that path — `AuthManager.createDatabaseConfig()` returns `createObjectQLAdapterFactory(dataEngine)`, and every `better-sqlite3` use under `plugin-auth` is knex's `client: 'better-sqlite3'` beneath ObjectQL. Measured anyway on the configuration the range *does* govern: better-auth 1.7.1 with `database: new Database(':memory:')`, running `getMigrations().runMigrations()`, `signUpEmail`, `signInEmail` and adapter `findOne`/`update`/`delete`, is green on **better-sqlite3 13.0.3** and byte-for-byte equivalent on **12.11.1**. The same probe with `Database.prototype.prepare` neutered fails, so that green is the driver's and not an unexercised path. Pinning our own `^13.0.3` declarations back to `^12` would downgrade a native module across the platform to satisfy a range measurement shows is simply behind.
96
+
97
+ **`@better-auth/scim`: the rc pin stays, and one `better-call` copy is the correct tree.** `npm view @better-auth/scim dist-tags` reads `latest: '1.7.1'`, but stable 1.7.x ships the rc.2 whole-model rewrite, so adopting it is a separate migration rather than a version bump; the exact `1.7.0-rc.1` pin is deliberate. The rc peers an exact `better-call@1.3.7` while better-auth 1.7.1 depends on `1.4.0` — and a better-auth plugin has to share the **host's** better-call instance, so the single 1.4.0 copy every install already resolves is right, not a skew to repair. This declaration retires together with the rc pin.
98
+
99
+ **What changed, and what deliberately did not.** Both remedies are pnpm `peerDependencyRules.allowedVersions` entries, scoped `<declaring package>><peer>` so each widens exactly one declaration. They ship *inside* the scaffold — the bundled `pnpm-workspace.yaml` template and the one `objectstack init` renders — because a block in this repo's own workspace file does not travel with published packages. `allowedVersions` changes what pnpm **reports**, never what it resolves: measured on both scaffold paths, the lockfile is byte-identical with and without it (0 lines of diff), and no dependency version, range or resolution moved anywhere. This repo's own resolutions are untouched.
100
+ - e85182d: Converge the blank scaffold template's `README.md` docs links on the ruled
101
+ canonical origin, `https://objectstack.ai` (maintainer ruling, 2026-08-21:
102
+ 「这个仓的文档站规范 URL 是 https://objectstack.ai」; enforced by
103
+ `CANONICAL_DOCS_ORIGIN` in `scripts/check-published-readme-links.mjs`). The
104
+ template previously linked the accepted-but-unratified `docs.objectstack.ai`
105
+ alias in three places, which disagreed with the root `README.md`'s already-
106
+ canonical spelling — so a single `npm create objectstack@latest` run handed
107
+ the user two different hostnames for the same docs site.
108
+ - aea1e64: Fix the declared bin (`bin/create-objectstack.js`) being tracked non-executable
109
+ in git. It carries a `#!/usr/bin/env node` shebang and is pnpm's link target
110
+ for the `create-objectstack` command, but was committed `100644` instead of
111
+ `100755` — matching the sibling declared bin `packages/cli/bin/run.js`, which
112
+ was already tracked executable.
113
+
114
+ Patch bump: this is a packaging-mode correction with no content, API or
115
+ behavior change (the blob hash is identical) — it only fixes how the file is
116
+ tracked in git and therefore how it is packed for npm.
117
+ - 818e027: Fix `objectstack init`'s closing "Created files" summary omitting `pnpm-lock.yaml` / `package-lock.json` and `node_modules/` (#10557).
118
+
119
+ The summary used to be printed from a list accumulated while the template
120
+ files were written — before `<pm> install` ran — so it could never name what
121
+ the package manager wrote. `init` now prints it after the install attempt
122
+ (succeeded or failed) from a walk of the finished project directory, reusing
123
+ `create-objectstack`'s `created-summary.ts` (now published as the
124
+ `create-objectstack/created-summary` subpath) instead of a second copy of the
125
+ same renderer.
126
+ - afe1c4e: fix(cli): declare the four `@better-auth/utils` peer skews a freshly scaffolded project reports (#10931)
127
+
128
+ Both scaffold paths emit a `peerDependencyRules.allowedVersions` block whose
129
+ stated purpose is that a brand-new project's first `pnpm install` does not open
130
+ with a peer-skew report. It declared two skews and left four showing:
131
+
132
+ ```
133
+ ├─┬ @better-auth/core 1.7.1
134
+ │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
135
+ ├─┬ @better-auth/scim 1.7.0-rc.1
136
+ │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
137
+ ├─┬ @better-auth/oauth-provider 1.7.1
138
+ │ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
139
+ └─┬ @better-auth/sso 1.7.1
140
+ └── ✕ unmet peer @better-auth/utils@0.4.2: found 0.5.0
141
+ ```
142
+
143
+ `@better-auth/core`, `/oauth-provider`, `/scim` and `/sso` each peer an **exact**
144
+ `@better-auth/utils@0.4.2`. The 0.5.0 they are handed comes from
145
+ `better-call@1.4.0` — better-auth's own HTTP layer — which *depends* on
146
+ `^0.5.0`; `@objectstack/plugin-auth` names the four as direct dependencies
147
+ without naming utils, so pnpm satisfies their peer from better-call's copy
148
+ instead of better-auth's own exact 0.4.2 dependency.
149
+
150
+ **Measured compatible before widening, not assumed.** Those four import three
151
+ symbols in total: `base64`/`base64Url` (`@better-auth/utils/base64`),
152
+ `createHash` (`/hash`) and, in core only, `createRandomStringGenerator`
153
+ (`/random`). 0.5.0 declares all three with identical signatures; `/random` is
154
+ unchanged apart from formatting, `/base64` swaps `new Uint8Array(data)` for a
155
+ helper that *is* `new Uint8Array(data)` on non-strings, and `/hash` only widens
156
+ its input coercion for views not backed by a plain `ArrayBuffer`. Run against
157
+ the input shapes those call sites actually pass, the two versions agree on every
158
+ value; run end to end — better-auth with the `sso`, `oauth-provider` and `scim`
159
+ plugins — a tree where the four resolve 0.5.0 and one where they resolve 0.4.2
160
+ produce the same transcript: sign-up, sign-in, session, both OAuth metadata
161
+ documents, the RFC 7636 PKCE challenge, and the SCIM and SSO endpoint outcomes.
162
+
163
+ A resolution change was measured too, and rejected: pinning utils back to 0.4.2
164
+ clears the four lines only by dragging `better-call@1.4.0` off its own declared
165
+ `^0.5.0` — manufacturing one real range violation to silence four benign ones.
166
+
167
+ Four scoped entries, one per declaring package, matching the block's convention
168
+ that each rule widens exactly one declaration. `allowedVersions` suppresses the
169
+ report only: the lockfile a scaffold resolves is byte-identical with and without
170
+ the block. The version is spelled `0.5.0` exactly rather than `0.5`, so a future
171
+ `0.6.0` reports again instead of inheriting this finding.
172
+
173
+ Both scaffold paths — `objectstack init` (rendered by the CLI) and
174
+ `npx create-objectstack` (a copied template file) — are changed together, and
175
+ `packages/cli/test/scaffold-workspace-consistency.test.ts` gains a limb that
176
+ compares the peer maps the two produce, so they cannot drift apart again.
177
+ - 21756b3: fix(create-objectstack): converge scaffold docs on the canonical host and drop the last two dead monorepo references (#10990, #11022)
178
+
179
+ A freshly scaffolded project shipped a handful of text lines a reader with
180
+ only their own project — no monorepo, no `docs/adr/`, no issue tracker —
181
+ could not follow:
182
+
183
+ - `templates/AGENTS.md` linked `https://objectstack.com/docs`, a domain that
184
+ is not this project's docs site at all (not even a redirecting alias).
185
+ - `templates/blank/Dockerfile` and `templates/blank/docker-compose.yml` both
186
+ linked `https://docs.objectstack.ai/...`, an accepted-but-unratified alias.
187
+ All three now point at the ruled canonical origin, `https://objectstack.ai`
188
+ (maintainer ruling, 2026-08-21).
189
+ - `templates/blank/README.md` cited `ADR-0097` and named "the ObjectStack
190
+ framework repo" as the home of `skills/` — both rewritten self-contained,
191
+ keeping the fact each was carrying: the connector-materialization line now
192
+ links the public [Automation → Connectors](https://objectstack.ai/docs/automation/connectors)
193
+ page, and the skills line now names the followable
194
+ `npx skills add objectstack-ai/objectstack/skills` install the scaffolder's
195
+ own closing output already uses.
196
+
197
+ `packages/create-objectstack/src/starter-comments-self-contained.test.ts`
198
+ (#10324) gains two pin obligations these two fixes call for: a host-convergence
199
+ assertion driven by the same `shippedFiles()` walker that already enumerates
200
+ everything a scaffold ships (no other repo gate's population reaches these
201
+ template files — `check:published-readme-links` reads publishable packages'
202
+ published markdown only), and a fifth `MONOREPO_ONLY` pattern that catches a
203
+ prose-shaped reference to this repo ("the ObjectStack framework repo") the
204
+ first four, syntax-shaped patterns could not. The self-retiring `EXCLUDED`
205
+ entry for `blank/README.md` is removed now that the file cites nothing
206
+ monorepo-only.
207
+ - 568de19: Scaffolded projects declare an explicit empty `packages: []` in their
208
+ `pnpm-workspace.yaml` (#10933). Both scaffold paths render it —
209
+ `renderPnpmWorkspaceYaml` in `objectstack init`, and the bundled `blank`
210
+ template `npx create-objectstack` copies.
211
+
212
+ The file was deliberately keyless so it would act purely as a settings file.
213
+ That intent is now written down rather than inferred from a missing key, and
214
+ writing it down is what fixes a first-command failure: pnpm 9.x and 10.0–10.4
215
+ parse `pnpm-workspace.yaml` **before** they read `engines`, so they refused a
216
+ brand-new project outright with
217
+
218
+ ```
219
+ ERROR packages field missing or empty
220
+ ```
221
+
222
+ naming a file the user never wrote and giving no hint that the cause is their
223
+ pnpm version — and no `engines.pnpm` floor could reach them, because they never
224
+ got as far as the engines check. Measured, one clean install per pnpm version,
225
+ each with its own store:
226
+
227
+ | pnpm | before | after |
228
+ |---|---|---|
229
+ | 9.15.9, 10.0.0, 10.4.0 | `ERROR packages field missing or empty` | `ERR_PNPM_UNSUPPORTED_ENGINE`, naming `>=10.15` |
230
+ | 10.5.0–10.14.0 | `ERR_PNPM_UNSUPPORTED_ENGINE` | unchanged |
231
+ | 10.15.0, 10.34.5, 11.22.0 | installs | installs, byte-identical `pnpm-lock.yaml` |
232
+
233
+ So every unsupported pnpm now reports the same actionable cause, and supported
234
+ pnpm is unaffected: the empty key was measured equivalent to omission on
235
+ 10.15.0, 10.34.5 and 11.22.0 — identical lockfile bytes, identical
236
+ `node_modules/.modules.yaml` once the run-local `prunedAt`/`storeDir` fields are
237
+ dropped, identical `pnpm ls -r --depth -1`, and an identical second-install
238
+ "Already up to date".
239
+
240
+ The declaration is an **empty** list on purpose. `packages: ['.']` satisfies the
241
+ same parsers but declares the project root a workspace *member* — a monorepo
242
+ root — which a single-package scaffold is not, and which reads to the next
243
+ author (human or AI) as an invitation to add member packages to an app.
244
+
245
+ `engines.pnpm` is unchanged at `>=10.15`.
246
+ - 8d21f7a: Fix `create-objectstack`'s closing "Next steps" and install-failure remedy
247
+ hardcoding `npm` regardless of which package manager the run actually used
248
+ (#10322). `detectPackageManager()` already prefers `pnpm` and falls back to
249
+ `npm` only when `pnpm` is unreachable — confirmed still true at HEAD, and
250
+ confirmed empirically: a real run with `pnpm` on `PATH` installs with `pnpm`
251
+ (`pnpm-lock.yaml`, "Done in … using pnpm vX") and then told the newcomer to
252
+ run `npm run dev` / `npm run validate` next, a package manager the run never
253
+ touched. The detected package manager is now read once, up front, and reused
254
+ consistently for the install command, the install-failure remedy, and every
255
+ line of "Next steps" — so the printed guidance always names the tool the run
256
+ actually used, in both the `pnpm` and the `npm`-fallback case.
257
+
258
+ Also names `validate` — the step the generated `AGENTS.md` calls
259
+ unskippable — in the "Getting started" section of the generated `blank`
260
+ template's README, not only in its later "Verify your changes" section, so a
261
+ newcomer reading top-to-bottom sees it at first touch.
262
+
263
+ No install behaviour changes: the scaffolder still installs by default and
264
+ still supports `--skip-install`; this is a messaging-only fix.
265
+ - 9d101d2: Declare a pnpm floor (`engines.pnpm: ">=10.15"`) in the `package.json` both
266
+ scaffolders write, so an unsupported pnpm reports its own version instead of an
267
+ error about a file the user never wrote.
268
+
269
+ Both scaffold paths emit a settings-only `pnpm-workspace.yaml` with no
270
+ `packages:` key. Early pnpm 10 refuses that file outright — `pnpm install` exits
271
+ 1 with `ERROR packages field missing or empty` before resolving a single
272
+ dependency, so a brand-new project could not be installed at all. Measured on
273
+ the rendered shape, one clean install per pnpm version, each with its own store:
274
+
275
+ | pnpm | before | after |
276
+ | --- | --- | --- |
277
+ | 10.0.0 – 10.4.0 | `packages field missing or empty` | unchanged — see below |
278
+ | 10.5.0 – 10.14.0 | `packages field missing or empty` | `ERR_PNPM_UNSUPPORTED_ENGINE`, naming the expected range |
279
+ | >= 10.15.0 | installs | installs |
280
+
281
+ The floor is a diagnosis, not a repair: pnpm 10.0.0–10.4.0 parse
282
+ `pnpm-workspace.yaml` *before* they read `engines`, so they still print the raw
283
+ workspace error. Closing that remaining sliver requires deciding what a
284
+ single-package scaffold should declare under `packages:`, which is tracked
285
+ separately and deliberately not decided here.
286
+
287
+ `engines.pnpm` rather than a `packageManager` stamp: npm, yarn and bun ignore
288
+ `engines.pnpm` entirely, so the scaffold keeps working for all four package
289
+ managers `objectstack init` hands off to. A `packageManager: "pnpm@x.y.z"` stamp
290
+ would declare the project pnpm-only (corepack-driven yarn refuses to run in such
291
+ a project) and pin one exact version that goes stale on every pnpm release — and
292
+ it buys nothing on 10.0–10.4, which reach the workspace error before reading
293
+ that field either.
294
+
295
+ No existing project is affected; this only changes what a newly scaffolded
296
+ `package.json` contains.
297
+ - 6d441e4: Correct the pnpm boundary the blank template states for `allowBuilds`, and gate
298
+ the two scaffold paths against each other (#10498, #10499).
299
+
300
+ `packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml` is copied
301
+ verbatim into every scaffolded project, so its header comment is prose that
302
+ ships **inside the user's own repository**. It said `allowBuilds` needs
303
+ pnpm >= 10.31 and that `onlyBuiltDependencies` covers pnpm 10.0–10.30. Measured
304
+ on a probe depending on `esbuild@0.28.2`, with a workspace file carrying only
305
+ `allowBuilds`, one clean install per pnpm version and each with its own
306
+ `--store-dir` (isolation matters — pnpm's side-effects cache will otherwise hand
307
+ a later run a build an earlier run performed, and it reads as "the key worked"):
308
+
309
+ | pnpm | `allowBuilds` alone |
310
+ |:--|:--|
311
+ | 10.15.0 – 10.25.0 | ignored — build not run |
312
+ | **10.26.0** | **honoured — build ran** |
313
+ | 10.28.0 – 10.33.0 | honoured — build ran |
314
+
315
+ So the floor is 10.26.0 and the older-key band is 10.0–10.25. A user on pnpm
316
+ 10.28 was being told by the file in front of them that their pnpm cannot read
317
+ the key it is in fact reading. Both load-bearing claims in that comment were
318
+ correct and are unchanged: both keys are needed, and pnpm 11 reads only
319
+ `allowBuilds`. No setting, no assertion and no install behaviour changes — the
320
+ rendered `onlyBuiltDependencies` / `allowBuilds` values are byte-identical.
321
+
322
+ The reason it was wrong for so long is the second half of this change.
323
+ `objectstack init` renders the same file from `renderPnpmWorkspaceYaml()` in
324
+ `packages/cli`, it was corrected to the measured numbers separately, and each
325
+ package's ratchets are package-local — so neither could ever fail for the other
326
+ file's regression, and the two scaffold paths shipped contradictory prose about
327
+ the same rule with every gate green. `packages/cli/test/scaffold-workspace-consistency.test.ts`
328
+ now compares the two **rendered outputs**: the packages each key actually grants
329
+ a build to, and the pnpm versions each file actually names for each key. It was
330
+ confirmed failing against the live divergence before this correction landed.
331
+
332
+ Bumped `patch` rather than left out: the corrected text is user-visible — it is
333
+ delivered into every new project — while nothing executable moves.
334
+ - ecd06f6: Rewrite the scaffolded project's starter comments so a newcomer can actually
335
+ follow them (#10324). `objectstack.config.ts` and `src/objects/note.object.ts`
336
+ are the first two files opened after scaffolding, and between them they cited
337
+ four ADR identifiers, one bare issue number and the path of a release-time
338
+ script in this monorepo — none of which ship in, or are linked from, a
339
+ scaffolded project. `// per ADR-0097` read as a reference the reader was
340
+ failing to follow rather than as the context it was meant to be.
341
+
342
+ The explanations are kept and made self-contained; only the dead ends are
343
+ gone. Each now states the fact the identifier stood for — the protocol range
344
+ is checked before anything loads and was stamped to match the installed
345
+ version rather than hand-tuned; `automation` must stay whenever `plugins:`
346
+ lists a connector or the executors have nowhere to register; a declarative
347
+ `mcp` stdio transport is denied by default; the org-wide default is required
348
+ so the baseline is an authored decision — and points at the public docs page
349
+ that covers it in full. The blank `Dockerfile` likewise stops pointing at a
350
+ file in this repo and points at the self-hosting guide it already links.
351
+
352
+ A pin (`starter-comments-self-contained.test.ts`) keeps it that way from both
353
+ sides: no shipped template file may cite an ADR identifier, a bare issue
354
+ number or a repo script path, and the facts those references carried must
355
+ still be stated — so the comments cannot be "fixed" by deleting them. It also
356
+ resolves every canonical-origin docs URL in the shipped tree against
357
+ `content/docs`, because a link that 404s is the same defect one level out.
358
+
3
359
  ## 17.1.0
4
360
 
5
361
  ### Minor Changes
package/README.md CHANGED
@@ -87,7 +87,7 @@ npm run validate # verify metadata: schema + predicates + bindings
87
87
  ```
88
88
 
89
89
  See the docs:
90
- [Your First Project](https://docs.objectstack.ai/docs/getting-started/your-first-project).
90
+ [Your First Project](https://objectstack.ai/docs/getting-started/your-first-project).
91
91
 
92
92
  ## License
93
93
 
@@ -0,0 +1,112 @@
1
+ // src/created-summary.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ var COLLAPSE_AT = 10;
5
+ var MEASURE_BUDGET = 2e3;
6
+ function scan(abs, name, budget) {
7
+ let st;
8
+ try {
9
+ st = fs.lstatSync(abs);
10
+ } catch {
11
+ return { name, dir: false, entries: 0, bytes: 0, truncated: false, children: [] };
12
+ }
13
+ if (!st.isDirectory()) {
14
+ budget.left -= 1;
15
+ return { name, dir: false, entries: 1, bytes: st.size, truncated: false, children: [] };
16
+ }
17
+ const node = { name, dir: true, entries: 0, bytes: 0, truncated: false, children: [] };
18
+ let dirents;
19
+ try {
20
+ dirents = fs.readdirSync(abs, { withFileTypes: true });
21
+ } catch {
22
+ return node;
23
+ }
24
+ for (const entry of dirents) {
25
+ if (budget.left <= 0) {
26
+ node.truncated = true;
27
+ break;
28
+ }
29
+ const child = scan(path.join(abs, entry.name), entry.name, budget);
30
+ node.entries += child.entries;
31
+ node.bytes += child.bytes;
32
+ if (child.truncated) node.truncated = true;
33
+ node.children.push(child);
34
+ }
35
+ return node;
36
+ }
37
+ function byName(a, b) {
38
+ return a.name.localeCompare(b.name, "en");
39
+ }
40
+ function flatten(node, prefix, out) {
41
+ for (const child of [...node.children].sort(byName)) {
42
+ const rel = prefix + child.name;
43
+ if (!child.dir) {
44
+ out.push({ path: rel, kind: "file", entries: 1, bytes: child.bytes, truncated: false });
45
+ continue;
46
+ }
47
+ if (!child.truncated && child.entries <= COLLAPSE_AT) {
48
+ flatten(child, `${rel}/`, out);
49
+ continue;
50
+ }
51
+ let deepest = child;
52
+ let shown = rel;
53
+ while (deepest.children.length === 1 && deepest.children[0].dir) {
54
+ deepest = deepest.children[0];
55
+ shown = `${shown}/${deepest.name}`;
56
+ }
57
+ out.push({
58
+ path: `${shown}/`,
59
+ kind: "dir",
60
+ entries: deepest.entries,
61
+ bytes: deepest.bytes,
62
+ truncated: deepest.truncated
63
+ });
64
+ }
65
+ }
66
+ function summarizeTree(root) {
67
+ let dirents;
68
+ try {
69
+ dirents = fs.readdirSync(root, { withFileTypes: true });
70
+ } catch {
71
+ return [];
72
+ }
73
+ const top = { name: "", dir: true, entries: 0, bytes: 0, truncated: false, children: [] };
74
+ for (const entry of dirents) {
75
+ top.children.push(scan(path.join(root, entry.name), entry.name, { left: MEASURE_BUDGET }));
76
+ }
77
+ const out = [];
78
+ flatten(top, "", out);
79
+ return [
80
+ ...out.filter((e) => e.kind === "file"),
81
+ ...out.filter((e) => e.kind === "dir")
82
+ ];
83
+ }
84
+ function formatBytes(bytes) {
85
+ if (bytes < 1024) return `${bytes} B`;
86
+ const kb = bytes / 1024;
87
+ if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)} KB`;
88
+ const mb = kb / 1024;
89
+ return `${mb < 10 ? mb.toFixed(1) : Math.round(mb)} MB`;
90
+ }
91
+ function describeEntry(entry) {
92
+ if (entry.kind === "file") return "";
93
+ const noun = entry.entries === 1 ? "file" : "files";
94
+ if (entry.truncated) return `over ${entry.entries.toLocaleString("en-US")} ${noun}`;
95
+ return `${entry.entries.toLocaleString("en-US")} ${noun}, ${formatBytes(entry.bytes)}`;
96
+ }
97
+ function unreachablePaths(entries, written) {
98
+ const named = new Set(entries.filter((e) => e.kind === "file").map((e) => e.path));
99
+ const dirs = entries.filter((e) => e.kind === "dir").map((e) => e.path);
100
+ return written.filter(
101
+ (p) => !named.has(p) && !dirs.some((d) => `${p}/`.startsWith(d))
102
+ );
103
+ }
104
+
105
+ export {
106
+ COLLAPSE_AT,
107
+ MEASURE_BUDGET,
108
+ summarizeTree,
109
+ formatBytes,
110
+ describeEntry,
111
+ unreachablePaths
112
+ };
@@ -0,0 +1,48 @@
1
+ /** A directory holding more than this many entries is collapsed to one line. */
2
+ declare const COLLAPSE_AT = 10;
3
+ /**
4
+ * Entries this module is willing to `lstat` per top-level entry before it
5
+ * stops counting and reports a lower bound.
6
+ *
7
+ * The budget is PER TOP-LEVEL ENTRY, not global, and that is load-bearing:
8
+ * with one shared budget, `node_modules/` (17,920 paths in the measurement
9
+ * above) exhausts it before the walk reaches the project's own files, and the
10
+ * summary silently truncates the very content it exists to disclose. Whether
11
+ * that happened would depend on `readdir` order.
12
+ */
13
+ declare const MEASURE_BUDGET = 2000;
14
+ interface SummaryEntry {
15
+ /** Project-relative path. Directories carry a trailing `/`. */
16
+ path: string;
17
+ kind: 'file' | 'dir';
18
+ /** Files and symlinks in the subtree (always 1 for a file). */
19
+ entries: number;
20
+ /** Total size in bytes. Meaningless when `truncated`. */
21
+ bytes: number;
22
+ /** Measurement stopped at the budget — `entries` and `bytes` are lower bounds. */
23
+ truncated: boolean;
24
+ }
25
+ /**
26
+ * Summarize everything under `root`, collapsing large directories.
27
+ *
28
+ * Returns files first (alphabetical), then collapsed directories
29
+ * (alphabetical), so the enumerated content reads as a list and the bulk
30
+ * trees read as a block with their sizes.
31
+ */
32
+ declare function summarizeTree(root: string): SummaryEntry[];
33
+ /** Human-readable byte count. */
34
+ declare function formatBytes(bytes: number): string;
35
+ /** The measurement note that follows a collapsed directory's path. */
36
+ declare function describeEntry(entry: SummaryEntry): string;
37
+ /**
38
+ * The property this module exists to hold: every path in `written` is either
39
+ * named outright by a summary entry, or lies beneath a directory entry that
40
+ * is. Returns the paths that are NOT reachable — empty means the summary is
41
+ * complete.
42
+ *
43
+ * Exported because it is the assertion, and an assertion that lives only in a
44
+ * test file cannot be run against a real scaffold from anywhere else.
45
+ */
46
+ declare function unreachablePaths(entries: SummaryEntry[], written: string[]): string[];
47
+
48
+ export { COLLAPSE_AT, MEASURE_BUDGET, type SummaryEntry, describeEntry, formatBytes, summarizeTree, unreachablePaths };
@@ -0,0 +1,16 @@
1
+ import {
2
+ COLLAPSE_AT,
3
+ MEASURE_BUDGET,
4
+ describeEntry,
5
+ formatBytes,
6
+ summarizeTree,
7
+ unreachablePaths
8
+ } from "./chunk-ZIUW7UEA.js";
9
+ export {
10
+ COLLAPSE_AT,
11
+ MEASURE_BUDGET,
12
+ describeEntry,
13
+ formatBytes,
14
+ summarizeTree,
15
+ unreachablePaths
16
+ };
package/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
+ import {
2
+ describeEntry,
3
+ summarizeTree
4
+ } from "./chunk-ZIUW7UEA.js";
5
+
1
6
  // src/index.ts
2
7
  import { Command } from "commander";
3
- import chalk from "chalk";
8
+ import chalk2 from "chalk";
4
9
  import fs4 from "fs";
5
10
  import path4 from "path";
6
11
  import { execSync } from "child_process";
@@ -192,6 +197,26 @@ function pinRuntimeImage(targetDir, version) {
192
197
  return { pinned: true, tag: version };
193
198
  }
194
199
 
200
+ // src/banner.ts
201
+ import chalk from "chalk";
202
+ var PREFIX = " \u25C6 Create ObjectStack ";
203
+ var MIN_INNER_WIDTH = 35;
204
+ var MIN_TRAILING_PAD = 3;
205
+ function renderVersionBanner(version) {
206
+ const versionLabel = `v${version}`;
207
+ const innerWidth = Math.max(
208
+ MIN_INNER_WIDTH,
209
+ PREFIX.length + versionLabel.length + MIN_TRAILING_PAD
210
+ );
211
+ const trailingPad = innerWidth - PREFIX.length - versionLabel.length;
212
+ const border = "\u2550".repeat(innerWidth);
213
+ return [
214
+ chalk.bold.cyan(` \u2554${border}\u2557`),
215
+ chalk.bold.cyan(" \u2551") + chalk.bold(PREFIX) + chalk.dim(versionLabel) + chalk.bold.cyan(`${" ".repeat(trailingPad)}\u2551`),
216
+ chalk.bold.cyan(` \u255A${border}\u255D`)
217
+ ];
218
+ }
219
+
195
220
  // src/index.ts
196
221
  var __filename2 = fileURLToPath(import.meta.url);
197
222
  var __dirname2 = path4.dirname(__filename2);
@@ -220,24 +245,24 @@ function readCliVersion() {
220
245
  }
221
246
  }
222
247
  function printHeader(title) {
223
- console.log(chalk.bold(`
248
+ console.log(chalk2.bold(`
224
249
  \u25C6 ${title}`));
225
- console.log(chalk.dim("\u2500".repeat(40)));
250
+ console.log(chalk2.dim("\u2500".repeat(40)));
226
251
  }
227
252
  function printKV(key, value) {
228
- console.log(` ${chalk.dim(key + ":")} ${chalk.white(value)}`);
253
+ console.log(` ${chalk2.dim(key + ":")} ${chalk2.white(value)}`);
229
254
  }
230
255
  function printSuccess(msg) {
231
- console.log(chalk.green(` \u2713 ${msg}`));
256
+ console.log(chalk2.green(` \u2713 ${msg}`));
232
257
  }
233
258
  function printError(msg) {
234
- console.log(chalk.red(` \u2717 ${msg}`));
259
+ console.log(chalk2.red(` \u2717 ${msg}`));
235
260
  }
236
261
  function printStep(msg) {
237
- console.log(chalk.yellow(` \u2192 ${msg}`));
262
+ console.log(chalk2.yellow(` \u2192 ${msg}`));
238
263
  }
239
264
  function printWarning(msg) {
240
- console.log(chalk.yellow(` \u26A0 ${msg}`));
265
+ console.log(chalk2.yellow(` \u26A0 ${msg}`));
241
266
  }
242
267
  function detectPackageManager() {
243
268
  try {
@@ -328,6 +353,49 @@ function writeAgentGuides(targetDir, title, projectName) {
328
353
  fs4.mkdirSync(path4.dirname(copilotPath), { recursive: true });
329
354
  writeIfAbsent(copilotPath, rendered);
330
355
  }
356
+ function topLevelNames(dir) {
357
+ try {
358
+ return new Set(fs4.readdirSync(dir));
359
+ } catch {
360
+ return /* @__PURE__ */ new Set();
361
+ }
362
+ }
363
+ function printCreatedSummary(targetDir, opts) {
364
+ const entries = summarizeTree(targetDir);
365
+ if (entries.length === 0) return;
366
+ console.log(
367
+ chalk2.bold(opts.wasEmpty ? " Created files:" : " Project contents:")
368
+ );
369
+ if (!opts.wasEmpty) {
370
+ console.log(
371
+ chalk2.dim(" (the directory already had contents; this lists all of it)")
372
+ );
373
+ }
374
+ const isSkillPath = (p) => opts.skillPaths.has(p.split("/")[0]);
375
+ const width = Math.min(
376
+ 44,
377
+ Math.max(...entries.map((e) => e.path.length)) + 2
378
+ );
379
+ let flagged = false;
380
+ for (const entry of entries) {
381
+ const note = describeEntry(entry);
382
+ const flag = isSkillPath(entry.path);
383
+ if (flag) flagged = true;
384
+ const pad = note || flag ? entry.path.padEnd(width) : entry.path;
385
+ const line = ` + ${pad}${note ? chalk2.dim(note) : ""}`;
386
+ console.log(chalk2.green(line) + (flag ? chalk2.yellow(" \u26A0 skills") : ""));
387
+ }
388
+ if (flagged) {
389
+ console.log("");
390
+ console.log(
391
+ chalk2.yellow(" \u26A0 Skill files run with your coding agent's full permissions.")
392
+ );
393
+ console.log(
394
+ chalk2.dim(" Review the paths marked \u26A0 above before letting an agent use them.")
395
+ );
396
+ }
397
+ console.log("");
398
+ }
331
399
  function writeIfAbsent(filePath, contents) {
332
400
  try {
333
401
  fs4.writeFileSync(filePath, contents, { flag: "wx" });
@@ -341,23 +409,21 @@ var program = new Command().name("create-objectstack").description("Create a new
341
409
  "blank"
342
410
  ).option("--skip-install", "Skip dependency installation").option("--skip-skills", "Skip installing ObjectStack AI skills").action((name, options) => {
343
411
  console.log("");
344
- console.log(chalk.bold.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
345
- console.log(chalk.bold.cyan(" \u2551") + chalk.bold(" \u25C6 Create ObjectStack ") + chalk.dim("v6.x") + chalk.bold.cyan(" \u2551"));
346
- console.log(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
412
+ for (const line of renderVersionBanner(readCliVersion())) console.log(line);
347
413
  printHeader("New Environment");
348
414
  const lookup = lookupTemplate(options.template);
349
415
  if (lookup.kind !== "found") {
350
416
  if (lookup.kind === "retired") {
351
417
  printError(`Template "${lookup.name}" has been retired and is no longer available.`);
352
418
  console.log(
353
- chalk.dim(
419
+ chalk2.dim(
354
420
  " It was delisted from the ObjectStack template marketplace and is no longer maintained."
355
421
  )
356
422
  );
357
423
  } else {
358
424
  printError(`Unknown template: ${lookup.name}`);
359
425
  }
360
- console.log(chalk.dim(` Available: ${templateNames().join(", ")}`));
426
+ console.log(chalk2.dim(` Available: ${templateNames().join(", ")}`));
361
427
  process.exit(1);
362
428
  }
363
429
  const template = lookup.template;
@@ -366,6 +432,7 @@ var program = new Command().name("create-objectstack").description("Create a new
366
432
  const namespace = sanitizeNamespace(projectName);
367
433
  const targetDir = name ? path4.resolve(cwd, name) : cwd;
368
434
  const isCurrentDir = targetDir === cwd;
435
+ const pm = detectPackageManager();
369
436
  printKV("Environment", projectName);
370
437
  printKV("Namespace", namespace);
371
438
  printKV("Template", `${options.template} \u2014 ${template.description}`);
@@ -378,28 +445,22 @@ var program = new Command().name("create-objectstack").description("Create a new
378
445
  process.exit(1);
379
446
  }
380
447
  }
448
+ const targetWasEmpty = topLevelNames(targetDir).size === 0;
381
449
  try {
382
450
  fs4.mkdirSync(targetDir, { recursive: true });
383
451
  const createdFiles = loadBundled(template.source.dir, targetDir);
384
452
  rewriteProjectIdentity(targetDir, projectName, namespace);
385
- console.log(chalk.bold(" Created files:"));
386
- for (const f of createdFiles.slice(0, 20)) {
387
- console.log(chalk.green(` + ${f}`));
388
- }
389
- if (createdFiles.length > 20) {
390
- console.log(chalk.dim(` \u2026 and ${createdFiles.length - 20} more`));
391
- }
453
+ printSuccess(`Template files written (${createdFiles.length})`);
392
454
  console.log("");
393
455
  if (!options.skipInstall) {
394
456
  printStep("Installing dependencies...");
395
457
  let installed = false;
396
458
  try {
397
- const pm = detectPackageManager();
398
459
  execSync(`${pm} install`, { stdio: "inherit", cwd: targetDir });
399
460
  installed = true;
400
461
  console.log("");
401
462
  } catch {
402
- printWarning("Dependency installation failed. Run `npm install` manually.");
463
+ printWarning(`Dependency installation failed. Run \`${pm} install\` manually.`);
403
464
  console.log("");
404
465
  }
405
466
  if (installed) {
@@ -417,6 +478,7 @@ var program = new Command().name("create-objectstack").description("Create a new
417
478
  }
418
479
  }
419
480
  }
481
+ const beforeSkills = topLevelNames(targetDir);
420
482
  if (!options.skipInstall && !options.skipSkills) {
421
483
  printStep("Installing AI skills for your coding agent...");
422
484
  try {
@@ -432,22 +494,29 @@ var program = new Command().name("create-objectstack").description("Create a new
432
494
  console.log("");
433
495
  }
434
496
  }
497
+ const skillPaths = new Set(
498
+ [...topLevelNames(targetDir)].filter((p) => !beforeSkills.has(p))
499
+ );
500
+ printCreatedSummary(targetDir, { wasEmpty: targetWasEmpty, skillPaths });
435
501
  printSuccess("Environment created!");
436
502
  console.log("");
437
- console.log(chalk.bold(" Next steps:"));
503
+ console.log(chalk2.bold(" Next steps:"));
438
504
  if (!isCurrentDir) {
439
- console.log(chalk.dim(` cd ${name}`));
505
+ console.log(chalk2.dim(` cd ${name}`));
440
506
  }
441
507
  if (options.skipInstall) {
442
- console.log(chalk.dim(" npm install"));
508
+ console.log(chalk2.dim(` ${pm} install`));
443
509
  }
444
- console.log(chalk.dim(" npm run dev # Start development server"));
445
- console.log(chalk.dim(" npm run validate # Verify metadata: schema + predicates + bindings"));
446
- console.log(chalk.dim(" # (run after every metadata edit \u2014 see AGENTS.md)"));
510
+ const devLabel = `${pm} run dev`;
511
+ const validateLabel = `${pm} run validate`;
512
+ const labelWidth = Math.max(devLabel.length, validateLabel.length) + 3;
513
+ console.log(chalk2.dim(` ${devLabel.padEnd(labelWidth)}# Start development server`));
514
+ console.log(chalk2.dim(` ${validateLabel.padEnd(labelWidth)}# Verify metadata: schema + predicates + bindings`));
515
+ console.log(chalk2.dim(` ${" ".repeat(labelWidth)}# (run after every metadata edit \u2014 see AGENTS.md)`));
447
516
  if (options.skipInstall || options.skipSkills) {
448
517
  console.log("");
449
- console.log(chalk.bold(" AI Skills (recommended):"));
450
- console.log(chalk.dim(" npx skills add objectstack-ai/objectstack/skills"));
518
+ console.log(chalk2.bold(" AI Skills (recommended):"));
519
+ console.log(chalk2.dim(" npx skills add objectstack-ai/objectstack/skills"));
451
520
  }
452
521
  console.log("");
453
522
  } catch (error) {
@@ -96,6 +96,6 @@ Skills are triggered automatically based on task context:
96
96
 
97
97
  ## Learn More
98
98
 
99
- - [ObjectStack Documentation](https://objectstack.com/docs)
99
+ - [ObjectStack Documentation](https://objectstack.ai/docs)
100
100
  - [GitHub: objectstack-ai/objectstack](https://github.com/objectstack-ai/objectstack)
101
101
  - [Skills CLI](https://skills.sh/) — Manage AI skills across agents
@@ -7,7 +7,7 @@
7
7
  # my-app
8
8
  #
9
9
  # Or run the full app + Postgres stack: see docker-compose.yml.
10
- # Docs: https://docs.objectstack.ai/docs/deployment/self-hosting
10
+ # Docs: https://objectstack.ai/docs/deployment/self-hosting
11
11
 
12
12
  # ── Build stage: compile TypeScript metadata to the artifact ─────────
13
13
  FROM node:22-slim AS build
@@ -20,7 +20,7 @@ RUN npx os build # → dist/objectstack.json
20
20
  # ── Runtime: the official ObjectStack runtime image ──────────────────
21
21
  # Ships Node + @objectstack/cli with `os start`, a non-root user, the
22
22
  # /api/v1/health HEALTHCHECK, and OS_ARTIFACT_PATH/OS_PORT preset (port 8080)
23
- # — see docker/README.md in the framework repo.
23
+ # — see the self-hosting guide linked above.
24
24
  #
25
25
  # Dependencies were not installed while scaffolding, so the tag below could
26
26
  # not be resolved for you. `latest` floats to whatever release is newest,
@@ -9,6 +9,11 @@ pnpm install
9
9
  pnpm dev
10
10
  ```
11
11
 
12
+ After editing any metadata (an object, view, flow, …), run `pnpm validate` —
13
+ see [Verify your changes](#verify-your-changes) below. It is the one command
14
+ this project's `AGENTS.md` calls unskippable: it catches mistakes that
15
+ otherwise fail silently at runtime.
16
+
12
17
  The REST API is served at `http://localhost:3000/api/v1`. Data endpoints
13
18
  require a session — the dev server seeds a login-ready admin
14
19
  (`admin@objectos.ai` / `admin123`) on an empty database:
@@ -21,6 +26,23 @@ curl -c cookies.txt -X POST http://localhost:3000/api/v1/auth/sign-in/email \
21
26
  curl -b cookies.txt "http://localhost:3000/api/v1/data/<your_object>"
22
27
  ```
23
28
 
29
+ ## The Console — this starter ships no app
30
+
31
+ `pnpm dev` also serves the admin Console at `http://localhost:3000/_console/`,
32
+ and prints the link on boot. Open it and you will see the platform's own apps
33
+ (Setup, Account) and **not** the object in `src/objects/` — this starter ships
34
+ objects only, with no app and no views.
35
+
36
+ That is the intended starting point, not a broken install. The object is live
37
+ the whole time — the `curl` above returns it, and an MCP client can read and
38
+ write it. What it has no route into is the Console's navigation.
39
+
40
+ **An object appears in Console navigation only when an app lists it.** Add an
41
+ `*.app.ts` under `src/apps/` (plus the views it points at), and the Console
42
+ renders it after the next `pnpm dev` rebuild. The `objectstack-ui` skill covers
43
+ the shape; describing the app you want to your coding agent is the intended
44
+ path.
45
+
24
46
  ## Your app is an MCP server
25
47
 
26
48
  Every ObjectStack app is itself a
@@ -36,7 +58,7 @@ claude mcp add --transport http my-app http://localhost:3000/api/v1/mcp
36
58
 
37
59
  Set `OS_MCP_SERVER_ENABLED=false` to turn it off. This is the *serve* side — the
38
60
  reverse of the `mcp` connector below (which lets your app *call* other MCP
39
- servers). See [Connect an MCP Client](https://docs.objectstack.ai/docs/ai/connect-mcp)
61
+ servers). See [Connect an MCP Client](https://objectstack.ai/docs/ai/connect-mcp)
40
62
  for OAuth, API keys, and which objects/actions become tools.
41
63
 
42
64
  ## Layout
@@ -57,9 +79,11 @@ can call an external system from a flow as pure metadata — no host code:
57
79
 
58
80
  Add a `connectors:` entry that names one of these `provider`s and the
59
81
  `automation` capability materializes it into a live, dispatchable connector at
60
- boot (ADR-0097); a flow's `connector_action` node then calls it. To add a brand
61
- connector (e.g. Slack), install its package and add `new ConnectorSlackPlugin()`
62
- to `plugins:`; to drop a provider, remove its plugin.
82
+ boot see [Automation Connectors](https://objectstack.ai/docs/automation/connectors)
83
+ for how that materialization works; a flow's `connector_action` node then
84
+ calls it. To add a brand connector (e.g. Slack), install its package and add
85
+ `new ConnectorSlackPlugin()` to `plugins:`; to drop a provider, remove its
86
+ plugin.
63
87
 
64
88
  > **Security — declarative MCP over stdio.** An `mcp` connector whose transport
65
89
  > spawns a local process (`stdio`) is denied by default, because the command
@@ -67,7 +91,7 @@ to `plugins:`; to drop a provider, remove its plugin.
67
91
  > `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })`; `http` transports
68
92
  > need no opt-in.
69
93
 
70
- See [Automation → Flows](https://docs.objectstack.ai/docs/automation/flows) for
94
+ See [Automation → Flows](https://objectstack.ai/docs/automation/flows) for
71
95
  the full connector and `connector_action` guide.
72
96
 
73
97
  ## Verify your changes
@@ -105,7 +129,7 @@ curl -fsS http://localhost:8080/api/v1/health
105
129
  ```
106
130
 
107
131
  Bare Node, Kubernetes, reverse-proxy wiring, and the required secrets are
108
- covered in [Self-Hosted Deployment](https://docs.objectstack.ai/docs/deployment/self-hosting).
132
+ covered in [Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self-hosting).
109
133
 
110
134
  ## Next steps
111
135
 
@@ -114,5 +138,5 @@ covered in [Self-Hosted Deployment](https://docs.objectstack.ai/docs/deployment/
114
138
  - Add a flow or automation: see `objectstack-automation`.
115
139
  - Add an AI agent: see `objectstack-ai`.
116
140
 
117
- Skills live in `skills/` in the ObjectStack framework repo and in the in-IDE
118
- assistant catalog.
141
+ Skills are installed with `npx skills add objectstack-ai/objectstack/skills`
142
+ (see `AGENTS.md`) and also show up in the in-IDE assistant catalog.
@@ -4,7 +4,7 @@
4
4
  # POSTGRES_PASSWORD / OS_AUTH_SECRET / OS_SECRET_KEY (generate secrets with
5
5
  # `openssl rand -hex 32`), then `docker compose up -d`.
6
6
  #
7
- # Docs: https://docs.objectstack.ai/docs/deployment/self-hosting
7
+ # Docs: https://objectstack.ai/docs/deployment/self-hosting
8
8
 
9
9
  services:
10
10
  app:
@@ -12,27 +12,31 @@ export default defineStack({
12
12
  type: 'app',
13
13
  name: 'Blank Starter',
14
14
  description: 'Minimal ObjectStack environment — a clean slate for building.',
15
- // Protocol compatibility range (ADR-0087 D1): lets an incompatible runtime
16
- // refuse this package at the boundary with the exact migration command,
17
- // instead of crashing later. Kept in lockstep with releases by
18
- // scripts/sync-template-versions.mjs.
15
+ // Protocol compatibility range: the metadata-protocol major this app is
16
+ // authored against. The runtime checks it before it loads anything, so a
17
+ // runtime outside the range refuses this app at the boundary with the exact
18
+ // migration command instead of crashing later. Scaffolding stamped it to
19
+ // match the ObjectStack version you installed — change it when you
20
+ // deliberately move to a new protocol major, not to silence a mismatch.
21
+ // Guide: https://objectstack.ai/docs/upgrading
19
22
  engines: { protocol: '^17' },
20
23
  },
21
24
 
22
- // `automation` backs flow execution and, per ADR-0097, materializes any
23
- // declarative `connectors:` entry into a live, dispatchable connector at boot.
24
- // The connector executors below register their provider factories with it —
25
- // without `automation` loaded they have nowhere to register and boot fails, so
26
- // keep this capability whenever `plugins:` lists a connector.
25
+ // `automation` backs flow execution and materializes any declarative
26
+ // `connectors:` entry into a live, dispatchable connector at boot. The
27
+ // connector executors below register their provider factories with it —
28
+ // without `automation` loaded they have nowhere to register and boot fails,
29
+ // so keep this capability whenever `plugins:` lists a connector.
27
30
  requires: ['automation'],
28
31
 
29
- // Generic connector executors (ADR-0022/0023/0024 + ADR-0097), default-present
30
- // so you can add a `connectors:` entry naming `provider: 'rest' | 'openapi' |
31
- // 'mcp'` and have it materialize with zero host code. Zero-arg = contribute the
32
- // provider factory only. Brand connectors (Slack, …) stay marketplace/opt-in.
33
- // Security (#3055): a declarative `mcp` stdio transport spawns a local process
34
- // from metadata and is denied by default — opt in per host with
32
+ // Generic connector executors, default-present so you can add a `connectors:`
33
+ // entry naming `provider: 'rest' | 'openapi' | 'mcp'` and have it materialize
34
+ // with zero host code. Zero-arg = contribute the provider factory only. Brand
35
+ // connectors (Slack, …) stay marketplace/opt-in.
36
+ // Security: a declarative `mcp` stdio transport spawns a local process from
37
+ // metadata, so it is denied by default — opt in per host with
35
38
  // `new ConnectorMcpPlugin({ declarativeStdio: ['<trusted-command>'] })`.
39
+ // Authoring guide: https://objectstack.ai/docs/automation/connectors
36
40
  plugins: [
37
41
  new ConnectorRestPlugin(),
38
42
  new ConnectorOpenApiPlugin(),
@@ -3,6 +3,9 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
+ "engines": {
7
+ "pnpm": ">=10.15"
8
+ },
6
9
  "scripts": {
7
10
  "dev": "objectstack dev",
8
11
  "start": "objectstack start",
@@ -1,11 +1,20 @@
1
+ # An explicit EMPTY workspace: this project has no member packages, so
2
+ # this file is settings-only. The key is not decoration — pnpm 9.x and
3
+ # 10.0–10.4 parse this file BEFORE they read `engines`, and refuse a file
4
+ # without a `packages:` key outright ("ERROR packages field missing or
5
+ # empty") before resolving a single dependency.
6
+ # Not `packages: ['.']`: that would declare this project a workspace
7
+ # MEMBER — a monorepo root, which it is not.
8
+ packages: []
9
+
1
10
  # pnpm does not run dependency install scripts unless they are approved here.
2
11
  # Without this file a fresh `pnpm install` on pnpm 11 exits 1 with
3
12
  # ERR_PNPM_IGNORED_BUILDS — pnpm 10 only warned, pnpm 11 made it a hard error.
4
13
  #
5
14
  # Both keys are needed; they are read by different pnpm versions:
6
- # allowBuilds pnpm >= 10.31 and pnpm 11+. pnpm 11 reads ONLY this
15
+ # allowBuilds pnpm >= 10.26 and pnpm 11+. pnpm 11 reads ONLY this
7
16
  # one — onlyBuiltDependencies alone still errors.
8
- # onlyBuiltDependencies pnpm 10.0–10.30, which do not understand allowBuilds.
17
+ # onlyBuiltDependencies pnpm 10.0–10.25, which do not understand allowBuilds.
9
18
  #
10
19
  # better-sqlite3 is the native sqlite driver (@objectstack/driver-sql's optional
11
20
  # dependency); esbuild compiles objectstack.config.ts. Both ship prebuilt
@@ -21,3 +30,53 @@ onlyBuiltDependencies:
21
30
  allowBuilds:
22
31
  better-sqlite3: true
23
32
  esbuild: true
33
+
34
+ # Some third-party peer ranges resolve outside what their declaring package
35
+ # states. `pnpm install` reports each one as an unmet peer — which would be the
36
+ # first thing a brand-new project shows you — and none is a real
37
+ # incompatibility:
38
+ #
39
+ # better-auth peers better-sqlite3 ^12.0.0 while the tree resolves 13.x. That
40
+ # peer is optional and covers handing better-auth a raw better-sqlite3
41
+ # `Database`; ObjectStack hands it an ObjectQL adapter instead, so nothing
42
+ # here goes down that path. Measured on the configuration the range does
43
+ # cover — better-auth's own Kysely dialect, running its migrations, sign-up,
44
+ # sign-in and adapter find/update/delete — 1.7.1 behaves identically on
45
+ # better-sqlite3 13.0.3 and on 12.11.1. The upstream range is stale.
46
+ #
47
+ # @better-auth/scim (held at a release candidate deliberately, not by
48
+ # neglect) peers better-call at an exact 1.3.7, while better-auth itself
49
+ # depends on 1.4.0. A better-auth plugin has to share the host's better-call
50
+ # instance, so the single 1.4.0 copy is the correct resolution rather than a
51
+ # skew to repair. This entry retires when SCIM moves off the rc: stable
52
+ # @better-auth/scim 1.7.1 already peers better-call 1.4.0.
53
+ #
54
+ # @better-auth/core, @better-auth/oauth-provider, @better-auth/scim and
55
+ # @better-auth/sso each peer an exact @better-auth/utils 0.4.2, while the
56
+ # tree hands them 0.5.0 — because better-call, better-auth's own HTTP layer,
57
+ # depends on ^0.5.0, and that is the copy your project's plugins resolve
58
+ # their peer against. Measured on the surface the range governs: those four
59
+ # import three symbols in total (base64/base64Url, createHash and, in core,
60
+ # createRandomStringGenerator), 0.5.0 declares all three unchanged, and on
61
+ # the inputs those call sites pass the two versions return identical values —
62
+ # checked again end to end through better-auth with the sso, oauth-provider
63
+ # and scim plugins, where both resolutions produce the same sign-up, sign-in,
64
+ # session, OAuth metadata, PKCE challenge and SCIM/SSO responses. Forcing
65
+ # utils back to 0.4.2 instead would push better-call off its own declared
66
+ # ^0.5.0 — trading four reported skews for one real one.
67
+ # Reported by pnpm 10.15–10.30; pnpm 10.31 changed peer resolution so all
68
+ # four land on 0.4.2 on their own. These four entries retire when the pnpm
69
+ # floor reaches 10.31, or when the four packages accept 0.5.0 upstream —
70
+ # NOT with the SCIM rc pin above, which the utils skew outlives.
71
+ #
72
+ # These suppress the report only: no resolution moves, and the lockfile is
73
+ # byte-identical with and without this block.
74
+
75
+ peerDependencyRules:
76
+ allowedVersions:
77
+ 'better-auth>better-sqlite3': '13'
78
+ '@better-auth/scim>better-call': '1.4.0'
79
+ '@better-auth/core>@better-auth/utils': '0.5.0'
80
+ '@better-auth/oauth-provider>@better-auth/utils': '0.5.0'
81
+ '@better-auth/scim>@better-auth/utils': '0.5.0'
82
+ '@better-auth/sso>@better-auth/utils': '0.5.0'
@@ -21,8 +21,12 @@ export const Note = ObjectSchema.create({
21
21
  }),
22
22
  },
23
23
 
24
- // Org-wide default (OWD): who can see records they don't own. The security
25
- // posture gate (ADR-0090) requires an explicit, authored decision here.
24
+ // Org-wide default (OWD): who can see records they don't own. `private` is
25
+ // owner-only until access is widened by a permission grant or a sharing rule.
26
+ // Declaring it is required, deliberately: `objectstack build` refuses an
27
+ // object that declares no OWD, so the baseline is always an authored decision
28
+ // rather than an accident. The other values, and how to widen access safely:
29
+ // https://objectstack.ai/docs/permissions/sharing-rules
26
30
  sharingModel: 'private',
27
31
 
28
32
  enable: {
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "create-objectstack",
3
- "version": "17.1.0",
3
+ "version": "17.2.0",
4
4
  "description": "Create a new ObjectStack project — npx create-objectstack",
5
5
  "bin": {
6
6
  "create-objectstack": "./bin/create-objectstack.js"
7
7
  },
8
+ "exports": {
9
+ "./created-summary": {
10
+ "types": "./dist/created-summary.d.ts",
11
+ "import": "./dist/created-summary.js"
12
+ }
13
+ },
8
14
  "keywords": [
9
15
  "objectstack",
10
16
  "create",