@pikku/cli 0.12.20 → 0.12.21

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 (99) hide show
  1. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  2. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  3. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  4. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  5. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  6. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  7. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +10 -1
  9. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  13. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  14. package/dist/.pikku/function/pikku-function-types.gen.d.ts +8 -2
  15. package/dist/.pikku/function/pikku-function-types.gen.js +4 -1
  16. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  17. package/dist/.pikku/function/pikku-functions-meta.gen.json +276 -188
  18. package/dist/.pikku/function/pikku-functions.gen.js +9 -9
  19. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  20. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  21. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  22. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  23. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  24. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  25. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  26. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  27. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  28. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  29. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  30. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  31. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-types.gen.js +1 -1
  33. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  34. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  35. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  36. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +11 -7
  37. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  38. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  39. package/dist/.pikku/schemas/register.gen.js +15 -11
  40. package/dist/.pikku/schemas/schemas/PikkuCommandHTTPOutput.schema.json +1 -0
  41. package/dist/.pikku/schemas/schemas/PikkuCommandQueueOutput.schema.json +1 -0
  42. package/dist/.pikku/schemas/schemas/WorkflowRunStatus.schema.json +1 -1
  43. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  44. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  45. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  46. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  47. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  48. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  49. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  50. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  51. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  52. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  53. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +9 -9
  54. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  55. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  56. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  57. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  58. package/dist/src/cli.wiring.js +12 -2
  59. package/dist/src/deploy/analyzer/manifest.d.ts +10 -0
  60. package/dist/src/deploy/build-pipeline.d.ts +2 -0
  61. package/dist/src/deploy/build-pipeline.js +44 -6
  62. package/dist/src/deploy/bundler/bundler.d.ts +2 -1
  63. package/dist/src/deploy/bundler/bundler.js +28 -5
  64. package/dist/src/deploy/bundler/dep-extractor.d.ts +5 -2
  65. package/dist/src/deploy/bundler/dep-extractor.js +103 -23
  66. package/dist/src/deploy/bundler/types.d.ts +5 -1
  67. package/dist/src/deploy/codegen/per-unit-codegen.d.ts +3 -1
  68. package/dist/src/deploy/codegen/per-unit-codegen.js +3 -1
  69. package/dist/src/deploy/plan/planner.js +25 -3
  70. package/dist/src/deploy/plan/provider.d.ts +2 -0
  71. package/dist/src/functions/commands/deploy-apply.js +6 -4
  72. package/dist/src/functions/commands/deploy-plan.js +7 -1
  73. package/dist/src/functions/commands/pikku-command-summary.js +4 -1
  74. package/dist/src/functions/commands/versions-update.js +4 -2
  75. package/dist/src/functions/wirings/channels/pikku-command-channels.d.ts +1 -1
  76. package/dist/src/functions/wirings/channels/pikku-command-channels.js +1 -1
  77. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +5 -1
  78. package/dist/src/functions/wirings/functions/serialize-function-types.js +31 -1
  79. package/dist/src/functions/wirings/http/pikku-command-http-routes.d.ts +1 -1
  80. package/dist/src/functions/wirings/http/pikku-command-http-routes.js +1 -1
  81. package/dist/src/functions/wirings/queue/pikku-command-queue-map.d.ts +1 -1
  82. package/dist/src/functions/wirings/queue/pikku-command-queue-map.js +1 -1
  83. package/dist/src/functions/wirings/queue/pikku-command-queue.d.ts +1 -1
  84. package/dist/src/functions/wirings/queue/pikku-command-queue.js +1 -1
  85. package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +5 -1
  86. package/dist/src/functions/wirings/rpc/pikku-command-remote-rpc.js +5 -1
  87. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +16 -2
  88. package/dist/src/functions/wirings/workflow/serialize-workflow-routes.js +42 -0
  89. package/dist/src/functions/workflows/all.workflow.js +4 -4
  90. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  91. package/dist/src/scaffold/workflow-routes.gen.js +33 -1
  92. package/dist/src/services/cli-logger.service.d.ts +22 -2
  93. package/dist/src/services/cli-logger.service.js +97 -21
  94. package/dist/src/services.js +8 -1
  95. package/dist/tsconfig.tsbuildinfo +1 -1
  96. package/package.json +3 -3
  97. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +0 -10
  98. package/dist/.pikku/cli/pikku-cli-client.gen.js +0 -44
  99. /package/dist/.pikku/schemas/schemas/{PikkuChannelsOutput.schema.json → PikkuCommandChannelsOutput.schema.json} +0 -0
