@pikku/cli 0.12.1 → 0.12.3

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 (128) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-Ci24-VT-.js +657 -0
  3. package/console-app/assets/{index-0Ui5UudO.css → index-DvrDbftC.css} +1 -1
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.js +1 -1
  7. package/dist/.pikku/agent/pikku-agent-wirings.gen.d.ts +1 -1
  8. package/dist/.pikku/agent/pikku-agent-wirings.gen.js +1 -1
  9. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  10. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  11. package/dist/.pikku/channel/pikku-channels-meta.gen.js +1 -1
  12. package/dist/.pikku/channel/pikku-channels.gen.d.ts +1 -1
  13. package/dist/.pikku/channel/pikku-channels.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-channel.d.ts +13 -1
  15. package/dist/.pikku/cli/pikku-cli-channel.js +37 -2
  16. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  17. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  18. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  20. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  21. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +64 -1
  22. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  23. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  24. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  25. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  26. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  27. package/dist/.pikku/function/pikku-function-types.gen.d.ts +30 -1
  28. package/dist/.pikku/function/pikku-function-types.gen.js +17 -1
  29. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  30. package/dist/.pikku/function/pikku-functions-meta.gen.json +155 -76
  31. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  32. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  33. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  34. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  35. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  36. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  37. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  38. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  39. package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.js +1 -1
  40. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.d.ts +1 -1
  41. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.js +1 -1
  42. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  43. package/dist/.pikku/pikku-services.gen.d.ts +3 -1
  44. package/dist/.pikku/pikku-services.gen.js +2 -0
  45. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  46. package/dist/.pikku/pikku-types.gen.js +1 -1
  47. package/dist/.pikku/pikku-websocket.gen.d.ts +1 -1
  48. package/dist/.pikku/pikku-websocket.gen.js +1 -1
  49. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  50. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  51. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  52. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  53. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  54. package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.js +1 -1
  55. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  56. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +10 -5
  57. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  58. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  59. package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.js +1 -1
  60. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.d.ts +1 -1
  61. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.js +1 -1
  62. package/dist/.pikku/schemas/register.gen.js +9 -7
  63. package/dist/.pikku/schemas/schemas/ConsoleCommandInput.schema.json +1 -1
  64. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  65. package/dist/.pikku/schemas/schemas/PikkuGatewayOutput.schema.json +1 -0
  66. package/dist/.pikku/schemas/schemas/PikkuNewAddonInput.schema.json +1 -1
  67. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  68. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  69. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  70. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  71. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  72. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  73. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  74. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  75. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  76. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  77. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  78. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  79. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  80. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.d.ts +1 -1
  81. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  82. package/dist/src/cli.wiring.js +55 -0
  83. package/dist/src/functions/commands/all.js +40 -13
  84. package/dist/src/functions/commands/console.d.ts +3 -0
  85. package/dist/src/functions/commands/console.js +4 -2
  86. package/dist/src/functions/commands/enable.d.ts +4 -0
  87. package/dist/src/functions/commands/enable.js +39 -0
  88. package/dist/src/functions/commands/new-addon.d.ts +3 -0
  89. package/dist/src/functions/commands/new-addon.js +8 -6
  90. package/dist/src/functions/runtimes/fetch/index.js +2 -1
  91. package/dist/src/functions/runtimes/nextjs/pikku-command-nextjs.js +4 -1
  92. package/dist/src/functions/runtimes/websocket/pikku-command-websocket-typed.js +2 -1
  93. package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +3 -5
  94. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +19 -0
  95. package/dist/src/functions/wirings/channels/pikku-command-channels-map.js +1 -1
  96. package/dist/src/functions/wirings/channels/serialize-typed-channel-map.d.ts +1 -1
  97. package/dist/src/functions/wirings/channels/serialize-typed-channel-map.js +7 -6
  98. package/dist/src/functions/wirings/cli/pikku-command-cli-entry.js +9 -1
  99. package/dist/src/functions/wirings/cli/serialize-channel-cli.js +35 -12
  100. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +4 -6
  101. package/dist/src/functions/wirings/console/pikku-command-node-types.js +2 -2
  102. package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +20 -11
  103. package/dist/src/functions/wirings/console/serialize-console-functions.js +1 -1
  104. package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +1 -1
  105. package/dist/src/functions/wirings/functions/serialize-addon-types.js +23 -1
  106. package/dist/src/functions/wirings/functions/serialize-function-types.js +38 -0
  107. package/dist/src/functions/wirings/gateway/pikku-command-gateway.d.ts +1 -0
  108. package/dist/src/functions/wirings/gateway/pikku-command-gateway.js +22 -0
  109. package/dist/src/functions/wirings/mcp/pikku-command-mcp-json.js +1 -1
  110. package/dist/src/functions/wirings/queue/pikku-command-queue-service.js +2 -1
  111. package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +3 -5
  112. package/dist/src/functions/wirings/rpc/pikku-command-remote-rpc.js +3 -5
  113. package/dist/src/functions/wirings/rpc/pikku-command-rpc-client.js +2 -1
  114. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +3 -10
  115. package/dist/src/services.js +13 -1
  116. package/dist/src/utils/file-import-path.js +5 -2
  117. package/dist/src/utils/openapi/codegen.d.ts +1 -0
  118. package/dist/src/utils/openapi/codegen.js +214 -46
  119. package/dist/src/utils/openapi/parse-openapi.d.ts +25 -0
  120. package/dist/src/utils/openapi/parse-openapi.js +119 -9
  121. package/dist/src/utils/openapi/zod-codegen.d.ts +1 -53
  122. package/dist/src/utils/openapi/zod-codegen.js +1 -251
  123. package/dist/src/utils/pikku-cli-config.js +45 -18
  124. package/dist/src/utils/strip-verbose-meta.d.ts +2 -0
  125. package/dist/src/utils/strip-verbose-meta.js +34 -0
  126. package/dist/tsconfig.tsbuildinfo +1 -1
  127. package/package.json +5 -4
  128. package/console-app/assets/index-DiYPTQU_.js +0 -676
