@pikku/inspector 0.12.42 → 0.12.43

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 (102) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/dist/add/add-addon-bans.js +1 -0
  3. package/dist/add/add-ai-agent.d.ts +3 -1
  4. package/dist/add/add-ai-agent.js +82 -0
  5. package/dist/add/add-credential.js +3 -1
  6. package/dist/add/add-functions.js +10 -9
  7. package/dist/add/add-gateway.js +3 -0
  8. package/dist/add/add-http-route.js +2 -2
  9. package/dist/add/add-mcp-prompt.js +0 -1
  10. package/dist/add/add-mcp-resource.js +0 -1
  11. package/dist/add/add-permission.d.ts +1 -1
  12. package/dist/add/add-permission.js +3 -177
  13. package/dist/add/add-queue-worker.js +3 -0
  14. package/dist/add/add-schedule.js +3 -0
  15. package/dist/add/add-scope.d.ts +2 -0
  16. package/dist/add/add-scope.js +146 -0
  17. package/dist/add/add-trigger.js +3 -0
  18. package/dist/add/add-wire-remote-addon.d.ts +10 -0
  19. package/dist/add/add-wire-remote-addon.js +66 -0
  20. package/dist/add/add-workflow-graph.js +32 -3
  21. package/dist/error-codes.d.ts +3 -0
  22. package/dist/error-codes.js +4 -0
  23. package/dist/inspector.js +11 -5
  24. package/dist/types.d.ts +19 -13
  25. package/dist/utils/custom-types-generator.js +2 -1
  26. package/dist/utils/ensure-function-metadata.d.ts +15 -0
  27. package/dist/utils/ensure-function-metadata.js +24 -0
  28. package/dist/utils/filter-inspector-state.js +9 -0
  29. package/dist/utils/get-property-value.d.ts +7 -2
  30. package/dist/utils/get-property-value.js +48 -1
  31. package/dist/utils/load-addon-functions-meta.js +81 -6
  32. package/dist/utils/permissions.d.ts +1 -21
  33. package/dist/utils/permissions.js +17 -108
  34. package/dist/utils/post-process.d.ts +30 -1
  35. package/dist/utils/post-process.js +150 -72
  36. package/dist/utils/serialize-inspector-state.d.ts +13 -8
  37. package/dist/utils/serialize-inspector-state.js +12 -6
  38. package/dist/utils/serialize-permissions-groups-meta.d.ts +0 -2
  39. package/dist/utils/serialize-permissions-groups-meta.js +0 -26
  40. package/dist/utils/workflow/derive-workflow-plan.js +9 -3
  41. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +56 -1
  42. package/dist/utils/workflow/dsl/patterns.d.ts +4 -0
  43. package/dist/utils/workflow/dsl/patterns.js +11 -0
  44. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +14 -0
  45. package/dist/utils/workflow/graph/finalize-workflows.d.ts +7 -0
  46. package/dist/utils/workflow/graph/finalize-workflows.js +11 -2
  47. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +2 -0
  48. package/dist/utils/workflow/graph/serialize-workflow-graph.js +4 -0
  49. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +6 -2
  50. package/dist/visit.js +4 -0
  51. package/package.json +3 -3
  52. package/src/add/add-addon-bans.ts +1 -0
  53. package/src/add/add-ai-agent.test.ts +87 -0
  54. package/src/add/add-ai-agent.ts +122 -0
  55. package/src/add/add-credential.ts +6 -0
  56. package/src/add/add-functions.ts +10 -12
  57. package/src/add/add-gateway.ts +11 -0
  58. package/src/add/add-http-route.ts +2 -8
  59. package/src/add/add-mcp-prompt.ts +0 -1
  60. package/src/add/add-mcp-resource.ts +0 -1
  61. package/src/add/add-permission.ts +4 -242
  62. package/src/add/add-queue-worker.ts +11 -0
  63. package/src/add/add-schedule.ts +11 -0
  64. package/src/add/add-scope.test.ts +346 -0
  65. package/src/add/add-scope.ts +225 -0
  66. package/src/add/add-trigger.ts +11 -0
  67. package/src/add/add-wire-remote-addon.ts +77 -0
  68. package/src/add/add-workflow-graph-input.test.ts +94 -0
  69. package/src/add/add-workflow-graph-notes.test.ts +74 -0
  70. package/src/add/add-workflow-graph.ts +35 -3
  71. package/src/add/inline-wiring-function.test.ts +104 -0
  72. package/src/add/validate-workflow-graph-addons.test.ts +102 -0
  73. package/src/error-codes.ts +5 -0
  74. package/src/inspector.ts +14 -4
  75. package/src/types.ts +16 -17
  76. package/src/utils/custom-types-generator.test.ts +26 -1
  77. package/src/utils/custom-types-generator.ts +2 -1
  78. package/src/utils/ensure-function-metadata.ts +42 -0
  79. package/src/utils/filter-inspector-state.test.ts +0 -2
  80. package/src/utils/filter-inspector-state.ts +9 -0
  81. package/src/utils/get-property-value.test.ts +141 -0
  82. package/src/utils/get-property-value.ts +62 -1
  83. package/src/utils/load-addon-functions-meta.test.ts +157 -0
  84. package/src/utils/load-addon-functions-meta.ts +94 -6
  85. package/src/utils/load-addon-scopes.test.ts +138 -0
  86. package/src/utils/permissions.test.ts +7 -232
  87. package/src/utils/permissions.ts +20 -160
  88. package/src/utils/post-process.test.ts +269 -4
  89. package/src/utils/post-process.ts +216 -95
  90. package/src/utils/serialize-inspector-state.ts +22 -25
  91. package/src/utils/serialize-permissions-groups-meta.ts +0 -28
  92. package/src/utils/workflow/derive-workflow-plan.test.ts +41 -3
  93. package/src/utils/workflow/derive-workflow-plan.ts +11 -4
  94. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +66 -0
  95. package/src/utils/workflow/dsl/patterns.ts +18 -0
  96. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +15 -0
  97. package/src/utils/workflow/graph/finalize-workflows.test.ts +50 -0
  98. package/src/utils/workflow/graph/finalize-workflows.ts +13 -2
  99. package/src/utils/workflow/graph/serialize-workflow-graph.ts +6 -0
  100. package/src/utils/workflow/graph/workflow-graph.types.ts +8 -0
  101. package/src/visit.ts +4 -0
  102. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,101 @@
