@happyvertical/smrt-cli 0.53.3 → 0.53.5
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/AGENTS.md +3 -0
- package/README.md +64 -0
- package/agents/app-commands.md +118 -0
- package/bin/smrt-web.mjs +19 -0
- package/bin/smrt.js +18 -12
- package/dist/{ApiKey-BZmbOTnL-DchyhIPA.js → ApiKey-BZmbOTnL-BbZupUZ3.js} +2 -2
- package/dist/ApiKeyCollection-Bfb_nw81-DYAZwx0O.js +87 -0
- package/dist/AuditLogCollection-B1YonCAP-Bkj2Ezon.js +193 -0
- package/dist/{NostrIdentity-n70E0vck-CLGPdJ1W.js → NostrIdentity-n70E0vck-Di3_eNCq.js} +4 -4
- package/dist/NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js +127 -0
- package/dist/{OidcIdentity-Cb1bxreM-DPBTZddF.js → OidcIdentity-Cb1bxreM-LXmC7uAf.js} +2 -2
- package/dist/OidcIdentityCollection-TV8VdK69-Cv1ZTeuk.js +86 -0
- package/dist/ProfileAssetCollection-B9hacUJa-CPuaWLk4.js +101 -0
- package/dist/{ProfileCollection-DuJT7Mum-BUYQQ9xZ.js → ProfileCollection-DuJT7Mum-nXbHrPpX.js} +31 -31
- package/dist/ProfileLinkCollection-4uNURz32-CbKZYmYm.js +233 -0
- package/dist/ProfileMetadataCollection-DQhbfEfa-_1rQCLTR.js +108 -0
- package/dist/ProfileMetafieldCollection-CoBuM_5G-CPjW3l5r.js +145 -0
- package/dist/ProfileRelationshipCollection-DUVgaIqf-RCDqYz6O.js +161 -0
- package/dist/ProfileRelationshipTermCollection-DtA9OM23-BoGhQBSQ.js +107 -0
- package/dist/{ProfileRelationshipType-BRiRka6t-xzRfZRFG.js → ProfileRelationshipType-BRiRka6t-Dezjv5jP.js} +1 -1
- package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-HoTmV1q-.js +57 -0
- package/dist/app/cli.d.ts +51 -0
- package/dist/app/cli.d.ts.map +1 -0
- package/dist/app/errors.d.ts +55 -0
- package/dist/app/errors.d.ts.map +1 -0
- package/dist/app/identity.d.ts +24 -0
- package/dist/app/identity.d.ts.map +1 -0
- package/dist/app/index.d.ts +20 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/launchers.d.ts +51 -0
- package/dist/app/launchers.d.ts.map +1 -0
- package/dist/app/operations.d.ts +30 -0
- package/dist/app/operations.d.ts.map +1 -0
- package/dist/app/portability-assets.d.ts +151 -0
- package/dist/app/portability-assets.d.ts.map +1 -0
- package/dist/app/portability.d.ts +117 -0
- package/dist/app/portability.d.ts.map +1 -0
- package/dist/app/process-record.d.ts +54 -0
- package/dist/app/process-record.d.ts.map +1 -0
- package/dist/app/runtime.d.ts +98 -0
- package/dist/app/runtime.d.ts.map +1 -0
- package/dist/app/tokens.d.ts +21 -0
- package/dist/app/tokens.d.ts.map +1 -0
- package/dist/app.d.ts +1 -0
- package/dist/app.js +4 -0
- package/dist/cli-OZsH5cdL.js +1602 -0
- package/dist/cli-generator.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/utilities.d.ts.map +1 -1
- package/dist/{commands-B8N38deG.js → commands-UXK2F_0j.js} +49 -336
- package/dist/database-environment-D_Gk1Brf.js +20 -0
- package/dist/database-environment.d.ts +43 -0
- package/dist/database-environment.d.ts.map +1 -0
- package/dist/dist-BVjJWjT-.js +20 -0
- package/dist/{dist-B62O-xH6.js → dist-C87Ib9Vx.js} +2 -2
- package/dist/{dist-CL9KGm1j.js → dist-DLw_E34G.js} +1654 -1748
- package/dist/{dist-BBQyIRsO.js → dist-GIaWaUho.js} +4 -4
- package/dist/{dist-C8BOgli4.js → dist-RSE5hCY4.js} +3 -3
- package/dist/index.js +19 -9
- package/dist/mcp-apps-packaging-CixRnxz_.js +286 -0
- package/dist/{oidcProvisioningPrimitives-D9OYu5-F-l8afZs4F.js → oidcProvisioningPrimitives-D9OYu5-F-T9KkkDCd.js} +5 -5
- package/dist/portability-CES1YC2m.js +859 -0
- package/dist/resolveIdentity-B-PK6RwU-C9ogf8YG.js +269 -0
- package/package.json +19 -14
- package/dist/ApiKeyCollection-Bfb_nw81-DR5XF_SZ.js +0 -2
- package/dist/AuditLogCollection-B1YonCAP-PbzALncL.js +0 -2
- package/dist/NostrIdentityCollection-Ca74dkaL-C2r-uDlC.js +0 -2
- package/dist/OidcIdentityCollection-TV8VdK69-2fZROnih.js +0 -2
- package/dist/ProfileAssetCollection-B9hacUJa-D33sifgF.js +0 -2
- package/dist/ProfileLinkCollection-4uNURz32-CHDTinax.js +0 -2
- package/dist/ProfileMetadataCollection-DQhbfEfa-BQTogfVX.js +0 -2
- package/dist/ProfileMetafieldCollection-CoBuM_5G-BXtoIP02.js +0 -2
- package/dist/ProfileRelationshipCollection-DUVgaIqf-CNMliUF3.js +0 -2
- package/dist/ProfileRelationshipTermCollection-DtA9OM23-DPOMtMr7.js +0 -2
- package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-Co3PdH01.js +0 -2
- package/dist/resolveIdentity-B-PK6RwU-B306Ch1I.js +0 -2
package/AGENTS.md
CHANGED
|
@@ -42,8 +42,11 @@ smrt config:export # Export agent config for SSG
|
|
|
42
42
|
smrt init # Init new project
|
|
43
43
|
smrt gnode # Scaffold gnode site
|
|
44
44
|
smrt dispatch:* # Dispatch management (list/process/retry/cleanup)
|
|
45
|
+
smrt app <op> # App install/setup/start/migrate/worker…: agents/app-commands.md
|
|
45
46
|
```
|
|
46
47
|
|
|
48
|
+
DB target: config `database.url` > `DATABASE_URL` > `:memory:` (README).
|
|
49
|
+
|
|
47
50
|
File-backed SQL/TypeScript migration generation is not supported. SMRT schema
|
|
48
51
|
migrations are manifest-driven through registered objects and project manifests.
|
|
49
52
|
|
package/README.md
CHANGED
|
@@ -208,6 +208,13 @@ Run the project's package-manager install command after initialization. Existing
|
|
|
208
208
|
versions are preserved; s-m-r-t workspace projects receive `workspace:*` ranges and
|
|
209
209
|
consumer projects receive the CLI's published release line.
|
|
210
210
|
|
|
211
|
+
The generated `src/lib/server/smrt.ts` exports only `runtime` (`classOptions()`
|
|
212
|
+
and `getCollection()` over `DATABASE_URL`/`DATABASE_TYPE`), which generated API
|
|
213
|
+
routes call per request; it no longer exports the deprecated
|
|
214
|
+
`getCollection`/`getSmrtConfig` accessors. To adopt the full application
|
|
215
|
+
runtime, replace that object with `createSmrtSvelteKitRuntime()` from
|
|
216
|
+
`@happyvertical/smrt-app-runtime/sveltekit` and mount its `handle`/`init`.
|
|
217
|
+
|
|
211
218
|
### Dispatch
|
|
212
219
|
|
|
213
220
|
| Command | Description |
|
|
@@ -242,6 +249,45 @@ consumer projects receive the CLI's published release line.
|
|
|
242
249
|
| `smrt playground dev` | Run the shared workspace host or local app playground |
|
|
243
250
|
| `smrt playground list` | Show discovered playground modules and preview entries |
|
|
244
251
|
|
|
252
|
+
### Application operations (`smrt app`)
|
|
253
|
+
|
|
254
|
+
Operational commands for a generated s-m-r-t application, run from the
|
|
255
|
+
application root. They replace the template's copied `scripts/*.mjs`, so an
|
|
256
|
+
app's `package.json` scripts are one-liners:
|
|
257
|
+
|
|
258
|
+
```json
|
|
259
|
+
{
|
|
260
|
+
"dev": "smrt app dev",
|
|
261
|
+
"build": "smrt app build",
|
|
262
|
+
"preview": "smrt app start",
|
|
263
|
+
"db:migrate": "vite build && smrt app migrate",
|
|
264
|
+
"app:setup": "smrt app setup",
|
|
265
|
+
"app:doctor": "smrt app doctor",
|
|
266
|
+
"worker": "smrt app worker task",
|
|
267
|
+
"worker:schedule": "smrt app worker schedule"
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
| Command | Description |
|
|
272
|
+
|---------|------------|
|
|
273
|
+
| `smrt app install` | Local: setup, start, and open owner onboarding under one operation lock |
|
|
274
|
+
| `smrt app setup` | Build, run `smrt db:migrate` explicitly, and prepare the private owner-onboarding handoff |
|
|
275
|
+
| `smrt app recover` | Local: rotate the single-use owner onboarding invitation |
|
|
276
|
+
| `smrt app start` / `stop` | Local: run the production build on loopback; readiness is proven by app id, process instance, and configuration fingerprint |
|
|
277
|
+
| `smrt app doctor` | Secret-free findings (`invalid-runtime-profile`, `unsafe-local-bind`, `runtime-path-unavailable`, `migration-required`, …); exits 1 on any error (default operation) |
|
|
278
|
+
| `smrt app open` | Open the app, or the pending onboarding launch file |
|
|
279
|
+
| `smrt app backup [destination]` | Local: copy the validated data root to a new private directory outside the checkout |
|
|
280
|
+
| `smrt app export [path]` / `import <path>` | Logical, asset-aware bundle; import requires an empty target (deployed: `SMRT_MAINTENANCE_MODE=true`) |
|
|
281
|
+
| `smrt app migrate` | Establish local storage custody, then `smrt db:migrate`, under the operation lock |
|
|
282
|
+
| `smrt app worker [task\|schedule]` | Deployed: imports `.smrt/runtime/register.js`, then runs the jobs runner until SIGTERM. The kind defaults to `task`; any other value is a usage error before anything starts |
|
|
283
|
+
| `smrt app dev\|build\|vite [args]` | Run the app's installed Vite with `.env` loaded (shell wins); `build` validates `./mcp-apps` first |
|
|
284
|
+
|
|
285
|
+
Success output is JSON on stdout; a failure is one JSON envelope on stderr with
|
|
286
|
+
exit code 1 and never contains secret values. The same primitives (operation
|
|
287
|
+
lock, writer lease, state root, artifact-path custody, portability) are
|
|
288
|
+
importable without side effects from `@happyvertical/smrt-cli/app`. Contract
|
|
289
|
+
details: [agents/app-commands.md](agents/app-commands.md).
|
|
290
|
+
|
|
245
291
|
### Auto-Generated Object Commands
|
|
246
292
|
|
|
247
293
|
For each registered s-m-r-t object, the CLI generates:
|
|
@@ -325,6 +371,23 @@ export default {
|
|
|
325
371
|
};
|
|
326
372
|
```
|
|
327
373
|
|
|
374
|
+
### Database precedence
|
|
375
|
+
|
|
376
|
+
Every `db:*` command resolves its database once, at startup:
|
|
377
|
+
|
|
378
|
+
1. `packages.cli.database.url` from any config layer — an explicit project
|
|
379
|
+
setting always wins, so a stray shell `DATABASE_URL` never retargets it.
|
|
380
|
+
2. `DATABASE_URL`, with the engine from the config's `database.type`, else
|
|
381
|
+
`DATABASE_TYPE` (`sqlite` | `postgres`), else the URL scheme
|
|
382
|
+
(`postgres://` / `postgresql://` → postgres, anything else sqlite). An
|
|
383
|
+
unsupported `DATABASE_TYPE` disables this step with a warning.
|
|
384
|
+
3. The `:memory:` default, which schema commands refuse.
|
|
385
|
+
|
|
386
|
+
`smrt app setup` and `smrt app migrate` hand `smrt db:migrate` the profile's
|
|
387
|
+
database through step 2, so an application does not forward
|
|
388
|
+
`DATABASE_URL` in its own config. The rule is smrt-config's
|
|
389
|
+
`resolveCliDatabaseConfig()`, which `smrt-dev-mcp` uses too.
|
|
390
|
+
|
|
328
391
|
### Entry Point Discovery
|
|
329
392
|
|
|
330
393
|
The CLI loads s-m-r-t objects from your project entry point:
|
|
@@ -345,4 +408,5 @@ If compiled classes cannot be loaded, the CLI falls back to manifest-only mode (
|
|
|
345
408
|
|
|
346
409
|
- `@happyvertical/smrt-core` -- ORM, manifest, code generation
|
|
347
410
|
- `@happyvertical/smrt-config` -- configuration loading
|
|
411
|
+
- `@happyvertical/smrt-app-runtime` -- local storage custody and runtime composition for `smrt app`
|
|
348
412
|
- `@happyvertical/smrt-scanner` -- AST scanning for metadata extraction
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# `smrt app` — application operator commands (#3371)
|
|
2
|
+
|
|
3
|
+
`smrt app <operation>` is the CLI home of the operations a generated SMRT app
|
|
4
|
+
used to ship as copied `scripts/*.mjs`. It is a port, not a redesign: state
|
|
5
|
+
files, JSON output, exit codes, environment variables, and messages match the
|
|
6
|
+
template's scripts so an app's `package.json` scripts become one-liners.
|
|
7
|
+
|
|
8
|
+
| Template script | Command |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `scripts/smrt-app.mjs <op>` | `smrt app install\|setup\|recover\|start\|stop\|doctor\|open\|backup\|export\|import` |
|
|
11
|
+
| `scripts/smrt-prepare-migration.mjs` | `smrt app migrate` |
|
|
12
|
+
| `scripts/smrt-worker.mjs [task\|schedule]` | `smrt app worker [task\|schedule]` |
|
|
13
|
+
| `node --env-file-if-exists=.env scripts/smrt-vite.mjs <args>` | `smrt app vite <args>` (`smrt app dev …` = `vite dev …` plus `--host 127.0.0.1` / `--port $PORT` unless given: `devServerArguments`) |
|
|
14
|
+
| `smrt-mcp-apps.mjs validate-if-present && …smrt-vite.mjs build` | `smrt app build [vite args]` |
|
|
15
|
+
| `scripts/smrt-web.mjs` | `bin/smrt-web.mjs` (spawned by `start`) |
|
|
16
|
+
| — (new, #3413) | `smrt app token [create] --scopes … [--expires 30d] [--label …]`, `token list`, `token revoke <id>` |
|
|
17
|
+
| helper modules | `@happyvertical/smrt-cli/app` (side-effect-free subpath) |
|
|
18
|
+
|
|
19
|
+
## Source
|
|
20
|
+
|
|
21
|
+
`src/app/`: `cli.ts` (dispatch, help, error envelope), `operations.ts`,
|
|
22
|
+
`launchers.ts` (migrate/worker/vite), `identity.ts` (artifact-path custody;
|
|
23
|
+
re-exports identity and state root), `process-record.ts`, `portability.ts`,
|
|
24
|
+
`portability-assets.ts`, `runtime.ts` (context, runners, profile-aware child
|
|
25
|
+
environment). Application ID, configuration fingerprint, state root,
|
|
26
|
+
operation lock, writer lease, stale reclaim, and provider readiness are
|
|
27
|
+
`@happyvertical/smrt-app-runtime`'s (the web process takes the same lease
|
|
28
|
+
without depending on the CLI); `./app` re-exports them unchanged. `main()` dispatches `app` before manifest or
|
|
29
|
+
config loading; `bin/smrt.js` imports `dist/` in-process so SIGTERM reaches
|
|
30
|
+
`smrt app worker`.
|
|
31
|
+
|
|
32
|
+
## Contracts that must not drift
|
|
33
|
+
|
|
34
|
+
- **Profile**: `resolveConfiguredRuntime` and app-runtime's SvelteKit entry
|
|
35
|
+
both call smrt-config's `resolveEffectiveApplicationRuntime(loaded)` (#3446),
|
|
36
|
+
so operator and web process agree: no `runtime` block (or no config) is
|
|
37
|
+
`local`; any present value other than `undefined` (including
|
|
38
|
+
`null`/`false`/`0`/`''`) is validated and fails closed when it is not a
|
|
39
|
+
valid block.
|
|
40
|
+
- **State root** `<XDG_STATE_HOME|~/.local/state|~/Library/Application Support|%LOCALAPPDATA%>/.<appId>-<sha256(dataRoot)[:12]>-state`,
|
|
41
|
+
mode 0700, every ancestor user/root-owned and not group/world-writable,
|
|
42
|
+
app-bound empty 0600 marker `.smrt-state-<appId>`. Shared with the running
|
|
43
|
+
web server (`writer.lease`), so record formats are byte-compatible.
|
|
44
|
+
- **Locks**: `operation.lock` (one operator op; dead owner reclaimed;
|
|
45
|
+
unparsable fails closed; never unlinks a replaced lock) and `writer.lease`
|
|
46
|
+
(one writer; a live op blocks a writer unless it presents that op's
|
|
47
|
+
`instance`, which `start` passes as `SMRT_OPERATION_INSTANCE`). Stale
|
|
48
|
+
records are removed only under an exclusive SQLite transaction on
|
|
49
|
+
`<state>/.smrt-lock-reclaim.sqlite` (kernel lock, released on death), after
|
|
50
|
+
re-reading that the file still holds the exact dead record
|
|
51
|
+
(app-runtime `stale-reclaim.ts`); a contender waits up to 2 s, then fails. Acquisition
|
|
52
|
+
is still a plain `O_EXCL` create, so formats are unchanged.
|
|
53
|
+
- **Storage custody** belongs to `@happyvertical/smrt-app-runtime`: `setup`
|
|
54
|
+
goes through `initializeLocalApplicationRuntime` with an explicit
|
|
55
|
+
`prepareDatabase` that runs `smrt db:migrate`; `migrate` calls
|
|
56
|
+
`prepareLocalDatabaseStorage`; `doctor`/`backup`/`export`/`import` call
|
|
57
|
+
`validateLocalDatabaseStorage`, which never creates or repairs storage.
|
|
58
|
+
- **No runtime schema**: migrations run only through `smrt db:migrate`.
|
|
59
|
+
- **Output**: success JSON on stdout; failure is one stderr line
|
|
60
|
+
`{schemaVersion:1,status:'error',code:'operation-failed',message,recovery,secretValuesIncluded:false}`
|
|
61
|
+
plus `runtimeCode` when a `LocalRuntimeError` carries one
|
|
62
|
+
(`migration_failed` + `MIGRATION_FAILED_MESSAGE`). Messages pass through
|
|
63
|
+
`redactSecrets()` (secret-named env values, URL userinfo, `token=`, Bearer;
|
|
64
|
+
every span is found on the raw text and the union masked at once, so a
|
|
65
|
+
literal value never splits a structural match; existing `[redacted]`
|
|
66
|
+
markers are opaque, so a second pass neither corrupts nor grows text).
|
|
67
|
+
`start` sends the detached web process's stdout/stderr to a fresh 0600
|
|
68
|
+
`<state>/app.log` per start (removed, then created `wx`; a pipe would EPIPE
|
|
69
|
+
once the CLI exits); a start that never proves readiness adds `output` (the
|
|
70
|
+
last ≤ 8 KiB, redacted against the child env before the cut with
|
|
71
|
+
`{ strict: true }`: no 8-character floor for env values or Bearer tokens) and `logFile`
|
|
72
|
+
(`ApplicationStartError`).
|
|
73
|
+
The bootstrap token is written only to mode-0600 `onboarding.json` /
|
|
74
|
+
`onboarding-launch.html`; `open` passes the launch file URL, never the token. `setup`/`recover` print the one-time
|
|
75
|
+
`/setup?token=` URL only to an interactive terminal (`io.operatorTerminal`,
|
|
76
|
+
stderr when both stdio are TTYs), never to stdout/stderr logs or pipes. The
|
|
77
|
+
web runtime deletes the files after a successful owner claim.
|
|
78
|
+
- **`token`** (`tokens.ts`, local only): parses every argument before touching
|
|
79
|
+
state, opens `openLocalMcpTokenStore` (validated custody, uncached
|
|
80
|
+
connection, no writer lease, so it runs beside `app:start`), and serializes
|
|
81
|
+
create/revoke under `operation.lock`. Only the `issued` document carries
|
|
82
|
+
the token (`secretValuesIncluded: true`); list/revoke/error output never
|
|
83
|
+
does.
|
|
84
|
+
- **Artifacts**: backup/export/import paths go through
|
|
85
|
+
`assertExternalArtifactPath` (real path of nearest existing ancestor, never
|
|
86
|
+
in or over the checkout). Backup refuses an existing destination; export
|
|
87
|
+
publishes via `link()` (never replaces); import requires a 0600 regular
|
|
88
|
+
bundle for the same app, an exactly matching manifest schema, and an empty
|
|
89
|
+
target; deployed import requires `SMRT_MAINTENANCE_MODE=true`.
|
|
90
|
+
- **Process identity**: `app.pid` is trusted only when the live command
|
|
91
|
+
line's last two tokens are a path with basename `smrt-web.mjs` and exactly
|
|
92
|
+
`--smrt-instance=<record instance>` (an app started by the template's former
|
|
93
|
+
`scripts/smrt-web.mjs` still matches); `stop` re-reads it immediately before SIGTERM
|
|
94
|
+
(`checkOwnedProcess`: only `gone`/`mismatched` drop `app.pid`; an
|
|
95
|
+
`unverifiable` query keeps it, sends nothing, and fails naming the pid). A `start`
|
|
96
|
+
whose launcher never proves readiness is sent SIGTERM, then SIGKILL; only a
|
|
97
|
+
confirmed exit removes `app.pid`, otherwise the record stays and the error
|
|
98
|
+
names the pid.
|
|
99
|
+
- **Application resolution**: Vite and bare readiness-module specifiers
|
|
100
|
+
resolve from the app's own `node_modules` chain (never `NODE_PATH`).
|
|
101
|
+
Readiness specifiers go through Node's own ESM resolver re-parented to the
|
|
102
|
+
app root (a temporary `module.registerHooks` resolve hook, because the
|
|
103
|
+
`import.meta.resolve` parent argument is still flagged), so `exports`
|
|
104
|
+
precedence, condition order, `null` targets, and invalid targets follow
|
|
105
|
+
Node exactly; the result must stay inside the installed package. Relative
|
|
106
|
+
readiness specifiers resolve against the app root.
|
|
107
|
+
- **Import recovery**: after the database commit the asset journal gains an
|
|
108
|
+
additive `committedAt`. A leftover journal with matching table counts is
|
|
109
|
+
accepted as complete only with that marker for the same bundle digest, or
|
|
110
|
+
when every table's rows equal the bundle's; otherwise recovery refuses
|
|
111
|
+
(`asset-recovery-target-mismatch`) and keeps the journal.
|
|
112
|
+
- `scripts/smrt-portability.mjs`, when an app provides it, still overrides
|
|
113
|
+
the built-in export/import adapter (the documented extension point).
|
|
114
|
+
|
|
115
|
+
Tests: `src/app/__tests__/` (primitives, operations in-process, real CLI
|
|
116
|
+
process, and the template's portability-asset suite ported verbatim); lock,
|
|
117
|
+
lease, state-custody, and readiness cases (including review findings R1, R3,
|
|
118
|
+
T2) live in app-runtime's `src/operator-primitives.test.ts`.
|
package/bin/smrt-web.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Web launcher spawned by `smrt app start` (cwd = application root).
|
|
3
|
+
//
|
|
4
|
+
// The file name and the `--smrt-instance=<32 hex>` argument are the process
|
|
5
|
+
// identity `smrt app stop` verifies before signalling a recorded pid, so a
|
|
6
|
+
// recycled pid is never terminated. Ported from the template's
|
|
7
|
+
// `scripts/smrt-web.mjs`.
|
|
8
|
+
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const instance = process.argv
|
|
13
|
+
.find((value) => value.startsWith('--smrt-instance='))
|
|
14
|
+
?.slice('--smrt-instance='.length);
|
|
15
|
+
if (!instance || !/^[a-f0-9]{32}$/.test(instance)) {
|
|
16
|
+
throw new Error('A valid application process identity is required.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await import(pathToFileURL(resolve(process.cwd(), 'build', 'index.js')).href);
|
package/bin/smrt.js
CHANGED
|
@@ -3,24 +3,30 @@
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { dirname, resolve } from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
7
|
|
|
8
8
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
9
|
const distEntry = resolve(packageRoot, 'dist/index.js');
|
|
10
10
|
const sourceEntry = resolve(packageRoot, 'src/index.ts');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (existsSync(distEntry)) {
|
|
13
|
+
// Run the built CLI in this process so signals reach long-running commands
|
|
14
|
+
// (a container's SIGTERM to `smrt app worker`), and exit codes are its own.
|
|
15
|
+
await import(pathToFileURL(distEntry).href);
|
|
16
|
+
} else {
|
|
17
|
+
const result = spawnSync(
|
|
18
|
+
process.execPath,
|
|
19
|
+
['--import', 'tsx', sourceEntry, ...process.argv.slice(2)],
|
|
20
|
+
{ stdio: 'inherit' },
|
|
21
|
+
);
|
|
15
22
|
|
|
16
|
-
|
|
23
|
+
if (result.error) {
|
|
24
|
+
throw result.error;
|
|
25
|
+
}
|
|
17
26
|
|
|
18
|
-
if (result.
|
|
19
|
-
|
|
20
|
-
}
|
|
27
|
+
if (result.signal) {
|
|
28
|
+
process.kill(process.pid, result.signal);
|
|
29
|
+
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
process.kill(process.pid, result.signal);
|
|
31
|
+
process.exit(result.status ?? 1);
|
|
24
32
|
}
|
|
25
|
-
|
|
26
|
-
process.exit(result.status ?? 1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-
|
|
2
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
3
3
|
import { SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
//#region ../profiles/dist/chunks/ApiKey-BZmbOTnL.js
|
|
@@ -127,7 +127,7 @@ var ApiKey = class extends SmrtObject {
|
|
|
127
127
|
*/
|
|
128
128
|
static async verify(key, options = {}) {
|
|
129
129
|
const keyHash = ApiKey.hashKey(key);
|
|
130
|
-
const { ApiKeyCollection } = await import("./ApiKeyCollection-Bfb_nw81-
|
|
130
|
+
const { ApiKeyCollection } = await import("./ApiKeyCollection-Bfb_nw81-DYAZwx0O.js").then((n) => n.n).then((n) => n.n);
|
|
131
131
|
const apiKey = await (await ApiKeyCollection.create(options)).findOne({ where: { keyHash } });
|
|
132
132
|
if (!apiKey) return null;
|
|
133
133
|
if (!apiKey.isValid()) return null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
3
|
+
import { t as ApiKey } from "./ApiKey-BZmbOTnL-BbZupUZ3.js";
|
|
4
|
+
import { SmrtCollection } from "@happyvertical/smrt-core";
|
|
5
|
+
//#region ../profiles/dist/chunks/ApiKeyCollection-Bfb_nw81.js
|
|
6
|
+
var ApiKeyCollection_Bfb_nw81_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
n: () => ApiKeyCollection_exports,
|
|
8
|
+
t: () => ApiKeyCollection
|
|
9
|
+
});
|
|
10
|
+
var ApiKeyCollection_exports = /* @__PURE__ */ __exportAll$1({ ApiKeyCollection: () => ApiKeyCollection });
|
|
11
|
+
var ApiKeyCollection = class extends SmrtCollection {
|
|
12
|
+
static _itemClass = ApiKey;
|
|
13
|
+
/**
|
|
14
|
+
* Find all keys for a profile
|
|
15
|
+
*/
|
|
16
|
+
async findByProfile(profileId) {
|
|
17
|
+
return await this.list({ where: { profileId } });
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Find active (non-revoked, non-expired) keys for a profile
|
|
21
|
+
*/
|
|
22
|
+
async findActiveByProfile(profileId) {
|
|
23
|
+
return (await this.findByProfile(profileId)).filter((key) => key.isValid());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Find key by hash
|
|
27
|
+
*/
|
|
28
|
+
async findByHash(keyHash) {
|
|
29
|
+
return await this.findOne({ where: { keyHash } });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Verify an API key string and return the record if valid
|
|
33
|
+
*/
|
|
34
|
+
async verify(key) {
|
|
35
|
+
const keyHash = ApiKey.hashKey(key);
|
|
36
|
+
const apiKey = await this.findByHash(keyHash);
|
|
37
|
+
if (!apiKey) return null;
|
|
38
|
+
if (!apiKey.isValid()) return null;
|
|
39
|
+
await apiKey.recordUsage();
|
|
40
|
+
if (!apiKey.isValid()) return null;
|
|
41
|
+
return apiKey;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate a new key for a profile
|
|
45
|
+
*/
|
|
46
|
+
async generateForProfile(profile, options) {
|
|
47
|
+
return await ApiKey.generate(profile, {
|
|
48
|
+
...options,
|
|
49
|
+
db: this.options.db
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Revoke all keys for a profile
|
|
54
|
+
*/
|
|
55
|
+
async revokeAllForProfile(profileId) {
|
|
56
|
+
const keys = await this.findActiveByProfile(profileId);
|
|
57
|
+
for (const key of keys) await key.revoke();
|
|
58
|
+
return keys.length;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Revoke a specific key by prefix
|
|
62
|
+
*/
|
|
63
|
+
async revokeByPrefix(keyPrefix) {
|
|
64
|
+
const key = await this.findOne({ where: { keyPrefix } });
|
|
65
|
+
if (key?.isValid()) {
|
|
66
|
+
await key.revoke();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Clean up expired keys (soft delete by setting revokedAt)
|
|
73
|
+
*/
|
|
74
|
+
async cleanupExpired() {
|
|
75
|
+
const now = /* @__PURE__ */ new Date();
|
|
76
|
+
const allKeys = await this.list({});
|
|
77
|
+
let cleaned = 0;
|
|
78
|
+
for (const key of allKeys) if (key.expiresAt && key.expiresAt < now && !key.revokedAt) {
|
|
79
|
+
key.revokedAt = now;
|
|
80
|
+
await key.save();
|
|
81
|
+
cleaned++;
|
|
82
|
+
}
|
|
83
|
+
return cleaned;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
//#endregion
|
|
87
|
+
export { ApiKeyCollection_Bfb_nw81_exports as n, ApiKeyCollection as t };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
|
|
2
|
+
import { i as tenantId, r as TenantScoped } from "./dist-C87Ib9Vx.js";
|
|
3
|
+
import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
|
|
4
|
+
import { SmrtCollection, SmrtObject, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
5
|
+
//#region ../profiles/dist/chunks/AuditLogCollection-B1YonCAP.js
|
|
6
|
+
var AuditLogCollection_B1YonCAP_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
n: () => AuditLogCollection_exports,
|
|
8
|
+
r: () => AuditLog,
|
|
9
|
+
t: () => AuditLogCollection
|
|
10
|
+
});
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
14
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
15
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
16
|
+
if (kind && result) __defProp(target, key, result);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
var AuditLog = class extends SmrtObject {
|
|
20
|
+
tenantId = null;
|
|
21
|
+
profileId;
|
|
22
|
+
/**
|
|
23
|
+
* Action performed (e.g., 'issue.update', 'feedback.incorporate')
|
|
24
|
+
*/
|
|
25
|
+
action = "";
|
|
26
|
+
/**
|
|
27
|
+
* Type of resource affected (e.g., 'Issue', 'Repository')
|
|
28
|
+
*/
|
|
29
|
+
resourceType = "";
|
|
30
|
+
/**
|
|
31
|
+
* ID of the affected resource
|
|
32
|
+
*/
|
|
33
|
+
resourceId = "";
|
|
34
|
+
/**
|
|
35
|
+
* Source of the action
|
|
36
|
+
*/
|
|
37
|
+
source = "web";
|
|
38
|
+
/**
|
|
39
|
+
* Additional context (CI run ID, request ID, etc.)
|
|
40
|
+
*/
|
|
41
|
+
metadata = {};
|
|
42
|
+
onBehalfOfId;
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
super(options);
|
|
45
|
+
if (options.profileId) this.profileId = options.profileId;
|
|
46
|
+
if (options.action) this.action = options.action;
|
|
47
|
+
if (options.resourceType) this.resourceType = options.resourceType;
|
|
48
|
+
if (options.resourceId) this.resourceId = options.resourceId;
|
|
49
|
+
if (options.source) this.source = options.source;
|
|
50
|
+
if (options.metadata) this.metadata = options.metadata;
|
|
51
|
+
if (options.onBehalfOfId !== void 0) this.onBehalfOfId = options.onBehalfOfId;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the profile who performed the action
|
|
55
|
+
*/
|
|
56
|
+
async getProfile() {
|
|
57
|
+
return await this.getRelated("profileId");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the profile on whose behalf the action was performed (if any)
|
|
61
|
+
*/
|
|
62
|
+
async getOnBehalfOf() {
|
|
63
|
+
if (!this.onBehalfOfId) return null;
|
|
64
|
+
return await this.getRelated("onBehalfOfId");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the effective actor (onBehalfOf if set, otherwise profile)
|
|
68
|
+
*/
|
|
69
|
+
async getEffectiveActor() {
|
|
70
|
+
const onBehalfOf = await this.getOnBehalfOf();
|
|
71
|
+
if (onBehalfOf) return onBehalfOf;
|
|
72
|
+
return await this.getProfile();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create an audit log entry
|
|
76
|
+
*/
|
|
77
|
+
static async record(options) {
|
|
78
|
+
const log = new AuditLog({
|
|
79
|
+
...options,
|
|
80
|
+
profileId: options.profile.id,
|
|
81
|
+
onBehalfOfId: options.onBehalfOf?.id
|
|
82
|
+
});
|
|
83
|
+
await log.initialize();
|
|
84
|
+
await log.save();
|
|
85
|
+
return log;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
__decorateClass([tenantId({ nullable: true })], AuditLog.prototype, "tenantId", 2);
|
|
89
|
+
__decorateClass([foreignKey("Profile", { required: true })], AuditLog.prototype, "profileId", 2);
|
|
90
|
+
__decorateClass([foreignKey("Profile", { nullable: true })], AuditLog.prototype, "onBehalfOfId", 2);
|
|
91
|
+
AuditLog = __decorateClass([TenantScoped({
|
|
92
|
+
mode: "optional",
|
|
93
|
+
allowSuperAdminBypass: true
|
|
94
|
+
}), smrt({
|
|
95
|
+
tableName: "audit_logs",
|
|
96
|
+
api: { include: ["list", "get"] },
|
|
97
|
+
mcp: { include: ["list", "get"] },
|
|
98
|
+
cli: { include: ["list"] }
|
|
99
|
+
})], AuditLog);
|
|
100
|
+
var AuditLogCollection_exports = /* @__PURE__ */ __exportAll$1({ AuditLogCollection: () => AuditLogCollection });
|
|
101
|
+
var AuditLogCollection = class extends SmrtCollection {
|
|
102
|
+
static _itemClass = AuditLog;
|
|
103
|
+
/**
|
|
104
|
+
* Find logs for a profile (actions they performed)
|
|
105
|
+
*/
|
|
106
|
+
async findByProfile(profileId) {
|
|
107
|
+
return await this.list({
|
|
108
|
+
where: { profileId },
|
|
109
|
+
orderBy: "createdAt DESC"
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Find logs for a resource
|
|
114
|
+
*/
|
|
115
|
+
async findByResource(resourceType, resourceId) {
|
|
116
|
+
return await this.list({
|
|
117
|
+
where: {
|
|
118
|
+
resourceType,
|
|
119
|
+
resourceId
|
|
120
|
+
},
|
|
121
|
+
orderBy: "createdAt DESC"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Find logs by action type
|
|
126
|
+
*/
|
|
127
|
+
async findByAction(action) {
|
|
128
|
+
return await this.list({
|
|
129
|
+
where: { action },
|
|
130
|
+
orderBy: "createdAt DESC"
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Find logs by source
|
|
135
|
+
*/
|
|
136
|
+
async findBySource(source) {
|
|
137
|
+
return await this.list({
|
|
138
|
+
where: { source },
|
|
139
|
+
orderBy: "createdAt DESC"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Find logs where actions were performed on behalf of someone
|
|
144
|
+
*/
|
|
145
|
+
async findOnBehalfOf(profileId) {
|
|
146
|
+
return await this.list({
|
|
147
|
+
where: { onBehalfOfId: profileId },
|
|
148
|
+
orderBy: "createdAt DESC"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Record a new audit log entry
|
|
153
|
+
*/
|
|
154
|
+
async record(options) {
|
|
155
|
+
const log = new AuditLog({
|
|
156
|
+
...this.options,
|
|
157
|
+
profileId: options.profile.id,
|
|
158
|
+
action: options.action,
|
|
159
|
+
resourceType: options.resourceType,
|
|
160
|
+
resourceId: options.resourceId,
|
|
161
|
+
source: options.source || "web",
|
|
162
|
+
metadata: options.metadata || {},
|
|
163
|
+
onBehalfOfId: options.onBehalfOf?.id
|
|
164
|
+
});
|
|
165
|
+
await log.initialize();
|
|
166
|
+
await log.save();
|
|
167
|
+
return log;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get recent activity for a profile
|
|
171
|
+
*/
|
|
172
|
+
async getRecentActivity(profileId, limit = 50) {
|
|
173
|
+
return await this.list({
|
|
174
|
+
where: { profileId },
|
|
175
|
+
orderBy: "createdAt DESC",
|
|
176
|
+
limit
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get activity timeline for a resource
|
|
181
|
+
*/
|
|
182
|
+
async getResourceTimeline(resourceType, resourceId) {
|
|
183
|
+
return await this.list({
|
|
184
|
+
where: {
|
|
185
|
+
resourceType,
|
|
186
|
+
resourceId
|
|
187
|
+
},
|
|
188
|
+
orderBy: "createdAt ASC"
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
//#endregion
|
|
193
|
+
export { AuditLogCollection as n, AuditLogCollection_B1YonCAP_exports as r, AuditLog as t };
|
|
@@ -4097,21 +4097,21 @@ var NostrIdentity = class extends SmrtObject {
|
|
|
4097
4097
|
* Find identity by public key
|
|
4098
4098
|
*/
|
|
4099
4099
|
static async findByPubkey(pubkey, options = {}) {
|
|
4100
|
-
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-
|
|
4100
|
+
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js").then((n) => n.n).then((n) => n.n);
|
|
4101
4101
|
return await (await NostrIdentityCollection.create(options)).findOne({ where: { pubkey } });
|
|
4102
4102
|
}
|
|
4103
4103
|
/**
|
|
4104
4104
|
* Find identity by email
|
|
4105
4105
|
*/
|
|
4106
4106
|
static async findByEmail(email, options = {}) {
|
|
4107
|
-
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-
|
|
4107
|
+
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js").then((n) => n.n).then((n) => n.n);
|
|
4108
4108
|
return await (await NostrIdentityCollection.create(options)).findOne({ where: { email: email.toLowerCase() } });
|
|
4109
4109
|
}
|
|
4110
4110
|
/**
|
|
4111
4111
|
* Find identity by NIP-05 username
|
|
4112
4112
|
*/
|
|
4113
4113
|
static async findByNip05(username, options = {}) {
|
|
4114
|
-
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-
|
|
4114
|
+
const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js").then((n) => n.n).then((n) => n.n);
|
|
4115
4115
|
return await (await NostrIdentityCollection.create(options)).findOne({ where: { nip05Username: username.toLowerCase() } });
|
|
4116
4116
|
}
|
|
4117
4117
|
/**
|
|
@@ -4186,4 +4186,4 @@ NostrIdentity = __decorateClass([smrt({
|
|
|
4186
4186
|
cli: { include: ["list", "get"] }
|
|
4187
4187
|
})], NostrIdentity);
|
|
4188
4188
|
//#endregion
|
|
4189
|
-
export {
|
|
4189
|
+
export { deriveEncryptionKey as a, privkeyToNsec as c, verifyNostrSignature as d, decryptPrivkey as i, pubkeyToNpub as l, __exportAll as n, encryptPrivkey as o, computeEventId as r, generateNostrKeypair as s, NostrIdentity as t, verifyAuthEvent as u };
|