@pikku/core 0.9.12-next.0 → 0.10.1

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 (85) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/errors/errors.d.ts +2 -0
  3. package/dist/errors/errors.js +6 -0
  4. package/dist/function/function-runner.d.ts +5 -4
  5. package/dist/function/function-runner.js +10 -4
  6. package/dist/function/functions.types.d.ts +67 -1
  7. package/dist/function/functions.types.js +43 -1
  8. package/dist/index.d.ts +1 -7
  9. package/dist/index.js +1 -7
  10. package/dist/middleware/auth-apikey.d.ts +19 -11
  11. package/dist/middleware/auth-apikey.js +33 -33
  12. package/dist/middleware/auth-bearer.d.ts +34 -7
  13. package/dist/middleware/auth-bearer.js +53 -36
  14. package/dist/middleware/auth-cookie.d.ts +30 -12
  15. package/dist/middleware/auth-cookie.js +51 -43
  16. package/dist/middleware/timeout.d.ts +6 -2
  17. package/dist/middleware/timeout.js +7 -9
  18. package/dist/middleware-runner.d.ts +0 -18
  19. package/dist/middleware-runner.js +0 -21
  20. package/dist/permissions.d.ts +82 -20
  21. package/dist/permissions.js +175 -62
  22. package/dist/pikku-state.d.ts +22 -2
  23. package/dist/pikku-state.js +7 -1
  24. package/dist/types/core.types.d.ts +58 -1
  25. package/dist/types/core.types.js +23 -1
  26. package/dist/utils.d.ts +1 -1
  27. package/dist/wirings/channel/channel-handler.js +6 -5
  28. package/dist/wirings/channel/channel-runner.d.ts +3 -1
  29. package/dist/wirings/channel/channel-runner.js +4 -21
  30. package/dist/wirings/channel/channel.types.d.ts +12 -13
  31. package/dist/wirings/cli/channel/cli-channel-runner.js +5 -2
  32. package/dist/wirings/cli/cli-runner.d.ts +10 -0
  33. package/dist/wirings/cli/cli-runner.js +44 -20
  34. package/dist/wirings/cli/cli.types.d.ts +54 -27
  35. package/dist/wirings/cli/command-parser.js +34 -4
  36. package/dist/wirings/cli/index.d.ts +1 -1
  37. package/dist/wirings/cli/index.js +1 -1
  38. package/dist/wirings/http/http-runner.d.ts +31 -1
  39. package/dist/wirings/http/http-runner.js +36 -1
  40. package/dist/wirings/http/http.types.d.ts +2 -1
  41. package/dist/wirings/http/index.d.ts +1 -1
  42. package/dist/wirings/http/index.js +1 -1
  43. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  44. package/dist/wirings/mcp/mcp-runner.js +2 -0
  45. package/dist/wirings/mcp/mcp.types.d.ts +35 -14
  46. package/dist/wirings/queue/queue-runner.js +1 -0
  47. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  48. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  49. package/package.json +6 -5
  50. package/src/errors/errors.ts +6 -0
  51. package/src/factory-functions.test.ts +60 -1
  52. package/src/function/function-runner.test.ts +8 -3
  53. package/src/function/function-runner.ts +18 -5
  54. package/src/function/functions.types.ts +85 -2
  55. package/src/index.ts +1 -21
  56. package/src/middleware/auth-apikey.ts +42 -57
  57. package/src/middleware/auth-bearer.ts +66 -49
  58. package/src/middleware/auth-cookie.ts +63 -82
  59. package/src/middleware/timeout.ts +10 -14
  60. package/src/middleware-runner.ts +0 -25
  61. package/src/permissions.test.ts +26 -27
  62. package/src/permissions.ts +215 -104
  63. package/src/pikku-state.ts +35 -3
  64. package/src/types/core.types.ts +70 -3
  65. package/src/utils.ts +1 -1
  66. package/src/wirings/channel/channel-handler.ts +11 -10
  67. package/src/wirings/channel/channel-runner.ts +18 -27
  68. package/src/wirings/channel/channel.types.ts +24 -15
  69. package/src/wirings/cli/channel/cli-channel-runner.ts +8 -3
  70. package/src/wirings/cli/cli-runner.test.ts +80 -59
  71. package/src/wirings/cli/cli-runner.ts +57 -21
  72. package/src/wirings/cli/cli.types.ts +74 -44
  73. package/src/wirings/cli/command-parser.test.ts +91 -86
  74. package/src/wirings/cli/command-parser.ts +38 -4
  75. package/src/wirings/cli/index.ts +1 -0
  76. package/src/wirings/http/http-runner.ts +41 -1
  77. package/src/wirings/http/http.types.ts +3 -1
  78. package/src/wirings/http/index.ts +7 -1
  79. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  80. package/src/wirings/mcp/mcp-runner.ts +2 -0
  81. package/src/wirings/mcp/mcp.types.ts +48 -12
  82. package/src/wirings/queue/queue-runner.ts +1 -0
  83. package/src/wirings/scheduler/scheduler-runner.ts +1 -0
  84. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  85. package/tsconfig.tsbuildinfo +1 -1
