@pikku/cli 0.12.92 → 0.12.96

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 (196) hide show
  1. package/console-app/assets/{index-DDpIMCpy.js → index-CeQodYfg.js} +151 -149
  2. package/console-app/assets/{index-D0HG8q0B.css → index-CyYHTGWZ.css} +1 -1
  3. package/console-app/index.html +2 -2
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  5. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-channel.d.ts +13 -15
  8. package/dist/.pikku/cli/pikku-cli-channel.js +72 -3
  9. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +14 -4
  10. package/dist/.pikku/cli/pikku-cli-client.gen.js +20 -10
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  14. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  15. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +156 -2
  17. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  19. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  20. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  21. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  22. package/dist/.pikku/function/pikku-function-types.gen.d.ts +55 -20
  23. package/dist/.pikku/function/pikku-function-types.gen.js +27 -1
  24. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.json +176 -13
  26. package/dist/.pikku/function/pikku-functions.gen.js +11 -1
  27. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  28. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  29. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  31. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  37. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  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 +9 -0
  47. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
  48. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
  49. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
  50. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
  51. package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
  52. package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
  53. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  54. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  55. package/dist/.pikku/schemas/register.gen.js +21 -3
  56. package/dist/.pikku/schemas/schemas/KnowledgeIndexInput.schema.json +1 -0
  57. package/dist/.pikku/schemas/schemas/KnowledgeIndexOutput.schema.json +1 -0
  58. package/dist/.pikku/schemas/schemas/KnowledgeValidateInput.schema.json +1 -0
  59. package/dist/.pikku/schemas/schemas/KnowledgeValidateOutput.schema.json +1 -0
  60. package/dist/.pikku/schemas/schemas/PersonaRunInput.schema.json +1 -0
  61. package/dist/.pikku/schemas/schemas/PersonaSyncInput.schema.json +1 -0
  62. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  63. package/dist/.pikku/schemas/schemas/PikkuPersonasInput.schema.json +1 -0
  64. package/dist/.pikku/schemas/schemas/PikkuRolesInput.schema.json +1 -0
  65. package/dist/.pikku/schemas/schemas/RolesAuditInput.schema.json +1 -0
  66. package/dist/.pikku/schemas/schemas/RolesPruneInput.schema.json +1 -0
  67. package/dist/.pikku/scopes/pikku-personas-meta.gen.json +1 -0
  68. package/dist/.pikku/scopes/pikku-personas.gen.d.ts +36 -0
  69. package/dist/.pikku/scopes/pikku-personas.gen.js +17 -0
  70. package/dist/.pikku/scopes/pikku-roles-meta.gen.json +1 -0
  71. package/dist/.pikku/scopes/pikku-roles.gen.d.ts +30 -0
  72. package/dist/.pikku/scopes/pikku-roles.gen.js +18 -0
  73. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +4 -2
  74. package/dist/.pikku/scopes/pikku-scope-types.gen.js +3 -2
  75. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  76. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +2 -2
  77. package/dist/.pikku/secrets/pikku-secret-types.gen.js +2 -2
  78. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  79. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  80. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  81. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  82. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +2 -2
  83. package/dist/.pikku/variables/pikku-variable-types.gen.js +2 -2
  84. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  85. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  86. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +27 -3
  87. package/dist/.pikku/workflow/pikku-personas.gen.d.ts +15 -0
  88. package/dist/.pikku/workflow/pikku-personas.gen.js +17 -0
  89. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +96 -19
  90. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +22 -2
  91. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  92. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  93. package/dist/bin/pikku-bin.mjs +2 -2
  94. package/dist/bin/pikku.js +0 -0
  95. package/dist/src/cli.wiring.js +124 -2
  96. package/dist/src/deploy/analyzer/analyzer.js +14 -0
  97. package/dist/src/deploy/analyzer/manifest.d.ts +11 -0
  98. package/dist/src/fabric/functions/validate.function.js +20 -11
  99. package/dist/src/functions/commands/bootstrap.js +3 -0
  100. package/dist/src/functions/commands/dev-ai-runner.d.ts +16 -4
  101. package/dist/src/functions/commands/dev-ai-runner.js +63 -29
  102. package/dist/src/functions/commands/dev.js +9 -4
  103. package/dist/src/functions/commands/enable.js +15 -19
  104. package/dist/src/functions/commands/{scenario-environment.d.ts → environment.d.ts} +9 -6
  105. package/dist/src/functions/commands/{scenario-environment.js → environment.js} +5 -4
  106. package/dist/src/functions/commands/knowledge-index.d.ts +30 -0
  107. package/dist/src/functions/commands/knowledge-index.js +11 -0
  108. package/dist/src/functions/commands/knowledge-validate.d.ts +33 -0
  109. package/dist/src/functions/commands/knowledge-validate.js +11 -0
  110. package/dist/src/functions/commands/new-addon.d.ts +18 -0
  111. package/dist/src/functions/commands/new-addon.js +52 -36
  112. package/dist/src/functions/commands/persona-sync.d.ts +33 -0
  113. package/dist/src/functions/commands/persona-sync.js +151 -0
  114. package/dist/src/functions/commands/persona.d.ts +58 -0
  115. package/dist/src/functions/commands/persona.js +217 -0
  116. package/dist/src/functions/commands/roles-audit.d.ts +1 -0
  117. package/dist/src/functions/commands/roles-audit.js +37 -0
  118. package/dist/src/functions/commands/roles-prune.d.ts +16 -0
  119. package/dist/src/functions/commands/roles-prune.js +52 -0
  120. package/dist/src/functions/commands/roles-shared.d.ts +40 -0
  121. package/dist/src/functions/commands/roles-shared.js +77 -0
  122. package/dist/src/functions/commands/scenario-browser.d.ts +3 -3
  123. package/dist/src/functions/commands/scenario-browser.js +1 -1
  124. package/dist/src/functions/commands/scenario-ladder.d.ts +44 -2
  125. package/dist/src/functions/commands/scenario-ladder.js +51 -2
  126. package/dist/src/functions/commands/scenario.d.ts +7 -0
  127. package/dist/src/functions/commands/scenario.js +79 -17
  128. package/dist/src/functions/commands/scopes-shared.d.ts +8 -0
  129. package/dist/src/functions/commands/scopes-shared.js +1 -1
  130. package/dist/src/functions/commands/serve.js +7 -3
  131. package/dist/src/functions/commands/virtual-user-formatter.d.ts +30 -0
  132. package/dist/src/functions/commands/virtual-user-formatter.js +65 -0
  133. package/dist/src/functions/db/local-db.js +38 -1
  134. package/dist/src/functions/knowledge/render.d.ts +3 -0
  135. package/dist/src/functions/knowledge/render.js +66 -0
  136. package/dist/src/functions/knowledge/schemas.d.ts +51 -0
  137. package/dist/src/functions/knowledge/schemas.js +25 -0
  138. package/dist/src/functions/validate/workspace-validate.js +1 -1
  139. package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +2 -1
  140. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +1 -0
  141. package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +2 -2
  142. package/dist/src/functions/wirings/auth/serialize-auth-gen.js +8 -8
  143. package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +7 -3
  144. package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -18
  145. package/dist/src/functions/wirings/cli/serialize-channel-cli.js +63 -6
  146. package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -7
  147. package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +8 -1
  148. package/dist/src/functions/wirings/functions/serialize-function-types.js +88 -28
  149. package/dist/src/functions/wirings/middleware/serialize-middleware-imports.js +25 -9
  150. package/dist/src/functions/wirings/package/serialize-package-types.js +5 -3
  151. package/dist/src/functions/wirings/personas/pikku-command-personas.d.ts +7 -0
  152. package/dist/src/functions/wirings/personas/pikku-command-personas.js +43 -0
  153. package/dist/src/functions/wirings/personas/serialize-personas-types.d.ts +18 -0
  154. package/dist/src/functions/wirings/personas/serialize-personas-types.js +76 -0
  155. package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.js +2 -1
  156. package/dist/src/functions/wirings/roles/pikku-command-roles.d.ts +7 -0
  157. package/dist/src/functions/wirings/roles/pikku-command-roles.js +33 -0
  158. package/dist/src/functions/wirings/roles/serialize-roles-types.d.ts +14 -0
  159. package/dist/src/functions/wirings/roles/serialize-roles-types.js +55 -0
  160. package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +2 -1
  161. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +3 -3
  162. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +3 -3
  163. package/dist/src/functions/wirings/scopes/serialize-scopes-types.d.ts +1 -1
  164. package/dist/src/functions/wirings/scopes/serialize-scopes-types.js +1 -1
  165. package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +10 -3
  166. package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.js +2 -1
  167. package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.js +2 -2
  168. package/dist/src/functions/wirings/variables/serialize-variables-types.js +8 -1
  169. package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +2 -1
  170. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +13 -13
  171. package/dist/src/functions/wirings/workflow/serialize-personas.d.ts +10 -0
  172. package/dist/src/functions/wirings/workflow/serialize-personas.js +46 -0
  173. package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +11 -2
  174. package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +165 -29
  175. package/dist/src/functions/workflows/all.workflow.js +7 -1
  176. package/dist/src/server/content-signing-jwt.d.ts +13 -0
  177. package/dist/src/server/content-signing-jwt.js +51 -0
  178. package/dist/src/server/dev-server-runner.interface.d.ts +10 -2
  179. package/dist/src/server/node-server-runner.d.ts +2 -2
  180. package/dist/src/server/node-server-runner.js +2 -1
  181. package/dist/src/services.d.ts +3 -1
  182. package/dist/src/services.js +2 -1
  183. package/dist/src/utils/pikku-cli-config.js +45 -2
  184. package/dist/src/utils/resolve-personas.d.ts +32 -0
  185. package/dist/src/utils/resolve-personas.js +46 -0
  186. package/dist/src/utils/resolve-scaffold-feature.d.ts +19 -0
  187. package/dist/src/utils/resolve-scaffold-feature.js +49 -0
  188. package/dist/tsconfig.tsbuildinfo +1 -1
  189. package/package.json +17 -13
  190. package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +0 -1
  191. package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +0 -19
  192. package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +0 -17
  193. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +0 -2
  194. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +0 -40
  195. package/dist/src/utils/resolve-scenario-actors.d.ts +0 -23
  196. package/dist/src/utils/resolve-scenario-actors.js +0 -74
