@pikku/cli 0.12.65 → 0.12.67
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-C851uJHD.js → index-CttM2Aiz.js} +141 -141
- package/console-app/index.html +1 -1
- 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.js +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- 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.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 +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +6 -6
- package/dist/.pikku/function/pikku-functions.gen.js +1 -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/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.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.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/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 +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- 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 +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- 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/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- 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/src/fabric/functions/validate.function.js +1 -1
- package/dist/src/functions/commands/audit.js +25 -8
- package/dist/src/functions/commands/console.js +1 -1
- package/dist/src/functions/commands/enable.js +4 -4
- package/dist/src/functions/validate/workspace-validate.js +1 -1
- package/dist/src/functions/wirings/console/pikku-command-console-functions.js +13 -0
- package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +5 -1
- package/dist/src/functions/wirings/console/serialize-console-functions.js +6 -11
- package/dist/src/functions/wirings/functions/pikku-command-functions.js +5 -3
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +5 -1
- package/dist/src/utils/strip-verbose-meta.d.ts +3 -0
- package/dist/src/utils/strip-verbose-meta.js +8 -0
- package/package.json +3 -3
- package/skills/pikku-better-auth/SKILL.md +4 -0
- package/skills/pikku-deps/SKILL.md +91 -0
|
@@ -4,7 +4,7 @@ import { pikkuVoidFunc } from '#pikku';
|
|
|
4
4
|
const FEATURE_DEFAULTS = {
|
|
5
5
|
rpc: 'auth',
|
|
6
6
|
agent: 'auth',
|
|
7
|
-
console: '
|
|
7
|
+
console: 'auth',
|
|
8
8
|
workflow: 'auth',
|
|
9
9
|
events: 'auth',
|
|
10
10
|
remoteRpc: 'no-auth',
|
|
@@ -20,9 +20,9 @@ async function enableFeature(feature, logger, config, data) {
|
|
|
20
20
|
if (!json.scaffold.pikkuDir) {
|
|
21
21
|
json.scaffold.pikkuDir = 'pikku';
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// The console is an admin surface — every RPC requires a session, so it can
|
|
24
|
+
// never be scaffolded no-auth (the --no-auth flag is ignored for it).
|
|
25
|
+
const value = feature === 'console' ? 'auth' : noAuth ? 'no-auth' : FEATURE_DEFAULTS[feature];
|
|
26
26
|
json.scaffold[feature] = value;
|
|
27
27
|
await writeFile(configPath, JSON.stringify(json, null, 2) + '\n', 'utf-8');
|
|
28
28
|
logger.info(`Enabled scaffold.${feature} = '${value}' in ${configPath}`);
|
|
@@ -97,7 +97,7 @@ export async function runWorkspaceValidate(startDir = process.cwd()) {
|
|
|
97
97
|
}
|
|
98
98
|
const scaffold = pikkuConfig.scaffold;
|
|
99
99
|
if (!scaffold?.console) {
|
|
100
|
-
e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console": "
|
|
100
|
+
e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console": "auth" under "scaffold" in pikku.config.json — the console requires an authenticated session, so Better Auth must be set up (see the pikku-better-auth skill)');
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
const rootPkgPath = join(root, 'package.json');
|
|
@@ -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 { projectDeclaresBetterAuth } from '../../../utils/detect-better-auth.js';
|
|
5
6
|
import { serializeConsoleFunctions } from './serialize-console-functions.js';
|
|
6
7
|
export const pikkuConsoleFunctions = pikkuSessionlessFunc({
|
|
7
8
|
func: async ({ logger, config, variables }) => {
|
|
@@ -10,6 +11,18 @@ export const pikkuConsoleFunctions = pikkuSessionlessFunc({
|
|
|
10
11
|
return false;
|
|
11
12
|
}
|
|
12
13
|
if (config.scaffold?.console) {
|
|
14
|
+
// Every console RPC now requires an authenticated session (the console is
|
|
15
|
+
// an admin surface), so scaffolding it without an auth strategy produces a
|
|
16
|
+
// console that 403s on every call. Better Auth is the supported strategy —
|
|
17
|
+
// fail fast rather than ship a dead console.
|
|
18
|
+
const hasBetterAuth = await projectDeclaresBetterAuth(config.rootDir, config.srcDirectories, config.ignoreFiles);
|
|
19
|
+
if (!hasBetterAuth) {
|
|
20
|
+
throw new Error(`"scaffold.console" is enabled but no pikkuBetterAuth(...) was found in the project.\n` +
|
|
21
|
+
`The console addon requires an authenticated session on every RPC — a console without auth ` +
|
|
22
|
+
`returns 403 on every call.\n` +
|
|
23
|
+
`Fix: set up Better Auth (add a pikkuBetterAuth(...) config) before scaffolding the console, ` +
|
|
24
|
+
`or remove "scaffold.console" from pikku.config.json.`);
|
|
25
|
+
}
|
|
13
26
|
const pathToPikkuTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.typesDeclarationFile, config.packageMappings);
|
|
14
27
|
const pathToAgentTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.agentTypesFile, config.packageMappings);
|
|
15
28
|
await writeFileInDir(logger, config.consoleFunctionsFile, serializeConsoleFunctions(pathToPikkuTypes, pathToAgentTypes, config.globalHTTPPrefix || ''));
|
|
@@ -34,7 +34,8 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
|
|
|
34
34
|
const hasNodes = Object.keys(nodes.meta).length > 0;
|
|
35
35
|
const hasSecrets = secrets.definitions.length > 0;
|
|
36
36
|
const hasPackageMeta = !!addonMeta?.icon || !!addonMeta?.displayName;
|
|
37
|
-
|
|
37
|
+
const hasParentServices = (state.addonRequiredParentServices?.length ?? 0) > 0;
|
|
38
|
+
if (!hasNodes && !hasSecrets && !hasPackageMeta && !hasParentServices) {
|
|
38
39
|
return undefined;
|
|
39
40
|
}
|
|
40
41
|
const allowedCategories = addonMeta?.categories;
|
|
@@ -68,6 +69,9 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
|
|
|
68
69
|
if (serverlessIncompatible && serverlessIncompatible.length > 0) {
|
|
69
70
|
metaData.serverlessIncompatible = serverlessIncompatible;
|
|
70
71
|
}
|
|
72
|
+
if (hasParentServices) {
|
|
73
|
+
metaData.requiredParentServices = state.addonRequiredParentServices;
|
|
74
|
+
}
|
|
71
75
|
if (addonMetaJsonFile && (config.scaffold?.console || config.addon)) {
|
|
72
76
|
const minimalMeta = stripVerboseFields(metaData);
|
|
73
77
|
await writeFileInDir(logger, addonMetaJsonFile, JSON.stringify(minimalMeta, null, 2), { ignoreModifyComment: true });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const serializeConsoleFunctions = (pathToPikkuTypes, _pathToAgentTypes, globalHTTPPrefix = '') => {
|
|
2
|
-
return `import {
|
|
2
|
+
return `import { pikkuFunc, defineHTTPRoutes, wireHTTPRoutes, ref, wireAddon } from '${pathToPikkuTypes}'
|
|
3
3
|
|
|
4
|
-
export const pikkuConsoleSetSecret =
|
|
4
|
+
export const pikkuConsoleSetSecret = pikkuFunc<{
|
|
5
5
|
secretId: string
|
|
6
6
|
value: unknown
|
|
7
7
|
}, {
|
|
@@ -10,21 +10,19 @@ export const pikkuConsoleSetSecret = pikkuSessionlessFunc<{
|
|
|
10
10
|
tags: ['pikku'],
|
|
11
11
|
description: 'Set the value of a secret',
|
|
12
12
|
expose: true,
|
|
13
|
-
auth: false,
|
|
14
13
|
func: async ({ secrets }, { secretId, value }) => {
|
|
15
14
|
await secrets.setSecret(secretId, value)
|
|
16
15
|
return { success: true }
|
|
17
16
|
},
|
|
18
17
|
})
|
|
19
18
|
|
|
20
|
-
export const pikkuConsoleGetVariable =
|
|
19
|
+
export const pikkuConsoleGetVariable = pikkuFunc<
|
|
21
20
|
{ variableId: string },
|
|
22
21
|
{ exists: boolean; value: unknown | null }
|
|
23
22
|
>({
|
|
24
23
|
tags: ['pikku'],
|
|
25
24
|
description: 'Get the current value of a variable',
|
|
26
25
|
expose: true,
|
|
27
|
-
auth: false,
|
|
28
26
|
func: async ({ variables }, { variableId }) => {
|
|
29
27
|
const exists = await variables.has(variableId)
|
|
30
28
|
if (!exists) {
|
|
@@ -40,14 +38,13 @@ export const pikkuConsoleGetVariable = pikkuSessionlessFunc<
|
|
|
40
38
|
},
|
|
41
39
|
})
|
|
42
40
|
|
|
43
|
-
export const pikkuConsoleSetVariable =
|
|
41
|
+
export const pikkuConsoleSetVariable = pikkuFunc<
|
|
44
42
|
{ variableId: string; value: unknown },
|
|
45
43
|
{ success: boolean }
|
|
46
44
|
>({
|
|
47
45
|
tags: ['pikku'],
|
|
48
46
|
description: 'Set the value of a variable',
|
|
49
47
|
expose: true,
|
|
50
|
-
auth: false,
|
|
51
48
|
func: async ({ variables }, { variableId, value }) => {
|
|
52
49
|
if (typeof value === 'string') {
|
|
53
50
|
await variables.set(variableId, value)
|
|
@@ -58,28 +55,26 @@ export const pikkuConsoleSetVariable = pikkuSessionlessFunc<
|
|
|
58
55
|
},
|
|
59
56
|
})
|
|
60
57
|
|
|
61
|
-
export const pikkuConsoleHasSecret =
|
|
58
|
+
export const pikkuConsoleHasSecret = pikkuFunc<
|
|
62
59
|
{ secretId: string },
|
|
63
60
|
{ exists: boolean }
|
|
64
61
|
>({
|
|
65
62
|
tags: ['pikku'],
|
|
66
63
|
description: 'Check if a secret exists without reading its value',
|
|
67
64
|
expose: true,
|
|
68
|
-
auth: false,
|
|
69
65
|
func: async ({ secrets }, { secretId }) => {
|
|
70
66
|
const exists = await secrets.hasSecret(secretId)
|
|
71
67
|
return { exists }
|
|
72
68
|
},
|
|
73
69
|
})
|
|
74
70
|
|
|
75
|
-
export const pikkuConsoleGetSecret =
|
|
71
|
+
export const pikkuConsoleGetSecret = pikkuFunc<
|
|
76
72
|
{ secretId: string },
|
|
77
73
|
{ exists: boolean; value: unknown | null }
|
|
78
74
|
>({
|
|
79
75
|
tags: ['pikku'],
|
|
80
76
|
description: 'Get the current value of a secret',
|
|
81
77
|
expose: true,
|
|
82
|
-
auth: false,
|
|
83
78
|
func: async ({ secrets }, { secretId }) => {
|
|
84
79
|
const exists = await secrets.hasSecret(secretId)
|
|
85
80
|
if (!exists) {
|
|
@@ -3,13 +3,15 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
|
|
|
3
3
|
import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
|
|
4
4
|
import { serializeFunctionImports } from './serialize-function-imports.js';
|
|
5
5
|
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
6
|
-
import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
|
|
6
|
+
import { stripVerboseFields, hasVerboseFields, reattachFunctionServices, } from '../../../utils/strip-verbose-meta.js';
|
|
7
7
|
export const pikkuFunctions = pikkuSessionlessFunc({
|
|
8
8
|
func: async ({ logger, config, getInspectorState }) => {
|
|
9
9
|
const { functions, rpc } = await getInspectorState();
|
|
10
10
|
const { functionsMetaFile, functionsMetaJsonFile, functionsFile, packageMappings, schema, } = config;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
let minimalMeta = stripVerboseFields(functions.meta);
|
|
12
|
+
if (config.addonName) {
|
|
13
|
+
minimalMeta = reattachFunctionServices(minimalMeta, functions.meta);
|
|
14
|
+
}
|
|
13
15
|
await writeFileInDir(logger, functionsMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
|
|
14
16
|
// Write verbose JSON only if it has additional fields
|
|
15
17
|
if (hasVerboseFields(functions.meta)) {
|
|
@@ -10,7 +10,7 @@ import { serializeWorkflowBootstrapMap } from './serialize-workflow-bootstrap-ma
|
|
|
10
10
|
import { serializeWorkflowMeta } from './serialize-workflow-meta.js';
|
|
11
11
|
import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
|
|
12
12
|
import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
|
|
13
|
-
import { join } from 'path';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
14
|
export const pikkuWorkflow = pikkuSessionlessFunc({
|
|
15
15
|
func: async ({ logger, config, getInspectorState }, input) => {
|
|
16
16
|
const bootstrap = input?.bootstrap === true;
|
|
@@ -66,6 +66,10 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
|
|
|
66
66
|
const userFlowActors = config.userFlows?.actors;
|
|
67
67
|
if (userFlowActors && Object.keys(userFlowActors).length > 0) {
|
|
68
68
|
await writeFileInDir(logger, config.userFlowActorsFile, serializeUserFlowActors(userFlowActors));
|
|
69
|
+
// JSON twin for the runtime meta service (console personas view). Lives
|
|
70
|
+
// next to workflow/meta but NOT inside it — getWorkflowMeta() treats
|
|
71
|
+
// every workflow/meta/*.gen.json as a workflow.
|
|
72
|
+
await writeFileInDir(logger, join(dirname(config.userFlowActorsFile), 'user-flow-actors.gen.json'), JSON.stringify(userFlowActors, null, 2));
|
|
69
73
|
}
|
|
70
74
|
await writeFileInDir(logger, workflowMapDeclarationFile, bootstrap
|
|
71
75
|
? serializeWorkflowBootstrapMap(workflows.meta, workflows.graphMeta)
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
* Also strips addon namespace prefixes from pikkuFuncId values where packageName is present.
|
|
30
30
|
*/
|
|
31
31
|
export declare function stripVerboseFields<T>(obj: T): T;
|
|
32
|
+
export declare function reattachFunctionServices<T extends Record<string, {
|
|
33
|
+
services?: unknown;
|
|
34
|
+
}>>(minimalMeta: T, fullMeta: T): T;
|
|
32
35
|
/**
|
|
33
36
|
* Check if an object has any verbose fields that would be stripped
|
|
34
37
|
*/
|
|
@@ -179,6 +179,14 @@ function _stripVerboseFieldsInner(obj) {
|
|
|
179
179
|
}
|
|
180
180
|
return result;
|
|
181
181
|
}
|
|
182
|
+
export function reattachFunctionServices(minimalMeta, fullMeta) {
|
|
183
|
+
for (const [id, meta] of Object.entries(fullMeta)) {
|
|
184
|
+
if (meta?.services && minimalMeta[id]) {
|
|
185
|
+
minimalMeta[id].services = meta.services;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return minimalMeta;
|
|
189
|
+
}
|
|
182
190
|
/**
|
|
183
191
|
* Check if an object has any verbose fields that would be stripped
|
|
184
192
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.67",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"imports": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
30
30
|
"@pikku/better-auth": "^0.12.14",
|
|
31
31
|
"@pikku/bun-server": "^0.12.2",
|
|
32
|
-
"@pikku/core": "^0.12.
|
|
32
|
+
"@pikku/core": "^0.12.51",
|
|
33
33
|
"@pikku/deploy-cloudflare": "^0.12.8",
|
|
34
34
|
"@pikku/fetch": "^0.12.6",
|
|
35
|
-
"@pikku/inspector": "^0.12.
|
|
35
|
+
"@pikku/inspector": "^0.12.35",
|
|
36
36
|
"@pikku/kysely": "^0.13.0",
|
|
37
37
|
"@pikku/kysely-node-sqlite": "^0.12.3",
|
|
38
38
|
"@pikku/node-http-server": "^0.12.4",
|
|
@@ -52,6 +52,10 @@ Better Auth owns its own HTTP surface, database tables, and session cookie. The
|
|
|
52
52
|
|
|
53
53
|
You do NOT hand-write routes, the session middleware, or the secret wiring — `pikkuBetterAuth` + the CLI generate all of it. Re-run `pikku auth` (or `pikku all`) to regenerate.
|
|
54
54
|
|
|
55
|
+
### The console requires Better Auth
|
|
56
|
+
|
|
57
|
+
The Pikku console (`@pikku/addon-console`, enabled via `scaffold.console` in `pikku.config.json`) is an admin surface: **every console RPC now requires an authenticated session** (the functions are `pikkuFunc`; unauthenticated calls return `403`). So `scaffold.console` alone is **no longer the minimum** — you also need an auth strategy, and Better Auth is the supported one. `pikku all` **throws** if `scaffold.console` is set but no `pikkuBetterAuth(...)` is found in the project. Baseline is "must be logged in"; finer policy (admin-only, org scoping) is layered host-side via tag/HTTP middleware. See `pikku-deps` for the console's Security screen.
|
|
58
|
+
|
|
55
59
|
---
|
|
56
60
|
|
|
57
61
|
## Standard Setup
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-deps
|
|
3
|
+
description: 'Use for the Pikku dependency security audit: the `pikku audit` CLI command, the `.pikku/audit.json` artifact, the `SecurityAuditReport` type in @pikku/core, and the console Security screen (getSecurityAudit / runSecurityAudit / updateDependency + SecurityAuditView).
|
|
4
|
+
TRIGGER when: user asks about `pikku audit`, dependency vulnerabilities/advisories, outdated dependencies, the Security screen/page in the console, updating a vulnerable dependency, or reading/rendering audit.json.
|
|
5
|
+
DO NOT TRIGGER when: user asks about authentication/sessions/JWT (use pikku-security), permissions (use pikku-permissions), or secrets/env vars (use pikku-config).'
|
|
6
|
+
installGroups: [core]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Pikku Dependency Audit
|
|
10
|
+
|
|
11
|
+
## Agent Operating Procedure
|
|
12
|
+
|
|
13
|
+
1. The audit is a generated artifact, not live state. `pikku audit` writes the
|
|
14
|
+
normalised report to `.pikku/audit.json` (config `outDir`), so it rides the
|
|
15
|
+
same meta pipeline as every other codegen output — uploaded on deploy,
|
|
16
|
+
readable by the console addon and any tooling. Read it via
|
|
17
|
+
`metaService.readFile('audit.json')`, never by shelling out to the package
|
|
18
|
+
manager from a function.
|
|
19
|
+
2. One source of truth for the shape: `SecurityAuditReport` (and
|
|
20
|
+
`SecurityAuditIssue` / `SecurityAuditUpdate` / `SecurityAuditSummary` +
|
|
21
|
+
`SecuritySeverity` / `SecurityUpdateLevel`) are exported from **@pikku/core**.
|
|
22
|
+
The CLI writes it, the addon reads it, the UI renders it — never redeclare
|
|
23
|
+
the type at a call site.
|
|
24
|
+
3. Validate with `pikku tsc` after changes; the command never fails a build
|
|
25
|
+
(advisories are informational; a missing/failed audit yields an
|
|
26
|
+
empty-but-valid report).
|
|
27
|
+
|
|
28
|
+
## The `pikku audit` command
|
|
29
|
+
|
|
30
|
+
- `pikku audit` — reports **security advisories** only.
|
|
31
|
+
- `pikku audit --outdated` — also reports **available dependency updates**.
|
|
32
|
+
- Package-manager detection is by **lockfile** (walks up: `bun.lock`/`bun.lockb`,
|
|
33
|
+
then `yarn.lock`). Only **bun** runs a real audit (`bun audit --json` +
|
|
34
|
+
`bun outdated`, normalised into one `SecurityAuditReport` with per-severity /
|
|
35
|
+
per-update-level counts). Other PMs are detected but **stubbed** with a `note`
|
|
36
|
+
field until their shapes are normalised — issues/updates come back empty.
|
|
37
|
+
- `bun audit` exits non-zero when it *finds* advisories but still writes a valid
|
|
38
|
+
report — treat any non-zero exit as data, not failure.
|
|
39
|
+
|
|
40
|
+
## Console integration (@pikku/addon-console)
|
|
41
|
+
|
|
42
|
+
Three RPCs, all reading/writing the same artifact via the meta service. Shared
|
|
43
|
+
spawn/read helpers live in `lib/audit-exec.ts` (`readAuditReport`,
|
|
44
|
+
`runPikkuAudit`, `spawnProcess`, `findBin`) — reuse them, don't re-implement.
|
|
45
|
+
Like every console RPC these require an **authenticated session** (the console
|
|
46
|
+
is admin-only), so the host must have Better Auth wired — see `pikku-better-auth`.
|
|
47
|
+
|
|
48
|
+
- `getSecurityAudit` — reads `.pikku/audit.json`, returns the report (or `null`).
|
|
49
|
+
- `runSecurityAudit` — runs `pikku audit --outdated` server-side (regenerates the
|
|
50
|
+
artifact) then returns the fresh report. Same shape as the Run Tests action.
|
|
51
|
+
- `updateDependency({ package, version })` — bumps the package in `package.json`
|
|
52
|
+
(preserving the `^`/`~` range prefix), runs `bun install`, re-audits, and
|
|
53
|
+
returns the fresh report. Throws if the package is not a direct dependency.
|
|
54
|
+
NOTE: `bun install` must be scoped to a standalone project — do not run it
|
|
55
|
+
inside a yarn/bun monorepo member (it resolves the whole workspace).
|
|
56
|
+
|
|
57
|
+
## Console UI (@pikku/console)
|
|
58
|
+
|
|
59
|
+
- `SecurityPage` — the page: **Run audit** button (`lead`) + responsive
|
|
60
|
+
`ShellHeader` (structured `search` + `selection` for the Issues/Dependencies
|
|
61
|
+
lens; never cram raw controls into the non-collapsing `filters`/`view` escape
|
|
62
|
+
hatch). Empty state until an audit has run.
|
|
63
|
+
- `SecurityAuditView` — exported presentational component. Two lenses
|
|
64
|
+
(Issues grouped by severity; Dependencies table). Each finding row carries its
|
|
65
|
+
actions **right-aligned in the row header** (`Accordion.Control` sibling, so a
|
|
66
|
+
click acts instead of toggling): "View advisory" + a per-finding
|
|
67
|
+
**remediation slot**.
|
|
68
|
+
- `renderRemediation({ pkg, version, issue })` — the extension seam. OSS default
|
|
69
|
+
is `UpdateDependencyButton` (the free bump + `bun install`). Downstream
|
|
70
|
+
consoles (Fabric) pass their own sandbox-verified action here — replace the
|
|
71
|
+
action, keep the view.
|
|
72
|
+
- Hooks: `useSecurityAudit` (read), `useRunSecurityAudit` (run),
|
|
73
|
+
`useUpdateDependency` (bump). All are `useMutation`/`useQuery` — surface
|
|
74
|
+
`mutation.error`, never hand-roll loading/error state or swallow the error.
|
|
75
|
+
|
|
76
|
+
## Report shape (SecurityAuditReport)
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
{
|
|
80
|
+
tool: string // e.g. 'bun'
|
|
81
|
+
note?: string // set when the audit could NOT run (unsupported PM);
|
|
82
|
+
// render ONLY the note — never a reassuring "no vulnerabilities"
|
|
83
|
+
summary: { critical, high, moderate, low, info: number }
|
|
84
|
+
issues: SecurityAuditIssue[] // package, severity, title, advisoryId, cwe[], cvssScore?,
|
|
85
|
+
// url?, vulnerableVersions, recommendedVersion?
|
|
86
|
+
updates: SecurityAuditUpdate[] // package, current, latest, level (major|minor|patch|unknown)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When `note` is present the audit did not run — show only the note (an "Audit not
|
|
91
|
+
run" state), never the "no known vulnerabilities / up to date" copy.
|