1
+ ## 0.12.43
2
+
3
+ ### Patch Changes
4
+
5
+ - c478794: Simplify authorization to be session + function based (#972). Permissions are now function-scoped only: global permissions AND together, a function's own permissions OR together, and the two are independent gates that both must pass — a broad global can no longer satisfy an admin-only function. Removed wire-, tag-, and HTTP-route-level permissions (`addTagPermission`, `addHTTPPermission`, wire-level `permissions` on HTTP/channel/MCP wirings). Tags are now organizational only. `auth` (session presence) and tag/HTTP middleware are unchanged.
6
+ - b714fd4: Merge an addon's credential meta into the consuming app during inspection, the
7
+ same way addon secrets and variables are already merged. Without this, a
8
+ credential declared by an addon (e.g. an OAuth2 integration) never reached the
9
+ app's `CREDENTIAL_OAUTH2_CONFIGS`, so the `credential-oauth` provider and the
10
+ credential service could not resolve it — the addon's Connect flow and status
11
+ silently no-op'd. Addon credentials are added as fallbacks: an app-declared
12
+ credential of the same name still wins.
13
+ - cb079cc: `pikkuAIAgent` gains a `workflows: []` capability: a referenced workflow is exposed to the LLM as a tool that runs inline and returns its output.
14
+ - 13474a6: Generate a `ScopeId` union from `wireScope` declarations.
15
+
16
+ `pikku all` now emits `.pikku/scopes/pikku-scopes.gen.ts` with a `ScopeId` union
17
+ of every declared scope, plus a wildcard form for each node that has
18
+ descendants. A project's generated `pikkuFunc` narrows `scopes` to that union,
19
+ so an undeclared scope is a compile error with editor autocomplete:
20
+
21
+ ```ts
22
+ wireScope({ admin: { scopes: { invoices: { scopes: { create: {} } } } } })
23
+
24
+ pikkuFunc({
25
+ scopes: ['admin:invoices:create'], // ✓ autocompleted
26
+ func: ...,
27
+ })
28
+
29
+ pikkuFunc({
30
+ scopes: ['admin:invoice:create'], // ✗ compile error (typo)
31
+ func: ...,
32
+ })
33
+ ```
34
+
35
+ The inspector independently rejects undeclared scopes, so a cast that defeats
36
+ the compiler is still caught at build time.
37
+
38
+ Also fixes `getArrayPropertyValue` dropping any array behind a cast — idiomatic
39
+ `tags: ['a'] as const` was previously invisible to the inspector and silently
40
+ omitted from meta.
41
+
42
+ - ca0d14f: Apply `credentialOverrides` when merging an addon's credentials into the consuming app, mirroring the existing `secretOverrides`/`variableOverrides` handling. Previously the credentials merge ignored overrides and always registered the addon's logical credential name, so a second instance of the same package (`wireAddon` with a `credentialOverrides` map) failed `validateCredentialOverrides` and both instances shared one OAuth provider. Now each override's resolved name is provisioned as its own credential — and since the credential name doubles as the better-auth providerId, two instances surface two distinct providers instead of a shared account pool.
43
+ - 13474a6: feat: propagate an addon's declared scopes to the host
44
+
45
+ An addon can now declare scopes with `wireScope`, and a host that wires it picks
46
+ them up: they merge into the host's `ScopeId` union and its declared set, so a
47
+ host function can require an addon scope and the `pikku_scopes` foreign key
48
+ accepts granting one. This mirrors how addon secrets and variables are loaded.
49
+
50
+ The generated `pikku-scopes.gen.ts` now imports its metadata sidecar and derives
51
+ `SCOPES` from it, rather than inlining the list. TypeScript only emits a `.json`
52
+ into the build output when something imports it, and an addon publishes only
53
+ that output — without the import, an addon's scopes never reached a host.
54
+
55
+ - cb079cc: `pikkuWorkflowGraph` nodes accept an optional `notes?: string` and the graph an optional `notes?: string[]`; notes are documentation only and excluded from `graphHash`.
56
+ - cb079cc: Fix two corpus type-check failures: n8n `graph:sort`/`graph:summarize` enum rows now emit `as const`, and the inspector's `sanitizeTypeName` prefixes an underscore when a name starts with a digit.
57
+ - 8601505: Make `wireCredential` the single source of truth for an addon's OAuth2 config: `pikku-credentials.gen.ts` exports `CREDENTIAL_OAUTH2_CONFIGS`, generated services import from it, the OpenAPI importer emits a `wireCredential`, and the inspector now extracts `oauth2.additionalParams`.
58
+ - 70fa400: Add outgoing webhooks — `webhookService.send()` enqueues signed deliveries onto a retrying queue, `@pikku/kysely`'s `KyselyWebhookService` persists per-attempt delivery history, and `@pikku/console` gains a read-only `/webhooks` page; also caches resolved secrets in `TypedSecretService` and registers inline-`func` metadata for queue/scheduler/trigger/gateway wirings.
59
+ - 3c75366: Key `secretOverrides`/`variableOverrides` on the secretId/variableId (the string the addon actually reads by — its typed map is keyed by id, e.g. `getSecret('MAILGUN_CREDENTIALS')`), not the logical meta name. The runtime aliaser already keys on the id, but the inspector merge + validation keyed on the logical name, so a correctly-keyed override failed validation and never provisioned its target whenever an addon's logical name differed from its id (the common case — `mailgun`/`MAILGUN_CREDENTIALS`). The existing test masked it by using a secret whose name equalled its id. The merge now resolves and provisions by id (with a name-fallback for older meta), validation checks ids, and the console install codegen generates overrides keyed by id.
60
+ - 7b2ea23: `wireAddon` can install one addon package as multiple named instances, each with its own per-instance singleton services and `secretOverrides`/`variableOverrides`/`credentialOverrides` that alias logical names to real project secrets/variables/credentials.
61
+ - 13474a6: Extract `wireScope` declarations and validate scope references.
62
+
63
+ Functions referencing a scope that no `wireScope` declares now fail the build
64
+ with an `INVALID_VALUE` critical listing the available scopes, so a typo like
65
+ `admin:invoice:create` is caught at codegen rather than at runtime.
66
+
67
+ `wireScope` declarations wrapped in a cast (`as const`, `as any`) are unwrapped
68
+ before extraction rather than being silently skipped.
69
+
70
+ - d2a6eea: Add `wireRemoteAddon` — consume a hosted addon's `remote: true` RPCs transparently over HTTP, with the addon installed as a devDependency (types only). `rpc('ns:fn', input)` dispatches to the host's `/remote/rpc/:rpcName`, authenticating as a client with a token bound from a local source (`{ credentialId }` per-user, `{ secretId }` platform, or a custom `resolve()`), or omitted for a public surface. This is any-machine → hosted-library client auth, distinct from the trusted mesh (`PIKKU_REMOTE_SECRET`). A new `.remote.gen.d.ts` RPC map exposes only the `remote: true` surface to consumers. `pikku` verify errors if a `wireRemoteAddon` package is a production dependency (or missing) instead of a devDependency, and if a bound `credentialId`/`secretId` isn't wired.
71
+ - 30e62ee: Add `workflow.approval(reason, { schema, expiry })` — a return-valued, expiring human-in-the-loop gate that stays closed until a decision is recorded (via `workflowService.approveStep` or `POST /workflow/:workflowName/approve/:runId`), unlike the one-shot `workflow.suspend()`.
72
+ - Updated dependencies [7ab5287]
73
+ - Updated dependencies [e86bc17]
74
+ - Updated dependencies [a9b96a0]
75
+ - Updated dependencies [3f7fc54]
76
+ - Updated dependencies [c478794]
77
+ - Updated dependencies [3f04ae4]
78
+ - Updated dependencies [90d9f04]
79
+ - Updated dependencies [cb079cc]
80
+ - Updated dependencies [cb079cc]
81
+ - Updated dependencies [0a7db82]
82
+ - Updated dependencies [981c4db]
83
+ - Updated dependencies [13474a6]
84
+ - Updated dependencies [5a2b0d5]
85
+ - Updated dependencies [13474a6]
86
+ - Updated dependencies [ee040dc]
87
+ - Updated dependencies [cb079cc]
88
+ - Updated dependencies [13474a6]
89
+ - Updated dependencies [9f0d0eb]
90
+ - Updated dependencies [13474a6]
91
+ - Updated dependencies [70fa400]
92
+ - Updated dependencies [7b2ea23]
93
+ - Updated dependencies [1dc77d5]
94
+ - Updated dependencies [416606c]
95
+ - Updated dependencies [d2a6eea]
96
+ - Updated dependencies [30e62ee]
97
+ - @pikku/core@0.12.64
98
+
1
99
  ## 0.12.42
2
100
 
3
101
  ### Patch Changes
@@ -7,6 +7,7 @@ import { ErrorCode } from '../error-codes.js';
7
7
  */
8
8
  const BANNED_WIRINGS = new Set([
9
9
  'wireAddon',
10
+ 'wireRemoteAddon',
10
11
  'wireChannel',
11
12
  'wireCLI',
12
13
  'wireGateway',
@@ -1,2 +1,4 @@
1
- import type { AddWiring } from '../types.js';
1
+ import * as ts from 'typescript';
2
+ import type { AddWiring, InspectorLogger } from '../types.js';
3
+ export declare function resolveWorkflowReferences(obj: ts.ObjectLiteralExpression, checker: ts.TypeChecker, agentName: string, logger: InspectorLogger): string[] | null;
2
4
  export declare const addAIAgent: AddWiring;
@@ -83,6 +83,81 @@ function resolveAgentReferences(obj, checker, agentName, logger) {
83
83
  }
84
84
  return resolved.length > 0 ? resolved : null;
85
85
  }
86
+ export function resolveWorkflowReferences(obj, checker, agentName, logger) {
87
+ const property = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
88
+ ts.isIdentifier(p.name) &&
89
+ p.name.text === 'workflows');
90
+ if (!property || !ts.isPropertyAssignment(property)) {
91
+ return null;
92
+ }
93
+ const initializer = property.initializer;
94
+ if (!ts.isArrayLiteralExpression(initializer)) {
95
+ return null;
96
+ }
97
+ const resolved = [];
98
+ for (const element of initializer.elements) {
99
+ if (ts.isStringLiteral(element)) {
100
+ logger.critical(ErrorCode.INVALID_VALUE, `AI agent '${agentName}' workflows array contains a string literal '${element.text}'. ` +
101
+ `Use a workflow reference instead (e.g., ref('<workflowName>') or import the pikkuWorkflowGraph variable).`);
102
+ continue;
103
+ }
104
+ if (ts.isCallExpression(element) && ts.isIdentifier(element.expression)) {
105
+ const calleeName = element.expression.text;
106
+ if (calleeName === 'ref' || calleeName === 'workflow') {
107
+ const [firstArg] = element.arguments;
108
+ if (firstArg && ts.isStringLiteral(firstArg)) {
109
+ resolved.push(firstArg.text);
110
+ continue;
111
+ }
112
+ }
113
+ }
114
+ if (ts.isIdentifier(element)) {
115
+ const name = resolveIdentifierToWorkflowName(element, checker);
116
+ if (name) {
117
+ resolved.push(name);
118
+ continue;
119
+ }
120
+ logger.critical(ErrorCode.INVALID_VALUE, `AI agent '${agentName}' workflows array contains identifier '${element.text}' ` +
121
+ `that could not be resolved to a pikkuWorkflowGraph.`);
122
+ }
123
+ }
124
+ return resolved.length > 0 ? resolved : null;
125
+ }
126
+ function resolveIdentifierToWorkflowName(identifier, checker) {
127
+ const symbol = checker.getSymbolAtLocation(identifier);
128
+ if (!symbol)
129
+ return null;
130
+ let resolved = symbol;
131
+ if (resolved.flags & ts.SymbolFlags.Alias) {
132
+ resolved = checker.getAliasedSymbol(resolved) ?? resolved;
133
+ }
134
+ const decl = resolved.valueDeclaration ?? resolved.declarations?.[0];
135
+ if (!decl)
136
+ return null;
137
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
138
+ if (ts.isCallExpression(decl.initializer) &&
139
+ ts.isIdentifier(decl.initializer.expression) &&
140
+ (decl.initializer.expression.text === 'pikkuWorkflowGraph' ||
141
+ decl.initializer.expression.text === 'pikkuWorkflow' ||
142
+ decl.initializer.expression.text === 'pikkuWorkflowComplex')) {
143
+ const firstArg = decl.initializer.arguments[0];
144
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
145
+ for (const prop of firstArg.properties) {
146
+ if (ts.isPropertyAssignment(prop) &&
147
+ ts.isIdentifier(prop.name) &&
148
+ prop.name.text === 'name' &&
149
+ ts.isStringLiteral(prop.initializer)) {
150
+ return prop.initializer.text;
151
+ }
152
+ }
153
+ }
154
+ if (ts.isIdentifier(decl.name)) {
155
+ return decl.name.text;
156
+ }
157
+ }
158
+ }
159
+ return null;
160
+ }
86
161
  function resolveIdentifierToRpcName(identifier, checker) {
87
162
  const symbol = checker.getSymbolAtLocation(identifier);
88
163
  if (!symbol)
@@ -181,6 +256,12 @@ export const addAIAgent = (logger, node, checker, state, options) => {
181
256
  }
182
257
  }
