@happyvertical/smrt-cli 0.53.4 → 0.53.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +2 -0
  2. package/README.md +24 -0
  3. package/agents/app-commands.md +28 -3
  4. package/dist/{ApiKey-BZmbOTnL-DPPF6Swn.js → ApiKey-BZmbOTnL-BbZupUZ3.js} +2 -2
  5. package/dist/ApiKeyCollection-Bfb_nw81-DYAZwx0O.js +87 -0
  6. package/dist/AuditLogCollection-B1YonCAP-Crf-dCr_.js +193 -0
  7. package/dist/{NostrIdentity-n70E0vck-D60BSg6H.js → NostrIdentity-n70E0vck-Di3_eNCq.js} +4 -4
  8. package/dist/NostrIdentityCollection-Ca74dkaL-DvjOg9Xy.js +127 -0
  9. package/dist/{OidcIdentity-Cb1bxreM-B2eV2ahq.js → OidcIdentity-Cb1bxreM-B6XDawZQ.js} +2 -2
  10. package/dist/OidcIdentityCollection-TV8VdK69-ZwKF9iwf.js +86 -0
  11. package/dist/ProfileAssetCollection-B9hacUJa-BS8VMInH.js +101 -0
  12. package/dist/{ProfileCollection-DuJT7Mum-B3Uhqphs.js → ProfileCollection-DuJT7Mum-CCKEl9oT.js} +31 -31
  13. package/dist/ProfileLinkCollection-4uNURz32-Vo7wyan6.js +233 -0
  14. package/dist/ProfileMetadataCollection-DQhbfEfa-OvHyg4k0.js +108 -0
  15. package/dist/ProfileMetafieldCollection-CoBuM_5G-KUHXgANZ.js +145 -0
  16. package/dist/ProfileRelationshipCollection-DUVgaIqf-CedyrKgX.js +161 -0
  17. package/dist/ProfileRelationshipTermCollection-DtA9OM23-BoGhQBSQ.js +107 -0
  18. package/dist/{ProfileRelationshipType-BRiRka6t-Ci-qvFhm.js → ProfileRelationshipType-BRiRka6t-Dezjv5jP.js} +1 -1
  19. package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-HoTmV1q-.js +57 -0
  20. package/dist/app/cli.d.ts +7 -0
  21. package/dist/app/cli.d.ts.map +1 -1
  22. package/dist/app/errors.d.ts +34 -1
  23. package/dist/app/errors.d.ts.map +1 -1
  24. package/dist/app/index.d.ts +4 -3
  25. package/dist/app/index.d.ts.map +1 -1
  26. package/dist/app/launchers.d.ts +11 -0
  27. package/dist/app/launchers.d.ts.map +1 -1
  28. package/dist/app/operations.d.ts +2 -0
  29. package/dist/app/operations.d.ts.map +1 -1
  30. package/dist/app/runtime.d.ts +17 -2
  31. package/dist/app/runtime.d.ts.map +1 -1
  32. package/dist/app/tokens.d.ts +21 -0
  33. package/dist/app/tokens.d.ts.map +1 -0
  34. package/dist/app.js +3 -3
  35. package/dist/{cli-BRFsobey.js → cli-OZsH5cdL.js} +403 -76
  36. package/dist/cli-generator.d.ts.map +1 -1
  37. package/dist/commands/init.d.ts.map +1 -1
  38. package/dist/commands/utilities.d.ts.map +1 -1
  39. package/dist/{commands-dFz1e9A6.js → commands-C8SM-WRo.js} +35 -51
  40. package/dist/database-environment-D_Gk1Brf.js +20 -0
  41. package/dist/database-environment.d.ts +43 -0
  42. package/dist/database-environment.d.ts.map +1 -0
  43. package/dist/dist-BbDBCq3u.js +20 -0
  44. package/dist/{dist-D4BNILLN.js → dist-BcPl3hiD.js} +3 -3
  45. package/dist/{dist-Berwe80z.js → dist-BdrRmnpP.js} +4 -4
  46. package/dist/{dist-EJ63xUEp.js → dist-DbMWqynv.js} +2 -2
  47. package/dist/{dist-C2BqYVu7.js → dist-VNy6NiC_.js} +1654 -1748
  48. package/dist/index.js +15 -10
  49. package/dist/{oidcProvisioningPrimitives-D9OYu5-F-B-kYsBTM.js → oidcProvisioningPrimitives-D9OYu5-F-Cc2KYoAb.js} +5 -5
  50. package/dist/{portability-DmQWSSXA.js → portability-CES1YC2m.js} +1 -1
  51. package/dist/resolveIdentity-B-PK6RwU-JMNQ4YZ7.js +269 -0
  52. package/package.json +15 -15
  53. package/dist/ApiKeyCollection-Bfb_nw81-B1-2YKj8.js +0 -2
  54. package/dist/AuditLogCollection-B1YonCAP-DP4PCCw-.js +0 -2
  55. package/dist/NostrIdentityCollection-Ca74dkaL-C-v57o9N.js +0 -2
  56. package/dist/OidcIdentityCollection-TV8VdK69-BodvOtPJ.js +0 -2
  57. package/dist/ProfileAssetCollection-B9hacUJa-B7PLe0ld.js +0 -2
  58. package/dist/ProfileLinkCollection-4uNURz32-CLY1cBnn.js +0 -2
  59. package/dist/ProfileMetadataCollection-DQhbfEfa-Bzv-E4ep.js +0 -2
  60. package/dist/ProfileMetafieldCollection-CoBuM_5G-D-VqQuY1.js +0 -2
  61. package/dist/ProfileRelationshipCollection-DUVgaIqf-gEkDBmkh.js +0 -2
  62. package/dist/ProfileRelationshipTermCollection-DtA9OM23-DI_awdol.js +0 -2
  63. package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-CaQMLH9f.js +0 -2
  64. package/dist/resolveIdentity-B-PK6RwU-BmAU1Kse.js +0 -2