@@ -6,7 +6,7 @@ import { ErrorDetails, PikkuError } from './errors/error-handler.js';
6
6
  import { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuPermission } from './function/functions.types.js';
7
7
  import { queueWorkersMeta, CoreQueueWorker } from './wirings/queue/queue.types.js';
8
8
  import { CoreMCPResource, CoreMCPTool, CoreMCPPrompt, MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from './wirings/mcp/mcp.types.js';
9
- import { CLIProgramMeta, CLIProgramState } from './wirings/cli/cli.types.js';
9
+ import { CLIMeta, CLIProgramState } from './wirings/cli/cli.types.js';
10
10
  interface PikkuState {
11
11
  function: {
12
12
  meta: FunctionsMeta;
@@ -46,7 +46,7 @@ interface PikkuState {
46
46
  promptsMeta: MCPPromptMeta;
47
47
  };
48
48
  cli: {
49
- meta: Record<string, CLIProgramMeta>;
49
+ meta: CLIMeta | Record<string, any>;
50
50
  programs: Record<string, CLIProgramState>;
51
51
  };
52
52
  middleware: {
@@ -69,6 +69,26 @@ interface PikkuState {
69
69
  isFactory: boolean;
70
70
  }>;
71
71
  };
72
+ permissions: {
73
+ tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
74
+ httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>;
75
+ tagGroupMeta: Record<string, {
76
+ exportName: string | null;
77
+ sourceFile: string;
78
+ position: number;
79
+ services: FunctionServicesMeta;
80
+ permissionCount: number;
81
+ isFactory: boolean;
82
+ }>;
83
+ httpGroupMeta: Record<string, {
84
+ exportName: string | null;
85
+ sourceFile: string;
86
+ position: number;
87
+ services: FunctionServicesMeta;
88
+ permissionCount: number;
89
+ isFactory: boolean;
90
+ }>;
91
+ };
72
92
  misc: {
73
93
  errors: Map<PikkuError, ErrorDetails>;
74
94
  schemas: Map<string, any>;
@@ -34,7 +34,7 @@ export const resetPikkuState = () => {
34
34
  promptsMeta: {},
35
35
  },
36
36
  cli: {
37
- meta: {},
37
+ meta: { programs: {}, renderers: {} },
38
38
  programs: {},
39
39
  },
40
40
  middleware: {
@@ -43,6 +43,12 @@ export const resetPikkuState = () => {
43
43
  tagGroupMeta: {},
44
44
  httpGroupMeta: {},
45
45
  },
46
+ permissions: {
47
+ tagGroup: {},
48
+ httpGroup: {},
49
+ tagGroupMeta: {},
50
+ httpGroupMeta: {},
51
+ },
46
52
  misc: {
47
53
  errors: globalThis.pikkuState?.misc?.errors || new Map(),
48
54
  schemas: new Map(),
@@ -1,6 +1,7 @@
1
1
  import type { Logger, LogLevel } from '../services/logger.js';
2
2
  import { VariablesService } from '../services/variables-service.js';
3
3
  import { SchemaService } from '../services/schema-service.js';
4
+ import { JWTService } from '../services/jwt-service.js';
4
5
  import { PikkuHTTP } from '../wirings/http/http.types.js';
5
6
  import { UserSessionService } from '../services/user-session-service.js';
6
7
  import { PikkuChannel } from '../wirings/channel/channel.types.js';
@@ -39,6 +40,23 @@ export type MiddlewareMetadata = {
39
40
  name: string;
40
41
  inline?: boolean;
41
42
  };
43
+ /**
44
+ * Metadata for permissions at any level
45
+ * - type: 'http' = HTTP route permission group (references httpGroup in pikkuState)
46
+ * - type: 'tag' = Tag-based permission group (references tagGroup in pikkuState)
47
+ * - type: 'wire' = Wire-level individual permission
48
+ */
49
+ export type PermissionMetadata = {
50
+ type: 'http';
51
+ route: string;
52
+ } | {
53
+ type: 'tag';
54
+ tag: string;
55
+ } | {
56
+ type: 'wire';
57
+ name: string;
58
+ inline?: boolean;
59
+ };
42
60
  export type FunctionRuntimeMeta = {
43
61
  pikkuFuncName: string;
44
62
  inputSchemaName: string | null;
@@ -54,6 +72,7 @@ export type FunctionMeta = FunctionRuntimeMeta & Partial<{
54
72
  docs: PikkuDocs;
55
73
  isDirectFunction: boolean;
56
74
  middleware: MiddlewareMetadata[];
75
+ permissions: PermissionMetadata[];
57
76
  }>;
58
77
  export type FunctionsRuntimeMeta = Record<string, FunctionRuntimeMeta>;
59
78
  export type FunctionsMeta = Record<string, FunctionMeta>;
@@ -102,6 +121,8 @@ export interface CoreUserSession {
102
121
  export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
103
122
  /** The schema library used to validate data */
104
123
  schema?: SchemaService;
124
+ /** The JWT service used to encode and decode tokens */
125
+ jwt?: JWTService;
105
126
  /** The core configuration for the application. */
106
127
  config: Config;
107
128
  /** The logger used by the application. */
@@ -127,6 +148,20 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
127
148
  export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices & {
128
149
  userSession: UserSessionService<UserSession>;
129
150
  }, interactions: PikkuInteraction, next: () => Promise<void>) => Promise<void>;
151
+ /**
152
+ * Configuration object for creating middleware with metadata
153
+ *
154
+ * @template SingletonServices - The singleton services type
155
+ * @template UserSession - The user session type
156
+ */
157
+ export type CorePikkuMiddlewareConfig<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = {
158
+ /** The middleware function */
159
+ func: CorePikkuMiddleware<SingletonServices, UserSession>;
160
+ /** Optional human-readable name for the middleware */
161
+ name?: string;
162
+ /** Optional description of what the middleware does */
163
+ description?: string;
164
+ };
130
165
  /**
131
166
  * A factory function that takes input and returns middleware
132
167
  * Used when middleware needs configuration/input parameters
@@ -139,8 +174,30 @@ export type CorePikkuMiddlewareFactory<In = any, SingletonServices extends CoreS
139
174
  export type CorePikkuMiddlewareGroup<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = Array<CorePikkuMiddleware<SingletonServices, UserSession> | CorePikkuMiddlewareFactory<any, SingletonServices, UserSession>>;
140
175
  /**
141
176
  * Factory function for creating middleware with tree-shaking support
177
+ * Supports both direct function and configuration object syntax
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * // Direct function syntax
182
+ * export const logMiddleware = pikkuMiddleware(
183
+ * async ({ logger }, _interaction, next) => {
184
+ * logger.info('Request started')
185
+ * await next()
186
+ * }
187
+ * )
188
+ *
189
+ * // Configuration object syntax with metadata
190
+ * export const logMiddleware = pikkuMiddleware({
191
+ * name: 'Request Logger',
192
+ * description: 'Logs request information',
193
+ * func: async ({ logger }, _interaction, next) => {
194
+ * logger.info('Request started')
195
+ * await next()
196
+ * }
197
+ * })
198
+ * ```
142
199
  */
143
- export declare const pikkuMiddleware: <SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession>(middleware: CorePikkuMiddleware<SingletonServices, UserSession>) => CorePikkuMiddleware<SingletonServices, UserSession>;
200
+ export declare const pikkuMiddleware: <SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession>(middleware: CorePikkuMiddleware<SingletonServices, UserSession> | CorePikkuMiddlewareConfig<SingletonServices, UserSession>) => CorePikkuMiddleware<SingletonServices, UserSession>;
144
201
  /**
145
202
  * Factory function for creating middleware factories
146
203
  * Use this when your middleware needs configuration/input parameters
@@ -10,9 +10,31 @@ export var PikkuWiringTypes;
10
10
  })(PikkuWiringTypes || (PikkuWiringTypes = {}));
11
11
  /**
12
12
  * Factory function for creating middleware with tree-shaking support
13
+ * Supports both direct function and configuration object syntax
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Direct function syntax
18
+ * export const logMiddleware = pikkuMiddleware(
19
+ * async ({ logger }, _interaction, next) => {
20
+ * logger.info('Request started')
21
+ * await next()
22
+ * }
23
+ * )
24
+ *
25
+ * // Configuration object syntax with metadata
26
+ * export const logMiddleware = pikkuMiddleware({
27
+ * name: 'Request Logger',
28
+ * description: 'Logs request information',
29
+ * func: async ({ logger }, _interaction, next) => {
30
+ * logger.info('Request started')
31
+ * await next()
32
+ * }
33
+ * })
34
+ * ```
13
35
  */
14
36
  export const pikkuMiddleware = (middleware) => {
15
- return middleware;
37
+ return typeof middleware === 'function' ? middleware : middleware.func;
16
38
  };
17
39
  /**
18
40
  * Factory function for creating middleware factories
package/dist/utils.d.ts CHANGED
@@ -2,4 +2,4 @@ import { Logger } from './services/logger.js';
2
2
  export declare const closeSessionServices: (logger: Logger, sessionServices: Record<string, any>) => Promise<void>;
3
3
  export declare const createWeakUID: () => string;
4
4
  export declare const isSerializable: (data: any) => boolean;
5
- export declare const freezeDedupe: <T extends Function>(arr?: readonly T[] | T[] | undefined) => readonly T[];
5
+ export declare const freezeDedupe: <T>(arr?: readonly T[] | T[] | undefined) => readonly T[];
@@ -45,9 +45,9 @@ export const processMessageHandlers = (services, channelConfig, channelHandler)
45
45
  channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`);
46
46
  return;
47
47
  }
48
- const { pikkuFuncName, middleware: inheritedMiddleware } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
49
- const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
50
- const middleware = typeof onMessage === 'function' ? [] : onMessage.middleware;
48
+ const { pikkuFuncName, middleware: inheritedMiddleware, permissions: inheritedPermissions, } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
49
+ const wirePermissions = typeof onMessage === 'function' ? undefined : onMessage.permissions;
50
+ const wireMiddleware = typeof onMessage === 'function' ? [] : onMessage.middleware;
51
51
  return await runPikkuFunc(PikkuWiringTypes.channel, channelConfig.name, pikkuFuncName, {
52
52
  singletonServices: services,
53
53
  getAllServices: () => ({
@@ -56,9 +56,10 @@ export const processMessageHandlers = (services, channelConfig, channelHandler)
56
56
  }),
57
57
  data: () => data,
58
58
  userSession: services.userSession,
59
- permissions,
60
59
  inheritedMiddleware,
61
- wireMiddleware: middleware,
60
+ wireMiddleware,
61
+ inheritedPermissions,
62
+ wirePermissions,
62
63
  tags: channelConfig.tags,
63
64
  interaction: { channel: channelHandler.getChannel() },
64
65
  });
@@ -1,10 +1,12 @@
1
+ import { CorePikkuPermission } from '../../function/functions.types.js';
1
2
  import { UserSessionService } from '../../services/user-session-service.js';
3
+ import { CorePikkuMiddleware } from '../../types/core.types.js';
2
4
  import { ChannelMeta, CoreChannel, RunChannelOptions, RunChannelParams } from './channel.types.js';
3
5
  /**
4
6
  * Adds a channel and registers all functions referenced in it using the
5
7
  * function names already stored in the channel metadata
6
8
  */
7
- export declare const wireChannel: <In, Channel extends string, ChannelFunction, ChannelFunctionSessionless, PikkuPermission>(channel: CoreChannel<In, Channel, ChannelFunction, PikkuPermission>) => void;
9
+ export declare const wireChannel: <In, Channel extends string, PikkuPermission extends CorePikkuPermission<In>, PikkuMiddleware extends CorePikkuMiddleware, ChannelFunction>(channel: CoreChannel<In, Channel, PikkuPermission, PikkuMiddleware, ChannelFunction>) => void;
8
10
  export declare const openChannel: ({ route, singletonServices, coerceDataFromSchema, request, }: Pick<CoreChannel<unknown, string>, "route"> & RunChannelParams<unknown> & {
9
11
  userSession: UserSessionService<any>;
10
12
  } & RunChannelOptions) => Promise<{
@@ -17,24 +17,15 @@ export const wireChannel = (channel) => {
17
17
  pikkuState('channel', 'channels').set(channel.name, channel);
18
18
  // Register onConnect function if provided
19
19
  if (channel.onConnect && channelMeta.connect) {
20
- addFunction(channelMeta.connect.pikkuFuncName, {
21
- func: channel.onConnect,
22
- });
20
+ addFunction(channelMeta.connect.pikkuFuncName, channel.onConnect);
23
21
  }
24
22
  // Register onDisconnect function if provided
25
23
  if (channel.onDisconnect && channelMeta.disconnect) {
26
- addFunction(channelMeta.disconnect.pikkuFuncName, {
27
- func: channel.onDisconnect,
28
- });
24
+ addFunction(channelMeta.disconnect.pikkuFuncName, channel.onDisconnect);
29
25
  }
30
26
  // Register onMessage function if provided
31
27
  if (channel.onMessage && channelMeta.message?.pikkuFuncName) {
32
- const messageFunc = typeof channel.onMessage === 'function'
33
- ? channel.onMessage
34
- : channel.onMessage.func;
35
- addFunction(channelMeta.message.pikkuFuncName, {
36
- func: messageFunc,
37
- });
28
+ addFunction(channelMeta.message.pikkuFuncName, channel.onMessage);
38
29
  }
39
30
  // Register functions in onMessageWiring
40
31
  if (channel.onMessageWiring && channelMeta.messageWirings) {
@@ -48,16 +39,8 @@ export const wireChannel = (channel) => {
48
39
  const wiringMeta = channelWirings[wiringKey];
49
40
  if (!wiringMeta)
50
41
  return;
51
- // Extract the function from the handler
52
- const wiringFunc = typeof handler === 'function'
53
- ? { func: handler }
54
- : {
55
- func: handler.func,
56
- auth: handler.auth,
57
- permissions: handler.permissions,
58
- };
59
42
  // Register the function using the pikku name from metadata
60
- addFunction(wiringMeta.pikkuFuncName, wiringFunc);
43
+ addFunction(wiringMeta.pikkuFuncName, handler);
61
44
  });
62
45
  });
63
46
  }
@@ -1,7 +1,7 @@
1
1
  import { PikkuError } from '../../errors/error-handler.js';
2
2
  import { HTTPFunctionMetaInputTypes, PikkuHTTPRequest, PikkuHTTPResponse } from '../http/http.types.js';
3
- import { PikkuDocs, CoreSingletonServices, CreateSessionServices, CorePikkuMiddleware, MiddlewareMetadata } from '../../types/core.types.js';
4
- import { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission } from '../../function/functions.types.js';
3
+ import { PikkuDocs, CoreSingletonServices, CreateSessionServices, CorePikkuMiddleware, MiddlewareMetadata, PermissionMetadata } from '../../types/core.types.js';
4
+ import { CorePermissionGroup, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuFunctionSessionless, CorePikkuPermission } from '../../function/functions.types.js';
5
5
  export type RunChannelOptions = Partial<{
6
6
  skipUserSession: boolean;
7
7
  respondWith404: boolean;
@@ -20,6 +20,7 @@ export interface ChannelMessageMeta {
20
20
  pikkuFuncName: string;
21
21
  docs?: PikkuDocs;
22
22
  middleware?: MiddlewareMetadata[];
23
+ permissions?: PermissionMetadata[];
23
24
  }
24
25
  export interface ChannelMeta {
25
26
  name: string;
@@ -35,25 +36,23 @@ export interface ChannelMeta {
35
36
  docs?: PikkuDocs;
36
37
  tags?: string[];
37
38
  middleware?: MiddlewareMetadata[];
39
+ permissions?: PermissionMetadata[];
38
40
  }
39
41
  export type ChannelsMeta = Record<string, ChannelMeta>;
40
- export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = CorePikkuFunction<void, unknown, ChannelData> | CorePikkuFunctionSessionless<void, unknown, ChannelData>, ChannelDisconnect = CorePikkuFunction<void, void, ChannelData> | CorePikkuFunctionSessionless<void, void, ChannelData>, ChannelFunctionMessage = CorePikkuFunction<unknown, unknown, ChannelData> | CorePikkuFunctionSessionless<unknown, unknown, ChannelData>, PikkuPermission = CorePikkuPermission<ChannelData>, PikkuMiddleware = CorePikkuMiddleware> = {
42
+ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = CorePikkuFunctionConfig<CorePikkuFunction<void, unknown, ChannelData> | CorePikkuFunctionSessionless<void, unknown, ChannelData>, CorePikkuPermission<void>, CorePikkuMiddleware>, ChannelDisconnect = CorePikkuFunctionConfig<CorePikkuFunction<void, void, ChannelData> | CorePikkuFunctionSessionless<void, void, ChannelData>, CorePikkuPermission<void>, CorePikkuMiddleware>, ChannelFunctionMessage = CorePikkuFunctionConfig<CorePikkuFunction<unknown, unknown, ChannelData> | CorePikkuFunctionSessionless<unknown, unknown, ChannelData>, CorePikkuPermission<unknown>, CorePikkuMiddleware>, PikkuPermission = CorePikkuPermission<ChannelData>, PikkuMiddleware = CorePikkuMiddleware> = {
41
43
  name: string;
42
44
  route: Channel;
43
45
  onConnect?: ChannelConnect;
44
46
  onDisconnect?: ChannelDisconnect;
45
- onMessage?: {
46
- func: ChannelFunctionMessage;
47
- permissions?: Record<string, PikkuPermission[] | PikkuPermission>;
48
- auth?: boolean;
49
- } | ChannelFunctionMessage;
47
+ onMessage?: ChannelFunctionMessage;
50
48
  onMessageWiring?: Record<string, Record<string, ChannelFunctionMessage | {
51
49
  func: ChannelFunctionMessage;
52
- permissions?: Record<string, PikkuPermission[] | PikkuPermission>;
50
+ permissions?: CorePermissionGroup<PikkuPermission>;
53
51
  auth?: boolean;
52
+ middleware?: PikkuMiddleware[];
54
53
  }>>;
55
54
  middleware?: PikkuMiddleware[];
56
- permissions?: Record<string, PikkuPermission[] | PikkuPermission>;
55
+ permissions?: CorePermissionGroup<PikkuPermission>;
57
56
  auth?: boolean;
58
57
  docs?: Partial<{
59
58
  description: string;
@@ -63,11 +62,11 @@ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = Co
63
62
  }>;
64
63
  tags?: string[];
65
64
  };
66
- export interface PikkuChannel<OpeningData, Out> {
65
+ export interface PikkuChannel<OpeningData, out Out> {
67
66
  channelId: string;
68
67
  openingData: OpeningData;
69
- send: (data: Out, isBinary?: boolean) => Promise<void> | void;
70
- close: () => Promise<void> | void;
68
+ send(data: Out, isBinary?: boolean): Promise<void> | void;
69
+ close(): Promise<void> | void;
71
70
  state: 'initial' | 'open' | 'closed';
72
71
  }
73
72
  export interface PikkuChannelHandler<OpeningData = unknown, Out = unknown> {
@@ -12,8 +12,11 @@ const defaultJSONRenderer = (_services, data) => {
12
12
  */
13
13
  export async function executeCLIViaChannel({ programName, pikkuWS, args = process.argv.slice(2), renderers = {}, defaultRenderer, }) {
14
14
  // Get CLI metadata from state
15
- const allCLIMeta = pikkuState('cli', 'meta') || {};
16
- const programMeta = allCLIMeta[programName];
15
+ const allCLIMeta = pikkuState('cli', 'meta');
16
+ if (!allCLIMeta) {
17
+ throw new Error('[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/errors/pku342 for more information.');
18
+ }
19
+ const programMeta = allCLIMeta.programs[programName];
17
20
  if (!programMeta) {
18
21
  console.error(`Error: CLI program "${programName}" not found`);
19
22
  process.exit(1);
@@ -1,6 +1,14 @@
1
1
  import { CorePikkuMiddleware, CoreUserSession } from '../../types/core.types.js';
2
2
  import { CoreCLI, CorePikkuCLIRender, CoreCLICommandConfig } from './cli.types.js';
3
3
  import type { CoreSingletonServices, CoreServices, CreateSessionServices, CreateConfig, CreateSingletonServices } from '../../types/core.types.js';
4
+ /**
5
+ * CLI command execution error - thrown when CLI execution fails
6
+ * Should be caught by the wrapper to call process.exit()
7
+ */
8
+ export declare class CLIError extends Error {
9
+ exitCode: number;
10
+ constructor(message: string, exitCode?: number);
11
+ }
4
12
  /**
5
13
  * Registers a CLI command tree and all its functions
6
14
  */
@@ -22,6 +30,8 @@ export declare const pikkuCLIRender: <Data, Services extends CoreSingletonServic
22
30
  /**
23
31
  * Execute a CLI program with the given arguments
24
32
  * This is the main entry point for CLI programs
33
+ *
34
+ * @throws {CLIError} When CLI execution fails - should be caught by wrapper to call process.exit()
25
35
  */
26
36
  export declare function executeCLI({ programName, args, createConfig, createSingletonServices, createSessionServices, }: {
27
37
  programName: string;
@@ -7,6 +7,18 @@ import { rpcService } from '../rpc/rpc-runner.js';
7
7
  import { PikkuUserSessionService } from '../../services/user-session-service.js';
8
8
  import { LocalVariablesService } from '../../services/local-variables.js';
9
9
  import { generateCommandHelp, parseCLIArguments } from './command-parser.js';
10
+ /**
11
+ * CLI command execution error - thrown when CLI execution fails
12
+ * Should be caught by the wrapper to call process.exit()
13
+ */
14
+ export class CLIError extends Error {
15
+ exitCode;
16
+ constructor(message, exitCode = 1) {
17
+ super(message);
18
+ this.exitCode = exitCode;
19
+ this.name = 'CLIError';
20
+ }
21
+ }
10
22
  /**
11
23
  * Default JSON renderer for CLI output
12
24
  */
@@ -19,7 +31,7 @@ const defaultJSONRenderer = (_services, data) => {
19
31
  export const wireCLI = (cli) => {
20
32
  // Get the existing metadata that was generated during inspection
21
33
  const cliMeta = pikkuState('cli', 'meta') || {};
22
- if (!cliMeta[cli.program]) {
34
+ if (!cliMeta.programs?.[cli.program]) {
23
35
  throw new Error(`CLI metadata not found for program '${cli.program}'. Did you run 'pikku all'?`);
24
36
  }
25
37
  // Get existing programs state and add this program
@@ -63,7 +75,7 @@ function unwrapFunc(command) {
63
75
  */
64
76
  function registerCLICommands(commands, path = [], inheritedOptions = {}, program) {
65
77
  // Get the CLI metadata to find actual function names
66
- const cliMeta = pikkuState('cli', 'meta')[program];
78
+ const cliMeta = pikkuState('cli', 'meta').programs[program];
67
79
  for (const [name, command] of Object.entries(commands)) {
68
80
  const fullPath = [...path, name];
69
81
  const commandId = fullPath.join('.');
@@ -148,7 +160,7 @@ function pluckCLIData(mergedData, funcName, availableOptions) {
148
160
  export async function runCLICommand({ program, commandPath, data, singletonServices, createSessionServices, }) {
149
161
  // Get the command metadata to find the function name
150
162
  const cliMeta = pikkuState('cli', 'meta');
151
- const programMeta = cliMeta[program];
163
+ const programMeta = cliMeta.programs?.[program];
152
164
  if (!programMeta) {
153
165
  throw new NotFoundError(`Program not found: ${program}`);
154
166
  }
@@ -225,13 +237,6 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
225
237
  channel,
226
238
  }, interaction, false);
227
239
  };
228
- // Build inherited middleware from tags
229
- const inheritedMiddleware = [];
230
- if (programData?.tags) {
231
- for (const tag of programData.tags) {
232
- inheritedMiddleware.push({ type: 'tag', tag });
233
- }
234
- }
235
240
  try {
236
241
  const result = await runPikkuFunc(PikkuWiringTypes.cli, commandId, funcName, {
237
242
  singletonServices,
@@ -239,8 +244,11 @@ export async function runCLICommand({ program, commandPath, data, singletonServi
239
244
  data: pluckedData,
240
245
  auth: false,
241
246
  userSession,
242
- inheritedMiddleware,
247
+ inheritedMiddleware: currentCommand.middleware,
243
248
  wireMiddleware: allWireMiddleware,
249
+ inheritedPermissions: currentCommand.permissions,
250
+ wirePermissions: undefined,
251
+ tags: programData?.tags,
244
252
  interaction,
245
253
  });
246
254
  // Apply renderer one final time with the final output (if renderer exists)
@@ -267,20 +275,31 @@ export const pikkuCLIRender = (renderer) => {
267
275
  /**
268
276
  * Execute a CLI program with the given arguments
269
277
  * This is the main entry point for CLI programs
278
+ *
279
+ * @throws {CLIError} When CLI execution fails - should be caught by wrapper to call process.exit()
270
280
  */
271
- export async function executeCLI({ programName, args = process.argv.slice(2), createConfig, createSingletonServices, createSessionServices, }) {
281
+ export async function executeCLI({ programName, args, createConfig, createSingletonServices, createSessionServices, }) {
282
+ if (!args) {
283
+ throw new Error('CLI arguments are required, this is to satisfy release diffs');
284
+ }
272
285
  try {
273
286
  // Get CLI metadata from state
274
- const allCLIMeta = pikkuState('cli', 'meta') || {};
275
- const programMeta = allCLIMeta[programName];
287
+ const allCLIMeta = pikkuState('cli', 'meta');
288
+ if (!allCLIMeta) {
289
+ throw new Error('[PKU342] CLI metadata not found. No CLI wirings were registered. See https://pikku.dev/errors/pku342 for more information.');
290
+ }
291
+ const programMeta = allCLIMeta.programs[programName];
276
292
  if (!programMeta) {
277
- console.error(`Error: CLI program "${programName}" not found`);
278
- process.exit(1);
293
+ throw new CLIError(`CLI program "${programName}" not found`, 1);
279
294
  }
280
295
  // Parse arguments for this specific program
281
296
  const parsed = parseCLIArguments(args, programName, allCLIMeta);
282
297
  // Handle help (check after parsing to support subcommand help)
283
- if (args.includes('--help') || args.includes('-h') || args.length === 0) {
298
+ // Show help if --help/-h is present, or if no args AND no default command
299
+ const shouldShowHelp = args.includes('--help') ||
300
+ args.includes('-h') ||
301
+ (args.length === 0 && parsed.commandPath.length === 0);
302
+ if (shouldShowHelp) {
284
303
  const helpText = generateCommandHelp(programName, allCLIMeta, parsed.commandPath);
285
304
  console.log(helpText);
286
305
  return;
@@ -293,13 +312,13 @@ export async function executeCLI({ programName, args = process.argv.slice(2), cr
293
312
  // Show help instead of error for unknown commands
294
313
  const helpText = generateCommandHelp(programName, allCLIMeta, parsed.commandPath);
295
314
  console.log(helpText);
296
- process.exit(1);
315
+ throw new CLIError('Unknown command', 1);
297
316
  }
298
317
  else {
299
318
  // Show errors for other types of errors
300
319
  console.error('Errors:');
301
320
  parsed.errors.forEach((error) => console.error(` ${error}`));
302
- process.exit(1);
321
+ throw new CLIError(parsed.errors.join('\n'), 1);
303
322
  }
304
323
  }
305
324
  // Merge positionals and options into single data object
@@ -318,11 +337,16 @@ export async function executeCLI({ programName, args = process.argv.slice(2), cr
318
337
  });
319
338
  }
320
339
  catch (error) {
340
+ // Re-throw CLIError as-is
341
+ if (error instanceof CLIError) {
342
+ throw error;
343
+ }
344
+ // Wrap other errors in CLIError
321
345
  console.error('Error:', error);
322
346
  // Show stack trace in verbose mode
323
347
  if (args.includes('--verbose') || args.includes('-v')) {
324
348
  console.error('Stack trace:', error.stack);
325
349
  }
326
- process.exit(1);
350
+ throw new CLIError(error.message || String(error), 1);
327
351
  }
328
352
  }