@pikku/cli 0.12.85 → 0.12.88

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 (138) hide show
  1. package/console-app/assets/{index-CEv-kj3J.js → index-C3FPwdl7.js} +161 -147
  2. package/console-app/index.html +1 -1
  3. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  4. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  6. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  8. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  9. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +4 -0
  12. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +4 -0
  16. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  17. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  18. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  20. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  21. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  22. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  23. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  24. package/dist/.pikku/function/pikku-functions-meta.gen.json +41 -17
  25. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  26. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  27. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  28. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  29. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  30. package/dist/.pikku/pikku-bootstrap.gen.d.ts +3 -1
  31. package/dist/.pikku/pikku-bootstrap.gen.js +3 -1
  32. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  33. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  34. package/dist/.pikku/pikku-services.gen.d.ts +2 -1
  35. package/dist/.pikku/pikku-services.gen.js +1 -0
  36. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  37. package/dist/.pikku/pikku-types.gen.js +1 -1
  38. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  39. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.d.ts +1 -0
  41. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +6 -0
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +6 -0
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +4 -0
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +5 -0
  45. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -0
  47. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  48. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  49. package/dist/.pikku/schemas/register.gen.js +3 -1
  50. package/dist/.pikku/schemas/schemas/FabricLoginInput.schema.json +1 -1
  51. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  52. package/dist/.pikku/schemas/schemas/PikkuUserAdminFunctionsOutput.schema.json +1 -0
  53. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
  54. package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
  55. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  56. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  57. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  58. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  59. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  60. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  61. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  62. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  63. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  64. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  65. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  66. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +8 -2
  67. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  68. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  69. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  70. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  71. package/dist/bin/pikku-bin.mjs +2 -2
  72. package/dist/bin/pikku.js +22 -0
  73. package/dist/src/fabric/fabric-commands.d.ts +6 -3
  74. package/dist/src/fabric/fabric-commands.js +4 -0
  75. package/dist/src/fabric/functions/deploy.function.js +6 -0
  76. package/dist/src/fabric/functions/init.function.js +2 -2
  77. package/dist/src/fabric/functions/llm-key.function.d.ts +4 -4
  78. package/dist/src/fabric/functions/login.function.d.ts +4 -0
  79. package/dist/src/fabric/functions/login.function.js +45 -4
  80. package/dist/src/fabric/functions/validate.function.js +12 -2
  81. package/dist/src/fabric/lib/config.d.ts +8 -0
  82. package/dist/src/fabric/lib/config.js +15 -1
  83. package/dist/src/fabric/lib/git.js +2 -2
  84. package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +6 -2
  85. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.d.ts +5 -1
  86. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +81 -48
  87. package/dist/src/functions/wirings/auth/pikku-command-auth.js +4 -0
  88. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +6 -2
  89. package/dist/src/functions/wirings/console/serialize-console-functions.d.ts +5 -1
  90. package/dist/src/functions/wirings/console/serialize-console-functions.js +55 -23
  91. package/dist/src/functions/wirings/functions/schemas.js +10 -1
  92. package/dist/src/functions/wirings/graph/pikku-command-graph-wirings.js +2 -0
  93. package/dist/src/functions/wirings/queue/pikku-command-queue.js +10 -2
  94. package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.js +8 -2
  95. package/dist/src/functions/wirings/realtime/serialize-events-scaffold.d.ts +5 -4
  96. package/dist/src/functions/wirings/realtime/serialize-events-scaffold.js +17 -7
  97. package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +6 -2
  98. package/dist/src/functions/wirings/rpc/pikku-command-remote-rpc.js +6 -2
  99. package/dist/src/functions/wirings/rpc/serialize-public-rpc.d.ts +5 -1
  100. package/dist/src/functions/wirings/rpc/serialize-public-rpc.js +16 -5
  101. package/dist/src/functions/wirings/rpc/serialize-remote-rpc.d.ts +5 -1
  102. package/dist/src/functions/wirings/rpc/serialize-remote-rpc.js +16 -5
  103. package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +8 -2
  104. package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +5 -1
  105. package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +22 -16
  106. package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.d.ts +1 -0
  107. package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.js +49 -0
  108. package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.d.ts +24 -0
  109. package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.js +283 -0
  110. package/dist/src/functions/wirings/webhook/pikku-command-webhook.js +6 -2
  111. package/dist/src/functions/wirings/webhook/serialize-webhook.d.ts +5 -8
  112. package/dist/src/functions/wirings/webhook/serialize-webhook.js +19 -12
  113. package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +8 -2
  114. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.d.ts +5 -1
  115. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +61 -31
  116. package/dist/src/functions/workflows/all.workflow.js +8 -0
  117. package/dist/src/utils/pikku-cli-config.js +40 -10
  118. package/dist/src/utils/remove-legacy-scaffold-file.d.ts +3 -0
  119. package/dist/src/utils/remove-legacy-scaffold-file.js +43 -0
  120. package/dist/src/utils/serialize-schemas.js +57 -2
  121. package/dist/tsconfig.tsbuildinfo +1 -1
  122. package/package.json +7 -7
  123. package/skills/pikku-addon/SKILL.md +1 -0
  124. package/skills/pikku-ai-agent/SKILL.md +2 -1
  125. package/skills/pikku-ai-vercel/SKILL.md +1 -0
  126. package/skills/pikku-better-auth/SKILL.md +57 -0
  127. package/skills/pikku-concepts/SKILL.md +17 -1
  128. package/skills/pikku-concepts/references/concept-mapping.md +11 -9
  129. package/skills/pikku-emails/SKILL.md +1 -0
  130. package/skills/pikku-fabric/SKILL.md +92 -19
  131. package/skills/pikku-fabric-debug/SKILL.md +112 -0
  132. package/skills/pikku-permissions/SKILL.md +28 -12
  133. package/skills/pikku-react/SKILL.md +32 -3
  134. package/skills/pikku-react-query/SKILL.md +38 -1
  135. package/skills/pikku-realtime/SKILL.md +1 -1
  136. package/skills/pikku-security/SKILL.md +3 -3
  137. package/skills/pikku-template-clone/SKILL.md +1 -0
  138. package/cli.schema.json +0 -1
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Generate the user-management functions into the project scaffold: the
3
+ * directory read plus the writes that wrap better-auth's `admin()` endpoints.
4
+ *
5
+ * Scaffolded rather than shipped in an addon because managing users is ordinary
6
+ * application behaviour: an app should not have to install the console — which
7
+ * exists to be served alongside a running dev server — just to list or ban its
8
+ * own users. The console UI calls these same functions when it is present.
9
+ *
10
+ * Each function is gated on its own `admin:users:*` scope. The writes are only
11
+ * meaningful where `admin()` is wired, which is why this file is generated only
12
+ * in that case.
13
+ *
14
+ * Emitted as two files. The schemas are zod, and the inspector reads a zod
15
+ * schema by importing the module that declares it — which it cannot do for the
16
+ * functions file, whose relative pikku-types import per-unit deploy codegen
17
+ * rewrites. Keeping the schemas in a sibling module that imports nothing but
18
+ * zod sidesteps that entirely.
19
+ */
20
+ export const serializeUserAdminFunctions = (pathToPikkuTypes, requireAuth = true) => {
21
+ const authFlag = requireAuth ? 'true' : 'false';
22
+ const schemas = `/**
23
+ * Auto-generated user management schemas
24
+ * Do not edit manually - regenerate with 'npx pikku'
25
+ */
26
+ import { z } from 'zod'
27
+
28
+ /**
29
+ * A user, as the directory sees one. Ban state is optional because those
30
+ * columns belong to better-auth's \`admin()\` plugin: a host without it reports
31
+ * no ban state at all, which a client can render as "unknown" rather than as a
32
+ * misleading "not banned".
33
+ */
34
+ export const User = z.object({
35
+ id: z.string(),
36
+ email: z.string(),
37
+ name: z.string().optional(),
38
+ image: z.string().optional(),
39
+ createdAt: z.string().optional(),
40
+ banned: z.boolean().optional(),
41
+ banReason: z.string().optional(),
42
+ banExpires: z.string().optional(),
43
+ })
44
+
45
+ export const ListUsersInput = z.object({
46
+ search: z.string().optional(),
47
+ limit: z.number().int().positive().optional(),
48
+ })
49
+
50
+ export const ListUsersOutput = z.object({
51
+ users: z.array(User),
52
+ })
53
+
54
+ export const CreateUserInput = z.object({
55
+ email: z.string(),
56
+ password: z.string(),
57
+ name: z.string().optional(),
58
+ })
59
+
60
+ export const CreateUserOutput = z.object({
61
+ userId: z.string(),
62
+ })
63
+
64
+ /** Every write targets one user by id. */
65
+ export const UserRef = z.object({
66
+ userId: z.string(),
67
+ })
68
+
69
+ export const SetUserBannedInput = z.object({
70
+ userId: z.string(),
71
+ banned: z.boolean(),
72
+ reason: z.string().optional(),
73
+ expiresInSeconds: z.number().int().positive().optional(),
74
+ })
75
+
76
+ export const SetUserPasswordInput = z.object({
77
+ userId: z.string(),
78
+ newPassword: z.string(),
79
+ })
80
+
81
+ export const Success = z.object({
82
+ success: z.boolean(),
83
+ })
84
+ `;
85
+ const functions = `/**
86
+ * Auto-generated user management functions
87
+ * Do not edit manually - regenerate with 'npx pikku'
88
+ */
89
+ import { pikkuFunc, wireScope } from '${pathToPikkuTypes}'
90
+ import { callAdminApi } from '@pikku/better-auth'
91
+ import {
92
+ CreateUserInput,
93
+ CreateUserOutput,
94
+ ListUsersInput,
95
+ ListUsersOutput,
96
+ SetUserBannedInput,
97
+ SetUserPasswordInput,
98
+ Success,
99
+ UserRef,
100
+ } from './user-admin.schemas.gen.js'
101
+
102
+ // pikku requires every declaration of a shared scope root to be identical, so
103
+ // this is the whole \`admin\` tree — not just the leaves gated below. It must stay
104
+ // byte-identical to ADMIN_SCOPE_TREE in @pikku/better-auth and to the copy in
105
+ // @pikku/addon-console, or codegen fails with conflicting declarations.
106
+ wireScope({
107
+ admin: {
108
+ displayName: 'Administration',
109
+ description: 'Capabilities that act on the application as a whole',
110
+ scopes: {
111
+ impersonate: { description: 'Act as another user' },
112
+ credentials: {
113
+ description: 'Application-wide credentials',
114
+ scopes: {
115
+ link: { description: 'Bind a shared credential for every user' },
116
+ },
117
+ },
118
+ users: {
119
+ description: 'The user directory',
120
+ scopes: {
121
+ list: { description: 'List and search users' },
122
+ create: { description: 'Create users out of band' },
123
+ ban: { description: 'Ban and unban users' },
124
+ remove: { description: 'Delete users and all their data' },
125
+ sessions: { description: "Revoke a user's sessions" },
126
+ password: { description: "Set a user's password" },
127
+ },
128
+ },
129
+ },
130
+ },
131
+ })
132
+
133
+ /**
134
+ * Synthetic principals — the platform credential owner, fabric service users
135
+ * and agent actors — are not people, so they never belong in a directory a
136
+ * human picks from.
137
+ */
138
+ const isPerson = (row: any) =>
139
+ row.fabric !== true && row.actor !== true && row.id !== 'pikku-platform'
140
+
141
+ export const pikkuAdminListUsers = pikkuFunc({
142
+ tags: ['pikku'],
143
+ title: 'List Users',
144
+ description:
145
+ 'Lists and searches the user directory, read through the auth adapter rather than an admin endpoint so it works on any database better-auth supports.',
146
+ expose: true,
147
+ auth: ${authFlag},
148
+ scopes: ['admin:users:list'],
149
+ input: ListUsersInput,
150
+ output: ListUsersOutput,
151
+ func: async ({ auth }, { search, limit }) => {
152
+ const ctx = await (await auth()).$context
153
+ const rows = (await ctx.adapter.findMany({
154
+ model: 'user',
155
+ limit: limit ?? 200,
156
+ where: search
157
+ ? [{ field: 'email', operator: 'contains', value: search }]
158
+ : undefined,
159
+ })) as any[]
160
+
161
+ return {
162
+ users: rows.filter(isPerson).map((row) => ({
163
+ id: row.id,
164
+ email: row.email,
165
+ name: row.name ?? undefined,
166
+ image: row.image ?? undefined,
167
+ createdAt: row.createdAt
168
+ ? new Date(row.createdAt).toISOString()
169
+ : undefined,
170
+ banned: typeof row.banned === 'boolean' ? row.banned : undefined,
171
+ banReason: row.banReason ?? undefined,
172
+ banExpires: row.banExpires
173
+ ? new Date(row.banExpires).toISOString()
174
+ : undefined,
175
+ })),
176
+ }
177
+ },
178
+ })
179
+
180
+ export const pikkuAdminCreateUser = pikkuFunc({
181
+ tags: ['pikku'],
182
+ title: 'Create User',
183
+ description:
184
+ 'Creates a user directly, for provisioning an account out of band rather than through your sign-up flow. better-auth enforces the configured password bounds and rejects a duplicate email.',
185
+ expose: true,
186
+ auth: ${authFlag},
187
+ scopes: ['admin:users:create'],
188
+ input: CreateUserInput,
189
+ output: CreateUserOutput,
190
+ func: async ({ auth }, { email, password, name }, { http }) => {
191
+ const created: any = await callAdminApi(auth, http, (api, headers) =>
192
+ api.createUser!({
193
+ body: { email, password, name: name ?? email },
194
+ headers,
195
+ })
196
+ )
197
+ return { userId: created?.user?.id ?? created?.id }
198
+ },
199
+ })
200
+
201
+ export const pikkuAdminSetUserBanned = pikkuFunc({
202
+ tags: ['pikku'],
203
+ title: 'Ban or Unban User',
204
+ description:
205
+ 'Bans a user — revoking their sessions and blocking sign-in — or lifts an existing ban. An expiry lets the ban lapse on its own; without one it holds until it is lifted.',
206
+ expose: true,
207
+ auth: ${authFlag},
208
+ scopes: ['admin:users:ban'],
209
+ input: SetUserBannedInput,
210
+ output: Success,
211
+ func: async (
212
+ { auth },
213
+ { userId, banned, reason, expiresInSeconds },
214
+ { http }
215
+ ) => {
216
+ await callAdminApi(auth, http, (api, headers) =>
217
+ banned
218
+ ? api.banUser!({
219
+ body: { userId, banReason: reason, banExpiresIn: expiresInSeconds },
220
+ headers,
221
+ })
222
+ : api.unbanUser!({ body: { userId }, headers })
223
+ )
224
+ return { success: true }
225
+ },
226
+ })
227
+
228
+ export const pikkuAdminRemoveUser = pikkuFunc({
229
+ tags: ['pikku'],
230
+ title: 'Remove User',
231
+ description:
232
+ 'Permanently deletes a user along with their sessions and linked accounts. Cannot be undone.',
233
+ expose: true,
234
+ auth: ${authFlag},
235
+ scopes: ['admin:users:remove'],
236
+ input: UserRef,
237
+ output: Success,
238
+ func: async ({ auth }, { userId }, { http }) => {
239
+ await callAdminApi(auth, http, (api, headers) =>
240
+ api.removeUser!({ body: { userId }, headers })
241
+ )
242
+ return { success: true }
243
+ },
244
+ })
245
+
246
+ export const pikkuAdminRevokeUserSessions = pikkuFunc({
247
+ tags: ['pikku'],
248
+ title: 'Revoke User Sessions',
249
+ description:
250
+ 'Signs a user out of every device by deleting all of their sessions. They keep their account and can sign in again.',
251
+ expose: true,
252
+ auth: ${authFlag},
253
+ scopes: ['admin:users:sessions'],
254
+ input: UserRef,
255
+ output: Success,
256
+ func: async ({ auth }, { userId }, { http }) => {
257
+ await callAdminApi(auth, http, (api, headers) =>
258
+ api.revokeUserSessions!({ body: { userId }, headers })
259
+ )
260
+ return { success: true }
261
+ },
262
+ })
263
+
264
+ export const pikkuAdminSetUserPassword = pikkuFunc({
265
+ tags: ['pikku'],
266
+ title: "Set User's Password",
267
+ description:
268
+ 'Sets a user password out of band, for when they cannot complete a reset themselves. better-auth enforces the configured length bounds.',
269
+ expose: true,
270
+ auth: ${authFlag},
271
+ scopes: ['admin:users:password'],
272
+ input: SetUserPasswordInput,
273
+ output: Success,
274
+ func: async ({ auth }, { userId, newPassword }, { http }) => {
275
+ await callAdminApi(auth, http, (api, headers) =>
276
+ api.setUserPassword!({ body: { userId, newPassword }, headers })
277
+ )
278
+ return { success: true }
279
+ },
280
+ })
281
+ `;
282
+ return { schemas, functions };
283
+ };
@@ -2,6 +2,7 @@ import { pikkuSessionlessFunc } from '#pikku';
2
2
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
3
3
  import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