@@ -12,7 +12,7 @@ export const serializeFunctionTypes = (userSessionTypeImport, userSessionTypeNam
12
12
  * Core function, middleware, and permission types for all wirings
13
13
  */
14
14
 
15
- import type { CorePikkuMiddleware, CorePermissionGroup, PikkuWire, PickRequired } from '@pikku/core'
15
+ import type { CorePikkuMiddleware, CorePermissionGroup, ListInput, ListOutput, PikkuWire, PickRequired } from '@pikku/core'
16
16
  import type { CorePikkuFunctionConfig, CorePikkuAuth, CorePikkuAuthConfig, CorePikkuPermission } from '@pikku/core/function'
17
17
  import { pikkuAuth as pikkuAuthCore } from '@pikku/core/function'
18
18
  import { addMiddleware as addMiddlewareCore, addPermission as addPermissionCore } from '@pikku/core/middleware'
@@ -338,10 +338,13 @@ export type PikkuFunctionConfigWithSchema<
338
338
  RequiredWires extends keyof PikkuWire = never
339
339
  > = {
340
340
  name?: string
341
+ description?: string
341
342
  tags?: string[]
342
343
  expose?: boolean
343
344
  mcp?: boolean
344
345
  internal?: boolean
346
+ remote?: boolean
347
+ deploy?: 'serverless' | 'server' | 'auto'
345
348
  approvalRequired?: boolean
346
349
  approvalDescription?: InputSchema extends StandardSchemaV1 ? PikkuApprovalDescription<InferSchemaOutput<InputSchema>> : never
347
350
  func: PikkuFunction<
@@ -415,6 +418,32 @@ export function pikkuFunc(func: any) {
415
418
  return typeof func === 'function' ? { func } : func
416
419
  }
417
420
 
421
+ export type PikkuListFunction<
422
+ F extends Record<string, unknown> = {},
423
+ Row = unknown,
424
+ S extends string = never
425
+ > =
426
+ | PikkuFunction<ListInput<F, S>, ListOutput<Row>, 'session' | 'rpc'>
427
+ | PikkuFunctionSessionless<
428
+ ListInput<F, S>,
429
+ ListOutput<Row>,
430
+ 'session' | 'rpc'
431
+ >
432
+
433
+ export const pikkuListFunc = <
434
+ F extends Record<string, unknown> = {},
435
+ Row = unknown,
436
+ S extends string = never
437
+ >(
438
+ config: PikkuFunctionConfig<
439
+ ListInput<F, S>,
440
+ ListOutput<Row>,
441
+ 'session' | 'rpc'
442
+ >
443
+ ): PikkuFunctionConfig<ListInput<F, S>, ListOutput<Row>, 'session' | 'rpc'> => {
444
+ return pikkuFunc(config)
445
+ }
446
+
418
447
  /**
419
448
  * Configuration object for sessionless Pikku functions with Zod schema validation.
420
449
  */
@@ -430,6 +459,7 @@ export type PikkuFunctionSessionlessConfigWithSchema<
430
459
  mcp?: boolean
431
460
  internal?: boolean
432
461
  remote?: boolean
462
+ deploy?: 'serverless' | 'server' | 'auto'
433
463
  approvalRequired?: boolean
434
464
  approvalDescription?: InputSchema extends StandardSchemaV1 ? PikkuApprovalDescription<InferSchemaOutput<InputSchema>> : never
435
465
  func: PikkuFunctionSessionless<
@@ -1 +1 @@
1
- export declare const pikkuHTTP: 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>;
1
+ export declare const pikkuCommandHTTP: 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>;
@@ -4,7 +4,7 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
6
6
  import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
7
- export const pikkuHTTP = pikkuSessionlessFunc({
7
+ export const pikkuCommandHTTP = pikkuSessionlessFunc({
8
8
  func: async ({ logger, config, getInspectorState }) => {
9
9
  const visitState = await getInspectorState();
10
10
  const { httpWiringsFile, httpWiringMetaFile, httpWiringMetaJsonFile, packageMappings, schema, } = config;
@@ -1 +1 @@
1
- export declare const pikkuQueueMap: import("#pikku").PikkuFunctionConfig<void, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
1
+ export declare const pikkuCommandQueueMap: import("#pikku").PikkuFunctionConfig<void, void, "session" | "rpc", import("#pikku").PikkuFunctionSessionless<void, void, "session" | "rpc", import("#pikku").Services> | import("#pikku").PikkuFunction<void, void, "session" | "rpc", import("#pikku").Services>, undefined, undefined>;
@@ -2,7 +2,7 @@ import { pikkuSessionlessFunc } from '#pikku';
2
2
  import { writeFileInDir } from '../../../utils/file-writer.js';
3
3
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
4
4
  import { serializeQueueMap } from './serialize-queue-map.js';
5
- export const pikkuQueueMap = pikkuSessionlessFunc({
5
+ export const pikkuCommandQueueMap = pikkuSessionlessFunc({
6
6
  func: async ({ logger, config, getInspectorState }) => {
7
7
  const { queueWorkers, functions, resolvedIOTypes } = await getInspectorState();
8
8
  const { queueMapDeclarationFile, packageMappings } = config;
@@ -1 +1 @@
1
- export declare const pikkuQueue: 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>;
1
+ export declare const pikkuCommandQueue: 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>;
@@ -5,7 +5,7 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
5
5
  import { serializeQueueMeta, serializeQueueMetaTS, } from './serialize-queue-meta.js';
6
6
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
7
7
  import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
8
- export const pikkuQueue = pikkuSessionlessFunc({
8
+ export const pikkuCommandQueue = pikkuSessionlessFunc({
9
9
  func: async ({ logger, config, getInspectorState }) => {
10
10
  const visitState = await getInspectorState();
11
11
  const { queueWorkersWiringFile, queueWorkersWiringMetaFile, queueWorkersWiringMetaJsonFile, packageMappings, schema, } = config;
@@ -4,7 +4,11 @@ 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
6
  export const pikkuPublicRPC = pikkuSessionlessFunc({
7
- func: async ({ logger, config }) => {
7
+ func: async ({ logger, config, variables }) => {
8
+ const deployCodegenFlag = await variables.get('PIKKU_DEPLOY_CODEGEN');
9
+ if (deployCodegenFlag === '1') {
10
+ return false;
11
+ }
8
12
  if (config.scaffold?.rpc) {
9
13
  const pathToPikkuTypes = getFileImportRelativePath(config.publicRpcFile, config.typesDeclarationFile, config.packageMappings);
10
14
  await writeFileInDir(logger, config.publicRpcFile, serializePublicRPC(pathToPikkuTypes, config.scaffold.rpc === 'auth', config.globalHTTPPrefix || ''));
@@ -4,7 +4,11 @@ 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
6
  export const pikkuRemoteRPC = pikkuSessionlessFunc({
7
- func: async ({ logger, config }) => {
7
+ func: async ({ logger, config, variables }) => {
8
+ const deployCodegenFlag = await variables.get('PIKKU_DEPLOY_CODEGEN');
9
+ if (deployCodegenFlag === '1') {
10
+ return false;
11
+ }
8
12
  if (config.remoteRpcWorkersFile) {
9
13
  const pathToPikkuTypes = getFileImportRelativePath(config.remoteRpcWorkersFile, config.typesDeclarationFile, config.packageMappings);
10
14
  await writeFileInDir(logger, config.remoteRpcWorkersFile, serializeRemoteRPC(pathToPikkuTypes));
@@ -41,13 +41,13 @@ ${addonImports}
41
41
  ${mergedRPCMap}
42
42
 
43
43
  export type RPCInvoke = <Name extends keyof FlattenedRPCMap>(
44
- ...args: FlattenedRPCMap[Name]['input'] extends void | null
44
+ ...args: IsVoidishInput<FlattenedRPCMap[Name]['input']> extends true
45
45
  ? [name: Name]
46
46
  : [name: Name, data: FlattenedRPCMap[Name]['input']]
47
47
  ) => Promise<FlattenedRPCMap[Name]['output']>
48
48
 
49
49
  export type RPCRemote = <Name extends keyof FlattenedRPCMap>(
50
- ...args: FlattenedRPCMap[Name]['input'] extends void | null
50
+ ...args: IsVoidishInput<FlattenedRPCMap[Name]['input']> extends true
51
51
  ? [name: Name]
52
52
  : [name: Name, data: FlattenedRPCMap[Name]['input']]
53
53
  ) => Promise<FlattenedRPCMap[Name]['output']>
@@ -117,6 +117,13 @@ function generateMergedRPCMap(wireAddonDeclarations) {
117
117
  return `
118
118
  // No addon packages, use RPCMap directly
119
119
  export type FlattenedRPCMap = RPCMap
120
+
121
+ type IsAny<T> = 0 extends (1 & T) ? true : false
122
+ type IsVoidishInput<T> = IsAny<T> extends true
123
+ ? false
124
+ : [T] extends [void | null | undefined]
125
+ ? true
126
+ : false
120
127
  `;
121
128
  }
122
129
  // TypeScript utility to flatten namespaced RPC maps
@@ -131,6 +138,13 @@ export type FlattenedRPCMap =
131
138
  RPCMap${Array.from(wireAddonDeclarations.keys())
132
139
  .map((namespace) => ` & PrefixKeys<${toPascalCase(namespace)}RPCMap, '${namespace}'>`)
133
140
  .join('')}
141
+
142
+ type IsAny<T> = 0 extends (1 & T) ? true : false
143
+ type IsVoidishInput<T> = IsAny<T> extends true
144
+ ? false
145
+ : [T] extends [void | null | undefined]
146
+ ? true
147
+ : false
134
148
  `;
135
149
  return utilityTypes;
136
150
  }
@@ -68,6 +68,7 @@ export const workflowStatusStream = pikkuSessionlessFunc<
68
68
 
69
69
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
70
70
  let lastHash = ''
71
+ let initSent = false
71
72
 
72
73
  const poll = async () => {
73
74
  const run = await workflowRunService.getRun(runId)
@@ -78,6 +79,26 @@ export const workflowStatusStream = pikkuSessionlessFunc<
78
79
 
79
80
  const steps = await workflowRunService.getRunSteps(runId)
80
81
 
82
+ if (!initSent && run.deterministic) {
83
+ const statusByStep = new Map(
84
+ steps.map((s: { stepName: string; status: string }) => [
85
+ s.stepName,
86
+ s.status,
87
+ ])
88
+ )
89
+ channel.send({
90
+ type: 'init',
91
+ deterministic: true,
92
+ steps: (run.plannedSteps ?? []).map(
93
+ (s: { stepName: string }) => ({
94
+ stepName: s.stepName,
95
+ status: statusByStep.get(s.stepName) ?? 'pending',
96
+ })
97
+ ),
98
+ })
99
+ initSent = true
100
+ }
101
+
81
102
  const hash = JSON.stringify({
82
103
  s: run.status,
83
104
  steps: steps.map((s: { stepName: string; status: string }) => [s.stepName, s.status]),
@@ -133,6 +154,7 @@ export const workflowStatusStreamFull = pikkuSessionlessFunc<
133
154
 
134
155
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
135
156
  let lastHash = ''
157
+ let initSent = false
136
158
 
137
159
  const poll = async () => {
138
160
  const run = await workflowRunService.getRun(runId)
@@ -143,6 +165,26 @@ export const workflowStatusStreamFull = pikkuSessionlessFunc<
143
165
 
144
166
  const steps = await workflowRunService.getRunSteps(runId)
145
167
 
168
+ if (!initSent && run.deterministic) {
169
+ const statusByStep = new Map(
170
+ steps.map((s: { stepName: string; status: string }) => [
171
+ s.stepName,
172
+ s.status,
173
+ ])
174
+ )
175
+ channel.send({
176
+ type: 'init',
177
+ deterministic: true,
178
+ steps: (run.plannedSteps ?? []).map(
179
+ (s: { stepName: string }) => ({
180
+ stepName: s.stepName,
181
+ status: statusByStep.get(s.stepName) ?? 'pending',
182
+ })
183
+ ),
184
+ })
185
+ initSent = true
186
+ }
187
+
146
188
  const hash = JSON.stringify({
147
189
  s: run.status,
148
190
  o: run.output,
@@ -128,7 +128,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
128
128
  }
129
129
  if (!config.addon) {
130
130
  const [http, scheduler, triggers] = await Promise.all([
131
- workflow.do('HTTP', 'pikkuHTTP', null),
131
+ workflow.do('HTTP', 'pikkuCommandHTTP', null),
132
132
  workflow.do('Scheduler', 'pikkuScheduler', null),
133
133
  workflow.do('Trigger', 'pikkuTrigger', null),
134
134
  ]);
@@ -158,15 +158,15 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
158
158
  }
159
159
  if (!config.addon) {
160
160
  const [queues, channels, gateways, mcp, cli] = await Promise.all([
161
- workflow.do('Queue', 'pikkuQueue', null),
162
- workflow.do('Channels', 'pikkuChannels', null),
161
+ workflow.do('Queue', 'pikkuCommandQueue', null),
162
+ workflow.do('Channels', 'pikkuCommandChannels', null),
163
163
  workflow.do('Gateway', 'pikkuGateway', null),
164
164
  workflow.do('MCP', 'pikkuMCP', null),
165
165
  workflow.do('CLI', 'pikkuCLI', null),
166
166
  ]);
167
167
  if (queues) {
168
168
  await Promise.all([
169
- workflow.do('Queue map', 'pikkuQueueMap', null),
169
+ workflow.do('Queue map', 'pikkuCommandQueueMap', null),
170
170
  workflow.do('Queue service', 'pikkuQueueService', null),
171
171
  ]);
172
172
  allImports.push(config.queueWorkersWiringMetaFile, config.queueWorkersWiringFile);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.20
2
+ * This file was generated by @pikku/cli@0.12.21
3
3
  */
4
4
  /**
5
5
  * Auto-generated remote internal RPC queue worker and HTTP endpoint
@@ -1,5 +1,5 @@
1
1
  /**
2
- * This file was generated by @pikku/cli@0.12.20
2
+ * This file was generated by @pikku/cli@0.12.21
3
3
  */
4
4
  /**
5
5
  * Workflow HTTP catch-all routes
@@ -50,6 +50,7 @@ export const workflowStatusStream = pikkuSessionlessFunc({
50
50
  return;
51
51
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled']);
52
52
  let lastHash = '';
53
+ let initSent = false;
53
54
  const poll = async () => {
54
55
  const run = await workflowRunService.getRun(runId);
55
56
  if (!run) {
@@ -57,6 +58,21 @@ export const workflowStatusStream = pikkuSessionlessFunc({
57
58
  return false;
58
59
  }
59
60
  const steps = await workflowRunService.getRunSteps(runId);
61
+ if (!initSent && run.deterministic) {
62
+ const statusByStep = new Map(steps.map((s) => [
63
+ s.stepName,
64
+ s.status,
65
+ ]));
66
+ channel.send({
67
+ type: 'init',
68
+ deterministic: true,
69
+ steps: (run.plannedSteps ?? []).map((s) => ({
70
+ stepName: s.stepName,
71
+ status: statusByStep.get(s.stepName) ?? 'pending',
72
+ })),
73
+ });
74
+ initSent = true;
75
+ }
60
76
  const hash = JSON.stringify({
61
77
  s: run.status,
62
78
  steps: steps.map((s) => [s.stepName, s.status]),
@@ -105,6 +121,7 @@ export const workflowStatusStreamFull = pikkuSessionlessFunc({
105
121
  return;
106
122
  const terminalStatuses = new Set(['completed', 'failed', 'cancelled']);
107
123
  let lastHash = '';
124
+ let initSent = false;
108
125
  const poll = async () => {
109
126
  const run = await workflowRunService.getRun(runId);
110
127
  if (!run) {
@@ -112,6 +129,21 @@ export const workflowStatusStreamFull = pikkuSessionlessFunc({
112
129
  return false;
113
130
  }
114
131
  const steps = await workflowRunService.getRunSteps(runId);
132
+ if (!initSent && run.deterministic) {
133
+ const statusByStep = new Map(steps.map((s) => [
134
+ s.stepName,
135
+ s.status,
136
+ ]));
137
+ channel.send({
138
+ type: 'init',
139
+ deterministic: true,
140
+ steps: (run.plannedSteps ?? []).map((s) => ({
141
+ stepName: s.stepName,
142
+ status: statusByStep.get(s.stepName) ?? 'pending',
143
+ })),
144
+ });
145
+ initSent = true;
146
+ }
115
147
  const hash = JSON.stringify({
116
148
  s: run.status,
117
149
  o: run.output,
@@ -1,26 +1,46 @@
1
1
  import type { Logger } from '@pikku/core/services';
2
2
  import { LogLevel } from '@pikku/core/services';
3
3
  import type { ErrorCode } from '@pikku/inspector';
4
+ export type CLIOutputMode = 'text' | 'json';
4
5
  export declare class CLILogger implements Logger {
5
6
  private silent;
6
7
  private level;
7
8
  private criticalErrors;
9
+ private outputMode;
10
+ private jsonFlushHookRegistered;
8
11
  constructor({ logLogo, silent, }: {
9
12
  logLogo: boolean;
10
13
  silent?: boolean;
11
14
  });
12
15
  setLevel(level: LogLevel): void;
13
16
  setSilent(silent: boolean): void;
17
+ setOutputMode(mode: CLIOutputMode): void;
18
+ getOutputMode(): CLIOutputMode;
14
19
  isSilent(): boolean;
20
+ private normalizeMessage;
21
+ private writeJSONLine;
22
+ private ensureJSONFlushHook;
23
+ flushJSONBuffer(): void;
24
+ private emit;
15
25
  info(message: string | {
16
26
  message: string;
17
27
  type?: string;
28
+ data?: Record<string, unknown>;
29
+ }): void;
30
+ error(message: string | Error | {
31
+ message: string;
32
+ type?: string;
33
+ data?: Record<string, unknown>;
34
+ }): void;
35
+ warn(message: string | {
36
+ message: string;
37
+ type?: string;
38
+ data?: Record<string, unknown>;
18
39
  }): void;
19
- error(message: string): void;
20
- warn(message: string): void;
21
40
  debug(message: string | {
22
41
  message: string;
23
42
  type?: string;
43
+ data?: Record<string, unknown>;
24
44
  }): void;
25
45
  critical(code: ErrorCode, message: string): void;
26
46
  hasCriticalErrors(): boolean;
@@ -9,10 +9,13 @@ const logo = `
9
9
  |_| |_|_| _)_| _)____/
10
10
  `;
11
11
  const BASE_ERROR_URL = 'https://pikku.dev/docs/pikku-cli/errors';
12
+ const ANSI_ESCAPE_REGEX = /\x1B\[[0-?]*[ -/]*[@-~]/g;
12
13
  export class CLILogger {
13
14
  silent;
14
15
  level = LogLevel.warn; // default to warn level
15
16
  criticalErrors = [];
17
+ outputMode = 'text';
18
+ jsonFlushHookRegistered = false;
16
19
  constructor({ logLogo, silent = false, }) {
17
20
  this.silent = silent;
18
21
  if (logLogo && !silent) {
@@ -25,49 +28,122 @@ export class CLILogger {
25
28
  setSilent(silent) {
26
29
  this.silent = silent;
27
30
  }
31
+ setOutputMode(mode) {
32
+ this.outputMode = mode;
33
+ if (mode === 'json') {
34
+ this.ensureJSONFlushHook();
35
+ }
36
+ }
37
+ getOutputMode() {
38
+ return this.outputMode;
39
+ }
28
40
  isSilent() {
29
41
  return this.silent;
30
42
  }
43
+ normalizeMessage(message) {
44
+ const str = typeof message === 'string'
45
+ ? message
46
+ : message instanceof Error
47
+ ? (message.stack ?? message.message)
48
+ : typeof message === 'object' && message !== null
49
+ ? JSON.stringify(message)
50
+ : String(message);
51
+ if (this.outputMode === 'json') {
52
+ return str.replace(ANSI_ESCAPE_REGEX, '');
53
+ }
54
+ return str;
55
+ }
56
+ writeJSONLine(payload) {
57
+ process.stdout.write(`${JSON.stringify(payload)}\n`);
58
+ }
59
+ ensureJSONFlushHook() {
60
+ if (this.jsonFlushHookRegistered)
61
+ return;
62
+ this.jsonFlushHookRegistered = true;
63
+ process.once('beforeExit', () => this.flushJSONBuffer());
64
+ process.once('exit', () => this.flushJSONBuffer());
65
+ }
66
+ // NDJSON records are written synchronously in `emit` so consumers can
67
+ // stream output; nothing is buffered. Kept as a no-op because the
68
+ // beforeExit/exit hooks installed by `ensureJSONFlushHook` still
69
+ // reference it, and external callers may rely on the signature.
70
+ flushJSONBuffer() { }
71
+ emit(level, message, type, code, data) {
72
+ const normalizedMessage = this.normalizeMessage(message);
73
+ if (this.outputMode === 'json') {
74
+ this.writeJSONLine({
75
+ level,
76
+ message: normalizedMessage,
77
+ ...(type ? { type } : {}),
78
+ ...(code ? { code } : {}),
79
+ ...(code ? { url: `${BASE_ERROR_URL}/${code.toLowerCase()}` } : {}),
80
+ ...(data ? { data } : {}),
81
+ timestamp: new Date().toISOString(),
82
+ });
83
+ return;
84
+ }
85
+ if (level === 'error') {
86
+ console.error(chalk.red(normalizedMessage));
87
+ return;
88
+ }
89
+ if (level === 'warn') {
90
+ console.error(chalk.yellow(normalizedMessage));
91
+ return;
92
+ }
93
+ if (level === 'critical') {
94
+ console.error(chalk.red.bold(normalizedMessage));
95
+ return;
96
+ }
97
+ let c = level === 'info' ? chalk.blue : chalk.gray;
98
+ if (type === 'success') {
99
+ c = chalk.green;
100
+ }
101
+ else if (type === 'timing' && level === 'info') {
102
+ c = chalk.gray;
103
+ }
104
+ console.log(c(normalizedMessage));
105
+ }
31
106
  info(message) {
32
107
  if (this.level > LogLevel.info || this.silent)
33
108
  return;
34
- let c = chalk.blue;
35
- if (typeof message === 'object') {
36
- if (message.type === 'success') {
37
- c = chalk.green;
38
- }
39
- else if (message.type === 'timing') {
40
- c = chalk.gray;
41
- }
42
- }
43
- console.log(c(typeof message === 'string' ? message : message.message));
109
+ const msg = typeof message === 'string' ? message : message.message;
110
+ const type = typeof message === 'string' ? undefined : message.type;
111
+ const data = typeof message === 'string' ? undefined : message.data;
112
+ this.emit('info', msg, type, undefined, data);
44
113
  }
45
114
  error(message) {
46
115
  if (this.level > LogLevel.error)
47
116
  return;
48
- console.error(chalk.red(message));
117
+ if (message instanceof Error) {
118
+ this.emit('error', message.stack ?? message.message);
119
+ return;
120
+ }
121
+ const msg = typeof message === 'string' ? message : message.message;
122
+ const type = typeof message === 'string' ? undefined : message.type;
123
+ const data = typeof message === 'string' ? undefined : message.data;
124
+ this.emit('error', msg, type, undefined, data);
49
125
  }
50
126
  warn(message) {
51
127
  if (this.level > LogLevel.warn)
52
128
  return;
53
- console.error(chalk.yellow(message));
129
+ const msg = typeof message === 'string' ? message : message.message;
130
+ const type = typeof message === 'string' ? undefined : message.type;
131
+ const data = typeof message === 'string' ? undefined : message.data;
132
+ this.emit('warn', msg, type, undefined, data);
54
133
  }
55
134
  debug(message) {
56
135
  if (this.level > LogLevel.debug || this.silent)
57
136
  return;
58
- let c = chalk.gray;
59
- if (typeof message === 'object') {
60
- if (message.type === 'success') {
61
- c = chalk.green;
62
- }
63
- }
64
- console.log(c(typeof message === 'string' ? message : message.message));
137
+ const msg = typeof message === 'string' ? message : message.message;
138
+ const type = typeof message === 'string' ? undefined : message.type;
139
+ const data = typeof message === 'string' ? undefined : message.data;
140
+ this.emit('debug', msg, type, undefined, data);
65
141
  }
66
142
  critical(code, message) {
67
143
  const url = `${BASE_ERROR_URL}/${code.toLowerCase()}`;
68
144
  const formattedMessage = `[${code}] ${message}\n → ${url}`;
69
145
  this.criticalErrors.push(formattedMessage);
70
- console.error(chalk.red.bold(formattedMessage));
146
+ this.emit('critical', formattedMessage, undefined, code);
71
147
  }
72
148
  hasCriticalErrors() {
73
149
  return this.criticalErrors.length > 0;
@@ -83,7 +159,7 @@ export class CLILogger {
83
159
  }
84
160
  primary(message) {
85
161
  if (!this.silent) {
86
- console.log(chalk.green(message));
162
+ this.emit('info', message);
87
163
  }
88
164
  }
89
165
  }
@@ -103,6 +103,13 @@ export const createConfig = async (_variablesService, data) => {
103
103
  // --silent > --loglevel > --verbose > --info > default (warn)
104
104
  let logLevel = LogLevel.warn; // default
105
105
  let isSilent = false;
106
+ // --output is constrained to 'text' | 'json' by the CLI parser
107
+ // (choices + default in cli.wiring.ts), so no runtime validation
108
+ // is needed here. --json is kept as an alias that forces 'json'.
109
+ const outputMode = data.json
110
+ ? 'json'
111
+ : data.output;
112
+ logger.setOutputMode(outputMode);
106
113
  if (data.silent) {
107
114
  logLevel = LogLevel.critical;
108
115
  isSilent = true;
@@ -125,7 +132,7 @@ export const createConfig = async (_variablesService, data) => {
125
132
  logger.setLevel(logLevel);
126
133
  logger.setSilent(isSilent);
127
134
  // Display logo unless in silent mode
128
- if (!isSilent) {
135
+ if (!isSilent && outputMode !== 'json') {
129
136
  logger.logLogo();
130
137
  }
131
138
  const cliConfig = await getPikkuCLIConfig(logger, data.configFile, [], true, data.outDir);