@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.
- package/console-app/assets/{index-DDpIMCpy.js → index-CeQodYfg.js} +151 -149
- package/console-app/assets/{index-D0HG8q0B.css → index-CyYHTGWZ.css} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.d.ts +13 -15
- package/dist/.pikku/cli/pikku-cli-channel.js +72 -3
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +14 -4
- package/dist/.pikku/cli/pikku-cli-client.gen.js +20 -10
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +156 -2
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +55 -20
- package/dist/.pikku/function/pikku-function-types.gen.js +27 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +176 -13
- package/dist/.pikku/function/pikku-functions.gen.js +11 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +9 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +21 -3
- package/dist/.pikku/schemas/schemas/KnowledgeIndexInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeIndexOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PersonaRunInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PersonaSyncInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuPersonasInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuRolesInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/RolesAuditInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/RolesPruneInput.schema.json +1 -0
- package/dist/.pikku/scopes/pikku-personas-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-personas.gen.d.ts +36 -0
- package/dist/.pikku/scopes/pikku-personas.gen.js +17 -0
- package/dist/.pikku/scopes/pikku-roles-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-roles.gen.d.ts +30 -0
- package/dist/.pikku/scopes/pikku-roles.gen.js +18 -0
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +4 -2
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +3 -2
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +2 -2
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +2 -2
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +2 -2
- package/dist/.pikku/variables/pikku-variable-types.gen.js +2 -2
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +27 -3
- package/dist/.pikku/workflow/pikku-personas.gen.d.ts +15 -0
- package/dist/.pikku/workflow/pikku-personas.gen.js +17 -0
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +96 -19
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +22 -2
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/bin/pikku.js +0 -0
- package/dist/src/cli.wiring.js +124 -2
- package/dist/src/deploy/analyzer/analyzer.js +14 -0
- package/dist/src/deploy/analyzer/manifest.d.ts +11 -0
- package/dist/src/fabric/functions/validate.function.js +20 -11
- package/dist/src/functions/commands/bootstrap.js +3 -0
- package/dist/src/functions/commands/dev-ai-runner.d.ts +16 -4
- package/dist/src/functions/commands/dev-ai-runner.js +63 -29
- package/dist/src/functions/commands/dev.js +9 -4
- package/dist/src/functions/commands/enable.js +15 -19
- package/dist/src/functions/commands/{scenario-environment.d.ts → environment.d.ts} +9 -6
- package/dist/src/functions/commands/{scenario-environment.js → environment.js} +5 -4
- package/dist/src/functions/commands/knowledge-index.d.ts +30 -0
- package/dist/src/functions/commands/knowledge-index.js +11 -0
- package/dist/src/functions/commands/knowledge-validate.d.ts +33 -0
- package/dist/src/functions/commands/knowledge-validate.js +11 -0
- package/dist/src/functions/commands/new-addon.d.ts +18 -0
- package/dist/src/functions/commands/new-addon.js +52 -36
- package/dist/src/functions/commands/persona-sync.d.ts +33 -0
- package/dist/src/functions/commands/persona-sync.js +151 -0
- package/dist/src/functions/commands/persona.d.ts +58 -0
- package/dist/src/functions/commands/persona.js +217 -0
- package/dist/src/functions/commands/roles-audit.d.ts +1 -0
- package/dist/src/functions/commands/roles-audit.js +37 -0
- package/dist/src/functions/commands/roles-prune.d.ts +16 -0
- package/dist/src/functions/commands/roles-prune.js +52 -0
- package/dist/src/functions/commands/roles-shared.d.ts +40 -0
- package/dist/src/functions/commands/roles-shared.js +77 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +3 -3
- package/dist/src/functions/commands/scenario-browser.js +1 -1
- package/dist/src/functions/commands/scenario-ladder.d.ts +44 -2
- package/dist/src/functions/commands/scenario-ladder.js +51 -2
- package/dist/src/functions/commands/scenario.d.ts +7 -0
- package/dist/src/functions/commands/scenario.js +79 -17
- package/dist/src/functions/commands/scopes-shared.d.ts +8 -0
- package/dist/src/functions/commands/scopes-shared.js +1 -1
- package/dist/src/functions/commands/serve.js +7 -3
- package/dist/src/functions/commands/virtual-user-formatter.d.ts +30 -0
- package/dist/src/functions/commands/virtual-user-formatter.js +65 -0
- package/dist/src/functions/db/local-db.js +38 -1
- package/dist/src/functions/knowledge/render.d.ts +3 -0
- package/dist/src/functions/knowledge/render.js +66 -0
- package/dist/src/functions/knowledge/schemas.d.ts +51 -0
- package/dist/src/functions/knowledge/schemas.js +25 -0
- package/dist/src/functions/validate/workspace-validate.js +1 -1
- package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +2 -1
- package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +1 -0
- package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +2 -2
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +8 -8
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +7 -3
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -18
- package/dist/src/functions/wirings/cli/serialize-channel-cli.js +63 -6
- package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -7
- package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +8 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +88 -28
- package/dist/src/functions/wirings/middleware/serialize-middleware-imports.js +25 -9
- package/dist/src/functions/wirings/package/serialize-package-types.js +5 -3
- package/dist/src/functions/wirings/personas/pikku-command-personas.d.ts +7 -0
- package/dist/src/functions/wirings/personas/pikku-command-personas.js +43 -0
- package/dist/src/functions/wirings/personas/serialize-personas-types.d.ts +18 -0
- package/dist/src/functions/wirings/personas/serialize-personas-types.js +76 -0
- package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.js +2 -1
- package/dist/src/functions/wirings/roles/pikku-command-roles.d.ts +7 -0
- package/dist/src/functions/wirings/roles/pikku-command-roles.js +33 -0
- package/dist/src/functions/wirings/roles/serialize-roles-types.d.ts +14 -0
- package/dist/src/functions/wirings/roles/serialize-roles-types.js +55 -0
- package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +2 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +3 -3
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +3 -3
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.d.ts +1 -1
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.js +1 -1
- package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +10 -3
- package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.js +2 -1
- package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.js +2 -2
- package/dist/src/functions/wirings/variables/serialize-variables-types.js +8 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +2 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +13 -13
- package/dist/src/functions/wirings/workflow/serialize-personas.d.ts +10 -0
- package/dist/src/functions/wirings/workflow/serialize-personas.js +46 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +11 -2
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +165 -29
- package/dist/src/functions/workflows/all.workflow.js +7 -1
- package/dist/src/server/content-signing-jwt.d.ts +13 -0
- package/dist/src/server/content-signing-jwt.js +51 -0
- package/dist/src/server/dev-server-runner.interface.d.ts +10 -2
- package/dist/src/server/node-server-runner.d.ts +2 -2
- package/dist/src/server/node-server-runner.js +2 -1
- package/dist/src/services.d.ts +3 -1
- package/dist/src/services.js +2 -1
- package/dist/src/utils/pikku-cli-config.js +45 -2
- package/dist/src/utils/resolve-personas.d.ts +32 -0
- package/dist/src/utils/resolve-personas.js +46 -0
- package/dist/src/utils/resolve-scaffold-feature.d.ts +19 -0
- package/dist/src/utils/resolve-scaffold-feature.js +49 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -13
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +0 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +0 -19
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +0 -17
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +0 -2
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +0 -40
- package/dist/src/utils/resolve-scenario-actors.d.ts +0 -23
- package/dist/src/utils/resolve-scenario-actors.js +0 -74
|
@@ -110,7 +110,23 @@ function sanitizeAddonName(raw) {
|
|
|
110
110
|
name = `x${name}`;
|
|
111
111
|
return name || raw;
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
/**
|
|
114
|
+
* `--display-name` and `--description` are free-form prose, so an apostrophe in
|
|
115
|
+
* them is ordinary — "Bob's CRM". `JSON.stringify` emits a valid, escaped
|
|
116
|
+
* string literal; interpolating the raw text into a quoted one terminates the
|
|
117
|
+
* string and the scaffolded file no longer parses. Prose composed with other
|
|
118
|
+
* words is stringified as a whole, not swapped in place.
|
|
119
|
+
*/
|
|
120
|
+
const literal = (value) => JSON.stringify(value);
|
|
121
|
+
/**
|
|
122
|
+
* The same prose inside a generated template literal, where a backtick or a
|
|
123
|
+
* `${` opens an interpolation rather than ending the string — so the emitted
|
|
124
|
+
* message keeps its own `\${response.status}` holes.
|
|
125
|
+
*/
|
|
126
|
+
const inTemplate = (value) => value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
127
|
+
/** The same prose on a `//` comment line, where a newline ends the comment. */
|
|
128
|
+
const inComment = (value) => value.replace(/\s+/g, ' ').trim();
|
|
129
|
+
export function getAddonFiles(vars, flags) {
|
|
114
130
|
const { name, camelName, pascalName, screamingName, displayName, description, category, } = vars;
|
|
115
131
|
const files = {};
|
|
116
132
|
// package.json
|
|
@@ -205,7 +221,7 @@ ${description}
|
|
|
205
221
|
6. Build: \`yarn install && yarn pikku && yarn build\`
|
|
206
222
|
`;
|
|
207
223
|
// src/index.ts
|
|
208
|
-
files['src/index.ts'] = `// ${displayName} functions
|
|
224
|
+
files['src/index.ts'] = `// ${inComment(displayName)} functions
|
|
209
225
|
// export { ${camelName}Operation } from './functions/operation.function.js'
|
|
210
226
|
`;
|
|
211
227
|
// src/services.ts
|
|
@@ -224,10 +240,10 @@ export const createWireServices = pikkuAddonWireServices(
|
|
|
224
240
|
}
|
|
225
241
|
const cred = await wire.getCredential<{ token: string; expiresAt?: number }>('${camelName}')
|
|
226
242
|
if (!cred?.token) {
|
|
227
|
-
throw new UnauthorizedError(
|
|
243
|
+
throw new UnauthorizedError(${literal(`No ${displayName} session — sign in again`)})
|
|
228
244
|
}
|
|
229
245
|
if (cred.expiresAt && cred.expiresAt * 1000 < Date.now()) {
|
|
230
|
-
throw new UnauthorizedError(
|
|
246
|
+
throw new UnauthorizedError(${literal(`${displayName} session expired — sign in again`)})
|
|
231
247
|
}
|
|
232
248
|
const ${camelName} = new ${pascalName}Service(cred, variables)
|
|
233
249
|
|
|
@@ -275,7 +291,7 @@ export const createWireServices = pikkuAddonWireServices(
|
|
|
275
291
|
}
|
|
276
292
|
const cred = await wire.getCredential<{ accessToken: string }>('${camelName}')
|
|
277
293
|
if (!cred?.accessToken) {
|
|
278
|
-
throw new UnauthorizedError(
|
|
294
|
+
throw new UnauthorizedError(${literal(`No ${displayName} connection — connect ${displayName} first`)})
|
|
279
295
|
}
|
|
280
296
|
const ${camelName} = new ${pascalName}Service(cred, variables)
|
|
281
297
|
|
|
@@ -363,7 +379,7 @@ export class ${pascalName}Service {
|
|
|
363
379
|
|
|
364
380
|
if (!response.ok) {
|
|
365
381
|
const errorText = await response.text()
|
|
366
|
-
throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
|
|
382
|
+
throw new Error(\`${inTemplate(displayName)} API error (\${response.status}): \${errorText}\`)
|
|
367
383
|
}
|
|
368
384
|
|
|
369
385
|
return response.json() as Promise<T>
|
|
@@ -416,7 +432,7 @@ export class ${pascalName}Service {
|
|
|
416
432
|
|
|
417
433
|
if (!response.ok) {
|
|
418
434
|
const errorText = await response.text()
|
|
419
|
-
throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
|
|
435
|
+
throw new Error(\`${inTemplate(displayName)} API error (\${response.status}): \${errorText}\`)
|
|
420
436
|
}
|
|
421
437
|
|
|
422
438
|
return response.json() as Promise<T>
|
|
@@ -464,7 +480,7 @@ export class ${pascalName}Service {
|
|
|
464
480
|
|
|
465
481
|
if (!response.ok) {
|
|
466
482
|
const errorText = await response.text()
|
|
467
|
-
throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
|
|
483
|
+
throw new Error(\`${inTemplate(displayName)} API error (\${response.status}): \${errorText}\`)
|
|
468
484
|
}
|
|
469
485
|
|
|
470
486
|
return response.json() as Promise<T>
|
|
@@ -507,7 +523,7 @@ export class ${pascalName}Service {
|
|
|
507
523
|
|
|
508
524
|
if (!response.ok) {
|
|
509
525
|
const errorText = await response.text()
|
|
510
|
-
throw new Error(\`${displayName} API error (\${response.status}): \${errorText}\`)
|
|
526
|
+
throw new Error(\`${inTemplate(displayName)} API error (\${response.status}): \${errorText}\`)
|
|
511
527
|
}
|
|
512
528
|
|
|
513
529
|
return response.json() as Promise<T>
|
|
@@ -550,16 +566,16 @@ export interface Services extends CoreServices<SingletonServices> {}
|
|
|
550
566
|
// Conditional: credential / secret file
|
|
551
567
|
if (flags.credential === 'apikey') {
|
|
552
568
|
files[`src/${name}.credential.ts`] = `import { z } from 'zod'
|
|
553
|
-
import {
|
|
569
|
+
import { defineCredential } from '@pikku/core/credential'
|
|
554
570
|
|
|
555
571
|
export const ${camelName}CredentialSchema = z.object({
|
|
556
|
-
apiKey: z.string().describe(
|
|
572
|
+
apiKey: z.string().describe(${literal(`${displayName} API key`)}),
|
|
557
573
|
})
|
|
558
574
|
|
|
559
|
-
|
|
575
|
+
defineCredential({
|
|
560
576
|
name: '${camelName}',
|
|
561
|
-
displayName:
|
|
562
|
-
description:
|
|
577
|
+
displayName: ${literal(displayName)},
|
|
578
|
+
description: ${literal(description)},
|
|
563
579
|
type: 'wire',
|
|
564
580
|
schema: ${camelName}CredentialSchema,
|
|
565
581
|
})
|
|
@@ -567,21 +583,21 @@ wireCredential({
|
|
|
567
583
|
}
|
|
568
584
|
else if (flags.credential === 'bearer') {
|
|
569
585
|
const credentialFields = flags.delegated
|
|
570
|
-
? ` token: z.string().describe(
|
|
586
|
+
? ` token: z.string().describe(${literal(`${displayName} session token captured at delegated sign-in`)}),
|
|
571
587
|
expiresAt: z.number().optional().describe('Token expiry (epoch seconds)'),
|
|
572
588
|
tenantId: z.string().optional().describe('Upstream tenant id'),`
|
|
573
|
-
: ` token: z.string().describe(
|
|
589
|
+
: ` token: z.string().describe(${literal(`${displayName} bearer token`)}),`;
|
|
574
590
|
files[`src/${name}.credential.ts`] = `import { z } from 'zod'
|
|
575
|
-
import {
|
|
591
|
+
import { defineCredential } from '@pikku/core/credential'
|
|
576
592
|
|
|
577
593
|
export const ${camelName}CredentialSchema = z.object({
|
|
578
594
|
${credentialFields}
|
|
579
595
|
})
|
|
580
596
|
|
|
581
|
-
|
|
597
|
+
defineCredential({
|
|
582
598
|
name: '${camelName}',
|
|
583
|
-
displayName:
|
|
584
|
-
description:
|
|
599
|
+
displayName: ${literal(displayName)},
|
|
600
|
+
description: ${literal(description)},
|
|
585
601
|
type: 'wire',
|
|
586
602
|
schema: ${camelName}CredentialSchema,
|
|
587
603
|
})
|
|
@@ -589,18 +605,18 @@ wireCredential({
|
|
|
589
605
|
}
|
|
590
606
|
else if (flags.oauth || flags.credential === 'oauth2') {
|
|
591
607
|
files[`src/${name}.credential.ts`] = `import { z } from 'zod'
|
|
592
|
-
import {
|
|
593
|
-
import {
|
|
608
|
+
import { defineCredential } from '@pikku/core/credential'
|
|
609
|
+
import { defineSecret } from '@pikku/core/secret'
|
|
594
610
|
|
|
595
611
|
export const ${camelName}TokenSchema = z.object({
|
|
596
612
|
accessToken: z.string(),
|
|
597
613
|
refreshToken: z.string().optional(),
|
|
598
614
|
})
|
|
599
615
|
|
|
600
|
-
|
|
616
|
+
defineCredential({
|
|
601
617
|
name: '${camelName}',
|
|
602
|
-
displayName:
|
|
603
|
-
description:
|
|
618
|
+
displayName: ${literal(displayName)},
|
|
619
|
+
description: ${literal(description)},
|
|
604
620
|
type: 'wire',
|
|
605
621
|
schema: ${camelName}TokenSchema,
|
|
606
622
|
oauth2: {
|
|
@@ -612,29 +628,29 @@ wireCredential({
|
|
|
612
628
|
},
|
|
613
629
|
})
|
|
614
630
|
|
|
615
|
-
|
|
631
|
+
defineSecret({
|
|
616
632
|
name: '${camelName}OAuthApp',
|
|
617
|
-
displayName:
|
|
618
|
-
description:
|
|
633
|
+
displayName: ${literal(`${displayName} OAuth App`)},
|
|
634
|
+
description: ${literal(`OAuth2 app credentials for ${displayName}`)},
|
|
619
635
|
secretId: '${screamingName}_OAUTH_APP',
|
|
620
636
|
})
|
|
621
637
|
`;
|
|
622
638
|
}
|
|
623
639
|
else if (flags.secret) {
|
|
624
640
|
files[`src/${name}.secret.ts`] = `import { z } from 'zod'
|
|
625
|
-
import {
|
|
641
|
+
import { defineSecret } from '@pikku/core/secret'
|
|
626
642
|
|
|
627
643
|
export const ${camelName}SecretsSchema = z.object({
|
|
628
|
-
apiKey: z.string().describe(
|
|
644
|
+
apiKey: z.string().describe(${literal(`${displayName} API key`)}),
|
|
629
645
|
// Add other secret fields as needed
|
|
630
646
|
})
|
|
631
647
|
|
|
632
648
|
export type ${pascalName}Secrets = z.infer<typeof ${camelName}SecretsSchema>
|
|
633
649
|
|
|
634
|
-
|
|
650
|
+
defineSecret({
|
|
635
651
|
name: '${camelName}',
|
|
636
|
-
displayName:
|
|
637
|
-
description:
|
|
652
|
+
displayName: ${literal(`${displayName} API`)},
|
|
653
|
+
description: ${literal(description)},
|
|
638
654
|
secretId: '${screamingName}_CREDENTIALS',
|
|
639
655
|
schema: ${camelName}SecretsSchema,
|
|
640
656
|
})
|
|
@@ -643,13 +659,13 @@ wireSecret({
|
|
|
643
659
|
// Conditional: variable file
|
|
644
660
|
if (flags.variable) {
|
|
645
661
|
files[`src/${name}.variable.ts`] = `import { z } from 'zod'
|
|
646
|
-
import {
|
|
662
|
+
import { defineVariable } from '@pikku/core/variable'
|
|
647
663
|
|
|
648
664
|
export const ${camelName}VariableSchema = z.string().optional().describe('TODO: describe this variable')
|
|
649
665
|
|
|
650
|
-
|
|
666
|
+
defineVariable({
|
|
651
667
|
name: '${camelName}_variable',
|
|
652
|
-
displayName:
|
|
668
|
+
displayName: ${literal(`${displayName} Variable`)},
|
|
653
669
|
description: 'TODO: describe this variable',
|
|
654
670
|
variableId: '${screamingName}_VARIABLE',
|
|
655
671
|
schema: ${camelName}VariableSchema,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provision the declared personas into an environment: their accounts, and the
|
|
3
|
+
* roles they declare.
|
|
4
|
+
*
|
|
5
|
+
* This is deployment, not seeding. `pikku db seed` applies a project's test
|
|
6
|
+
* fixtures and does not run in production; an accountable persona doing a real
|
|
7
|
+
* job in production still needs an account and its grants, and this is where
|
|
8
|
+
* they come from.
|
|
9
|
+
*
|
|
10
|
+
* Additive in the same way `syncSystemRoles` is: it creates and grants, and
|
|
11
|
+
* never deletes or revokes. Removing a persona from the code leaves their
|
|
12
|
+
* account and their grants standing until somebody decides otherwise — which is
|
|
13
|
+
* the point, because the alternative is a rolling deploy quietly locking out the
|
|
14
|
+
* teammate that the older replica is still running.
|
|
15
|
+
*
|
|
16
|
+
* It needs both halves of an environment: its API, to sign the persona in, and
|
|
17
|
+
* its database, to write the grants. They must be the same deployment — the
|
|
18
|
+
* command has no way to check that, so pointing it at a stage's API and a local
|
|
19
|
+
* database provisions nobody usefully.
|
|
20
|
+
*/
|
|
21
|
+
export declare const personaSync: import("#pikku").PikkuFunctionConfig<{
|
|
22
|
+
environment: string;
|
|
23
|
+
apiUrl?: string;
|
|
24
|
+
dryRun?: boolean;
|
|
25
|
+
}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
|
|
26
|
+
environment: string;
|
|
27
|
+
apiUrl?: string;
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{
|
|
30
|
+
environment: string;
|
|
31
|
+
apiUrl?: string;
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { pikkuSessionlessFunc } from '#pikku';
|
|
2
|
+
import { createHttpPersonas } from '@pikku/core/persona';
|
|
3
|
+
import { personaEnvironmentRefusal } from '@pikku/core/persona';
|
|
4
|
+
import { resolvePersonas } from '../../utils/resolve-personas.js';
|
|
5
|
+
import { resolveEnvironment } from './environment.js';
|
|
6
|
+
import { loadDeclaredRoles, openScopeServiceForRoles } from './roles-shared.js';
|
|
7
|
+
/**
|
|
8
|
+
* The user id behind a persona's address.
|
|
9
|
+
*
|
|
10
|
+
* Read straight off better-auth's own `user` table rather than through the
|
|
11
|
+
* ScopeService, because the ScopeService has no notion of an address — its
|
|
12
|
+
* grants are keyed by user id, and `pikku_user_role.user_id` already FKs into
|
|
13
|
+
* this exact table. Looking it up anywhere else would mean inventing a second
|
|
14
|
+
* account concept for personas, which is the one thing this command exists to
|
|
15
|
+
* avoid: a persona is a user, provisioned the way users are.
|
|
16
|
+
*/
|
|
17
|
+
const userIdByEmail = async (db, email) => {
|
|
18
|
+
const row = await db
|
|
19
|
+
.selectFrom('user')
|
|
20
|
+
.select('id')
|
|
21
|
+
.where('email', '=', email)
|
|
22
|
+
.executeTakeFirst();
|
|
23
|
+
return row ? String(row.id) : null;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Provision the declared personas into an environment: their accounts, and the
|
|
27
|
+
* roles they declare.
|
|
28
|
+
*
|
|
29
|
+
* This is deployment, not seeding. `pikku db seed` applies a project's test
|
|
30
|
+
* fixtures and does not run in production; an accountable persona doing a real
|
|
31
|
+
* job in production still needs an account and its grants, and this is where
|
|
32
|
+
* they come from.
|
|
33
|
+
*
|
|
34
|
+
* Additive in the same way `syncSystemRoles` is: it creates and grants, and
|
|
35
|
+
* never deletes or revokes. Removing a persona from the code leaves their
|
|
36
|
+
* account and their grants standing until somebody decides otherwise — which is
|
|
37
|
+
* the point, because the alternative is a rolling deploy quietly locking out the
|
|
38
|
+
* teammate that the older replica is still running.
|
|
39
|
+
*
|
|
40
|
+
* It needs both halves of an environment: its API, to sign the persona in, and
|
|
41
|
+
* its database, to write the grants. They must be the same deployment — the
|
|
42
|
+
* command has no way to check that, so pointing it at a stage's API and a local
|
|
43
|
+
* database provisions nobody usefully.
|
|
44
|
+
*/
|
|
45
|
+
export const personaSync = pikkuSessionlessFunc({
|
|
46
|
+
remote: true,
|
|
47
|
+
func: async ({ logger, config, getInspectorState, variables }, { environment, apiUrl, dryRun }) => {
|
|
48
|
+
const state = await getInspectorState(true, false, false, true);
|
|
49
|
+
const personas = resolvePersonas(state.personas?.definitions ?? [], config.scenarios?.emailDomain);
|
|
50
|
+
if (Object.keys(personas).length === 0) {
|
|
51
|
+
logger.info('persona sync: no personas are declared — add a definePersonas({ … }) call.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const environments = config.environments ?? {};
|
|
55
|
+
const env = resolveEnvironment({ environment, environments, apiUrl });
|
|
56
|
+
// The same rule that decides who may *run* against an environment decides
|
|
57
|
+
// who may be provisioned into it. Two rules would drift, and the one that
|
|
58
|
+
// drifted would leave an account standing in production for a persona the
|
|
59
|
+
// engine then refuses to sign in.
|
|
60
|
+
//
|
|
61
|
+
// A `runnable: false` persona is still provisioned: their account is the
|
|
62
|
+
// whole reason they were declared, because other people act on it.
|
|
63
|
+
const eligible = [];
|
|
64
|
+
const skipped = [];
|
|
65
|
+
for (const [id, persona] of Object.entries(personas)) {
|
|
66
|
+
const refusal = personaEnvironmentRefusal(id, persona, environment, environments);
|
|
67
|
+
if (refusal) {
|
|
68
|
+
skipped.push(refusal);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
eligible.push([id, persona]);
|
|
72
|
+
}
|
|
73
|
+
if (eligible.length === 0) {
|
|
74
|
+
logger.info(`persona sync: no persona may act in '${environment}' — ${skipped.length} declared persona(s) were all skipped.`);
|
|
75
|
+
logger.info(' Run with --dry-run for the reasons, or give the persona this environment and disposition `accountable` if it is production.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (dryRun) {
|
|
79
|
+
logger.info(`persona sync (dry run): ${eligible.length} persona(s) would be provisioned into '${environment}' at ${env.apiUrl}:`);
|
|
80
|
+
for (const [id, persona] of eligible) {
|
|
81
|
+
logger.info(` ${id.padEnd(20)} ${persona.email}${persona.roles.length
|
|
82
|
+
? ` -> ${persona.roles.join(' + ')}`
|
|
83
|
+
: ' (no roles)'}`);
|
|
84
|
+
}
|
|
85
|
+
for (const refusal of skipped) {
|
|
86
|
+
logger.info(` skipped: ${refusal}`);
|
|
87
|
+
}
|
|
88
|
+
logger.info('Re-run without --dry-run to apply.');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const secret = await variables.get('SCENARIO_ACTOR_SECRET');
|
|
92
|
+
if (!secret) {
|
|
93
|
+
throw new Error('SCENARIO_ACTOR_SECRET is not set — a persona account cannot be created. ' +
|
|
94
|
+
'Export it in the environment running this command (never put it in pikku.config.json).');
|
|
95
|
+
}
|
|
96
|
+
const declaredRoles = await loadDeclaredRoles(config.rolesMetaJsonFile, logger);
|
|
97
|
+
if (!declaredRoles) {
|
|
98
|
+
throw new Error('role metadata not found');
|
|
99
|
+
}
|
|
100
|
+
const signedIn = createHttpPersonas({
|
|
101
|
+
apiUrl: env.apiUrl,
|
|
102
|
+
secret,
|
|
103
|
+
personas: Object.fromEntries(eligible),
|
|
104
|
+
signInPath: env.signInPath,
|
|
105
|
+
rpcPath: env.rpcPath,
|
|
106
|
+
});
|
|
107
|
+
// Roles are synced on the way in, so a grant can name a role this deploy
|
|
108
|
+
// introduced. Both that sync and everything below are additive.
|
|
109
|
+
const opened = await openScopeServiceForRoles({ config, logger }, declaredRoles, 'pikku persona sync');
|
|
110
|
+
if (!opened) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
let accounts = 0;
|
|
115
|
+
let granted = 0;
|
|
116
|
+
let held = 0;
|
|
117
|
+
for (const [id, persona] of eligible) {
|
|
118
|
+
// Signing in is what creates the account: the actor plugin upserts an
|
|
119
|
+
// `actor: true` user row on first sign-in, and nothing else does.
|
|
120
|
+
// `sessionRoles()` is the public call that forces it — its answer is a
|
|
121
|
+
// by-product here, not the reason for the call.
|
|
122
|
+
await signedIn[id].sessionRoles();
|
|
123
|
+
accounts++;
|
|
124
|
+
if (persona.roles.length === 0) {
|
|
125
|
+
logger.info(` ${id.padEnd(20)} ${persona.email} (no roles)`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const userId = await userIdByEmail(opened.db, persona.email);
|
|
129
|
+
if (!userId) {
|
|
130
|
+
throw new Error(`persona sync: signed '${id}' in at ${env.apiUrl}, but no user row for ${persona.email} exists in the database this command opened. ` +
|
|
131
|
+
`That database is not the one '${environment}' runs on, so its grants would land nowhere.`);
|
|
132
|
+
}
|
|
133
|
+
const already = new Set(await opened.service.listUserRoles(userId));
|
|
134
|
+
const missing = persona.roles.filter((role) => !already.has(role));
|
|
135
|
+
for (const role of missing) {
|
|
136
|
+
await opened.service.addUserToRole(userId, role);
|
|
137
|
+
}
|
|
138
|
+
granted += missing.length;
|
|
139
|
+
held += persona.roles.length - missing.length;
|
|
140
|
+
logger.info(` ${id.padEnd(20)} ${persona.email} -> ${persona.roles.join(' + ')}${missing.length === 0 ? ' (already held)' : ''}`);
|
|
141
|
+
}
|
|
142
|
+
logger.info(`persona sync: ${accounts} account(s) in '${environment}', ${granted} role grant(s) applied, ${held} already held`);
|
|
143
|
+
if (skipped.length > 0) {
|
|
144
|
+
logger.info(` ${skipped.length} persona(s) skipped — they do not act in '${environment}'. Run with --dry-run for the reasons.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await opened.destroy();
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run one declared persona against a real stage as a virtual user.
|
|
3
|
+
*
|
|
4
|
+
* Everything it needs is already in the project: the catalogue is the function
|
|
5
|
+
* meta, the intents are the scenarios' own prose, the identity is the persona
|
|
6
|
+
* signing in over the app's real auth, and the scopes come from the roles they
|
|
7
|
+
* declared. The only new input is which person to be — everything else is a
|
|
8
|
+
* flag for narrowing or replaying a run.
|
|
9
|
+
*
|
|
10
|
+
* This is deliberately not a test runner. It asserts nothing, and a green run
|
|
11
|
+
* proves nothing — what it produces is findings, and the absence of them is
|
|
12
|
+
* only ever "not this time, not with this seed".
|
|
13
|
+
*/
|
|
14
|
+
export declare const personaRun: import("#pikku").PikkuFunctionConfig<{
|
|
15
|
+
environment: string;
|
|
16
|
+
persona: string;
|
|
17
|
+
disposition?: string;
|
|
18
|
+
goals?: string;
|
|
19
|
+
steps?: string;
|
|
20
|
+
mutations?: string;
|
|
21
|
+
duration?: string;
|
|
22
|
+
seed?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
apiUrl?: string;
|
|
25
|
+
allowApproval?: boolean;
|
|
26
|
+
skipRoleCheck?: boolean;
|
|
27
|
+
out?: string;
|
|
28
|
+
}, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<{
|
|
29
|
+
environment: string;
|
|
30
|
+
persona: string;
|
|
31
|
+
disposition?: string;
|
|
32
|
+
goals?: string;
|
|
33
|
+
steps?: string;
|
|
34
|
+
mutations?: string;
|
|
35
|
+
duration?: string;
|
|
36
|
+
seed?: string;
|
|
37
|
+
model?: string;
|
|
38
|
+
apiUrl?: string;
|
|
39
|
+
allowApproval?: boolean;
|
|
40
|
+
skipRoleCheck?: boolean;
|
|
41
|
+
out?: string;
|
|
42
|
+
}, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<{
|
|
43
|
+
environment: string;
|
|
44
|
+
persona: string;
|
|
45
|
+
disposition?: string;
|
|
46
|
+
goals?: string;
|
|
47
|
+
steps?: string;
|
|
48
|
+
mutations?: string;
|
|
49
|
+
duration?: string;
|
|
50
|
+
seed?: string;
|
|
51
|
+
model?: string;
|
|
52
|
+
apiUrl?: string;
|
|
53
|
+
allowApproval?: boolean;
|
|
54
|
+
skipRoleCheck?: boolean;
|
|
55
|
+
out?: string;
|
|
56
|
+
}, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
|
|
57
|
+
/** Who this app is for, and what each of them wants, without running anything. */
|
|
58
|
+
export declare const personaList: import("#pikku").PikkuFunctionConfig<void, void, "rpc" | "session", import("#pikku").PikkuFunctionSessionless<void, void, "rpc" | "session", import("#pikku").WiredServices> | import("#pikku").PikkuFunction<void, void, "rpc" | "session", import("#pikku").WiredServices>, undefined, undefined>;
|