@@ -5,7 +5,8 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
5
5
  import { serializeWebsocketWrapper } from './serialize-websocket-wrapper.js';
6
6
  export const pikkuWebSocketTyped = pikkuSessionlessFunc({
7
7
  func: async ({ logger, config }) => {
8
- const { websocketFile, channelsMapDeclarationFile, packageMappings } = config;
8
+ const websocketFile = config.clientFiles?.websocketFile;
9
+ const { channelsMapDeclarationFile, packageMappings } = config;
9
10
  // If websocketFile is not set, clean up any existing file and return
10
11
  if (!websocketFile) {
11
12
  logger.debug({
@@ -3,13 +3,11 @@ 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
5
  import { serializePublicAgent } from './serialize-public-agent.js';
6
- import { join } from 'path';
7
6
  export const pikkuPublicAgent = pikkuSessionlessFunc({
8
7
  func: async ({ logger, config }) => {
9
- if (config.agent?.publicAgentPath) {
10
- const publicAgentPath = join(config.rootDir, config.agent.publicAgentPath);
11
- const pathToPikkuTypes = getFileImportRelativePath(publicAgentPath, config.typesDeclarationFile, config.packageMappings);
12
- await writeFileInDir(logger, publicAgentPath, serializePublicAgent(pathToPikkuTypes, config.agent.publicAgentRequireAuth ?? true));
8
+ if (config.scaffold?.agent) {
9
+ const pathToPikkuTypes = getFileImportRelativePath(config.publicAgentFile, config.typesDeclarationFile, config.packageMappings);
10
+ await writeFileInDir(logger, config.publicAgentFile, serializePublicAgent(pathToPikkuTypes, config.scaffold.agent === 'auth'));
13
11
  return true;
14
12
  }
15
13
  return false;
@@ -40,6 +40,19 @@ export const agentApproveCaller = pikkuSessionlessFunc<
40
40
  },
41
41
  })
42
42
 
43
+ export const agentResumeCaller = pikkuSessionlessFunc<
44
+ { agentName: string; runId: string; toolCallId: string; approved: boolean },
45
+ void
46
+ >({
47
+ auth: ${authFlag},
48
+ func: async (_services, data, { rpc }) => {
49
+ await rpc.agent.resume(data.runId, {
50
+ toolCallId: data.toolCallId,
51
+ approved: data.approved,
52
+ })
53
+ },
54
+ })
55
+
43
56
  export const agentRoutes = defineHTTPRoutes({
44
57
  auth: ${authFlag},
45
58
  tags: ['pikku:public'],
@@ -60,6 +73,12 @@ export const agentRoutes = defineHTTPRoutes({
60
73
  method: 'post',
61
74
  func: agentApproveCaller,
62
75
  },
76
+ agentResume: {
77
+ route: '/rpc/agent/:agentName/resume',
78
+ method: 'post',
79
+ sse: true,
80
+ func: agentResumeCaller,
81
+ },
63
82
  },
64
83
  })
65
84
 
@@ -6,7 +6,7 @@ export const pikkuChannelsMap = pikkuSessionlessFunc({
6
6
  func: async ({ logger, config, getInspectorState }) => {
7
7
  const state = await getInspectorState();
8
8
  const { channelsMapDeclarationFile, packageMappings } = config;
9
- const content = serializeTypedChannelsMap(logger, channelsMapDeclarationFile, packageMappings, state.functions.typesMap, state.functions.meta, state.channels.meta);
9
+ const content = serializeTypedChannelsMap(logger, channelsMapDeclarationFile, packageMappings, state.functions.typesMap, state.functions.meta, state.addonFunctions, state.channels.meta);
10
10
  await writeFileInDir(logger, channelsMapDeclarationFile, content);
11
11
  },
12
12
  middleware: [
@@ -2,4 +2,4 @@ import type { ChannelsMeta } from '@pikku/core/channel';
2
2
  import type { TypesMap } from '@pikku/inspector';
3
3
  import type { FunctionsMeta } from '@pikku/core';
4
4
  import type { Logger } from '@pikku/core/services';
5
- export declare const serializeTypedChannelsMap: (logger: Logger, relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, channelsMeta: ChannelsMeta) => string;
5
+ export declare const serializeTypedChannelsMap: (logger: Logger, relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta, addonFunctions: Record<string, FunctionsMeta>, channelsMeta: ChannelsMeta) => string;
@@ -1,7 +1,7 @@
1
1
  import { serializeImportMap } from '../../../utils/serialize-import-map.js';
2
- import { generateCustomTypes } from '@pikku/inspector';
3
- export const serializeTypedChannelsMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta, channelsMeta) => {
4
- const { channels, requiredTypes } = generateChannels(functionsMeta, channelsMeta);
2
+ import { generateCustomTypes, resolveFunctionMeta } from '@pikku/inspector';
3
+ export const serializeTypedChannelsMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta, addonFunctions, channelsMeta) => {
4
+ const { channels, requiredTypes } = generateChannels(functionsMeta, addonFunctions, channelsMeta);
5
5
  typesMap.customTypes.forEach(({ references }) => {
6
6
  for (const reference of references) {
7
7
  if (reference !== '__object' && !reference.startsWith('__object_')) {
@@ -40,7 +40,8 @@ export type ChannelWiringHandlerOf<
40
40
  : never;
41
41
  `;
42
42
  };
43
- function generateChannels(functionsMeta, channelsMeta) {
43
+ function generateChannels(functionsMeta, addonFunctions, channelsMeta) {
44
+ const state = { functions: { meta: functionsMeta }, addonFunctions };
44
45
  const requiredTypes = new Set();
45
46
  const channelsObject = {};
46
47
  for (const meta of Object.values(channelsMeta)) {
@@ -49,7 +50,7 @@ function generateChannels(functionsMeta, channelsMeta) {
49
50
  channelsObject[name] = { message: null, routes: {} };
50
51
  }
51
52
  if (message) {
52
- const func = functionsMeta[message.pikkuFuncId];
53
+ const func = resolveFunctionMeta(state, message.pikkuFuncId);
53
54
  if (!func) {
54
55
  throw new Error(`Function ${message.pikkuFuncId} not found in functionsMeta for channel ${name}`);
55
56
  }
@@ -67,7 +68,7 @@ function generateChannels(functionsMeta, channelsMeta) {
67
68
  channelsObject[name].routes[key] = {};
68
69
  }
69
70
  for (const [method, { pikkuFuncId }] of Object.entries(route)) {
70
- const func = functionsMeta[pikkuFuncId];
71
+ const func = resolveFunctionMeta(state, pikkuFuncId);
71
72
  if (!func) {
72
73
  throw new Error(`Function ${pikkuFuncId} not found in functionsMeta for channel ${name}, route ${key}, method ${method}`);
73
74
  }
@@ -47,7 +47,15 @@ export const pikkuCLIEntry = pikkuSessionlessFunc({
47
47
  const channelClientPath = entrypointConfig.path;
48
48
  const channelName = entrypointConfig.name;
49
49
  const channelRoute = entrypointConfig.route;
50
- const channelWireFile = join(config.rootDir, channelWirePath);
50
+ // Validate wirePath is not inside the output directory
51
+ const resolvedWirePath = join(config.rootDir, channelWirePath);
52
+ const resolvedOutDir = join(config.rootDir, config.outDir);
53
+ if (resolvedWirePath.startsWith(resolvedOutDir)) {
54
+ throw new Error(`CLI channel wirePath "${channelWirePath}" must not be inside the output directory "${config.outDir}". ` +
55
+ `The wire file uses wireChannel() which must be in a source directory so the inspector can discover it. ` +
56
+ `Move it to a source directory like "src/wirings/cli-channel.gen.ts".`);
57
+ }
58
+ const channelWireFile = resolvedWirePath;
51
59
  const channelCode = serializeChannelCLI(programName, programMeta, channelWireFile, visitState.functions.files, config.packageMappings, config.channelsTypesFile, config.functionTypesFile, channelName, channelRoute);
52
60
  await writeFileInDir(logger, channelWireFile, channelCode);
53
61
  logger.debug(`Serialized CLI channel for ${programName}: ${channelWireFile}`);
@@ -15,6 +15,7 @@ export function serializeChannelCLI(programName, programMeta, channelFile, funct
15
15
  if (cmd.pikkuFuncId) {
16
16
  commandMap[commandKey] = {
17
17
  pikkuFuncId: cmd.pikkuFuncId,
18
+ isAddon: !!cmd.packageName,
18
19
  };
19
20
  }
20
21
  // Recursively process subcommands
@@ -24,20 +25,21 @@ export function serializeChannelCLI(programName, programMeta, channelFile, funct
24
25
  }
25
26
  };
26
27
  collectCommands(programMeta.commands);
27
- // Generate imports from function file locations
28
- const funcNames = [
28
+ const allFuncs = [
29
29
  ...new Set(Object.values(commandMap).map((v) => v.pikkuFuncId)),
30
30
  ];
31
- const imports = funcNames
32
- .map((pikkuFuncId) => {
31
+ const localFuncs = allFuncs.filter((id) => !Object.values(commandMap).find((v) => v.pikkuFuncId === id && v.isAddon));
32
+ const hasAddonFuncs = allFuncs.length > localFuncs.length;
33
+ const importLines = [];
34
+ for (const pikkuFuncId of localFuncs) {
33
35
  const fileInfo = functionFiles.get(pikkuFuncId);
34
36
  if (!fileInfo) {
35
37
  throw new Error(`Function not found in files map: ${pikkuFuncId}`);
36
38
  }
37
39
  const importPath = getFileImportRelativePath(channelFile, fileInfo.path, packageMappings);
38
- return `import { ${fileInfo.exportedName} } from '${importPath}'`;
39
- })
40
- .join('\n');
40
+ importLines.push(`import { ${fileInfo.exportedName} } from '${importPath}'`);
41
+ }
42
+ const imports = importLines.join('\n');
41
43
  // Get relative path to channel types file
42
44
  const channelTypesPath = getFileImportRelativePath(channelFile, channelTypesFile, packageMappings);
43
45
  // Get relative path to function types file
@@ -46,7 +48,9 @@ export function serializeChannelCLI(programName, programMeta, channelFile, funct
46
48
  * WebSocket channel backend for '${programName}' CLI commands
47
49
  */
48
50
  import { wireChannel } from '${channelTypesPath}'
49
- import { pikkuMiddleware } from '${functionTypesPath}'
51
+ import { pikkuMiddleware${hasAddonFuncs ? ', addon' : ''}, pikkuSessionlessFunc } from '${functionTypesPath}'
52
+ import { generateCommandHelp } from '@pikku/core/cli'
53
+ import { pikkuState } from '@pikku/core/internal'
50
54
  ${imports}
51
55
 
52
56
  // Middleware to close the channel after CLI command completes
@@ -61,7 +65,7 @@ const cliCloseOnComplete = pikkuMiddleware(async (_services, { channel }, next)
61
65
  }
62
66
  }, 200)
63
67
  }
64
-
68
+
65
69
  try {
66
70
  const result = await next()
67
71
  closeChannel()
@@ -72,17 +76,36 @@ const cliCloseOnComplete = pikkuMiddleware(async (_services, { channel }, next)
72
76
  }
73
77
  })
74
78
 
79
+ export const cliHelp = pikkuSessionlessFunc<{ args?: string[] }, { help: string }>({
80
+ auth: false,
81
+ func: async (_services, data: { args?: string[] }) => {
82
+ const cliMeta = pikkuState(null, 'cli', 'meta')
83
+ const commandPath = data?.args?.length ? data.args : []
84
+ const helpText = generateCommandHelp('${programName}', cliMeta as any, commandPath)
85
+ return { help: helpText }
86
+ },
87
+ })
88
+
75
89
  wireChannel({
76
90
  name: '${finalChannelName}',
77
91
  route: '${finalChannelRoute}',
78
92
  auth: false,
79
93
  onMessageWiring: {
80
94
  command: {
95
+ '__help': {
96
+ func: cliHelp,
97
+ middleware: [cliCloseOnComplete],
98
+ },
81
99
  ${Object.entries(commandMap)
82
- .map(([commandKey, { pikkuFuncId }]) => ` '${commandKey}': {
83
- func: ${pikkuFuncId},
100
+ .map(([commandKey, { pikkuFuncId, isAddon }]) => {
101
+ const funcRef = isAddon
102
+ ? `addon('${pikkuFuncId}')`
103
+ : (functionFiles.get(pikkuFuncId)?.exportedName ?? pikkuFuncId);
104
+ return ` '${commandKey}': {
105
+ func: ${funcRef},
84
106
  middleware: [cliCloseOnComplete],
85
- }`)
107
+ }`;
108
+ })
86
109
  .join(',\n')}
87
110
  }
88
111
  },
@@ -3,14 +3,12 @@ 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
5
  import { serializeConsoleFunctions } from './serialize-console-functions.js';
6
- import { join } from 'path';
7
6
  export const pikkuConsoleFunctions = pikkuSessionlessFunc({
8
7
  func: async ({ logger, config }) => {
9
- if (config.console?.functionsPath) {
10
- const consoleFunctionsPath = join(config.rootDir, config.console.functionsPath);
11
- const pathToPikkuTypes = getFileImportRelativePath(consoleFunctionsPath, config.typesDeclarationFile, config.packageMappings);
12
- const pathToAgentTypes = getFileImportRelativePath(consoleFunctionsPath, config.agentTypesFile, config.packageMappings);
13
- await writeFileInDir(logger, consoleFunctionsPath, serializeConsoleFunctions(pathToPikkuTypes, pathToAgentTypes));
8
+ if (config.scaffold?.console) {
9
+ const pathToPikkuTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.typesDeclarationFile, config.packageMappings);
10
+ const pathToAgentTypes = getFileImportRelativePath(config.consoleFunctionsFile, config.agentTypesFile, config.packageMappings);
11
+ await writeFileInDir(logger, config.consoleFunctionsFile, serializeConsoleFunctions(pathToPikkuTypes, pathToAgentTypes));
14
12
  return true;
15
13
  }
16
14
  return false;
@@ -5,10 +5,10 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
5
5
  import { serializeNodeTypes } from './serialize-node-types.js';
6
6
  export const pikkuNodeTypes = pikkuSessionlessFunc({
7
7
  func: async ({ logger, config }) => {
8
- const { nodeTypesFile, rpcInternalMapDeclarationFile, packageMappings, node, } = config;
8
+ const { nodeTypesFile, rpcInternalMapDeclarationFile, packageMappings, addon, } = config;
9
9
  let rpcMapImportPath = getFileImportRelativePath(nodeTypesFile, rpcInternalMapDeclarationFile, packageMappings);
10
10
  rpcMapImportPath = rpcMapImportPath.replace('.d.js', '.js');
11
- const categories = node?.categories;
11
+ const categories = typeof addon === 'object' ? addon?.categories : undefined;
12
12
  const content = serializeNodeTypes(rpcMapImportPath, categories);
13
13
  await writeFileInDir(logger, nodeTypesFile, content);
14
14
  },
@@ -28,14 +28,16 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
28
28
  func: async ({ logger, config, getInspectorState }) => {
29
29
  const state = await getInspectorState();
30
30
  const { nodes, secrets } = state;
31
- const { nodesMetaJsonFile, node, rootDir } = config;
31
+ const { addonMetaJsonFile, addon, rootDir } = config;
32
+ const addonMeta = typeof addon === 'object' ? addon : undefined;
32
33
  const secretsMeta = validateAndBuildSecretDefinitionsMeta(secrets.definitions, state.schemaLookup);
33
34
  const hasNodes = Object.keys(nodes.meta).length > 0;
34
35
  const hasSecrets = secrets.definitions.length > 0;
35
- if (!hasNodes && !hasSecrets) {
36
+ const hasPackageMeta = !!addonMeta?.icon || !!addonMeta?.displayName;
37
+ if (!hasNodes && !hasSecrets && !hasPackageMeta) {
36
38
  return undefined;
37
39
  }
38
- const allowedCategories = node?.categories;
40
+ const allowedCategories = addonMeta?.categories;
39
41
  if (allowedCategories && allowedCategories.length > 0) {
40
42
  for (const [name, meta] of Object.entries(nodes.meta)) {
41
43
  if (!allowedCategories.includes(meta.category)) {
@@ -49,23 +51,30 @@ export const pikkuNodesMeta = pikkuSessionlessFunc({
49
51
  const { icon: _icon, ...nodeMetaWithoutIcon } = meta;
50
52
  outputMeta[name] = nodeMetaWithoutIcon;
51
53
  }
52
- const packageIcon = await loadIcon(node?.icon, rootDir, logger);
54
+ const packageIcon = await loadIcon(addonMeta?.icon, rootDir, logger);
53
55
  const metaData = {
54
56
  nodes: outputMeta,
55
57
  secrets: secretsMeta,
56
58
  package: {
57
- displayName: node?.displayName,
58
- description: node?.description,
59
+ displayName: addonMeta?.displayName,
60
+ description: addonMeta?.description,
59
61
  icon: packageIcon,
60
- categories: node?.categories,
62
+ categories: addonMeta?.categories,
61
63
  },
62
64
  };
63
- if (nodesMetaJsonFile) {
65
+ if (addonMetaJsonFile && (config.scaffold?.console || config.addon)) {
64
66
  const minimalMeta = stripVerboseFields(metaData);
65
- await writeFileInDir(logger, nodesMetaJsonFile, JSON.stringify(minimalMeta, null, 2), { ignoreModifyComment: true });
67
+ await writeFileInDir(logger, addonMetaJsonFile, JSON.stringify(minimalMeta, null, 2), { ignoreModifyComment: true });
66
68
  if (hasVerboseFields(metaData)) {
67
- const verbosePath = nodesMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json');
68
- await writeFileInDir(logger, verbosePath, JSON.stringify(metaData, null, 2), { ignoreModifyComment: true });
69
+ const verbosePath = addonMetaJsonFile.endsWith('.gen.json')
70
+ ? addonMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json')
71
+ : addonMetaJsonFile.replace(/(\.\w+)$/, '-verbose$1');
72
+ if (verbosePath === addonMetaJsonFile) {
73
+ logger.warn(`Cannot derive verbose path from ${addonMetaJsonFile}, skipping verbose metadata`);
74
+ }
75
+ else {
76
+ await writeFileInDir(logger, verbosePath, JSON.stringify(metaData, null, 2), { ignoreModifyComment: true });
77
+ }
69
78
  }
70
79
  }
71
80
  return true;
@@ -85,6 +85,6 @@ export const consoleRoutes = defineHTTPRoutes({
85
85
  })
86
86
 
87
87
  wireAddon({ name: 'console', package: '@pikku/addon-console' })
88
- wireHTTPRoutes({ baseRoute: '/api', routes: { console: consoleRoutes } })
88
+ wireHTTPRoutes({ basePath: '/api', routes: { console: consoleRoutes } })
89
89
  `;
90
90
  };
@@ -21,7 +21,7 @@ export const pikkuFunctionTypesSplit = pikkuSessionlessFunc({
21
21
  const configTypeImport = pikkuConfigType
22
22
  ? `import type { ${pikkuConfigType.type} } from '${getFileImportRelativePath(functionTypesFile, pikkuConfigType.typePath, packageMappings)}'`
23
23
  : '// Config type not found, will use fallback';
24
- const content = serializeFunctionTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(functionTypesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${wireServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, wireServicesType.typePath, packageMappings)}'`, wireServicesType.type, `import type { TypedPikkuRPC, FlattenedRPCMap } from '${getFileImportRelativePath(functionTypesFile, rpcInternalMapDeclarationFile, packageMappings)}'`, `import type { RequiredSingletonServices, RequiredWireServices } from '${getFileImportRelativePath(functionTypesFile, servicesFile, packageMappings)}'`, configTypeImport, config.addonName, undefined, config.node?.categories);
24
+ const content = serializeFunctionTypes(`import type { ${userSessionType.type} } from '${getFileImportRelativePath(functionTypesFile, userSessionType.typePath, packageMappings)}'`, userSessionType.type, `import type { ${singletonServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, singletonServicesType.typePath, packageMappings)}'`, singletonServicesType.type, `import type { ${wireServicesType.type} } from '${getFileImportRelativePath(functionTypesFile, wireServicesType.typePath, packageMappings)}'`, wireServicesType.type, `import type { TypedPikkuRPC, FlattenedRPCMap } from '${getFileImportRelativePath(functionTypesFile, rpcInternalMapDeclarationFile, packageMappings)}'`, `import type { RequiredSingletonServices, RequiredWireServices } from '${getFileImportRelativePath(functionTypesFile, servicesFile, packageMappings)}'`, configTypeImport, config.addonName, undefined, typeof config.addon === 'object' ? config.addon?.categories : undefined);
25
25
  await writeFileInDir(logger, functionTypesFile, content);
26
26
  },
27
27
  middleware: [
@@ -61,18 +61,40 @@ export const pikkuAddonServices = <T extends Record<string, any>, ExistingServic
61
61
  func: (config: Config, services: ExistingServices) => Promise<T>
62
62
  ) => {
63
63
  return async (config: Config, existingServices?: Partial<SingletonServices>): Promise<RequiredSingletonServices> => {
64
- const { logger, variables, secrets } = (existingServices ?? {}) as unknown as SingletonServices
64
+ const { logger, variables, secrets, schema } = (existingServices ?? {}) as unknown as SingletonServices
65
65
  const typedVariables = new TypedVariablesService(variables)
66
66
  const typedSecrets = new TypedSecretService(secrets)
67
67
  const result = await func(config, { ...existingServices, logger, variables: typedVariables, secrets: typedSecrets } as ExistingServices)
68
68
  return {
69
69
  config,
70
70
  logger,
71
+ schema,
71
72
  variables: typedVariables,
72
73
  secrets: typedSecrets,
73
74
  ...result,
74
75
  } as unknown as RequiredSingletonServices
75
76
  }
76
77
  }
78
+
79
+ /**
80
+ * Creates a Pikku wire services factory for addon packages.
81
+ * Wire services are created per-request and have access to the HTTP request context.
82
+ *
83
+ * @param func - Wire services factory function that receives singleton services and the wire context
84
+ * @returns The wire services factory function
85
+ *
86
+ * @example
87
+ * \`\`\`typescript
88
+ * export const createWireServices = pikkuAddonWireServices(async (services, wire) => {
89
+ * const authHeader = wire.http?.request?.header('authorization')
90
+ * return { myService: new MyService(authHeader) }
91
+ * })
92
+ * \`\`\`
93
+ */
94
+ export const pikkuAddonWireServices = (
95
+ func: (services: SingletonServices, wire: any) => Promise<Record<string, any>>
96
+ ) => {
97
+ return func as any
98
+ }
77
99
  `;
78
100
  };
@@ -15,6 +15,7 @@ export const serializeFunctionTypes = (userSessionTypeImport, userSessionTypeNam
15
15
  import { CorePikkuFunctionConfig, CorePikkuAuth, CorePikkuAuthConfig, CorePikkuPermission, CorePikkuMiddleware, CorePermissionGroup, addMiddleware as addMiddlewareCore, addPermission as addPermissionCore, PikkuWire, PickRequired } from '@pikku/core'
16
16
  import { pikkuState as __pikkuState, CreateWireServices } from '@pikku/core/internal'
17
17
  import { pikkuAuth as pikkuAuthCore } from '@pikku/core'
18
+ import { PikkuError } from '@pikku/core/errors'
18
19
  import type { NodeType } from '@pikku/core/node'
19
20
  import type { StandardSchemaV1 } from '@standard-schema/spec'
20
21
  import { CorePikkuFunction, CorePikkuFunctionSessionless } from '@pikku/core/function'
@@ -229,6 +230,37 @@ export const pikkuPermissionFactory = <In = any>(
229
230
  return factory
230
231
  }
231
232
 
233
+ /**
234
+ * A function that generates a human-readable description of a pending approval action.
235
+ * Used by AI agents to show meaningful approval prompts instead of raw tool arguments.
236
+ *
237
+ * @template In - The input type (same as the function it describes)
238
+ * @template RequiredServices - The services required for this description function
239
+ */
240
+ export type PikkuApprovalDescription<In = unknown, RequiredServices extends Services = Services> = (
241
+ services: RequiredServices,
242
+ data: In
243
+ ) => Promise<string>
244
+
245
+ /**
246
+ * Factory function for creating approval description functions with tree-shaking support.
247
+ *
248
+ * @example
249
+ * \`\`\`typescript
250
+ * export const deleteTodoApproval = pikkuApprovalDescription(
251
+ * async ({ todoStore }, { id }) => {
252
+ * const todo = await todoStore.get(id)
253
+ * return \\\`Delete todo: "\${todo.title}"\\\`
254
+ * }
255
+ * )
256
+ * \`\`\`
257
+ */
258
+ export const pikkuApprovalDescription = <In = unknown, RequiredServices extends Services = Services>(
259
+ fn: PikkuApprovalDescription<In, RequiredServices>
260
+ ): PikkuApprovalDescription<In, RequiredServices> => {
261
+ return fn
262
+ }
263
+
232
264
  /**
233
265
  * A sessionless API function that doesn't require user authentication.
234
266
  * Use this for public endpoints, health checks, or operations that don't need user context.
@@ -308,6 +340,8 @@ export type PikkuFunctionConfigWithSchema<
308
340
  expose?: boolean
309
341
  mcp?: boolean
310
342
  internal?: boolean
343
+ approvalRequired?: boolean
344
+ approvalDescription?: PikkuApprovalDescription<InferSchemaOutput<InputSchema>>
311
345
  func: PikkuFunction<
312
346
  InferSchemaOutput<InputSchema>,
313
347
  OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
@@ -323,6 +357,7 @@ export type PikkuFunctionConfigWithSchema<
323
357
  input: InputSchema
324
358
  output?: OutputSchema
325
359
  node?: NodeConfig
360
+ errors?: Array<typeof PikkuError>
326
361
  }
327
362
 
328
363
  /**
@@ -393,6 +428,8 @@ export type PikkuFunctionSessionlessConfigWithSchema<
393
428
  mcp?: boolean
394
429
  internal?: boolean
395
430
  remote?: boolean
431
+ approvalRequired?: boolean
432
+ approvalDescription?: PikkuApprovalDescription<InferSchemaOutput<InputSchema>>
396
433
  func: PikkuFunctionSessionless<
397
434
  InferSchemaOutput<InputSchema>,
398
435
  OutputSchema extends StandardSchemaV1 ? InferSchemaOutput<OutputSchema> : unknown,
@@ -404,6 +441,7 @@ export type PikkuFunctionSessionlessConfigWithSchema<
404
441
  input: InputSchema
405
442
  output?: OutputSchema
406
443
  node?: NodeConfig
444
+ errors?: Array<typeof PikkuError>
407
445
  }
408
446
 
409
447
  /**
@@ -0,0 +1 @@
1
+ export declare const pikkuGateway: import("#pikku").PikkuFunctionConfig<void, boolean | undefined, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, boolean | undefined, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, boolean | undefined, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -0,0 +1,22 @@
1
+ import { pikkuSessionlessFunc } from '#pikku';
2
+ import { serializeFileImports } from '../../../utils/file-imports-serializer.js';
3
+ import { writeFileInDir } from '../../../utils/file-writer.js';
4
+ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
+ export const pikkuGateway = pikkuSessionlessFunc({
6
+ func: async ({ logger, config, getInspectorState }) => {
7
+ const visitState = await getInspectorState();
8
+ const { gatewaysWiringFile, packageMappings } = config;
9
+ const { gateways } = visitState;
10
+ if (Object.keys(gateways.meta).length === 0) {
11
+ return undefined;
12
+ }
13
+ await writeFileInDir(logger, gatewaysWiringFile, serializeFileImports('wireGateway', gatewaysWiringFile, gateways.files, packageMappings));
14
+ return true;
15
+ },
16
+ middleware: [
17
+ logCommandInfoAndTime({
18
+ commandStart: 'Finding gateways',
19
+ commandEnd: 'Found gateways',
20
+ }),
21
+ ],
22
+ });
@@ -5,7 +5,7 @@ import { serializeMCPJson } from '@pikku/inspector';
5
5
  export const pikkuMCPJSON = pikkuSessionlessFunc({
6
6
  func: async ({ logger, config, getInspectorState }) => {
7
7
  const state = await getInspectorState();
8
- const { mcpJsonFile } = config;
8
+ const mcpJsonFile = config.clientFiles?.mcpJsonFile;
9
9
  if (mcpJsonFile) {
10
10
  const mcpJson = serializeMCPJson(logger, state);
11
11
  await writeFileInDir(logger, mcpJsonFile, mcpJson, {
@@ -5,7 +5,8 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
5
5
  import { serializeQueueWrapper } from './serialize-queue-wrapper.js';
6
6
  export const pikkuQueueService = pikkuSessionlessFunc({
7
7
  func: async ({ logger, config }) => {
8
- const { queueWiringsFile, queueMapDeclarationFile, packageMappings } = config;
8
+ const queueWiringsFile = config.clientFiles?.queueWiringsFile;
9
+ const { queueMapDeclarationFile, packageMappings } = config;
9
10
  // If queueWiringsFile is not set, clean up any existing file and return
10
11
  if (!queueWiringsFile) {
11
12
  logger.debug({
@@ -3,13 +3,11 @@ 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
5
  import { serializePublicRPC } from './serialize-public-rpc.js';
6
- import { join } from 'path';
7
6
  export const pikkuPublicRPC = pikkuSessionlessFunc({
8
7
  func: async ({ logger, config }) => {
9
- if (config.rpc?.publicRpcPath) {
10
- const publicRpcPath = join(config.rootDir, config.rpc.publicRpcPath);
11
- const pathToPikkuTypes = getFileImportRelativePath(publicRpcPath, config.typesDeclarationFile, config.packageMappings);
12
- await writeFileInDir(logger, publicRpcPath, serializePublicRPC(pathToPikkuTypes, config.rpc.publicRpcRequireAuth ?? true));
8
+ if (config.scaffold?.rpc) {
9
+ const pathToPikkuTypes = getFileImportRelativePath(config.publicRpcFile, config.typesDeclarationFile, config.packageMappings);
10
+ await writeFileInDir(logger, config.publicRpcFile, serializePublicRPC(pathToPikkuTypes, config.scaffold.rpc === 'auth'));
13
11
  return true;
14
12
  }
15
13
  return false;
@@ -3,13 +3,11 @@ 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
5
  import { serializeRemoteRPC } from './serialize-remote-rpc.js';
6
- import { join } from 'path';
7
6
  export const pikkuRemoteRPC = pikkuSessionlessFunc({
8
7
  func: async ({ logger, config }) => {
9
- if (config.rpc?.remoteRpcWorkersPath) {
10
- const remoteRpcPath = join(config.rootDir, config.rpc.remoteRpcWorkersPath);
11
- const pathToPikkuTypes = getFileImportRelativePath(remoteRpcPath, config.typesDeclarationFile, config.packageMappings);
12
- await writeFileInDir(logger, remoteRpcPath, serializeRemoteRPC(pathToPikkuTypes));
8
+ if (config.remoteRpcWorkersFile) {
9
+ const pathToPikkuTypes = getFileImportRelativePath(config.remoteRpcWorkersFile, config.typesDeclarationFile, config.packageMappings);
10
+ await writeFileInDir(logger, config.remoteRpcWorkersFile, serializeRemoteRPC(pathToPikkuTypes));
13
11
  return true;
14
12
  }
15
13
  return false;
@@ -5,7 +5,8 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
5
5
  import { serializeRPCWrapper } from './serialize-rpc-wrapper.js';
6
6
  export const pikkuRPCClient = pikkuSessionlessFunc({
7
7
  func: async ({ logger, config }) => {
8
- const { rpcWiringsFile, rpcMapDeclarationFile, packageMappings } = config;
8
+ const rpcWiringsFile = config.clientFiles?.rpcWiringsFile;
9
+ const { rpcMapDeclarationFile, packageMappings } = config;
9
10
  // If rpcWiringsFile is not set, clean up any existing file and return
10
11
  if (!rpcWiringsFile) {
11
12
  logger.debug({
@@ -46,16 +46,9 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
46
46
  const workflowMapImportPath = getFileImportRelativePath(workflowTypesFile, workflowMapDeclarationFile, packageMappings);
47
47
  await writeFileInDir(logger, workflowTypesFile, serializeWorkflowTypes(functionTypesImportPath, rpcMapImportPath, workflowMapImportPath));
48
48
  await writeFileInDir(logger, workflowMapDeclarationFile, serializeWorkflowMap(logger, workflowMapDeclarationFile, packageMappings, typesMap, functionState.meta, workflows.meta, workflows.graphMeta));
49
- if (config.workflows) {
50
- if (config.workflows.singleQueue) {
51
- const workflowPath = join(config.rootDir, config.workflows.path);
52
- const pathToPikkuTypes = getFileImportRelativePath(workflowPath, typesDeclarationFile, packageMappings);
53
- await writeFileInDir(logger, workflowPath, serializeWorkflowWorkers(pathToPikkuTypes));
54
- }
55
- else if (workflows.files.size > 0) {
56
- logger.critical(ErrorCode.WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED, 'Multi-queue workflows are not supported when workflows.singleQueue is false. Please enable singleQueue in your configuration.');
57
- return false;
58
- }
49
+ if (config.scaffold?.workflow) {
50
+ const pathToPikkuTypes = getFileImportRelativePath(config.workflowWorkersFile, typesDeclarationFile, packageMappings);
51
+ await writeFileInDir(logger, config.workflowWorkersFile, serializeWorkflowWorkers(pathToPikkuTypes));
59
52
  }
60
53
  return hasWorkflows;
61
54
  },
@@ -6,6 +6,7 @@ import { inspect, serializeInspectorState, deserializeInspectorState, filterInsp
6
6
  import { glob } from 'tinyglobby';
7
7
  import path from 'path';
8
8
  import { CLILoggerForwarder } from './services/cli-logger-forwarder.service.js';
9
+ import { existsSync } from 'fs';
9
10
  import { readFile, writeFile } from 'fs/promises';
10
11
  import { loadManifest } from './utils/contract-versions.js';
11
12
  import { join } from 'path';
@@ -202,6 +203,17 @@ export const createSingletonServices = async (config) => {
202
203
  ignore: config.ignoreFiles || [],
203
204
  absolute: true,
204
205
  })))).flat();
206
+ const scaffoldFiles = [
207
+ config.consoleFunctionsFile,
208
+ config.publicRpcFile,
209
+ config.publicAgentFile,
210
+ config.workflowWorkersFile,
211
+ ];
212
+ for (const file of scaffoldFiles) {
213
+ if (file && !wiringFiles.includes(file) && existsSync(file)) {
214
+ wiringFiles.push(file);
215
+ }
216
+ }
205
217
  const manifest = !setupOnly
206
218
  ? ((await loadManifest(join(config.outDir, 'versions.json'))) ??
207
219
  undefined)
@@ -209,7 +221,7 @@ export const createSingletonServices = async (config) => {
209
221
  unfilteredState = await inspect(logger, wiringFiles, {
210
222
  setupOnly,
211
223
  rootDir,
212
- isAddon: config.addon,
224
+ isAddon: !!config.addon,
213
225
  types: {
214
226
  configFileType: config.configFile,
215
227
  userSessionType: config.userSessionType,