+ import { removeLegacyScaffoldFile } from '../../../utils/remove-legacy-scaffold-file.js';
5
6
  import { serializeWebhook } from './serialize-webhook.js';
6
7
  export const pikkuWebhook = pikkuSessionlessFunc({
7
8
  func: async ({ logger, config, variables }) => {
@@ -9,9 +10,12 @@ export const pikkuWebhook = pikkuSessionlessFunc({
9
10
  if (deployCodegenFlag === '1') {
10
11
  return false;
11
12
  }
12
- if (config.webhookWorkersFile) {
13
+ if (config.webhookWorkersFile && config.webhookSchemasFile) {
13
14
  const pathToPikkuTypes = getFileImportRelativePath(config.webhookWorkersFile, config.typesDeclarationFile, config.packageMappings);
14
- await writeFileInDir(logger, config.webhookWorkersFile, serializeWebhook(pathToPikkuTypes));
15
+ const { schemas, functions } = serializeWebhook(pathToPikkuTypes);
16
+ await writeFileInDir(logger, config.webhookSchemasFile, schemas);
17
+ await writeFileInDir(logger, config.webhookWorkersFile, functions);
18
+ await removeLegacyScaffoldFile(config.webhookWorkersFile);
15
19
  return true;
16
20
  }
17
21
  return false;
@@ -1,14 +1,11 @@
1
+ export interface WebhookGenOutput {
2
+ schemas: string;
3
+ functions: string;
4
+ }
1
5
  /**
2
6
  * Generate the outgoing webhook delivery queue worker.
3
7
  *
4
- * The payload type is an inline TS literal (not zod): the inspector imports a
5
- * zod schema's declaring module at codegen time, and the wiring file's
6
- * pikku-types import can go stale between deploy per-unit codegen runs, which
7
- * would fail schema generation before the wiring gets rewritten. TS-based
8
- * schema generation goes through the type checker instead, so it never
9
- * imports this file.
10
- *
11
8
  * The handler is inlined: it is wired exactly once, so a named export would
12
9
  * only be indirection.
13
10
  */
14
- export declare const serializeWebhook: (pathToPikkuTypes: string) => string;
11
+ export declare const serializeWebhook: (pathToPikkuTypes: string) => WebhookGenOutput;
@@ -1,34 +1,41 @@
1
1
  /**
2
2
  * Generate the outgoing webhook delivery queue worker.
3
3
  *
4
- * The payload type is an inline TS literal (not zod): the inspector imports a
5
- * zod schema's declaring module at codegen time, and the wiring file's
6
- * pikku-types import can go stale between deploy per-unit codegen runs, which
7
- * would fail schema generation before the wiring gets rewritten. TS-based
8
- * schema generation goes through the type checker instead, so it never
9
- * imports this file.
10
- *
11
4
  * The handler is inlined: it is wired exactly once, so a named export would
12
5
  * only be indirection.
13
6
  */
14
7
  export const serializeWebhook = (pathToPikkuTypes) => {
15
- return `/**
8
+ const schemas = `/**
9
+ * Auto-generated outgoing webhook delivery schemas
10
+ * Do not edit manually - regenerate with 'npx pikku'
11
+ */
12
+ import { z } from 'zod'
13
+
14
+ export const WebhookDelivery = z.object({
15
+ url: z.string(),
16
+ event: z.string().optional(),
17
+ body: z.string(),
18
+ headers: z.record(z.string(), z.string()),
19
+ deliveryId: z.string().optional(),
20
+ })
21
+ `;
22
+ const functions = `/**
16
23
  * Auto-generated outgoing webhook delivery queue worker
17
24
  * Do not edit manually - regenerate with 'npx pikku'
18
25
  */
19
26
  import { pikkuSessionlessFunc, wireQueueWorker } from '${pathToPikkuTypes}'
20
27
  import { pikkuWebhookWorkerFunc } from '@pikku/core/services'
28
+ import { WebhookDelivery } from './webhook.schemas.gen.js'
21
29
 
22
30
  wireQueueWorker({
23
31
  name: 'pikku-outgoing-webhooks',
24
32
  tags: ['pikku'],
25
- func: pikkuSessionlessFunc<
26
- { url: string, event?: string, body: string, headers: Record<string, string>, deliveryId?: string },
27
- void
28
- >({
33
+ func: pikkuSessionlessFunc({
29
34
  tags: ['pikku'],
35
+ input: WebhookDelivery,
30
36
  func: async (services, data) => pikkuWebhookWorkerFunc(services, data),
31
37
  }),
32
38
  })
33
39
  `;
40
+ return { schemas, functions };
34
41
  };
@@ -2,12 +2,18 @@ import { pikkuSessionlessFunc } from '#pikku';
2
2
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
3
3
  import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
+ import { removeLegacyScaffoldFile } from '../../../utils/remove-legacy-scaffold-file.js';
5
6
  import { serializeWorkflowRoutes } from './serialize-workflow-routes.js';
6
7
  export const pikkuWorkflowRoutes = pikkuSessionlessFunc({
7
8
  func: async ({ logger, config }) => {
8
- if (config.scaffold?.workflow && config.workflowRoutesFile) {
9
+ if (config.scaffold?.workflow &&
10
+ config.workflowRoutesFile &&
11
+ config.workflowRoutesSchemasFile) {
9
12
  const pathToPikkuTypes = getFileImportRelativePath(config.workflowRoutesFile, config.typesDeclarationFile, config.packageMappings);
10
- await writeFileInDir(logger, config.workflowRoutesFile, serializeWorkflowRoutes(pathToPikkuTypes, config.scaffold?.workflow === 'auth'));
13
+ const { schemas, functions } = serializeWorkflowRoutes(pathToPikkuTypes, config.scaffold?.workflow === 'auth');
14
+ await writeFileInDir(logger, config.workflowRoutesSchemasFile, schemas);
15
+ await writeFileInDir(logger, config.workflowRoutesFile, functions);
16
+ await removeLegacyScaffoldFile(config.workflowRoutesFile);
11
17
  return true;
12
18
  }
13
19
  return false;
@@ -1,4 +1,8 @@
1
+ export interface WorkflowRoutesGenOutput {
2
+ schemas: string;
3
+ functions: string;
4
+ }
1
5
  /**
2
6
  * Generate catch-all HTTP routes for workflow operations
3
7
  */
4
- export declare const serializeWorkflowRoutes: (pathToPikkuTypes: string, requireAuth?: boolean) => string;
8
+ export declare const serializeWorkflowRoutes: (pathToPikkuTypes: string, requireAuth?: boolean) => WorkflowRoutesGenOutput;
@@ -3,13 +3,53 @@
3
3
  */
4
4
  export const serializeWorkflowRoutes = (pathToPikkuTypes, requireAuth = true) => {
5
5
  const authFlag = requireAuth ? 'true' : 'false';
6
- return `/**
6
+ const schemas = `/**
7
+ * Auto-generated workflow route schemas
8
+ * Do not edit manually - regenerate with 'npx pikku'
9
+ */
10
+ import { z } from 'zod'
11
+
12
+ export const WorkflowStart = z.object({
13
+ workflowName: z.string(),
14
+ data: z.unknown().optional(),
15
+ })
16
+
17
+ export const GraphStart = z.object({
18
+ workflowName: z.string(),
19
+ nodeId: z.string(),
20
+ data: z.unknown().optional(),
21
+ })
22
+
23
+ export const WorkflowRunRef = z.object({
24
+ workflowName: z.string(),
25
+ runId: z.string(),
26
+ })
27
+
28
+ export const WorkflowRunId = z.object({ runId: z.string() })
29
+
30
+ export const WorkflowApproval = z.object({
31
+ workflowName: z.string(),
32
+ runId: z.string(),
33
+ reason: z.string(),
34
+ decision: z.unknown(),
35
+ })
36
+
37
+ export const Acknowledged = z.object({ ok: z.literal(true) })
38
+ `;
39
+ const functions = `/**
7
40
  * Workflow HTTP catch-all routes
8
41
  * Do not edit manually - regenerate with 'npx pikku'
9
42
  */
10
43
  import { pikkuSessionlessFunc, wireHTTPRoutes } from '${pathToPikkuTypes}'
11
44
  import { MissingServiceError } from '@pikku/core/errors'
12
- import type { WorkflowRunStatus } from '@pikku/core/workflow'
45
+ import {
46
+ WorkflowStart,
47
+ GraphStart,
48
+ WorkflowRunRef,
49
+ WorkflowRunId,
50
+ WorkflowApproval,
51
+ Acknowledged,
52
+ } from './workflow-routes.schemas.gen.js'
13
53
 
14
54
  function assertWorkflowService(workflowService: unknown): asserts workflowService {
15
55
  if (!workflowService) throw new MissingServiceError('workflowService is required')
@@ -19,12 +59,11 @@ function assertWorkflowRunService(workflowRunService: unknown): asserts workflow
19
59
  if (!workflowRunService) throw new MissingServiceError('workflowRunService is required')
20
60
  }
21
61
 
22
- export const workflowStarter = pikkuSessionlessFunc<
23
- { workflowName: string; data?: unknown },
24
- { runId: string }
25
- >({
62
+ export const workflowStarter = pikkuSessionlessFunc({
26
63
  tags: ['pikku'],
27
64
  auth: ${authFlag},
65
+ input: WorkflowStart,
66
+ output: WorkflowRunId,
28
67
  // workflowService is destructured (even though we delegate via rpc) so the
29
68
  // analyzer assigns workflow-state capability to this unit — without it,
30
69
  // rpc.startWorkflow() runs against a container missing workflowService.
@@ -34,24 +73,20 @@ export const workflowStarter = pikkuSessionlessFunc<
34
73
  },
35
74
  })
36
75
 
37
- export const workflowRunner = pikkuSessionlessFunc<
38
- { workflowName: string; data?: unknown },
39
- unknown
40
- >({
76
+ export const workflowRunner = pikkuSessionlessFunc({
41
77
  tags: ['pikku'],
42
78
  auth: ${authFlag},
79
+ input: WorkflowStart,
43
80
  func: async ({ workflowService }, { workflowName, data }, { rpc }) => {
44
81
  assertWorkflowService(workflowService)
45
82
  return await workflowService.runToCompletion(workflowName, data ?? {}, rpc)
46
83
  },
47
84
  })
48
85
 
49
- export const workflowStatusChecker = pikkuSessionlessFunc<
50
- { workflowName: string; runId: string },
51
- WorkflowRunStatus
52
- >({
86
+ export const workflowStatusChecker = pikkuSessionlessFunc({
53
87
  tags: ['pikku'],
54
88
  auth: ${authFlag},
89
+ input: WorkflowRunRef,
55
90
  func: async ({ workflowService }, { runId }) => {
56
91
  assertWorkflowService(workflowService)
57
92
  const status = await workflowService.getRunStatus(runId)
@@ -64,12 +99,10 @@ export const workflowStatusChecker = pikkuSessionlessFunc<
64
99
  * Minimal workflow status stream — sends step names and statuses only.
65
100
  * Use this for user-facing frontends where internal details should not be exposed.
66
101
  */
67
- export const workflowStatusStream = pikkuSessionlessFunc<
68
- { workflowName: string; runId: string },
69
- unknown
70
- >({
102
+ export const workflowStatusStream = pikkuSessionlessFunc({
71
103
  tags: ['pikku'],
72
104
  auth: ${authFlag},
105
+ input: WorkflowRunRef,
73
106
  func: async ({ workflowRunService }, { runId }, { channel }) => {
74
107
  assertWorkflowRunService(workflowRunService)
75
108
  if (!channel) return
@@ -151,12 +184,10 @@ export const workflowStatusStream = pikkuSessionlessFunc<
151
184
  * Full workflow status stream — includes output, error, and child run IDs.
152
185
  * Use this for admin consoles and internal tooling.
153
186
  */
154
- export const workflowStatusStreamFull = pikkuSessionlessFunc<
155
- { workflowName: string; runId: string },
156
- unknown
157
- >({
187
+ export const workflowStatusStreamFull = pikkuSessionlessFunc({
158
188
  tags: ['pikku'],
159
189
  auth: ${authFlag},
190
+ input: WorkflowRunRef,
160
191
  func: async ({ workflowRunService }, { runId }, { channel }) => {
161
192
  assertWorkflowRunService(workflowRunService)
162
193
  if (!channel) return
@@ -238,12 +269,11 @@ export const workflowStatusStreamFull = pikkuSessionlessFunc<
238
269
  },
239
270
  })
240
271
 
241
- export const graphStarter = pikkuSessionlessFunc<
242
- { workflowName: string; nodeId: string; data?: unknown },
243
- { runId: string }
244
- >({
272
+ export const graphStarter = pikkuSessionlessFunc({
245
273
  tags: ['pikku'],
246
274
  auth: ${authFlag},
275
+ input: GraphStart,
276
+ output: WorkflowRunId,
247
277
  // See workflowStarter — destructure workflowService so the analyzer
248
278
  // assigns workflow-state capability to this unit.
249
279
  func: async ({ workflowService }, { workflowName, nodeId, data }, { rpc }) => {
@@ -261,18 +291,17 @@ export const graphStarter = pikkuSessionlessFunc<
261
291
  * run. Gate this route with your own auth/permissions to control WHO may
262
292
  * approve: the framework does not model approver identity.
263
293
  */
264
- export const workflowApprover = pikkuSessionlessFunc<
265
- { workflowName: string; runId: string; reason: string; decision: unknown },
266
- { ok: true }
267
- >({
294
+ export const workflowApprover = pikkuSessionlessFunc({
268
295
  tags: ['pikku'],
269
296
  auth: ${authFlag},
297
+ input: WorkflowApproval,
298
+ output: Acknowledged,
270
299
  // See workflowStarter — destructure workflowService so the analyzer
271
300
  // assigns workflow-state capability to this unit.
272
301
  func: async ({ workflowService }, { runId, reason, decision }) => {
273
302
  assertWorkflowService(workflowService)
274
303
  await workflowService.approveStep(runId, reason, decision)
275
- return { ok: true }
304
+ return { ok: true as const }
276
305
  },
277
306
  })
278
307
 
@@ -320,4 +349,5 @@ wireHTTPRoutes({
320
349
  },
321
350
  })
322
351
  `;
352
+ return { schemas, functions };
323
353
  };
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { pikkuWorkflowComplexFunc } from '#pikku/workflow/pikku-workflow-types.gen.js';
3
3
  import { assertSingleCoreVersion } from '../../utils/assert-single-core-version.js';
4
+ import { pruneLegacyScaffoldFiles } from '../../utils/remove-legacy-scaffold-file.js';
4
5
  import { PikkuTypecheckFailedError, renderTscFull, renderTscSummary, runProjectTypecheck, } from '../../utils/tsc-check.js';
5
6
  const scaffoldFiles = (config) => {
6
7
  const files = [];
@@ -21,6 +22,11 @@ const scaffoldFiles = (config) => {
21
22
  file: config.scenariosFunctionsFile,
22
23
  generator: 'pikkuScenarioFunctions',
23
24
  });
25
+ if (config.scaffold?.userAdmin && config.userAdminFunctionsFile)
26
+ files.push({
27
+ file: config.userAdminFunctionsFile,
28
+ generator: 'pikkuUserAdminFunctions',
29
+ });
24
30
  if (config.scaffold?.agent && config.publicAgentFile)
25
31
  files.push({
26
32
  file: config.publicAgentFile,
@@ -39,6 +45,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
39
45
  // Preflight: a split @pikku/core install silently breaks wiring registration
40
46
  // (workflows/RPCs "disappear"), so refuse to codegen against it.
41
47
  await assertSingleCoreVersion(config.rootDir, logger);
48
+ await pruneLegacyScaffoldFiles(config);
42
49
  const allImports = [];
43
50
  let typesDeclarationFileExists = true;
44
51
  if (!existsSync(config.outDir)) {
@@ -144,6 +151,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
144
151
  workflow.do('Public RPC', 'pikkuPublicRPC', null),
145
152
  workflow.do('Console functions', 'pikkuConsoleFunctions', null),
146
153
  workflow.do('Scenario functions', 'pikkuScenarioFunctions', null),
154
+ workflow.do('User admin functions', 'pikkuUserAdminFunctions', null),
147
155
  workflow.do('Events scaffold', 'pikkuEventsScaffold', null),
148
156
  workflow.do('Emails', 'pikkuEmails', null),
149
157
  workflow.do('Secret definition types', 'pikkuSecretDefinitionTypes', null),