package/AGENTS.md CHANGED
@@ -45,6 +45,8 @@ smrt dispatch:* # Dispatch management (list/process/retry/cleanup)
45
45
  smrt app <op> # App install/setup/start/migrate/worker…: agents/app-commands.md
46
46
  ```
47
47
 
48
+ DB target: config `database.url` > `DATABASE_URL` > `:memory:` (README).
49
+
48
50
  File-backed SQL/TypeScript migration generation is not supported. SMRT schema
49
51
  migrations are manifest-driven through registered objects and project manifests.
50
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 |
@@ -364,6 +371,23 @@ export default {
364
371
  };
365
372
  ```
366
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
+
367
391
  ### Entry Point Discovery
368
392
 
369
393
  The CLI loads s-m-r-t objects from your project entry point:
@@ -10,9 +10,10 @@ template's scripts so an app's `package.json` scripts become one-liners.
10
10
  | `scripts/smrt-app.mjs <op>` | `smrt app install\|setup\|recover\|start\|stop\|doctor\|open\|backup\|export\|import` |
11
11
  | `scripts/smrt-prepare-migration.mjs` | `smrt app migrate` |
12
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 …`) |
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
14
  | `smrt-mcp-apps.mjs validate-if-present && …smrt-vite.mjs build` | `smrt app build [vite args]` |
15
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>` |
16
17
  | helper modules | `@happyvertical/smrt-cli/app` (side-effect-free subpath) |
17
18
 
18
19
  ## Source
@@ -30,6 +31,12 @@ config loading; `bin/smrt.js` imports `dist/` in-process so SIGTERM reaches
30
31
 
31
32
  ## Contracts that must not drift
32
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.
33
40
  - **State root** `<XDG_STATE_HOME|~/.local/state|~/Library/Application Support|%LOCALAPPDATA%>/.<appId>-<sha256(dataRoot)[:12]>-state`,
34
41
  mode 0700, every ancestor user/root-owned and not group/world-writable,
35
42
  app-bound empty 0600 marker `.smrt-state-<appId>`. Shared with the running