183
258
  const agentsValue = resolveAgentReferences(obj, checker, nameValue || '', logger);
259
+ const workflowsValue = resolveWorkflowReferences(obj, checker, nameValue || '', logger);
260
+ if (workflowsValue) {
261
+ for (const workflowName of workflowsValue) {
262
+ state.workflows.invokedWorkflows.add(workflowName);
263
+ }
264
+ }
184
265
  if (!nameValue) {
185
266
  logger.critical(ErrorCode.MISSING_NAME, "AI agent is missing the required 'name' property.");
186
267
  return;
@@ -307,6 +388,7 @@ export const addAIAgent = (logger, node, checker, state, options) => {
307
388
  }),
308
389
  ...(toolsValue !== null && { tools: toolsValue }),
309
390
  ...(agentsValue !== null && { agents: agentsValue }),
391
+ ...(workflowsValue !== null && { workflows: workflowsValue }),
310
392
  tags,
311
393
  inputSchema,
312
394
  outputSchema,
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue, getArrayPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
2
+ import { getPropertyValue, getArrayPropertyValue, getRecordPropertyValue, assertStringLiteralProperty, } from '../utils/get-property-value.js';
3
3
  import { ErrorCode } from '../error-codes.js';
4
4
  import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
5
5
  export const addCredential = (logger, node, checker, state, _options) => {
@@ -81,6 +81,7 @@ export const addCredential = (logger, node, checker, state, _options) => {
81
81
  const tokenUrl = getPropertyValue(oauth2Obj, 'tokenUrl');
82
82
  const scopes = getArrayPropertyValue(oauth2Obj, 'scopes');
83
83
  const pkce = getPropertyValue(oauth2Obj, 'pkce');
84
+ const additionalParams = getRecordPropertyValue(oauth2Obj, 'additionalParams');
84
85
  if (appCredentialSecretId && authorizationUrl && tokenUrl && scopes) {
85
86
  oauth2 = {
86
87
  appCredentialSecretId,
@@ -89,6 +90,7 @@ export const addCredential = (logger, node, checker, state, _options) => {
89
90
  tokenUrl,
90
91
  scopes,
91
92
  pkce: pkce || undefined,
93
+ additionalParams: additionalParams || undefined,
92
94
  };
93
95
  }
94
96
  }
@@ -4,7 +4,7 @@ import { extractFunctionName, funcIdToTypeName, } from '../utils/extract-functio
4
4
  import { extractFunctionNode } from '../utils/extract-function-node.js';
5
5
  import { extractUsedWires } from '../utils/extract-services.js';
6
6
  import { formatVersionedId, parseVersionedId } from '@pikku/core';
7
- import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
7
+ import { getPropertyValue, getArrayPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
8
8
  import { canonicalJSON, hashString } from '../utils/hash.js';
9
9
  import { resolveMiddleware } from '../utils/middleware.js';
10
10
  import { resolvePermissions } from '../utils/permissions.js';
@@ -304,6 +304,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
304
304
  let workflowQueued;
305
305
  let workflowRetries;
306
306
  let workflowTimeout;
307
+ let scopes;
307
308
  let version;
308
309
  let objectNode;
309
310
  let nodeDisplayName = null;
@@ -374,6 +375,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
374
375
  workflowQueued = getPropertyValue(firstArg, 'workflowQueued');
375
376
  workflowRetries = getPropertyValue(firstArg, 'workflowRetries');
376
377
  workflowTimeout = getPropertyValue(firstArg, 'workflowTimeout');
378
+ scopes = getArrayPropertyValue(firstArg, 'scopes') ?? undefined;
377
379
  // Extract approvalDescription identifier reference
378
380
  for (const prop of firstArg.properties) {
379
381
  if (ts.isPropertyAssignment(prop) &&
@@ -735,13 +737,6 @@ export const addFunctions = (logger, node, checker, state, options) => {
735
737
  if (newMiddleware.length > 0) {
736
738
  middleware = [...(middleware || []), ...newMiddleware];
737
739
  }
738
- const existingPermissionTags = new Set((permissions || [])
739
- .filter((p) => p.type === 'tag')
740
- .map((p) => p.tag));
741
- const newPermissions = tagEntries.filter((e) => !existingPermissionTags.has(e.tag));
742
- if (newPermissions.length > 0) {
743
- permissions = [...(permissions || []), ...newPermissions];
744
- }
745
740
  }
746
741
  const implementationHash = computeImplementationHash({
747
742
  wrapper: expression.text,
@@ -786,6 +781,7 @@ export const addFunctions = (logger, node, checker, state, options) => {
786
781
  workflowQueued: workflowQueued === true ? true : undefined,
787
782
  workflowRetries: workflowRetries ?? undefined,
788
783
  workflowTimeout: workflowTimeout ?? undefined,
784
+ scopes: scopes ?? undefined,
789
785
  implementationHash,
790
786
  version,
791
787
  title,
@@ -836,7 +832,6 @@ export const addFunctions = (logger, node, checker, state, options) => {
836
832
  inputSchema: inputNames[0] ?? null,
837
833
  outputSchema: outputNames[0] ?? null,
838
834
  middleware,
839
- permissions,
840
835
  };
841
836
  state.serviceAggregation.usedFunctions.add(pikkuFuncId);
842
837
  extractWireNames(middleware).forEach((n) => state.serviceAggregation.usedMiddleware.add(n));
@@ -870,6 +865,12 @@ export const addFunctions = (logger, node, checker, state, options) => {
870
865
  }
871
866
  if (remote) {
872
867
  state.rpc.invokedFunctions.add(pikkuFuncId);
868
+ // The consumer-facing surface a wireRemoteAddon imports (mirrors exposedMeta)
869
+ state.rpc.remoteMeta[name] = pikkuFuncId;
870
+ state.rpc.remoteFiles.set(name, {
871
+ path: node.getSourceFile().fileName,
872
+ exportedName,
873
+ });
873
874
  }
874
875
  if (expose) {
875
876
  state.rpc.exposedMeta[name] = pikkuFuncId;
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
2
2
  import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
+ import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
5
6
  import { resolveMiddleware } from '../utils/middleware.js';
6
7
  import { extractWireNames } from '../utils/post-process.js';
7
8
  import { resolveAddonName } from '../utils/resolve-addon-package.js';
@@ -44,6 +45,8 @@ export const addGateway = (logger, node, checker, state, _options) => {
44
45
  if (!nameValue || !typeValue) {
45
46
  return;
46
47
  }
48
+ // Register metadata for a func inlined into the wiring (see helper).
49
+ ensureInlineWiringFunction(state, pikkuFuncId, nameValue, funcInitializer, checker, extracted.isHelper);
47
50
  const middleware = resolveMiddleware(state, obj, tags, checker);
48
51
  state.serviceAggregation.usedFunctions.add(pikkuFuncId);
49
52
  extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
@@ -4,7 +4,7 @@ import { pathToRegexp } from 'path-to-regexp';
4
4
  import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
5
5
  import { getPropertyAssignmentInitializer, extractTypeKeys, } from '../utils/type-utils.js';
6
6
  import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
7
- import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js';
7
+ import { resolvePermissions } from '../utils/permissions.js';
8
8
  import { extractWireNames } from '../utils/post-process.js';
9
9
  import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
10
10
  import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
@@ -219,7 +219,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
219
219
  // Resolve middleware
220
220
  const middleware = resolveHTTPMiddlewareFromObject(state, fullRoute, obj, tags, checker);
221
221
  // Resolve permissions
222
- const permissions = resolveHTTPPermissionsFromObject(state, fullRoute, obj, tags, checker);
222
+ const permissions = resolvePermissions(state, obj, tags, checker);
223
223
  state.serviceAggregation.usedFunctions.add(funcName);
224
224
  if (refAddonTarget) {
225
225
  state.serviceAggregation.usedFunctions.add(refAddonTarget);
@@ -83,7 +83,6 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
83
83
  outputSchema,
84
84
  arguments: [], // Will be populated by CLI during serialization
85
85
  middleware,
86
- permissions,
87
86
  };
88
87
  }
89
88
  };
@@ -93,7 +93,6 @@ export const addMCPResource = (logger, node, checker, state, options) => {
93
93
  inputSchema,
94
94
  outputSchema,
95
95
  middleware,
96
- permissions,
97
96
  };
98
97
  }
99
98
  };
@@ -1,5 +1,5 @@
1
1
  import type { AddWiring } from '../types.js';
2
2
  /**
3
- * Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
3
+ * Inspect pikkuPermission, pikkuAuth, and pikkuPermissionFactory definitions.
4
4
  */
5
5
  export declare const addPermission: AddWiring;
@@ -1,34 +1,14 @@
1
1
  import * as ts from 'typescript';
2
- import { extractFunctionName, isNamedExport, makeContextBasedId, } from '../utils/extract-function-name.js';
2
+ import { extractFunctionName } from '../utils/extract-function-name.js';
3
3
  import { extractServicesFromFunction, extractUsedWires, } from '../utils/extract-services.js';
4
- import { extractPermissionPikkuNames } from '../utils/permissions.js';
5
4
  import { getPropertyValue } from '../utils/get-property-value.js';
6
5
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
- function renameTempDefinitions(state, definitionIds, groupType, groupKey) {
8
- const tempIndices = definitionIds
9
- .map((name, i) => (name.startsWith('__temp_') ? i : -1))
10
- .filter((i) => i >= 0);
11
- for (const idx of tempIndices) {
12
- const oldId = definitionIds[idx];
13
- const newId = tempIndices.length === 1
14
- ? makeContextBasedId(groupType, groupKey)
15
- : makeContextBasedId(groupType, groupKey, String(idx));
16
- const existing = state.permissions.definitions[oldId];
17
- if (existing) {
18
- delete state.permissions.definitions[oldId];
19
- state.permissions.definitions[newId] = existing;
20
- }
21
- definitionIds[idx] = newId;
22
- }
23
- }
24
6
  function isInsidePermissionContainer(node) {
25
7
  let current = node.parent;
26
8
  while (current) {
27
9
  if (ts.isCallExpression(current) &&
28
10
  ts.isIdentifier(current.expression) &&
29
- (current.expression.text === 'pikkuPermissionFactory' ||
30
- current.expression.text === 'addTagPermission' ||
31
- current.expression.text === 'addHTTPPermission')) {
11
+ current.expression.text === 'pikkuPermissionFactory') {
32
12
  return true;
33
13
  }
34
14
  current = current.parent;
@@ -36,7 +16,7 @@ function isInsidePermissionContainer(node) {
36
16
  return false;
37
17
  }
38
18
  /**
39
- * Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
19
+ * Inspect pikkuPermission, pikkuAuth, and pikkuPermissionFactory definitions.
40
20
  */
41
21
  export const addPermission = (logger, node, checker, state) => {
42
22
  if (!ts.isCallExpression(node))
@@ -249,158 +229,4 @@ export const addPermission = (logger, node, checker, state) => {
249
229
  logger.debug(`• Found permission factory with services: ${services.services.join(', ')}`);
250
230
  return;
251
231
  }
252
- // Handle addPermission('tag', [permission1, permission2])
253
- // Supports two patterns:
254
- // 1. export const x = () => addTagPermission('tag', [...]) (factory - tree-shakeable)
255
- // 2. export const x = addTagPermission('tag', [...]) (direct - no tree-shaking)
256
- if (expression.text === 'addTagPermission') {
257
- const tagArg = args[0];
258
- const permissionsArrayArg = args[1];
259
- if (!tagArg || !permissionsArrayArg)
260
- return;
261
- // Extract tag name
262
- let tag;
263
- if (ts.isStringLiteral(tagArg)) {
264
- tag = tagArg.text;
265
- }
266
- if (!tag) {
267
- logger.warn(`• addTagPermission call without valid tag string`);
268
- return;
269
- }
270
- // Check if permissions is a literal array or object
271
- if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
272
- !ts.isObjectLiteralExpression(permissionsArrayArg)) {
273
- logger.error(`• addTagPermission('${tag}', ...) must have a literal array or object as second argument`);
274
- return;
275
- }
276
- // Extract permission pikkuFuncIds from array
277
- const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
278
- if (permissionNames.length > 0) {
279
- renameTempDefinitions(state, permissionNames, 'tag', tag);
280
- }
281
- const allServices = new Set();
282
- for (const permissionName of permissionNames) {
283
- const permissionMeta = state.permissions.definitions[permissionName];
284
- if (permissionMeta && permissionMeta.services) {
285
- for (const service of permissionMeta.services.services) {
286
- allServices.add(service);
287
- }
288
- }
289
- }
290
- let isFactory = false;
291
- let exportedName = null;
292
- let parent = node.parent;
293
- if (parent && ts.isArrowFunction(parent)) {
294
- if (parent.parameters.length === 0) {
295
- isFactory = true;
296
- const arrowParent = parent.parent;
297
- if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
298
- if (ts.isIdentifier(arrowParent.name)) {
299
- if (isNamedExport(arrowParent)) {
300
- exportedName = arrowParent.name.text;
301
- }
302
- }
303
- }
304
- }
305
- }
306
- if (!isFactory) {
307
- const extracted = extractFunctionName(node, checker, state.rootDir);
308
- exportedName = extracted.exportedName;
309
- }
310
- if (!isFactory && exportedName) {
311
- logger.warn(`• Permission group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
312
- `For tree-shaking, use: export const ${exportedName} = () => addTagPermission('${tag}', [...])`);
313
- }
314
- state.permissions.tagPermissions.set(tag, {
315
- exportName: exportedName,
316
- sourceFile: node.getSourceFile().fileName,
317
- position: node.getStart(),
318
- services: {
319
- optimized: false,
320
- services: Array.from(allServices),
321
- },
322
- count: permissionNames.length,
323
- instanceIds: permissionNames,
324
- isFactory,
325
- });
326
- logger.debug(`• Found tag permission group: ${tag} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
327
- return;
328
- }
329
- // Handle addHTTPPermission(pattern, [permission1, permission2])
330
- // Supports two patterns:
331
- // 1. export const x = () => addHTTPPermission('*', [...]) (factory - tree-shakeable)
332
- // 2. export const x = addHTTPPermission('*', [...]) (direct - no tree-shaking)
333
- if (expression.text === 'addHTTPPermission') {
334
- const patternArg = args[0];
335
- const permissionsArrayArg = args[1];
336
- if (!patternArg || !permissionsArrayArg)
337
- return;
338
- // Extract route pattern
339
- let pattern;
340
- if (ts.isStringLiteral(patternArg)) {
341
- pattern = patternArg.text;
342
- }
343
- if (!pattern) {
344
- logger.warn(`• addHTTPPermission call without valid pattern string`);
345
- return;
346
- }
347
- // Check if permissions is a literal array or object
348
- if (!ts.isArrayLiteralExpression(permissionsArrayArg) &&
349
- !ts.isObjectLiteralExpression(permissionsArrayArg)) {
350
- logger.error(`• addHTTPPermission('${pattern}', ...) must have a literal array or object as second argument`);
351
- return;
352
- }
353
- // Extract permission pikkuFuncIds from array
354
- const permissionNames = extractPermissionPikkuNames(permissionsArrayArg, checker, state.rootDir);
355
- if (permissionNames.length > 0) {
356
- renameTempDefinitions(state, permissionNames, 'http', pattern);
357
- }
358
- const allServices = new Set();
359
- for (const permissionName of permissionNames) {
360
- const permissionMeta = state.permissions.definitions[permissionName];
361
- if (permissionMeta && permissionMeta.services) {
362
- for (const service of permissionMeta.services.services) {
363
- allServices.add(service);
364
- }
365
- }
366
- }
367
- let isFactory = false;
368
- let exportedName = null;
369
- let parent = node.parent;
370
- if (parent && ts.isArrowFunction(parent)) {
371
- if (parent.parameters.length === 0) {
372
- isFactory = true;
373
- const arrowParent = parent.parent;
374
- if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
375
- if (ts.isIdentifier(arrowParent.name)) {
376
- if (isNamedExport(arrowParent)) {
377
- exportedName = arrowParent.name.text;
378
- }
379
- }
380
- }
381
- }
382
- }
383
- if (!isFactory) {
384
- const extracted = extractFunctionName(node, checker, state.rootDir);
385
- exportedName = extracted.exportedName;
386
- }
387
- if (!isFactory && exportedName) {
388
- logger.warn(`• HTTP permission group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
389
- `For tree-shaking, use: export const ${exportedName} = () => addHTTPPermission('${pattern}', [...])`);
390
- }
391
- state.http.routePermissions.set(pattern, {
392
- exportName: exportedName,
393
- sourceFile: node.getSourceFile().fileName,
394
- position: node.getStart(),
395
- services: {
396
- optimized: false,
397
- services: Array.from(allServices),
398
- },
399
- count: permissionNames.length,
400
- instanceIds: permissionNames,
401
- isFactory,
402
- });
403
- logger.debug(`• Found HTTP route permission group: ${pattern} -> [${permissionNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
404
- return;
405
- }
406
232
  };
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
2
2
  import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
+ import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
5
6
  import { resolveMiddleware } from '../utils/middleware.js';
6
7
  import { extractWireNames } from '../utils/post-process.js';
7
8
  import { resolveAddonName } from '../utils/resolve-addon-package.js';
@@ -44,6 +45,8 @@ export const addQueueWorker = (logger, node, checker, state) => {
44
45
  logger.critical(ErrorCode.MISSING_QUEUE_NAME, `No 'name' provided for queue processor function '${pikkuFuncId}'.`);
45
46
  return;
46
47
  }
48
+ // Register metadata for a func inlined into the wiring (see helper).
49
+ ensureInlineWiringFunction(state, pikkuFuncId, name, funcInitializer, checker, extracted.isHelper);
47
50
  // --- resolve middleware ---
48
51
  const middleware = resolveMiddleware(state, obj, tags, checker);
49
52
  // --- track used functions/middleware for service aggregation ---
@@ -2,6 +2,7 @@ import * as ts from 'typescript';
2
2
  import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
3
3
  import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
+ import { ensureInlineWiringFunction } from '../utils/ensure-function-metadata.js';
5
6
  import { resolveMiddleware } from '../utils/middleware.js';
6
7
  import { extractWireNames } from '../utils/post-process.js';
7
8
  import { resolveAddonName } from '../utils/resolve-addon-package.js';
@@ -43,6 +44,8 @@ export const addSchedule = (logger, node, checker, state, options) => {
43
44
  if (!nameValue || !scheduleValue) {
44
45
  return;
45
46
  }
47
+ // Register metadata for a func inlined into the wiring (see helper).
48
+ ensureInlineWiringFunction(state, pikkuFuncId, nameValue, funcInitializer, checker, extracted.isHelper);
46
49
  // --- resolve middleware ---
47
50
  const middleware = resolveMiddleware(state, obj, tags, checker);
48
51
  // --- track used functions/middleware for service aggregation ---
@@ -0,0 +1,2 @@
1
+ import type { AddWiring } from '../types.js';
2
+ export declare const addScope: AddWiring;