@@ -7,6 +7,7 @@
7
7
  * Workflow orchestrators dispatch to step units via queue or RPC (inline).
8
8
  */
9
9
  import { resolveDeployTarget, } from '@pikku/inspector';
10
+ import { relative } from 'node:path';
10
11
  import { isScenarioFunction, withoutScenarios, withoutScenarioWorkflows, } from '../../functions/wirings/scenarios/scenario-partition.js';
11
12
  export function analyzeDeployment(state, options) {
12
13
  const serverlessIncompatible = new Set(options.serverlessIncompatible ?? []);
@@ -353,10 +354,22 @@ export function analyzeDeployment(state, options) {
353
354
  }
354
355
  }
355
356
  // ── Secrets & Variables ────────────────────────────────────────────
357
+ const readSecretIds = new Set();
358
+ const unresolvedSecretReads = [];
359
+ for (const [file, usage] of state.secrets.usage ?? []) {
360
+ for (const key of usage.keys) {
361
+ readSecretIds.add(key);
362
+ }
363
+ for (const read of usage.dynamic) {
364
+ unresolvedSecretReads.push(`${relative(state.rootDir, file)}:${read}`);
365
+ }
366
+ }
356
367
  const secrets = state.secrets.definitions.map((s) => ({
357
368
  secretId: s.secretId,
358
369
  displayName: s.displayName,
359
370
  description: s.description,
371
+ allowedHosts: s.allowedHosts,
372
+ read: readSecretIds.has(s.secretId),
360
373
  }));