@@ -53,9 +60,27 @@ config loading; `bin/smrt.js` imports `dist/` in-process so SIGTERM reaches
53
60
  `{schemaVersion:1,status:'error',code:'operation-failed',message,recovery,secretValuesIncluded:false}`
54
61
  plus `runtimeCode` when a `LocalRuntimeError` carries one
55
62
  (`migration_failed` + `MIGRATION_FAILED_MESSAGE`). Messages pass through
56
- `redactSecrets()` (secret-named env values, URL userinfo, `token=`, Bearer).
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`).
57
73
  The bootstrap token is written only to mode-0600 `onboarding.json` /
58
- `onboarding-launch.html`; `open` passes the launch file URL, never the token.
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.
59
84
  - **Artifacts**: backup/export/import paths go through
60
85
  `assertExternalArtifactPath` (real path of nearest existing ancestor, never
61
86
  in or over the checkout). Backup refuses an existing destination; export
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { n as __exportAll$1 } from "./NostrIdentity-n70E0vck-D60BSg6H.js";
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-B1-2YKj8.js").then((n) => n.n);
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-DbMWqynv.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-C-v57o9N.js").then((n) => n.n);
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-C-v57o9N.js").then((n) => n.n);
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-C-v57o9N.js").then((n) => n.n);
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 { verifyAuthEvent as a, generateNostrKeypair as i, __exportAll as n, encryptPrivkey as r, NostrIdentity as t };
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 };
@@ -0,0 +1,127 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { n as __exportAll$1, o as encryptPrivkey, s as generateNostrKeypair, t as NostrIdentity } from "./NostrIdentity-n70E0vck-Di3_eNCq.js";
3
+ import { SmrtCollection } from "@happyvertical/smrt-core";
4
+ //#region ../profiles/dist/chunks/NostrIdentityCollection-Ca74dkaL.js
5
+ var NostrIdentityCollection_Ca74dkaL_exports = /* @__PURE__ */ __exportAll({
6
+ n: () => NostrIdentityCollection_exports,
7
+ t: () => NostrIdentityCollection
8
+ });
9
+ var NostrIdentityCollection_exports = /* @__PURE__ */ __exportAll$1({ NostrIdentityCollection: () => NostrIdentityCollection });
10
+ var NostrIdentityCollection = class extends SmrtCollection {
11
+ static _itemClass = NostrIdentity;
12
+ /**
13
+ * Find identities for a profile
14
+ */
15
+ async findByProfile(profileId) {
16
+ return await this.list({ where: { profileId } });
17
+ }
18
+ /**
19
+ * Find identity by public key
20
+ */
21
+ async findByPubkey(pubkey) {
22
+ return await this.findOne({ where: { pubkey } });
23
+ }
24
+ /**
25
+ * Find identity by email
26
+ */
27
+ async findByEmail(email) {
28
+ return await this.findOne({ where: { email: email.toLowerCase() } });
29
+ }
30
+ /**
31
+ * Find identity by NIP-05 username
32
+ */
33
+ async findByNip05(username) {
34
+ return await this.findOne({ where: { nip05Username: username.toLowerCase() } });
35
+ }
36
+ /**
37
+ * Link a new Nostr identity to a profile with a new keypair
38
+ * @param profile - The profile to link to
39
+ * @param email - Email address for this identity
40
+ * @param masterSecret - Server master secret for encryption
41
+ * @param nip05Username - Optional NIP-05 username (defaults to email local part)
42
+ */
43
+ async createForProfile(profile, email, masterSecret, nip05Username) {
44
+ const normalizedEmail = email.toLowerCase();
45
+ if (await this.findByEmail(normalizedEmail)) throw new Error(`Nostr identity already exists for email: ${email}`);
46
+ const keypair = generateNostrKeypair();
47
+ const encrypted = encryptPrivkey(keypair.privkey, masterSecret);
48
+ const username = nip05Username?.toLowerCase() || normalizedEmail.split("@")[0];
49
+ const identity = new NostrIdentity({
50
+ ...this.options,
51
+ profileId: profile.id,
52
+ pubkey: keypair.pubkey,
53
+ encryptedPrivkey: encrypted.ciphertext,
54
+ encryptionIv: encrypted.iv,
55
+ encryptionTag: encrypted.tag,
56
+ email: normalizedEmail,
57
+ nip05Username: username
58
+ });
59
+ await identity.initialize();
60
+ await identity.save();
61
+ return identity;
62
+ }
63
+ /**
64
+ * Link an existing Nostr identity (with encrypted keypair) to a profile
65
+ */
66
+ async linkToProfile(profile, nostrData) {
67
+ if (await this.findByPubkey(nostrData.pubkey)) throw new Error("Nostr identity with this public key already exists");
68
+ if (await this.findByEmail(nostrData.email)) throw new Error("Nostr identity with this email already exists");
69
+ const identity = new NostrIdentity({
70
+ ...this.options,
71
+ profileId: profile.id,
72
+ pubkey: nostrData.pubkey,
73
+ encryptedPrivkey: nostrData.encryptedPrivkey,
74
+ encryptionIv: nostrData.encryptionIv,
75
+ encryptionTag: nostrData.encryptionTag,
76
+ email: nostrData.email.toLowerCase(),
77
+ nip05Username: nostrData.nip05Username?.toLowerCase() || nostrData.email.toLowerCase().split("@")[0]
78
+ });
79
+ await identity.initialize();
80
+ await identity.save();
81
+ return identity;
82
+ }
83
+ /**
84
+ * Unlink a Nostr identity from a profile
85
+ */
86
+ async unlinkFromProfile(profileId, pubkey) {
87
+ const identity = await this.findOne({ where: {
88
+ profileId,
89
+ pubkey
90
+ } });
91
+ if (identity) {
92
+ await identity.delete();
93
+ return true;
94
+ }
95
+ return false;
96
+ }
97
+ /**
98
+ * Get NIP-05 response data for the /.well-known/nostr.json endpoint
99
+ * @param username - Optional username filter, returns all if not provided
100
+ */
101
+ async getNip05Response(username) {
102
+ const where = username ? { nip05Username: username.toLowerCase() } : {};
103
+ const identities = await this.list({ where });
104
+ const names = {};
105
+ for (const identity of identities) if (identity.nip05Username && identity.pubkey) names[identity.nip05Username] = identity.pubkey;
106
+ return { names };
107
+ }
108
+ /**
109
+ * Check if a NIP-05 username is available
110
+ */
111
+ async isNip05Available(username) {
112
+ return await this.findByNip05(username) === null;
113
+ }
114
+ /**
115
+ * Update NIP-05 username for an identity
116
+ */
117
+ async updateNip05Username(identity, newUsername) {
118
+ const normalizedUsername = newUsername.toLowerCase();
119
+ const existing = await this.findByNip05(normalizedUsername);
120
+ if (existing && existing.id !== identity.id) throw new Error(`NIP-05 username "${newUsername}" is already taken`);
121
+ identity.nip05Username = normalizedUsername;
122
+ await identity.save();
123
+ return identity;
124
+ }
125
+ };
126
+ //#endregion
127
+ export { NostrIdentityCollection_Ca74dkaL_exports as n, NostrIdentityCollection as t };
@@ -35,7 +35,7 @@ var OidcIdentity = class extends SmrtObject {
35
35
  * Find identity by issuer and subject
36
36
  */
37
37
  static async findBySubject(issuer, subject, options = {}) {
38
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-TV8VdK69-BodvOtPJ.js").then((n) => n.r);
38
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-TV8VdK69-ZwKF9iwf.js").then((n) => n.r).then((n) => n.r);
39
39
  return await (await OidcIdentityCollection.create(options)).findBySubject(issuer, subject);
40
40
  }
41
41
  /** Build the collision-free natural key for one OIDC issuer subject. */
@@ -58,7 +58,7 @@ var OidcIdentity = class extends SmrtObject {
58
58
  static async findOrCreate(profile, oidcData, options = {}) {
59
59
  const profileId = profile.id;
60
60
  if (typeof profileId !== "string" || !profileId) throw new Error("OidcIdentity.findOrCreate() requires a saved Profile.");
61
- const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-B-PK6RwU-BmAU1Kse.js").then((n) => n.i);
61
+ const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-B-PK6RwU-JMNQ4YZ7.js").then((n) => n.i).then((n) => n.i);
62
62
  const profileOptions = profile.options ?? {};
63
63
  return (await reuseExistingOidcIdentityForProfile(profileId, {
64
64
  email: oidcData.email,
@@ -0,0 +1,86 @@
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 OidcIdentity } from "./OidcIdentity-Cb1bxreM-B6XDawZQ.js";
4
+ import { SmrtCollection } from "@happyvertical/smrt-core";
5
+ //#region ../profiles/dist/chunks/OidcIdentityCollection-TV8VdK69.js
6
+ var OidcIdentityCollection_TV8VdK69_exports = /* @__PURE__ */ __exportAll({
7
+ n: () => OidcIdentityCollection,
8
+ r: () => OidcIdentityCollection_exports,
9
+ t: () => AmbiguousOidcIdentityError
10
+ });
11
+ var OidcIdentityCollection_exports = /* @__PURE__ */ __exportAll$1({
12
+ AmbiguousOidcIdentityError: () => AmbiguousOidcIdentityError,
13
+ OidcIdentityCollection: () => OidcIdentityCollection
14
+ });
15
+ var AmbiguousOidcIdentityError = class extends Error {
16
+ constructor(issuer, subject) {
17
+ super("Multiple OIDC identities match the same issuer and subject.");
18
+ this.issuer = issuer;
19
+ this.subject = subject;
20
+ this.name = "AmbiguousOidcIdentityError";
21
+ }
22
+ issuer;
23
+ subject;
24
+ };
25
+ var OidcIdentityCollection = class extends SmrtCollection {
26
+ static _itemClass = OidcIdentity;
27
+ /**
28
+ * Find identities for a profile
29
+ */
30
+ async findByProfile(profileId) {
31
+ return await this.list({ where: { profileId } });
32
+ }
33
+ /**
34
+ * Find identity by issuer and subject
35
+ */
36
+ async findBySubject(issuer, subject) {
37
+ const result = await this.db.query(`SELECT CAST(id AS VARCHAR) AS id,
38
+ CAST(profile_id AS VARCHAR) AS profile_id
39
+ FROM oidc_identities
40
+ WHERE issuer = ? AND subject = ?
41
+ LIMIT 2`, issuer, subject);
42
+ if (result.rows.length > 1) throw new AmbiguousOidcIdentityError(issuer, subject);
43
+ const row = result.rows[0];
44
+ const id = row?.id;
45
+ const profileId = row?.profile_id;
46
+ if (typeof id !== "string" || typeof profileId !== "string") return null;
47
+ const identity = await this.get({ id });
48
+ if (!identity) return null;
49
+ identity.id = id;
50
+ identity.profileId = profileId;
51
+ return identity;
52
+ }
53
+ /**
54
+ * Find identities by provider
55
+ */
56
+ async findByProvider(provider) {
57
+ return await this.list({ where: { provider } });
58
+ }
59
+ /**
60
+ * Reuse an existing exact OIDC identity for its unchanged Profile.
61
+ *
62
+ * @deprecated New authentication links require the owner-aware,
63
+ * transactional provisioning APIs. This compatibility helper may refresh a
64
+ * legacy Profile type, but refuses to create or rebind authority.
65
+ */
66
+ async linkToProfile(profile, oidcData) {
67
+ return OidcIdentity.findOrCreate(profile, oidcData, this.options);
68
+ }
69
+ /**
70
+ * Unlink an OIDC identity from a profile
71
+ */
72
+ async unlinkFromProfile(profileId, issuer, subject) {
73
+ const identity = await this.findOne({ where: {
74
+ profileId,
75
+ issuer,
76
+ subject
77
+ } });
78
+ if (identity) {
79
+ await identity.delete();
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ };
85
+ //#endregion
86
+ export { OidcIdentityCollection as n, OidcIdentityCollection_TV8VdK69_exports as r, AmbiguousOidcIdentityError as t };