@owlmeans/create-app 0.1.18-rc.58 → 0.1.18-rc.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/consts.local.d.ts +1 -1
- package/build/consts.local.js +1 -1
- package/build/run.js +2 -2
- package/package.json +2 -2
- package/template/AGENTS.md +1 -1
- package/template/README.bare.md +1 -1
- package/template/_agents/skills/owlmeans-code-structure/SKILL.md +12 -7
- package/template/_agents/skills/reuse-code/SKILL.md +1 -1
- package/template/_bare.json +1 -0
- package/template/sources/api/src/app/session/add.ts +4 -16
- package/template/sources/api/src/app/session/list.ts +4 -13
- package/template/sources/api/src/app/session/remove.ts +4 -14
- package/template/sources/api/src/models/session/types.ts +14 -0
- package/template/sources/api/src/models/session.ts +43 -0
- package/template/sources/web/src/consts.ts +3 -0
- package/template/sources/web/src/screens/session.tsx +3 -5
package/README.md
CHANGED
package/build/consts.local.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PackageManager } from './types.js';
|
|
2
|
-
export declare const HELP = "@owlmeans/create-app \u2014 scaffold a fullstack OwlMeans Common app\n\nUsage:\n npm create @owlmeans/app@latest <dir> [options]\n bun create @owlmeans/app <dir> [options]\n yarn create @owlmeans/app <dir> [options]\n npx @owlmeans/create-app@^0.1.18-rc.
|
|
2
|
+
export declare const HELP = "@owlmeans/create-app \u2014 scaffold a fullstack OwlMeans Common app\n\nUsage:\n npm create @owlmeans/app@latest <dir> [options]\n bun create @owlmeans/app <dir> [options]\n yarn create @owlmeans/app <dir> [options]\n npx @owlmeans/create-app@^0.1.18-rc.59 <dir> [options]\n\nGenerates three workspaces (common + api + web) with shadcn UI navigation and\nlayout, no authentication, and a session-scoped in-memory resource on the\nbackend. With --bare the demo is left out and only the working shell remains.\nAgent skills are deployed into the project by default.\n\nOptions:\n --name <name> human-readable app name (default: derived from <dir>)\n --slug <slug> package slug (default: derived from <dir>)\n --lang <code> language of the generated UI text (default: en)\n --description <t> one-line project description for the generated docs\n --bare scaffold the shell only \u2014 no example/demo code\n --pm <bun|npm|yarn> package manager (default: bun)\n --no-install do not install dependencies\n --no-skills do not deploy agent skills via @owlmeans/agent-skills\n --no-git do not run \"git init\"\n --yes, -y proceed without prompts / into a non-empty directory\n --help, -h show this help\n";
|
|
3
3
|
export declare const PMS: PackageManager[];
|
|
4
4
|
/** Dotfiles/dirs are shipped with an underscore prefix so npm does not strip them from the tarball. */
|
|
5
5
|
export declare const DOTFILE_RENAMES: Record<string, string>;
|
package/build/consts.local.js
CHANGED
|
@@ -5,7 +5,7 @@ Usage:
|
|
|
5
5
|
npm create @owlmeans/app@latest <dir> [options]
|
|
6
6
|
bun create @owlmeans/app <dir> [options]
|
|
7
7
|
yarn create @owlmeans/app <dir> [options]
|
|
8
|
-
npx @owlmeans/create-app@^0.1.18-rc.
|
|
8
|
+
npx @owlmeans/create-app@^0.1.18-rc.59 <dir> [options]
|
|
9
9
|
|
|
10
10
|
Generates three workspaces (common + api + web) with shadcn UI navigation and
|
|
11
11
|
layout, no authentication, and a session-scoped in-memory resource on the
|
package/build/run.js
CHANGED
|
@@ -50,7 +50,7 @@ export const run = async (args) => {
|
|
|
50
50
|
log(args.install
|
|
51
51
|
? '\nDeploying agent skills via @owlmeans/agent-skills…'
|
|
52
52
|
: '\nDeploying harness guidance via @owlmeans/agent-skills (general skills only — re-run'
|
|
53
|
-
+ '\n`npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
53
|
+
+ '\n`npx @owlmeans/agent-skills@^0.1.18-rc.50` after installing to add the package-specific ones)…');
|
|
54
54
|
try {
|
|
55
55
|
const result = await installSkills({
|
|
56
56
|
dir: dest,
|
|
@@ -62,7 +62,7 @@ export const run = async (args) => {
|
|
|
62
62
|
help: false,
|
|
63
63
|
});
|
|
64
64
|
if (result.code !== 0) {
|
|
65
|
-
process.stderr.write(` agent-skills exited with code ${result.code} — you can re-run \`npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
65
|
+
process.stderr.write(` agent-skills exited with code ${result.code} — you can re-run \`npx @owlmeans/agent-skills@^0.1.18-rc.50\` later.\n`);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/create-app",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.59",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Scaffold a fullstack OwlMeans Common app — common + api + web workspaces, shadcn UI navigation/layout, no auth, and a session-scoped in-memory resource, or --bare for the demo-free shell. Also usable programmatically via scaffold(). Deploys agent skills via @owlmeans/agent-skills by default.",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"template"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@owlmeans/agent-skills": "^0.1.18-rc.
|
|
33
|
+
"@owlmeans/agent-skills": "^0.1.18-rc.50"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@owlmeans/dep-config": "workspace:*",
|
package/template/AGENTS.md
CHANGED
|
@@ -114,7 +114,7 @@ from the installed `@owlmeans/*` packages. Agents load a skill by topic, or you
|
|
|
114
114
|
explicitly. Copilot and Codex read `.agents/skills/` directly; Claude Code reads the generated
|
|
115
115
|
symlinks in `.claude/skills/` (see "Claude Code" below).
|
|
116
116
|
|
|
117
|
-
- After adding or updating any `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
117
|
+
- After adding or updating any `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills@^0.1.18-rc.50` to refresh
|
|
118
118
|
the deployed skills.
|
|
119
119
|
- `/logging` — before adding any log line, catch block or `console` call (mechanics: `/log`).
|
|
120
120
|
- Deployed files carry an `AUTO-GENERATED` banner and are refreshed in place — never hand-edit them.
|
package/template/README.bare.md
CHANGED
|
@@ -140,16 +140,21 @@ scripts, error classes and schema files.
|
|
|
140
140
|
Applications the viable agent generates follow the same rules, in these fixed shapes:
|
|
141
141
|
|
|
142
142
|
- **Domain model per entity.** `backend/src/models/<entity>/types.ts` declares
|
|
143
|
-
`interface <
|
|
144
|
-
`make<
|
|
143
|
+
`interface <Base>Model` first; `models/<entity>/<base>.ts` exports only
|
|
144
|
+
`make<Base>Model = (ctx: Context): <Base>Model => { const op = async (actor: Actor, …) => …; return { op, … } }`.
|
|
145
145
|
It is built where it is used — `await makeTaskModel(ctx).complete(actorOf(request), id)` — and is
|
|
146
146
|
never registered as a context service. Its member names are the endpoint keys of the entity.
|
|
147
147
|
- **Handlers and job processors**: one plain exported function per file
|
|
148
|
-
(`api/src/app/<entity>/<action>.ts
|
|
149
|
-
the model.
|
|
150
|
-
- **Seed helpers are objects
|
|
151
|
-
`
|
|
152
|
-
|
|
148
|
+
(`api/src/app/<entity>/<action>.ts` beside a GENERATED `index.ts` barrel, `worker/src/jobs/<job>.ts`);
|
|
149
|
+
a handler's body is one call into the model.
|
|
150
|
+
- **Seed helpers are objects** whose members keep the old function names:
|
|
151
|
+
`actorOf(request): Actor` with `actor.inOrganization(…)`, `actor.assertOrganization(…)`,
|
|
152
|
+
`actor.grantedIds(…)`, `actor.organizationScope(…)`; `recordOwnerOf(request)`, `visitOf(request)`,
|
|
153
|
+
`planningAccessOf(request, ctx)`, `landingHandoff`, `visitKey`. Their types live in the module's
|
|
154
|
+
same-named folder (`lib/actor/types.ts`); the old free functions stay as
|
|
155
|
+
`@deprecated generated-app:factory-objects` wrappers so a project generated before still compiles.
|
|
156
|
+
- A model module generated before this shape keeps its plain functions: its callers import them by
|
|
157
|
+
name, so it is extended in that shape and never converted in place.
|
|
153
158
|
- **Resources** keep their maker and their `xResource(ctx)` accessor; the resource's interface lives
|
|
154
159
|
in `resources/<entity>/types.ts`.
|
|
155
160
|
- Shared types in `common` are interfaces.
|
|
@@ -47,7 +47,7 @@ How you research the repo depends on whether `@owlmeans/*` is linked locally:
|
|
|
47
47
|
**https://github.com/owlmeans/common** — `tree.md` and package READMEs — to find the right package.
|
|
48
48
|
|
|
49
49
|
This is the same dev-linked detection `@owlmeans/agent-skills` uses (see its `detectLinked`). After
|
|
50
|
-
adding an `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
50
|
+
adding an `@owlmeans/*` dependency, run `npx @owlmeans/agent-skills@^0.1.18-rc.50` to deploy its
|
|
51
51
|
skill. Prefer an `@owlmeans/*` package over a third-party library or bespoke code whenever one fits.
|
|
52
52
|
|
|
53
53
|
### Never add an OwlMeans dependency without an explicit range
|
package/template/_bare.json
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto'
|
|
2
1
|
import { handlers } from '@owlmeans/server-app'
|
|
3
|
-
import { session
|
|
4
|
-
import {
|
|
2
|
+
import { session } from '__APP_SLUG__-common'
|
|
3
|
+
import { makeSessionModel } from '../../models/session.js'
|
|
5
4
|
import type { Context } from '../../types.js'
|
|
6
5
|
|
|
7
6
|
const handle = handlers<Context>()
|
|
8
7
|
|
|
9
|
-
export const add = handle.body(session.add, async (payload, context, request) =>
|
|
10
|
-
|
|
11
|
-
const resource = context.getStaticResource<SessionItem>(SESSION_ITEMS)
|
|
12
|
-
|
|
13
|
-
const item: SessionItem = {
|
|
14
|
-
id: randomUUID(),
|
|
15
|
-
sessionId: sid,
|
|
16
|
-
text: payload.text,
|
|
17
|
-
createdAt: new Date().toISOString(),
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return await resource.create(item)
|
|
21
|
-
})
|
|
8
|
+
export const add = handle.body(session.add, async (payload, context, request) =>
|
|
9
|
+
await makeSessionModel(context, request.params.sid).add(payload.text))
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { handlers } from '@owlmeans/server-app'
|
|
2
|
-
import { session
|
|
3
|
-
import {
|
|
2
|
+
import { session } from '__APP_SLUG__-common'
|
|
3
|
+
import { makeSessionModel } from '../../models/session.js'
|
|
4
4
|
import type { Context } from '../../types.js'
|
|
5
5
|
|
|
6
6
|
const handle = handlers<Context>()
|
|
7
7
|
|
|
8
|
-
export const list = handle.params(session.list, async (params, context) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// The resource answers the whole question — this session's items, newest first.
|
|
12
|
-
const { items } = await resource.list(
|
|
13
|
-
{ sessionId: params.sid },
|
|
14
|
-
{ sort: [{ field: 'createdAt', order: 'desc' }] }
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
return items
|
|
18
|
-
})
|
|
8
|
+
export const list = handle.params(session.list, async (params, context) =>
|
|
9
|
+
await makeSessionModel(context, params.sid).list())
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { handlers } from '@owlmeans/server-app'
|
|
2
|
-
import { session
|
|
3
|
-
import {
|
|
2
|
+
import { session } from '__APP_SLUG__-common'
|
|
3
|
+
import { makeSessionModel } from '../../models/session.js'
|
|
4
4
|
import type { Context } from '../../types.js'
|
|
5
5
|
|
|
6
6
|
const handle = handlers<Context>()
|
|
7
7
|
|
|
8
|
-
export const remove = handle.params(session.remove, async (params, context) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const existing = await resource.load(params.id)
|
|
12
|
-
// Only remove the item if it belongs to the requesting session.
|
|
13
|
-
if (existing == null || existing.sessionId !== params.sid) {
|
|
14
|
-
return { removed: false }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
await resource.delete(params.id)
|
|
18
|
-
return { removed: true }
|
|
19
|
-
})
|
|
8
|
+
export const remove = handle.params(session.remove, async (params, context) =>
|
|
9
|
+
await makeSessionModel(context, params.sid).remove(params.id))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SessionItem } from '__APP_SLUG__-common'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The items of ONE browser session. The session id is bound when the model is built, so no
|
|
5
|
+
* member can reach another session's items; the member names are the session endpoints' keys.
|
|
6
|
+
*/
|
|
7
|
+
export interface SessionModel {
|
|
8
|
+
/** This session's items, newest first. */
|
|
9
|
+
list: () => Promise<SessionItem[]>
|
|
10
|
+
/** Store a new item under this session. */
|
|
11
|
+
add: (text: string) => Promise<SessionItem>
|
|
12
|
+
/** Remove an item — only when it belongs to this session. */
|
|
13
|
+
remove: (id: string) => Promise<{ removed: boolean }>
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import type { SessionItem } from '__APP_SLUG__-common'
|
|
3
|
+
import { SESSION_ITEMS } from '../consts.js'
|
|
4
|
+
import type { Context } from '../types.js'
|
|
5
|
+
import type { SessionModel } from './session/types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The session domain, bound to the context that holds its resource and to the session it serves.
|
|
9
|
+
* Built where it is used — `makeSessionModel(context, sid).list()` — never stored.
|
|
10
|
+
*/
|
|
11
|
+
export const makeSessionModel = (context: Context, sid: string): SessionModel => {
|
|
12
|
+
const resource = context.getStaticResource<SessionItem>(SESSION_ITEMS)
|
|
13
|
+
|
|
14
|
+
const list: SessionModel['list'] = async () => {
|
|
15
|
+
// The resource answers the whole question — this session's items, newest first.
|
|
16
|
+
const { items } = await resource.list(
|
|
17
|
+
{ sessionId: sid },
|
|
18
|
+
{ sort: [{ field: 'createdAt', order: 'desc' }] }
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return items
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const add: SessionModel['add'] = async text => await resource.create({
|
|
25
|
+
id: randomUUID(),
|
|
26
|
+
sessionId: sid,
|
|
27
|
+
text,
|
|
28
|
+
createdAt: new Date().toISOString(),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const remove: SessionModel['remove'] = async id => {
|
|
32
|
+
const existing = await resource.load(id)
|
|
33
|
+
// Only remove the item if it belongs to this session.
|
|
34
|
+
if (existing == null || existing.sessionId !== sid) {
|
|
35
|
+
return { removed: false }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await resource.delete(id)
|
|
39
|
+
return { removed: true }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { list, add, remove }
|
|
43
|
+
}
|
|
@@ -3,6 +3,9 @@ import { type PanelNavLink, HOME } from '@owlmeans/web-panel'
|
|
|
3
3
|
/** The alias the session items are stored under. Screens address the store by it, never by path. */
|
|
4
4
|
export const SESSION_STATE = 'session-items'
|
|
5
5
|
|
|
6
|
+
/** The `localStorage` key that keeps this browser's session id across reloads. */
|
|
7
|
+
export const SESSION_ID_KEY = '__APP_SLUG__-sid'
|
|
8
|
+
|
|
6
9
|
// The platform/owner credit — "Powered by OwlMeans" and the copyright the platform delivers — is
|
|
7
10
|
// rendered by the shell itself (`NavLayout`'s `Footer`) and is never a footer link: an app link
|
|
8
11
|
// list is places IN the app, and the credit is not one of those.
|
|
@@ -2,18 +2,16 @@ import { useEffect, useState, type FC } from 'react'
|
|
|
2
2
|
import { useStoreList } from '@owlmeans/client'
|
|
3
3
|
import { session, type SessionItem } from '__APP_SLUG__-common'
|
|
4
4
|
import { useContext } from '../context.js'
|
|
5
|
-
import { SESSION_STATE } from '../consts.js'
|
|
5
|
+
import { SESSION_ID_KEY, SESSION_STATE } from '../consts.js'
|
|
6
6
|
import { Button } from '@/components/ui/button'
|
|
7
7
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
8
8
|
import { Input } from '@/components/ui/input'
|
|
9
9
|
|
|
10
|
-
const SID_KEY = '__APP_SLUG__-sid'
|
|
11
|
-
|
|
12
10
|
const sessionId = (): string => {
|
|
13
|
-
let sid = localStorage.getItem(
|
|
11
|
+
let sid = localStorage.getItem(SESSION_ID_KEY)
|
|
14
12
|
if (sid == null) {
|
|
15
13
|
sid = crypto.randomUUID()
|
|
16
|
-
localStorage.setItem(
|
|
14
|
+
localStorage.setItem(SESSION_ID_KEY, sid)
|
|
17
15
|
}
|
|
18
16
|
return sid
|
|
19
17
|
}
|