361
374
  const variables = state.variables.definitions.map((v) => ({
362
375
  variableId: v.variableId,
@@ -374,6 +387,7 @@ export function analyzeDeployment(state, options) {
374
387
  mcpEndpoints,
375
388
  workflows,
376
389
  secrets,
390
+ unresolvedSecretReads: unresolvedSecretReads.sort(),
377
391
  variables,
378
392
  };
379
393
  }
@@ -101,7 +101,13 @@ export interface SecretDeclaration {
101
101
  secretId: string;
102
102
  displayName: string;
103
103
  description?: string;
104
+ /** Hosts this secret may be sent to. Absent means unrestricted. */
105
+ allowedHosts?: string[];
106
+ /** True when some code reads this secret with a statically resolvable key. */
107
+ read?: boolean;
104
108
  }
109
+ /** Secret reads whose key the inspector could not resolve, as `file:line:source`. */
110
+ export type UnresolvedSecretRead = string;
105
111
  export interface VariableDeclaration {
106
112
  variableId: string;
107
113
  displayName: string;
@@ -118,5 +124,10 @@ export interface DeploymentManifest {
118
124
  mcpEndpoints: MCPEndpointDefinition[];
119
125
  workflows: WorkflowDefinition[];
120
126
  secrets: SecretDeclaration[];
127
+ /**
128
+ * Reads with a non-literal key. A deployment can only narrow a unit's secret
129
+ * scope to the declared set when this is empty.
130
+ */
131
+ unresolvedSecretReads: UnresolvedSecretRead[];
121
132
  variables: VariableDeclaration[];
122
133
  }
@@ -6,6 +6,7 @@ import { createRequire } from 'node:module';
6
6
  import { pikkuSessionlessFunc } from '../../../.pikku/pikku-types.gen.js';
7
7
  import { added, changed, removed, dim } from '../lib/output.js';
8
8
  import { typeCheckFrontends } from '../lib/frontend-typecheck.js';
9
+ import { runKnowledgeValidate } from '@pikku/knowledge';
9
10
  const FindingSchema = z.object({
10
11
  id: z.string(),
11
12
  severity: z.enum(['error', 'warn', 'info']),
@@ -243,7 +244,7 @@ export async function runValidate(startDir = process.cwd(), opts = {}) {
243
244
  key: 'console',
244
245
  severity: 'error',
245
246
  surface: 'app introspection (console:getFunctionsMeta and friends) — the sandbox builder shows no functions',
246
- value: '"auth"',
247
+ value: 'true',
247
248
  },
248
249
  {
249
250
  key: 'rpc',
@@ -255,13 +256,13 @@ export async function runValidate(startDir = process.cwd(), opts = {}) {
255
256
  key: 'agent',
256
257
  severity: 'error',
257
258
  surface: 'the agent endpoints (/rpc/agent/:agentName) — the agent playground 404s',
258
- value: '"no-auth"',
259
+ value: 'true',
259
260
  },
260
261
  {
261
262
  key: 'workflow',
262
263
  severity: 'error',
263
264
  surface: 'the workflow endpoints (/workflow/:workflowName/start) — triggering a workflow 404s',
264
- value: '"no-auth"',
265
+ value: 'true',
265
266
  },
266
267
  {
267
268
  key: 'events',
@@ -312,16 +313,21 @@ export async function runValidate(startDir = process.cwd(), opts = {}) {
312
313
  // ── required project files ────────────────────────────────────────────
313
314
  // These files must exist and be committed — they are seeded from the sandbox
314
315
  // but belong to the project so the AI agent can read and update them.
315
- for (const relPath of [
316
- 'db/annotations.ts',
317
- 'knowledge/design-language.md',
318
- 'knowledge/security.md',
319
- 'knowledge/technology.md',
320
- ]) {
316
+ for (const relPath of ['db/annotations.ts']) {
321
317
  if (!existsSync(join(root, relPath))) {
322
318
  w(`missing-required-file-${relPath.replace(/[^a-z0-9]/gi, '-')}`, `${relPath} is missing — this file must be committed to the project`, join(root, relPath), lines(`Create ${relPath} and commit it.`, 'The starter-template ships a stub you can copy as a starting point.'));
323
319
  }
324
320
  }
321
+ // ── knowledge base ─────────────────────────────────────────────────────
322
+ // Delegated to the shared checker rather than asserting a list of filenames.
323
+ // The three flat files this used to require (design-language.md, security.md,
324
+ // technology.md) are not a knowledge base — they are three documents nothing
325
+ // can link into, and the profile now organises the same content into sections
326
+ // of notes. Fabric adds decisions/design/ on top of what is checked here.
327
+ const knowledge = await runKnowledgeValidate(root, join(root, typeof pikkuConfig?.outDir === 'string' ? pikkuConfig.outDir : '.pikku'));
328
+ for (const finding of knowledge.findings) {
329
+ findings.push({ ...finding, path: join(root, finding.path) });
330
+ }
325
331
  const rootPkgPath = join(root, 'package.json');
326
332
  const rootPkg = await readJsonSafe(rootPkgPath);
327
333
  if (!rootPkg) {
@@ -671,11 +677,14 @@ export async function runValidate(startDir = process.cwd(), opts = {}) {
671
677
  // nothing — flagging it told every fresh scaffold to import a dialect it
672
678
  // must not use.
673
679
  const constructsKysely = /new\s+Kysely\s*[<(]/.test(servicesText);
674
- if (dbEngine !== 'postgres' && usesKysely && constructsKysely && !usesLibsql) {
680
+ if (dbEngine !== 'postgres' &&
681
+ usesKysely &&
682
+ constructsKysely &&
683
+ !usesLibsql) {
675
684
  e('services-wrong-db-adapter', 'services.ts uses Kysely but not LibsqlWebDialect — Fabric injects a Turso/libSQL DATABASE_URL at runtime, not a PostgreSQL URL', servicesPath, 'Import LibsqlWebDialect from @pikku/kysely-sqlite and replace the dialect: new Kysely({ dialect: new LibsqlWebDialect({ url: databaseUrl }) })');
676
685
  }
677
686
  if (usesProcessEnv) {
678
- info('services-process-env', 'services.ts reads process.env directly — prefer variables.get() for portable secret/variable access', servicesPath, 'Replace process.env.SOME_VAR with await variables.get("SOME_VAR") — declare the binding with wireVariable/wireSecret; process.env is fine for optional/non-secret config');
687
+ info('services-process-env', 'services.ts reads process.env directly — prefer variables.get() for portable secret/variable access', servicesPath, 'Replace process.env.SOME_VAR with await variables.get("SOME_VAR") — declare the binding with defineVariable/defineSecret; process.env is fine for optional/non-secret config');
679
688
  }
680
689
  if (dbEngine !== 'postgres' &&
681
690
  usesLibsql &&
@@ -5,6 +5,9 @@ export const bootstrap = pikkuVoidFunc({
5
5
  await getInspectorState(false, true, true);
6
6
  // Before function types: they import ScopeId from the scopes codegen.
7
7
  await rpc.invoke('pikkuScopes', { bootstrap: true });
8
+ // Before function types too: personas import SystemRoleName.
9
+ await rpc.invoke('pikkuRoles', { bootstrap: true });
10
+ await rpc.invoke('pikkuPersonas', { bootstrap: true });
8
11
  await rpc.invoke('pikkuFunctionTypesSplit', { bootstrap: true });
9
12
  // Stub auth.types.ts (if better-auth is used) so the pikkuBetterAuth
10
13
  // re-export resolves before any user file is imported.
@@ -7,10 +7,22 @@ import type { AIAgentRunnerService } from '@pikku/core/services';
7
7
  * has no equivalent, so agents 503 with AIProviderNotConfiguredError unless we
8
8
  * construct one here. When an OpenAI-compatible base URL + key are present
9
9
  * (fabric injects LITELLM_PROXY_URL/LITELLM_API_KEY; the standard OPENAI_*
10
- * vars are also honored) we point a single openai-compatible provider at it and
11
- * register it under every common provider prefix. Returns undefined when no AI
12
- * env is configured (agents stay disabled, with the clear downstream error) or
13
- * when the AI SDK packages aren't installed in the project.
10
+ * vars are also honored) we point one provider at it and register it under
11
+ * `'*'`, so every provider prefix resolves to it. The CLI ships the SDK rather
12
+ * than asking the project for it behind a proxy one openai-compatible
13
+ * provider answers for `openai/…`, `anthropic/…` and `google/…` alike, so there
14
+ * was never a per-vendor package worth making somebody install. The project's
15
+ * own copies still win when it has them. Returns undefined when no AI env is
16
+ * configured (agents stay disabled, with the clear downstream error) or when
17
+ * the SDK packages cannot be loaded from either place.
18
+ *
19
+ * Audio does not work here: `@ai-sdk/openai-compatible` exposes only
20
+ * language/embedding/image models, so a voice agent under `pikku dev` throws
21
+ * `Provider does not support transcription models`. Fixing that means the full
22
+ * `@ai-sdk/openai` provider, which assumes OpenAI's own request specifics — a
23
+ * bad default for a path whose entire purpose is fronting arbitrary gateways.
24
+ * If it becomes worth it, delegate just `transcription`/`speech` to the full
25
+ * provider and leave everything else on this one.
14
26
  */
15
27
  export declare function createDevAIAgentRunner({ logger, projectRoot, variables, }: {
16
28
  logger: Logger;
@@ -1,19 +1,6 @@
1
1
  import { createRequire } from 'module';
2
2
  import { join } from 'path';
3
3
  import { pathToFileURL } from 'url';
4
- // Provider prefixes a fabric/proxy baseURL fronts. Models are written as
5
- // `openai/gpt-4o-mini`, `openai/deepseek-v4-flash`, etc. — the runner splits on
6
- // the first `/`, so the prefix only selects the provider entry and the bare
7
- // model name is forwarded to the OpenAI-compatible proxy, which routes it.
8
- const PROXY_PROVIDER_NAMES = [
9
- 'openai',
10
- 'anthropic',
11
- 'google',
12
- 'gemini',
13
- 'deepseek',
14
- 'xai',
15
- 'litellm',
16
- ];
17
4
  /**
18
5
  * Build the AI agent runner for `pikku dev` from env.
19
6
  *
@@ -21,10 +8,22 @@ const PROXY_PROVIDER_NAMES = [
21
8
  * has no equivalent, so agents 503 with AIProviderNotConfiguredError unless we
22
9
  * construct one here. When an OpenAI-compatible base URL + key are present
23
10
  * (fabric injects LITELLM_PROXY_URL/LITELLM_API_KEY; the standard OPENAI_*
24
- * vars are also honored) we point a single openai-compatible provider at it and
25
- * register it under every common provider prefix. Returns undefined when no AI
26
- * env is configured (agents stay disabled, with the clear downstream error) or
27
- * when the AI SDK packages aren't installed in the project.
11
+ * vars are also honored) we point one provider at it and register it under
12
+ * `'*'`, so every provider prefix resolves to it. The CLI ships the SDK rather
13
+ * than asking the project for it behind a proxy one openai-compatible
14
+ * provider answers for `openai/…`, `anthropic/…` and `google/…` alike, so there
15
+ * was never a per-vendor package worth making somebody install. The project's
16
+ * own copies still win when it has them. Returns undefined when no AI env is
17
+ * configured (agents stay disabled, with the clear downstream error) or when
18
+ * the SDK packages cannot be loaded from either place.
19
+ *
20
+ * Audio does not work here: `@ai-sdk/openai-compatible` exposes only
21
+ * language/embedding/image models, so a voice agent under `pikku dev` throws
22
+ * `Provider does not support transcription models`. Fixing that means the full
23
+ * `@ai-sdk/openai` provider, which assumes OpenAI's own request specifics — a
24
+ * bad default for a path whose entire purpose is fronting arbitrary gateways.
25
+ * If it becomes worth it, delegate just `transcription`/`speech` to the full
26
+ * provider and leave everything else on this one.
28
27
  */
29
28
  export async function createDevAIAgentRunner({ logger, projectRoot, variables, }) {
30
29
  // Pair the URL with its matching key — coalescing each var independently could
@@ -43,28 +42,63 @@ export async function createDevAIAgentRunner({ logger, projectRoot, variables, }
43
42
  logger.debug('pikku dev: no AI provider env (OPENAI_BASE_URL/OPENAI_API_KEY or LITELLM_PROXY_URL/LITELLM_API_KEY) — AI agents disabled');
44
43
  return undefined;
45
44
  }
46
- // Resolve from the project's node_modules the AI SDK packages are the
47
- // project's deps, not the CLI's, so they share the project's `ai` version.
48
- const require = createRequire(pathToFileURL(join(projectRoot, 'package.json')).href);
45
+ // The project's own copies win, so an app that pins an `ai` version gets a
46
+ // runner built from that one rather than a second copy alongside it. The
47
+ // CLI's are the fallback: this function exists for projects that have not
48
+ // installed an AI SDK at all, and behind a proxy a single openai-compatible
49
+ // provider reaches every model, so making someone add three packages before
50
+ // agents work in dev buys nothing.
51
+ const fromProject = createRequire(pathToFileURL(join(projectRoot, 'package.json')).href);
52
+ //
53
+ // Both come from the same place or neither does. A provider built against one
54
+ // copy of `@ai-sdk/provider` handed to a runner built against another is the
55
+ // one combination that fails at call time rather than at load time, with an
56
+ // error about the model spec that names neither package.
57
+ const loadPair = async () => {
58
+ const project = ['@pikku/ai-vercel', '@ai-sdk/openai-compatible'].map((name) => {
59
+ try {
60
+ return pathToFileURL(fromProject.resolve(name)).href;
61
+ }
62
+ catch {
63
+ return undefined;
64
+ }
65
+ });
66
+ const [runner, provider] = project.every(Boolean)
67
+ ? project
68
+ : ['@pikku/ai-vercel', '@ai-sdk/openai-compatible'];
69
+ return Promise.all([import(runner), import(provider)]);
70
+ };
49
71
  let VercelAIAgentRunner;
50
72
  let createOpenAICompatible;
51
73
  try {
52
- ;
53
- ({ VercelAIAgentRunner } = await import(pathToFileURL(require.resolve('@pikku/ai-vercel')).href));
54
- ({ createOpenAICompatible } = await import(pathToFileURL(require.resolve('@ai-sdk/openai-compatible')).href));
74
+ const [runnerModule, providerModule] = await loadPair();
75
+ ({ VercelAIAgentRunner } = runnerModule);
76
+ ({ createOpenAICompatible } = providerModule);
55
77
  }
56
78
  catch (error) {
57
- logger.warn(`pikku dev: AI provider env is set but the AI SDK packages could not be loaded (install @pikku/ai-vercel, @ai-sdk/openai-compatible, and ai) — AI agents disabled: ${error instanceof Error ? error.message : String(error)}`);
79
+ logger.warn(`pikku dev: AI provider env is set but the AI SDK packages could not be loaded — AI agents disabled: ${error instanceof Error ? error.message : String(error)}`);
58
80
  return undefined;
59
81
  }
60
- const buildProviders = (key) => {
61
- const provider = createOpenAICompatible({
82
+ // One provider under '*', so every prefix a model might name resolves to the
83
+ // proxy including ones nobody thought to list. This was a hardcoded array
84
+ // of seven names, which silently excluded everything else.
85
+ //
86
+ // `supportsStructuredOutputs` defaults to false, and the default fails in the
87
+ // worst available way: an `outputSchema` is dropped, the call degrades to
88
+ // bare `json_object`, and the model returns well-formed JSON with whatever
89
+ // keys it felt like. Nothing errors — the caller reads `undefined` off every
90
+ // field it asked for and behaves as though the model said nothing. Anything
91
+ // fronting this URL that is worth calling speaks `json_schema`; the ones that
92
+ // do not now fail loudly on the first schema'd call, which is the outcome we
93
+ // want over silently ignoring the schema.
94
+ const buildProviders = (key) => ({
95
+ '*': createOpenAICompatible({
62
96
  name: 'pikku-dev',
63
97
  baseURL,
64
98
  apiKey: key,
65
- });
66
- return Object.fromEntries(PROXY_PROVIDER_NAMES.map((name) => [name, provider]));
67
- };
99
+ supportsStructuredOutputs: true,
100
+ }),
101
+ });
68
102
  logger.info(`pikku dev: AI agent runner wired to ${baseURL}`);
69
103
  return new VercelAIAgentRunner(buildProviders(apiKey), buildProviders);
70
104
  }
@@ -16,6 +16,8 @@ import { startCoverageService } from './start-coverage.js';
16
16
  import { createDevAIAgentRunner } from './dev-ai-runner.js';
17
17
  import { resolveConsoleMount } from './serve-console.js';
18
18
  import { serverReadyLine } from '../../server/server-ready.js';
19
+ import { createEphemeralContentSigningJWT } from '../../server/content-signing-jwt.js';
20
+ import { resolveScaffoldFeature } from '../../utils/resolve-scaffold-feature.js';
19
21
  export const dev = pikkuSessionlessFunc({
20
22
  remote: true,
21
23
  func: async ({ logger, config, getInspectorState, invalidateInspectorState, variables, devServerRunner, }, { port, watch, hmr, coverage, test }, { rpc }) => {
@@ -122,7 +124,7 @@ export const dev = pikkuSessionlessFunc({
122
124
  // project — an app bootstrap is what a deployed bundle imports, and coverage
123
125
  // endpoints have no business in one.
124
126
  if (config.scaffold?.scenarios) {
125
- registerScenarioInstrumentation(config.scaffold.scenarios === 'auth');
127
+ registerScenarioInstrumentation(resolveScaffoldFeature('scenarios', config.scaffold.scenarios).auth);
126
128
  }
127
129
  const configModule = await loadUserModule(pikkuConfigFactory.file);
128
130
  const servicesModule = await loadUserModule(singletonServicesFactory.file);
@@ -150,8 +152,11 @@ export const dev = pikkuSessionlessFunc({
150
152
  sizeLimit: userConfig.content.sizeLimit,
151
153
  }
152
154
  : undefined;
153
- const localContent = localContentConfig
154
- ? new LocalContent(localContentConfig, logger)
155
+ const contentSigningJWT = localContentConfig
156
+ ? createEphemeralContentSigningJWT()
157
+ : undefined;
158
+ const localContent = localContentConfig && contentSigningJWT
159
+ ? new LocalContent(localContentConfig, logger, contentSigningJWT)
155
160
  : undefined;
156
161
  const schedulerService = new InMemorySchedulerService();
157
162
  const aiStorage = kysely
@@ -239,7 +244,7 @@ export const dev = pikkuSessionlessFunc({
239
244
  port: resolvedPort,
240
245
  content: localContentConfig,
241
246
  ...(consoleMount ? { staticMounts: [consoleMount] } : {}),
242
- }, logger);
247
+ }, logger, { contentSigningJWT });
243
248
  const lifecycle = await loadLifecycle();
244
249
  await pikkuServer.init();
245
250
  await lifecycle?.beforeStart?.(resolvedServices);
@@ -1,15 +1,12 @@
1
1
  import { readFile, writeFile } from 'fs/promises';
2
2
  import { join } from 'path';
3
3
  import { pikkuVoidFunc } from '#pikku';
4
- const FEATURE_DEFAULTS = {
5
- rpc: 'auth',
6
- agent: 'auth',
7
- console: 'auth',
8
- scenarios: 'auth',
9
- workflow: 'auth',
10
- events: 'auth',
11
- remoteRpc: 'no-auth',
12
- };
4
+ /**
5
+ * Remote RPC workers carry no auth dimension — `serialize-remote-rpc.ts`
6
+ * hardcodes `auth: false` on what it generates — so enabling it writes a bare
7
+ * `true` and there is nothing to opt out of.
8
+ */
9
+ const NO_AUTH_DIMENSION = new Set(['remoteRpc']);
13
10
  async function enableFeature(feature, logger, config, data) {
14
11
  const noAuth = data?.noAuth ?? false;
15
12
  const configPath = join(config.configDir, 'pikku.config.json');
@@ -21,18 +18,17 @@ async function enableFeature(feature, logger, config, data) {
21
18
  if (!json.scaffold.pikkuDir) {
22
19
  json.scaffold.pikkuDir = 'pikku';
23
20
  }
24
- // The console is an admin surface every RPC requires a session, so it can
25
- // never be scaffolded no-auth (the --no-auth flag is ignored for it).
26
- const value = feature === 'webhook'
27
- ? true
28
- : feature === 'console'
29
- ? 'auth'
30
- : noAuth
31
- ? 'no-auth'
32
- : FEATURE_DEFAULTS[feature];
21
+ // `true` is enabled AND authenticated. Only an explicit --no-auth writes the
22
+ // object form, so a feature can never be turned on public by accident.
23
+ //
24
+ // The console is an admin surface — every RPC requires a session, so
25
+ // --no-auth is ignored for it.
26
+ const wantsPublic = noAuth && feature !== 'console' && !NO_AUTH_DIMENSION.has(feature);
27
+ const value = feature === 'webhook' ? true : wantsPublic ? { auth: false } : true;
33
28
  json.scaffold[feature] = value;
34
29
  await writeFile(configPath, JSON.stringify(json, null, 2) + '\n', 'utf-8');
35
- logger.info(`Enabled scaffold.${feature} = '${value}' in ${configPath}`);
30
+ logger.info(`Enabled scaffold.${feature} = ${JSON.stringify(value)} in ${configPath}` +
31
+ (wantsPublic ? ' (public — no session required)' : ''));
36
32
  }
37
33
  export const enableRpc = pikkuVoidFunc({
38
34
  func: async ({ logger, config }, data) => enableFeature('rpc', logger, config, data),
@@ -1,22 +1,25 @@
1
1
  /**
2
- * Resolving `pikku scenario run <environment>` to the URLs the run targets.
2
+ * Resolving `pikku scenario run <environment>` and `pikku persona run
3
+ * <environment>` to the URLs the run targets.
3
4
  *
4
5
  * The configured environment is the base; `--api-url`/`--app-url` override it
5
6
  * for one invocation, which is what makes a suite runnable against a target
6
7
  * that only exists at run time (a freshly provisioned sandbox) without writing
7
8
  * a pikku.config.json per deployment.
8
9
  */
9
- /** One entry of `scenarios.environments` in pikku.config.json. */
10
- export interface ScenarioEnvironment {
10
+ /** One entry of `environments` in pikku.config.json. */
11
+ export interface PikkuEnvironment {
11
12
  apiUrl: string;
12
13
  signInPath?: string;
13
14
  rpcPath?: string;
14
15
  appUrl?: string;
16
+ /** Real consequences: only an `accountable` persona may run against it. */
17
+ production?: boolean;
15
18
  }
16
- export interface ResolveScenarioEnvironmentOptions {
19
+ export interface ResolveEnvironmentOptions {
17
20
  /** The name given on the command line. Must exist in config. */
18
21
  environment: string;
19
- environments: Record<string, ScenarioEnvironment>;
22
+ environments: Record<string, PikkuEnvironment>;
20
23
  /** `--api-url`, overriding the configured `apiUrl`. */
21
24
  apiUrl?: string;
22
25
  /** `--app-url`, overriding the configured `appUrl`. */
@@ -29,4 +32,4 @@ export interface ResolveScenarioEnvironmentOptions {
29
32
  * all see the same target. The environment is still looked up by name: the
30
33
  * flags override fields on a configured environment, they do not invent one.
31
34
  */
32
- export declare const resolveScenarioEnvironment: ({ environment, environments, apiUrl, appUrl, spawn, }: ResolveScenarioEnvironmentOptions) => ScenarioEnvironment;
35
+ export declare const resolveEnvironment: ({ environment, environments, apiUrl, appUrl, spawn, }: ResolveEnvironmentOptions) => PikkuEnvironment;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Resolving `pikku scenario run <environment>` to the URLs the run targets.
2
+ * Resolving `pikku scenario run <environment>` and `pikku persona run
3
+ * <environment>` to the URLs the run targets.
3
4
  *
4
5
  * The configured environment is the base; `--api-url`/`--app-url` override it
5
6
  * for one invocation, which is what makes a suite runnable against a target
@@ -35,14 +36,14 @@ const assertAbsoluteUrl = (flag, value) => {
35
36
  * all see the same target. The environment is still looked up by name: the
36
37
  * flags override fields on a configured environment, they do not invent one.
37
38
  */
38
- export const resolveScenarioEnvironment = ({ environment, environments, apiUrl, appUrl, spawn = false, }) => {
39
+ export const resolveEnvironment = ({ environment, environments, apiUrl, appUrl, spawn = false, }) => {
39
40
  const configured = environments[environment];
40
41
  if (!configured) {
41
42
  const known = Object.keys(environments);
42
- throw new Error(`Unknown scenario environment '${environment}'. ` +
43
+ throw new Error(`Unknown environment '${environment}'. ` +
43
44
  (known.length
44
45
  ? `Configured environments: ${known.join(', ')}`
45
- : `Add scenarios.environments to pikku.config.json, e.g. { "${environment}": { "apiUrl": "https://app.example.com/api" } }`));
46
+ : `Add environments to pikku.config.json, e.g. { "${environment}": { "apiUrl": "https://app.example.com/api" } }`));
46
47
  }
47
48
  const resolved = { ...configured };
48
49
  if (apiUrl !== undefined) {
@@ -0,0 +1,30 @@
1
+ import { renderKnowledgeIndex } from '../knowledge/render.js';
2
+ export declare const knowledgeIndex: import("#pikku").PikkuFunctionConfig<{
3
+ check?: boolean | undefined;
4
+ }, {
5
+ ok: boolean;
6
+ check: boolean;
7
+ files: {
8
+ path: string;
9
+ action: "unchanged" | "created" | "updated";
10
+ }[];
11
+ }, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
12
+ check?: boolean | undefined;
13
+ }, {
14
+ ok: boolean;
15
+ check: boolean;
16
+ files: {
17
+ path: string;
18
+ action: "unchanged" | "created" | "updated";
19
+ }[];
20
+ }, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{
21
+ check?: boolean | undefined;
22
+ }, {
23
+ ok: boolean;
24
+ check: boolean;
25
+ files: {
26
+ path: string;
27
+ action: "unchanged" | "created" | "updated";
28
+ }[];
29
+ }, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
30
+ export { renderKnowledgeIndex };
@@ -0,0 +1,11 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { runKnowledgeIndex } from '@pikku/knowledge';
3
+ import { renderKnowledgeIndex } from '../knowledge/render.js';
4
+ import { KnowledgeIndexInputSchema, KnowledgeIndexOutputSchema, } from '../knowledge/schemas.js';
5
+ export const knowledgeIndex = pikkuSessionlessFunc({
6
+ description: "Refresh every index.md so each section lists the notes actually in it, replacing only the generated block and leaving each index's prose alone.",
7
+ input: KnowledgeIndexInputSchema,
8
+ output: KnowledgeIndexOutputSchema,
9
+ func: async ({ config }, { check }) => runKnowledgeIndex(config.rootDir, check ?? false),
10
+ });
11
+ export { renderKnowledgeIndex };
@@ -0,0 +1,33 @@
1
+ import { renderKnowledgeValidate } from '../knowledge/render.js';
2
+ export declare const knowledgeValidate: import("#pikku").PikkuFunctionConfig<Record<string, never>, {
3
+ ok: boolean;
4
+ notes: number;
5
+ findings: {
6
+ id: string;
7
+ severity: "warn" | "error" | "info";
8
+ message: string;
9
+ path: string;
10
+ fixHint: string;
11
+ }[];
12
+ }, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<Record<string, never>, {
13
+ ok: boolean;
14
+ notes: number;
15
+ findings: {
16
+ id: string;
17
+ severity: "warn" | "error" | "info";
18
+ message: string;
19
+ path: string;
20
+ fixHint: string;
21
+ }[];
22
+ }, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<Record<string, never>, {
23
+ ok: boolean;
24
+ notes: number;
25
+ findings: {
26
+ id: string;
27
+ severity: "warn" | "error" | "info";
28
+ message: string;
29
+ path: string;
30
+ fixHint: string;
31
+ }[];
32
+ }, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
33
+ export { renderKnowledgeValidate };
@@ -0,0 +1,11 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { runKnowledgeValidate } from '@pikku/knowledge';
3
+ import { renderKnowledgeValidate } from '../knowledge/render.js';
4
+ import { KnowledgeValidateInputSchema, KnowledgeValidateOutputSchema, } from '../knowledge/schemas.js';
5
+ export const knowledgeValidate = pikkuSessionlessFunc({
6
+ description: 'Check the knowledge base against the app-project profile: every note typed, every section indexed, every slice gated, and every resource: pointing at something that still exists.',
7
+ input: KnowledgeValidateInputSchema,
8
+ output: KnowledgeValidateOutputSchema,
9
+ func: async ({ config }) => runKnowledgeValidate(config.rootDir, config.outDir),
10
+ });
11
+ export { renderKnowledgeValidate };
@@ -9,6 +9,24 @@
9
9
  * the only option there.
10
10
  */
11
11
  export declare function resolveAddonDepProtocol(baseDir: string): string;
12
+ export interface AddonVars {
13
+ name: string;
14
+ camelName: string;
15
+ pascalName: string;
16
+ screamingName: string;
17
+ displayName: string;
18
+ description: string;
19
+ category: string;
20
+ addonDepProtocol: string;
21
+ }
22
+ export declare function getAddonFiles(vars: AddonVars, flags: {
23
+ secret: boolean;
24
+ variable: boolean;
25
+ oauth: boolean;
26
+ credential?: 'apikey' | 'bearer' | 'oauth2';
27
+ /** Delegated login: credential is the upstream token + expiry, checked per call. */
28
+ delegated?: boolean;
29
+ }): Record<string, string>;
12
30
  export declare const pikkuNewAddon: import("#pikku").PikkuFunctionConfig<{
13
31
  name: string;
14
32
  displayName?: string;